diff --git a/.circleci/ccache.conf b/.circleci/ccache.conf new file mode 100644 index 0000000000000..4e782aab1350f --- /dev/null +++ b/.circleci/ccache.conf @@ -0,0 +1,3 @@ +debug = true +debug_level = 1 +compiler_check = none diff --git a/.circleci/config.yml b/.circleci/config.yml index 1e0ad3c5ce870..8e762fe57fd27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,36 +2,32 @@ version: 2.1 orbs: win: circleci/windows@5.0 + macos: circleci/macos@2.4.1 executors: linux-node: docker: - - image: circleci/node:stretch + - image: cimg/node:24.13.1 linux-python: docker: - image: cimg/python:3.10.7 - bionic: + environment: + EMCC_CORES: "4" + ubuntu-lts: docker: - - image: emscripten/emscripten-ci + - image: emscripten/emscripten-ci:jammy environment: LANG: "C.UTF-8" EMCC_CORES: "4" EMSDK_NOTTY: "1" EMTEST_WASI_SYSROOT: "~/wasi-sdk/wasi-sysroot" - EMTEST_BUILD_VERBOSE: "2" EMTEST_DETECT_TEMPFILE_LEAKS: "1" - mac: - environment: - EMSDK_NOTTY: "1" - macos: - xcode: "13.4.1" - resource_class: macos.x86.medium.gen2 mac-arm64: environment: EMSDK_NOTTY: "1" macos: - xcode: "14.2.0" - resource_class: macos.m1.medium.gen1 + xcode: "16.4.0" + resource_class: m4pro.medium commands: download-chrome: @@ -41,12 +37,13 @@ commands: name: download chrome command: | # TODO: Make these part of the base image - apt-get install libu2f-udev libvulkan1 - # wget -O ~/chrome.deb https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb + apt-get install -q -y libu2f-udev libvulkan1 xdg-utils + wget -O ~/chrome.deb https://dl.google.com/linux/direct/google-chrome-unstable_current_amd64.deb # If that download link breaks, temporarily use this URL instead: # wget -O ~/chrome.deb https://storage.googleapis.com/webassembly/chrome/google-chrome-stable_current_amd64.deb - wget -O ~/chrome.deb https://dl.google.com/linux/direct/google-chrome-unstable_current_amd64.deb dpkg -i ~/chrome.deb + echo "Chrome version:" + /usr/bin/google-chrome --version emsdk-env: description: "emsdk_env.sh" steps: @@ -61,10 +58,6 @@ commands: description: "bootstrap" steps: - run: ./bootstrap - npm-install: - description: "npm ci" - steps: - - run: npm ci pip-install: description: "pip install" parameters: @@ -76,6 +69,15 @@ commands: - run: name: pip install command: << parameters.python >> -m pip install -r requirements-dev.txt + install-rust: + steps: + - run: + name: install rust + command: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + export PATH=${HOME}/.cargo/bin:${PATH} + rustup target add wasm32-unknown-emscripten + echo "export PATH=\"\$HOME/.cargo/bin:\$PATH\"" >> $BASH_ENV install-node-version: description: "install a specific version of node" parameters: @@ -103,17 +105,22 @@ commands: echo "if os.path.exists(V8_ENGINE[0]): JS_ENGINES.append(V8_ENGINE)" >> ~/emsdk/.emscripten cat ~/emsdk/.emscripten echo "export PATH=\"$HOME/node-v${version}-linux-x64/bin:\$PATH\"" >> $BASH_ENV - install-node-latest: - description: "install latest version of node" + install-node-oldest: + description: "install node 18.3.0 (oldest)" + steps: + - install-node-version: + # Keep this in sync with `OLDEST_SUPPORTED_NODE` in `feature_matrix.py` + node_version: "18.3.0" + install-node-lts: + description: "install node 22.21.0 (current LTS)" steps: - install-node-version: - node_version: "19.0.0" - install-node-canary: - description: "install canary version of node" + node_version: "22.21.0" + install-node-newest: + description: "install node 25.4.0 (newest)" steps: - install-node-version: - node_version: "22.0.0-v8-canary20231108ff311d5a39" - canary: true + node_version: "25.4.0" install-v8: description: "install v8 using jsvu" steps: @@ -140,6 +147,10 @@ commands: cd ~/emsdk ./emsdk install tot ./emsdk activate tot + # Write the version of clang into a file for use in the ccache key + ./upstream/bin/clang --version > clang_version.txt + echo "clang version:" + cat ~/emsdk/clang_version.txt # Remove the emsdk version of emscripten to save space in the # persistent workspace and to avoid any confusion with the version # we are trying to test. @@ -161,7 +172,6 @@ commands: cat ~/emsdk/.emscripten - emsdk-env - bootstrap - - npm-install build-libs: description: "Build all libraries" steps: @@ -170,7 +180,13 @@ commands: command: | ./emcc --clear-cache - pip-install - - run: apt-get install ninja-build + - run: apt-get install -q -y ninja-build ccache + - run: + name: Ccache stats and configuration + command: | + ccache -s + ccache --print-stats + ccache -p - run: name: embuilder build ALL command: | @@ -195,6 +211,19 @@ commands: command: | ./embuilder build MINIMAL --pic --lto ./test/runner test_hello_world + - run: + name: "Ccache stats and configuration" + command: | + ls -l ~/cache/sysroot/lib/wasm32-emscripten || echo "no build" + ls -l ~/cache/sysroot/include/emscripten/heap.h + cat ~/cache/sysroot/lib/wasm32-emscripten/crtbegin.o.ccache-log + date + cat ~/cache/build/libcompiler_rt/absvdi2.o.ccache-log + ccache -s + ccache --print-stats + ccache -p + ccache --zero-stats + persist: description: "Persist the emsdk, libraries, and engines" steps: @@ -208,6 +237,19 @@ commands: - vms/ - wasi-sdk/ - .jsvu/ + - when: + # Only the main branch will store its cache results, and only the non-main branches + # will use cached results. This prevents untrusted PRs from polluting caches used + # by other branches. + condition: + equal: [ "main", << pipeline.git.branch >> ] + steps: + - save_cache: + name: "Save Ccache cache" + paths: + - ~/.ccache + key: clang-{{ checksum "~/emsdk/clang_version.txt" }} + prepare-for-tests: description: "Setup emscripten tests" steps: @@ -215,13 +257,22 @@ commands: # Must be absolute path or relative path from working_directory at: ~/ - checkout - - run: - name: submodule update - command: git submodule update --init - emsdk-env - bootstrap - - npm-install - pip-install + - when: + # We only set EMTEST_RETRY_FLAKY on pull requests. When we run + # normal CI jobs on branches like main we still want to be able to + # detect flakyness. + condition: ${CIRCLE_PULL_REQUEST} + steps: + - set-retry-flaky-tests + remove-linux-binaries: + description: "Remove linux binaries from workspace" + steps: + - run: + name: "Remove Linux binaries" + command: rm -rf ~/emsdk ~/vms ~/.jsvu ~/wasi-sdk upload-test-results: description: "Upload test results" steps: @@ -237,16 +288,15 @@ commands: description: "Name of given test suite" type: string default: "" + extra-cflags: + description: "Extra EMCC_CFLAGS" + type: string + default: "" steps: - - when: - # We only set EMTEST_RETRY_FLAKY on pull requests. When we run - # normal CI jobs on branches like main we still want to be able to - # detect flakyness. - condition: ${CIRCLE_PULL_REQUEST} - steps: - set-retry-flaky-tests - run: name: run tests (<< parameters.title >>) + environment: + EMCC_CFLAGS: << parameters.extra-cflags >> command: | env ./test/runner << parameters.test_targets >> @@ -303,36 +353,31 @@ commands: - run: name: run tests (<< parameters.title >>) environment: - EMTEST_LACKS_SOUND_HARDWARE: "1" EMTEST_DETECT_TEMPFILE_LEAKS: "0" - # --no-sandbox because we are running as root and chrome requires - # this flag for now: https://crbug.com/638180 - CHROME_FLAGS_BASE: "--no-first-run -start-maximized --no-sandbox --use-gl=swiftshader --user-data-dir=/tmp/chrome-emscripten-profile --enable-experimental-web-platform-features" - CHROME_FLAGS_HEADLESS: "--headless=new --remote-debugging-port=1234" - CHROME_FLAGS_WASM: "--enable-experimental-webassembly-features --js-flags=\"--experimental-wasm-memory64 --experimental-wasm-stack-switching --experimental-wasm-type-reflection\"" - CHROME_FLAGS_NOCACHE: "--disk-cache-dir=/dev/null --disk-cache-size=1 --media-cache-size=1 --disable-application-cache --incognito" - command: | - export EMTEST_BROWSER="/usr/bin/google-chrome $CHROME_FLAGS_BASE $CHROME_FLAGS_HEADLESS $CHROME_FLAGS_WASM $CHROME_FLAGS_NOCACHE" + EMTEST_HEADLESS: "1" + EMTEST_BROWSER: "/usr/bin/google-chrome" + EMTEST_CORES: "2" + command: | # There are tests in the browser test suite that using libraries # that are not included by "./embuilder build ALL". For example the # PIC version of libSDL which is used by test_sdl2_misc_main_module export EM_FROZEN_CACHE="" test/runner << parameters.test_targets >> + - upload-test-results setup-macos: steps: + - macos/install-rosetta - run: name: Install brew package dependencies environment: HOMEBREW_NO_AUTO_UPDATE: "1" - command: | - brew list cmake || brew install cmake - brew list ninja || brew install ninja - brew list python3 || brew install python3 - brew list pkg-config || brew install pkg-config + # Use --force-bottle to force homebrew use binary packages avoiding + # costly build times. + command: brew install --force-bottle cmake ninja pkg-config + - attach_workspace: + at: ~/ - checkout - - run: - name: submodule update - command: git submodule update --init + - remove-linux-binaries run-tests-firefox: description: "Runs emscripten tests under firefox" parameters: @@ -344,22 +389,20 @@ commands: type: string default: "" steps: - - prepare-for-tests - run: name: download firefox command: | wget -O ~/ff.tar.bz2 "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" tar -C ~ -xf ~/ff.tar.bz2 - run: - name: configure firefox + name: Add audio dependencies command: | - mkdir ~/tmp-firefox-profile/ - cat > ~/tmp-firefox-profile/user.js \<>) environment: @@ -367,25 +410,101 @@ commands: # TODO: Do GL testing when https://bugzil.la/1375585 (lack of WebGL # support in headless mode) resolves EMTEST_LACKS_GRAPHICS_HARDWARE: "1" + # TODO(https://github.com/emscripten-core/emscripten/issues/24205) EMTEST_LACKS_SOUND_HARDWARE: "1" - # OffscreenCanvas support is not yet done in Firefox. - EMTEST_LACKS_OFFSCREEN_CANVAS: "1" + EMTEST_LACKS_WEBGPU: "1" EMTEST_DETECT_TEMPFILE_LEAKS: "0" + EMTEST_HEADLESS: "1" + EMTEST_CORES: "2" DISPLAY: ":0" command: | - export EMTEST_BROWSER="$HOME/firefox/firefox -headless -profile $HOME/tmp-firefox-profile/" + export EMTEST_BROWSER="$HOME/firefox/firefox" # There are tests in the browser test suite that using libraries # that are not included by "./embuilder build ALL". For example the # PIC version of libSDL which is used by test_sdl2_misc_main_module export EM_FROZEN_CACHE="" echo "-----" - echo "Running browser tests" + echo "Running browser tests (EMTEST_BROWSER=$EMTEST_BROWSER)" echo "-----" test/runner << parameters.test_targets >> # posix and emrun suites are disabled because firefox errors on # "Firefox is already running, but is not responding." # TODO: find out a way to shut down and restart firefox - upload-test-results + run-tests-firefox-windows: + description: "Runs emscripten tests under firefox on Windows" + parameters: + test_targets: + description: "Test suites to run" + type: string + title: + description: "Name of given test suite" + type: string + default: "" + steps: + - run: + name: download firefox + shell: powershell.exe -ExecutionPolicy Bypass + command: | + $ErrorActionPreference = 'Stop' + + # To download Firefox, we must first figure out what the latest Firefox version name is. + # This is because there does not exist a stable/static URL to download latest Firefox from. + $html = Invoke-WebRequest "https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/" + $zipLink = $html.Links | + Where-Object { $_.href -match "firefox-.*\.en-US\.win64\.zip$" } | + Select-Object -Last 1 -ExpandProperty href + + # Download Win64 Firefox. + $nightlyUrl = "https://archive.mozilla.org$zipLink" + $outZip = "$env:TEMP\firefox_nightly.zip" + + Write-Host "Downloading latest Firefox Nightly: $nightlyUrl" + Invoke-WebRequest -Uri $nightlyUrl -OutFile $outZip + + # Extract to user home directory + Write-Host "Extracing $outZip" + $installDir = Join-Path $env:USERPROFILE "firefox" + if (Test-Path $installDir) { Remove-Item -Recurse -Force $installDir } + # All files in the archive are in the firefox/ directory so extract + # directly to $USERPROFILE. + Expand-Archive -LiteralPath $outZip -DestinationPath $env:USERPROFILE + + # FIXME(sbc): This does not seem to work, and EMTEST_BROWSER is + # not set in the bash job below. + # Set environment variable that will be visibvle in future steps + #$ffExe = Join-Path $installDir "firefox.exe" + #Write-Host "Setting EMTEST_BROWSER to '$ffExe' in $env:BASH_ENV" + #Add-Content -Path $env:BASH_ENV -Value "export EMTEST_BROWSER='$ffExe'" + - run: + name: run tests (<< parameters.title >>) + environment: + EMTEST_LACKS_GRAPHICS_HARDWARE: "1" + # TODO(https://github.com/emscripten-core/emscripten/issues/24205) + EMTEST_LACKS_SOUND_HARDWARE: "1" + EMTEST_LACKS_WEBGPU: "1" + EMTEST_DETECT_TEMPFILE_LEAKS: "0" + EMTEST_HEADLESS: "1" + EMTEST_CORES: "2" + DISPLAY: ":0" + command: | + # For some reason its not possible for the powershell job above to + # add to BASH_ENV, so we need set this here + export EMTEST_BROWSER="$USERPROFILE\\firefox\\firefox.exe" + + # There are tests in the browser test suite that using libraries + # that are not included by "./embuilder build ALL". For example the + # PIC version of libSDL which is used by test_sdl2_misc_main_module + export EM_FROZEN_CACHE= + + echo "BASH_ENV($BASH_ENV):" + echo "-----" + cat $BASH_ENV + echo "-----" + echo "Running browser tests (EMTEST_BROWSER=$EMTEST_BROWSER)" + echo "-----" + test/runner.bat << parameters.test_targets >> + - upload-test-results test-sockets-chrome: description: "Runs emscripten sockets tests under chrome" steps: @@ -396,20 +515,16 @@ commands: environment: EMTEST_LACKS_SOUND_HARDWARE: "1" EMTEST_DETECT_TEMPFILE_LEAKS: "0" - # --no-sandbox becasue we are running as root and chrome requires - # this flag for now: https://crbug.com/638180 - CHROME_FLAGS_BASE: "--no-first-run -start-maximized --no-sandbox --use-gl=swiftshader --user-data-dir=/tmp/chrome-emscripten-profile" - CHROME_FLAGS_HEADLESS: "--headless=new --remote-debugging-port=1234" - CHROME_FLAGS_WASM: "--enable-experimental-webassembly-features --js-flags=\"--experimental-wasm-memory64\"" - CHROME_FLAGS_NOCACHE: "--disk-cache-dir=/dev/null --disk-cache-size=1 --media-cache-size=1 --disable-application-cache --incognito" - command: | - export EMTEST_BROWSER="/usr/bin/google-chrome $CHROME_FLAGS_BASE $CHROME_FLAGS_HEADLESS $CHROME_FLAGS_WASM $CHROME_FLAGS_NOCACHE" + EMTEST_HEADLESS: "1" + EMTEST_BROWSER: "/usr/bin/google-chrome" + EMTEST_CORES: "2" + command: | test/runner sockets - upload-test-results jobs: build-docs: - executor: bionic + executor: ubuntu-lts steps: - checkout - pip-install @@ -417,14 +532,21 @@ jobs: - run: make -C site text - run: tools/maint/check_emcc_help_text.py - run: make -C site html - flake8: - executor: bionic + ruff: + # Keep in sync with ruff commands in tools/maint/pre-push + executor: ubuntu-lts + steps: + - checkout + - pip-install + - run: ruff check + vulture: + executor: ubuntu-lts steps: - checkout - pip-install - - run: python3 -m flake8 --show-source --statistics + - run: vulture . --min-confidence 100 mypy: - executor: bionic + executor: ubuntu-lts steps: - checkout - pip-install @@ -433,18 +555,18 @@ jobs: executor: linux-node steps: - checkout - - npm-install + - run: npm ci - run: | npm run lint npm run check test-sanity: - executor: bionic + executor: ubuntu-lts steps: - run-tests-linux: frozen_cache: false test_targets: "sanity" build-linux: - executor: bionic + executor: ubuntu-lts # xlarge has 4x the cores of the default medium, costs 4x as much, and runs # in about 1/2 the time, so it is not cost-effective (overall it is 2x the # cost for the same work), but given this blocks almost all the other jobs @@ -453,6 +575,7 @@ jobs: environment: EMCC_CORES: 16 EMCC_USE_NINJA: 1 + EM_COMPILER_WRAPPER: "ccache" steps: - checkout - run: @@ -480,26 +603,43 @@ jobs: tar xvf wasi-sysroot-11.0.tar.gz -C ~/wasi-sdk/ - install-v8 - install-emsdk + - when: + condition: + not: + equal: [ "main", << pipeline.git.branch >> ] + steps: + - restore_cache: + name: "Restore Ccache cache" + key: clang-{{ checksum "~/emsdk/clang_version.txt" }} + - run: + name: Configure ccache + command: | + mkdir -p ~/.ccache + cat .circleci/ccache.conf + cp .circleci/ccache.conf ~/.ccache - build-libs + - run: + name: Clean build directory + command: rm -rf ~/cache/build - persist # Perhaps we don't need to run this suite with every commit. Consider moving this to FYI bot. test-posixtest: - executor: bionic + executor: ubuntu-lts steps: - run-tests-linux: test_targets: "posixtest" test-core0: - executor: bionic + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_SKIP_NODE_25: "1" steps: - run-tests-linux: test_targets: "core0" test-core2: - executor: bionic + executor: ubuntu-lts environment: EMTEST_BROWSER: "node" - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_SKIP_NODE_25: "1" steps: - run-tests-linux: # also run a few asan tests. Run these with frozen_cache disabled @@ -508,6 +648,7 @@ jobs: title: "asan+lsan" test_targets: " asan.test_stat + asan.test_stack asan.test_float_builtins asan.test_embind* asan.test_abort_on_exceptions @@ -518,14 +659,24 @@ jobs: asan.test_dlfcn_basic asan.test_async_hello_jspi asan.test_cubescript - asan.test_wasm_worker_hello asan.test_externref_emjs_dynlink asan.test_asyncify_longjmp asan.test_pthread_run_on_main_thread + asan.test_minimal_runtime_global_initializer + asan.test_fs_js_api_wasmfs + asan.test_modularize_instance_pthreads + asan.test_minimal_runtime_hello_world + asan.test_select_blocking + asan.test_ppoll_blocking + lsan.test_dylink_dso_needed lsan.test_stdio_locking lsan.test_dlfcn_basic lsan.test_pthread_create - lsan.test_pthread_exit_main_stub" + lsan.test_pthread_exit_main_stub + lsan.test_dylink_iostream + lsan.test_embind_lib_with_asyncify + ubsan.test_dlfcn_self + ubsan.test_externref_emjs_dynlink" - freeze-cache - run-tests: # also run a single test of EMTEST_BROWSER=node. @@ -537,9 +688,9 @@ jobs: corez.test_dylink_syslibs_all" - upload-test-results test-core3: - executor: bionic + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_SKIP_NODE_25: "1" steps: - run-tests-linux: frozen_cache: false @@ -549,7 +700,11 @@ jobs: test_targets: " lto2.test_dylink_syslibs_all lto2.test_float_builtins + lto2.test_avx_nontrapping lto0.test_exceptions_allowed_uncaught + lto0.test_longjmp_standalone_standalone + lto0.test_embind_i64_val + thinlto0.test_pthread_dlsym core3 core2g.test_externref corez.test_dylink_iostream @@ -557,7 +712,9 @@ jobs: core2ss.test_pthread_thread_local_storage core2s.test_dylink_syslibs_missing_assertions core2s.test_module_wasm_memory + cores.test_minimal_runtime_safe_heap wasm2js3.test_memorygrowth_2 + wasm2js2.test_pthread_proxying wasmfs.test_hello_world wasmfs.test_unistd_links* wasmfs.test_atexit_standalone @@ -567,6 +724,7 @@ jobs: wasmfs.test_readdir_rawfs wasmfs.test_utime wasmfs.test_unistd_unlink + wasmfs.test_unistd_dup wasmfs.test_unistd_access wasmfs.test_unistd_close wasmfs.test_unistd_truncate @@ -580,8 +738,6 @@ jobs: wasmfs.test_fs_write wasmfs.test_fs_writev wasmfs.test_fs_writev_rawfs - wasmfs.test_fs_writeFile - wasmfs.test_fs_writeFile_rawfs wasmfs.test_fs_readv wasmfs.test_fs_write wasmfs.test_fs_readv_rawfs @@ -605,32 +761,36 @@ jobs: wasmfs.test_stat wasmfs.test_fstatat wasmfs.test_futimens - wasmfs.test_unistd_links_memfs + wasmfs.test_unistd_links wasmfs.test_fcntl_open - wasmfs.test_fs_js_api wasmfs.test_fs_llseek wasmfs.test_fs_llseek_rawfs - wasmfs.test_freetype" - test-wasm2js1: + wasmfs.test_freetype + minimal0.test_utf + minimal0.test_ubsan_full_stack_trace_gsource_map + minimal0.test_static_variable + minimal0.test_stack_overflow + omitexports0.test_asyncify_longjmp + omitexports0.test_emscripten_api + strict.test_no_declare_asm_module_exports + strict.test_dylink_global_inits_reversed + " + test-modularize-instance: + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" - executor: bionic + EMTEST_SKIP_NODE_25: "1" steps: - run-tests-linux: - test_targets: "wasm2js1" - test-wasm64l: + test_targets: "instance" + test-stress: + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" - executor: bionic + EMTEST_SKIP_NODE_25: "1" steps: - run-tests-linux: - frozen_cache: false - # On older versions of node wasm64l.test_bigswitch uses a lot (~3.5Gb) - # of memory, so skip it here, but run it in test-wasm64 instead which - # has node canary installed. - test_targets: "wasm64l skip:wasm64l.test_bigswitch" - test-wasm64: - # We don't use `bionic` here since its tool old to run recent node versions: + test_targets: "stress" + test-esm-integration: + # We don't use `bionic` here since its too old to run recent node versions: # `/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found` executor: linux-python steps: @@ -639,22 +799,21 @@ jobs: # hardcodes /root into its launcher scripts so we need to reinstall v8. - run: rm -rf $HOME/.jsvu - install-v8 - - install-node-canary + - install-node-newest - run-tests: - title: "wasm64" - test_targets: " - wasm64 - core_2gb.test_*em_asm* - core_2gb.test_*embind* - wasm64l.test_bigswitch - other.test_memory64_proxies - other.test_failing_growth_wasm64" + title: "esm_integration" + test_targets: "esm_integration" - upload-test-results + test-wasm2js1: + environment: + EMTEST_SKIP_NODE_25: "1" + executor: ubuntu-lts + steps: + - run-tests-linux: + test_targets: "wasm2js1" test-wasm64-4gb: environment: LANG: "C.UTF-8" - # Only run 2 tests at a time to avoid OOM (since each tests used >4gb) - EMCC_CORES: "2" # We don't use `bionic` here since its too old to run recent node versions: # `/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found` executor: linux-python @@ -664,18 +823,86 @@ jobs: # hardcodes /root into its launcher scripts so we need to reinstall v8. - run: rm -rf $HOME/.jsvu - install-v8 - - install-node-canary + - install-node-newest + # When running wasm64 tests we need to make sure we use the latest + # version of node (node 25) when running the compiler output (e.g. + # in configure tests. + - run: + name: configure node v25 + command: echo "NODE_JS = NODE_JS_TEST" >> ~/emsdk/.emscripten - run-tests: title: "wasm64_4gb" test_targets: "wasm64_4gb" - upload-test-results - test-jsc: + test-wasm64-misc: + environment: + LANG: "C.UTF-8" + # We don't use `bionic` here since its too old to run recent node versions: + # `/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found` + executor: linux-python + steps: + - prepare-for-tests + # The linux-python image uses /home/circleci rather than /root and jsvu + # hardcodes /root into its launcher scripts so we need to reinstall v8. + - run: rm -rf $HOME/.jsvu + - install-v8 + - install-node-newest + # When running wasm64 tests we need to make sure we use the latest + # version of node (node v25) when running the compiler output (e.g. + # in configure tests. + - run: + name: configure node v25 + command: echo "NODE_JS = NODE_JS_TEST" >> ~/emsdk/.emscripten + - run-tests: + title: "wasm64" + test_targets: " + other.*_wasm64 + core_2gb.test_*em_asm* + core_2gb.test_*embind* + core_2gb.test_fs_js_api_wasmfs + wasm64.test_safe_stack + wasm64l.test_hello_world + wasm64l.test_bigswitch + wasm64l.test_module_wasm_memory + wasm64l.test_longjmp2_emscripten + wasm64l.test_embind_val_basics_legacy" + - upload-test-results + test-bun: + executor: linux-python + environment: + EMTEST_SKIP_NEW_CMAKE: "1" + steps: + - checkout + - pip-install + - install-emsdk + - run: + name: install bun + command: | + curl -fsSL https://bun.com/install | bash + echo "BUN = os.path.expanduser('~/.bun/bin/bun')" >> ~/emsdk/.emscripten + echo "JS_ENGINES = [BUN]" >> ~/emsdk/.emscripten + - run-tests: + test_targets: "--crossplatform-only" + test-deno: executor: linux-python + environment: + EMTEST_SKIP_NEW_CMAKE: "1" steps: - checkout + - pip-install + - install-emsdk - run: - name: submodule update - command: git submodule update --init + name: install deno + command: | + curl -fsSL https://deno.land/install.sh | sh + echo "DENO = os.path.expanduser('~/.deno/bin/deno')" >> ~/emsdk/.emscripten + echo "JS_ENGINES = [DENO]" >> ~/emsdk/.emscripten + - run-tests: + test_targets: "--crossplatform-only" + test-jsc: + executor: linux-python + steps: + - checkout - pip-install - install-emsdk - run: @@ -698,9 +925,6 @@ jobs: executor: linux-python steps: - checkout - - run: - name: submodule update - command: git submodule update --init - pip-install - install-emsdk - run: @@ -718,6 +942,7 @@ jobs: test_targets: " core0.test_hello_argc core2.test_demangle_stacks_symbol_map" + - upload-test-results test-node-compat: # We don't use `bionic` here since its too old to run recent node versions: # `/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found` @@ -727,101 +952,233 @@ jobs: EMTEST_SKIP_V8: "1" steps: - checkout - - run: - name: submodule update - command: git submodule update --init - pip-install - install-emsdk - - install-node-canary + # `install-node-version` only changes the NODE_JS_TEST (the version of + # node used to run test), not NODE_JS (the version of node used to run the + # compiler itself). + # In order to test that the compiler itself can run under the oldest + # supported version of node, we run all the tests in the runner under that + # version. + # Keep this in sync with MINIMUM_NODE_VERSION in tools/shared.py. + - install-node-version: + node_version: "18.3.0" + - run: + name: configure compiler to use 18.3.0 + command: echo "NODE_JS = '$(which node)'" >> ~/emsdk/.emscripten + # Run some basic tests with the minimum version of node that we currently + # support in the generated code. + - install-node-oldest - run-tests: - title: "node (canary)" + title: "node (oldest / 18.3.0)" + # We include most but not all of the nodefs and node rawfs tests here. + # test_fs_nodefs_rw, test_fs_nodefs_statvfs, and test_unistd_io_nodefs_bigint fail. test_targets: " - other.test_deterministic + other.test_embind_tsgen_remove_relaxed_simd other.test_gen_struct_info other.test_native_call_before_init - other.test_node_unhandled_rejection + other.test_js_optimizer_verbose + other.test_file_packager_separate_metadata + other.test_full_js_library* core2.test_hello_world - core0.test_pthread_join_and_asyncify - core0.test_async_ccall_promise_jspi - core0.test_async_ccall_promise_exit_runtime_jspi - core0.test_cubescript_jspi" - # Run some basic tests with the minimum version of node that we currently - # support. - - install-node-version: - node_version: "10.19.0" + core2.test_fcntl_open_nodefs + core2.test_fcntl_open_rawfs + core2.test_fgetc_ungetc_nodefs + core2.test_fgetc_ungetc_rawfs + core2.test_fs_append_rawfs + core2.test_fs_emptyPath_rawfs + core2.test_fs_enotdir_nodefs + core2.test_fs_enotdir_rawfs + core2.test_fs_errorstack_rawfs + core2.test_fs_llseek_rawfs + core2.test_fs_mkdir_dotdot_nodefs + core2.test_fs_mkdir_dotdot_rawfs + core2.test_fs_mmap_nodefs + core2.test_fs_mmap_rawfs + core2.test_fs_nodefs_cloexec + core2.test_fs_nodefs_cloexec_rawfs + core2.test_fs_nodefs_dup + core2.test_fs_nodefs_dup_rawfs + core2.test_fs_nodefs_home + core2.test_fs_nodefs_nofollow + core2.test_fs_nodefs_readdir + core2.test_fs_noderawfs_nofollow + core2.test_fs_readdir_ino_matches_stat_ino_nodefs + core2.test_fs_readdir_ino_matches_stat_ino_rawfs + core2.test_fs_readv_rawfs + core2.test_fs_rename_on_existing_nodefs + core2.test_fs_rename_on_existing_rawfs + core2.test_fs_symlink_resolution_nodefs + core2.test_fs_symlink_resolution_rawfs + core2.test_fs_writeFile* + core2.test_fs_write_rawfs + core2.test_fs_writev_rawfs + core2.test_futimens_rawfs + core2.test_readdir_rawfs + core2.test_stat_chmod_rawfs + core2.test_unistd_access_nodefs + core2.test_unistd_access_rawfs + core2.test_unistd_close_rawfs + core2.test_unistd_dup_rawfs + core2.test_unistd_io_nodefs + core2.test_unistd_links_nodefs + core2.test_unistd_misc_nodefs + core2.test_unistd_pipe_rawfs + core2.test_unistd_symlink_on_nodefs + core2.test_unistd_truncate_nodefs + core2.test_unistd_truncate_rawfs + core2.test_unistd_unlink_nodefs + core2.test_unistd_unlink_rawfs + core2.test_unistd_write_broken_link_rawfs + " + # Run a few test with the most recent LTS version of node + - install-node-lts - run-tests: - title: "node (oldest / 10.19.0)" - test_targets: " + # Run tests that on older versions of node would require flags, but + # those flags should not be injected on newer versions. + title: "node (lts)" + test_targets: "-v other.test_gen_struct_info other.test_native_call_before_init other.test_js_optimizer_verbose - other.test_node_unhandled_rejection - other.test_full_js_library* - core2.test_hello_world" + other.test_min_node_version + other.test_node_emscripten_num_logical_cores + other.test_exceptions_stack_trace* + other.test_exceptions_rethrow_stack_trace* + core2.test_pthread_create + core2.test_i64_invoke_bigint + core2.test_sse2 + core2.test_source_map + core2.test_exceptions_wasm_legacy + core2.test_pthread_unhandledrejection + " # Run a few test with the most recent version of node - # In particular we have some tests that require node flags on older - # versions of node but not with the most recent version. - - install-node-latest + - install-node-newest - run-tests: # Run tests that on older versions of node would require flags, but # those flags should not be injected on newer versions. title: "node (latest)" test_targets: "-v + other.test_deterministic other.test_gen_struct_info other.test_native_call_before_init - other.test_node_unhandled_rejection + other.test_*growable_arraybuffers + other.test_add_js_function_bigint_memory64 other.test_js_optimizer_verbose other.test_min_node_version other.test_node_emscripten_num_logical_cores + other.test_js_base64_api + core2.test_hello_world core2.test_pthread_create core2.test_i64_invoke_bigint core2.test_sse2 core2.test_source_map - core2.test_exceptions_wasm - core2.test_pthread_unhandledrejection" + core2.test_exceptions_wasm_legacy + core2.test_pthread_unhandledrejection + core2.test_esm_integration* + core0.test_esm_integration* + core0.test_pthread_join_and_asyncify + core0.test_async_ccall_promise_jspi* + core0.test_cubescript_jspi + core0.test_poll_blocking_asyncify_jspi + " - upload-test-results test-other: - executor: bionic + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_SKIP_NODE_25: "1" + EMTEST_SKIP_RUST: "1" + EMTEST_SKIP_WASM64: "1" + EMTEST_SKIP_NEW_CMAKE: "1" steps: - - run: apt-get install ninja-build scons + - install-rust + - run: apt-get install -q -y ninja-build scons ccache - run-tests-linux: # some native-dependent tests fail because of the lack of native # headers on emsdk-bundled clang - test_targets: "other skip:other.test_native_link_error_message" + test_targets: "other jslib skip:other.test_native_link_error_message" + - run-tests-linux: + # Include a single test from test_benchmark.py to ensure it's always + # kept functional. Must be run separately from above because we cannot + # run non-parallel tests in the same run as parallel tests. + test_targets: "benchmark.test_primes" test-browser-chrome: - executor: bionic + executor: ubuntu-lts + environment: + EMTEST_LACKS_WEBGPU: "1" steps: + - checkout + - pip-install + - install-emsdk - run-tests-chrome: title: "browser" - # Skip test_4gb_fail as it OOMs on the current bot + # Just tests that don't run in browser 2G mode test_targets: " - browser skip:browser.test_4gb_fail + browser.test_gl_stride + browser.test_memory_growth_during_startup + browser.test_pthread_large_pthread_allocation + browser.test_pthread_sbrk* + browser.test_pthread_asan* + browser.test_webgl_multi_draw* + browser.test_pthread_growth* + browser.test_4gb + browser.test_emmalloc_3gb* + browser.test_dlmalloc_3gb + browser.test_2gb_fail + browser.test_audio_worklet* " test-browser-chrome-wasm64: - executor: bionic + executor: ubuntu-lts + environment: + EMTEST_LACKS_WEBGPU: "1" + EMTEST_VISUALIZE: "1" steps: - run-tests-chrome: title: "browser64" - test_targets: "browser64" + # Just tests that don't run in browser64 4G mode + # TODO: restore this coverage (and the corresponding wasm64) and/or + # make this exclusion list a bit more automatic. + test_targets: " + browser64.test_emscripten_animate_canvas_element_size* + browser64.test_*malloc_*gb* + browser64.test_emmalloc_memgrowth + browser64.test_subdata_es2* + browser64.test_webgl2_garbage_free_entrypoints* + browser64.test_gl_stride + browser64.test_webgl2_get_buffer_sub_data + browser64.test_webgl2_pbo + browser64.test_webgl2_sokol* + browser64.test_memory_growth_during_startup + browser64.test_pthread_large_pthread_allocation + browser64.test_pthread_sbrk* + browser64.test_pthread_asan* + browser64.test_webgl_multi_draw* + browser64.test_pthread_growth*" + - run: + name: Check profile + command: file -E out/graph.html test-browser-chrome-2gb: - executor: bionic + executor: ubuntu-lts + environment: + EMTEST_LACKS_WEBGPU: "1" steps: - run-tests-chrome: title: "browser_2gb" test_targets: "browser_2gb" test-browser-chrome-wasm64-4gb: - executor: bionic + executor: ubuntu-lts environment: - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_LACKS_WEBGPU: "1" steps: - run-tests-chrome: title: "browser64_4gb" test_targets: "browser64_4gb" test-browser-firefox: - executor: bionic + executor: ubuntu-lts + environment: + EMTEST_LACKS_GROWABLE_ARRAYBUFFERS: "1" steps: + - prepare-for-tests - run-tests-firefox: title: "browser" # browser.test_sdl2_mouse and/or SDL2 should be fixed. The case happens @@ -829,26 +1186,102 @@ jobs: # initial position of the mouse pointer relative to the canvas. # browser.test_html5_webgl_create_context is skipped because # anti-aliasing is not well supported. - # browser.test_webgl_offscreen_canvas_in_pthread and - # browser.test_webgl_offscreen_canvas_in_mainthread_after_pthread - # are crashing Firefox (bugzil.la/1281796). The former case is - # further blocked by issue #6897. - test_targets: "browser skip:browser.test_sdl2_mouse skip:browser.test_html5_webgl_create_context skip:browser.test_webgl_offscreen_canvas_in_pthread skip:browser.test_webgl_offscreen_canvas_in_mainthread_after_pthread skip:browser.test_glut_glutget" - # TODO(sbc): Re-enable once we figure out why the emrun tests are - # locking up. + test_targets: " + browser + skip:browser.test_sdl2_mouse + skip:browser.test_html5_webgl_create_context + skip:browser.test_glut_glutget + " + test-browser-firefox-wasm64: + executor: ubuntu-lts + steps: + - checkout + - pip-install + - install-emsdk + - run-tests-firefox: + title: "browser64" + test_targets: " + browser64.test_sdl_image + browser64.test_dylink_many + " + test-windows-browser-firefox: + # Use spaces and other special chars in the path to emsdk to flush out + # issues with windows. In particular, the .bat file launchers can have + # trouble with these. + working_directory: "~/path with spaces ()/" + executor: + name: win/server-2019 + shell: bash.exe -eo pipefail + environment: + PYTHONUNBUFFERED: "1" + EMSDK_NOTTY: "1" + steps: + - checkout + - run: + name: Install packages + command: | + choco install -y cmake.portable ninja pkgconfiglite + - install-emsdk + - pip-install: + python: "$EMSDK_PYTHON" + - run-tests-firefox-windows: + title: "browser on firefox on windows" + # skip browser.test_glbook, as it requires mingw32-make, which is not + # installed on CircleCI. + # skip browser.test_sdl2_mixer_wav_dash_l, fails to build on Windows + # on CircleCI (works locally) + # + # For now, just run a small subset of tests that are know to work. + # TODO: Include more tests here. + test_targets: " + browser.test_sdl_image + browser.test_dylink_many + skip:browser.test_glbook + skip:browser.test_sdl2_mixer_wav_dash_l + " + # TODO(https://github.com/emscripten-core/emscripten/issues/26182): + # Re-enable once we figure out why the emrun tests are locking up. #test-browser-chrome-emrun: - # executor: bionic + # executor: ubuntu-lts # steps: # - run-tests-chrome: # test_targets: "emrun" test-sockets-chrome: - executor: bionic + executor: ubuntu-lts steps: - test-sockets-chrome + + build-windows-launcher: + executor: + name: win/server-2022 + shell: bash.exe -eo pipefail + steps: + - checkout + - run: + name: "build pylauncher" + shell: cmd.exe + command: .circleci\setup_vs2022.bat && cd tools\pylauncher && call build.bat + - store_artifacts: + path: tools/pylauncher/pylauncher.exe + destination: pylauncher.exe + - install-emsdk + - pip-install: + python: "$EMSDK_PYTHON" + - run: + name: "create_entry_points" + command: $EMSDK_PYTHON tools/maint/create_entry_points.py --exe-files + - run: + name: "crossplatform tests" + command: test/runner.exe core0.test_hello_world + + # windows and mac do not have separate build and test jobs, as they only run # a limited set of tests; it is simpler and faster to do it all in one job. test-windows: - working_directory: "~/path with spaces" + # Use spaces and other special chars in the path to emsdk to flush out + # issues with windows. In particular, the .bat file launchers can have + # trouble with these. + working_directory: "~/path with spaces ()/" executor: name: win/server-2019 shell: bash.exe -eo pipefail @@ -859,66 +1292,36 @@ jobs: # https://github.com/emscripten-core/emscripten/pull/11382#pullrequestreview-428902638 EMTEST_LACKS_NATIVE_CLANG: "1" EMTEST_SKIP_V8: "1" - EMTEST_SKIP_EH: "1" + EMTEST_SKIP_WASM_LEGACY_EH: "1" + EMTEST_SKIP_WASM_EH: "1" EMTEST_SKIP_WASM64: "1" - EMTEST_SKIP_SIMD: "1" EMTEST_SKIP_SCONS: "1" - EMTEST_SKIP_NODE_CANARY: "1" + EMTEST_SKIP_RUST: "1" + EMTEST_SKIP_NODE_25: "1" EMTEST_BROWSER: "0" steps: - checkout + - run: + name: Build launcher + command: call .circleci\setup_vs2019.bat && cd tools\pylauncher && call build.bat + shell: cmd.exe - run: name: Install packages command: | choco install -y cmake.portable ninja pkgconfiglite - - run: - name: Add python to bash path - command: echo "export PATH=\"$PATH:/c/Python27amd64/\"" >> $BASH_ENV # note we do *not* build all libraries and freeze the cache; as we run # only limited tests here, it's more efficient to build on demand - install-emsdk - pip-install: python: "$EMSDK_PYTHON" - - run-tests: - title: "crossplatform tests" - test_targets: "--crossplatform-only" + - run: + name: "crossplatform tests" + command: test/runner.bat --crossplatform-only - upload-test-results # Run a single websockify-based test to ensure it works on windows. - - run-tests: - title: "sockets.test_nodejs_sockets_echo*" - test_targets: "sockets.test_nodejs_sockets_echo*" - - upload-test-results - - test-mac: - executor: mac - environment: - # We don't install d8 or modern node on the mac runner so we skip any - # tests that depend on those. - EMTEST_SKIP_V8: "1" - EMTEST_SKIP_EH: "1" - EMTEST_SKIP_WASM64: "1" - EMTEST_SKIP_SCONS: "1" - EMCC_SKIP_SANITY_CHECK: "1" - # test_sse1 (the only @crossplatform native clang test) currently fails - # on the macOS bots). - EMTEST_LACKS_NATIVE_CLANG: "1" - EMTEST_SKIP_NODE_CANARY: "1" - steps: - - setup-macos - - attach_workspace: - at: ~/ - run: - name: Remove Linux binaries - command: | - rm -rf ~/emsdk ~/vms ~/.jsvu - # note we do *not* build all libraries and freeze the cache; as we run - # only limited tests here, it's more efficient to build on demand - - install-emsdk - - pip-install: - python: "$EMSDK_PYTHON" - - run-tests: - title: "crossplatform tests" - test_targets: "--crossplatform-only" + name: "sockets.test_nodejs_sockets_echo*" + command: "test/runner.bat sockets.test_nodejs_sockets_echo*" - upload-test-results test-mac-arm64: @@ -927,17 +1330,20 @@ jobs: # We don't install d8 or modern node on the mac runner so we skip any # tests that depend on those. EMTEST_SKIP_V8: "1" - EMTEST_SKIP_EH: "1" + EMTEST_SKIP_WASM_LEGACY_EH: "1" + EMTEST_SKIP_WASM_EH: "1" EMTEST_SKIP_WASM64: "1" EMTEST_SKIP_SCONS: "1" + EMTEST_SKIP_RUST: "1" # Some native clang tests assume x86 clang (e.g. -sse2) EMTEST_LACKS_NATIVE_CLANG: "1" EMCC_SKIP_SANITY_CHECK: "1" steps: - setup-macos - install-emsdk - # TODO: We can't currently do pip install here since numpy and other packages - # are currently missing arm64 macos binaries. + - pip-install: + python: "$EMSDK_PYTHON" + - freeze-cache - run-tests: title: "crossplatform tests" test_targets: "--crossplatform-only" @@ -946,8 +1352,9 @@ jobs: workflows: build-test: jobs: - - flake8 + - ruff - mypy + - vulture - eslint - build-docs - build-linux @@ -960,30 +1367,34 @@ workflows: - test-core0: requires: - build-linux - - test-core2: - requires: - - build-linux + #- test-core2: + # requires: + # - build-linux - test-core3: requires: - build-linux - - test-wasm64: - requires: - - build-linux + #- test-wasm64-misc: + # requires: + # - build-linux - test-wasm64-4gb: requires: - build-linux - - test-wasm64l: - requires: - - build-linux - test-wasm2js1: requires: - build-linux - test-other: requires: - build-linux - - test-browser-chrome: + - test-modularize-instance: requires: - build-linux + #- test-esm-integration: + # requires: + # - build-linux + - test-stress: + requires: + - build-linux + - test-browser-chrome - test-browser-chrome-2gb: requires: - build-linux @@ -996,17 +1407,18 @@ workflows: - test-browser-firefox: requires: - build-linux + - test-browser-firefox-wasm64 - test-sockets-chrome: requires: - build-linux - - test-jsc - - test-spidermonkey - - test-node-compat - - test-windows - - test-mac-arm64 - - test-mac: - # The mac tester also uses the libraries built on the linux builder to - # save total build time (this is fine because the libraries are wasm - # and do not depend on the underlying build platform) - requires: - - build-linux + #- test-bun + #- test-deno + #- test-jsc + #- test-spidermonkey + #- test-node-compat + #- test-windows + #- test-windows-browser-firefox + - build-windows-launcher + #- test-mac-arm64: + # requires: + # - build-linux diff --git a/.circleci/setup_vs2019.bat b/.circleci/setup_vs2019.bat new file mode 100644 index 0000000000000..cf84fc8f8086a --- /dev/null +++ b/.circleci/setup_vs2019.bat @@ -0,0 +1,4 @@ +echo "setting up x64 toolchain" +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 +where cl.exe +echo "done" diff --git a/.circleci/setup_vs2022.bat b/.circleci/setup_vs2022.bat new file mode 100644 index 0000000000000..096f1f27ed46d --- /dev/null +++ b/.circleci/setup_vs2022.bat @@ -0,0 +1,4 @@ +echo "setting up x64 toolchain" +call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 +where cl.exe +echo "done" diff --git a/.clang-format b/.clang-format index a60db77c9266d..ffed08d155e57 100644 --- a/.clang-format +++ b/.clang-format @@ -14,3 +14,5 @@ Language: JavaScript BasedOnStyle: LLVM ColumnLimit: 100 --- +Language: Json +BasedOnStyle: LLVM diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000000000..b123ca96c8e28 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,15 @@ +# ignore third_party code from clang-format checks +third_party/ +test/third_party/ +system/lib/libc/musl/ +system/lib/libcxx/ +system/lib/libcxxabi/ +system/lib/libunwind/ +system/lib/mimalloc/ +system/lib/llvm-libc/ + +# Don't try to format our weird JS library code +src/lib/ + +# Ignore auto-generate test JS files +test/**/*.expected.js diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index e8ca40a3d059c..0000000000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -source = . -omit = ./test/* - ./third_party/* - ./tools/emcoverage.py - test.py - diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index d1e8a40d678b9..0000000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,60 +0,0 @@ -env: - browser: true - es2020: true - node: true -extends: - - prettier -parserOptions: - ecmaVersion: 12 -ignorePatterns: - - "out/" - - "site/" - - "cache/" - - "third_party/" - - "test/" - - "src/polyfill/" - - "src/library*.js" - - "src/runtime_*.js" - - "src/shell*.js" - - "src/preamble*.js" - - "src/postamble*.js" - - "src/closure-externs/" - - "src/embind/" - - "src/emrun_postjs.js" - - "src/worker.js" - - "src/wasm_worker.js" - - "src/audio_worklet.js" - - "src/wasm2js.js" - - "src/webGLClient.js" - - "src/webGLWorker.js" - - "src/*_shell_read.js" - - "src/wasm_offset_converter.js" - - "src/threadprofiler.js" - - "src/cpuprofiler.js" - - "src/memoryprofiler.js" - - "src/gl-matrix.js" - - "src/headless.js" - - "src/headlessCanvas.js" - - "src/emscripten-source-map.min.js" - - "src/source_map_support.js" - - "src/Fetch.js" - - "src/settings.js" - - "src/settings_internal.js" - - "src/arrayUtils.js" - - "src/deterministic.js" - - "src/base64Utils.js" - - "src/base64Decode.js" - - "src/proxyWorker.js" - - "src/proxyClient.js" - - "src/IDBStore.js" - - "src/URIUtils.js" - - "tools/experimental" -rules: - #max-len: ["error", 100] - max-len: "off" - no-multi-spaces: "off" - require-jsdoc: "off" - no-unused-vars: "off" - arrow-body-style: ["error", "as-needed"] - space-infix-ops: "error" - quotes: ["error", "single", {"avoidEscape": true}] diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 74d7233230b35..0000000000000 --- a/.flake8 +++ /dev/null @@ -1,18 +0,0 @@ -[flake8] -ignore = E111,E114,E501,E261,E266,E121,E402,E241,W504,E741,B011,B023,U101 -exclude = - ./node_modules/, # third-party code - ./third_party/, # third-party code - ./tools/filelock.py, # third-party code - ./tools/scons/, # third-party code - ./test/third_party/, # third-party code - ./site/source/conf.py, - ./system/lib/, # system libraries - ./cache/, # download/built content - .git -# The ports plugins have a lot of unused imports because -# they need to implement the specific plugin APIs -per-file-ignores = - ./tools/ports/*.py: U100 - ./test/*.py: U100 - ./tools/toolchain_profiler.py: U100 diff --git a/.gitattributes b/.gitattributes index bddcc6d2240d2..95c5a1e388bc9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,8 +3,7 @@ tools/crunch-worker.js -diff third_party/lzma.js/lzma-decoder.js -diff third_party/lzma.js/lzma-full.js -diff -test/other/test_emsize.js -diff -src/emscripten-source-map.min.js -diff +test/other/test_emsize.js -diff eol=lf test/* linguist-vendored third_party/* linguist-vendored system/ linguist-vendored diff --git a/.github/ISSUE_TEMPLATE/feature_depreaction.md b/.github/ISSUE_TEMPLATE/feature_depreaction.md new file mode 100644 index 0000000000000..3bb404163327a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_depreaction.md @@ -0,0 +1,23 @@ +--- +name: Feature deprecation +about: Use this template for removal of features from emscripten. +title: 'Deprecation proposal for ' +labels: 'deprecation' +assignees: '' + +--- + +Tracking issue for the deprecation and removal of . + +The process for deprecating and removing features from emscripten is laid out out in https://github.com/emscripten-core/emscripten/blob/main/docs/process.md#deprecating-settings-and-features. + +At any point in the process we may decide to delay or abort the deprecation and close this issue. + +Deprecation checklist: + +- [ ] Open an issue (this) +- [ ] Perform global search for usage in public repos +- [ ] Send message to emscripten-discuss +- [ ] Add "deprecation" warning to emcc +- [ ] Wait at least 4 releases or 2 months +- [ ] Remove feature from codebase diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 90e05c40d0459..e3ae1a9ac556c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,21 @@ version: 2 updates: - - package-ecosystem: "github-actions" # See documentation for possible values + - package-ecosystem: "npm" + directory: "/" # Look for `package.json` and `lock` files in the `root` directory + schedule: + interval: "monthly" + commit-message: + prefix: "[deps]" + groups: + production-dependencies: + dependency-type: "production" + development-dependencies: + dependency-type: "development" + + - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: - interval: "weekly" + interval: "monthly" + commit-message: + prefix: "[deps]" diff --git a/.github/workflows/archive.yml b/.github/workflows/archive.yml deleted file mode 100644 index 480bf446da5c9..0000000000000 --- a/.github/workflows/archive.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: CI - -on: - create: - tags: - push: - branches: - - main - pull_request: - -permissions: - contents: read - -jobs: - archive: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: make dist - run: | - make dist - version=`cat emscripten-version.txt | sed s/\"//g` - echo "VERSION=$version" >> $GITHUB_ENV - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - with: - name: emscripten-${{ env.VERSION }} - path: emscripten-${{ env.VERSION }}.tar.bz2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000..be9dfcd59af6f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,111 @@ +name: CI + +on: + create: + tags: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + archive: + name: Archive + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - name: make dist + run: | + make dist + version=`cat emscripten-version.txt | sed s/\"//g` + echo "VERSION=$version" >> $GITHUB_ENV + - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.5 + with: + name: emscripten-${{ env.VERSION }} + path: emscripten-${{ env.VERSION }}.tar.bz2 + + codesize-checks: + name: Codesize Checks + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Install emsdk + uses: emscripten-core/setup-emsdk@v16 + with: + version: tot + - name: Set EM_CONFIG + run: + echo "EM_CONFIG=$EMSDK/.emscripten" >> $GITHUB_ENV + - name: Checkout repo + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 # We want access to other branches, specifically `main` + - name: pip install + run: | + which python3 + python3 --version + python3 -m pip install -r requirements-dev.txt + - name: Check test expectations on target branch + # Skip this step for rebaseline PRs, since the target branch is expected + # to be out of date in this case. + if: "!contains(github.event.pull_request.title, 'Automatic rebaseline of codesize expectations')" + run: | + echo "Checking out ${{ github.base_ref }}" + git checkout ${{ github.base_ref }} + git rev-parse HEAD + # Uncomment this like to pull the rebaseline_tests.py from the + # current branch: + # git checkout - ./tools/maint/rebaseline_tests.py + ./bootstrap + if ! ./tools/maint/rebaseline_tests.py --check-only; then + echo "" + echo "Test expectations are out-of-date on the target branch." + echo "Please run the 'Rebaseline Tests' github action on the target" + echo "branch (normally 'main') before proceeding. You can do this" + echo "from the web UI or from the command line:" + echo "" + echo " gh workflow run rebaseline-tests.yml" + echo "" + echo "You can also run the following command locally and upload" + echo "your own PR:" + echo "" + echo " ./tools/maint/rebaseline_tests.py --new-branch'" + exit 1 + fi + - name: Check test expectations on PR branch + run: | + echo "Checking out ${{ github.ref }} (${{ github.sha }})" + # For some reason we cannot pass ${{ github.ref }} direclty to git + # since it doesn't recognise it. + git checkout ${{ github.sha }} + git rev-parse HEAD + ./bootstrap + if ! ./tools/maint/rebaseline_tests.py --check-only --clear-cache; then + echo "Test expectations are out-of-date on the PR branch." + echo "You can run './tools/maint/rebaseline_tests.py' to" + echo "create a commit updating the expectations." + echo "Be sure to have 'emsdk install tot' first." + echo "-- This failure is only a warning and can be ignored" + exit 1 + fi + + clang-format-diff: + # This job is disabled until we can make it more precise + if: false + env: + LLVM_VERSION: 19 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Fetch all history for all tags and branches + with: + fetch-depth: 0 + - name: Install clang-format + run: | + sudo apt-get install clang-format-19 + sudo update-alternatives --install /usr/bin/git-clang-format git-clang-format /usr/bin/git-clang-format-19 100 + - run: tools/maint/clang-format-diff.sh origin/$GITHUB_BASE_REF diff --git a/.github/workflows/rebaseline-tests.yml b/.github/workflows/rebaseline-tests.yml new file mode 100644 index 0000000000000..48092dd999e8d --- /dev/null +++ b/.github/workflows/rebaseline-tests.yml @@ -0,0 +1,55 @@ +name: Rebaseline Tests + +on: + workflow_dispatch: + # Trigger every day at 3.30 AM PST (= 10:30 AM UTC) + schedule: + - cron: "30 10 * * *" + +permissions: + contents: write + pull-requests: write + +jobs: + rebaseline-tests: + name: Rebaseline Tests + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + steps: + - name: Install emsdk + uses: emscripten-core/setup-emsdk@v16 + with: + version: tot + - name: Set EM_CONFIG + run: + echo "EM_CONFIG=$EMSDK/.emscripten" >> $GITHUB_ENV + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + - name: pip install + run: | + which python3 + python3 --version + python3 -m pip install -r requirements-dev.txt + - name: Rebaseline tests + run: | + git config user.name emscripten-bot + git config user.email emscripten-bot@users.noreply.github.com + ./bootstrap + if ./tools/maint/rebaseline_tests.py --new-branch; then + echo "rebaseline_tests returned zero, expectations up-to-date" + # Exit early and don't create a PR + exit 0 + else + code=$? + if [[ $code != 2 ]] ; then + echo "rebaseline_tests.py failed with unexpected error $code (expected 2)" + exit 1 + fi + fi + git push origin rebaseline_tests + gh pr create --fill --base ${{ github.ref_name }} --reviewer sbc100,kripken + gh pr merge --squash --auto diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2aabafe3c7fc7..6f8a036e68ac2 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -29,7 +29,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -44,7 +44,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: SARIF file path: results.sarif @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 + uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: sarif_file: results.sarif diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml new file mode 100644 index 0000000000000..66c9d08d51b6b --- /dev/null +++ b/.github/workflows/tag-release.yml @@ -0,0 +1,53 @@ +# Tag release and update changelog +name: Tag release and update Changelog + +on: + workflow_dispatch: + inputs: + release-sha: + required: true + type: string + +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + # Updates changelog and writes the release version into the environment + - name: Update Changelog + run: python3 tools/maint/create_release.py --action + - name: Create Changelog PR + id: cpr + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + title: Mark ${{ env.RELEASE_VERSION }} as released + body: Update changelog and emscripten-version.txt [ci skip] + team-reviewers: release-reviewers + branch: release_${{ env.RELEASE_VERSION }} + delete-branch: true + - name: Enable auto-merge + run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}" + env: + GH_TOKEN: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + - name: Tag release sha + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + script: | + const tag_sha = '${{ inputs.release-sha }}'; + const release_version = '${{ env.RELEASE_VERSION }}'; + console.log(`Version ${release_version} at SHA ${tag_sha}`); + const regex = /^[0-9]+.[0-9]+.[0-9]+$/; + const match = release_version.match(regex); + if (!match) { + throw new Error('Malformed release version'); + } + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${release_version}`, + sha: tag_sha + }); + diff --git a/.github/workflows/update-website.yml b/.github/workflows/update-website.yml new file mode 100644 index 0000000000000..f3a7776735027 --- /dev/null +++ b/.github/workflows/update-website.yml @@ -0,0 +1,55 @@ +name: Update website + +on: + workflow_dispatch: + push: + branches: [ main ] + +jobs: + update-website: + name: Update website + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + - name: Checkout website repo + uses: actions/checkout@v4 + with: + repository: kripken/emscripten-site + ref: gh-pages + path: site/emscripten-site + token: ${{ secrets.EMSCRIPTEN_BOT_TOKEN }} + - name: pip install + run: | + which python3 + python3 --version + python3 -m pip install -r requirements-dev.txt + - name: Update docs + run: | + set -o errexit + set -o xtrace + git config --global user.name emscripten-bot + git config --global user.email emscripten-bot@users.noreply.github.com + if ./tools/maint/update_docs.py; then + echo "update_docs.py returned zero, expectations up-to-date" + # Exit early and don't create a PR + exit 0 + else + code=$? + if [[ $code != 2 ]] ; then + echo "update_docs.py failed with unexpected error $code (expected 2)" + exit 1 + fi + fi + # Create a PR against the emscripten-site repo + cd site/emscripten-site + git push -f origin update + gh pr create --fill --head update --base gh-pages --reviewer sbc100,kripken + # TODO: Enable this once we figure out how to enforce review + # requirment + #gh pr merge --squash --auto diff --git a/.gitignore b/.gitignore index 161bb569e7059..7d9644a2cbee6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ *.swp # Compiled python files -*.pyc +__pycache__ # Default emscripten cache /cache @@ -30,14 +30,95 @@ coverage.xml # Test output /out/ -# All the ps1 files are generated by bootstrap... +# When updating the website we check it out here. +/site/emscripten-site/ + +# Windows ps1 launchers (created by ./tools/maint/create_entry_points.py) *.ps1 # ...except the templates. !/tools/run_python.ps1 !/tools/run_python_compiler.ps1 +/tools/pylauncher/pylauncher.obj + # dotnet changes !/eng/**/*.ps1 /.dotnet /.packages /artifacts + +# Shell scripts (created by ./tools/maint/create_entry_points.py) +em++ +emcc +em-config +emar +embuilder +emcmake +emconfigure +emdump +emdwp +emmake +emnm +empath-split +emprofile +emranlib +emrun +emscan-deps +emscons +emsize +emstrip +emsymbolizer +test/runner +tools/file_packager +tools/webidl_binder + +# Windows .bat files (created by ./tools/maint/create_entry_points.py) +em++.bat +emcc.bat +em-config.bat +emar.bat +embuilder.bat +emcmake.bat +emconfigure.bat +emdump.bat +emdwp.bat +emmake.bat +emnm.bat +empath-split.bat +emprofile.bat +emranlib.bat +emrun.bat +emscan-deps.bat +emscons.bat +emsize.bat +emstrip.bat +emsymbolizer.bat +test/runner.bat +tools/file_packager.bat +tools/webidl_binder.bat + +bootstrap.exe +em++.exe +emcc.exe +em-config.exe +emar.exe +embuilder.exe +emcmake.exe +emconfigure.exe +emdump.exe +emdwp.exe +emmake.exe +emnm.exe +empath-split.exe +emprofile.exe +emranlib.exe +emrun.exe +emscan-deps.exe +emscons.exe +emsize.exe +emstrip.exe +emsymbolizer.exe +tools/file_packager.exe +tools/webidl_binder.exe +test/runner.exe + diff --git a/.mypy.ini b/.mypy.ini deleted file mode 100644 index 6974ee7bb8f0c..0000000000000 --- a/.mypy.ini +++ /dev/null @@ -1,15 +0,0 @@ -[mypy] -mypy_path = third_party/,third_party/ply,third_party/websockify -files = . -exclude = (?x)( - cache | - third_party | - conf\.py | - emrun\.py | - tools/scons/site_scons/site_tools/emscripten/__init__\.py | - site/source/get_wiki\.py | - test/parse_benchmark_output\.py - ) - -[mypy-tools.create_dom_pk_codes,tools.webidl_binder,tools.toolchain_profiler,tools.filelock,tools.find_bigvars,leb128,ply.*] -ignore_errors = True diff --git a/AUTHORS b/AUTHORS index 312de41a8d90d..aa0e91eebc7ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -597,3 +597,9 @@ a license to everyone to use it as detailed in LICENSE.) * James Hu * Jerry Zhuang * Taisei Kon +* YAMAMOTO Takashi +* Artur Gatin (copyright owned by Teladoc Health, Inc.) +* Christian Lloyd (copyright owned by Teladoc Health, Inc.) +* Sean Morris +* Mitchell Wills (copyright owned by Google, Inc.) +* Han Jiang diff --git a/ChangeLog.md b/ChangeLog.md index 75f7d15036dfa..2e1d037f5970f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,8 +18,824 @@ to browse the changes between the tags. See docs/process.md for more on how version tagging works. -3.1.56 (in development) +5.0.6 (in development) +---------------------- +- The minimum version of node supported by the generated code was bumped from + v12.22.0 to v18.3.0. (#26604) +- The DETERMINISIC settings was marked as deprecated (#26653) +- Some musl-internal headers are no longer installed into the sysroot include + directory. In particular, `syscall_arch.h` no longer exists, but can be + replaced with `emscripten/syscalls.h`. (#26658) + +5.0.5 - 04/03/26 +---------------- +- C++ exceptions are now always thrown as CppException objects rather than raw + pointers/numbers. However, the `.message` and `.stack` fields of the thrown + object will only be populated if `-sEXCEPTION_STACK_TRACES` is set. (#26523) +- `emcmake` no longer automatically injects `--experimental-wasm-threads` and + `--experimental-wasm-bulk-memory` flags when used with versions of node older + than v16. (#26560) +- SDL3 port updated from 3.2.30 to 3.4.2 (#26572) +- Fixed a race condition in syscall proxying that caused some hangs and ASan + errors (#26582) + +5.0.4 - 03/23/26 +---------------- +- `EXPORT_EXCEPTION_HANDLING_HELPERS` is deprecated and setting it will not do + anything. `getExceptionMessage` is exported anyway when `ASSERTIONS` or + `EXCEPTION_STACK_TRACES` is set, which are set by default at `-O0`. At `-O1` + or above, you can export it separately by + `-sEXPORTED_RUNTIME_METHODS=getExceptionMessage,decrementExceptionRefcount`. + (#26499) +- The deprecated `EMSCRIPTEN` macro is now defined in `emscripten.h` rather than + on the command line (`__EMSCRIPTEN__`, which is built into LLVM, should be + used instead). (#26417) +- All pthread functions are now undefined when building with `-sWASM_WORKERS`. + This is an extension of #26336 which removed many of them. These APIs were + not previously functional under Wasm Workers, but if there is strong use case + it may be possible to enable them in future. (#26487) +- pipe2 implementation was added (with limited flag support) (#26480) +- ppoll and pselect implementations were added (#26482) + +5.0.3 - 03/14/26 +---------------- +- The low level FS.write API now only accepts TypedArray. The higher level + writeFile and createDataFile file still also accept string and Array. + (#26413) +- Warn on usage of the deprecated `EMSCRIPTEN` macro (`__EMSCRIPTEN__` should + be used instead). (#26381) +- The `-sRELOCATABLE` setting was effectively removed (moved to legacy + settings). This setting was deprecated in #25265 and has not been used + internally since #25522. +- When building with `-sWASM_WORKERS` emscripten will no longer include pthread + API stub functions. These stub functions where never designed to work under + Wasm Workers, so its safer to error at link time if pthread APIs are used + in Wasm Worker-based programs. (#26336) +- SDL2 port updated to include stub functions for `SDL_hid_init()` and related + functions. (#26297) +- libpng port updated from 1.6.39 to 1.6.55. (#26388) +- Added sdl3_ttf port. (#24601) + +5.0.2 - 02/25/26 +---------------- +- The `NODEJS_CATCH_REJECTION` setting was removed. This setting only has an + effect when targeting very old versions of node (< 15). Its trivial to replace + with a simple `--pre-js` file or with the `--unhandled-rejections=strict` + command line flag which it essentially emulates. Versions of node above v15 + have this behavior by default. (#26330) +- The `NODEJS_CATCH_EXIT` setting was removed. This setting was disabled by + default in #22257, and is no longer used by emscripten itself. It is also + problematic as it injects a global process.on handler. It is easy to replace + with a simple `--pre-js` file for those that require it. (#26326) +- The following APIs are now available in Wasm Workers: + - emscripten_futex_wait + - emscripten_futex_wake + - emscripten_is_main_runtime_thread + - emscripten_is_main_browser_thread + (#26325) +- Several low level emscripten APIs that return success/failure now return the + C `bool` type rather than `int`. For example `emscripten_proxy_sync` and + `emscripten_is_main_runtime_thread`. (#26316) +- SDL2 port updated from 2.32.8 to 2.32.10. (#26298) +- The remaining launcher scripts (e.g. `emcc.bat`) were removed from the git + repository. These scripts are created by the `./bootstrap` script which + must be run before the toolchain is usable (for folks using a git checkout of + emscripten). (#26247) + +5.0.1 - 02/13/26 +---------------- +- `logReadFiles` was removed from the default `INCOMING_MODULE_JS_API` list. + To use this feature you now need to explictly add `logReadFiles` to + `INCOMING_MODULE_JS_API`. (#26190); +- Support for the `WASM_OBJECT_FILES` setting (which has been long deprecated) + was removed. `-flto` enables bitcode object files, otherwise Wasm object + files have been the default for a long time already. (#26219). + +5.0.0 - 01/24/26 +---------------- +- Source map's 'names' field support is removed, because the way we used it was + inconsistent with JS and was not supported in browser devtools. We plan to + provide this information using Scopes encoding later. (#26149) +- compiler-rt, libcxx, libcxxabi, libunwind, and llvm-libc were updated to LLVM + 21.1.8. (#26036, #26045, #26058, and #26151) +- Calling pthread_create in a single-threaded build will now return ENOTSUP + rather then EAGAIN. (#26105) +- compiler-rt and libunwind were updated to LLVM 21.1.8. (#26036 and #26045) +- A new `-sEXECUTABLE` setting was added which adds a #! line to the resulting + JavaScript and makes it executable. This setting defaults to true when the + output filename has no extension, or ends in `.out` (e.g. `a.out`) (#26085) +- Embind now supports the JS iterable protocol on bound classes via + `class_::iterable()`. `register_vector` uses this so bound `std::vector` + works with `for...of`/`Array.from()`/spread. (#25993) +- ASYNCIFY/JSPI functions in JS library files can now be marked as `__async: + 'auto'`, which allows async JS function to be used unmodified with + ASYNCIFY/JSPI. In addition, when such function are also marked as `__proxy: + 'sync'` it is now possible for them to be called from back background threads + with the same blocking semantics. (#26130, #26019, #26000) +- SDL3 port updated to 3.2.30. (#26135) + +4.0.23 - 01/10/26 +----------------- +- The inconsistency of incrementing / decrementing refcounts between Wasm EH and + Emscripten EH has been fixed. See `test_EXPORT_EXCEPTION_HANDLING_HELPERS` in + `test_core.py` to see the usage. (#25988) +- The `select()` and `poll()` system calls can now block under certain + circumstances. Specifically, if they are called from a background thread and + file descriptors include pipes. (#25523, #25990) +- It is now possible to load emscripten-generated code directly into an Audio + Worklet context without using the `-sAUDIO_WORKLET` setting (which depends on + shared memory and `-sWASM_WORKERS`). To do this, build with + `-sENVIRONMENT=worklet`. In this environment, because audio worklets don't + have a fetch API, you will need to either use `-sSINGLE_FILE` (to embed the + Wasm file), or use a custom `instantiateWasm` callback to supply the + Wasm module yourself. (#25942) +- Upgraded `contrib.lua` to Lua version 5.5.0 (#26033) + +4.0.22 - 12/18/25 +----------------- +- Source maps now support 'names' field with function name information. + emsymbolizer will show function names when used with a source map. The size + of source maps may increase 2-3x and the link time can increase slightly due + to more processing on source map creation. (#25928) +- Emscripten will now cache the JS code that it generates and re-use when + linking with the same settings at a later date. This should improve link + times generally but should especially noticeable when linking lots of small + programs such as during autoconf or CMake feature detection. (#25929) +- The minimum version of python required to run emscripten was updated from 3.8 + to 3.10. (#25891) + +4.0.21 - 12/02/25 +----------------- +- The `-sPROXY_TO_WORKER` setting (along with the corresponding + `--proxy-to-worker` flag) was removed due to lack of usage. If you were + depending on this feature but missed the PSA, please let us know about your + use case. (#25645, #25440) +- The fetch library now supports streaming data requests when + `-sFETCH_STREAMING` is enabled. +- A new `NODE_HOST_ENV` setting was added which exposes the host environment + variables to the generated program, when running under Node. This setting is + enabled by default when `-sNODERAWFS` is used but can also be controlled + separately. (#18820) +- A new `-sFAKE_DYLIBS` setting was added. When enabled you get the current + emscripten behavior of the `-shared` flag, which is to produce regular object + files instead of actual shared libraries (side modules). Because this + setting is enabled by default this doesn't change the default behavior of the + compiler. If you want to experiment with real shared libraries you can + explicitly disable this setting. (#25826) + +4.0.20 - 11/18/25 +----------------- +- Linker flags specified on the command line are now passed to `wasm-ld` after + the internal emscripten linker flags. This means that users can now override + emscripten defaults with things `-Wl,--stack-first`. (#25803) +- Added `emscripten_html5_remove_event_listener` function in `html5.h` in order + to be able to remove a single callback. (#25535) +- The standalone `file_packager.py` script no longer supports `--embed` with JS + output (use `--obj-output` is now required for embedding data). This usage + has been producing a warning since #16050 which is now an error. (#25049) +- Embind now requires C++17 or newer. (#25773) + +4.0.19 - 11/04/25 +----------------- +- The `RETAIN_COMPILER_SETTINGS` setting and the corresponding + `emscripten_get_compiler_setting` API no longer store or report internal + compiler settings (those listed in `settings_internal.js`). We made an + exception here for `EMSCRIPTEN_VERSION` which is the only internal setting + where we could find usage of `emscripten_get_compiler_setting` (in a global + GitHub search). (#25667) +- When using dynamic linking the main module is no longer built as a relocatable + binary. This will significantly reduce the overhead of dynamic linking for + the main program, for example, eliminating all internal relocations. If you + encounter any issues with new default it is possible to revert to the old + behaviour by adding `-sRELOCATABLE` when linking the main module. (#25522) + +4.0.18 - 10/24/25 +----------------- +- The `emrun.py` script no longer support running on python2. (#25597) +- `-sUSE_WEBGPU` was removed in favor of the external port Emdawnwebgpu which + are used via `--use-port=emdawnwebgpu`. See 4.0.10 release notes for details. +- A new `CROSS_ORIGIN` setting was added in order to work around issues hosting + emscripten programs across different origins (#25581) +- The binary data encoding for `SINGLE_FILE` mode was changed from base64 to + directly embed binary data into UTF-8 string. Users who use the `SINGLE_FILE` + mode along with a custom HTML file should declare the files to have UTF-8 + encoding. See `src/settings.js` docs on `SINGLE_FILE`. Use the option + `-sSINGLE_FILE_BINARY_ENCODE=0` to fall back to base64 encoding. (#25599) + +4.0.17 - 10/17/25 +----------------- +- Mutable Wasm globals can now be exported from native code. Currently these + cannot be declared in C/C++ but can be defined and exported in assembly code. + This currently only works for mutable globals since immutables are already + (and continue to be) exported as plain JS numbers. (#25530) +- Minimum Firefox version was bumped up to Firefox 68 ESR, since older Firefox + versions are not able to run the parallel browser harness: (#25493) + - Firefox: v65 -> v68 +- For windows users, colored console output for error messages and logging now + requires Windows 10 or above. (#25502) +- Fixed an issue from previous release 4.0.16 where "-sENVIRONMENT=worker" was + erroneously made to imply "-sENVIRONMENT=web,worker" (#25514) +- Passing '-sENVIRONMENT=worker' is now disallowed due to being ambiguous in + its meaning. Instead, use '-sENVIRONMENT=web,worker' or + '-sENVIRONMENT=node,worker' to refer to either Web or Node.js multithreading. + +4.0.16 - 10/07/25 +----------------- +- A warning was added about usage of embind without C++17 or above. (#25424) +- The minimum supported versions of Node, Chrome and Firefox were bumped + enabling the removal of the `globalThis` polyfill and universally enabling + mutable globals: (#25375, #25385) + - Node: v10.19.0 -> v12.22.9 + - Chrome: v70 -> v74 + - Firefox: v55 -> v65 +- The Embind `val` functions `call`, `operator()`, and `new_` now support + passing `pointer`s by using the `allow_raw_pointers()` argument. This feature + is only enabled with C++17 and newer. Older versions will allow pointers by + default. + +4.0.15 - 09/17/25 +----------------- +- The `RELOCATABLE` and `LINKABLE` settings were deprecated in favor of the higher + level and better supported `MAIN_MODULE` / `SIDE_MODULE` settings. (#25265) +- The `-gsource-map` flag has been updated to be independent of other types of + debugging effects (in particular it no longer causes the wasm binary to have + a name section, and it no longer suppresses minification of the JS output). + To get the previous behavior, add `-g2` along with `-gsource-map`. + See also the newly updated + [documentation](https://emscripten.org/docs/porting/Debugging.html) which + covers debugging flags and use cases (#25238). +- Ogg port updated to 1.3.5. (#25274) +- Vorbis port updated to 1.3.7. (#25274) +- SDL3 port updated to 3.2.22. (#25273) + +4.0.14 - 09/02/25 +----------------- +- The `-sASYNCIFY_LAZY_LOAD_CODE` setting and the corresponding C function + `emscripten_lazy_load_code` were removed. (#25236) +- The `addRunDependency` and `removeRunDependency` API are now optional and need + to be included and/or exported using, for example, + `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE` or `EXPORTED_RUNTIME_METHODS`. (#24974) +- The `LOAD_SOURCE_MAP` setting was made an internal setting. This was always + an internal detail of the sanitizers, which is enabled automatically when + needed, so setting it explicitly should never be needed. (#24967) +- The wasm offset converter was removed along with the `USE_OFFSET_CONVERTER` + setting. This feature only existed to work around an old v8 bug that was fixed + back in 2019. (#24963) + +4.0.13 - 08/14/25 +----------------- +- The `handle` callback on the `preloadPlugins` used by `--use-preload-plugins` + (and `FS_createPreloadedFile` API`) was converted from callbacks to async. + Any externally managed plugins would need to be updated accordingly. An + assertion will detect any such non-async plugins in the wild. (#24914) +- SDL2 updated from 2.32.0 to 2.32.8. (#24912) +- `sdl-config` and `sdl2-config` scripts were simplified to avoid using python + and the `.bat` file versions were removed, matching upstream SDL. (#24907) +- The `addRunDependency`/`removeRunDependency` now assert in debug builds if + they are not passed an `id` parameter. We have been issuing warnings in + this case since 2012 (f67ad60), so it seems highly unlikely anyone is not + passing IDs here. (#24890). +- The `-sMODULARIZE` setting generates a factory function that must be called + before the program is instantiated that run. However, emscripten previously + had very special case where this instantiation would happen automatically + (with no parameterization) under certain specific circumstances: When + `-sMINIMAL_RUNTIME`, `-sSINGLE_FILE` and `-sMODULARIZE` were used in + combination with default html output. This special case was removed. If you + want to instantiate the module on startup you can still do so by adding a call + the factory function in `--extern-post-js`. (#24874) +- emcc will now error if `MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION` is used + when not generating html output. This was always incompatible but previously + ignored. (#24849) +- emcc will now error if `MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION` or + `MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION` are used with `SINGLE_FILE`. + These are fundamentally incompatible but were previously ignored. (#24849) +- `--export-es6` flag was added to `file_packager.py` available when run + standalone, to enable ES6 imports of generated JavaScript code (#24737) + +4.0.12 - 08/01/25 +----------------- +- The `#!` line that emscripten, under some circumstances, will add to the + generated JS code no longer injects the `--experimental-wasm-bigint` node + flag. This flag is not needed on recent versions of node, and in fact + errors there, so it's not possible to know if it's safe to include. (#24808) +- In `-sMODULARIZE` mode the factory function will now always return a promise, + even when `WASM_ASYNC_COMPILATION` is disabled. This is because emscripten + has other features that might also return async module creation (e.g. loading + files over the network, or other users of the `addRunDependency` API). For + consistency and simplicity we now *always* return a promise here. (#24727) +- libcxx, libcxxabi, libunwind, and compiler-rt were updated to LLVM 20.1.8. + (#24757) +- The `fsblkcnt_t` and `fsfilcnt_t` types used by `statfs`/`statvfs` were + changed from 32-bit to 64-bit. (#24769) +- Support for `-sTEXT_DECODER=0` was removed, due to widespread support for + `TextDecoder`. The remaining valid values for this setting are `=1` + (conditional use of `TextDecoder` with fallback) and `=2` (unconditional use + of `TextDecoder`). (#24700) + +4.0.11 - 07/14/25 +----------------- +- `emdump` tool/script was removed. This tool was mostly useful for analyzing + asm.js code, which emscripten has not generated in a long time now. +- Add support for [Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) + To build with coverage enabled use `-fprofile-instr-generate -fcoverage-mapping`. (#24160) +- The `ENVIRONMENT` setting will now be automatically updated to include + `worker` if multi-threading is enabled. (#24525) +- Removed the `HEADLESS` option. It tried to simulate a minimal browser-like + environment before browser engines had real headless modes. For headless + testing, users are now encouraged to use + [Playwright](https://playwright.dev/), [Puppeteer](https://pptr.dev/) or + Node.js with [JSDOM](https://github.com/jsdom/jsdom) instead. You can also + use browser headless mode with `emrun` as follows: + emrun --browser=chrome --browser-args=--headless [..] + for chrome, or + emrun --browser=firefox --browser-args=-headless [..] + for firefox. (#24537) +- When JSPI is enabled `async` library functions are no longer automatically + wrapped with `WebAssembly.Suspending` functions. To automatically wrap library + functions for use with JSPI they must now explicitly set + `myLibraryFunction__async: true`. (#24550) +- Removed special casing for `size_t` in Embind, since it was also inadvertently + affecting `unsigned long` on wasm64. Both will now match the behaviour of + other 64-bit integers on wasm64 and will be passed as `bigint` instead of + `number` to the JavaScript code. (#24678) + +4.0.10 - 06/07/25 +----------------- +- Emscripten ports now install pkg-config `.pc` files so they will show up, for + example, when you run `pkg-config --list-all` or `pkg-config --cflags + `. Bare in mind that the correct PKG_CONFIG_PATH needs to be set for + this to work. One way to do this is to run `emmake pkg-config`. (#24426) +- libcxx, libcxxabi, and compiler-rt were updated to LLVM 20.1.4. (#24346 and + #24357) +- Emscripten will not longer generate trampoline functions for Wasm exports + prior to the module being instantiated. Storing a reference to a Wasm export + (e.g. `Module['_malloc']`) prior to instantiation will no longer work. In + debug builds we generate stub functions that can detect this case. (#24384) +- The `-sASYNCIFY_LAZY_LOAD_CODE` setting was deprecated. This setting was + added as an experiment a long time ago and as far we know has no active users. + In addition, it cannot work with JSPI (the future of ASYNCIFY). (#24383) +- `-sUSE_WEBGPU` was deprecated in favor of the external port Emdawnwebgpu, a + fork of Emscripten's original bindings, implementing a newer, more stable + version of the standardized `webgpu.h` interface. Please try migrating using + `--use-port=emdawnwebgpu`. If you find issues, verify in the [latest + nightly release](https://github.com/google/dawn/releases) and file feedback + with Dawn. (Emdawnwebgpu is maintained as part of Dawn, the open-source + WebGPU implementation used by Chromium, but it is still cross-browser.) +- The `-sMAYBE_WASM2JS` setting was removed. This was originally added for + debugging purposes, and we now have `-sWASM=2` for folks that want to be able + to fall back to js if wasm fails. (#24176) +- The field `responseUrl` is added to `emscripten_fetch_t`. This is notably + usable for obtaining resolved URL, in line with JS `XMLHttpRequest.responseURL` + field. (#24414) +- `emscripten_fetch_get_response_headers_length` now excludes the trailing + null character from the length calculation to match the documented behaviour. + (#24486) +- `--closure=1` can now be used while preserving readable function names with + `-g2` or `-g`. +- Functions `UTF8ToString`, `UTF16ToString` and `UTF32ToString` take a new + optional `ignoreNul` parameter that allows to ignore the NUL characters and + read the entire string up to the specific byte length. (#24487) + +4.0.9 - 05/19/25 +---------------- +- cmake will not longer detect SDL2 or SDL3 as being present until they are + installed in the sysroot. This means that they now need to be installed, + either indirectly (e.g. by running any emcc command with `-sUSE_SDL=2`) or + directly (e.g. by running `./embuilder build sdl2`). (#24306) +- libunwind was updated to LLVM 20.1.4. (#24251) +- When using cmake the EMSCRIPTEN_FORCE_COMPILERS setting was reverted to + being on by default due to issues that were found with disabling it. (#24223) +- Several symbols from embind (`InternalError`, `BindingError`, + `count_emval_handles`) and from `libbrowser.py` (`requestFullscreen`, + `requestFullScreen`, `createContext`, `getUserMedia`, `setCanvasSize`) are no + longer exported by default. They can be exported using + `-sEXPORTED_RUNTIME_METHODS=requestFullscreen`, for example. (#24223, #24269) +- Embind: fixed support for unsigned 64-bit integers, which were previously + returned to JavaScript as their signed counterparts. (#24285) +- Added handing for 64-bit integer access to AddressSanitizer, `-sSAFE_HEAP` and + `-sSUPPORT_BIG_ENDIAN` features. (#24283) + +4.0.8 - 04/30/25 +---------------- +- Programs built with `-sWASM_WORKERS` and `-sAUDIO_WORKLET` no longer generate + separate `.ww.js` and `.aw.js` files. This is similar to the change that was + already made for pthreads in #21701. This saves on complexity, code size and + network requests. (#24163, #24190) +- Closure arguments can now be used from ports using `settings.CLOSURE_ARGS` + (#24192) +- Embind's `val` now requires a pointer policy when using pointers. e.g. + `(val v(pointer, allow_raw_pointers())`. + +4.0.7 - 04/15/25 +---------------- +- Added experimental support for Wasm ESM integration with + `-sWASM_ESM_INTEGRATION`. This is currently only supported in node behind a + flag and not in any browsers. (#23985) +- Runtime callbacks registered in `Module['preRun']` or `Module['postRun']`, or + using `addOnPreRun()`, `addOnInit()`, `addOnPostCtor()`, `addOnPreMain()`, + `addOnExit()`, or `addOnPostRun()`, are now enqueued and executed following + the order of registration (i.e. `Module['preRun'] = [a, b]`, or equivalently + `addOnPreRun(a); addOnPreRun(b);` will run `a` then `b`; the previous behavior + was to run `b` then `a`). While this might be a breaking change for some users, + the intention is to be more consistent by making those callbacks match the + behavior of `Module['preInit']` and compile time callbacks (rather than the + contrary, as we generally expect an array of functions to be executed left to + right). (#24012) +- The standard memory views (HEAP8, HEAP32, etc) are no longer exported by + default. This matches the existing behaviour of `-sSTRICT` and + `-sMINIMAL_RUNTIME`. If you need to access those from outside the module code + you can export them by adding them to `-sEXPORTED_RUNTIME_METHODS`. For + example, `-sEXPORTED_RUNTIME_METHODS=HEAP8,HEAPU32` (if you need `HEAP8` and + `HEAPU32`). (#24079) +- libjpeg port updated from 9c to 9f. (#24085) +- Missing exports in EXPORTED_RUNTIME_METHODS will now error instead of warn. + +4.0.6 - 03/26/25 +---------------- +- Added support for applying path prefix substitution to the sources of the + source map : use `-sSOURCE_MAP_PREFIXES=["="]` with `-gsource-map`. + Alternatively, you can now embed the sources content into the source map file + using `-gsource-map=inline`. (#23741) +- The python `__file__` builtin now works in the emscripten config file. + (#23973) +- Three deprecated settings were removed. These settings were marked as + deprecated for more than year: + - SUPPORT_ERRNO: Instead, export `__errno_location` if needed. + - EXTRA_EXPORTED_RUNTIME_METHODS: Instead use EXPORTED_RUNTIME_METHODS. + - DEMANGLE_SUPPORT: Instead use the `$demangle` JS library function. + (#23975) + +4.0.5 - 03/12/25 +---------------- +- Added initial support for wasm source phase imports via + `-sSOURCE_PHASE_IMPORTS`. This is currently experimental and not yet + implemented in browsers. (#23175) +- The `FS.allocate` API was removed. This was originally intended to + implement the fallocate/posix_fallocate system calls, but without the ability + to punch holes (`FALLOC_FL_PUNCH_HOLE`) the `FS.truncate` API is sufficient + for resizing files. + +4.0.4 - 02/25/25 +---------------- +- An initial port of SDL3 was added. Use it with `-sUSE_SDL=3`. This port + is still experimental. (#23630) +- The `--output_eol` command line flag was renamed `--output-eol` for + consistency with other flags. The old name continues to work as an alias. + (#20735) +- Added Lua contrib port (`--use-port=contrib.lua`) to easily embed the Lua + scripting language in any C/C++ Emscripten project (#23682) +- The `USE_ES6_IMPORT_META` settings was removed. This setting was always + on by default, but now it cannot be disabled. This setting was originally + added in 2019 as a temporary measure while engines and bundlers learned to + deal with `import.meta`. (#23171) + +4.0.3 - 02/07/25 +---------------- +- emscan-deps tools was added. This tool wraps clang-scan-deps and injects the + needed `--target` and `--sysroot` argument that would normally be injected by + emcc itself. This enables support for C++20 in cmake projects. (#21987) +- The version of python required to run emscripten was bumped from 3.6 to 3.8. + (#23417) +- The `EM_LOG_C_STACK` flag to `emscripten_log` was deprecated and the helper + file on which it was based (`emscripten-source-map.min.js`) deleted. This + feature (userspace source map parsing in logs) was never ported to wasm + source maps, so it has not worked in many years, and there have been no + requests for it. This has no impact on the source map support in browser + devtools. (#23553) +- The WASMFS fetch backend now fetches files in chunks using HTTP range + requests (if supported by the server). `wasmfs_create_fetch_backend` now + takes a second parameter (`uint32_t chunk_size`) to configure the size of + each chunk. If a file is read a few times with random accesses, a small + chunk size will minimize bandwidth; if a file is read in larger contiguous + ranges, a larger chunk size will reduce the number of requests. (#23021) + +4.0.2 - 01/30/25 +---------------- +- The standard Wasm EH, enabled by `-sWASM_LEGACY_EXCEPTIONS=0`, now uses the + LLVM backend implementation rather than the previously used Binaryen + translator + (https://github.com/WebAssembly/binaryen/blob/main/src/passes/TranslateEH.cpp). + (#23469) No specific action from the user is required. +- Added support for compiling AVX2 intrinsics, 256-bit wide intrinsic is emulated + on top of 128-bit Wasm SIMD instruction set. (#23035). Pass `-msimd128 -mavx2` + to enable targeting AVX2. +- The system JS libraries in `src/` were renamed from `library_foo.js` to + `lib/libfoo.js`. They are still included via the same `-lfoo.js` flag so + this should not be a user-visible change. (#23348) +- When using cmake the emscripten toolchain will no longer skip the toolchain + detection stages. This means the initial cmake run will be slower, but will + result in more accurate information. If cmake is running too slow for you, + you can revert to the previous behaviour with `-DEMSCRIPTEN_FORCE_COMPILERS=ON`. + +4.0.1 - 01/17/25 +---------------- +- The minimum version of node required to run emscripten was bumped from v16.20 + to v18.3. Version 4.0 was mistakenly shipped with a change that required v20, + but that was reverted. (#23410) +- `emscripten_webgl_create_context` now displays a warning message when there is + a conflict between the `majorVersion` requested and the WebGL support defined + via linker flags (`MIN_WEBGL_VERSION` and `MAX_WEBGL_VERSION`). This warning + will be turned into a hard failure in a future release. (#23372, #23416) +- zlib port updated from 1.2.13 to 1.3.1. (#23462) + +4.0.0 - 01/14/25 +---------------- +- Emscripten version was bumped to 4.0.0. Happy new year, happy new major + version! While version has a few interesting changes, there is nothing huge + that makes it different from any other release. (#23235) +- `-sWASM_LEGACY_EXCEPTIONS` option is added. (#23365) If true, it will emit + instructions for the legacy Wasm exception handling proposal + (https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md), + and if false, the new standardized exception handling proposal + (https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md). + This option defaults to true, given that major web browsers do not support the + new proposal by default yet. This option replaces the existing + `-sWASM_EXNREF`, whose meaning was the opposite. +- compiler-rt, libcxx, libcxxabi, and libunwind were updated to LLVM 19.1.6. + (#22937, #22994, and #23294) +- The default Safari version targeted by Emscripten has been raised from 14.1 + to 15.0 (the `MIN_SAFARI_VERSION` setting) (#23312). This has several effects: + - The Wasm nontrapping-fptoint feature is enabled by default. Clang will + generate nontrapping (saturating) float-to-int conversion instructions for + C typecasts. This should have no effect on programs that do not have + undefined behavior but if the casted floating-point value is outside the range + of the target integer type, the result will be a number of the max or min value + instead of a trap. This also results in a small code size improvement because + of details of the LLVM IR semantics. This feature can be disabled in clang with + the `-mno-nontrapping-fptoint` flag. (#23007) + - The `WASM_BIGINT` feature is enabled by default. This has the effect that + Wasm i64 values are passed and returned between Wasm and JS as BigInt values + rather than being split by Binaryen into pairs of Numbers. (#22993) + - The `BULK_MEMORY` feature is enabled by default. `memory.copy` and + `memory.fill` instructions are used in the implementation of C `memcpy` and + `memset`, and Clang may generate them elsewhere (#22873). It can be + disabled with the `-mno-bulk-memory -mno-bulk-memory-opt` flags. +- When using `-sMODULARIZE` we now assert if the factory function is called with + the JS `new` keyword. e.g. `a = new Module()` rather than `b = Module()`. + This paves the way for marking the function as `async` which does not allow + `new` to be used. This usage of `new` here was never documented and is + considered an antipattern. (#23210) +- `PATH.basename()` no longer calls `PATH.normalize()`, so that + `PATH.basename("a/.")` returns `"."` instead of `"a"` and + `PATH.basename("a/b/..")` returns `".."` instead of `"a"`. This is in line with + the behaviour of both node and coreutils, and is already the case when using + NODERAWFS". (#23180) +- The factory function exposed in `-sMODULARIZE` mode is now marked as `async` + when `WASM_ASYNC_COMPILATION` is enabled (the default). This allows us to use + `await` during module creation. One side effect of this is that code in + `--post-js` files will now be delayed until after module creation and after + `main` runs. This matches the existing behaviour when using sync instantation + (`-sWASM_ASYNC_COMPILATION=0`) but is an observable difference. (#23157) +- The `POLYFILL_OLD_MATH_FUNCTIONS` setting was removed. The browser versions + that require these polyfills are no longer supported by emscripten so the + polyfills should never be needed. (#23262) +- JavaScript libraries can now be specified via `-lfoo.js`. This works like the + existing `--js-library` flag but will search the library path (all paths + specified with `-L`) for `libfoo.js`. (#23338) +- The `mallinfo` struct members are now defined as `size_t` which makes them + compatible with larger memories, and is also how linux defines them. (#23368) +- Emscripten now uses the debug version of malloc (i.e. assertions enabled) + when linking in debug mode (`-O0` and/or `-sASSERTIONS`). This means that + things like double-free will be detected in these builds. Previously this was + only true with `-sASSERTIONS=2`. (#23330) +- The code generated in `--proxy-to-worker` no longer contains support for + reading the `?noProxy` URL parameter (this was not documented or tested). + (#23297) + +3.1.74 - 12/14/24 +----------------- +- The file system was updated to independently track atime, mtime and ctime + instead of using the same time for all three. (#22998) +- Emscripten-generated code will now use async/await internally when loading + the Wasm module. This will be lowered away by babel when targeting older + browsers. (#23068) +- Due to the discontinued support for invalid specializations of + `std::basic_string` (https://github.com/llvm/llvm-project/pull/72694), the + support for `std::basic_string` was removed from embind. + (#23070) +- The minimum supported versions of browser engines that we support were updated + to versions that support Promise, Fetch and Object.assign APIs, allowing the + polyfills for these to be removed. Chrome 32 -> 45, Firefox 34 -> 40, Safari + 9.0 -> 10.1. These browser engines version are all over 8 years old now. + (#23077, #23118) + +3.1.73 - 11/28/24 +----------------- +- libunwind was updated to LLVM 19.1.4. (#22934) +- mimalloc was updated to 2.1.7. (#21548) + +3.1.72 - 11/19/24 +----------------- +- The `MEMORY64` setting is no longer experimental. At time of writing all + browsers still require a flag to run the resulting binaries but that should + change in the coming months since the proposal is now at stage 4. (#22864) +- GLFW: Fixed regression introduced in 3.1.51. CSS scaling is now available + again. Note that CSS scaling is disabled in HiDPI mode. (#22847, #22900) + +3.1.71 - 11/04/24 +----------------- +- SDL2 port updated to 2.30.9. (#22830) +- LLVM's `-Wnontrivial-memaccess` warning has been updated to also warn about + passing non-trivially-copyable destination parameter to `memcpy`, + `memset` and similar functions for which it is a documented undefined + behavior (#22798). See https://github.com/llvm/llvm-project/pull/111434 +- The automatic fallback to `$HOME/.emscripten_cache` when the emscripten + directory is read-only was removed. This automatic behaviour could cause + confusion. Anyone who really wants to use `$HOME/.emscripten_cache` can + still do so either via an environment variable (`EMCC_CACHE`) or via a config + file setting `CACHE`. +- The standalone `file_packager.py` tool now outputs modern JS (specifically it + includes nullish assignment). If you use this output directly and you want + to support older browsers you may need to transpile it. If you use + `file_packager` via emcc the output will be transpiled as part of the emcc + output. (#22805) + +3.1.70 - 10/25/24 +----------------- +- Improvements to Audio Worklet support (#22731, #22681) +- Small improvements to embind (#22734) + +3.1.69 - 10/12/24 +----------------- +- The usage of `EM_BOOL` in the emscripten API has been replaced with C/C++ + bool. This change should not be observable since `EM_BOOL` has been + equivalent to `bool` since #22157. (#22155) +- Fix regression introduced in 3.1.67 (#22557) which broke webgpu / int64 + integration. (#22689) +- SDL2 port updated from 2.28.4 to 2.30.8. (#22697) +- embind no longer exports any library functions by default. Previously we + would export getInheritedInstanceCount, getLiveInheritedInstances, + flushPendingDeletes and setDelayFunction. If you need these library function + exported they can be added to `EXPORTED_RUNTIME_METHODS`. (#22705) + +3.1.68 - 09/30/24 +----------------- +- Added support for compiling 256-bit wide AVX intrinsics, emulated on top + of 128-bit Wasm SIMD instruction set. (#22430). Pass `-msimd128 -mavx` to + enable targeting AVX. +- Pthread-based programs no longer generates `.worker.js` file. This file was + made redundant back in 3.1.58 and now is completely removed. (#22598) +- The freetype port was updated from v2.6 to v2.13.3. (#22585) +- The number of arguments passed to Embind function calls is now only verified + with ASSERTIONS enabled. (#22591) +- Optional arguments can now be omitted from Embind function calls. (#22591) +- Recent changes to Binaryen included in this version significantly improve + the speed at which the post-link optimizations run for some large programs. + +3.1.67 - 09/17/24 +----------------- +- Add option `nonnull()` to Embind to omit `| null` from TS definitions + for functions that return pointers. + +3.1.66 - 09/10/24 +----------------- +- The behaviour of the `pthread_kill` function was fixed to match the spec + and will now run the designated handler on the target thread. (#22467) +- Added support for WebGL extensions EXT_clip_control, EXT_depth_clamp, + EXT_polygon_offset_clamp and WEBGL_polygon_mode (#20841) +- New `emscripten_console_trace` and `emscripten_dbg_backtrace` APIs we were + added to `console.h`. The former simply maps directly to `console.trace`. + The latter uses `dbg()` so it writes directly to stderr under node (better for + multi-threaded apps). + +3.1.65 - 08/22/24 +----------------- +- A new `--emit-minification-map` command line flag was added, which can be used + to emit a minification map in the case that import/export minification is + performed (this happens at higher optimization levels). (#22428) +- Remove `Module['quit']` handling. This could be used to override the internal + method for shutting down the program, but it was neither documented nor + tested. Programs that want to intercept the shutting down of a program can + use `Module['onExit']`. (#22371) +- The `NODEJS_CATCH_EXIT` setting is now disabled by default. This setting + is only useful under very specific circumstances, and has some downsides, so + disabling it by default makes sense. (#22257) +- Add WebP (`.webp`) decoding support in file preloading. (#22282) + +3.1.64 - 07/22/24 ----------------------- +- Updated the SCons tool to not require the `EMSCRIPTEN_ROOT` environment + variable, in which case it will assume that SCons will find the binaries in + (its) `PATH`. +- Updated `emscons` to apply the `EMSCRIPTEN_ROOT`, `EMSCONS_PKG_CONFIG_LIBDIR` + and `EMSCONS_PKG_CONFIG_PATH` environment variables. The SCons tool will use + last two to set up `PKG_CONFIG_LIBDIR` and `PKG_CONFIG_PATH` respectively. + +3.1.63 - 07/12/24 +----------------- +- Fix html5 input event bug that was introduced in 3.1.62. (#22201) +- Fix webpack + pthreads bug that was introduced in 3.1.60. (#22165) + +3.1.62 - 07/02/24 +----------------- +- The `EM_BOOL` type changed from `int/u32` to `bool/u8`. This changes the + layout and size of some structs in the emscripten API. (#22157) +- The `EMSCRIPTEN_FETCH_WAITABLE` flag along with the `emscripten_fetch_wait` + API were marked a deprecated. These feature have not functions for several + years now. (#22138) +- The internal `read_` function was removed. We now just use `readBinary` or + `readAsync`. (#22080) +- reference-types feature is now enabled by default in Emscripten, due to the + upstream LLVM change (https://github.com/llvm/llvm-project/pull/93261). +- Emscripten now uses `strftime` from musl rather than using a custom + JavaScript implementation. (#21379) +- Embind now supports return value policies for properties. + +3.1.61 - 05/31/24 +----------------- +- The internal `readAsync` function now returns a promise rather than accepting + callback arguments. +- The JSPI feature now uses the updated browser API for JSPI (available in + Chrome v126+). To support older versions of Chrome use Emscripten version + 3.1.60 or earlier. +- IDBFS mount has gained a new option { autoPersist: true }, which if passed, + changes the semantics of the IDBFS mount to automatically persist any changes + made to the filesystem. (#21938) + +3.1.60 - 05/20/24 +----------------- +- Under nodefs, symbolic links to files outside of mount locations no longer work. + This reverts the previous behaviour added in #3277. (#21805) +- The `EXPORTED_FUNCTIONS` list can now include JS library symbols even if they + have not been otherwise included (e.g. via `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`). + (#21867) +- Due to the upstream LLVM changes + (https://github.com/llvm/llvm-project/pull/80923 and + https://github.com/llvm/llvm-project/pull/90792), multivalue feature is now + enabled by default in Emscripten. This only enables the language features and + does not turn on the multivalue ABI. +- Embind now supports return value policies to better define object lifetimes. + See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#object-ownership for more information. + +3.1.59 - 04/30/24 +----------------- +- Fix the location of the dummy `.worker.js` file that is now generated as part + of pthread builds so that is generated alongside the main JavaScript file. + See #21701. () +- `-sASYNCIFY=2` is setting now deprecated, use `-sJSPI` instead. (#21824) + +3.1.58 - 04/23/24 +----------------- +- The `-sMAIN_MODULE=1` mode no longer exports all the main module symbols on + `Module` object. This saves a huge amount of generated JS code due the fact + that `-sMAIN_MODULE=1` includes *all* native symbols in your program as well + is from the standard library. The generated JS code for a simple program + in this mode is reduced from from 3.3mb to 0.5mb. The current implementation + of this feature requires wasm-ld to be on the program twice which could have a + noticeable effect on link times. (#21785) +- In `-sMODULARIZE` mode, the argument passed into the module constructor is + no longer mutated in place. The expectation is that the module instance will + be available via the constructor return value. Attempting to access methods + on the object passed *into* the constructor will now abort. (#21775) +- Enable use of `::` to escape port option separator (#21710) +- In multi-threaded builds `--extern-pre-js` and `--extern-post-js` code is + now only run on the main thread, and not on each of the workers. (#21750) +- Fix crash when throwing exceptions in dynamically linked int64 functions (#21759) +- Multi-threaded builds no longer depend on a separate `.worker.js` file. This + saves on code size and network requests. In order to make this change go + smoothly, without breaking build systems that expect a `worker.js`, emscripten + will generate an empty `.worker.js` to give folks time to transition their + deployment scripts. In `-sSTRICT` mode, this empty file will not be + generated. (#21701) + +3.1.57 - 04/10/24 +----------------- +- libcxx, libcxxabi, libunwind, and compiler-rt were updated to LLVM 18.1.2. + (#21607, #21638, and #21663) +- musl libc updated from v1.2.4 to v1.2.5. (#21598) +- In `MODULARIZE` mode we no longer export the module ready promise as `ready`. + This was previously exposed on the Module for historical reasons even though + in `MODULARIZE` mode the only way to get access to the module is to wait on + the promise returned from the factory function. (#21564) +- JS library code is now executed in its own context/scope, which limits how + much of the compiler internals are accessible. If there are build time JS + symbols that you are depending on, but that were not added to this scope, + please file a bug and we can add more to this scope. (#21542) +- The JS functions for manipulating the native/shadow stack + (`stackSave`/`stackRestore`/`stackAlloc`) are now just regular JS library + function and as such are only included if you explicitly depend on them. If + you use these functions in your JS code you will need to depend on them via + either: + - The `EM_JS_DEPS` macro for `EM_ASM`/`EM_JS` code. + - The `__deps` attribute for JS library functions + - The `-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE` flag for `--pre-js`/`--post-js` + code + (#21555) +- TypeScript definitions for Wasm exports, runtime exports, and embind bindings + can now be generated with `--emit-tsd`. The option `--embind-emit-tsd` has been + deprecated, use `--emit-tsd` instead. +- Added the `ASYNCIFY_PROPAGATE_ADD` setting, to control whether the `ASYNCIFY_ADD` + list propagates or not. By default this is enabled; as a result you may see larger + ASYNCIFY builds as more of the function tree may be instrumented than you were + previously manually specifying in `ASYNCIFY_ADD`. To stop propagation you can + specify functions in the `ASYNCIFY_REMOVE` list, or to return to the previous + behaviour, disable this setting (set `-sNO_ASYNCIFY_PROPAGATE_ADD`.) (#21672) +- ports changes: + - Fixed transitive link dependencies (#21602) + - Enable use of options in ports dependencies (#21629) + +3.1.56 - 03/14/24 +----------------- - emscripten will now generate an `unused-command-line-argument` warning if a `-s` setting is specified more than once on the command line with conflicting values. In this case the first setting is ignored. (#21464) @@ -30,7 +846,7 @@ See docs/process.md for more on how version tagging works. - In `STRICT` mode the `HEAPXX` symbols (such as `HEAP8` and `HEAP32`) are now only exported on demand. This means that they must be added to `EXPORTED_RUNTIME_METHODS` for them to appear on the `Module` object. For - now, this only effects users of `STRICT` mode. (#21439) + now, this only affects users of `STRICT` mode. (#21439) - Emscripten no longer supports `--memory-init-file` (i.e. extracting static data into an external .mem file). This feature was only available under wasm2js (`-sWASM=0`) anyway so this change will only affect users of this @@ -200,7 +1016,7 @@ See docs/process.md for more on how version tagging works. which doesn't use `#if ASSERTIONS` guards around their `assert` calls. This behaviour matches that of `MINIMAL_RUNTIME`. (#20592) - The minimum version of node required run the compiler was updated from - 10.19 to 16.20. This does not effect the node requirements of the generated + 10.19 to 16.20. This does not affect the node requirements of the generated JavaScript code. (#20551) - A new top-level `bootstrap` script was added. This script is for emscripten developers and helps take a care of post-checkout tasks such as `npm install`. @@ -291,7 +1107,7 @@ See docs/process.md for more on how version tagging works. long-deprecated JS API. (#19820) - The internal `read_` and `readAsync` functions no longer handle data URIs. (Higher-level functions are expected to handle that themselves, before calling.) - This only effects builds that use `-sSINGLE_FILE` or `--memory-init-file`. + This only affects builds that use `-sSINGLE_FILE` or `--memory-init-file`. (#19792) - The `asm` property of the Module object (which held the raw exports of the wasm module) has been removed. Internally, this is now accessed via the @@ -456,7 +1272,7 @@ See docs/process.md for more on how version tagging works. `ASSERTIONS` is enabled. This option is mainly for the users who want only exceptions' stack traces without turning `ASSERTIONS` on. (#18642 and #18535) - `SUPPORT_LONGJMP`'s default value now depends on the exception mode. If Wasm - EH (`-fwasm-exception`) is used, it defaults to `wasm`, and if Emscripten EH + EH (`-fwasm-exceptions`) is used, it defaults to `wasm`, and if Emscripten EH (`-sDISABLE_EXCEPTION_CATCHING=0`) is used or no exception support is used, it defaults to `emscripten`. Previously it always defaulted to `emscripten`, so when a user specified `-fwasm-exceptions`, it resulted in Wasm EH + Emscripten @@ -674,7 +1490,7 @@ See docs/process.md for more on how version tagging works. ------------------- - The `getTempRet0`/`setTempRet0` helper functions are now implemented directly in WebAssembly, rather than supplied by the JS host. This simplifies the - wasm/JS interface. These function are no longer exported in all cases. If + wasm/JS interface. These functions are no longer exported in all cases. If your code directly calls these functions from JS, you can add them to `-sEXPORTED_RUNTIME_METHODS`. - Several linux-specific headers were removed from the emscripten sysroot. None @@ -689,7 +1505,7 @@ See docs/process.md for more on how version tagging works. 3.1.18 - 08/01/2022 ------------------- - The tests/ directory was renamed to just test/ to match other project close - emscripten (llvm, wabt, binaryen). This should not effect any users of + emscripten (llvm, wabt, binaryen). This should not affect any users of emscripten, only developers. (#17502) - The llvm version that emscripten uses was updated to 16.0.0 (#17534) - worker.js now propagates unhandled promise rejections to the main thread the @@ -699,7 +1515,7 @@ See docs/process.md for more on how version tagging works. ------------------- - Add support for dynamic linking with Asyncify. (#15893) - A source map file and DWARF info in the wasm can now be emitted at the same if - the user gives the both options: `-g -gsource-map`. (#17484) + the user gives both options: `-g -gsource-map`. (#17484) - The `align` argument to the makeGetValue/makeSetValue JS library macros was removed (and replaced with an assert) as it had no uses internally and was removed (and replaced with an assert) as it had now uses internally and was @@ -716,7 +1532,7 @@ See docs/process.md for more on how version tagging works. - When JS library functions are included as part of `EXPORTED_RUNTIME_METHODS` it is no longer necessary to also add them to `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. This change allows us to transition - runtime functions to JS library functions without the need to folks to add + runtime functions to JS library functions without the need for folks to add `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. (#17369) - The following function, which were previously part of the default runtime, are now JS library functions: @@ -746,7 +1562,7 @@ See docs/process.md for more on how version tagging works. disabled (which it may be in the future) these symbols would only be included if there were explicitly exported via `EXPORTED_RUNTIME_METHODS` or added to `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. `LEGACY_RUNTIME` is disabled by default - in `STRICT` mode so this change only effects users of `STRICT` mode. (#17370, + in `STRICT` mode so this change only affects users of `STRICT` mode. (#17370, #17403) - The `run` runtime function is no longer exported by default. It can be added to `EXPORTED_RUNTIME_METHODS` if needed. @@ -783,9 +1599,9 @@ See docs/process.md for more on how version tagging works. same way as with other wasm targets. This means that within the wasm module the name of the main function can now be `__main_argc_argv`, but, since we still export this to JS as `_main`, this should not be a user-visible change. -- Use of pkg-config from cmake not longer causes the C++ include path to be +- Use of pkg-config from cmake no longer causes the C++ include path to be broken. (#17137) -- `emscripten_runtime_keeplive_push()` and `emscripten_runtime_keeplive_push()` +- `emscripten_runtime_keeplive_push()` and `emscripten_runtime_keeplive_pop()` are now exposed to native code and can be used to keep the runtime alive without immediately unwinding the event loop (as `emscripten_exit_with_live_runtime()` does). (#17160) @@ -809,7 +1625,7 @@ See docs/process.md for more on how version tagging works. to `long` so that it is compatible with `MEMORY64`. The return value of this function sometimes contains a pointer value so `int` is not sufficiently wide under `wasm64`. (#16938) -- The `EM_BUILD_VERBOSE` environment variable only effects test code these days +- The `EM_BUILD_VERBOSE` environment variable only affects test code these days and therefore was renamed to `EMTEST_BUILD_VERBOSE`. (#16904) - compiler-rt updated to LLVM 14. (#16991) - libc++ updated to LLVM 14. (#17000) @@ -887,7 +1703,7 @@ See docs/process.md for more on how version tagging works. - Due to an llvm change (https://reviews.llvm.org/D118573) some clang flags that did not previously have any effect are now honored (e.g. `-fnew-alignment` and `-fshort-wchar`). -- llvm dependency updated to 15.0.0 to match upstream. (#16178) +- llvm dependency updated to 15.0.0 to match upstream. (#16182) - The `__EMSCRIPTEN_major__`, `__EMSCRIPTEN_minor__` and `__EMSCRIPTEN_tiny__` macros are now available via the `emscripten/version.h` header file. For the time being, unless you enable `-sSTRICT`, these are still also defined @@ -976,7 +1792,7 @@ See docs/process.md for more on how version tagging works. - The example `shell.html` and `shell_minimal.html` templates no longer override `printErr` on the module object. This means error message from emscripten and stderr from the application will go to the default location of `console.warn` - rather than `console.error`. This only effects application that use the + rather than `console.error`. This only affects applications that use the example shell html files. - The version of musl libc used by emscripten was upgraded from v1.1.15 to v1.2.2. There could be some minor size regressions (or gains) due to changes @@ -1068,7 +1884,7 @@ See docs/process.md for more on how version tagging works. ------------------- - Added `EM_ASYNC_JS` macro - similar to `EM_JS`, but allows using `await` inside the JS block and automatically integrates with Asyncify without - the need for listing the declared function in `ASYNCIFY_IMPORTS` (#9709). + the need for listing the declared function in `ASYNCIFY_IMPORTS` (#14728). - Errors that occur on pthreads (e.g. uncaught exception) will now get re-thrown on the main thread rather than simply being logged (#13666). @@ -1097,7 +1913,7 @@ See docs/process.md for more on how version tagging works. the library is loaded and then continue on as if dlopen was blocking. For users who don't want to use `ASYNCIFY` (which has a size and runtime cost) there is a async (callback-based) version of the dlopen API available as - `emscripten_dlopen()` declared in `emscropten/emscripten.h`. See + `emscripten_dlopen()` declared in `emscripten/emscripten.h`. See `docs/api_reference/emscripten.h.rst` (or the online version) for more details. - Constructors, functions and methods bound with Embind can now be `await`ed. @@ -1129,19 +1945,19 @@ See docs/process.md for more on how version tagging works. - Drop support for node versions older than v5.10.0. We now assume the existence of `Buffer.from` which was added in v5.10.0. If it turns out there is still a need to support these older node versions we can - add a polyfil under LEGACY_VM_SUPPORT (#14447). + add a polyfill under LEGACY_VM_SUPPORT (#14447). 2.0.24 - 06/10/2021 ------------------- - Support `--preload-file` in Node.js. (#11785) - System libraries are now passed to the linker internally via `-lfoo` rather than using their full path. This is in line with how gcc and clang pass system - libraries to the linker. This should not effect any builds unless a project a + libraries to the linker. This should not affect any builds unless a project a happens to have, for example, a file called `libc.a` in one of its library paths. This would have the effect of overriding the system library (as it would with gcc or clang) (#14342). - CMake projects (those that either use emcmake or use Emscripten.cmake - directly) are new configured to install (by default) directly into the + directly) are now configured to install (by default) directly into the emscripten sysroot. This means that running `cmake --install` (or running the install target, via `make install` for example) will install resources into the sysroot such that they can later be found and used by `find_path`, @@ -1151,7 +1967,7 @@ See docs/process.md for more on how version tagging works. their own secondary sysroot may be able to simplify their build system by removing this completely and relying on the new default. - Reinstated the warning on linker-only `-s` settings passed when not linking - (i.e. when compiling with `-c`). As before this can disabled with + (i.e. when compiling with `-c`). As before this can be disabled with `-Wno-unused-command-line-argument` (#14182). - Standalone wasm mode no longer does extra binaryen work during link. It used to remove unneeded imports, in hopes of avoiding nonstandard imports that @@ -1227,7 +2043,7 @@ See docs/process.md for more on how version tagging works. compile-only (`-c`) mode. Just like with clang itself, this warning can be disabled using the flag: `-Wno-unused-command-line-argument`. - When building with `-s MAIN_MODULE` emscripten will now error on undefined - symbol by default. This matches the behvious of clang/gcc/msvc. This + symbol by default. This matches the behaviour of clang/gcc/msvc. This requires that your side modules be present on the command line. If you do not specify your side modules on the command line (either directly or via `RUNTIME_LINKED_LIBS`) you may need to add `-s WARN_ON_UNDEFINED_SYMBOLS=0` to @@ -1325,7 +2141,7 @@ See docs/process.md for more on how version tagging works. due to the code size that it adds. (MINIMAL_RUNTIME=1 implies SUPPORT_ERRNO=0 by default) Pass -s SUPPORT_ERRNO=1 to enable errno support if necessary. - Using EM_ASM and EM_JS in a side module will now result in an error (since - this is not implemented yet). This could effect users were previously + this is not implemented yet). This could affect users were previously inadvertently including (but not actually using) EM_ASM or EM_JS functions in side modules (#13649). - Remove dependency on Uglify by finishing the rewrite of passes to acorn @@ -1382,10 +2198,10 @@ See docs/process.md for more on how version tagging works. - The system for linking native libraries on demand (based on the symbols present in input object files) has been removed. Libraries such as libgl, libal, and libhtml5 are now included on the link line by default unless - `-s AUTO_NATIVE_LIBRARIES=0` is used. This should not effect most builds + `-s AUTO_NATIVE_LIBRARIES=0` is used. This should not affect most builds in any way since wasm-ld ignores unreferenced library files. Only users of the `--whole-archive` linker flag (which is used when `MAIN_MODULE=1` is - set) should be effected. + set) should be affected. 2.0.12: 01/09/2021 ------------------ @@ -1417,16 +2233,16 @@ See docs/process.md for more on how version tagging works. Breaking change: This new setting is required if you provide a runtime value for `wasmMemory` or `INITIAL_MEMORY` on the Module object. - Internally, emscripten now uses the `--sysroot` argument to point clang at - it headers. This should not effect most projects but has a minor effect the + it headers. This should not affect most projects but has a minor effect the order of the system include paths: The root include path - (`/system/include`) is now always last in the include path. + (`/system/include`) is now always last in the include path. - Fix JS pthreads proxying + WASM_BIGINT (#12935) - Optimize makeDynCall to use dynCall_xx function directly where needed (#12741) 2.0.9: 11/16/2020 ----------------- -- dlopen, in conformace with the spec, now checks that one of either RTDL_LAZY - or RTDL_NOW flags ar set. Previously, it was possible set nether of these +- dlopen, in conformance with the spec, now checks that one of either RTDL_LAZY + or RTDL_NOW flags are set. Previously, it was possible set neither of these without generating an error. - Allow `-lSDL2_mixer` to just work. (Others like `-lSDL2` always worked, but for `SDL2_mixer` things were broken because we build multiple variations of @@ -1589,7 +2405,7 @@ See docs/process.md for more on how version tagging works. - The default output format is now executable JavaScript. Previously we would default to output objecting files unless, for example, the output name ended in `.js`. This is contrary to behaviour of clang and gcc. Now emscripten - will always produce and executable unless the `-c`, `-r` or `-shared` flags + will always produce an executable unless the `-c`, `-r` or `-shared` flags are given. This is true even when the name of the output file ends in `.o`. e.g, `emcc foo.c -o foo.o` will produce a JavaScript file called `foo.o`. This might surprise some users (although it matches the behavior of existing @@ -1677,7 +2493,7 @@ See docs/process.md for more on how version tagging works. - It is now an error if a function listed in the `EXPORTED_FUNCTIONS` list is missing from the build (can be disabled via `-Wno-undefined`) (ERROR_ON_UNDEFINED_SYMBOLS and WARN_ON_UNDEFINED_SYMBOLS no longer apply - to these symbols which are explicly exported). + to these symbols which are explicitly exported). - Support for pthreads with wasm2js (`WASM=0`; #11505). - Rename `emscripten/math.h` to `emscripten/em_math.h` because if a user adds `emscripten/` as an include path with `-I`, that can override libc math.h, @@ -1725,7 +2541,7 @@ See docs/process.md for more on how version tagging works. the sample config will still be written there in the case that the emscripten root is read-only. - The default location for downloaded ports is now a directory called "ports" - within the cache directory. In practice these means by default they live + within the cache directory. In practice this means by default they live in `cache/ports` inside the emscripten source directory. This can be controlled by setting the location of the cache directory, or for even more fine grained control the `EM_PORTS` environment variable and the `PORTS` @@ -1840,7 +2656,7 @@ See docs/process.md for more on how version tagging works. a global setting in the emscripten config file that would inject extra compiler options. - Allow spaces in a path to Python interpreter when running emscripten from Unix - shell (#11005). + shell (#11006). - Support atexit() in standalone mode (#10995). This also fixes stdio stream flushing on exit in that mode. @@ -1938,7 +2754,7 @@ v1.39.9: 03/05/2020 - Updated of libc++abi and libc++ to llvm 9.0.0 (#10510) - Refactor syscall interface: Syscalls are no longer variadic (except those that are inherently such as open) and no longer take the syscall number as - arg0. This should be invisible to most users but will effect any external + arg0. This should be invisible to most users but will affect any external projects that try to implement/emulate the emscripten syscall interface. See #10474 - Removed src/library_vr.js, as it was outdated and nonfunctional, and the WebVR @@ -1979,7 +2795,7 @@ v1.39.7: 02/03/2020 - The checked-in copy of the Closure compiler was removed in favor of getting it from npm. This means that developers now need to run `npm install` after checking out emscripten if they want to use closure (--closure). emsdk users - are not effected because emsdk runs this as a post install step (#9989). + are not affected because emsdk runs this as a post install step (#9989). - Added support for specifying JSDoc minification annotations for Closure in JS library, pre and post files. See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler @@ -2048,7 +2864,7 @@ v1.39.4: 12/03/2019 - Remove deprecated `requestFullScreen` method from `library_browser.js`, please use `requestFullscreen` (without the capital S). - Remove deprecated `requestFullScreen` and `cancelFullScreen` from `library_glut.js` -- Remove deprecated `requestFullScreen` and `cancelFullScreen`from `library_glfw.js` +- Remove deprecated `requestFullScreen` and `cancelFullScreen` from `library_glfw.js` - Fix SDL2_mixer support for ogg vorbis. See #9849 - Various source maps fixes, see #9926 #9882 #9837 #9814 @@ -2830,7 +3646,7 @@ v1.36.12: 10/20/2016 - Improved asm.js -s USE_PTHREADS=2 build mode compatibility when multithreading is not supported. - Improved WebGL support with closure compiler (#4619) - - Improved Bianaryen WebAssembly support + - Improved Binaryen WebAssembly support - Added support for GL_disjoint_timer_query extension (#4575) - Improved Emscripten compiler detection with CMake (#4129, #4314, #4318) - Added support for int64 in wasm. @@ -3412,7 +4228,7 @@ v1.34.10: 9/25/2015 - Removed the deprecated --compression option. - Fixed an issue with asm.js validation for pthreads being broken since v1.34.7 (#3719) - Added built-in cpu performance profiler, which is enabled with linker flag --cpuprofiler. (#3781) - - Added build-in memory usage profiler, which is enabled with linker flag --memoryprofiler. (#3781) + - Added built-in memory usage profiler, which is enabled with linker flag --memoryprofiler. (#3781) - Fixed multiple arities per EM_ASM block (#3804) - Fixed issues with SSE2 an NaN bit patterns. (emscripten-fastcomp #116) - Full list of changes: @@ -3516,7 +4332,7 @@ v1.34.4: 8/4/2015 - Fixed a memory allocation bug in pthreads code (#3636) - Cleaned up some debug assertion messages behind #ifdef ASSERTIONS (#3639) - Fixed umask syscall (#3637) - - Fixed double alignment issue with formatStrind and emscripten_log (#3647) + - Fixed double alignment issue with formatString and emscripten_log (#3647) - Added new EXTRA_EXPORTED_RUNTIME_METHODS build option - Updated emrun to latest version - Full list of changes: @@ -3961,7 +4777,7 @@ v1.29.4: 1/21/2015 - Notice async state in emterpreter trampolines (#3129) - Optimize SDL1 pixel copying to the screen. - Fixed an issue with emterpreter parsing. (#3141) - - Fixed an issue with native optimizer and -s PPRECISE_F32=1. + - Fixed an issue with native optimizer and -s PRECISE_F32=1. - Full list of changes: - Emscripten: https://github.com/emscripten-core/emscripten/compare/1.29.3...1.29.4 - Emscripten-LLVM: https://github.com/emscripten-core/emscripten-fastcomp/compare/1.29.3...1.29.4 @@ -4539,7 +5355,7 @@ v1.21.4: 7/17/2014 to compiled applications (#2343) - Fixed a bug where emrun did not properly capture the exit code when exit runtime via not calling exit(). - - Fixed an error message when symlinkin invalid filenams at runtime. + - Fixed an error message when symlinking invalid filenames at runtime. - Fixed a bug in EGL context creation that parsed the input context creation parameters with wrong terminator. - Improved ffdb.py to be smarter when to attempt port forwarding to connect to @@ -4802,7 +5618,7 @@ v1.16.0: 4/16/2014 v1.15.1: 4/15/2014 ------------------ - Added support for SDL2 touch api. - - Added new user-controllable emdind-related define #define + - Added new user-controllable embind-related define #define EMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES, which allows optimizing embind for minimal size when std::type_info is not needed. - Fixed issues with CMake support where CMAKE_AR and CMAKE_RANLIB were not @@ -4913,7 +5729,7 @@ v1.13.0: 3/3/2014 - Fixed a buffer overflow issue in emscripten_get_callstack (#2171). - Added support for -Os (optimize for size) and -Oz (aggressively optimize for size) arguments to emcc. - - Fixed a typo that broko the call signature of glCompressedTexSubImage2D() + - Fixed a typo that broke the call signature of glCompressedTexSubImage2D() function (#2173). - Added new browser fullscreen resize logic that always retains aspect ratio and adds support for IE11. @@ -5363,7 +6179,7 @@ v1.6.4: 9/30/2013 v1.6.3: 9/26/2013 ------------------ - Emscripten CMake toolchain now generates archive files with .a suffix when - project target type is static library, instead of generatic .bc files + project target type is static library, instead of generating .bc files (#1648). - Adds iconv library from the musl project to implement wide functions in C library (#1670). diff --git a/README.md b/README.md index e1354d4c00144..ee8857de1a53e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ It can probably port your codebase, too! While Emscripten mostly focuses on compiling C and C++ using [Clang](https://clang.llvm.org/), it can be integrated with other LLVM-using compilers (for example, Rust has Emscripten integration, with the -`wasm32-unknown-emscripten` and `asmjs-unknown-emscripten` targets). +`wasm32-unknown-emscripten` target). License ------- diff --git a/bootstrap b/bootstrap index eef0f00c6730f..7e507c13ba9c1 100755 --- a/bootstrap +++ b/bootstrap @@ -11,25 +11,23 @@ # To make modifications to this file, edit `tools/run_python.sh` and then run # `tools/maint/create_entry_points.py` -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. +# python -E does not ignore _PYTHON_SYSCONFIGDATA_NAME, an internal of cpython +# used in cross compilation via setup.py, so we unset it explicitly here. unset _PYTHON_SYSCONFIGDATA_NAME -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi +_EM_PY=$EMSDK_PYTHON -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) +if [ -z "$_EM_PY" ]; then + _EM_PY=$(command -v python3 2> /dev/null) fi -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) +if [ -z "$_EM_PY" ]; then + _EM_PY=$(command -v python 2> /dev/null) fi -if [ -z "$PYTHON" ]; then +if [ -z "$_EM_PY" ]; then echo 'unable to find python in $PATH' exit 1 fi -exec "$PYTHON" -E "$0.py" "$@" +exec "$_EM_PY" -E "$0.py" "$@" diff --git a/bootstrap.bat b/bootstrap.bat index 83edd646f7918..e9080623b77f0 100644 --- a/bootstrap.bat +++ b/bootstrap.bat @@ -2,8 +2,8 @@ :: :: Automatically generated by `create_entry_points.py`; DO NOT EDIT. :: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` +:: To make modifications to this file, edit `tools\run_python.bat` and then run +:: `tools\maint\create_entry_points.py` :: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, :: or there will be a parsing error. @@ -13,9 +13,9 @@ :: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal :: of cpython used in cross compilation via setup.py. @set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python +@set _EM_PY=%EMSDK_PYTHON% +@if "%_EM_PY%"=="" ( + set _EM_PY=python ) :: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this @@ -23,7 +23,7 @@ :: %~dp0 expansions will not work. :: So first try if %~dp0 might work, and if not, manually look up this script from PATH. @if exist "%~f0" ( - set MYDIR=%~dp0 + set "MYDIR=%~dp0" goto FOUND_MYDIR ) @for %%I in (%~n0.bat) do ( @@ -71,16 +71,16 @@ ) :NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* +@"%_EM_PY%" -E "%MYDIR%%~n0.py" %* @exit %ERRORLEVEL% :MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL +@"%_EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL @exit /b %ERRORLEVEL% :MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL +@"%_EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL @exit %ERRORLEVEL% :NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* +@"%_EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/bootstrap.py b/bootstrap.py index 695ae528b212b..059415053c5ed 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -10,6 +10,7 @@ import argparse import os import shutil +import subprocess import sys __rootdir__ = os.path.dirname(os.path.abspath(__file__)) @@ -17,18 +18,27 @@ STAMP_DIR = os.path.join(__rootdir__, 'out') -from tools import shared, utils +# N.b. This script bootstrap.py cannot use 'from tools import shared', +# because shared.py requires that a valid .emscripten config is already +# created. Bootstrap.py needs to be run before an .emscripten config exists. +from tools import utils actions = [ - ('npm packages', ['package.json'], [shutil.which('npm'), 'ci']), + ('npm packages', [ + 'package.json', + 'package-lock.json', + ], ['npm', 'ci']), ('create entry points', [ 'tools/maint/create_entry_points.py', 'tools/maint/run_python.bat', 'tools/maint/run_python.sh', 'tools/maint/run_python.ps1', - ], - [sys.executable, 'tools/maint/create_entry_points.py']), - ('git submodules', ['test/third_party/posixtestsuite/'], [shutil.which('git'), 'submodule', 'update', '--init']), + ], [sys.executable, 'tools/maint/create_entry_points.py']), + ('git submodules', [ + 'test/third_party/posixtestsuite/', + 'test/third_party/googletest', + 'test/third_party/wasi-test-suite', + ], ['git', 'submodule', 'update', '--init']), ] @@ -57,8 +67,23 @@ def main(args): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('-v', '--verbose', action='store_true', help='verbose', default=False) parser.add_argument('-n', '--dry-run', action='store_true', help='dry run', default=False) + parser.add_argument('-i', '--install-git-hooks', action='store_true', help='install emscripten git hooks', default=False) args = parser.parse_args() + if args.install_git_hooks: + if not os.path.exists(utils.path_from_root('.git')): + print('--install-git-hooks requires git checkout') + return 1 + + dst = utils.path_from_root('.git/hooks') + if not os.path.exists(dst): + os.mkdir(dst) + + src = utils.path_from_root('tools/maint/post-checkout') + for src in ('tools/maint/post-checkout', 'tools/maint/pre-push'): + shutil.copy(utils.path_from_root(src), dst) + return 0 + for name, deps, cmd in actions: if check_deps(name, deps): print('Up-to-date: %s' % name) @@ -67,11 +92,17 @@ def main(args): stamp_file = get_stamp_file(name) if args.dry_run: print(' (skipping: dry run) -> %s' % ' '.join(cmd)) - return + continue + orig_exe = cmd[0] + if not os.path.isabs(orig_exe): + cmd[0] = shutil.which(orig_exe) + if not cmd[0]: + utils.exit_with_error(f'command not found: {orig_exe}') print(' -> %s' % ' '.join(cmd)) - shared.run_process(cmd, cwd=utils.path_from_root()) + subprocess.run(cmd, check=True, text=True, encoding='utf-8', cwd=utils.path_from_root()) utils.safe_ensure_dirs(STAMP_DIR) utils.write_file(stamp_file, 'Timestamp file created by bootstrap.py') + return 0 if __name__ == '__main__': diff --git a/cmake/Modules/FindOpenGL.cmake b/cmake/Modules/FindOpenGL.cmake index 3220a11fddbce..b1d4135a336dc 100644 --- a/cmake/Modules/FindOpenGL.cmake +++ b/cmake/Modules/FindOpenGL.cmake @@ -30,3 +30,19 @@ mark_as_advanced( OPENGL_glu_LIBRARY OPENGL_gl_LIBRARY ) + +if (NOT TARGET OpenGL::GL) + add_library(OpenGL::GL INTERFACE IMPORTED) + set_target_properties(OpenGL::GL PROPERTIES + IMPORTED_LIBNAME "${OPENGL_gl_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}" + ) +endif() + +if (NOT TARGET OpenGL::GLU) + add_library(OpenGL::GLU INTERFACE IMPORTED) + set_target_properties(OpenGL::GLU PROPERTIES + IMPORTED_LIBNAME "${OPENGL_glu_LIBRARY}" + INTERFACE_LINK_LIBRARIES OpenGL::GL + ) +endif() diff --git a/cmake/Modules/Platform/Emscripten.cmake b/cmake/Modules/Platform/Emscripten.cmake index 1dd8a3efcba8e..26800a03ae765 100644 --- a/cmake/Modules/Platform/Emscripten.cmake +++ b/cmake/Modules/Platform/Emscripten.cmake @@ -24,7 +24,7 @@ set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE) # CMAKE_SYSTEM_PROCESSOR=x86_64 for 64-bit platform), since some projects (e.g. # OpenCV) use this to detect bitness. # Allow users to ovewrite this on the command line with -DEMSCRIPTEN_SYSTEM_PROCESSOR=arm. -if ("${EMSCRIPTEN_SYSTEM_PROCESSOR}" STREQUAL "") +if (NOT DEFINED EMSCRIPTEN_SYSTEM_PROCESSOR) set(EMSCRIPTEN_SYSTEM_PROCESSOR x86) endif() set(CMAKE_SYSTEM_PROCESSOR ${EMSCRIPTEN_SYSTEM_PROCESSOR}) @@ -34,22 +34,10 @@ set(CMAKE_SYSTEM_PROCESSOR ${EMSCRIPTEN_SYSTEM_PROCESSOR}) # This feature is activated if a shared library project has the property # SOVERSION defined. set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-Wl,-soname,") +set(CMAKE_DL_LIBS "") -# In CMake, CMAKE_HOST_WIN32 is set when we are cross-compiling from Win32 to -# Emscripten: -# http://www.cmake.org/cmake/help/v2.8.12/cmake.html#variable:CMAKE_HOST_WIN32 -# The variable WIN32 is set only when the target arch that will run the code -# will be WIN32, so unset WIN32 when cross-compiling. -set(WIN32) - -# The same logic as above applies for APPLE and CMAKE_HOST_APPLE, so unset -# APPLE. -set(APPLE) - -# And for UNIX and CMAKE_HOST_UNIX. However, Emscripten is often able to mimic -# being a Linux/Unix system, in which case a lot of existing CMakeLists.txt -# files can be configured for Emscripten while assuming UNIX build, so this is -# left enabled. +# Emscripten has good enough Linux/Unix emualtion that it makes sense to set +# UNIX by defaut. set(UNIX 1) # Do a no-op access on the CMAKE_TOOLCHAIN_FILE variable so that CMake will not @@ -58,30 +46,33 @@ if (CMAKE_TOOLCHAIN_FILE) endif() # Locate where the Emscripten compiler resides in relative to this toolchain file. -if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") +if (NOT DEFINED EMSCRIPTEN_ROOT_PATH) get_filename_component(GUESS_EMSCRIPTEN_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) - if (EXISTS "${GUESS_EMSCRIPTEN_ROOT_PATH}/emranlib") + if (EXISTS "${GUESS_EMSCRIPTEN_ROOT_PATH}/emranlib.py") set(EMSCRIPTEN_ROOT_PATH "${GUESS_EMSCRIPTEN_ROOT_PATH}") + else() + # If not found by above search, locate using the EMSCRIPTEN environment variable. + set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSCRIPTEN}") + # Abort if not found. + if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") + message(FATAL_ERROR "Could not locate the Emscripten compiler toolchain directory! Either set the EMSCRIPTEN environment variable, or pass -DEMSCRIPTEN_ROOT_PATH=xxx to CMake to explicitly specify the location of the compiler!") + endif() endif() endif() -# If not found by above search, locate using the EMSCRIPTEN environment variable. -if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") - set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSCRIPTEN}") -endif() - -# Abort if not found. -if ("${EMSCRIPTEN_ROOT_PATH}" STREQUAL "") - message(FATAL_ERROR "Could not locate the Emscripten compiler toolchain directory! Either set the EMSCRIPTEN environment variable, or pass -DEMSCRIPTEN_ROOT_PATH=xxx to CMake to explicitly specify the location of the compiler!") -endif() - # Normalize, convert Windows backslashes to forward slashes or CMake will crash. get_filename_component(EMSCRIPTEN_ROOT_PATH "${EMSCRIPTEN_ROOT_PATH}" ABSOLUTE) list(APPEND CMAKE_MODULE_PATH "${EMSCRIPTEN_ROOT_PATH}/cmake/Modules") if (CMAKE_HOST_WIN32) - set(EMCC_SUFFIX ".bat") + # We use windows executables these days rather than `.bat` files, but we + # still support a fallback of using `.bat` files. + if (EXISTS "${EMSCRIPTEN_ROOT_PATH}/emcc.exe") + set(EMCC_SUFFIX ".exe") + else() + set(EMCC_SUFFIX ".bat") + endif() else() set(EMCC_SUFFIX "") endif() @@ -96,6 +87,7 @@ set(CMAKE_C_COMPILER_AR "${CMAKE_AR}") set(CMAKE_CXX_COMPILER_AR "${CMAKE_AR}") set(CMAKE_C_COMPILER_RANLIB "${CMAKE_RANLIB}") set(CMAKE_CXX_COMPILER_RANLIB "${CMAKE_RANLIB}") +set(CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS "${EMSCRIPTEN_ROOT_PATH}/emscan-deps${EMCC_SUFFIX}") # Capture the Emscripten version to EMSCRIPTEN_VERSION variable. if (NOT EMSCRIPTEN_VERSION) @@ -113,10 +105,20 @@ if (NOT EMSCRIPTEN_VERSION) set(EMSCRIPTEN_VERSION "${CMAKE_MATCH_1}") endif() -# Don't allow CMake to autodetect the compiler, since this is quite slow with -# Emscripten. -# Pass -DEMSCRIPTEN_FORCE_COMPILERS=OFF to disable (sensible mostly only for -# testing/debugging purposes). +execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}" "CACHE" + RESULT_VARIABLE _emcache_result + OUTPUT_VARIABLE _emcache_output + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT _emcache_result EQUAL 0) + message(FATAL_ERROR "Failed to find emscripten cache directory with command \"'${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}' CACHE\"! Process returned with error code ${_emcache_result}.") +endif() +file(TO_CMAKE_PATH "${_emcache_output}" _emcache_output) +set(EMSCRIPTEN_SYSROOT "${_emcache_output}/sysroot") + +# Allow skipping of CMake compiler autodetection. On by default since this is +# quite slow with Emscripten and also leads to issues with +# CMAKE_C_IMPLICIT_LINK_LIBRARIES. +# See https://github.com/emscripten-core/emscripten/issues/23944 option(EMSCRIPTEN_FORCE_COMPILERS "Force C/C++ compiler" ON) if (EMSCRIPTEN_FORCE_COMPILERS) @@ -162,58 +164,58 @@ if (EMSCRIPTEN_FORCE_COMPILERS) set(CMAKE_C_PLATFORM_ID "emscripten") set(CMAKE_CXX_PLATFORM_ID "emscripten") + if (NOT DEFINED CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES) + set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include") + endif() + if (NOT DEFINED CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) + set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include;${EMSCRIPTEN_SYSROOT}/include/c++/v1") + endif() + + set(CXX_COMPILE_FEATURES_BASE "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17") + set(CXX11_COMPILE_FEATURES_BASE "cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") + set(CXX14_COMPILE_FEATURES_BASE "cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + if ("${CMAKE_VERSION}" VERSION_LESS "3.8") set(CMAKE_C_COMPILE_FEATURES "c_function_prototypes;c_restrict;c_variadic_macros;c_static_assert") set(CMAKE_C90_COMPILE_FEATURES "c_function_prototypes") set(CMAKE_C99_COMPILE_FEATURES "c_restrict;c_variadic_macros") set(CMAKE_C11_COMPILE_FEATURES "c_static_assert") - set(CMAKE_CXX_COMPILE_FEATURES "cxx_template_template_parameters;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + set(CMAKE_CXX_COMPILE_FEATURES "cxx_template_template_parameters;${CXX11_COMPILE_FEATURES_BASE};${CXX14_COMPILE_FEATURES_BASE}") set(CMAKE_CXX98_COMPILE_FEATURES "cxx_template_template_parameters") - set(CMAKE_CXX11_COMPILE_FEATURES "cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") - set(CMAKE_CXX14_COMPILE_FEATURES "cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + set(CMAKE_CXX11_COMPILE_FEATURES "${CXX11_COMPILE_FEATURES_BASE}") + set(CMAKE_CXX14_COMPILE_FEATURES "${CXX14_COMPILE_FEATURES_BASE}") else() # 3.8+ + set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") + set(CMAKE_CXX_COMPILE_FEATURES "${CXX_COMPILE_FEATURES_BASE}") set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes") set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros") set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert") set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") - set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") - set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;${CXX11_COMPILE_FEATURES_BASE}") + set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;${CXX14_COMPILE_FEATURES_BASE}") set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") - if ("${CMAKE_VERSION}" VERSION_LESS "3.12") # [3.8, 3.12) - set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") - set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17") - else() # 3.12+ + if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.12") # 3.12+ set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20") - if ("${CMAKE_VERSION}" VERSION_LESS "3.20") # [3.12, 3.20) - set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") - set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20") - else() # 3.20+ + set(CMAKE_CXX_COMPILE_FEATURES "${CMAKE_CXX_COMPILE_FEATURES};cxx_std_20") + if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.20") # 3.20+ set(CMAKE_CXX23_COMPILE_FEATURES "cxx_std_23") - set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23") - if ("${CMAKE_VERSION}" VERSION_LESS "3.21") # 3.20 - set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") - else() # 3.21+ + set(CMAKE_CXX_COMPILE_FEATURES "${CMAKE_CXX_COMPILE_FEATURES};cxx_std_23") + if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") # 3.25+ + set(CMAKE_CXX26_COMPILE_FEATURES "cxx_std_26") + set(CMAKE_CXX_COMPILE_FEATURES "${CMAKE_CXX_COMPILE_FEATURES};cxx_std_26") + endif() + if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.21") set(CMAKE_C17_COMPILE_FEATURES "c_std_17") set(CMAKE_C23_COMPILE_FEATURES "c_std_23") - set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert;c_std_17;c_std_23") + set(CMAKE_C_COMPILE_FEATURES "${CMAKE_C_COMPILE_FEATURES};c_std_17;c_std_23") endif() endif() endif() endif() endif() -execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}" "CACHE" - RESULT_VARIABLE _emcache_result - OUTPUT_VARIABLE _emcache_output - OUTPUT_STRIP_TRAILING_WHITESPACE) -if (NOT _emcache_result EQUAL 0) - message(FATAL_ERROR "Failed to find emscripten cache directory with command \"'${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}' CACHE\"! Process returned with error code ${_emcache_result}.") -endif() -file(TO_CMAKE_PATH "${_emcache_output}" _emcache_output) -set(EMSCRIPTEN_SYSROOT "${_emcache_output}/sysroot") - list(APPEND CMAKE_FIND_ROOT_PATH "${EMSCRIPTEN_SYSROOT}") list(APPEND CMAKE_SYSTEM_PREFIX_PATH /) @@ -272,21 +274,28 @@ endif() set(CMAKE_EXECUTABLE_SUFFIX ".js") -set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES 1) -set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES 1) -set(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1) -set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1) -set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1) -set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1) +if (CMAKE_HOST_WIN32) + # See https://github.com/emscripten-core/emscripten/issues/2386 + set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES 1) + set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES 1) + set(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1) + set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1) + set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1) + set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1) +endif() set(CMAKE_C_RESPONSE_FILE_LINK_FLAG "@") set(CMAKE_CXX_RESPONSE_FILE_LINK_FLAG "@") +# Enable $ for CMake 3.24+ +set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE "-Wl,--whole-archive" "" "-Wl,--no-whole-archive") +set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED True) + # Set a global EMSCRIPTEN variable that can be used in client CMakeLists.txt to # detect when building using Emscripten. -set(EMSCRIPTEN 1 CACHE BOOL "If true, we are targeting Emscripten output.") +set(EMSCRIPTEN 1 CACHE INTERNAL "If true, we are targeting Emscripten output.") -# Hardwire support for cmake-2.8/Modules/CMakeBackwardsCompatibilityC.cmake +# Hardwire support for CMake/Modules/CMakeBackwardCompatibilityC.cmake # without having CMake to try complex things to autodetect these: set(CMAKE_SKIP_COMPATIBILITY_TESTS 1) set(CMAKE_SIZEOF_CHAR 1) @@ -305,7 +314,6 @@ set(CMAKE_HAVE_SYS_PRCTL_H 1) set(CMAKE_WORDS_BIGENDIAN 0) set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN") set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN") -set(CMAKE_DL_LIBS) function(em_validate_asmjs_after_build target) message(WARNING "em_validate_asmjs_after_build no longer exists") @@ -365,12 +373,3 @@ endif() # complain about unused CMake variable. if (CMAKE_CROSSCOMPILING_EMULATOR) endif() - -# TODO: CMake appends /usr/include to implicit includes; switching to use usr/include will make this redundant. -if ("${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}" STREQUAL "") - set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include") -endif() -if ("${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}" STREQUAL "") - set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include") -endif() -unset(_em_sysroot_include) diff --git a/docs/design/01-precise-futex-wakeups.md b/docs/design/01-precise-futex-wakeups.md new file mode 100644 index 0000000000000..70fbf22d6a2a4 --- /dev/null +++ b/docs/design/01-precise-futex-wakeups.md @@ -0,0 +1,138 @@ +# Design Doc: Precise Futex Wakeups + +- **Status**: Draft +- **Bug**: https://github.com/emscripten-core/emscripten/issues/26633 + +## Context +Currently, `emscripten_futex_wait` (in +`system/lib/pthread/emscripten_futex_wait.c`) relies on a periodic wakeup loop +for pthreads and the main runtime thread. This is done for two primary reasons: + +1. **Thread Cancellation**: To check if the calling thread has been cancelled while it is blocked. +2. **Main Runtime Thread Events**: To allow the main runtime thread (even when not the main browser thread) to process its mailbox/event queue. + +The current implementation uses a 1ms wakeup interval for the main runtime +thread and a 100ms interval for cancellable pthreads. This leads to unnecessary +CPU wakeups and increased latency for events. + +## Goals +- Remove the periodic wakeup loop from `emscripten_futex_wait`. +- Implement precise, event-driven wakeups for cancellation and mailbox events. +- Maintain the existing `emscripten_futex_wait` API signature. +- Focus implementation on threads that support `atomic.wait` (pthreads and workers). + +## Non-Goals +- **Main Browser Thread**: Changes to the busy-wait loop in `futex_wait_main_browser_thread` are out of scope. +- **Direct Atomics Usage**: Threads that call `atomic.wait` directly (bypassing `emscripten_futex_wait`) will remain un-interruptible. +- **Wasm Workers**: Wasm Workers do not have a `pthread` structure, so they are not covered by this design. + +## Proposed Design + +The core idea is to allow "side-channel" wakeups (cancellation, mailbox events) +to interrupt the `atomic.wait` call by having the waker call `atomic.wake` on the +same address the waiter is currently blocked on. + +As part of this design we will need to explicitly state that +`emscripten_futex_wait` now supports spurious wakeups. i.e. it may return `0` +(success) even if the underlying futex was not explicitly woken by the +application. + +### 1. `struct pthread` Extensions +We will add a single atomic `wait_addr` field to `struct pthread` (in +`system/lib/libc/musl/src/internal/pthread_impl.h`). + +```c +// The address the thread is currently waiting on in emscripten_futex_wait. +// +// This field encodes the state using the following bitmask: +// - NULL: Not waiting, no pending notification. +// - NOTIFY_BIT (0x1): Not waiting, but a notification was sent. +// - addr: Waiting on `addr`, no pending notification. +// - addr | NOTIFY_BIT: Waiting on `addr`, notification sent. +// +// Since futex addresses must be 4-byte aligned, the low bit is safe to use. +_Atomic uintptr_t wait_addr; + +#define NOTIFY_BIT (1 << 0) +``` + +### 2. Waiter Logic (`emscripten_futex_wait`) +The waiter will follow this logic: + +1. **Notification Loop**: + ```c + uintptr_t expected_null = 0; + while (!atomic_compare_exchange_strong(&self->wait_addr, &expected_null, (uintptr_t)addr)) { + // If the CAS failed, it means NOTIFY_BIT was set by another thread. + assert(expected_null == NOTIFY_BIT); + // Let the notifier know that we received the wakeup notification by + // resetting wait_addr. + self->wait_addr = 0; + handle_wakeup(); // Process mailbox or handle cancellation + // Reset expected_null because CAS updates it to the observed value on failure. + expected_null = 0; + } + ``` +2. **Wait**: Call `ret = __builtin_wasm_memory_atomic_wait32(addr, val, timeout)`. +3. **Unpublish & Check**: + ```c + // Clear wait_addr and check if a notification arrived while we were sleeping. + if ((atomic_exchange(&self->wait_addr, 0) & NOTIFY_BIT) != 0) { + handle_wakeup(); + } + ``` +4. **Return**: Return the result of the wait. + +Note: We do **not** loop internally if `ret == ATOMICS_WAIT_OK`. Even if we +suspect the wake was caused by a side-channel event, we must return to the user +to avoid "swallowing" a simultaneous real application wake. + +### 3. Waker Logic +When a thread needs to wake another thread for a side-channel event: + +1. **Enqueue Work**: Add the task to the target's mailbox or set the cancellation flag. +2. **Signal**: + ```c + uintptr_t addr = atomic_fetch_or(&target->wait_addr, NOTIFY_BIT); + if (addr == 0 || (addr & NOTIFY_BIT) != 0) { + // Either the thread wasn't waiting (it will see NOTIFY_BIT later), + // or someone else is already in the process of notifying it. + return; + } + // We set the bit and are responsible for waking the target. + // The target is currently waiting on `addr`. + while (target->wait_addr == (addr | NOTIFY_BIT)) { + emscripten_futex_wake((void*)addr, INT_MAX); + sched_yield(); + } + ``` + +### 4. Handling the Race Condition +The protocol handles the "Lost Wakeup" race by having the waker loop until the +waiter clears its `wait_addr`. If the waker sets the `NOTIFY_BIT` just before +the waiter enters `atomic.wait`, the `atomic_wake` will be delivered once the +waiter is asleep. If the waiter wakes up for any reason (timeout, real wake, or +side-channel wake), its `atomic_exchange` will satisfy the waker's loop +condition. + +## Benefits + +- **Lower Power Consumption**: Threads can sleep indefinitely (or for the full duration of a user-requested timeout) without periodic wakeups. +- **Lower Latency**: Mailbox events and cancellation requests are processed immediately rather than waiting for the next 1ms or 100ms tick. +- **Simpler Loop**: The complex logic for calculating remaining timeout slices in `emscripten_futex_wait` is removed. + +## Alternatives Considered +- **Signal-based wakeups**: Not currently feasible in Wasm as signals are not + implemented in a way that can interrupt `atomic.wait`. +- **A single global "wake-up" address per thread**: This would require the + waiter to wait on *two* addresses simultaneously (the user's futex and its + own wakeup address), which `atomic.wait` does not support. The proposed + design works around this by having the waker use the *user's* futex address. + +## Security/Safety Considerations +- **The `wait_addr` must be managed carefully** to ensure wakers don't + call `atomic.wake` on stale addresses. Clearing the address upon wake + mitigates this. +- **The waker loop should have a reasonable fallback** (like a yield) to prevent a + busy-wait deadlock if the waiter is somehow prevented from waking up (though + `atomic.wait` is generally guaranteed to wake if `atomic.wake` is called). diff --git a/docs/design/02-wasm-worker-pthread-compat.md b/docs/design/02-wasm-worker-pthread-compat.md new file mode 100644 index 0000000000000..5fecd315a3e56 --- /dev/null +++ b/docs/design/02-wasm-worker-pthread-compat.md @@ -0,0 +1,75 @@ +# Design Doc: Wasm Worker Pthread Compatibility + +- **Status**: Draft +- **Bug**: https://github.com/emscripten-core/emscripten/issues/26631 + +## Context + +Wasm Workers in Emscripten are a lightweight alternative to pthreads. They use +the same memory and can use the same synchronization primitives, but they do not +have a full `struct pthread` and thus many pthread-based APIs (like +`pthread_self()`) currently do not work when called from a Wasm Worker. + +This is not an issue in pure Wasm Workers programs but we also support hybrid +programs that run both pthreads and Wasm Workers. In this cases the pthread +API is available, but will fail in undefined ways if called from Wasm Workers. + +This document proposes a plan to improve the hybrid mode by adding the pthread +metadata (`struct pthread`) to each Wasm Worker, allowing the pthread API (or at +least some subset of it) APIs to used from Wasm Workers. + +## Proposed Changes + +### 1. Memory Layout + +Currently, Wasm Workers allocate space for TLS and stack: `[TLS data] [Stack]`. +We propose to change this to: `[struct pthread] [TLS data] [Stack]`. + +The `struct pthread` will be located at the very beginning of the allocated +memory block for each Wasm Worker. + +### 2. `struct pthread` Initialization + +The `struct pthread` will be initialized by the creator thread in `emscripten_create_wasm_worker` (or `emscripten_malloc_wasm_worker`). +This includes: +- Zero-initializing the structure. +- Setting the `self` pointer to the start of the `struct pthread`. +- Initializing essential fields like `tid`. + +On the worker thread side, `_emscripten_wasm_worker_initialize` will need to set +the thread-local pointer (returned by `__get_tp()`) to the `struct pthread` +location. + +### 3. `__get_tp` Support + +We will modify `system/lib/pthread/emscripten_thread_state.S` to provide a +`__get_tp` implementation for Wasm workers that returns the address of the +`struct pthread`. This will allow `__pthread_self()` and other related functions +to work correctly. + +### 4. Supported Pthread API Subset + +We intend to support a subset of the pthread API within Wasm workers: +- `pthread_self()`: Returns the worker's `struct pthread` pointer. +- `pthread_equal()`: Works normally. +- `pthread_getspecific()` / `pthread_setspecific()`: TSD (Thread Specific Data) should work if `tsd` field in `struct pthread` is initialized. +- `pthread_mutex_*`: Mutexes will work as they rely on `struct pthread` for owner tracking. +- `pthread_cond_*`: Condition variables will work as they rely on `struct pthread` for waiter tracking. +- Low-level synchronization primitives that use `struct pthread` (e.g., some internal locks). + +APIs that will NOT be supported (or will have limited support): +- `pthread_create()` / `pthread_join()` / `pthread_detach()`: Wasm workers have their own creation and lifecycle management. +- `pthread_cancel()`: Not supported in Wasm workers. +- `pthread_kill()`: Not supported in Wasm workers. + +## Implementation Plan + +1. Modify `emscripten_create_wasm_worker` in `system/lib/wasm_worker/library_wasm_worker.c` to account for `sizeof(struct pthread)` in memory allocation and initialize the structure. +2. Update `_emscripten_wasm_worker_initialize` in `system/lib/wasm_worker/wasm_worker_initialize.S` to set the thread pointer. +3. Modify `system/lib/pthread/emscripten_thread_state.S` to enable `__get_tp` for Wasm workers. +4. Review and test essential pthread functions (like TSD) in Wasm workers. +5. Document the supported and unsupported pthread APIs for Wasm workers. + +## Verification +- Add a new test that makes use of `pthread_self()` and low level synchronization APIs from a Wasm Worker. +- Verify that existing Wasm worker tests still pass. diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000000000..6e5b4bb388e37 --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,21 @@ +# Emscripten Design Documents + +This directory contains design documents for emscripten features and major +changes/refactors. + +We are experimenting with keeping these documents here under source control with +the hope that this will increase understandability of the codebase. This has +some advantages over doing all our planning in Google Docs or GitHub issues. +For example, it allows us to track the history of designs and it allows them to +be searchable using standard tools like `git grep`. + +## Document Format + +Each document in this directory should be a markdown file. At the top of each +document should be a `Status` which can be either `Draft`, `Accepted`, +`Completed`. + +When a document is marked as `Completed` it should also be updated such that +it is clear the work has been done, and is now in the past. For example, +phrases such as "The current behavior" should be replaced with "The previous +behavior". diff --git a/docs/emcc.txt b/docs/emcc.txt index e2a7903e51959..9f26441ab8c21 100644 --- a/docs/emcc.txt +++ b/docs/emcc.txt @@ -49,8 +49,8 @@ Options that are modified or new in *emcc* are listed below: "-O1" [compile+link] Simple optimizations. During the compile step these - include LLVM "-O1" optimizations. During the link step this does - not include various runtime assertions in JS that *-O0* would do. + include LLVM "-O1" optimizations. During the link step this omits + various runtime assertions in JS that *-O0* would include. "-O2" [compile+link] Like "-O1", but enables more optimizations. During @@ -68,15 +68,16 @@ Options that are modified or new in *emcc* are listed below: "-O3" [compile+link] Like "-O2", but with additional optimizations that - may take longer to run. + may take longer to run and may increase code size. Note: This is a good setting for a release build. "-Og" - [compile+link] Like "-O1". In future versions, this option might - disable different optimizations in order to improve debuggability. + [compile+link] Like "-O1", with an additional flag to extend the + liveness of variables for improved debugging. In future versions, + additional optimizations might also be disabled. "-Os" [compile+link] Like "-O3", but focuses more on code size (and may @@ -181,43 +182,54 @@ Options that are modified or new in *emcc* are listed below: alongside the wasm object files. This option must be used together with "-c". -"-gsource-map" - [link] Generate a source map using LLVM debug information (which - must be present in object files, i.e., they should have been - compiled with "-g"). When this option is provided, the **.wasm** - file is updated to have a "sourceMappingURL" section. The resulting - URL will have format: "" + "" + ".map". - "" defaults to being empty (which means the source map is - served from the same directory as the Wasm file). It can be changed - using --source-map-base. +"-gsource-map[=inline]" + [compile+link] [same as -g3 if passed at compile time, otherwise + applies at link] Generate a source map using LLVM debug information + (which must be present in object files, i.e., they should have been + compiled with "-g" or "-gsource-map"). + + When this option is provided, the **.wasm** file is updated to have + a "sourceMappingURL" section. The resulting URL will have format: + "" + "" + ".map". "" defaults + to being empty (which means the source map is served from the same + directory as the Wasm file). It can be changed using --source-map- + base. + + Path substitution can be applied to the referenced sources using + the "-sSOURCE_MAP_PREFIXES" (link). If "inline" is specified, the + sources content is embedded in the source map (in this case you + don't need path substitution, but it comes with the cost of having + a large source map file). "-g" - [compile+link] Controls the level of debuggability. Each level - builds on the previous one: + [compile+link] If used at compile time, adds progressively more + DWARF information to the object file, according to the underlying + behavior of clang. If used at link time, controls the level of + debuggability overall. Each level builds on the previous one: * "-g0": Make no effort to keep code debuggable. - * "-g1": When linking, preserve whitespace in JavaScript. + * "-g1": Preserve whitespace in JavaScript. - * "-g2": When linking, preserve function names in compiled code. + * "-g2": Also preserve function names in compiled code (via the + wasm name section). - * "-g3": When compiling to object files, keep debug info, - including JS whitespace, function names, and LLVM debug info - (DWARF) if any (this is the same as -g). + * "-g3": Also keep LLVM debug info (DWARF) if there is any in + the object files (this is the same as -g). "--profiling" - [same as -g2 if passed at compile time, otherwise applies at link] - Use reasonable defaults when emitting JavaScript to make the build - readable but still useful for profiling. This sets "-g2" (preserve - whitespace and function names) and may also enable optimizations - that affect performance and otherwise might not be performed in - "-g2". + [link] Make the output suitable for profiling. This means including + function names in the wasm and JS output, and preserving whitespace + in the JS output. It does not affect optimizations (to ensure that + performance profiles reflect production builds). Currently this is + the same as "-g2". "--profiling-funcs" - [link] Preserve function names in profiling, but otherwise minify - whitespace and names as we normally do in optimized builds. This is - useful if you want to look at profiler results based on function - names, but do *not* intend to read the emitted code. + [link] Preserve wasm function names as in "--profiling", but + otherwise minify whitespace and names as we normally do in + optimized builds. This is useful if you want to look at profiler + results based on function names, but do *not* intend to read the + emitted code. "--tracing" [link] Enable the Emscripten Tracing API. @@ -232,7 +244,9 @@ Options that are modified or new in *emcc* are listed below: [link] Save a map file between function indexes in the Wasm and function names. By storing the names on a file on the side, you can avoid shipping the names, and can still reconstruct meaningful - stack traces by translating the indexes back to the names. + stack traces by translating the indexes back to the names. This is + a simpler format than source maps, but less detailed because it + only describes function names and not source locations. Note: @@ -240,13 +254,20 @@ Options that are modified or new in *emcc* are listed below: "[name].js.symbols" (with WASM symbols) and "[name].wasm.js.symbols" (with ASM.js symbols) +"--emit-minification-map " + [link] In cases where emscripten performs import/export + minification this option can be used to output a file that maps + minified names back to their original names. The format of this + file is single line per import/export of the form + ":". + "-flto" [compile+link] Enables link-time optimizations (LTO). "--closure 0|1|2" [link] Runs the *Closure Compiler*. Possible values are: - * "0": No closure compiler (default in "-O2" and below). + * "0": No closure compiler (default). * "1": Run closure compiler. This greatly reduces the size of the support JavaScript code (everything but the WebAssembly or @@ -271,9 +292,6 @@ Options that are modified or new in *emcc* are listed below: before the closure-compiled code runs, because then it will reuse that variable. - * Closure is only run if JavaScript opts are being done ("-O2" or - above). - "--closure-args=" [link] Pass arguments to the *Closure compiler*. This is an alternative to "EMCC_CLOSURE_ARGS". @@ -378,7 +396,7 @@ Options that are modified or new in *emcc* are listed below: Note: - * See src/shell.html and src/shell_minimal.html for examples. + * See html/shell.html and html/shell_minimal.html for examples. * This argument is ignored if a target other than HTML is specified using the "-o" option. @@ -410,9 +428,15 @@ Options that are modified or new in *emcc* are listed below: instead. "--embind-emit-tsd " - [link] Generate a TypeScript definition file from the exported - embind bindings. The program will be instrumented and run in node - in order to to generate the file. + [link] Generates TypeScript definition file. Deprecated: Use "-- + emit-tsd" instead. + +"--emit-tsd " + [link] Generate a TypeScript definition file for the emscripten + module. The definition file will include exported Wasm functions, + runtime exports, and exported embind bindings (if used). In order + to generate bindings from embind, the program will be instrumented + and run in node. "--ignore-dynamic-linking" [link] Tells the compiler to ignore dynamic linking (the user will @@ -499,15 +523,6 @@ Options that are modified or new in *emcc* are listed below: unintentional use of absolute paths, which is sometimes dangerous when referring to nonportable local system headers. -"--proxy-to-worker" - [link] Runs the main application code in a worker, proxying events - to it and output from it. If emitting HTML, this emits a **.html** - file, and a separate **.js** file containing the JavaScript to be - run in a worker. If emitting JavaScript, the target file name - contains the part to be run on the main thread, while a second - **.js** file with suffix ".worker.js" will contain the worker - portion. - "--emrun" [link] Enables the generated output to be aware of the emrun command line tool. This allows "stdout", "stderr" and @@ -561,16 +576,21 @@ Options that are modified or new in *emcc* are listed below: These rules only apply when linking. When compiling to object code (See *-c* below) the name of the output file is irrelevant. + Note: Linking to a file with no extension (or a file ending in + ".out", like "a.out") will cause the generated JavaScript file to + be exectuable, and include a "#!" line to make it runnable + directly. + "-c" [compile] Tells *emcc* to emit an object file which can then be linked with other object files to produce an executable. -"--output_eol windows|linux" +"--output-eol windows|linux" [link] Specifies the line ending to generate for the text files - that are outputted. If "--output_eol windows" is passed, the final - output files will have Windows rn line endings in them. With "-- - output_eol linux", the final generated files will be written with - Unix n line endings. + that are outputted. If "--output-eol windows" is passed, the final + output files will have Windows "\r\n" line endings in them. With " + --output-eol linux", the final generated files will be written with + Unix "\n" line endings. "--cflags" [other] Prints out the flags "emcc" would pass to "clang" to diff --git a/docs/process.md b/docs/process.md index 0c3b23b1062d7..9b8bbb15a0d0b 100644 --- a/docs/process.md +++ b/docs/process.md @@ -55,8 +55,8 @@ pre-processor. See [`.clang-format`][clang-format] for more details. ### Python Code We generally follow the pep8 standard with the major exception that we use 2 -spaces for indentation. `flake8` is run on all PRs to ensure that python code -conforms to this style. See [`.flake8`][flake8] for more details. +spaces for indentation. `ruff` is run on all PRs to ensure that Python code +conforms to this style. See [`pyproject.toml`][pyproject.toml] for more details. #### Static Type Checking @@ -291,6 +291,46 @@ To update our libraries to a newer musl release: [`update_musl.py`][update_musl_emscripten] for that. +## Deprecating settings and features + +Emscripten has a lot of settings and features which makes combinatorial testing +practically unfeasible. In order to manage the complexity and reduce +technical debt we constantly strive to deprecate and remove settings and features +that are no longer in use. + +In order to manage these deprecations in a way that minimizes user impact and +unintended consequences we have designed the following process. A primary +purpose of this process is to engage with the user community in order to assess +the impact of removing a given feature. At any point in the process we could +decide collectively to abandon the deprecation, or to delay it. + +1. Create an "Intent to deprecate" bug for the setting or feature. + +2. Send a message to the emscripten-discuss mailing with the title `[PSA] Indent + to deprecate XXX` where `XXX` is the name of the feature or setting in + question. Please include a link to the bug created above. + +3. If possible, update emscripten such that it will generate a `deprecated` + warning when the feature is used. For settings this is normally as simple + as adding it to `DEPRECATED_SETTINGS` in `settings.py`. + +4. Perform a [global search][global_github_search] of public GitHub repositories + for usage of the feature. If you work for a company with a large internal + codebase (e.g. Google) please also search globally there. + +5. Feedback from steps (2), (3) and (4) should be summarized in the bug where + discussions about the impact of deprecation can then proceed. + +6. After at least 4 emscripten releases, or 2 months (whichever is shorter) a + final decision on the deprecation may be agreed upon. The final decision + will be made by the Emscripten maintainers. + +7. If the decision is to proceed the feature can then be removed. If the + decision goes the other way the deprecation warning should be removed. When + the feature is removed, it should, where possible, continue to be detected + by the code so that users of the old feature see an actionable message. An + entry in `ChangeLog.md` should also be added. + [site_repo]: https://github.com/kripken/emscripten-site [releases_repo]: https://chromium.googlesource.com/emscripten-releases [waterfall]: https://ci.chromium.org/p/emscripten-releases/g/main/console @@ -304,7 +344,7 @@ To update our libraries to a newer musl release: [emsdk_tags]: https://github.com/emscripten-core/emsdk/tags [emscripten_tags]: https://github.com/emscripten-core/emscripten/tags [clang-format]: https://github.com/emscripten-core/emscripten/blob/main/.clang-format -[flake8]: https://github.com/emscripten-core/emscripten/blob/main/.flake8 +[pyproject.toml]: https://github.com/emscripten-core/emscripten/blob/main/pyproject.toml [mypy]: https://github.com/emscripten-core/emscripten/blob/main/.mypy [update_docs]: https://github.com/emscripten-core/emscripten/blob/main/tools/maint/update_docs.py [llvm_repo]: https://github.com/llvm/llvm-project @@ -316,3 +356,4 @@ To update our libraries to a newer musl release: [update_libcxxabi_emscripten]: https://github.com/emscripten-core/emscripten/blob/main/system/lib/update_libcxxabi.py [update_libunwind_emscripten]: https://github.com/emscripten-core/emscripten/blob/main/system/lib/update_libunwind.py [update_musl_emscripten]: https://github.com/emscripten-core/emscripten/blob/main/system/lib/update_musl.py +[global_github_search]: https://github.com/search?q=%2F%28%3F-i%29%5CbMY_SETTING%5Cb%2F+-org%3Aemscripten-core+-path%3Aemcc.*+-path%3Asettings.*+-path%3Asettings_reference.*&type=code diff --git a/em++ b/em++ deleted file mode 100755 index 0ca8082a2cc0e..0000000000000 --- a/em++ +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python_compiler.sh` and -# then run `tools/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -if [ -z "$_EMCC_CCACHE" ]; then - exec "$PYTHON" -E "$0.py" "$@" -else - unset _EMCC_CCACHE - exec ccache "$0" "$@" -fi diff --git a/em++.bat b/em++.bat deleted file mode 100644 index 68026fe148de5..0000000000000 --- a/em++.bat +++ /dev/null @@ -1,95 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python_compiler.bat` and -:: then run `tools/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: If _EMCC_CCACHE is not set, do a regular invocation of the python compiler driver. -:: Otherwise remove the ccache env. var, and then reinvoke this script with ccache enabled. -@if "%_EMCC_CCACHE%"=="" ( - set CMD="%EM_PY%" -E "%MYDIR%%~n0.py" -) else ( - set _EMCC_CCACHE= - set CMD=ccache "%MYDIR%%~n0.bat" -) - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@%CMD% %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@%CMD% %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@%CMD% %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@%CMD% %* diff --git a/em++.py b/em++.py index 4134eb1331ede..2ab74f0680db9 100755 --- a/em++.py +++ b/em++.py @@ -5,6 +5,7 @@ # found in the LICENSE file. import sys + import emcc from tools import shared diff --git a/em-config b/em-config deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/em-config +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/em-config.bat b/em-config.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/em-config.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/em-config.py b/em-config.py index 2708d18ca6dd2..cd50c1a2e8e8b 100755 --- a/em-config.py +++ b/em-config.py @@ -14,8 +14,9 @@ is found, or exits with 1 if the variable does not exist. """ -import sys import re +import sys + from tools import config @@ -24,7 +25,7 @@ def main(): not re.match(r"^[\w\W_][\w\W_\d]*$", sys.argv[1]) or \ not hasattr(config, sys.argv[1]): print('Usage: em-config VAR_NAME', file=sys.stderr) - exit(1) + sys.exit(1) print(getattr(config, sys.argv[1])) return 0 diff --git a/emar b/emar deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emar +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emar.bat b/emar.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emar.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emar.py b/emar.py index 061fc5d4ce927..8f57837b34fa7 100755 --- a/emar.py +++ b/emar.py @@ -8,6 +8,7 @@ """ import sys + from tools import shared shared.exec_process([shared.LLVM_AR] + sys.argv[1:]) diff --git a/embuilder b/embuilder deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/embuilder +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/embuilder.bat b/embuilder.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/embuilder.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/embuilder.py b/embuilder.py index 6e6c703b3d013..6232fc5dabc7d 100755 --- a/embuilder.py +++ b/embuilder.py @@ -15,43 +15,59 @@ import argparse import fnmatch import logging +import os import sys import time from contextlib import contextmanager -from tools import cache -from tools import shared -from tools import system_libs -from tools import ports -from tools import utils +from tools import cache, ports, shared, system_libs, utils from tools.settings import settings from tools.system_libs import USE_NINJA - -# Minimal subset of targets used by CI systems to build enough to useful +# Minimal subset of targets used by CI systems to build enough to be useful MINIMAL_TASKS = [ - 'libbulkmemory', 'libcompiler_rt', - 'libcompiler_rt-wasm-sjlj', + 'libcompiler_rt-mt', + 'libcompiler_rt-legacysjlj', + 'libcompiler_rt-wasmsjlj', + 'libcompiler_rt-ww', 'libc', 'libc-debug', + 'libc-mt-debug', + 'libc-ww-debug', 'libc_optz', 'libc_optz-debug', 'libc++abi', - 'libc++abi-except', + 'libc++abi-legacyexcept', + 'libc++abi-wasmexcept', 'libc++abi-noexcept', 'libc++abi-debug', - 'libc++abi-debug-except', + 'libc++abi-debug-legacyexcept', + 'libc++abi-debug-wasmexcept', 'libc++abi-debug-noexcept', + 'libc++abi-debug-mt-noexcept', + 'libc++abi-debug-ww-noexcept', 'libc++', - 'libc++-except', + 'libc++-legacyexcept', + 'libc++-wasmexcept', 'libc++-noexcept', + 'libc++-ww-noexcept', + 'libc++-debug', + 'libc++-debug-wasmexcept', + 'libc++-debug-legacyexcept', + 'libc++-debug-noexcept', + 'libc++-debug-mt-noexcept', + 'libc++-debug-ww-noexcept', 'libal', 'libdlmalloc', 'libdlmalloc-tracing', 'libdlmalloc-debug', + 'libdlmalloc-mt-debug', + 'libdlmalloc-ww', + 'libdlmalloc-ww-debug', 'libembind', 'libembind-rtti', + 'libembind-mt-rtti', 'libemmalloc', 'libemmalloc-debug', 'libemmalloc-memvalidate', @@ -61,56 +77,59 @@ 'libmimalloc-mt', 'libGL', 'libGL-getprocaddr', + 'libGL-mt-getprocaddr', + 'libGL-emu-getprocaddr', + 'libGL-emu-webgl2-ofb-getprocaddr', + 'libGL-webgl2-ofb-getprocaddr', + 'libGL-ww-getprocaddr', 'libhtml5', 'libsockets', + 'libsockets-mt', + 'libsockets-ww', 'libstubs', 'libstubs-debug', 'libstandalonewasm-nocatch', 'crt1', 'crt1_proxy_main', 'crtbegin', - 'libunwind-except', + 'libunwind-legacyexcept', + 'libunwind-wasmexcept', 'libnoexit', -# dotnet change, remove it, so that we don't download the sqlite3 source code in emsdk -# 'sqlite3', -# 'sqlite3-mt', - 'libwebgpu', - 'libwebgpu_cpp', + 'bullet', ] # Additional tasks on top of MINIMAL_TASKS that are necessary for PIC testing on # CI (which has slightly more tests than other modes that want to use MINIMAL) MINIMAL_PIC_TASKS = MINIMAL_TASKS + [ - 'libcompiler_rt-mt', 'libc-mt', - 'libc-mt-debug', 'libc_optz-mt', 'libc_optz-mt-debug', 'libc++abi-mt', 'libc++abi-mt-noexcept', 'libc++abi-debug-mt', - 'libc++abi-debug-mt-noexcept', 'libc++-mt', 'libc++-mt-noexcept', + 'libc++-debug-mt', 'libdlmalloc-mt', 'libGL-emu', 'libGL-emu-webgl2-getprocaddr', - 'libGL-mt-getprocaddr', 'libGL-mt-emu', 'libGL-mt-emu-webgl2-getprocaddr', 'libGL-mt-emu-webgl2-ofb-getprocaddr', 'libsockets_proxy', - 'libsockets-mt', 'crtbegin', 'libsanitizer_common_rt', 'libubsan_rt', - 'libwasm_workers_stub-debug', + 'libwasm_workers-debug', 'libfetch', 'libfetch-mt', 'libwasmfs', 'libwasmfs-debug', 'libwasmfs_no_fs', 'giflib', + 'sdl2', + 'sdl2_gfx', + 'sdl3', ] PORTS = sorted(list(ports.ports_by_name.keys()) + list(ports.port_variants.keys())) @@ -157,8 +176,8 @@ def clear_port(port_name): def build_port(port_name): - with get_port_variant(port_name) as port_name: - ports.build_port(port_name, settings) + with get_port_variant(port_name) as port_name_base: + ports.build_port(port_name_base, settings) def get_system_tasks(): @@ -184,10 +203,10 @@ def main(): parser.add_argument('--lto', action='store_const', const='full', help='build bitcode object for LTO') parser.add_argument('--lto=thin', dest='lto', action='store_const', const='thin', help='build bitcode object for ThinLTO') parser.add_argument('--pic', action='store_true', - help='build relocatable objects for suitable for dynamic linking') - parser.add_argument('--force', action='store_true', + help='build relocatable objects suitable for dynamic linking') + parser.add_argument('-f', '--force', action='store_true', help='force rebuild of target (by removing it first)') - parser.add_argument('--verbose', action='store_true', + parser.add_argument('-v', '--verbose', action='store_true', help='show build commands') parser.add_argument('--wasm64', action='store_true', help='use wasm64 architecture') @@ -196,10 +215,10 @@ def main(): args = parser.parse_args() if args.operation != 'rebuild' and len(args.targets) == 0: - shared.exit_with_error('no build targets specified') + utils.exit_with_error('no build targets specified') if args.operation == 'rebuild' and not USE_NINJA: - shared.exit_with_error('"rebuild" operation is only valid when using Ninja') + utils.exit_with_error('"rebuild" operation is only valid when using Ninja') # process flags @@ -216,11 +235,16 @@ def main(): shared.PRINT_SUBPROCS = True if args.pic: - settings.RELOCATABLE = 1 + settings.MAIN_MODULE = 1 + # Note: we have to filter out the `-ww` libraries here because wasm workers don't + # support dynamic linking. + global MINIMAL_TASKS + global MINIMAL_PIC_TASKS + MINIMAL_TASKS = [t for t in MINIMAL_TASKS if '-ww' not in t] + MINIMAL_PIC_TASKS = [t for t in MINIMAL_PIC_TASKS if '-ww' not in t] if args.wasm64: - settings.MEMORY64 = 2 - MINIMAL_TASKS[:] = [t for t in MINIMAL_TASKS if 'emmalloc' not in t] + settings.MEMORY64 = 1 do_build = args.operation == 'build' do_clear = args.operation == 'clear' @@ -257,7 +281,7 @@ def main(): tasks.append(name) else: # There are some ports that we don't want to build as part - # of ALL since the are not well tested or widely used: + # of ALL since they are not well tested or widely used: if 'cocos2d' in targets: targets.remove('cocos2d') @@ -267,6 +291,9 @@ def main(): if auto_tasks: print('Building targets: %s' % ' '.join(tasks)) + if USE_NINJA: + os.environ['EMBUILDER_PORT_BUILD_DEFERRED'] = '1' + for what in tasks: for old, new in legacy_prefixes.items(): if what.startswith(old): @@ -284,7 +311,7 @@ def main(): if USE_NINJA: library.generate() else: - library.build(deterministic_paths=True) + library.build() elif what == 'sysroot': if do_clear: cache.erase_file('sysroot_install.stamp') diff --git a/emcc b/emcc deleted file mode 100755 index 0ca8082a2cc0e..0000000000000 --- a/emcc +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python_compiler.sh` and -# then run `tools/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -if [ -z "$_EMCC_CCACHE" ]; then - exec "$PYTHON" -E "$0.py" "$@" -else - unset _EMCC_CCACHE - exec ccache "$0" "$@" -fi diff --git a/emcc.bat b/emcc.bat deleted file mode 100644 index 68026fe148de5..0000000000000 --- a/emcc.bat +++ /dev/null @@ -1,95 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python_compiler.bat` and -:: then run `tools/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: If _EMCC_CCACHE is not set, do a regular invocation of the python compiler driver. -:: Otherwise remove the ccache env. var, and then reinvoke this script with ccache enabled. -@if "%_EMCC_CCACHE%"=="" ( - set CMD="%EM_PY%" -E "%MYDIR%%~n0.py" -) else ( - set _EMCC_CCACHE= - set CMD=ccache "%MYDIR%%~n0.bat" -) - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@%CMD% %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@%CMD% %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@%CMD% %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@%CMD% %* diff --git a/emcc.py b/emcc.py index a55c37282011b..6814682d1266b 100644 --- a/emcc.py +++ b/emcc.py @@ -20,31 +20,37 @@ slows down compilation). """ -from tools.toolchain_profiler import ToolchainProfiler - -import json import logging import os -import re import shlex +import shutil import sys -import time import tarfile +from dataclasses import dataclass from enum import Enum, auto, unique -from subprocess import PIPE - -from tools import shared, system_libs, utils, ports -from tools import colored_logger, diagnostics, building -from tools.shared import unsuffixed, unsuffixed_basename, get_file_suffix -from tools.shared import run_process, exit_with_error, DEBUG -from tools.shared import in_temp, OFormat -from tools.shared import DYNAMICLIB_ENDINGS +# This assert needs to happen early, before any too-recent python syntax is used. +# In particular it needs to happen before we import any python file that uses the +# `match` keyword. +assert sys.version_info >= (3, 10), f'emscripten requires python 3.10 or above ({sys.executable} {sys.version})' + +from tools import ( + building, + cache, + cmdline, + compile, + config, + diagnostics, + shared, + system_libs, + utils, +) +from tools.cmdline import CLANG_FLAGS_WITH_ARGS, options from tools.response_file import substitute_response_files -from tools import config -from tools import cache -from tools.settings import default_setting, user_settings, settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS -from tools.utils import read_file, removeprefix +from tools.settings import COMPILE_TIME_SETTINGS, default_setting, settings, user_settings +from tools.shared import DEBUG, DYLIB_EXTENSIONS, in_temp +from tools.toolchain_profiler import ToolchainProfiler +from tools.utils import exit_with_error, get_file_suffix, read_file, unsuffixed_basename logger = logging.getLogger('emcc') @@ -56,106 +62,57 @@ import bootstrap bootstrap.check() -# endings = dot + a suffix, compare against result of shared.suffix() -C_ENDINGS = ['.c', '.i'] -CXX_ENDINGS = ['.cppm', '.pcm', '.cpp', '.cxx', '.cc', '.c++', '.CPP', '.CXX', '.C', '.CC', '.C++', '.ii'] -OBJC_ENDINGS = ['.m', '.mi'] -PREPROCESSED_ENDINGS = ['.i', '.ii'] -OBJCXX_ENDINGS = ['.mm', '.mii'] -SPECIAL_ENDINGLESS_FILENAMES = [os.devnull] -C_ENDINGS += SPECIAL_ENDINGLESS_FILENAMES # consider the special endingless filenames like /dev/null to be C - -SOURCE_ENDINGS = C_ENDINGS + CXX_ENDINGS + OBJC_ENDINGS + OBJCXX_ENDINGS + ['.bc', '.ll', '.S'] -ASSEMBLY_ENDINGS = ['.s'] -HEADER_ENDINGS = ['.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH'] - -# These symbol names are allowed in INCOMING_MODULE_JS_API but are not part of the -# default set. -EXTRA_INCOMING_JS_API = [ - 'fetchSettings' -] - -SIMD_INTEL_FEATURE_TOWER = ['-msse', '-msse2', '-msse3', '-mssse3', '-msse4.1', '-msse4.2', '-msse4', '-mavx'] -SIMD_NEON_FLAGS = ['-mfpu=neon'] +PREPROCESSED_EXTENSIONS = {'.i', '.ii'} +ASSEMBLY_EXTENSIONS = {'.s'} +HEADER_EXTENSIONS = {'.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH'} +SOURCE_EXTENSIONS = { + '.c', '.i', # C + '.cppm', '.pcm', '.cpp', '.cxx', '.cc', '.c++', '.CPP', '.CXX', '.C', '.CC', '.C++', '.ii', # C++ + '.m', '.mi', '.mm', '.mii', # ObjC/ObjC++ + '.bc', '.ll', # LLVM IR + '.S', # asm with preprocessor + os.devnull, # consider the special endingless filenames like /dev/null to be C +} | PREPROCESSED_EXTENSIONS + LINK_ONLY_FLAGS = { '--bind', '--closure', '--cpuprofiler', '--embed-file', '--emit-symbol-map', '--emrun', '--exclude-file', '--extern-post-js', '--extern-pre-js', '--ignore-dynamic-linking', '--js-library', - '--js-transform', '--oformat', '--output_eol', + '--js-transform', '--oformat', '--output_eol', '--output-eol', '--post-js', '--pre-js', '--preload-file', '--profiling-funcs', '--proxy-to-worker', '--shell-file', '--source-map-base', - '--threadprofiler', '--use-preload-plugins' + '--threadprofiler', '--use-preload-plugins', } @unique class Mode(Enum): - PREPROCESS_ONLY = auto() - PCH = auto() + # Used any time we are not linking, including PCH, pre-processing, etc COMPILE_ONLY = auto() + # Only when --post-link is specified POST_LINK_ONLY = auto() + # This is the default mode, in the absence of any flags such as -c, -E, etc COMPILE_AND_LINK = auto() +@dataclass +class LinkFlag: + """Used to represent a linker flag. + + The flag value is stored along with a bool that distinguishes input + files from non-files. + + A list of these is returned by separate_linker_flags. + """ + value: str + is_file: int + + class EmccState: def __init__(self, args): self.mode = Mode.COMPILE_AND_LINK # Using tuple here to prevent accidental mutation self.orig_args = tuple(args) - self.has_dash_c = False - self.has_dash_E = False - self.has_dash_S = False - self.link_flags = [] - self.lib_dirs = [] - self.forced_stdlibs = [] - - def add_link_flag(self, i, f): - if f.startswith('-L'): - self.lib_dirs.append(f[2:]) - - self.link_flags.append((i, f)) - - -class EmccOptions: - def __init__(self): - self.target = '' - self.output_file = None - self.no_minify = False - self.post_link = False - self.executable = False - self.compiler_wrapper = None - self.oformat = None - self.requested_debug = '' - self.emit_symbol_map = False - self.use_closure_compiler = None - self.closure_args = [] - self.js_transform = None - self.pre_js = [] # before all js - self.post_js = [] # after all js - self.extern_pre_js = [] # before all js, external to optimized code - self.extern_post_js = [] # after all js, external to optimized code - self.preload_files = [] - self.embed_files = [] - self.exclude_files = [] - self.ignore_dynamic_linking = False - self.shell_path = None - self.source_map_base = '' - self.emit_tsd = '' - self.embind_emit_tsd = '' - self.emrun = False - self.cpu_profiler = False - self.memory_profiler = False - self.use_preload_cache = False - self.use_preload_plugins = False - self.valid_abspaths = [] - # Specifies the line ending format to use for all generated text files. - # Defaults to using the native EOL on each platform (\r\n on Windows, \n on - # Linux & MacOS) - self.output_eol = os.linesep - self.no_entry = False - self.shared = False - self.relocatable = False - self.reproduce = None def create_reproduce_file(name, args): @@ -167,10 +124,10 @@ def make_relative(filename): root = unsuffixed_basename(name) with tarfile.open(name, 'w') as reproduce_file: - reproduce_file.add(shared.path_from_root('emscripten-version.txt'), os.path.join(root, 'version.txt')) + reproduce_file.add(utils.path_from_root('emscripten-version.txt'), os.path.join(root, 'version.txt')) with shared.get_temp_files().get_file(suffix='.tar') as rsp_name: - with open(rsp_name, 'w') as rsp: + with open(rsp_name, 'w', encoding='utf-8') as rsp: ignore_next = False output_arg = None @@ -180,7 +137,7 @@ def make_relative(filename): if arg.startswith('--reproduce='): continue - if arg.startswith('-o='): + if len(arg) > 2 and arg.startswith('-o'): rsp.write('-o\n') arg = arg[3:] output_arg = True @@ -204,13 +161,7 @@ def make_relative(filename): if ignore: continue - if arg in ('-MT', '-MF', '-MJ', '-MQ', '-D', '-U', '-o', '-x', - '-Xpreprocessor', '-include', '-imacros', '-idirafter', - '-iprefix', '-iwithprefix', '-iwithprefixbefore', - '-isysroot', '-imultilib', '-A', '-isystem', '-iquote', - '-install_name', '-compatibility_version', - '-current_version', '-I', '-L', '-include-pch', - '-Xlinker', '-Xclang'): + if arg in CLANG_FLAGS_WITH_ARGS: ignore_next = True if arg == '-o': @@ -219,266 +170,8 @@ def make_relative(filename): reproduce_file.add(rsp_name, os.path.join(root, 'response.txt')) -def expand_byte_size_suffixes(value): - """Given a string with KB/MB size suffixes, such as "32MB", computes how - many bytes that is and returns it as an integer. - """ - 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) - value, suffix = match.groups() - value = int(value) - if suffix: - size_suffixes = {suffix: 1024 ** i for i, suffix in enumerate(['b', 'kb', 'mb', 'gb', 'tb'])} - value *= size_suffixes[suffix.lower()] - return value - - -def apply_user_settings(): - """Take a map of users settings {NAME: VALUE} and apply them to the global - settings object. - """ - - # Stash a copy of all available incoming APIs before the user can potentially override it - settings.ALL_INCOMING_MODULE_JS_API = settings.INCOMING_MODULE_JS_API + EXTRA_INCOMING_JS_API - - for key, value in user_settings.items(): - if key in settings.internal_settings: - exit_with_error('%s is an internal setting and cannot be set from command line', key) - - # map legacy settings which have aliases to the new names - # but keep the original key so errors are correctly reported via the `setattr` below - user_key = key - if key in settings.legacy_settings and key in settings.alt_names: - key = settings.alt_names[key] - - # In those settings fields that represent amount of memory, translate suffixes to multiples of 1024. - if key in MEM_SIZE_SETTINGS: - value = str(expand_byte_size_suffixes(value)) - - filename = None - if value and value[0] == '@': - filename = removeprefix(value, '@') - if not os.path.isfile(filename): - exit_with_error('%s: file not found parsing argument: %s=%s' % (filename, key, value)) - value = read_file(filename).strip() - else: - value = value.replace('\\', '\\\\') - - expected_type = settings.types.get(key) - - if filename and expected_type == list and value.strip()[0] != '[': - # Prefer simpler one-line-per value parser - value = parse_symbol_list_file(value) - else: - try: - value = parse_value(value, expected_type) - except Exception as e: - exit_with_error(f'error parsing "-s" setting "{key}={value}": {e}') - - setattr(settings, user_key, value) - - if key == 'EXPORTED_FUNCTIONS': - # used for warnings in emscripten.py - settings.USER_EXPORTED_FUNCTIONS = settings.EXPORTED_FUNCTIONS.copy() - - # TODO(sbc): Remove this legacy way. - if key == 'WASM_OBJECT_FILES': - settings.LTO = 0 if value else 'full' - - -def cxx_to_c_compiler(cxx): - # Convert C++ compiler name into C compiler name - dirname, basename = os.path.split(cxx) - basename = basename.replace('clang++', 'clang').replace('g++', 'gcc').replace('em++', 'emcc') - return os.path.join(dirname, basename) - - -def is_dash_s_for_emcc(args, i): - # -s OPT=VALUE or -s OPT or -sOPT are all interpreted as emscripten flags. - # -s by itself is a linker option (alias for --strip-all) - if args[i] == '-s': - if len(args) <= i + 1: - return False - arg = args[i + 1] - else: - arg = removeprefix(args[i], '-s') - arg = arg.split('=')[0] - return arg.isidentifier() and arg.isupper() - - -def parse_s_args(args): - settings_changes = [] - for i in range(len(args)): - if args[i].startswith('-s'): - if is_dash_s_for_emcc(args, i): - if args[i] == '-s': - key = args[i + 1] - args[i + 1] = '' - else: - key = removeprefix(args[i], '-s') - args[i] = '' - - # If not = is specified default to 1 - if '=' not in key: - key += '=1' - - # Special handling of browser version targets. A version -1 means that the specific version - # is not supported at all. Replace those with INT32_MAX to make it possible to compare e.g. - # #if MIN_FIREFOX_VERSION < 68 - if re.match(r'MIN_.*_VERSION(=.*)?', key): - try: - if int(key.split('=')[1]) < 0: - key = key.split('=')[0] + '=0x7FFFFFFF' - except Exception: - pass - - settings_changes.append(key) - - newargs = [a for a in args if a] - return (settings_changes, newargs) - - -def get_target_flags(): - return ['-target', shared.get_llvm_target()] - - -def get_clang_flags(user_args): - flags = get_target_flags() - - # if exception catching is disabled, we can prevent that code from being - # generated in the frontend - if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS: - flags.append('-fignore-exceptions') - - if settings.INLINING_LIMIT: - flags.append('-fno-inline-functions') - - if settings.RELOCATABLE and '-fPIC' not in user_args: - flags.append('-fPIC') - - # We use default visiibilty=default in emscripten even though the upstream - # backend defaults visibility=hidden. This matched the expectations of C/C++ - # code in the wild which expects undecorated symbols to be exported to other - # DSO's by default. - if not any(a.startswith('-fvisibility') for a in user_args): - flags.append('-fvisibility=default') - - if settings.LTO: - if not any(a.startswith('-flto') for a in user_args): - flags.append('-flto=' + settings.LTO) - # setjmp/longjmp handling using Wasm EH - # For non-LTO, '-mllvm -wasm-enable-eh' added in - # building.llvm_backend_args() sets this feature in clang. But in LTO, the - # argument is added to wasm-ld instead, so clang needs to know that EH is - # enabled so that it can be added to the attributes in LLVM IR. - if settings.SUPPORT_LONGJMP == 'wasm': - flags.append('-mexception-handling') - - else: - # In LTO mode these args get passed instead at link time when the backend runs. - for a in building.llvm_backend_args(): - flags += ['-mllvm', a] - - return flags - - -cflags = None - - -def get_cflags(user_args, is_cxx): - global cflags - if cflags: - return cflags - - # Flags we pass to the compiler when building C/C++ code - # We add these to the user's flags (newargs), but not when building .s or .S assembly files - cflags = get_clang_flags(user_args) - cflags.append('--sysroot=' + cache.get_sysroot(absolute=True)) - - if settings.EMSCRIPTEN_TRACING: - cflags.append('-D__EMSCRIPTEN_TRACING__=1') - - if settings.SHARED_MEMORY: - cflags.append('-D__EMSCRIPTEN_SHARED_MEMORY__=1') - - if settings.WASM_WORKERS: - cflags.append('-D__EMSCRIPTEN_WASM_WORKERS__=1') - - if not settings.STRICT: - # The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code - # in strict mode. Code should use the define __EMSCRIPTEN__ instead. - cflags.append('-DEMSCRIPTEN') - - # Changes to default clang behavior - - # Implicit functions can cause horribly confusing function pointer type errors, see #2175 - # If your codebase really needs them - very unrecommended! - you can disable the error with - # -Wno-error=implicit-function-declaration - # or disable even a warning about it with - # -Wno-implicit-function-declaration - # This is already an error in C++ so we don't need to inject extra flags. - if not is_cxx: - cflags += ['-Werror=implicit-function-declaration'] - - ports.add_cflags(cflags, settings) - - def array_contains_any_of(hay, needles): - for n in needles: - if n in hay: - return True - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER) or array_contains_any_of(user_args, SIMD_NEON_FLAGS): - if '-msimd128' not in user_args and '-mrelaxed-simd' not in user_args: - exit_with_error('passing any of ' + ', '.join(SIMD_INTEL_FEATURE_TOWER + SIMD_NEON_FLAGS) + ' flags also requires passing -msimd128 (or -mrelaxed-simd)!') - cflags += ['-D__SSE__=1'] - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[1:]): - cflags += ['-D__SSE2__=1'] - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[2:]): - cflags += ['-D__SSE3__=1'] - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[3:]): - cflags += ['-D__SSSE3__=1'] - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[4:]): - cflags += ['-D__SSE4_1__=1'] - - # Handle both -msse4.2 and its alias -msse4. - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[5:]): - cflags += ['-D__SSE4_2__=1'] - - if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[7:]): - cflags += ['-D__AVX__=1'] - - if array_contains_any_of(user_args, SIMD_NEON_FLAGS): - cflags += ['-D__ARM_NEON__=1'] - - if '-nostdinc' not in user_args: - if not settings.USE_SDL: - cflags += ['-Xclang', '-iwithsysroot' + os.path.join('/include', 'fakesdl')] - cflags += ['-Xclang', '-iwithsysroot' + os.path.join('/include', 'compat')] - - return cflags - - -def get_library_basename(filename): - """Similar to get_file_suffix this strips off all numeric suffixes and then - then final non-numeric one. For example for 'libz.so.1.2.8' returns 'libz'""" - filename = os.path.basename(filename) - while filename: - filename, suffix = os.path.splitext(filename) - # Keep stipping suffixes until we strip a non-numeric one. - if not suffix[1:].isdigit(): - return filename - - -# -# Main run() function -# -def run(args): +@ToolchainProfiler.profile() +def main(args): if shared.run_via_emxx: clang = shared.CLANG_CXX else: @@ -491,17 +184,16 @@ def run(args): # want that to break for users of EMCC_CFLAGS. if len(args) == 2 and args[1] == '-v': # autoconf likes to see 'GNU' in the output to enable shared object support - print(version_string(), file=sys.stderr) - return shared.check_call([clang, '-v'] + get_target_flags(), check=False).returncode + print(cmdline.version_string(), file=sys.stderr) + return shared.check_call([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 - EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS') - if EMCC_CFLAGS: + if EMCC_CFLAGS := os.environ.get('EMCC_CFLAGS'): args += shlex.split(EMCC_CFLAGS) if DEBUG: - logger.warning(f'invocation: {shared.shlex_join(args)} (in {os.getcwd()})') + logger.warning(f'invocation: {shlex.join(args)} (in {os.getcwd()})') # Strip args[0] (program name) args = args[1:] @@ -511,7 +203,7 @@ def run(args): # read response files very early on try: args = substitute_response_files(args) - except IOError as e: + except OSError as e: exit_with_error(e) if '--help' in args: @@ -533,80 +225,42 @@ def run(args): ## Process argument and setup the compiler state = EmccState(args) - options, newargs = phase_parse_arguments(state) + newargs = cmdline.parse_arguments(state.orig_args) if not shared.SKIP_SUBPROCS: shared.check_sanity() + # Begin early-exit flag handling. + if '--version' in args: - print(version_string()) + print(cmdline.version_string()) print('''\ -Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt) +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 0 if '-dumpversion' in args: # gcc's doc states "Print the compiler version [...] and don't do anything else." - print(shared.EMSCRIPTEN_VERSION) - return 0 - - if '--cflags' in args: - # fake running the command, to see the full args we pass to clang - args = [x for x in args if x != '--cflags'] - with shared.get_temp_files().get_file(suffix='.o') as temp_target: - input_file = 'hello_world.c' - compiler = shared.EMCC - if shared.run_via_emxx: - compiler = shared.EMXX - cmd = [compiler, utils.path_from_root('test', input_file), '-v', '-c', '-o', temp_target] + args - proc = run_process(cmd, stderr=PIPE, check=False) - if proc.returncode != 0: - print(proc.stderr) - exit_with_error('error getting cflags') - lines = [x for x in proc.stderr.splitlines() if clang in x and input_file in x] - if not lines: - exit_with_error(f'unable to parse output of `{cmd}`:\n{proc.stderr}') - parts = shlex.split(lines[0].replace('\\', '\\\\')) - parts = [x for x in parts if x not in ['-c', '-o', '-v', '-emit-llvm'] and input_file not in x and temp_target not in x] - print(shared.shlex_join(parts[1:])) + print(utils.EMSCRIPTEN_VERSION) return 0 - if 'EMMAKEN_NO_SDK' in os.environ: - exit_with_error('EMMAKEN_NO_SDK is no longer supported. The standard -nostdlib and -nostdinc flags should be used instead') - - if 'EMMAKEN_COMPILER' in os.environ: - exit_with_error('`EMMAKEN_COMPILER` is no longer supported.\n' + - 'Please use the `LLVM_ROOT` and/or `COMPILER_WRAPPER` config settings instead') - - if 'EMMAKEN_CFLAGS' in os.environ: - exit_with_error('`EMMAKEN_CFLAGS` is no longer supported, please use `EMCC_CFLAGS` instead') - - if 'EMCC_REPRODUCE' in os.environ: - options.reproduce = os.environ['EMCC_REPRODUCE'] - - # For internal consistency, ensure we don't attempt or read or write any link time - # settings until we reach the linking phase. - settings.limit_settings(COMPILE_TIME_SETTINGS) - - newargs, input_files = phase_setup(options, state, newargs) - - if '-dumpmachine' in newargs or '-print-target-triple' in newargs or '--print-target-triple' in newargs: + if '-dumpmachine' in args or '-print-target-triple' in args or '--print-target-triple' in args: print(shared.get_llvm_target()) return 0 - if '-print-search-dirs' in newargs or '--print-search-dirs' in newargs: + if '-print-search-dirs' in args or '--print-search-dirs' in args: print(f'programs: ={config.LLVM_ROOT}') print(f'libraries: ={cache.get_lib_dir(absolute=True)}') return 0 - if '-print-libgcc-file-name' in newargs or '--print-libgcc-file-name' in newargs: + if '-print-libgcc-file-name' in args or '--print-libgcc-file-name' in args: settings.limit_settings(None) compiler_rt = system_libs.Library.get_usable_variations()['libcompiler_rt'] print(compiler_rt.get_path(absolute=True)) return 0 - print_file_name = [a for a in newargs if a.startswith('-print-file-name=') or a.startswith('--print-file-name=')] + 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] system_libpath = cache.get_lib_dir(absolute=True) @@ -617,188 +271,128 @@ def run(args): print(libname) return 0 + # End early-exit flag handling + + if 'EMMAKEN_NO_SDK' in os.environ: + exit_with_error('EMMAKEN_NO_SDK is no longer supported. The standard -nostdlib and -nostdinc flags should be used instead') + + if 'EMMAKEN_COMPILER' in os.environ: + exit_with_error('`EMMAKEN_COMPILER` is no longer supported.\n' + + 'Please use the `LLVM_ROOT` and/or `COMPILER_WRAPPER` config settings instead') + + if 'EMMAKEN_CFLAGS' in os.environ: + exit_with_error('`EMMAKEN_CFLAGS` is no longer supported, please use `EMCC_CFLAGS` instead') + + if 'EMCC_REPRODUCE' in os.environ: + options.reproduce = os.environ['EMCC_REPRODUCE'] + + # 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) + + if '-print-resource-dir' in args or any(a.startswith('--print-prog-name') for a in args): + shared.exec_process([clang] + compile.get_cflags(tuple(args)) + args) + 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 + if options.reproduce: create_reproduce_file(options.reproduce, args) if state.mode == Mode.POST_LINK_ONLY: - if len(input_files) != 1: + if len(options.input_files) != 1: exit_with_error('--post-link requires a single input file') + linker_args = separate_linker_flags(newargs)[1] + linker_args = [f.value for f in linker_args] # Delay import of link.py to avoid processing this file when only compiling - from tools import link - link.run_post_link(input_files[0][1], options, state, newargs) + from tools import link # noqa: PLC0415 + link.run_post_link(options.input_files[0], options, linker_args) return 0 - ## Compile source code to object files - linker_inputs = phase_compile_inputs(options, state, newargs, input_files) + # Compile source code to object files + # When only compiling this function never returns. + linker_args = phase_compile_inputs(options, state, newargs) if state.mode == Mode.COMPILE_AND_LINK: # Delay import of link.py to avoid processing this file when only compiling from tools import link - return link.run(linker_inputs, options, state, newargs) + return link.run(options, linker_args) else: logger.debug('stopping after compile phase') return 0 -def normalize_boolean_setting(name, value): - # boolean NO_X settings are aliases for X - # (note that *non*-boolean setting values have special meanings, - # and we can't just flip them, so leave them as-is to be - # handled in a special way later) - if name.startswith('NO_') and value in ('0', '1'): - name = removeprefix(name, 'NO_') - value = str(1 - int(value)) - return name, value - +def separate_linker_flags(newargs): + """Process argument list separating out compiler args and linker args. -@ToolchainProfiler.profile_block('parse arguments') -def phase_parse_arguments(state): - """The first phase of the compiler. Parse command line argument and - populate settings. + - Linker flags include input files and are returned a list of LinkFlag objects. + - Compiler flags are those to be passed to `clang -c`. """ - newargs = list(state.orig_args) - - # Scan and strip emscripten specific cmdline warning flags. - # This needs to run before other cmdline flags have been parsed, so that - # warnings are properly printed during arg parse. - newargs = diagnostics.capture_warnings(newargs) - - if not diagnostics.is_enabled('deprecated'): - settings.WARN_DEPRECATED = 0 - - for i in range(len(newargs)): - if newargs[i] in ('-l', '-L', '-I', '-z'): - # Scan for flags that can be written as either one or two arguments - # and normalize them to the single argument form. - newargs[i] += newargs[i + 1] - newargs[i + 1] = '' - - options, settings_changes, user_js_defines, newargs = parse_args(newargs) - - if options.post_link or options.oformat == OFormat.BARE: - diagnostics.warning('experimental', '--oformat=bare/--post-link are experimental and subject to change.') - - explicit_settings_changes, newargs = parse_s_args(newargs) - settings_changes += explicit_settings_changes - - for s in settings_changes: - key, value = s.split('=', 1) - key, value = normalize_boolean_setting(key, value) - old_value = user_settings.get(key) - if old_value and old_value != value: - diagnostics.warning('unused-command-line-argument', f'-s{key} specified multiple times. Ignoring previous value (`{old_value}`)') - user_settings[key] = value - - # STRICT is used when applying settings so it needs to be applied first before - # calling `apply_user_settings`. - strict_cmdline = user_settings.get('STRICT') - if strict_cmdline: - settings.STRICT = int(strict_cmdline) - - # Apply user -jsD settings - for s in user_js_defines: - settings[s[0]] = s[1] - - # Apply -s settings in newargs here (after optimization levels, so they can override them) - apply_user_settings() - return options, newargs + compiler_args = [] + linker_args = [] + def add_link_arg(flag, is_file=False): + linker_args.append(LinkFlag(flag, is_file)) -@ToolchainProfiler.profile_block('setup') -def phase_setup(options, state, newargs): - """Second phase: configure and setup the compiler based on the specified settings and arguments. - """ - - if settings.RUNTIME_LINKED_LIBS: - newargs += settings.RUNTIME_LINKED_LIBS - - # Find input files - - # These three arrays are used to store arguments of different types for - # type-specific processing. In order to shuffle the arguments back together - # after processing, all of these arrays hold tuples (original_index, value). - # Note that the index part of the tuple can have a fractional part for input - # arguments that expand into multiple processed arguments, as in -Wl,-f1,-f2. - input_files = [] - - # find input files with a simple heuristic. we should really analyze - # based on a full understanding of gcc params, right now we just assume that - # what is left contains no more |-x OPT| things skip = False - has_header_inputs = False for i in range(len(newargs)): if skip: skip = False continue arg = newargs[i] - if arg in {'-MT', '-MF', '-MJ', '-MQ', '-D', '-U', '-o', '-x', - '-Xpreprocessor', '-include', '-imacros', '-idirafter', - '-iprefix', '-iwithprefix', '-iwithprefixbefore', - '-isysroot', '-imultilib', '-A', '-isystem', '-iquote', - '-install_name', '-compatibility_version', - '-current_version', '-I', '-L', '-include-pch', - '-undefined', '-target', - '-Xlinker', '-Xclang', '-z'}: + if arg in CLANG_FLAGS_WITH_ARGS: skip = True - if not arg.startswith('-'): - # we already removed -o , so all these should be inputs - newargs[i] = '' - # os.devnul should always be reported as existing but there is bug in windows - # python before 3.8: - # https://bugs.python.org/issue1311 - if not os.path.exists(arg) and arg != os.devnull: + def get_next_arg(): + if len(newargs) <= i + 1: + exit_with_error(f"option '{arg}' requires an argument") + return newargs[i + 1] + + if not arg.startswith('-') or arg == '-': + if not os.path.exists(arg) and arg != '-': exit_with_error('%s: No such file or directory ("%s" was expected to be an input file, based on the commandline arguments provided)', arg, arg) - file_suffix = get_file_suffix(arg) - if file_suffix in HEADER_ENDINGS: - has_header_inputs = True - input_files.append((i, arg)) - elif arg.startswith('-L'): - state.add_link_flag(i, arg) - elif arg.startswith('-l'): - state.add_link_flag(i, arg) + add_link_arg(arg, True) elif arg == '-z': - state.add_link_flag(i, newargs[i]) - state.add_link_flag(i + 1, newargs[i + 1]) - elif arg.startswith('-z'): - state.add_link_flag(i, newargs[i]) + add_link_arg(arg) + add_link_arg(get_next_arg()) elif arg.startswith('-Wl,'): - # Multiple comma separated link flags can be specified. Create fake - # fractional indices for these: -Wl,a,b,c,d at index 4 becomes: - # (4, a), (4.25, b), (4.5, c), (4.75, d) - link_flags_to_add = arg.split(',')[1:] - for flag_index, flag in enumerate(link_flags_to_add): - state.add_link_flag(i + float(flag_index) / len(link_flags_to_add), flag) + for flag in arg.split(',')[1:]: + add_link_arg(flag) elif arg == '-Xlinker': - state.add_link_flag(i + 1, newargs[i + 1]) - elif arg == '-s': - state.add_link_flag(i, newargs[i]) - elif arg == '-': - input_files.append((i, arg)) - newargs[i] = '' + add_link_arg(get_next_arg()) + elif arg == '-s' or arg.startswith(('-l', '-L', '--js-library=', '-z', '-u')): + add_link_arg(arg) + elif not arg.startswith('-o') and arg not in {'-nostdlib', '-nostartfiles', '-nolibc', '-nodefaultlibs', '-s'}: + # All other flags are for the compiler + compiler_args.append(arg) + if skip: + compiler_args.append(get_next_arg()) + + return compiler_args, linker_args - newargs = [a for a in newargs if a] - # SSEx is implemented on top of SIMD128 instruction set, but do not pass SSE flags to LLVM - # so it won't think about generating native x86 SSE code. - newargs = [x for x in newargs if x not in SIMD_INTEL_FEATURE_TOWER and x not in SIMD_NEON_FLAGS] +@ToolchainProfiler.profile_block('setup') +def phase_setup(state): + """Second phase: configure and setup the compiler based on the specified settings and arguments. + """ - state.has_dash_c = '-c' in newargs or '--precompile' in newargs - state.has_dash_S = '-S' in newargs - state.has_dash_E = '-E' in newargs + has_header_inputs = any(get_file_suffix(f) in HEADER_EXTENSIONS for f in options.input_files) if options.post_link: state.mode = Mode.POST_LINK_ONLY - elif state.has_dash_E or '-M' in newargs or '-MM' in newargs or '-fsyntax-only' in newargs: - state.mode = Mode.PREPROCESS_ONLY - elif has_header_inputs: - state.mode = Mode.PCH - elif state.has_dash_c or state.has_dash_S: + elif has_header_inputs or options.dash_c or options.dash_S or options.syntax_only or options.dash_E or options.dash_M: state.mode = Mode.COMPILE_ONLY - if state.mode in (Mode.COMPILE_ONLY, Mode.PREPROCESS_ONLY): + if state.mode == Mode.COMPILE_ONLY: for key in user_settings: if key not in COMPILE_TIME_SETTINGS: diagnostics.warning( @@ -810,9 +404,6 @@ def phase_setup(options, state, newargs): 'unused-command-line-argument', "linker flag ignored during compilation: '%s'" % arg) - if settings.MAIN_MODULE or settings.SIDE_MODULE: - settings.RELOCATABLE = 1 - if 'USE_PTHREADS' in user_settings: settings.PTHREADS = settings.USE_PTHREADS @@ -820,22 +411,11 @@ def phase_setup(options, state, newargs): if settings.PTHREADS or settings.WASM_WORKERS: settings.SHARED_MEMORY = 1 - if settings.PTHREADS and '-pthread' not in newargs: - newargs += ['-pthread'] - elif settings.SHARED_MEMORY: - if '-matomics' not in newargs: - newargs += ['-matomics'] - if '-mbulk-memory' not in newargs: - newargs += ['-mbulk-memory'] - - if settings.SHARED_MEMORY: - settings.BULK_MEMORY = 1 - if 'DISABLE_EXCEPTION_CATCHING' in user_settings and 'EXCEPTION_CATCHING_ALLOWED' in user_settings: # If we get here then the user specified both DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED # on the command line. This is no longer valid so report either an error or a warning (for # backwards compat with the old `DISABLE_EXCEPTION_CATCHING=2` - if user_settings['DISABLE_EXCEPTION_CATCHING'] in ('0', '2'): + if user_settings['DISABLE_EXCEPTION_CATCHING'] in {'0', '2'}: diagnostics.warning('deprecated', 'DISABLE_EXCEPTION_CATCHING=X is no longer needed when specifying EXCEPTION_CATCHING_ALLOWED') else: exit_with_error('DISABLE_EXCEPTION_CATCHING and EXCEPTION_CATCHING_ALLOWED are mutually exclusive') @@ -867,10 +447,8 @@ def phase_setup(options, state, newargs): if options.target.startswith('wasm64'): default_setting('MEMORY64', 1) - if settings.MEMORY64: - if options.target.startswith('wasm32'): - exit_with_error('wasm32 target is not compatible with -sMEMORY64') - diagnostics.warning('experimental', '-sMEMORY64 is still experimental. Many features may not work.') + if settings.MEMORY64 and options.target.startswith('wasm32'): + exit_with_error('wasm32 target is not compatible with -sMEMORY64') # Wasm SjLj cannot be used with Emscripten EH if settings.SUPPORT_LONGJMP == 'wasm': @@ -894,29 +472,9 @@ def phase_setup(options, state, newargs): else: settings.SUPPORT_LONGJMP = 'emscripten' - # SDL2 requires eglGetProcAddress() to work. - # NOTE: if SDL2 is updated to not rely on eglGetProcAddress(), this can be removed - if settings.USE_SDL == 2 or settings.USE_SDL_MIXER == 2 or settings.USE_SDL_GFX == 2: - default_setting('GL_ENABLE_GET_PROC_ADDRESS', 1) - - return (newargs, input_files) - - -def get_clang_output_extension(state): - if '-emit-llvm' in state.orig_args: - if state.has_dash_S: - return '.ll' - else: - return '.bc' - - if state.has_dash_S: - return '.s' - else: - return '.o' - @ToolchainProfiler.profile_block('compile inputs') -def phase_compile_inputs(options, state, newargs, input_files): +def phase_compile_inputs(options, state, newargs): if shared.run_via_emxx: compiler = [shared.CLANG_CXX] else: @@ -926,655 +484,102 @@ def phase_compile_inputs(options, state, newargs, input_files): logger.debug('using compiler wrapper: %s', config.COMPILER_WRAPPER) compiler.insert(0, config.COMPILER_WRAPPER) - compile_args = newargs system_libs.ensure_sysroot() - def get_language_mode(args): - return_next = False - for item in args: - if return_next: - return item - if item == '-x': - return_next = True - continue - if item.startswith('-x'): - return removeprefix(item, '-x') - return '' - - language_mode = get_language_mode(newargs) - use_cxx = 'c++' in language_mode or shared.run_via_emxx - def get_clang_command(): - return compiler + get_cflags(state.orig_args, use_cxx) + compile_args + return compiler + compile.get_cflags(state.orig_args) def get_clang_command_preprocessed(): - return compiler + get_clang_flags(state.orig_args) + compile_args + return compiler + compile.get_clang_flags(state.orig_args) def get_clang_command_asm(): - return compiler + get_target_flags() + compile_args - - # preprocessor-only (-E) support - if state.mode == Mode.PREPROCESS_ONLY: - inputs = [i[1] for i in input_files] - cmd = get_clang_command() + inputs - if options.output_file: - cmd += ['-o', options.output_file] - # Do not compile, but just output the result from preprocessing stage or - # output the dependency rule. Warning: clang and gcc behave differently - # with -MF! (clang seems to not recognize it) - logger.debug(('just preprocessor ' if state.has_dash_E else 'just dependencies: ') + ' '.join(cmd)) - shared.exec_process(cmd) - - # Precompiled headers support - if state.mode == Mode.PCH: - inputs = [i[1] for i in input_files] - for header in inputs: - if not shared.suffix(header) in HEADER_ENDINGS: - exit_with_error(f'cannot mix precompiled headers with non-header inputs: {inputs} : {header}') - cmd = get_clang_command() + inputs - if options.output_file: - cmd += ['-o', options.output_file] - logger.debug(f"running (for precompiled headers): {cmd[0]} {' '.join(cmd[1:])}") - shared.exec_process(cmd) + return compiler + compile.get_target_flags() if state.mode == Mode.COMPILE_ONLY: - inputs = [i[1] for i in input_files] - if all(get_file_suffix(i) in ASSEMBLY_ENDINGS for i in inputs): - cmd = get_clang_command_asm() + inputs + if options.output_file and get_file_suffix(options.output_file) == '.bc' and not settings.LTO and '-emit-llvm' not in state.orig_args: + diagnostics.warning('emcc', '.bc output file suffix used without -flto or -emit-llvm. Consider using .o extension since emcc will output an object file, not a bitcode file') + if all(get_file_suffix(i) in ASSEMBLY_EXTENSIONS for i in options.input_files): + cmd = get_clang_command_asm() + newargs else: - cmd = get_clang_command() + inputs - if options.output_file: - cmd += ['-o', options.output_file] - if get_file_suffix(options.output_file) == '.bc' and not settings.LTO and '-emit-llvm' not in state.orig_args: - diagnostics.warning('emcc', '.bc output file suffix used without -flto or -emit-llvm. Consider using .o extension since emcc will output an object file, not a bitcode file') + cmd = get_clang_command() + newargs shared.exec_process(cmd) + assert False, 'exec_process should not return' # In COMPILE_AND_LINK we need to compile source files too, but we also need to # filter out the link flags + assert state.mode == Mode.COMPILE_AND_LINK + assert not options.dash_c + compile_args, linker_args = separate_linker_flags(newargs) - def is_link_flag(flag): - if flag in ('-nostdlib', '-nostartfiles', '-nolibc', '-nodefaultlibs', '-s'): - return True - return flag.startswith(('-l', '-L', '-Wl,', '-z')) - - compile_args = [a for a in compile_args if a and not is_link_flag(a)] - linker_inputs = [] + # Map of file basenames to how many times we've seen them. We use this to generate + # unique `_NN` suffix for object files in cases when we are compiling multiple sources that + # have the same basename. e.g. `foo/utils.c` and `bar/utils.c` on the same command line. seen_names = {} def uniquename(name): if name not in seen_names: - seen_names[name] = str(len(seen_names)) - return unsuffixed(name) + '_' + seen_names[name] + shared.suffix(name) + # No suffix needed the first time we see given name. + seen_names[name] = 1 + return name + + unique_suffix = '_%d' % seen_names[name] + seen_names[name] += 1 + base, ext = os.path.splitext(name) + return base + unique_suffix + ext def get_object_filename(input_file): - return in_temp(shared.replace_suffix(uniquename(input_file), '.o')) + objfile = unsuffixed_basename(input_file) + '.o' + return in_temp(uniquename(objfile)) - def compile_source_file(i, input_file): + def compile_source_file(input_file): logger.debug(f'compiling source file: {input_file}') output_file = get_object_filename(input_file) - linker_inputs.append((i, output_file)) - if get_file_suffix(input_file) in ASSEMBLY_ENDINGS: + ext = get_file_suffix(input_file) + if ext in ASSEMBLY_EXTENSIONS: cmd = get_clang_command_asm() - elif get_file_suffix(input_file) in PREPROCESSED_ENDINGS: + elif ext in PREPROCESSED_EXTENSIONS: cmd = get_clang_command_preprocessed() else: cmd = get_clang_command() - if get_file_suffix(input_file) in ['.pcm']: + if ext == '.pcm': cmd = [c for c in cmd if not c.startswith('-fprebuilt-module-path=')] - cmd += [input_file] - if not state.has_dash_c: - cmd += ['-c'] - cmd += ['-o', output_file] - if state.mode == Mode.COMPILE_AND_LINK and '-gsplit-dwarf' in newargs: - # When running in COMPILE_AND_LINK mode we compile to temporary location + cmd += compile_args + ['-c', input_file, '-o', output_file] + if options.requested_debug == '-gsplit-dwarf': + # When running in COMPILE_AND_LINK mode we compile objects to a temporary location # but we want the `.dwo` file to be generated in the current working directory, # like it is under clang. We could avoid this hack if we use the clang driver # to generate the temporary files, but that would also involve using the clang - # driver to perform linking which would be big change. + # driver to perform linking which would be a big change. cmd += ['-Xclang', '-split-dwarf-file', '-Xclang', unsuffixed_basename(input_file) + '.dwo'] cmd += ['-Xclang', '-split-dwarf-output', '-Xclang', unsuffixed_basename(input_file) + '.dwo'] shared.check_call(cmd) - if output_file not in ('-', os.devnull) and not shared.SKIP_SUBPROCS: + if not shared.SKIP_SUBPROCS: assert os.path.exists(output_file) + if options.save_temps: + shutil.copyfile(output_file, utils.unsuffixed_basename(input_file) + '.o') + return output_file - # First, generate LLVM bitcode. For each input file, we get base.o with bitcode - for i, input_file in input_files: + # Compile input files individually to temporary locations. + for arg in linker_args: + if not arg.is_file: + continue + input_file = arg.value file_suffix = get_file_suffix(input_file) - if file_suffix in SOURCE_ENDINGS + ASSEMBLY_ENDINGS or (state.has_dash_c and file_suffix == '.bc'): - compile_source_file(i, input_file) - elif file_suffix in DYNAMICLIB_ENDINGS: + if file_suffix in SOURCE_EXTENSIONS | ASSEMBLY_EXTENSIONS or (options.dash_c and file_suffix == '.bc'): + arg.value = compile_source_file(input_file) + elif file_suffix in DYLIB_EXTENSIONS: logger.debug(f'using shared library: {input_file}') - linker_inputs.append((i, input_file)) elif building.is_ar(input_file): logger.debug(f'using static library: {input_file}') - linker_inputs.append((i, input_file)) - elif language_mode: - compile_source_file(i, input_file) + elif options.input_language: + arg.value = compile_source_file(input_file) elif input_file == '-': exit_with_error('-E or -x required when input is from standard input') else: # Default to assuming the inputs are object files and pass them to the linker - logger.debug(f'using object file: {input_file}') - linker_inputs.append((i, input_file)) - - return linker_inputs - - -def version_string(): - # if the emscripten folder is not a git repo, don't run git show - that can - # look up and find the revision in a parent directory that is a git repo - revision_suffix = '' - if os.path.exists(utils.path_from_root('.git')): - git_rev = run_process( - ['git', 'rev-parse', 'HEAD'], - stdout=PIPE, stderr=PIPE, cwd=utils.path_from_root()).stdout.strip() - revision_suffix = ' (%s)' % git_rev - elif os.path.exists(utils.path_from_root('emscripten-revision.txt')): - rev = read_file(utils.path_from_root('emscripten-revision.txt')).strip() - revision_suffix = ' (%s)' % rev - return f'emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) {shared.EMSCRIPTEN_VERSION}{revision_suffix}' - - -def parse_args(newargs): - options = EmccOptions() - settings_changes = [] - user_js_defines = [] - should_exit = False - skip = False - - for i in range(len(newargs)): - if skip: - skip = False - continue - - # Support legacy '--bind' flag, by mapping to `-lembind` which now - # has the same effect - if newargs[i] == '--bind': - newargs[i] = '-lembind' - - arg = newargs[i] - arg_value = None - - def check_flag(value): - # Check for and consume a flag - if arg == value: - newargs[i] = '' - return True - return False - - def check_arg(name): - nonlocal arg_value - if arg.startswith(name) and '=' in arg: - arg_value = arg.split('=', 1)[1] - newargs[i] = '' - return True - if arg == name: - if len(newargs) <= i + 1: - exit_with_error("option '%s' requires an argument" % arg) - arg_value = newargs[i + 1] - newargs[i] = '' - newargs[i + 1] = '' - return True - return False - - def consume_arg(): - nonlocal arg_value - assert arg_value is not None - rtn = arg_value - arg_value = None - return rtn - - def consume_arg_file(): - name = consume_arg() - if not os.path.isfile(name): - exit_with_error("'%s': file not found: '%s'" % (arg, name)) - return name - - if arg.startswith('-O'): - # Let -O default to -O2, which is what gcc does. - requested_level = removeprefix(arg, '-O') or '2' - if requested_level == 's': - requested_level = 2 - settings.SHRINK_LEVEL = 1 - elif requested_level == 'z': - requested_level = 2 - settings.SHRINK_LEVEL = 2 - elif requested_level == 'g': - requested_level = 1 - settings.SHRINK_LEVEL = 0 - settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 1) - elif requested_level == 'fast': - # TODO(https://github.com/emscripten-core/emscripten/issues/21497): - # If we ever map `-ffast-math` to `wasm-opt --fast-math` then - # then we should enable that too here. - requested_level = 3 - settings.SHRINK_LEVEL = 0 - else: - settings.SHRINK_LEVEL = 0 - settings.OPT_LEVEL = validate_arg_level(requested_level, 3, 'invalid optimization level: ' + arg, clamp=True) - elif check_arg('--js-opts'): - logger.warning('--js-opts ignored when using llvm backend') - consume_arg() - elif check_arg('--llvm-opts'): - diagnostics.warning('deprecated', '--llvm-opts is deprecated. All non-emcc args are passed through to clang.') - elif arg.startswith('-flto'): - if '=' in arg: - settings.LTO = arg.split('=')[1] - else: - settings.LTO = 'full' - elif arg == "-fno-lto": - settings.LTO = 0 - elif check_arg('--llvm-lto'): - logger.warning('--llvm-lto ignored when using llvm backend') - consume_arg() - elif check_arg('--closure-args'): - args = consume_arg() - options.closure_args += shlex.split(args) - elif check_arg('--closure'): - options.use_closure_compiler = int(consume_arg()) - elif check_arg('--js-transform'): - options.js_transform = consume_arg() - elif check_arg('--reproduce'): - options.reproduce = consume_arg() - elif check_arg('--pre-js'): - options.pre_js.append(consume_arg_file()) - elif check_arg('--post-js'): - options.post_js.append(consume_arg_file()) - elif check_arg('--extern-pre-js'): - options.extern_pre_js.append(consume_arg_file()) - elif check_arg('--extern-post-js'): - options.extern_post_js.append(consume_arg_file()) - elif check_arg('--compiler-wrapper'): - config.COMPILER_WRAPPER = consume_arg() - elif check_flag('--post-link'): - options.post_link = True - elif check_arg('--oformat'): - formats = [f.lower() for f in OFormat.__members__] - fmt = consume_arg() - if fmt not in formats: - exit_with_error('invalid output format: `%s` (must be one of %s)' % (fmt, formats)) - options.oformat = getattr(OFormat, fmt.upper()) - elif check_arg('--minify'): - arg = consume_arg() - if arg != '0': - exit_with_error('0 is the only supported option for --minify; 1 has been deprecated') - options.no_minify = True - elif arg.startswith('-g'): - options.requested_debug = arg - requested_level = removeprefix(arg, '-g') or '3' - if is_int(requested_level): - # the -gX value is the debug level (-g1, -g2, etc.) - settings.DEBUG_LEVEL = validate_arg_level(requested_level, 4, 'invalid debug level: ' + arg) - # if we don't need to preserve LLVM debug info, do not keep this flag - # for clang - if settings.DEBUG_LEVEL < 3: - newargs[i] = '-g0' - else: - # for 3+, report -g3 to clang as -g4 etc. are not accepted - newargs[i] = '-g3' - if settings.DEBUG_LEVEL == 3: - settings.GENERATE_DWARF = 1 - if settings.DEBUG_LEVEL == 4: - settings.GENERATE_SOURCE_MAP = 1 - diagnostics.warning('deprecated', 'please replace -g4 with -gsource-map') - else: - if requested_level.startswith('force_dwarf'): - exit_with_error('gforce_dwarf was a temporary option and is no longer necessary (use -g)') - elif requested_level.startswith('separate-dwarf'): - # emit full DWARF but also emit it in a file on the side - newargs[i] = '-g' - # if a file is provided, use that; otherwise use the default location - # (note that we do not know the default location until all args have - # been parsed, so just note True for now). - if requested_level != 'separate-dwarf': - if not requested_level.startswith('separate-dwarf=') or requested_level.count('=') != 1: - exit_with_error('invalid -gseparate-dwarf=FILENAME notation') - settings.SEPARATE_DWARF = requested_level.split('=')[1] - else: - settings.SEPARATE_DWARF = True - settings.GENERATE_DWARF = 1 - elif requested_level == 'source-map': - settings.GENERATE_SOURCE_MAP = 1 - newargs[i] = '-g' - else: - # Other non-integer levels (e.g. -gline-tables-only or -gdwarf-5) are - # usually clang flags that emit DWARF. So we pass them through to - # clang and make the emscripten code treat it like any other DWARF. - settings.GENERATE_DWARF = 1 - # In all cases set the emscripten debug level to 3 so that we do not - # strip during link (during compile, this does not make a difference). - settings.DEBUG_LEVEL = 3 - elif check_flag('-profiling') or check_flag('--profiling'): - settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 2) - elif check_flag('-profiling-funcs') or check_flag('--profiling-funcs'): - settings.EMIT_NAME_SECTION = 1 - elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler': - if newargs[i] == '--memoryprofiler': - options.memory_profiler = True - newargs[i] = '' - settings_changes.append('EMSCRIPTEN_TRACING=1') - settings.JS_LIBRARIES.append((0, 'library_trace.js')) - elif check_flag('--emit-symbol-map'): - options.emit_symbol_map = True - settings.EMIT_SYMBOL_MAP = 1 - elif check_arg('--embed-file'): - options.embed_files.append(consume_arg()) - elif check_arg('--preload-file'): - options.preload_files.append(consume_arg()) - elif check_arg('--exclude-file'): - options.exclude_files.append(consume_arg()) - elif check_flag('--use-preload-cache'): - options.use_preload_cache = True - elif check_flag('--no-heap-copy'): - diagnostics.warning('legacy-settings', 'ignoring legacy flag --no-heap-copy (that is the only mode supported now)') - elif check_flag('--use-preload-plugins'): - options.use_preload_plugins = True - elif check_flag('--ignore-dynamic-linking'): - options.ignore_dynamic_linking = True - elif arg == '-v': - shared.PRINT_SUBPROCS = True - elif arg == '-###': - shared.SKIP_SUBPROCS = True - elif check_arg('--shell-file'): - options.shell_path = consume_arg_file() - elif check_arg('--source-map-base'): - options.source_map_base = consume_arg() - elif check_arg('--embind-emit-tsd'): - options.embind_emit_tsd = consume_arg() - elif check_arg('--emit-tsd'): - diagnostics.warning('experimental', '--emit-tsd is still experimental. Not all definitions are generated.') - options.emit_tsd = consume_arg() - elif check_flag('--no-entry'): - options.no_entry = True - elif check_arg('--js-library'): - settings.JS_LIBRARIES.append((i + 1, os.path.abspath(consume_arg_file()))) - elif check_flag('--remove-duplicates'): - diagnostics.warning('legacy-settings', '--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase') - elif check_flag('--jcache'): - logger.error('jcache is no longer supported') - elif check_arg('--cache'): - config.CACHE = os.path.abspath(consume_arg()) - cache.setup() - # Ensure child processes share the same cache (e.g. when using emcc to compiler system - # 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 - 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 - elif check_flag('--check'): - print(version_string(), file=sys.stderr) - shared.check_sanity(force=True) - should_exit = True - elif check_flag('--show-ports'): - ports.show_ports() - should_exit = True - elif check_arg('--memory-init-file'): - exit_with_error('--memory-init-file is no longer supported') - elif check_flag('--proxy-to-worker'): - settings_changes.append('PROXY_TO_WORKER=1') - elif check_arg('--valid-abspath'): - options.valid_abspaths.append(consume_arg()) - elif check_flag('--separate-asm'): - exit_with_error('cannot --separate-asm with the wasm backend, since not emitting asm.js') - elif arg.startswith(('-I', '-L')): - path_name = arg[2:] - if os.path.isabs(path_name) and not is_valid_abspath(options, path_name): - # Of course an absolute path to a non-system-specific library or header - # is fine, and you can ignore this warning. The danger are system headers - # that are e.g. x86 specific and non-portable. The emscripten bundled - # headers are modified to be portable, local system ones are generally not. - diagnostics.warning( - 'absolute-paths', f'-I or -L of an absolute path "{arg}" ' - 'encountered. If this is to a local system header/library, it may ' - 'cause problems (local system files make sense for compiling natively ' - 'on your system, but not necessarily to JavaScript).') - elif check_flag('--emrun'): - options.emrun = True - elif check_flag('--cpuprofiler'): - options.cpu_profiler = True - elif check_flag('--threadprofiler'): - settings_changes.append('PTHREADS_PROFILING=1') - elif arg == '-fno-exceptions': - settings.DISABLE_EXCEPTION_CATCHING = 1 - settings.DISABLE_EXCEPTION_THROWING = 1 - settings.WASM_EXCEPTIONS = 0 - elif arg == '-mbulk-memory': - settings.BULK_MEMORY = 1 - elif arg == '-mno-bulk-memory': - settings.BULK_MEMORY = 0 - elif arg == '-fexceptions': - # TODO Currently -fexceptions only means Emscripten EH. Switch to wasm - # exception handling by default when -fexceptions is given when wasm - # exception handling becomes stable. - settings.DISABLE_EXCEPTION_THROWING = 0 - settings.DISABLE_EXCEPTION_CATCHING = 0 - elif arg == '-fwasm-exceptions': - settings.WASM_EXCEPTIONS = 1 - elif arg == '-fignore-exceptions': - settings.DISABLE_EXCEPTION_CATCHING = 1 - elif check_arg('--default-obj-ext'): - exit_with_error('--default-obj-ext is no longer supported by emcc') - elif arg.startswith('-fsanitize=cfi'): - exit_with_error('emscripten does not currently support -fsanitize=cfi') - elif check_arg('--output_eol'): - style = consume_arg() - if style.lower() == 'windows': - options.output_eol = '\r\n' - elif style.lower() == 'linux': - options.output_eol = '\n' - else: - exit_with_error(f'Invalid value "{style}" to --output_eol!') - # Record PTHREADS setting because it controls whether --shared-memory is passed to lld - elif arg == '-pthread': - settings.PTHREADS = 1 - # Also set the legacy setting name, in case use JS code depends on it. - settings.USE_PTHREADS = 1 - elif arg == '-no-pthread': - settings.PTHREADS = 0 - # Also set the legacy setting name, in case use JS code depends on it. - settings.USE_PTHREADS = 0 - elif arg == '-pthreads': - exit_with_error('unrecognized command-line option `-pthreads`; did you mean `-pthread`?') - elif arg in ('-fno-diagnostics-color', '-fdiagnostics-color=never'): - colored_logger.disable() - diagnostics.color_enabled = False - elif arg == '-fno-rtti': - settings.USE_RTTI = 0 - elif arg == '-frtti': - settings.USE_RTTI = 1 - elif arg.startswith('-jsD'): - key = removeprefix(arg, '-jsD') - if '=' in key: - key, value = key.split('=') - else: - value = '1' - if key in settings.keys(): - exit_with_error(f'{arg}: cannot change built-in settings values with a -jsD directive. Pass -s{key}={value} instead!') - user_js_defines += [(key, value)] - newargs[i] = '' - elif check_flag('-shared'): - options.shared = True - elif check_flag('-r'): - options.relocatable = True - elif check_arg('-o'): - options.output_file = consume_arg() - elif arg.startswith('-o'): - options.output_file = removeprefix(arg, '-o') - newargs[i] = '' - elif check_arg('-target') or check_arg('--target'): - options.target = consume_arg() - 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()) - elif arg == '-mllvm': - # Ignore the next argument rather than trying to parse it. This is needed - # because llvm args could, for example, start with `-o` and we don't want - # to confuse that with a normal `-o` flag. - skip = True - - if should_exit: - sys.exit(0) - - newargs = [a for a in newargs if a] - return options, settings_changes, user_js_defines, newargs - - -def is_valid_abspath(options, path_name): - # Any path that is underneath the emscripten repository root must be ok. - if utils.normalize_path(path_name).startswith(utils.normalize_path(utils.path_from_root())): - return True - - def in_directory(root, child): - # make both path absolute - root = os.path.realpath(root) - child = os.path.realpath(child) - - # return true, if the common prefix of both is equal to directory - # e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b - return os.path.commonprefix([root, child]) == root - - for valid_abspath in options.valid_abspaths: - if in_directory(valid_abspath, path_name): - return True - return False - - -def parse_symbol_list_file(contents): - """Parse contents of one-symbol-per-line response file. This format can by used - with, for example, -sEXPORTED_FUNCTIONS=@filename and avoids the need for any - kind of quoting or escaping. - """ - values = contents.splitlines() - return [v.strip() for v in values if not v.startswith('#')] - - -def parse_value(text, expected_type): - # Note that using response files can introduce whitespace, if the file - # has a newline at the end. For that reason, we rstrip() in relevant - # places here. - def parse_string_value(text): - first = text[0] - if first == "'" or first == '"': - text = text.rstrip() - if text[-1] != text[0] or len(text) < 2: - raise ValueError(f'unclosed quoted string. expected final character to be "{text[0]}" and length to be greater than 1 in "{text[0]}"') - return text[1:-1] - return text - - def parse_string_list_members(text): - sep = ',' - values = text.split(sep) - result = [] - index = 0 - while True: - current = values[index].lstrip() # Cannot safely rstrip for cases like: "HERE-> ," - if not len(current): - raise ValueError('empty value in string list') - first = current[0] - if not (first == "'" or first == '"'): - result.append(current.rstrip()) - else: - start = index - while True: # Continue until closing quote found - if index >= len(values): - raise ValueError(f"unclosed quoted string. expected final character to be '{first}' in '{values[start]}'") - new = values[index].rstrip() - if new and new[-1] == first: - if start == index: - result.append(current.rstrip()[1:-1]) - else: - result.append((current + sep + new)[1:-1]) - break - else: - current += sep + values[index] - index += 1 - - index += 1 - if index >= len(values): - break - return result - - def parse_string_list(text): - text = text.rstrip() - if text and text[0] == '[': - if text[-1] != ']': - raise ValueError('unterminated string list. expected final character to be "]"') - text = text[1:-1] - if text.strip() == "": - return [] - return parse_string_list_members(text) - - if expected_type == list or (text and text[0] == '['): - # if json parsing fails, we fall back to our own parser, which can handle a few - # simpler syntaxes - try: - parsed = json.loads(text) - except ValueError: - return parse_string_list(text) - - # if we succeeded in parsing as json, check some properties of it before returning - if type(parsed) not in (str, list): - raise ValueError(f'settings must be strings or lists (not ${type(parsed)})') - if type(parsed) is list: - for elem in parsed: - if type(elem) is not str: - raise ValueError(f'list members in settings must be strings (not ${type(elem)})') - - return parsed - - if expected_type == float: - try: - return float(text) - except ValueError: pass - try: - if text.startswith('0x'): - base = 16 - else: - base = 10 - return int(text, base) - except ValueError: - return parse_string_value(text) - - -def validate_arg_level(level_string, max_level, err_msg, clamp=False): - try: - level = int(level_string) - except ValueError: - exit_with_error(err_msg) - if clamp: - if level > max_level: - logger.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead") - level = max_level - if not 0 <= level <= max_level: - exit_with_error(err_msg) - return level - - -def is_int(s): - try: - int(s) - return True - except ValueError: - return False - - -@ToolchainProfiler.profile() -def main(args): - start_time = time.time() - ret = run(args) - logger.debug('total time: %.2f seconds', (time.time() - start_time)) - return ret + return [f.value for f in linker_args] if __name__ == '__main__': diff --git a/emcmake b/emcmake deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emcmake +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emcmake.bat b/emcmake.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emcmake.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emcmake.py b/emcmake.py index a9dc2294b91c2..b10d24599eb99 100755 --- a/emcmake.py +++ b/emcmake.py @@ -5,19 +5,18 @@ # found in the LICENSE file. import os +import shlex import shutil import sys -from tools import shared -from tools import config -from tools import utils -from subprocess import CalledProcessError + +from tools import config, shared, utils # # Main run() function # def run(): - if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): + if len(sys.argv) < 2 or sys.argv[1] in {'--version', '--help'}: print('''\ emcmake is a helper for cmake, setting various environment variables so that emcc etc. are used. Typical usage: @@ -36,14 +35,15 @@ def has_substr(args, substr): args.append('-DCMAKE_TOOLCHAIN_FILE=' + utils.path_from_root('cmake/Modules/Platform/Emscripten.cmake')) if not has_substr(args, '-DCMAKE_CROSSCOMPILING_EMULATOR'): - node_js = [config.NODE_JS[0]] - # In order to allow cmake to run code built with pthreads we need to pass - # some extra flags to node. - node_js += shared.node_pthread_flags(config.NODE_JS) - node_js = ';'.join(node_js) + node_js = config.NODE_JS[0] # See https://github.com/emscripten-core/emscripten/issues/15522 args.append(f'-DCMAKE_CROSSCOMPILING_EMULATOR={node_js}') + # Print a better error if we have no CMake executable on the PATH + if not os.path.dirname(args[0]) and not shutil.which(args[0]): + print(f'emcmake: cmake executable not found on PATH: `{args[0]}`', file=sys.stderr) + return 1 + # On Windows specify MinGW Makefiles or ninja if we have them and no other # toolchain was specified, to keep CMake from pulling in a native Visual # Studio, or Unix Makefiles. @@ -56,12 +56,8 @@ def has_substr(args, substr): print('emcmake: no compatible cmake generator found; Please install ninja or mingw32-make, or specify a generator explicitly using -G', file=sys.stderr) return 1 - print('configure: ' + shared.shlex_join(args), file=sys.stderr) - try: - shared.check_call(args) - return 0 - except CalledProcessError as e: - return e.returncode + print(f'emcmake: {shlex.join(args)} in directory {os.getcwd()}', file=sys.stderr) + shared.exec_process(args) if __name__ == '__main__': diff --git a/emconfigure b/emconfigure deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emconfigure +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emconfigure.bat b/emconfigure.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emconfigure.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emconfigure.py b/emconfigure.py index deba798069bb3..83b35cf132fbf 100755 --- a/emconfigure.py +++ b/emconfigure.py @@ -16,17 +16,18 @@ tests will work properly. """ +import os +import shlex import sys -from tools import building -from tools import shared -from subprocess import CalledProcessError + +from tools import building, shared # # Main run() function # def run(): - if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): + if len(sys.argv) < 2 or sys.argv[1] in {'--version', '--help'}: print('''\ emconfigure is a helper for configure, setting various environment variables so that emcc etc. are used. Typical usage: @@ -39,20 +40,14 @@ def run(): args = sys.argv[1:] if 'cmake' in args: - print('error: use `emcmake` rather then `emconfigure` for cmake projects', file=sys.stderr) + print('error: use `emcmake` rather than `emconfigure` for cmake projects', file=sys.stderr) return 1 env = building.get_building_env() - # When we configure via a ./configure script, don't do config-time - # compilation with emcc, but instead do builds natively with Clang. This - # is a heuristic emulation that may or may not work. env['EMMAKEN_JUST_CONFIGURE'] = '1' - print('configure: ' + shared.shlex_join(args), file=sys.stderr) - try: - shared.check_call(args, env=env) - return 0 - except CalledProcessError as e: - return e.returncode + print(f'emconfigure: {shlex.join(args)} in directory {os.getcwd()}', file=sys.stderr) + os.environ.update(env) + shared.exec_process(args) if __name__ == '__main__': diff --git a/emdump b/emdump deleted file mode 100755 index 82f0e77a5c669..0000000000000 --- a/emdump +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$(dirname $0)/tools/emdump.py" "$@" diff --git a/emdump.bat b/emdump.bat deleted file mode 100644 index 0f19bc7842dc2..0000000000000 --- a/emdump.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (tools\emdump.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to tools\emdump.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emdump.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%tools\emdump.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emdump.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%tools\emdump.py" %* diff --git a/emdwp b/emdwp deleted file mode 100755 index 2f93cfaded2e4..0000000000000 --- a/emdwp +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$(dirname $0)/tools/emdwp.py" "$@" diff --git a/emdwp.bat b/emdwp.bat deleted file mode 100644 index 5e4d8658ab9ae..0000000000000 --- a/emdwp.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (tools\emdwp.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to tools\emdwp.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emdwp.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%tools\emdwp.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emdwp.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%tools\emdwp.py" %* diff --git a/emmake b/emmake deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emmake +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emmake.bat b/emmake.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emmake.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emmake.py b/emmake.py index 426e5cc4cd63e..776b7e87e9da0 100755 --- a/emmake.py +++ b/emmake.py @@ -21,19 +21,19 @@ generate JavaScript. """ +import os +import shlex import shutil import sys -from tools import building -from tools import shared -from tools import utils -from subprocess import CalledProcessError + +from tools import building, utils # # Main run() function # def run(): - if len(sys.argv) < 2 or sys.argv[1] in ('--version', '--help'): + if len(sys.argv) < 2 or sys.argv[1] in {'--version', '--help'}: print('''\ emmake is a helper for make, setting various environment variables so that emcc etc. are used. Typical usage: @@ -54,14 +54,14 @@ def run(): args[0] = mingw32_make # On Windows, run the execution through shell to get PATH expansion and - # executable extension lookup, e.g. 'sdl2-config' will match with - # 'sdl2-config.bat' in PATH. - print('make: ' + ' '.join(args), file=sys.stderr) - try: - shared.check_call(args, shell=utils.WINDOWS, env=env) - return 0 - except CalledProcessError as e: - return e.returncode + # executable extension lookup, e.g. 'make' will match with + # 'make.bat' in PATH. + print(f'emmake: "{shlex.join(args)}" in "{os.getcwd()}"', file=sys.stderr) + if utils.WINDOWS: + return utils.run_process(args, check=False, shell=True, env=env).returncode + else: + os.environ.update(env) + utils.exec(args) if __name__ == '__main__': diff --git a/emnm b/emnm deleted file mode 100755 index 19fba1368d2b4..0000000000000 --- a/emnm +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$(dirname $0)/tools/emnm.py" "$@" diff --git a/emnm.bat b/emnm.bat deleted file mode 100644 index 80c49452b38f9..0000000000000 --- a/emnm.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (tools\emnm.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to tools\emnm.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emnm.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%tools\emnm.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emnm.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%tools\emnm.py" %* diff --git a/emprofile b/emprofile deleted file mode 100755 index 7a35f7a83c39d..0000000000000 --- a/emprofile +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$(dirname $0)/tools/emprofile.py" "$@" diff --git a/emprofile.bat b/emprofile.bat deleted file mode 100644 index 5f5be5c32710c..0000000000000 --- a/emprofile.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (tools\emprofile.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to tools\emprofile.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emprofile.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%tools\emprofile.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%tools\emprofile.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%tools\emprofile.py" %* diff --git a/emranlib b/emranlib deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emranlib +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emranlib.bat b/emranlib.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emranlib.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emranlib.py b/emranlib.py index 811ef061cb6dc..3d1659d05de8e 100755 --- a/emranlib.py +++ b/emranlib.py @@ -11,6 +11,7 @@ """ import sys + from tools import shared shared.exec_process([shared.LLVM_RANLIB] + sys.argv[1:]) diff --git a/emrun b/emrun deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emrun +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emrun.bat b/emrun.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emrun.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emscan-deps.py b/emscan-deps.py new file mode 100755 index 0000000000000..b9d79863313be --- /dev/null +++ b/emscan-deps.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Copyright 2025 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. + +"""emscan-deps - clang-scan-deps helper script + +This script acts as a frontend replacement for clang-scan-deps. +""" + +import sys + +from tools import cmdline, compile, shared + +argv = sys.argv[1:] + +# Parse and discard any emcc-specific flags (e.g. -sXXX). +newargs = cmdline.parse_arguments(argv) + +# Add any clang flags that emcc would add. +newargs += compile.get_cflags(tuple(argv)) + +shared.exec_process([shared.CLANG_SCAN_DEPS] + newargs) diff --git a/emscons b/emscons deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emscons +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emscons.bat b/emscons.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emscons.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emscons.py b/emscons.py index ee5c41ce15ae5..643f4a0863ab1 100755 --- a/emscons.py +++ b/emscons.py @@ -11,11 +11,16 @@ import os import subprocess import sys -from tools import utils + +from tools import building, utils tool_path = utils.path_from_root('tools/scons/site_scons/site_tools/emscripten') +building_env = building.get_building_env() env = os.environ.copy() env['EMSCRIPTEN_TOOL_PATH'] = tool_path +env['EMSCRIPTEN_ROOT'] = utils.path_from_root() +env['EMSCONS_PKG_CONFIG_LIBDIR'] = building_env['PKG_CONFIG_LIBDIR'] +env['EMSCONS_PKG_CONFIG_PATH'] = building_env['PKG_CONFIG_PATH'] sys.exit(subprocess.call(sys.argv[1:], env=env)) diff --git a/emscripten-version.txt b/emscripten-version.txt index b9148f640cff4..aff2882b0fa03 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1 +1 @@ -3.1.56-git +5.0.6-git diff --git a/emsize b/emsize deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emsize +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emsize.bat b/emsize.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emsize.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emsize.py b/emsize.py index 8bf92a3843c01..935c237c9d224 100755 --- a/emsize.py +++ b/emsize.py @@ -28,7 +28,7 @@ from tools import shared -LLVM_SIZE = os.path.expanduser(shared.build_llvm_tool_path(shared.exe_suffix('llvm-size'))) +LLVM_SIZE = shared.llvm_tool_path('llvm-size') def error(text): @@ -46,7 +46,7 @@ def parse_args(argv): def print_sizes(js_file): if not os.path.isfile(js_file): - return error('Input JS file %s not foune' % js_file) + return error('Input JS file %s not found' % js_file) if not js_file.endswith('.js'): return error('Input file %s does not have a JS extension' % js_file) diff --git a/emstrip b/emstrip deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emstrip +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emstrip.bat b/emstrip.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emstrip.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emstrip.py b/emstrip.py index f054fe7e9f1df..3744e843d5f11 100755 --- a/emstrip.py +++ b/emstrip.py @@ -5,9 +5,32 @@ # found in the LICENSE file. """Wrapper script around `llvm-strip`. + + It also supports taking a JS file as an argument and running 'llvm-strip' on + the corresponding Wasm file. This is convenient for some build systems that + expect to strip the output of a compile. """ +import os import sys + from tools import shared -shared.exec_process([shared.LLVM_STRIP] + sys.argv[1:]) + +def run(): + llvm_strip = shared.llvm_tool_path('llvm-strip') + new_args = [] + for arg in sys.argv[1:]: + base, ext = os.path.splitext(arg) + if ext == '.js' and os.path.isfile(arg): + wasm_file = base + '.wasm' + if os.path.isfile(wasm_file): + new_args.append(wasm_file) + continue + new_args.append(arg) + + shared.exec_process([llvm_strip] + new_args) + + +if __name__ == '__main__': + run() diff --git a/emsymbolizer b/emsymbolizer deleted file mode 100755 index eef0f00c6730f..0000000000000 --- a/emsymbolizer +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright 2020 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. -# -# Entry point for running python scripts on UNIX systems. -# -# Automatically generated by `create_entry_points.py`; DO NOT EDIT. -# -# To make modifications to this file, edit `tools/run_python.sh` and then run -# `tools/maint/create_entry_points.py` - -# $PYTHON -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -# of cpython used in cross compilation via setup.py. -unset _PYTHON_SYSCONFIGDATA_NAME - -if [ -z "$PYTHON" ]; then - PYTHON=$EMSDK_PYTHON -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python3 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - PYTHON=$(command -v python 2> /dev/null) -fi - -if [ -z "$PYTHON" ]; then - echo 'unable to find python in $PATH' - exit 1 -fi - -exec "$PYTHON" -E "$0.py" "$@" diff --git a/emsymbolizer.bat b/emsymbolizer.bat deleted file mode 100644 index 83edd646f7918..0000000000000 --- a/emsymbolizer.bat +++ /dev/null @@ -1,86 +0,0 @@ -:: Entry point for running python scripts on windows systems. -:: -:: Automatically generated by `create_entry_points.py`; DO NOT EDIT. -:: -:: To make modifications to this file, edit `tools/run_python.bat` and then run -:: `tools/maint/create_entry_points.py` - -:: N.b. In Windows .bat scripts, the ':' character cannot appear inside any if () blocks, -:: or there will be a parsing error. - -:: All env. vars specified in this file are to be local only to this script. -@setlocal -:: -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal -:: of cpython used in cross compilation via setup.py. -@set _PYTHON_SYSCONFIGDATA_NAME= -@set EM_PY=%EMSDK_PYTHON% -@if "%EM_PY%"=="" ( - set EM_PY=python -) - -:: Work around Windows bug https://github.com/microsoft/terminal/issues/15212 : If this -:: script is invoked via enclosing the invocation in quotes via PATH lookup, then %~f0 and -:: %~dp0 expansions will not work. -:: So first try if %~dp0 might work, and if not, manually look up this script from PATH. -@if exist "%~f0" ( - set MYDIR=%~dp0 - goto FOUND_MYDIR -) -@for %%I in (%~n0.bat) do ( - @if exist %%~$PATH:I ( - set MYDIR=%%~dp$PATH:I - ) else ( - echo Fatal Error! Due to a Windows bug, we are unable to locate the path to %~n0.bat. - echo To help this issue, try removing unnecessary quotes in the invocation of emcc, - echo or add Emscripten directory to PATH. - echo See github.com/microsoft/terminal/issues/15212 and - echo github.com/emscripten-core/emscripten/issues/19207 for more details. - ) -) -:FOUND_MYDIR - -:: Python Windows bug https://bugs.python.org/issue34780: If this script was invoked via a -:: shared stdin handle from the parent process, and that parent process stdin handle is in -:: a certain state, running python.exe might hang here. To work around this, if -:: EM_WORKAROUND_PYTHON_BUG_34780 is defined, invoke python with '< NUL' stdin to avoid -:: sharing the parent's stdin handle to it, avoiding the hang. - -:: On Windows 7, the compiler batch scripts are observed to exit with a non-zero errorlevel, -:: even when the python executable above did succeed and quit with errorlevel 0 above. -:: On Windows 8 and newer, this issue has not been observed. It is possible that this -:: issue is related to the above python bug, but this has not been conclusively confirmed, -:: so using a separate env. var EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG to enable the known -:: workaround this issue, which is to explicitly quit the calling process with the previous -:: errorlevel from the above command. - -:: Also must use goto to jump to the command dispatch, since we cannot invoke emcc from -:: inside a if() block, because if a cmdline param would contain a char '(' or ')', that -:: would throw off the parsing of the cmdline arg. -@if "%EM_WORKAROUND_PYTHON_BUG_34780%"=="" ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto NORMAL - ) else ( - goto NORMAL_EXIT - ) -) else ( - @if "%EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG%"=="" ( - goto MUTE_STDIN - ) else ( - goto MUTE_STDIN_EXIT - ) -) - -:NORMAL_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* -@exit %ERRORLEVEL% - -:MUTE_STDIN -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit /b %ERRORLEVEL% - -:MUTE_STDIN_EXIT -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* < NUL -@exit %ERRORLEVEL% - -:NORMAL -@"%EM_PY%" -E "%MYDIR%%~n0.py" %* diff --git a/emsymbolizer.py b/emsymbolizer.py deleted file mode 100755 index 1a9cf27f25d56..0000000000000 --- a/emsymbolizer.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env python3 - -# This is a utility for looking up the symbol names and/or file+line numbers -# of code addresses. There are several possible sources of this information, -# with varying granularity (listed here in approximate preference order). - -# If the wasm has DWARF info, llvm-symbolizer can show the symbol, file, and -# line/column number, potentially including inlining. -# If the wasm has separate DWARF info, do the above with the side file -# If there is a source map, we can parse it to get file and line number. -# If there is an emscripten symbol map, we can use that to get the symbol name -# If there is a name section or symbol table, llvm-symbolizer can show the -# symbol name. -# Separate DWARF and emscripten symbol maps are not supported yet. - -import argparse -import json -import os -import re -import subprocess -import sys -from tools import shared -from tools import webassembly - -LLVM_SYMBOLIZER = os.path.expanduser( - shared.build_llvm_tool_path(shared.exe_suffix('llvm-symbolizer'))) - - -class Error(BaseException): - pass - - -# Class to treat location info in a uniform way across information sources. -class LocationInfo(object): - def __init__(self, source=None, line=0, column=0, func=None): - self.source = source - self.line = line - self.column = column - self.func = func - - def print(self): - source = self.source if self.source else '??' - func = self.func if self.func else '??' - print(f'{func}\n{source}:{self.line}:{self.column}') - - -def get_codesec_offset(module): - sec = module.get_section(webassembly.SecType.CODE) - if not sec: - raise Error(f'No code section found in {module.filename}') - return sec.offset - - -def has_debug_line_section(module): - return module.get_custom_section('.debug_line') is not None - - -def has_name_section(module): - return module.get_custom_section('name') is not None - - -def has_linking_section(module): - return module.get_custom_section('linking') is not None - - -def symbolize_address_symbolizer(module, address, is_dwarf): - if is_dwarf: - vma_adjust = get_codesec_offset(module) - else: - vma_adjust = 0 - cmd = [LLVM_SYMBOLIZER, '-e', module.filename, f'--adjust-vma={vma_adjust}', - str(address)] - out = shared.run_process(cmd, stdout=subprocess.PIPE).stdout.strip() - out_lines = out.splitlines() - - # Source location regex, e.g., /abc/def.c:3:5 - SOURCE_LOC_RE = re.compile(r'(.+):(\d+):(\d+)$') - # llvm-symbolizer prints two lines per location. The first line contains a - # function name, and the second contains a source location like - # '/abc/def.c:3:5'. If the function or source info is not available, it will - # be printed as '??', in which case we store None. If the line and column info - # is not available, they will be printed as 0, which we store as is. - for i in range(0, len(out_lines), 2): - func, loc_str = out_lines[i], out_lines[i + 1] - m = SOURCE_LOC_RE.match(loc_str) - source, line, column = m.group(1), m.group(2), m.group(3) - if func == '??': - func = None - if source == '??': - source = None - LocationInfo(source, line, column, func).print() - - -def get_sourceMappingURL_section(module): - for sec in module.sections(): - if sec.name == "sourceMappingURL": - return sec - return None - - -class WasmSourceMap(object): - class Location(object): - def __init__(self, source=None, line=0, column=0, func=None): - self.source = source - self.line = line - self.column = column - self.func = func - - def __init__(self): - self.version = None - self.sources = [] - self.mappings = {} - self.offsets = [] - - def parse(self, filename): - with open(filename) as f: - source_map_json = json.loads(f.read()) - if shared.DEBUG: - print(source_map_json) - - self.version = source_map_json['version'] - self.sources = source_map_json['sources'] - - vlq_map = {} - chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - for i, c in enumerate(chars): - vlq_map[c] = i - - def decodeVLQ(string): - result = [] - shift = 0 - value = 0 - for c in string: - try: - integer = vlq_map[c] - except ValueError: - raise Error(f'Invalid character ({c}) in VLQ') - value += (integer & 31) << shift - if integer & 32: - shift += 5 - else: - negate = value & 1 - value >>= 1 - result.append(-value if negate else value) - value = shift = 0 - return result - - offset = 0 - src = 0 - line = 1 - col = 1 - for segment in source_map_json['mappings'].split(','): - data = decodeVLQ(segment) - info = [] - - offset += data[0] - if len(data) >= 2: - src += data[1] - info.append(src) - if len(data) >= 3: - line += data[2] - info.append(line) - if len(data) >= 4: - col += data[3] - info.append(col) - # TODO: see if we need the name, which is the next field (data[4]) - - self.mappings[offset] = WasmSourceMap.Location(*info) - self.offsets.append(offset) - self.offsets.sort() - - def find_offset(self, offset): - # Find the largest mapped offset <= the search offset - lo = 0 - hi = len(self.offsets) - - while lo < hi: - mid = (lo + hi) // 2 - if self.offsets[mid] > offset: - hi = mid - else: - lo = mid + 1 - return self.offsets[lo - 1] - - def lookup(self, offset): - nearest = self.find_offset(offset) - assert nearest in self.mappings, 'Sourcemap has an offset with no mapping' - info = self.mappings[nearest] - return LocationInfo( - self.sources[info.source] if info.source is not None else None, - info.line, - info.column - ) - - -def symbolize_address_sourcemap(module, address, force_file): - URL = force_file - if not URL: - # If a sourcemap file is not forced, read it from the wasm module - section = get_sourceMappingURL_section(module) - assert section - module.seek(section.offset) - assert module.read_string() == 'sourceMappingURL' - # TODO: support stripping/replacing a prefix from the URL - URL = module.read_string() - - if shared.DEBUG: - print(f'Source Mapping URL: {URL}') - sm = WasmSourceMap() - sm.parse(URL) - if shared.DEBUG: - csoff = get_codesec_offset(module) - print(sm.mappings) - # Print with section offsets to easily compare against dwarf - for k, v in sm.mappings.items(): - print(f'{k-csoff:x}: {v}') - sm.lookup(address).print() - - -def main(args): - with webassembly.Module(args.wasm_file) as module: - base = 16 if args.address.lower().startswith('0x') else 10 - address = int(args.address, base) - - if args.addrtype == 'code': - address += get_codesec_offset(module) - - if ((has_debug_line_section(module) and not args.source) or - 'dwarf' in args.source): - symbolize_address_symbolizer(module, address, is_dwarf=True) - elif ((get_sourceMappingURL_section(module) and not args.source) or - 'sourcemap' in args.source): - symbolize_address_sourcemap(module, address, args.file) - elif ((has_name_section(module) and not args.source) or - 'names' in args.source): - symbolize_address_symbolizer(module, address, is_dwarf=False) - elif ((has_linking_section(module) and not args.source) or - 'symtab' in args.source): - symbolize_address_symbolizer(module, address, is_dwarf=False) - else: - raise Error('No .debug_line or sourceMappingURL section found in ' - f'{module.filename}.' - " I don't know how to symbolize this file yet") - - -def get_args(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--source', choices=['dwarf', 'sourcemap', - 'names', 'symtab'], - help='Force debug info source type', default=()) - parser.add_argument('-f', '--file', action='store', - help='Force debug info source file') - parser.add_argument('-t', '--addrtype', choices=['code', 'file'], - default='file', - help='Address type (code section or file offset)') - parser.add_argument('-v', '--verbose', action='store_true', - help='Print verbose info for debugging this script') - parser.add_argument('wasm_file', help='Wasm file') - parser.add_argument('address', help='Address to lookup') - args = parser.parse_args() - if args.verbose: - shared.PRINT_SUBPROCS = 1 - shared.DEBUG = True - return args - - -if __name__ == '__main__': - try: - rv = main(get_args()) - except (Error, webassembly.InvalidWasmError, OSError) as e: - print(f'{sys.argv[0]}: {str(e)}', file=sys.stderr) - rv = 1 - sys.exit(rv) diff --git a/eng/emscripten-revision.txt b/eng/emscripten-revision.txt index 51ae73ee24806..1d113d4b2a517 100644 --- a/eng/emscripten-revision.txt +++ b/eng/emscripten-revision.txt @@ -1 +1 @@ -57b21b8fdcbe3ebb523178b79465254668eab408 +6ea9c28c38cdd40c1032fa04400c9d16230ee180 diff --git a/eng/emscripten-version.txt b/eng/emscripten-version.txt new file mode 100644 index 0000000000000..f0837fb82cf9a --- /dev/null +++ b/eng/emscripten-version.txt @@ -0,0 +1 @@ +"5.0.6" diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000000..84f59b4d0173b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,97 @@ +import globals from 'globals'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; +import { loadDefaultSettings } from './src/utility.mjs'; + +const compat = new FlatCompat({ + baseDirectory: import.meta.dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + + +// Emscripten settings are made available to the compiler as global +// variables. Make sure eslint knows about them. +const settings = loadDefaultSettings(); +const settingsGlobals = {}; +for (const name of Object.keys(settings)) { + settingsGlobals[name] = 'writable'; +} + +export default [{ + ignores: [ + '**/out/', + '**/site/', + '**/cache/', + '**/third_party/', + '**/test/', + 'src/polyfill/', + 'src/lib/', + 'src/binaryDecode.js', + 'src/build_as_worker.js', + 'src/minimum_runtime_check.js', + 'src/runtime_*.js', + 'src/shell*.js', + 'src/modularize.js', + 'src/preamble*.js', + 'src/postlibrary.js', + 'src/postamble*.js', + 'src/closure-externs/', + 'src/embind/', + 'src/pthread_esm_startup.mjs', + 'src/emrun_postjs.js', + 'src/wasm_worker.js', + 'src/audio_worklet.js', + 'src/wasm2js.js', + 'src/webGLClient.js', + 'src/webGLWorker.js', + 'src/*_shell_read.js', + 'src/threadprofiler.js', + 'src/cpuprofiler.js', + 'src/memoryprofiler.js', + 'src/gl-matrix.js', + 'src/source_map_support.js', + 'src/Fetch.js', + 'src/settings.js', + 'src/settings_internal.js', + 'src/emrun_prejs.js', + 'src/deterministic.js', + 'src/proxyWorker.js', + 'src/proxyClient.js', + 'src/IDBStore.js', + 'tools/experimental', + ], +}, ...compat.extends('prettier'), js.configs.recommended, { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...settingsGlobals, + }, + + ecmaVersion: 'latest', + sourceType: 'module', + }, + + rules: { + 'max-len': 'off', + 'no-multi-spaces': 'off', + 'require-jsdoc': 'off', + 'arrow-body-style': ['error', 'as-needed'], + 'space-infix-ops': 'error', + 'no-prototype-builtins': 'off', + + quotes: ['error', 'single', { + avoidEscape: true, + }], + }, +}, { + files: ['**/*.mjs'], + + rules: { + 'no-unused-vars': ['error', { + argsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + }], + }, +}]; diff --git a/html/README.md b/html/README.md new file mode 100644 index 0000000000000..faefa400c30df --- /dev/null +++ b/html/README.md @@ -0,0 +1,9 @@ +HTML Template Files +=================== + +The files in this directory are html templates used by emscripten when +generating html output. These are only used when the output of the compiler +has the `.html` suffix (or when `-oformat=html` is specified). + +By default the `shell.html` file in this directory is used but an alternative +can be specified using the `--shell-file` flag. diff --git a/src/shell.css b/html/shell.css similarity index 100% rename from src/shell.css rename to html/shell.css diff --git a/html/shell.html b/html/shell.html new file mode 100644 index 0000000000000..f7c65bf38c8f3 --- /dev/null +++ b/html/shell.html @@ -0,0 +1,115 @@ + + + + + + Emscripten-Generated Code + + + + {{{ SHELL_LOGO }}} + +
+
Downloading...
+ + + Resize canvas + Lock/hide mouse pointer     + + + + +
+ +
+ +
+ +
+ + + + {{{ SCRIPT }}} +#if MODULARIZE && !EXPORT_ES6 + +#endif + + diff --git a/html/shell_minimal.html b/html/shell_minimal.html new file mode 100644 index 0000000000000..91f81b8179c2d --- /dev/null +++ b/html/shell_minimal.html @@ -0,0 +1,152 @@ + + + + + + Emscripten-Generated Code + + + +
+
emscripten
+
Downloading...
+
+ +
+
+ +
+
+
+ Resize canvas + Lock/hide mouse pointer +     + +
+ +
+ +
+ + {{{ SCRIPT }}} +#if MODULARIZE && !EXPORT_ES6 + +#endif + + diff --git a/html/shell_minimal_runtime.html b/html/shell_minimal_runtime.html new file mode 100644 index 0000000000000..74d50f07fce82 --- /dev/null +++ b/html/shell_minimal_runtime.html @@ -0,0 +1,26 @@ + + + + + + diff --git a/package-lock.json b/package-lock.json index 27dd44e4c861a..b6a5ddf966d48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,37 +1,26 @@ { "name": "emscripten", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "dependencies": { - "@babel/cli": "^7.23.4", - "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.8", - "acorn": "^8.11.3", - "google-closure-compiler": "20230802.0.0", + "@babel/cli": "^7.28.6", + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "acorn": "^8.15.0", + "google-closure-compiler": "20240317.0.0", "html-minifier-terser": "7.2.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/cli": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.4.tgz", - "integrity": "sha512-j3luA9xGKCXVyCa5R7lJvOMM+Kc2JEnAEIgz2ggtjQ/j5YUVgfsg/WsG95bbsgq7YLHuiCOzMnoSasuY16qiCw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/cli/-/cli-7.28.6.tgz", + "integrity": "sha1-HajrefksvnVUK4U7Ox22g8XqQGo=", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "commander": "^4.0.1", + "@jridgewell/trace-mapping": "^0.3.28", + "commander": "^6.2.0", "convert-source-map": "^2.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.2.0", @@ -47,18 +36,19 @@ }, "optionalDependencies": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" + "chokidar": "^3.6.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha1-fNelnxWzzA3NgDA493knEqfQsVw=", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -67,28 +57,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha1-ANA+jArCTdm+lCxTcJkMvh8X2I0=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha1-UoateF33951lbojOhuZQ0Wyl8yI=", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -103,58 +95,43 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.29.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha1-0Jh2KQERq7sA75Yqe4OlMH+6DVA=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.27.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha1-8x/Ya5FfxNrx86xpdsWb5whO2cU=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha1-MsSj9B8S7RUyF5sQik10bhBcKyU=", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -162,27 +139,18 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", - "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha1-YR/1SC2p7w22KRvNJDA0ALyhcPs=", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -192,21 +160,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.28.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha1-fB3dZLIGXH94A0sltDNGp+Ge2Zc=", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -216,92 +177,66 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.6.8", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha1-zx5EYrYT8rVMQeb/dY1d/KoshdE=", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha1-uUMN8qpOF7woZl6t6uiqHZheZnQ=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.28.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha1-8+B6EL437XpjRhxj5pKVdZRaYVA=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha1-YGMsvW/7cLIoIxhyARFnYqA+LVw=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha1-kxLZ2eVu3DWutulcJdQQa1C56x4=", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -311,32 +246,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha1-xlIhthpkPz5icF5d0rXxFeNfkgA=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha1-bxPqJRtoyFMumF/VMvKHQaivmsg=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha1-RgHVx84usq6lgyjUNyVSP802LOY=", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -346,13 +284,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha1-lKqaHXQjoArq0/IE94g0zn1T/kQ=", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -361,34 +300,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha1-YruRs6u6jH8f7AJS2dvqEbPuelY=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -396,59 +315,65 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha1-VNp5YJerGc5n7Z+ItHuy7Ek2doc=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha1-AQtpOPq3y333SqK7wGqlA7j+X7Q=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha1-+lL1sefbGrBJRFtCHERxMDiXcC8=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha1-TjSf+SItq2mpOgGcwpbN2EQuJ5o=", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.29.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha1-nPvMsCuOIpiSwLBwOAUswahwnEk=", + "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha1-WL1QuaeVHRNJiKGuF3o175pwO6E=", + "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -457,12 +382,14 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha1-+95Xl0cHu/oDdtNNQl/0+mxzJCE=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -471,29 +398,28 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha1-Q/cKbX79UjcO7731WuA9kbKThW0=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha1-vrYjvVc7i28wR70EwyUGrcPlinI=", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -502,96 +428,44 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha1-4TSlR56yupwCcU6MHr8eyQdhJP0=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha1-DoKJzsKLqvBdVP0I2BrjZ2Bl9p8=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha1-eET5KJVG76n+usLeTP41igUL1wM=", + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -599,100 +473,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha1-rpvBkjprpSe3AQTdIZGwzYcshQc=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -701,12 +488,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha1-tx1ZFGZfYBJOEzaW8XzXZpBixQM=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -717,8 +505,9 @@ }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha1-1Jo7PmtS5b5nQAIjF1gCNKakc1c=", + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -731,11 +520,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha1-biBhBnujqwJm2DSp+UgRGW8qupo=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -745,14 +535,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha1-Y+2CmCApjwvxQ9Wkpo+4wG/9dC8=", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -762,13 +552,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha1-vZe0Ijey0byQ10vLSGw5vltNfnc=", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -778,11 +569,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha1-VYqdbiTPcoAt07YqS1Hg1iwPV/k=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -792,11 +584,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha1-4e9WM0SMJOdjRhJcJTTus1lpmpk=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -806,12 +599,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha1-0nSkR4tueC2eqYf9oJvbbSjWa3I=", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -821,13 +615,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha1-EldJHoJZxtElrE2abzn50r89unA=", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -837,18 +631,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha1-j2+3m6NwOXjnAc4ql+NzqufdpLc=", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -858,12 +651,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha1-k2gk/HHCbLXEM0hXdtecjnsCAtI=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -873,11 +667,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "version": "7.28.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha1-uEAnZN+WF5ogcLt7UBoVhs+K16c=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -887,12 +683,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha1-3vMe2E4PtuJcceU8Ek57dqSrjmE=", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -902,11 +699,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha1-8fv2KOzhjhLnsysXWUDmg1j1RtE=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -915,28 +713,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha1-gBS4ps/Q57knYnJEQ78NJADybfE=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha1-THjzVVKsDgaqH248Vz1naV6K9aQ=", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -945,13 +744,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha1-3WeI+YLIt36Gd50dApWR452di+c=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -960,13 +760,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha1-Xkd+t+r68qtVN6BKqvzzfi1/EJE=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -975,14 +775,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha1-ccpp00ce3W2qcRz038NABBXfnCM=", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -991,13 +790,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha1-vCT3CA6f9yG2OnCseyVkyhW2xAo=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1006,12 +806,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha1-TQvzB3IOTc5tfDD8sf1sp3ves6c=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1020,13 +823,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha1-TIwVstxJ4oXREKTPPaxS/S38MDg=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1035,12 +838,43 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha1-uq76TRCh1CBvnc3aUNfVgnu3CyQ=", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha1-UwKKPXfjPFDvMKj85coXBlk25gU=", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha1-N7iLpZTYUkGOmVNvVhL3lfI66vk=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1050,12 +884,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha1-pBRfnYfCKR/i0F+ZS2XbpOPnGW8=", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1065,13 +900,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha1-wCMuDf5mpzTMStDV51/DMhtv3vE=", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1081,14 +916,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha1-5FipWheAfEFZJBBqP/GIo7je6WQ=", + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1098,12 +934,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha1-Y/LPT23BXevBL2lORHFIY9NM0zQ=", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1113,12 +950,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha1-omzVHgnEcYWI/EzOHF0cAVIQLWo=", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1128,11 +966,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha1-JZxDk5coytFwasFzUbfmp76hq+s=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1142,12 +981,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha1-m8YglukKt6iH88qcRp9q3sVnl1c=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1157,12 +996,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha1-ExCwKSdi56SjNd9fWAwzIO59np8=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1172,15 +1011,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha1-/dS8LXJIDbbKQq7VwFHxSNewZ/c=", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1190,12 +1030,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha1-HJMs0nvzh0xDpcrE9D6/lwyYcbU=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1205,12 +1046,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha1-dRB74Ux4OFl4IBpJyGQUoVCiC0w=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1220,13 +1061,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha1-kmzxUL1CH8g2J1PpEbShsc5DVs0=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1236,11 +1077,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "version": "7.27.7", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha1-H9L+u3x059Ic87BfeuvJB5QK9To=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1250,12 +1092,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha1-x2+/7zuGx3Xbf3wQb/9URhC9tBE=", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1265,14 +1108,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha1-T6/vHhMSnXnx11rBgMUqr+/bKBE=", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1282,11 +1125,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha1-B+r9YYgAWR6IBzoK8blA2aQsZCQ=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1296,12 +1140,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha1-3sI3zsG5MzCHbW2pmSxKvULJ0Ys=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1310,12 +1154,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha1-fvAWO9i0phBIGyUJxYzyF/BlKQs=", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha1-QPukh4zL0cVmBaRHmjqJGsAnS7Q=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1325,11 +1186,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha1-Uyq9rN7Ie/7h4O+OL83uVD/jK5A=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1339,12 +1201,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha1-QKK0I/bbe3DwQ60Celi8tEqXV7Y=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1354,11 +1217,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha1-GJhJNdnSKWhDpJHXigFJOffc0oA=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1368,11 +1232,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha1-Gg6zXYuz5u/AbJ/UDrC871SDKLg=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1382,11 +1247,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha1-cOlmu0kuA1Cc836vptzDBR+EQ2k=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1396,11 +1262,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha1-PjFD+EOK74Qt4ogW7OWHgBkM+AY=", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1410,12 +1277,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha1-Y6emwhoOddrpsYYUVBEepcqiKCE=", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1425,12 +1293,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "version": "7.27.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha1-JZSPXDldsV9gkCjjcGZ+2Lrpr5c=", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1440,12 +1309,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha1-kkkSkU5d+f5hXsRy+I/0eIzgTU4=", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1455,89 +1325,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", - "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "version": "7.29.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha1-Whc/IsfY3zYq8cn+MfrNMg3kqGw=", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1547,18 +1408,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha1-zLiKLEnIFyNoYf7ngmCAVzuKkjo=", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1568,56 +1422,43 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha1-Dn5W7O23iu72bOeXKwgvznaiPlc=", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha1-8yPQUAFEAlPurTychYrb4AuQMQo=", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha1-n1seg4xEbnLPPNS5GBUrjGBeN8c=", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -1627,52 +1468,55 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.13", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha1-Y0Khn0Q0dRjJPkOxrGnes8Rlah8=", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "engines": { - "node": ">=6.0.0" + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha1-N1xHbRlylHhRuh4Vro8SMEdEWqE=", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha1-eg7mAfYPmaIMfHxf8MgDiMEYm9Y=", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.11", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha1-shg1y9Nttla4V8KtAuvUE8wTqbo=", + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha1-aRKwDSxjHA0Vzhp6tXzWV/Ko+Lo=", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha1-2xXWeByTHzolGj2sOVAcmKYIL9A=", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1680,14 +1524,16 @@ }, "node_modules/@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha1-Mj1y3SUQPQxPvc6J2t9XSnh7H5s=", + "license": "MIT", "optional": true }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha1-TOecib5Ar+ev6POtuQKh8c6awIo=", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1695,10 +1541,26 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha1-eQxYsZuhcgqEIFtXxhjVrYUklz4=", + "license": "ISC", "optional": true, "dependencies": { "normalize-path": "^3.0.0", @@ -1709,44 +1571,39 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "version": "0.4.17", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha1-GY+XDxyZqFa0ZtEYfojOML0ZnZE=", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "version": "0.14.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha1-asCNLzEq/7cMTGnA+7pMtBfuVYc=", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "version": "0.6.8", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha1-imv9XdVCOTYrPQbOR6xSstlddyE=", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -1754,30 +1611,40 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=", + "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz", - "integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==", + "version": "2.10.24", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha1-bcMgx79ThZ7Cv1XVTbbS5cB43xY=", + "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha1-9uFKl4WNMnJSIAJC1Mz+UixEVSI=", + "license": "MIT", "optional": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha1-2d5gI3DZE0fNndrRIk1P1wHrNIs=", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1785,8 +1652,9 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/braces/-/braces-3.0.3.tgz", + "integrity": "sha1-SQMy9AkZRSJy1VqEgK3AxEE1h4k=", + "license": "MIT", "optional": true, "dependencies": { "fill-range": "^7.1.1" @@ -1796,9 +1664,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha1-9QtlNi70iXTKn1CzaAVm14a4EdI=", "funding": [ { "type": "opencollective", @@ -1813,12 +1681,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -1829,22 +1698,24 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha1-KxRqb9cugLT1XSVfNe1Zo6mkG9U=", + "license": "MIT" }, "node_modules/camel-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha1-lygHKpVPgFIoIlpt7qazhGHhvVo=", + "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "version": "1.0.30001791", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha1-37k9hcQK04DFcSPnLhDzxXV4a1E=", "funding": [ { "type": "opencollective", @@ -1858,18 +1729,30 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha1-qsTit3NKdAhnrrFr8CqtVWoeegE=", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha1-GXxsxmnvKo3F57TZfuTgksPrDVs=", + "license": "MIT", "optional": true, "dependencies": { "anymatch": "~3.1.2", @@ -1883,14 +1766,18 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "node_modules/clean-css": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha1-szBlPNO9a3UAnMJccUyue5M1HM0=", + "license": "MIT", "dependencies": { "source-map": "~0.6.0" }, @@ -1898,69 +1785,87 @@ "node": ">= 10.0" } }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/clone": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/clone-buffer": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/clone-stats": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "license": "MIT" }, "node_modules/cloneable-readable": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha1-EgoAywU7+2OiIucJ+Wg+ouEdjOw=", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=", + "license": "MIT" + }, "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "6.2.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-6.2.1.tgz", + "integrity": "sha1-B5LraC37wyWZm7K4T93duhEKxzw=", + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha1-S1YPZJ/E6RjdCrdc9JYei8iC2Co=", + "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", - "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "version": "3.49.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha1-BhRUR9kvSq8ligxE8ktHr66v/vY=", + "license": "MIT", "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -1969,15 +1874,17 @@ }, "node_modules/core-util-is": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha1-pgQtNjTCsn6TKPg3uWX6yDgI24U=", + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/debug/-/debug-4.4.3.tgz", + "integrity": "sha1-xq5DLZvZZiWC/OCHCbA4xY6ePWo=", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1988,29 +1895,27 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha1-mytnDQCkMWZ6inW6Kc0bmICc51E=", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "node_modules/electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==" + "version": "1.5.344", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha1-ZDfMCKfZuRSpgSDhgvN3k8nq/9Q=", + "license": "ISC" }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/entities/-/entities-4.5.0.tgz", + "integrity": "sha1-XSaOpecRPsdMTQM7eepaNaSI+0g=", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -2018,26 +1923,38 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha1-BfdaJdq5jk+x3NXhRywFRtUFfI8=", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha1-ARo/aYVroYnf+n3I/M6Z0qh5A+U=", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha1-RCZdPKwH4+p9wkdRY4BkN1SgUpI=", + "license": "MIT", "optional": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2048,19 +1965,21 @@ }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", + "license": "MIT" }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha1-ysZAd4XQNnWipeGlMFxpezR9kNY=", + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2071,24 +1990,27 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha1-LALYZNl/PqbIgwxGTL0Rq26rehw=", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA=", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob/-/glob-7.2.3.tgz", + "integrity": "sha1-uN8PuAK7+o6JvR2Ti04WV47UTys=", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2106,8 +2028,9 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ=", + "license": "ISC", "optional": true, "dependencies": { "is-glob": "^4.0.1" @@ -2116,21 +2039,14 @@ "node": ">= 6" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, "node_modules/google-closure-compiler": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230802.0.0.tgz", - "integrity": "sha512-o2fYoc8lqOBdhm95Ick0vWrtwH2Icd5yLZhbTcQ0T7NfGiBepYvx1BB63hR8ebgzEZemz9Fh+O6Kg/3Mjm28ww==", + "version": "20240317.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/google-closure-compiler/-/google-closure-compiler-20240317.0.0.tgz", + "integrity": "sha1-KzWylgR9aCTBh2d3yIwuWsYFD7k=", + "license": "Apache-2.0", "dependencies": { "chalk": "4.x", - "google-closure-compiler-java": "^20230802.0.0", + "google-closure-compiler-java": "^20240317.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -2142,124 +2058,74 @@ "node": ">=10" }, "optionalDependencies": { - "google-closure-compiler-linux": "^20230802.0.0", - "google-closure-compiler-osx": "^20230802.0.0", - "google-closure-compiler-windows": "^20230802.0.0" + "google-closure-compiler-linux": "^20240317.0.0", + "google-closure-compiler-osx": "^20240317.0.0", + "google-closure-compiler-windows": "^20240317.0.0" } }, "node_modules/google-closure-compiler-java": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230802.0.0.tgz", - "integrity": "sha512-PWKLMLwj7pR/U0yYbiy649LLqAscu+F1gyY4Y/jK6CmSLb8cIJbL8BTJd00828TzTNfWnYwxbkcQw0y9C2YsGw==" + "version": "20240317.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/google-closure-compiler-java/-/google-closure-compiler-java-20240317.0.0.tgz", + "integrity": "sha1-jIhCzpBkstC3nLQddcTrXrRh7ao=", + "license": "Apache-2.0" }, "node_modules/google-closure-compiler-linux": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230802.0.0.tgz", - "integrity": "sha512-F13U4iSXiWeGtHOFS25LVem1s6zI+pJvXVPVR7zSib5ppoUJ0JXnABJQezUR3FnpxmnkALG4oIGW0syH9zPLZA==", + "version": "20240317.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/google-closure-compiler-linux/-/google-closure-compiler-linux-20240317.0.0.tgz", + "integrity": "sha1-jmtwi/XyMda7SM8anucHwxQt53E=", "cpu": [ "x32", "x64" ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" ] }, "node_modules/google-closure-compiler-osx": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230802.0.0.tgz", - "integrity": "sha512-ANAi/ux92Tt+Na7vFDLeK2hRzotjC5j+nxoPtE0OcuNcbjji5dREKoJxkq7r0YwRTCzAFZszK5ip/NPdTOdCEg==", + "version": "20240317.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/google-closure-compiler-osx/-/google-closure-compiler-osx-20240317.0.0.tgz", + "integrity": "sha1-wXC3CaepWRlnvH8hP+oglsIZn/Y=", "cpu": [ "x32", "x64", "arm64" ], + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ] }, "node_modules/google-closure-compiler-windows": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230802.0.0.tgz", - "integrity": "sha512-ZQPujoNiiUyTGl8zEGR/0yAygWnbMtX/NQ/S/EHVgq5nmYkvDEVuiVbgpPAmO9lzBTq0hvUTRRATZbTU2ISxgA==", + "version": "20240317.0.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/google-closure-compiler-windows/-/google-closure-compiler-windows-20240317.0.0.tgz", + "integrity": "sha1-dkicPSyNCB4mHonp9f+r/6im6WE=", "cpu": [ "x32", "x64" ], + "license": "Apache-2.0", "optional": true, "os": [ "win32" ] }, - "node_modules/google-closure-compiler/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/google-closure-compiler/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/google-closure-compiler/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/google-closure-compiler/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/google-closure-compiler/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha1-XlwrFbYDcKTHkww4Pft2vxe8QDw=", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2269,8 +2135,9 @@ }, "node_modules/html-minifier-terser": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha1-GHUuI6Lw7UsPVQ8he7QWk+l1uUI=", + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "clean-css": "~5.3.2", @@ -2289,16 +2156,18 @@ }, "node_modules/html-minifier-terser/node_modules/commander": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-10.0.1.tgz", + "integrity": "sha1-iB7ka0930cHczFgjQzqjmwIsvgY=", + "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2306,13 +2175,15 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", + "license": "ISC" }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", + "license": "MIT", "optional": true, "dependencies": { "binary-extensions": "^2.0.0" @@ -2322,11 +2193,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.16.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha1-KpiAGoSfQ+Kt1kT7trxiKbGaTvQ=", + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2334,8 +2209,9 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -2343,8 +2219,9 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", + "license": "MIT", "optional": true, "dependencies": { "is-extglob": "^2.1.1" @@ -2355,8 +2232,9 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", + "license": "MIT", "optional": true, "engines": { "node": ">=0.12.0" @@ -2364,29 +2242,33 @@ }, "node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=", + "license": "MIT" }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha1-dNM1ojT2ftGZB/2t+sfM+dQJgl0=", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/json5/-/json5-2.2.3.tgz", + "integrity": "sha1-eM1vGhm9wStz21rQxh79ZsHikoM=", + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -2396,29 +2278,33 @@ }, "node_modules/lodash.debounce": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "license": "MIT" }, "node_modules/lower-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha1-b6I3xj29xKgsoP2ILkci3F5jTig=", + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=", + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/make-dir": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=", + "license": "MIT", "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -2427,6 +2313,15 @@ "node": ">=6" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/semver/-/semver-5.7.2.tgz", + "integrity": "sha1-SNVdtzfDKHzUg14X+hP+rOHEHvg=", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimatch/-/minimatch-3.1.5.tgz", @@ -2441,30 +2336,40 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha1-waRk52kzAuCCoHXO4MBXdBrEdyw=", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/ms/-/ms-2.1.3.tgz", + "integrity": "sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=", + "license": "MIT" + }, "node_modules/no-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha1-02H9XJgA9VhVGoNp/A3NRmK2Ek0=", + "license": "MIT", "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" + "version": "2.0.38", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha1-eRVpueRCSgROEsOr+tQY7YPOmUc=", + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -2472,16 +2377,18 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/param-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha1-fRf+SqEr3jTUp32RrPtiGcqtAcU=", + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -2489,8 +2396,9 @@ }, "node_modules/pascal-case": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha1-tI4O8rmOIF58Ha50fQsVCCN2YOs=", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -2498,21 +2406,24 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU=", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha1-PTIa8+q5ObCDyPkpodEs2oHCa2s=", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.2", @@ -2529,21 +2440,24 @@ }, "node_modules/pify": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/pify/-/pify-4.0.1.tgz", + "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/process-nextick-args": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", + "license": "MIT" }, "node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha1-kRJegEK7obmIf0k0X2J3Anzovps=", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2556,8 +2470,9 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha1-dKNwvYVxFuJFspzJc0DNQxoCpsc=", + "license": "MIT", "optional": true, "dependencies": { "picomatch": "^2.2.1" @@ -2568,13 +2483,15 @@ }, "node_modules/regenerate": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha1-uTRtiCfo9aMve6KWN9OYtpAUhIo=", + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha1-qhE4ErqJm2MGWMdiNGa+ceH4b2Y=", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -2582,136 +2499,155 @@ "node": ">=4" } }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.4.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha1-NYDODE+u3vWZ7MsUZhJDa2KhduU=", + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha1-3yP/JuDFswCmRwytFgqdCQw6N6s=", + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.13.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha1-BZPLrLJ1J5J2kgMJKK5NO4eNb40=", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/relateurl": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/remove-trailing-separator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "license": "ISC" }, "node_modules/replace-ext": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha1-LW2ZbQShWFXZZ0Q2Md1fd4JbAWo=", + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.12", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha1-9bKmgIl8acI4oTzRaxVnH4tzVJ8=", + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", + "license": "MIT" }, "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "6.3.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/semver/-/semver-6.3.1.tgz", + "integrity": "sha1-VW0u+GiRRuRtzqS/3QlfNDTf/LQ=", + "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" } }, "node_modules/slash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/slash/-/slash-2.0.0.tgz", + "integrity": "sha1-3lUoUaF1nfOo8gZTVEL17E3eq0Q=", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha1-BP58f54e0tZiIzwoyys1ufY/bk8=", + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha1-btpL00SjyUrqN21MwxvHcxEDngk=", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2720,9 +2656,10 @@ } }, "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "version": "5.46.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/terser/-/terser-5.46.2.tgz", + "integrity": "sha1-uVKWctWwAkx5WVccg7gvZQd7Kk8=", + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -2738,13 +2675,15 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/commander/-/commander-2.20.3.tgz", + "integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=", + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "license": "MIT", "optional": true, "dependencies": { "is-number": "^7.0.0" @@ -2754,22 +2693,25 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", + "license": "0BSD" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha1-yzFz/kfKdD4ighbko93EyE1ijMI=", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha1-VP0W4OyxZ88Ezx91a9zJLrp5dsM=", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -2779,25 +2721,27 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.1", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha1-Zaet+thXTCGYkOIZKFzkxk7Wfqo=", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha1-MB1PikPSt1yXrfrYfJ3VNQyUddE=", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha1-ZNdttYcTE2rL60xJEUNmzGzC6A0=", "funding": [ { "type": "opencollective", @@ -2812,6 +2756,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -2825,13 +2770,15 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "license": "MIT" }, "node_modules/vinyl": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha1-I8+4u6tezjgDqiwKHrKK98u6GXQ=", + "license": "MIT", "dependencies": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -2846,2017 +2793,33 @@ }, "node_modules/vinyl-sourcemaps-apply": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "license": "ISC", "dependencies": { "source-map": "^0.5.1" } }, "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "license": "ISC" }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/cli": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.4.tgz", - "integrity": "sha512-j3luA9xGKCXVyCa5R7lJvOMM+Kc2JEnAEIgz2ggtjQ/j5YUVgfsg/WsG95bbsgq7YLHuiCOzMnoSasuY16qiCw==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.17", - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^2.0.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - } - }, - "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "requires": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" - }, - "@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", - "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", - "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "requires": { - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" - } - }, - "@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" - }, - "@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" - }, - "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" - }, - "@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", - "requires": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" - } - }, - "@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "requires": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - } - }, - "@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "requires": { - "@babel/types": "^7.28.5" - } - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" - } - }, - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", - "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "requires": {} - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", - "requires": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", - "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", - "requires": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", - "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", - "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", - "requires": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" - } - }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/preset-env": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", - "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" - }, - "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - } - }, - "@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "optional": true - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", - "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.4" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "baseline-browser-mapping": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz", - "integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true - }, - "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "optional": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", - "requires": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==" - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "optional": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==" - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "core-js-compat": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", - "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", - "requires": { - "browserslist": "^4.22.2" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==" - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "optional": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "google-closure-compiler": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230802.0.0.tgz", - "integrity": "sha512-o2fYoc8lqOBdhm95Ick0vWrtwH2Icd5yLZhbTcQ0T7NfGiBepYvx1BB63hR8ebgzEZemz9Fh+O6Kg/3Mjm28ww==", - "requires": { - "chalk": "4.x", - "google-closure-compiler-java": "^20230802.0.0", - "google-closure-compiler-linux": "^20230802.0.0", - "google-closure-compiler-osx": "^20230802.0.0", - "google-closure-compiler-windows": "^20230802.0.0", - "minimist": "1.x", - "vinyl": "2.x", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "google-closure-compiler-java": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230802.0.0.tgz", - "integrity": "sha512-PWKLMLwj7pR/U0yYbiy649LLqAscu+F1gyY4Y/jK6CmSLb8cIJbL8BTJd00828TzTNfWnYwxbkcQw0y9C2YsGw==" - }, - "google-closure-compiler-linux": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230802.0.0.tgz", - "integrity": "sha512-F13U4iSXiWeGtHOFS25LVem1s6zI+pJvXVPVR7zSib5ppoUJ0JXnABJQezUR3FnpxmnkALG4oIGW0syH9zPLZA==", - "optional": true - }, - "google-closure-compiler-osx": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230802.0.0.tgz", - "integrity": "sha512-ANAi/ux92Tt+Na7vFDLeK2hRzotjC5j+nxoPtE0OcuNcbjji5dREKoJxkq7r0YwRTCzAFZszK5ip/NPdTOdCEg==", - "optional": true - }, - "google-closure-compiler-windows": { - "version": "20230802.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230802.0.0.tgz", - "integrity": "sha512-ZQPujoNiiUyTGl8zEGR/0yAygWnbMtX/NQ/S/EHVgq5nmYkvDEVuiVbgpPAmO9lzBTq0hvUTRRATZbTU2ISxgA==", - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "requires": { - "function-bind": "^1.1.2" - } - }, - "html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "requires": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "dependencies": { - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" - } - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "requires": { - "hasown": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "optional": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "optional": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "requires": { - "tslib": "^2.0.3" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha1-WAyI+NVEXyvWqo88re+g3nn71p4=", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "picomatch": { - "version": "2.3.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha1-WpQpFeJrNy3A8OZ1MUmhbmscVgE=", - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" - }, - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==" - }, - "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" - }, - "update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", - "requires": { - "source-map": "^0.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha1-27fa+b/YusmrRev2ArjLrQ1dCP0=", + "license": "ISC" } } } diff --git a/package.json b/package.json index 5fbfc36ff9b3d..96c577cf8033a 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "private": true, "dependencies": { - "@babel/cli": "^7.23.4", - "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.8", - "acorn": "^8.11.3", - "google-closure-compiler": "20230802.0.0", + "@babel/cli": "^7.28.6", + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "acorn": "^8.15.0", + "google-closure-compiler": "20240317.0.0", "html-minifier-terser": "7.2.0" }, "scripts": { "lint": "eslint .", - "fmt": "prettier --write src/*.mjs tools/*.mjs", - "check": "prettier --check src/*.mjs tools/*.mjs" + "fmt": "prettier --write src/*.mjs tools/*.mjs --ignore-path src/pthread_esm_startup.mjs", + "check": "prettier --check src/*.mjs tools/*.mjs --ignore-path src/pthread_esm_startup.mjs" } } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000..263f46c9e5587 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,131 @@ +[project] +requires-python = ">=3.10" + +[tool.ruff] +preview = true +line-length = 100 +indent-width = 2 +exclude = [ + "./cache/", + "./node_modules/", + "./site/source/_themes/", + "./site/source/conf.py", + "./system/lib/", + "./test/third_party/", + "./third_party/", + "./tools/filelock.py", + "./tools/scons/", + ".git", +] + +lint.select = [ + "ARG", + "ASYNC", + "B", + "C4", + "C90", + "COM", + "E", + "F", + "I", + "PERF", + "PIE", + "PL", + "UP", + "W", + "YTT", +] +lint.external = [ "D" ] +lint.ignore = [ + "B011", # See https://github.com/PyCQA/flake8-bugbear/issues/66 + "B023", + "B026", + "E272", + "E402", + "E241", + "E266", + "E501", + "E721", + "E741", + "E111", # Does not seem to honor `indent-width = 2` above + "E114", # Does not seem to honor `indent-width = 2` above + "E261", + "PERF203", + "PERF401", + "PLC0415", + "PLR0904", + "PLR0916", + "PLR0914", + "PLR1702", + "PLR1704", + "PLR5501", + "PLR6301", + "PLW0602", + "PLW0603", + "PLW1510", + "PLW2901", + "UP030", # TODO + "UP031", # TODO + "UP032", # TODO +] +lint.per-file-ignores."tools/ports/*.py" = [ "ARG001", "ARG005" ] +lint.per-file-ignores."test/other/ports/*.py" = [ "ARG001" ] +lint.per-file-ignores."test/parallel_testsuite.py" = [ "ARG002" ] +lint.per-file-ignores."test/test_benchmark.py" = [ "ARG002" ] +lint.mccabe.max-complexity = 51 # Recommended: 10 +lint.pylint.allow-magic-value-types = [ + "bytes", + "float", + "int", + "str", +] +lint.pylint.max-args = 15 # Recommended: 5 +lint.pylint.max-branches = 50 # Recommended: 12 +lint.pylint.max-returns = 16 # Recommended: 6 +lint.pylint.max-statements = 142 # Recommended: 50 + +[tool.coverage.run] +source = [ "." ] +omit = [ + "./test/*", + "./third_party/*", + "./tools/emcoverage.py", + "test.py", +] + +[tool.mypy] +mypy_path = "third_party/,third_party/ply,third_party/websockify" +files = [ "." ] +exclude = ''' +(?x)( +cache | +third_party | +conf\.py | +emrun\.py | +site/source/_themes/ | +tools/scons/site_scons/site_tools/emscripten/__init__\.py | +tools/maint/create_dom_pk_codes.py | +site/source/get_wiki\.py | +test/parse_benchmark_output\.py +)''' + +[[tool.mypy.overrides]] +module = [ + "tools.webidl_binder", + "tools.toolchain_profiler", + "tools.filelock", + "tools.find_bigvars", + "leb128", + "ply.*", +] +ignore_errors = true + +[[tool.mypy.overrides]] +module = ["psutil", "win32con", "win32gui", "win32process"] +ignore_missing_imports = true + +[tool.deadcode] +exclude = ["out", "cache", "third_party", "test/third_party", "node_modules", "site/source/_themes", "site/source/conf.py"] + +[tool.vulture] +exclude = ["out", "cache", "third_party", "test/third_party", "node_modules", "site/source/_themes", "site/source/conf.py"] diff --git a/requirements-dev.txt b/requirements-dev.txt index d21da9cc6b377..9cb5413753980 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,28 +1,22 @@ # TODO(sbc): switch to using Pipenv since it seems like that way to go # these day managing python deps. -# These requirements are only needed for developers who want to run flake8 on +# These requirements are only needed for developers who want to run ruff on # the codebase and generate docs using Sphinx, not for users of emscripten. # Install with `pip3 install -r requirements-dev.txt` -flake8==5.0.4 -flake8-bugbear==22.9.23 -flake8-unused-arguments==0.0.11 -coverage==5.5 -mypy==0.971 -types-requests==2.27.14 -unittest-xml-reporting==3.1.0 - -# See https://github.com/emscripten-core/emscripten/issues/19785 -lxml==4.9.2 +coverage[toml]==6.5 +mypy==1.14 +psutil==7.0.0 +ruff==0.15.7 +types-requests==2.32.0.20241016 +unittest-xml-reporting==3.2.0 +deadcode==2.3.1 +vulture==2.14 # This version is mentioned in `site/source/docs/site/about.rst`. # Please keep them in sync. -sphinx==2.4.4 -# See https://github.com/readthedocs/readthedocs.org/issues/9038 -jinja2<3.1 - -# Pin docutils because newer versions are not compatible with sphinx 2.4.4 -docutils==0.17.1 +sphinx==7.1.2 +sphinxcontrib-jquery==4.0 # Needed by test/test_sockets.py -websockify==0.10.0 +websockify==0.12.0 diff --git a/site/Makefile b/site/Makefile index 8ac46ed3d9a85..c83abbd936f66 100644 --- a/site/Makefile +++ b/site/Makefile @@ -4,13 +4,13 @@ # You can set these variables from the command line. SPHINXOPTS = -W SPHINXBUILD = sphinx-build -SPHINXVERSION = 2.4.4 +SPHINXVERSION = 7.1.2 PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx version 2.4.4 installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx version 7.1.2 installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif ifneq ($(shell $(SPHINXBUILD) --version), $(SPHINXBUILD) $(SPHINXVERSION)) diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/__init__.py b/site/source/_themes/emscripten_sphinx_rtd_theme/__init__.py index 6630f217a154b..7938cf1c900f6 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/__init__.py +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/__init__.py @@ -3,20 +3,109 @@ # University of Illinois/NCSA Open Source License. Both these licenses can be # found in the LICENSE file. -"""Sphinx ReadTheDocs theme. +""" +Sphinx Read the Docs theme. From https://github.com/ryan-roemer/sphinx-bootstrap-theme. - """ + import os +from os import path +from sys import version_info as python_version -VERSION = (0, 1, 6) +from sphinx import version_info as sphinx_version +from sphinx.locale import _ +from sphinx.util.logging import getLogger -__version__ = ".".join(str(v) for v in VERSION) + +__version__ = '3.0.1' __version_full__ = __version__ +logger = getLogger(__name__) + def get_html_theme_path(): """Return list of HTML theme paths.""" - cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + logger.warning( + _('Calling get_html_theme_path is deprecated. If you are calling it to define html_theme_path, you are safe to remove that code.') + ) + + cur_dir = path.abspath(path.dirname(path.dirname(__file__))) return cur_dir + + +def config_initiated(app, config): + theme_options = config.html_theme_options or {} + if theme_options.get('canonical_url'): + logger.warning( + _('The canonical_url option is deprecated, use the html_baseurl option from Sphinx instead.') + ) + + if theme_options.get("analytics_id"): + logger.warning( + _('The analytics_id option is deprecated, use the sphinxcontrib-googleanalytics extension instead.') + ) + + if theme_options.get("analytics_anonymize_ip"): + logger.warning( + _('The analytics_anonymize_ip option is deprecated, use the sphinxcontrib-googleanalytics extension instead.') + ) + + if "extra_css_files" in config.html_context: + logger.warning( + _('The extra_css_file option is deprecated, use the html_css_files option from Sphinx instead.') + ) + + +def extend_html_context(app, pagename, templatename, context, doctree): + # Add ``sphinx_version_info`` tuple for use in Jinja templates + context['sphinx_version_info'] = sphinx_version + + # Inject all the Read the Docs environment variables in the context: + # https://docs.readthedocs.io/en/stable/reference/environment-variables.html + context['READTHEDOCS'] = os.environ.get("READTHEDOCS", False) == "True" + if context['READTHEDOCS']: + for key, value in os.environ.items(): + if key.startswith("READTHEDOCS_"): + context[key] = value + + + +# See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package +def setup(app): + if python_version[0] < 3: + logger.error("Python 2 is not supported with sphinx_rtd_theme, update to Python 3.") + + app.require_sphinx('6.0') + if app.config.html4_writer: + logger.error("'html4_writer' is not supported with sphinx_rtd_theme.") + + # Since Sphinx 6, jquery isn't bundled anymore and we need to ensure that + # the sphinxcontrib-jquery extension is enabled. + # See: https://dev.readthedocs.io/en/latest/design/sphinx-jquery.html + if sphinx_version >= (6, 0, 0): + # Documentation of Sphinx guarantees that an extension is added and + # enabled at most once. + # See: https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.setup_extension + app.setup_extension("sphinxcontrib.jquery") + # However, we need to call the extension's callback since setup_extension doesn't do it + # See: https://github.com/sphinx-contrib/jquery/issues/23 + from sphinxcontrib.jquery import add_js_files as jquery_add_js_files + jquery_add_js_files(app, app.config) + + # Register the theme that can be referenced without adding a theme path + app.add_html_theme('sphinx_rtd_theme', path.abspath(path.dirname(__file__))) + + # Add Sphinx message catalog for newer versions of Sphinx + # See http://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_message_catalog + rtd_locale_path = path.join(path.abspath(path.dirname(__file__)), 'locale') + app.add_message_catalog('sphinx', rtd_locale_path) + app.connect('config-inited', config_initiated) + + # sphinx emits the permalink icon for headers, so choose one more in keeping with our theme + app.config.html_permalinks_icon = "\uf0c1" + + # Extend the default context when rendering the templates. + app.connect("html-page-context", extend_html_context) + + return {'parallel_read_safe': True, 'parallel_write_safe': True} diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/layout.html b/site/source/_themes/emscripten_sphinx_rtd_theme/layout.html index e8a0e17931b30..1eac43b058f78 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/layout.html +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/layout.html @@ -6,203 +6,255 @@ {%- else %} {%- set titlesuffix = "" %} {%- endif %} - +{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %} + +{# Build sphinx_version_info tuple from sphinx_version string in pure Jinja #} +{%- set (_ver_major, _ver_minor) = (sphinx_version.split('.') | list)[:2] | map('int') -%} +{%- set sphinx_version_info = (_ver_major, _ver_minor, -1) -%} + - - += (7, 2) %} data-content_root="{{ content_root }}"{% endif %}> - - - {% block htmltitle %} + + {%- if READTHEDOCS and not embedded %} + + {%- endif %} + {{- metatags }} + + {%- block htmltitle %} {{ title|striptags|e }}{{ titlesuffix }} - {% endblock %} + {%- endblock -%} - {# FAVICON #} - {% if favicon %} - - {% endif %} + {#- CSS #} + {%- for css_file in css_files %} + {%- if css_file|attr("filename") %} + {{ css_tag(css_file) }} + {%- else %} + + {%- endif %} + {%- endfor %} - {# CSS #} - + {# + "extra_css_files" is an undocumented Read the Docs theme specific option. + There is no need to check for ``|attr("filename")`` here because it's always a string. + Note that this option should be removed in favor of regular ``html_css_files``: + https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_css_files + #} + {%- for css_file in extra_css_files %} + + {%- endfor -%} - {# OPENSEARCH #} - {% if not embedded %} - {% if use_opensearch %} - - {% endif %} + {#- FAVICON #} + {%- if favicon_url %} + + {%- endif %} - {% endif %} + {#- CANONICAL URL (deprecated) #} + {%- if theme_canonical_url and not pageurl %} + + {%- endif -%} - {# RTD hosts this file, so just load on non RTD builds #} - {% if not READTHEDOCS %} - - {% endif %} + {#- CANONICAL URL #} + {%- if pageurl %} + + {%- endif -%} - {% for cssfile in css_files %} - - {% endfor %} + {#- JAVASCRIPTS #} + {%- block scripts %} + {%- if not embedded %} + {%- for scriptfile in script_files %} + {{ js_tag(scriptfile) }} + {%- endfor %} + + + {%- if READTHEDOCS or DEBUG %} + + {%- endif %} + + {#- OPENSEARCH #} + {%- if use_opensearch %} + + {%- endif %} + {%- endif %} + {%- endblock %} {%- block linktags %} {%- if hasdoc('about') %} - + {%- endif %} {%- if hasdoc('genindex') %} - + {%- endif %} {%- if hasdoc('search') %} - + {%- endif %} {%- if hasdoc('copyright') %} - - {%- endif %} - - {%- if parents %} - + {%- endif %} {%- if next %} - + {%- endif %} {%- if prev %} - + {%- endif %} {%- endblock %} {%- block extrahead %} {% endblock %} - - {# Keep modernizr in head - http://modernizr.com/docs/#installing #} - - - - +
- + + {%- block extrabody %} {% endblock %}
- + {#- SIDE NAV, TOGGLES ON MOBILE #} + - +
- {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} -
-
- {% include "versions.html" %} - - {% if not embedded %} - - - {%- for scriptfile in script_files %} - - {%- endfor %} + {% include "versions.html" -%} - {% endif %} - - {# RTD hosts this file, so just load on non RTD builds #} - {% if not READTHEDOCS %} - - {% endif %} - - {# STICKY NAVIGATION #} - {% if theme_sticky_navigation %} - - {% endif %} + + {#- Do not conflict with RTD insertion of analytics script #} + {%- if not READTHEDOCS %} + {%- if theme_analytics_id %} + + + + + {%- endif %} + {%- endif %} {%- block footer %} {% endblock %}
diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/search.html b/site/source/_themes/emscripten_sphinx_rtd_theme/search.html index e3aa9b5c6e75b..e71589509b133 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/search.html +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/search.html @@ -1,50 +1,56 @@ {# - basic/search.html - ~~~~~~~~~~~~~~~~~ + basic/search.html + ~~~~~~~~~~~~~~~~~ - Template for the search page. + Template for the search page. - :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see https://github.com/sphinx-doc/sphinx/blob/master/LICENSE for details. #} {%- extends "layout.html" %} {% set title = _('Search') %} -{% set script_files = script_files + ['_static/searchtools.js'] %} -{% block footer %} - - {# this is used when loading the search index using $.ajax fails, - such as on Chrome for documents on localhost #} - +{% set display_vcs_links = False %} +{%- block scripts %} {{ super() }} + + +{%- endblock %} +{% block footer %} + +{# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + +{{ super() }} {% endblock %} {% block body %} - + - {% if search_performed %} -

{{ _('Search Results') }}

- {% if not search_results %} -

{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

- {% endif %} +{% if search_performed %} + {# Translators: Search is a noun, not a verb #} +

{{ _('Search Results') }}

+ {% if not search_results %} +

{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

{% endif %} -
- {% if search_results %} -
    - {% for href, caption, context in search_results %} -
  • - {{ caption }} -

    {{ context|e }}

    -
  • - {% endfor %} -
- {% endif %} -
-{% endblock %} +{% endif %} +
+{% if search_results %} +
    + {% for href, caption, context in search_results %} +
  • + {{ caption }} +

    {{ context|e }}

    +
  • + {% endfor %} +
+{% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/searchbox.html b/site/source/_themes/emscripten_sphinx_rtd_theme/searchbox.html index 24418d32bcb05..a16ab2c899afa 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/searchbox.html +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/searchbox.html @@ -1,7 +1,9 @@ +{%- if 'singlehtml' not in builder %}
-
- + +
+{%- endif %} diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/badge_only.css b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/badge_only.css index 4868a00277d1c..88ba55b965c28 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/badge_only.css +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/badge_only.css @@ -1 +1 @@ -.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:"\f02d"}.icon-book:before{content:"\f02d"}.fa-caret-down:before{content:"\f0d7"}.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000000000..6cb60000181db Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000000000..7059e23142aae Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000000000..f815f63f99da8 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000000000..f2c76e5bda18a Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.eot b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000..e9f60ca953f93 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.eot differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.svg b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000000..855c845e538b6 --- /dev/null +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.ttf b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000..35acda2fa1196 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.ttf differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000..400014a4b06ee Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000000..4d13fc60404b9 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000000000..88ad05b9ff413 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000000000..c4e3d804b57b6 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold-italic.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff new file mode 100644 index 0000000000000..c6dff51f063cc Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000000000..bb195043cfc07 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-bold.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000000000..76114bc033622 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000000000..3404f37e2e312 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal-italic.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff new file mode 100644 index 0000000000000..ae1307ff5f4c4 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff2 b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000000000..3bf9843328a63 Binary files /dev/null and b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/fonts/lato-normal.woff2 differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css index a7b7eba2dc89c..9c78dc4f7f563 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/static/css/theme.css @@ -1,394 +1,8002 @@ -*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%} - -body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:20px 0;padding:0}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical} -table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline} -.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""} -pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}} -.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1} -.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome +html { + box-sizing: border-box +} + +*, +:after, +:before { + box-sizing: inherit +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block +} + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1 +} + +[hidden], +audio:not([controls]) { + display: none +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100% +} + +body { + margin: 0 +} + +a:active, +a:hover { + outline: 0 +} + +abbr[title] { + border-bottom: 1px dotted +} + +b, +strong { + font-weight: 700 +} + +blockquote { + margin: 0 +} + +dfn { + font-style: italic +} + +ins { + background: #ff9; + text-decoration: none +} + +ins, +mark { + color: #000 +} + +mark { + background: #ff0; + font-style: italic; + font-weight: 700 +} + +.rst-content code, +.rst-content tt, +code, +kbd, +pre, +samp { + font-family: monospace, serif; + _font-family: courier new, monospace; + font-size: 1em +} + +pre { + white-space: pre +} + +q { + quotes: none +} + +q:after, +q:before { + content: ""; + content: none +} + +small { + font-size: 85% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sup { + top: -.5em +} + +sub { + bottom: -.25em +} + +dl, +ol, +ul { + margin: 0; + padding: 0; + list-style: none; + list-style-image: none +} + +li { + list-style: none +} + +dd { + margin: 0 +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; + max-width: 100% +} + +svg:not(:root) { + overflow: hidden +} + +figure, +form { + margin: 0 +} + +label { + cursor: pointer +} + +button, +input, +select, +textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle +} + +button, +input { + line-height: normal +} + +button, +input[type=button], +input[type=reset], +input[type=submit] { + cursor: pointer; + -webkit-appearance: button; + *overflow: visible +} + +button[disabled], +input[disabled] { + cursor: default +} + +input[type=search] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box +} + +textarea { + resize: vertical +} + +table { + border-collapse: collapse; + border-spacing: 0 +} + +td { + vertical-align: top +} + +.chromeframe { + margin: .2em 0; + background: #ccc; + color: #000; + padding: .2em 0 +} + +.ir { + display: block; + border: 0; + text-indent: -999em; + overflow: hidden; + background-color: transparent; + background-repeat: no-repeat; + text-align: left; + direction: ltr; + *line-height: 0 +} + +.ir br { + display: none +} + +.hidden { + display: none !important; + visibility: hidden +} + +.visuallyhidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px +} + +.visuallyhidden.focusable:active, +.visuallyhidden.focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto +} + +.invisible { + visibility: hidden +} + +.relative { + position: relative +} + +big, +small { + font-size: 100% +} + +@media print { + + body, + html, + section { + background: none !important + } + + * { + box-shadow: none !important; + text-shadow: none !important; + filter: none !important; + -ms-filter: none !important + } + + a, + a:visited { + text-decoration: underline + } + + .ir a:after, + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: "" + } + + blockquote, + pre { + page-break-inside: avoid + } + + thead { + display: table-header-group + } + + img, + tr { + page-break-inside: avoid + } + + img { + max-width: 100% !important + } + + @page { + margin: .5cm + } + + .rst-content .toctree-wrapper>p.caption, + h2, + h3, + p { + orphans: 3; + widows: 3 + } + + .rst-content .toctree-wrapper>p.caption, + h2, + h3 { + page-break-after: avoid + } +} + +.btn, +.fa:before, +.icon:before, +.rst-content .admonition, +.rst-content .admonition-title:before, +.rst-content .admonition-todo, +.rst-content .attention, +.rst-content .caution, +.rst-content .code-block-caption .headerlink:before, +.rst-content .danger, +.rst-content .eqno .headerlink:before, +.rst-content .error, +.rst-content .hint, +.rst-content .important, +.rst-content .note, +.rst-content .seealso, +.rst-content .tip, +.rst-content .warning, +.rst-content code.download span:first-child:before, +.rst-content dl dt .headerlink:before, +.rst-content h1 .headerlink:before, +.rst-content h2 .headerlink:before, +.rst-content h3 .headerlink:before, +.rst-content h4 .headerlink:before, +.rst-content h5 .headerlink:before, +.rst-content h6 .headerlink:before, +.rst-content p.caption .headerlink:before, +.rst-content p .headerlink:before, +.rst-content table>caption .headerlink:before, +.rst-content tt.download span:first-child:before, +.wy-alert, +.wy-dropdown .caret:before, +.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-info .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-success .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, +.wy-menu-vertical li.current>a button.toctree-expand:before, +.wy-menu-vertical li.on a button.toctree-expand:before, +.wy-menu-vertical li button.toctree-expand:before, +input[type=color], +input[type=date], +input[type=datetime-local], +input[type=datetime], +input[type=email], +input[type=month], +input[type=number], +input[type=password], +input[type=search], +input[type=tel], +input[type=text], +input[type=time], +input[type=url], +input[type=week], +select, +textarea { + -webkit-font-smoothing: antialiased +} + +.clearfix { + *zoom: 1 +} + +.clearfix:after, +.clearfix:before { + display: table; + content: "" +} + +.clearfix:after { + clear: both +} + +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.0.3");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.0.3") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.0.3") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before,.icon-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before,.icon-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:"\f057"}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before,.icon-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before,.icon-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:"\f0a8"}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before,.icon-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-reply-all:before{content:"\f122"}.fa-mail-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before,.icon-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a{color:#2980b9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:60px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:60px}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27ae60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27ae60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#e74c3c !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#e67e22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980b9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:0.5em 1em 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:0.5em}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{display:block;float:left;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{display:block;float:left;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{display:block;float:left;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:0.5em 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#ccc;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e74c3c}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:0.8em;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #e74c3c}.wy-control-group.wy-control-group-error textarea{border:solid 1px #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}} - - @media screen and (max-width: 768px){.tablet-hide{display:none}} - @media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px;margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0} - - .wy-table-responsive{ - margin-bottom:24px; - max-width:100%; - overflow:auto - } - -.wy-table-responsive table{ - margin-bottom:0 !important - } - -.wy-table-responsive table td {white-space:nowrap} - -.wy-table-responsive table th{white-space:nowrap} - -a{color:#2980b9;text-decoration:none}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%;overflow-x:hidden} - -body{ - /* font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; */ - font-family:"proxima-nova","Helvetica Neue",Arial,sans-serif; - font-weight:normal; - color:#404040; - min-height:100%; - overflow-x:hidden; - background:#edf0f2 - } - -.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980b9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27ae60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#e74c3c !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%} - -code, .rst-content tt - { - white-space:nowrap; - max-width:100%; - background:#fff; - border:solid 1px #e1e4e5; - /* font-size:75%; */ - font-size:90%; - padding:0 5px; - font-family:"Inconsolata","Consolata","Monaco",monospace;color:#e74c3c; - overflow-x:auto - } - -code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px} -.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li { - list-style:disc;margin-left:24px} - -.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px} - -/* .wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li { */ -.wy-plain-list-decimal ol li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li { - list-style:decimal;margin-left:24px - } - -/* HamishW - add to fix unordered bullets inside decimal lists */ -.rst-content ol.arabic ul li { - list-style:disc; - } - - - -.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9b59b6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:"Inconsolata","Consolata","Monaco",monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:"Inconsolata","Consolata","Monaco",monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#eaf2f5} - .wy-breadcrumbs li{display:inline-block} - .wy-breadcrumbs li.wy-breadcrumbs-aside { - float:right; - padding-left:5px; - font-size:0.8em; - } - - .wy-breadcrumbs li a{ - display:inline-block;padding:5px - } - - .wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block} - - @media screen and (max-width: 480px) - {.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980b9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3} - - .wy-menu-vertical li.current a{ - color:gray; - border-right:solid 1px #c9c9c9; - padding:0.4045em 2.427em - } - - .wy-menu-vertical li.current a:hover{background:#d6d6d6} - - .wy-menu-vertical li.on a { - color:#404040; - padding:0.4045em 1.618em; - font-weight:bold; - position:relative; - background:#fcfcfc; - border:none; - border-bottom:solid 1px #c9c9c9; - border-top:solid 1px #c9c9c9; - padding-left:1.618em -4px - } - - -.wy-menu-vertical li.current>a { - color:#404040; - padding:0.4045em 1.618em; - font-weight:bold; - position:relative; - background:#fcfcfc; - border:none; - border-bottom:solid 1px #c9c9c9; - border-top:solid 1px #c9c9c9; - padding-left:1.618em -4px - } - - .wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc} - .wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff} - - .wy-side-nav-search {z-index:200; - background-color:#2980b9; - text-align:center; - /* padding:0.809em; */ - /* padding-top: 0.809em;*/ - padding-right: 0.809em; - padding-bottom: 0.809em; - padding-left: 0.809em; - display:block; - color:#fcfcfc; - margin-bottom:0.809em - } - - .wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4} - - - .wy-side-nav-search img{ - display:block; - margin:auto auto 0.809em auto; - /*height:45px;*/ - /*width:45px;*/ - width:200px; - /*background-color:#2980b9;*/ - padding:5px; - /*border-radius:100%*/ - } - - .wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a { - color:#fcfcfc; - font-size:100%; - font-weight:bold; - display:inline-block; - padding-top: 4px; - padding-right: 6px; - /*padding-bottom: 4px;*/ - padding-left: 6px; - /* margin-bottom:0.809em */ - } - - .wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0} - - .wy-body-for-nav{ - /* background:left repeat-y #F1F0F0; */ - /* - background:left repeat-y rgb(97, 6, 6); - background-size:300px 1px - */ /* This is deep red colour - removed */ - /* background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC); */ - - } - - .wy-grid-for-nav { - /* position:absolute; */ - position:relative; /* Make left column full length */ - width:100%; - height:100% - } - - .wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200} - - .wy-nav-top{ - display:none; - background:#2980b9; - color:#fff; - padding:0.4045em 0.809em; - position:relative; - line-height:50px; - text-align:center; - font-size:100%; - *zoom:1} - - .wy-nav-top:before,.wy-nav-top:after{display:table;content:""} - - .wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold} - - .wy-nav-top img{ - margin-right:12px; - /*height:45px; */ - /*width:45px;*/ - width:200px; - background-color:#2980b9; - padding:5px; - /*border-radius:100%*/ - } - - .wy-nav-top i{font-size:30px;float:left;cursor:pointer} - .wy-nav-content-wrap{ - margin-left:300px; - /* background:#fcfcfc; */ - min-height:100% - } - - .wy-nav-content{ + */ +@font-face { + font-family: FontAwesome; + src: url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713); + src: url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"), url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"), url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"), url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"), url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg"); + font-weight: 400; + font-style: normal +} + +.fa, +.icon, +.rst-content .admonition-title, +.rst-content .code-block-caption .headerlink, +.rst-content .eqno .headerlink, +.rst-content code.download span:first-child, +.rst-content dl dt .headerlink, +.rst-content h1 .headerlink, +.rst-content h2 .headerlink, +.rst-content h3 .headerlink, +.rst-content h4 .headerlink, +.rst-content h5 .headerlink, +.rst-content h6 .headerlink, +.rst-content p.caption .headerlink, +.rst-content p .headerlink, +.rst-content table>caption .headerlink, +.rst-content tt.download span:first-child, +.wy-menu-vertical li.current>a button.toctree-expand, +.wy-menu-vertical li.on a button.toctree-expand, +.wy-menu-vertical li button.toctree-expand { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.fa-lg { + font-size: 1.33333em; + line-height: .75em; + vertical-align: -15% +} + +.fa-2x { + font-size: 2em +} + +.fa-3x { + font-size: 3em +} + +.fa-4x { + font-size: 4em +} + +.fa-5x { + font-size: 5em +} + +.fa-fw { + width: 1.28571em; + text-align: center +} + +.fa-ul { + padding-left: 0; + margin-left: 2.14286em; + list-style-type: none +} + +.fa-ul>li { + position: relative +} + +.fa-li { + position: absolute; + left: -2.14286em; + width: 2.14286em; + top: .14286em; + text-align: center +} + +.fa-li.fa-lg { + left: -1.85714em +} + +.fa-border { + padding: .2em .25em .15em; + border: .08em solid #eee; + border-radius: .1em +} + +.fa-pull-left { + float: left +} + +.fa-pull-right { + float: right +} + +.fa-pull-left.icon, +.fa.fa-pull-left, +.rst-content .code-block-caption .fa-pull-left.headerlink, +.rst-content .eqno .fa-pull-left.headerlink, +.rst-content .fa-pull-left.admonition-title, +.rst-content code.download span.fa-pull-left:first-child, +.rst-content dl dt .fa-pull-left.headerlink, +.rst-content h1 .fa-pull-left.headerlink, +.rst-content h2 .fa-pull-left.headerlink, +.rst-content h3 .fa-pull-left.headerlink, +.rst-content h4 .fa-pull-left.headerlink, +.rst-content h5 .fa-pull-left.headerlink, +.rst-content h6 .fa-pull-left.headerlink, +.rst-content p .fa-pull-left.headerlink, +.rst-content table>caption .fa-pull-left.headerlink, +.rst-content tt.download span.fa-pull-left:first-child, +.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand, +.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand, +.wy-menu-vertical li button.fa-pull-left.toctree-expand { + margin-right: .3em +} + +.fa-pull-right.icon, +.fa.fa-pull-right, +.rst-content .code-block-caption .fa-pull-right.headerlink, +.rst-content .eqno .fa-pull-right.headerlink, +.rst-content .fa-pull-right.admonition-title, +.rst-content code.download span.fa-pull-right:first-child, +.rst-content dl dt .fa-pull-right.headerlink, +.rst-content h1 .fa-pull-right.headerlink, +.rst-content h2 .fa-pull-right.headerlink, +.rst-content h3 .fa-pull-right.headerlink, +.rst-content h4 .fa-pull-right.headerlink, +.rst-content h5 .fa-pull-right.headerlink, +.rst-content h6 .fa-pull-right.headerlink, +.rst-content p .fa-pull-right.headerlink, +.rst-content table>caption .fa-pull-right.headerlink, +.rst-content tt.download span.fa-pull-right:first-child, +.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand, +.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand, +.wy-menu-vertical li button.fa-pull-right.toctree-expand { + margin-left: .3em +} + +.pull-right { + float: right +} + +.pull-left { + float: left +} + +.fa.pull-left, +.pull-left.icon, +.rst-content .code-block-caption .pull-left.headerlink, +.rst-content .eqno .pull-left.headerlink, +.rst-content .pull-left.admonition-title, +.rst-content code.download span.pull-left:first-child, +.rst-content dl dt .pull-left.headerlink, +.rst-content h1 .pull-left.headerlink, +.rst-content h2 .pull-left.headerlink, +.rst-content h3 .pull-left.headerlink, +.rst-content h4 .pull-left.headerlink, +.rst-content h5 .pull-left.headerlink, +.rst-content h6 .pull-left.headerlink, +.rst-content p .pull-left.headerlink, +.rst-content table>caption .pull-left.headerlink, +.rst-content tt.download span.pull-left:first-child, +.wy-menu-vertical li.current>a button.pull-left.toctree-expand, +.wy-menu-vertical li.on a button.pull-left.toctree-expand, +.wy-menu-vertical li button.pull-left.toctree-expand { + margin-right: .3em +} + +.fa.pull-right, +.pull-right.icon, +.rst-content .code-block-caption .pull-right.headerlink, +.rst-content .eqno .pull-right.headerlink, +.rst-content .pull-right.admonition-title, +.rst-content code.download span.pull-right:first-child, +.rst-content dl dt .pull-right.headerlink, +.rst-content h1 .pull-right.headerlink, +.rst-content h2 .pull-right.headerlink, +.rst-content h3 .pull-right.headerlink, +.rst-content h4 .pull-right.headerlink, +.rst-content h5 .pull-right.headerlink, +.rst-content h6 .pull-right.headerlink, +.rst-content p .pull-right.headerlink, +.rst-content table>caption .pull-right.headerlink, +.rst-content tt.download span.pull-right:first-child, +.wy-menu-vertical li.current>a button.pull-right.toctree-expand, +.wy-menu-vertical li.on a button.pull-right.toctree-expand, +.wy-menu-vertical li button.pull-right.toctree-expand { + margin-left: .3em +} + +.fa-spin { + -webkit-animation: fa-spin 2s linear infinite; + animation: fa-spin 2s linear infinite +} + +.fa-pulse { + -webkit-animation: fa-spin 1s steps(8) infinite; + animation: fa-spin 1s steps(8) infinite +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) + } + + to { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) + } + + to { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg) +} + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg) +} + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg) +} + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scaleX(-1); + -ms-transform: scaleX(-1); + transform: scaleX(-1) +} + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scaleY(-1); + -ms-transform: scaleY(-1); + transform: scaleY(-1) +} + +:root .fa-flip-horizontal, +:root .fa-flip-vertical, +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270 { + filter: none +} + +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle +} + +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center +} + +.fa-stack-1x { + line-height: inherit +} + +.fa-stack-2x { + font-size: 2em +} + +.fa-inverse { + color: #fff +} + +.fa-glass:before { + content: "" +} + +.fa-music:before { + content: "" +} + +.fa-search:before, +.icon-search:before { + content: "" +} + +.fa-envelope-o:before { + content: "" +} + +.fa-heart:before { + content: "" +} + +.fa-star:before { + content: "" +} + +.fa-star-o:before { + content: "" +} + +.fa-user:before { + content: "" +} + +.fa-film:before { + content: "" +} + +.fa-th-large:before { + content: "" +} + +.fa-th:before { + content: "" +} + +.fa-th-list:before { + content: "" +} + +.fa-check:before { + content: "" +} + +.fa-close:before, +.fa-remove:before, +.fa-times:before { + content: "" +} + +.fa-search-plus:before { + content: "" +} + +.fa-search-minus:before { + content: "" +} + +.fa-power-off:before { + content: "" +} + +.fa-signal:before { + content: "" +} + +.fa-cog:before, +.fa-gear:before { + content: "" +} + +.fa-trash-o:before { + content: "" +} + +.fa-home:before, +.icon-home:before { + content: "" +} + +.fa-file-o:before { + content: "" +} + +.fa-clock-o:before { + content: "" +} + +.fa-road:before { + content: "" +} + +.fa-download:before, +.rst-content code.download span:first-child:before, +.rst-content tt.download span:first-child:before { + content: "" +} + +.fa-arrow-circle-o-down:before { + content: "" +} + +.fa-arrow-circle-o-up:before { + content: "" +} + +.fa-inbox:before { + content: "" +} + +.fa-play-circle-o:before { + content: "" +} + +.fa-repeat:before, +.fa-rotate-right:before { + content: "" +} + +.fa-refresh:before { + content: "" +} + +.fa-list-alt:before { + content: "" +} + +.fa-lock:before { + content: "" +} + +.fa-flag:before { + content: "" +} + +.fa-headphones:before { + content: "" +} + +.fa-volume-off:before { + content: "" +} + +.fa-volume-down:before { + content: "" +} + +.fa-volume-up:before { + content: "" +} + +.fa-qrcode:before { + content: "" +} + +.fa-barcode:before { + content: "" +} + +.fa-tag:before { + content: "" +} + +.fa-tags:before { + content: "" +} + +.fa-book:before, +.icon-book:before { + content: "" +} + +.fa-bookmark:before { + content: "" +} + +.fa-print:before { + content: "" +} + +.fa-camera:before { + content: "" +} + +.fa-font:before { + content: "" +} + +.fa-bold:before { + content: "" +} + +.fa-italic:before { + content: "" +} + +.fa-text-height:before { + content: "" +} + +.fa-text-width:before { + content: "" +} + +.fa-align-left:before { + content: "" +} + +.fa-align-center:before { + content: "" +} + +.fa-align-right:before { + content: "" +} + +.fa-align-justify:before { + content: "" +} + +.fa-list:before { + content: "" +} + +.fa-dedent:before, +.fa-outdent:before { + content: "" +} + +.fa-indent:before { + content: "" +} + +.fa-video-camera:before { + content: "" +} + +.fa-image:before, +.fa-photo:before, +.fa-picture-o:before { + content: "" +} + +.fa-pencil:before { + content: "" +} + +.fa-map-marker:before { + content: "" +} + +.fa-adjust:before { + content: "" +} + +.fa-tint:before { + content: "" +} + +.fa-edit:before, +.fa-pencil-square-o:before { + content: "" +} + +.fa-share-square-o:before { + content: "" +} + +.fa-check-square-o:before { + content: "" +} + +.fa-arrows:before { + content: "" +} + +.fa-step-backward:before { + content: "" +} + +.fa-fast-backward:before { + content: "" +} + +.fa-backward:before { + content: "" +} + +.fa-play:before { + content: "" +} + +.fa-pause:before { + content: "" +} + +.fa-stop:before { + content: "" +} + +.fa-forward:before { + content: "" +} + +.fa-fast-forward:before { + content: "" +} + +.fa-step-forward:before { + content: "" +} + +.fa-eject:before { + content: "" +} + +.fa-chevron-left:before { + content: "" +} + +.fa-chevron-right:before { + content: "" +} + +.fa-plus-circle:before { + content: "" +} + +.fa-minus-circle:before { + content: "" +} + +.fa-times-circle:before, +.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before { + content: "" +} + +.fa-check-circle:before, +.wy-inline-validate.wy-inline-validate-success .wy-input-context:before { + content: "" +} + +.fa-question-circle:before { + content: "" +} + +.fa-info-circle:before { + content: "" +} + +.fa-crosshairs:before { + content: "" +} + +.fa-times-circle-o:before { + content: "" +} + +.fa-check-circle-o:before { + content: "" +} + +.fa-ban:before { + content: "" +} + +.fa-arrow-left:before { + content: "" +} + +.fa-arrow-right:before { + content: "" +} + +.fa-arrow-up:before { + content: "" +} + +.fa-arrow-down:before { + content: "" +} + +.fa-mail-forward:before, +.fa-share:before { + content: "" +} + +.fa-expand:before { + content: "" +} + +.fa-compress:before { + content: "" +} + +.fa-plus:before { + content: "" +} + +.fa-minus:before { + content: "" +} + +.fa-asterisk:before { + content: "" +} + +.fa-exclamation-circle:before, +.rst-content .admonition-title:before, +.wy-inline-validate.wy-inline-validate-info .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before { + content: "" +} + +.fa-gift:before { + content: "" +} + +.fa-leaf:before { + content: "" +} + +.fa-fire:before, +.icon-fire:before { + content: "" +} + +.fa-eye:before { + content: "" +} + +.fa-eye-slash:before { + content: "" +} + +.fa-exclamation-triangle:before, +.fa-warning:before { + content: "" +} + +.fa-plane:before { + content: "" +} + +.fa-calendar:before { + content: "" +} + +.fa-random:before { + content: "" +} + +.fa-comment:before { + content: "" +} + +.fa-magnet:before { + content: "" +} + +.fa-chevron-up:before { + content: "" +} + +.fa-chevron-down:before { + content: "" +} + +.fa-retweet:before { + content: "" +} + +.fa-shopping-cart:before { + content: "" +} + +.fa-folder:before { + content: "" +} + +.fa-folder-open:before { + content: "" +} + +.fa-arrows-v:before { + content: "" +} + +.fa-arrows-h:before { + content: "" +} + +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "" +} + +.fa-twitter-square:before { + content: "" +} + +.fa-facebook-square:before { + content: "" +} + +.fa-camera-retro:before { + content: "" +} + +.fa-key:before { + content: "" +} + +.fa-cogs:before, +.fa-gears:before { + content: "" +} + +.fa-comments:before { + content: "" +} + +.fa-thumbs-o-up:before { + content: "" +} + +.fa-thumbs-o-down:before { + content: "" +} + +.fa-star-half:before { + content: "" +} + +.fa-heart-o:before { + content: "" +} + +.fa-sign-out:before { + content: "" +} + +.fa-linkedin-square:before { + content: "" +} + +.fa-thumb-tack:before { + content: "" +} + +.fa-external-link:before { + content: "" +} + +.fa-sign-in:before { + content: "" +} + +.fa-trophy:before { + content: "" +} + +.fa-github-square:before { + content: "" +} + +.fa-upload:before { + content: "" +} + +.fa-lemon-o:before { + content: "" +} + +.fa-phone:before { + content: "" +} + +.fa-square-o:before { + content: "" +} + +.fa-bookmark-o:before { + content: "" +} + +.fa-phone-square:before { + content: "" +} + +.fa-twitter:before { + content: "" +} + +.fa-facebook-f:before, +.fa-facebook:before { + content: "" +} + +.fa-github:before, +.icon-github:before { + content: "" +} + +.fa-unlock:before { + content: "" +} + +.fa-credit-card:before { + content: "" +} + +.fa-feed:before, +.fa-rss:before { + content: "" +} + +.fa-hdd-o:before { + content: "" +} + +.fa-bullhorn:before { + content: "" +} + +.fa-bell:before { + content: "" +} + +.fa-certificate:before { + content: "" +} + +.fa-hand-o-right:before { + content: "" +} + +.fa-hand-o-left:before { + content: "" +} + +.fa-hand-o-up:before { + content: "" +} + +.fa-hand-o-down:before { + content: "" +} + +.fa-arrow-circle-left:before, +.icon-circle-arrow-left:before { + content: "" +} + +.fa-arrow-circle-right:before, +.icon-circle-arrow-right:before { + content: "" +} + +.fa-arrow-circle-up:before { + content: "" +} + +.fa-arrow-circle-down:before { + content: "" +} + +.fa-globe:before { + content: "" +} + +.fa-wrench:before { + content: "" +} + +.fa-tasks:before { + content: "" +} + +.fa-filter:before { + content: "" +} + +.fa-briefcase:before { + content: "" +} + +.fa-arrows-alt:before { + content: "" +} + +.fa-group:before, +.fa-users:before { + content: "" +} + +.fa-chain:before, +.fa-link:before, +.icon-link:before { + content: "" +} + +.fa-cloud:before { + content: "" +} + +.fa-flask:before { + content: "" +} + +.fa-cut:before, +.fa-scissors:before { + content: "" +} + +.fa-copy:before, +.fa-files-o:before { + content: "" +} + +.fa-paperclip:before { + content: "" +} + +.fa-floppy-o:before, +.fa-save:before { + content: "" +} + +.fa-square:before { + content: "" +} + +.fa-bars:before, +.fa-navicon:before, +.fa-reorder:before { + content: "" +} + +.fa-list-ul:before { + content: "" +} + +.fa-list-ol:before { + content: "" +} + +.fa-strikethrough:before { + content: "" +} + +.fa-underline:before { + content: "" +} + +.fa-table:before { + content: "" +} + +.fa-magic:before { + content: "" +} + +.fa-truck:before { + content: "" +} + +.fa-pinterest:before { + content: "" +} + +.fa-pinterest-square:before { + content: "" +} + +.fa-google-plus-square:before { + content: "" +} + +.fa-google-plus:before { + content: "" +} + +.fa-money:before { + content: "" +} + +.fa-caret-down:before, +.icon-caret-down:before, +.wy-dropdown .caret:before { + content: "" +} + +.fa-caret-up:before { + content: "" +} + +.fa-caret-left:before { + content: "" +} + +.fa-caret-right:before { + content: "" +} + +.fa-columns:before { + content: "" +} + +.fa-sort:before, +.fa-unsorted:before { + content: "" +} + +.fa-sort-desc:before, +.fa-sort-down:before { + content: "" +} + +.fa-sort-asc:before, +.fa-sort-up:before { + content: "" +} + +.fa-envelope:before { + content: "" +} + +.fa-linkedin:before { + content: "" +} + +.fa-rotate-left:before, +.fa-undo:before { + content: "" +} + +.fa-gavel:before, +.fa-legal:before { + content: "" +} + +.fa-dashboard:before, +.fa-tachometer:before { + content: "" +} + +.fa-comment-o:before { + content: "" +} + +.fa-comments-o:before { + content: "" +} + +.fa-bolt:before, +.fa-flash:before { + content: "" +} + +.fa-sitemap:before { + content: "" +} + +.fa-umbrella:before { + content: "" +} + +.fa-clipboard:before, +.fa-paste:before { + content: "" +} + +.fa-lightbulb-o:before { + content: "" +} + +.fa-exchange:before { + content: "" +} + +.fa-cloud-download:before { + content: "" +} + +.fa-cloud-upload:before { + content: "" +} + +.fa-user-md:before { + content: "" +} + +.fa-stethoscope:before { + content: "" +} + +.fa-suitcase:before { + content: "" +} + +.fa-bell-o:before { + content: "" +} + +.fa-coffee:before { + content: "" +} + +.fa-cutlery:before { + content: "" +} + +.fa-file-text-o:before { + content: "" +} + +.fa-building-o:before { + content: "" +} + +.fa-hospital-o:before { + content: "" +} + +.fa-ambulance:before { + content: "" +} + +.fa-medkit:before { + content: "" +} + +.fa-fighter-jet:before { + content: "" +} + +.fa-beer:before { + content: "" +} + +.fa-h-square:before { + content: "" +} + +.fa-plus-square:before { + content: "" +} + +.fa-angle-double-left:before { + content: "" +} + +.fa-angle-double-right:before { + content: "" +} + +.fa-angle-double-up:before { + content: "" +} + +.fa-angle-double-down:before { + content: "" +} + +.fa-angle-left:before { + content: "" +} + +.fa-angle-right:before { + content: "" +} + +.fa-angle-up:before { + content: "" +} + +.fa-angle-down:before { + content: "" +} + +.fa-desktop:before { + content: "" +} + +.fa-laptop:before { + content: "" +} + +.fa-tablet:before { + content: "" +} + +.fa-mobile-phone:before, +.fa-mobile:before { + content: "" +} + +.fa-circle-o:before { + content: "" +} + +.fa-quote-left:before { + content: "" +} + +.fa-quote-right:before { + content: "" +} + +.fa-spinner:before { + content: "" +} + +.fa-circle:before { + content: "" +} + +.fa-mail-reply:before, +.fa-reply:before { + content: "" +} + +.fa-github-alt:before { + content: "" +} + +.fa-folder-o:before { + content: "" +} + +.fa-folder-open-o:before { + content: "" +} + +.fa-smile-o:before { + content: "" +} + +.fa-frown-o:before { + content: "" +} + +.fa-meh-o:before { + content: "" +} + +.fa-gamepad:before { + content: "" +} + +.fa-keyboard-o:before { + content: "" +} + +.fa-flag-o:before { + content: "" +} + +.fa-flag-checkered:before { + content: "" +} + +.fa-terminal:before { + content: "" +} + +.fa-code:before { + content: "" +} + +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "" +} + +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "" +} + +.fa-location-arrow:before { + content: "" +} + +.fa-crop:before { + content: "" +} + +.fa-code-fork:before { + content: "" +} + +.fa-chain-broken:before, +.fa-unlink:before { + content: "" +} + +.fa-question:before { + content: "" +} + +.fa-info:before { + content: "" +} + +.fa-exclamation:before { + content: "" +} + +.fa-superscript:before { + content: "" +} + +.fa-subscript:before { + content: "" +} + +.fa-eraser:before { + content: "" +} + +.fa-puzzle-piece:before { + content: "" +} + +.fa-microphone:before { + content: "" +} + +.fa-microphone-slash:before { + content: "" +} + +.fa-shield:before { + content: "" +} + +.fa-calendar-o:before { + content: "" +} + +.fa-fire-extinguisher:before { + content: "" +} + +.fa-rocket:before { + content: "" +} + +.fa-maxcdn:before { + content: "" +} + +.fa-chevron-circle-left:before { + content: "" +} + +.fa-chevron-circle-right:before { + content: "" +} + +.fa-chevron-circle-up:before { + content: "" +} + +.fa-chevron-circle-down:before { + content: "" +} + +.fa-html5:before { + content: "" +} + +.fa-css3:before { + content: "" +} + +.fa-anchor:before { + content: "" +} + +.fa-unlock-alt:before { + content: "" +} + +.fa-bullseye:before { + content: "" +} + +.fa-ellipsis-h:before { + content: "" +} + +.fa-ellipsis-v:before { + content: "" +} + +.fa-rss-square:before { + content: "" +} + +.fa-play-circle:before { + content: "" +} + +.fa-ticket:before { + content: "" +} + +.fa-minus-square:before { + content: "" +} + +.fa-minus-square-o:before, +.wy-menu-vertical li.current>a button.toctree-expand:before, +.wy-menu-vertical li.on a button.toctree-expand:before { + content: "" +} + +.fa-level-up:before { + content: "" +} + +.fa-level-down:before { + content: "" +} + +.fa-check-square:before { + content: "" +} + +.fa-pencil-square:before { + content: "" +} + +.fa-external-link-square:before { + content: "" +} + +.fa-share-square:before { + content: "" +} + +.fa-compass:before { + content: "" +} + +.fa-caret-square-o-down:before, +.fa-toggle-down:before { + content: "" +} + +.fa-caret-square-o-up:before, +.fa-toggle-up:before { + content: "" +} + +.fa-caret-square-o-right:before, +.fa-toggle-right:before { + content: "" +} + +.fa-eur:before, +.fa-euro:before { + content: "" +} + +.fa-gbp:before { + content: "" +} + +.fa-dollar:before, +.fa-usd:before { + content: "" +} + +.fa-inr:before, +.fa-rupee:before { + content: "" +} + +.fa-cny:before, +.fa-jpy:before, +.fa-rmb:before, +.fa-yen:before { + content: "" +} + +.fa-rouble:before, +.fa-rub:before, +.fa-ruble:before { + content: "" +} + +.fa-krw:before, +.fa-won:before { + content: "" +} + +.fa-bitcoin:before, +.fa-btc:before { + content: "" +} + +.fa-file:before { + content: "" +} + +.fa-file-text:before { + content: "" +} + +.fa-sort-alpha-asc:before { + content: "" +} + +.fa-sort-alpha-desc:before { + content: "" +} + +.fa-sort-amount-asc:before { + content: "" +} + +.fa-sort-amount-desc:before { + content: "" +} + +.fa-sort-numeric-asc:before { + content: "" +} + +.fa-sort-numeric-desc:before { + content: "" +} + +.fa-thumbs-up:before { + content: "" +} + +.fa-thumbs-down:before { + content: "" +} + +.fa-youtube-square:before { + content: "" +} + +.fa-youtube:before { + content: "" +} + +.fa-xing:before { + content: "" +} + +.fa-xing-square:before { + content: "" +} + +.fa-youtube-play:before { + content: "" +} + +.fa-dropbox:before { + content: "" +} + +.fa-stack-overflow:before { + content: "" +} + +.fa-instagram:before { + content: "" +} + +.fa-flickr:before { + content: "" +} + +.fa-adn:before { + content: "" +} + +.fa-bitbucket:before, +.icon-bitbucket:before { + content: "" +} + +.fa-bitbucket-square:before { + content: "" +} + +.fa-tumblr:before { + content: "" +} + +.fa-tumblr-square:before { + content: "" +} + +.fa-long-arrow-down:before { + content: "" +} + +.fa-long-arrow-up:before { + content: "" +} + +.fa-long-arrow-left:before { + content: "" +} + +.fa-long-arrow-right:before { + content: "" +} + +.fa-apple:before { + content: "" +} + +.fa-windows:before { + content: "" +} + +.fa-android:before { + content: "" +} + +.fa-linux:before { + content: "" +} + +.fa-dribbble:before { + content: "" +} + +.fa-skype:before { + content: "" +} + +.fa-foursquare:before { + content: "" +} + +.fa-trello:before { + content: "" +} + +.fa-female:before { + content: "" +} + +.fa-male:before { + content: "" +} + +.fa-gittip:before, +.fa-gratipay:before { + content: "" +} + +.fa-sun-o:before { + content: "" +} + +.fa-moon-o:before { + content: "" +} + +.fa-archive:before { + content: "" +} + +.fa-bug:before { + content: "" +} + +.fa-vk:before { + content: "" +} + +.fa-weibo:before { + content: "" +} + +.fa-renren:before { + content: "" +} + +.fa-pagelines:before { + content: "" +} + +.fa-stack-exchange:before { + content: "" +} + +.fa-arrow-circle-o-right:before { + content: "" +} + +.fa-arrow-circle-o-left:before { + content: "" +} + +.fa-caret-square-o-left:before, +.fa-toggle-left:before { + content: "" +} + +.fa-dot-circle-o:before { + content: "" +} + +.fa-wheelchair:before { + content: "" +} + +.fa-vimeo-square:before { + content: "" +} + +.fa-try:before, +.fa-turkish-lira:before { + content: "" +} + +.fa-plus-square-o:before, +.wy-menu-vertical li button.toctree-expand:before { + content: "" +} + +.fa-space-shuttle:before { + content: "" +} + +.fa-slack:before { + content: "" +} + +.fa-envelope-square:before { + content: "" +} + +.fa-wordpress:before { + content: "" +} + +.fa-openid:before { + content: "" +} + +.fa-bank:before, +.fa-institution:before, +.fa-university:before { + content: "" +} + +.fa-graduation-cap:before, +.fa-mortar-board:before { + content: "" +} + +.fa-yahoo:before { + content: "" +} + +.fa-google:before { + content: "" +} + +.fa-reddit:before { + content: "" +} + +.fa-reddit-square:before { + content: "" +} + +.fa-stumbleupon-circle:before { + content: "" +} + +.fa-stumbleupon:before { + content: "" +} + +.fa-delicious:before { + content: "" +} + +.fa-digg:before { + content: "" +} + +.fa-pied-piper-pp:before { + content: "" +} + +.fa-pied-piper-alt:before { + content: "" +} + +.fa-drupal:before { + content: "" +} + +.fa-joomla:before { + content: "" +} + +.fa-language:before { + content: "" +} + +.fa-fax:before { + content: "" +} + +.fa-building:before { + content: "" +} + +.fa-child:before { + content: "" +} + +.fa-paw:before { + content: "" +} + +.fa-spoon:before { + content: "" +} + +.fa-cube:before { + content: "" +} + +.fa-cubes:before { + content: "" +} + +.fa-behance:before { + content: "" +} + +.fa-behance-square:before { + content: "" +} + +.fa-steam:before { + content: "" +} + +.fa-steam-square:before { + content: "" +} + +.fa-recycle:before { + content: "" +} + +.fa-automobile:before, +.fa-car:before { + content: "" +} + +.fa-cab:before, +.fa-taxi:before { + content: "" +} + +.fa-tree:before { + content: "" +} + +.fa-spotify:before { + content: "" +} + +.fa-deviantart:before { + content: "" +} + +.fa-soundcloud:before { + content: "" +} + +.fa-database:before { + content: "" +} + +.fa-file-pdf-o:before { + content: "" +} + +.fa-file-word-o:before { + content: "" +} + +.fa-file-excel-o:before { + content: "" +} + +.fa-file-powerpoint-o:before { + content: "" +} + +.fa-file-image-o:before, +.fa-file-photo-o:before, +.fa-file-picture-o:before { + content: "" +} + +.fa-file-archive-o:before, +.fa-file-zip-o:before { + content: "" +} + +.fa-file-audio-o:before, +.fa-file-sound-o:before { + content: "" +} + +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "" +} + +.fa-file-code-o:before { + content: "" +} + +.fa-vine:before { + content: "" +} + +.fa-codepen:before { + content: "" +} + +.fa-jsfiddle:before { + content: "" +} + +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-ring:before, +.fa-life-saver:before, +.fa-support:before { + content: "" +} + +.fa-circle-o-notch:before { + content: "" +} + +.fa-ra:before, +.fa-rebel:before, +.fa-resistance:before { + content: "" +} + +.fa-empire:before, +.fa-ge:before { + content: "" +} + +.fa-git-square:before { + content: "" +} + +.fa-git:before { + content: "" +} + +.fa-hacker-news:before, +.fa-y-combinator-square:before, +.fa-yc-square:before { + content: "" +} + +.fa-tencent-weibo:before { + content: "" +} + +.fa-qq:before { + content: "" +} + +.fa-wechat:before, +.fa-weixin:before { + content: "" +} + +.fa-paper-plane:before, +.fa-send:before { + content: "" +} + +.fa-paper-plane-o:before, +.fa-send-o:before { + content: "" +} + +.fa-history:before { + content: "" +} + +.fa-circle-thin:before { + content: "" +} + +.fa-header:before { + content: "" +} + +.fa-paragraph:before { + content: "" +} + +.fa-sliders:before { + content: "" +} + +.fa-share-alt:before { + content: "" +} + +.fa-share-alt-square:before { + content: "" +} + +.fa-bomb:before { + content: "" +} + +.fa-futbol-o:before, +.fa-soccer-ball-o:before { + content: "" +} + +.fa-tty:before { + content: "" +} + +.fa-binoculars:before { + content: "" +} + +.fa-plug:before { + content: "" +} + +.fa-slideshare:before { + content: "" +} + +.fa-twitch:before { + content: "" +} + +.fa-yelp:before { + content: "" +} + +.fa-newspaper-o:before { + content: "" +} + +.fa-wifi:before { + content: "" +} + +.fa-calculator:before { + content: "" +} + +.fa-paypal:before { + content: "" +} + +.fa-google-wallet:before { + content: "" +} + +.fa-cc-visa:before { + content: "" +} + +.fa-cc-mastercard:before { + content: "" +} + +.fa-cc-discover:before { + content: "" +} + +.fa-cc-amex:before { + content: "" +} + +.fa-cc-paypal:before { + content: "" +} + +.fa-cc-stripe:before { + content: "" +} + +.fa-bell-slash:before { + content: "" +} + +.fa-bell-slash-o:before { + content: "" +} + +.fa-trash:before { + content: "" +} + +.fa-copyright:before { + content: "" +} + +.fa-at:before { + content: "" +} + +.fa-eyedropper:before { + content: "" +} + +.fa-paint-brush:before { + content: "" +} + +.fa-birthday-cake:before { + content: "" +} + +.fa-area-chart:before { + content: "" +} + +.fa-pie-chart:before { + content: "" +} + +.fa-line-chart:before { + content: "" +} + +.fa-lastfm:before { + content: "" +} + +.fa-lastfm-square:before { + content: "" +} + +.fa-toggle-off:before { + content: "" +} + +.fa-toggle-on:before { + content: "" +} + +.fa-bicycle:before { + content: "" +} + +.fa-bus:before { + content: "" +} + +.fa-ioxhost:before { + content: "" +} + +.fa-angellist:before { + content: "" +} + +.fa-cc:before { + content: "" +} + +.fa-ils:before, +.fa-shekel:before, +.fa-sheqel:before { + content: "" +} + +.fa-meanpath:before { + content: "" +} + +.fa-buysellads:before { + content: "" +} + +.fa-connectdevelop:before { + content: "" +} + +.fa-dashcube:before { + content: "" +} + +.fa-forumbee:before { + content: "" +} + +.fa-leanpub:before { + content: "" +} + +.fa-sellsy:before { + content: "" +} + +.fa-shirtsinbulk:before { + content: "" +} + +.fa-simplybuilt:before { + content: "" +} + +.fa-skyatlas:before { + content: "" +} + +.fa-cart-plus:before { + content: "" +} + +.fa-cart-arrow-down:before { + content: "" +} + +.fa-diamond:before { + content: "" +} + +.fa-ship:before { + content: "" +} + +.fa-user-secret:before { + content: "" +} + +.fa-motorcycle:before { + content: "" +} + +.fa-street-view:before { + content: "" +} + +.fa-heartbeat:before { + content: "" +} + +.fa-venus:before { + content: "" +} + +.fa-mars:before { + content: "" +} + +.fa-mercury:before { + content: "" +} + +.fa-intersex:before, +.fa-transgender:before { + content: "" +} + +.fa-transgender-alt:before { + content: "" +} + +.fa-venus-double:before { + content: "" +} + +.fa-mars-double:before { + content: "" +} + +.fa-venus-mars:before { + content: "" +} + +.fa-mars-stroke:before { + content: "" +} + +.fa-mars-stroke-v:before { + content: "" +} + +.fa-mars-stroke-h:before { + content: "" +} + +.fa-neuter:before { + content: "" +} + +.fa-genderless:before { + content: "" +} + +.fa-facebook-official:before { + content: "" +} + +.fa-pinterest-p:before { + content: "" +} + +.fa-whatsapp:before { + content: "" +} + +.fa-server:before { + content: "" +} + +.fa-user-plus:before { + content: "" +} + +.fa-user-times:before { + content: "" +} + +.fa-bed:before, +.fa-hotel:before { + content: "" +} + +.fa-viacoin:before { + content: "" +} + +.fa-train:before { + content: "" +} + +.fa-subway:before { + content: "" +} + +.fa-medium:before { + content: "" +} + +.fa-y-combinator:before, +.fa-yc:before { + content: "" +} + +.fa-optin-monster:before { + content: "" +} + +.fa-opencart:before { + content: "" +} + +.fa-expeditedssl:before { + content: "" +} + +.fa-battery-4:before, +.fa-battery-full:before, +.fa-battery:before { + content: "" +} + +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "" +} + +.fa-battery-2:before, +.fa-battery-half:before { + content: "" +} + +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "" +} + +.fa-battery-0:before, +.fa-battery-empty:before { + content: "" +} + +.fa-mouse-pointer:before { + content: "" +} + +.fa-i-cursor:before { + content: "" +} + +.fa-object-group:before { + content: "" +} + +.fa-object-ungroup:before { + content: "" +} + +.fa-sticky-note:before { + content: "" +} + +.fa-sticky-note-o:before { + content: "" +} + +.fa-cc-jcb:before { + content: "" +} + +.fa-cc-diners-club:before { + content: "" +} + +.fa-clone:before { + content: "" +} + +.fa-balance-scale:before { + content: "" +} + +.fa-hourglass-o:before { + content: "" +} + +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "" +} + +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "" +} + +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "" +} + +.fa-hourglass:before { + content: "" +} + +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "" +} + +.fa-hand-paper-o:before, +.fa-hand-stop-o:before { + content: "" +} + +.fa-hand-scissors-o:before { + content: "" +} + +.fa-hand-lizard-o:before { + content: "" +} + +.fa-hand-spock-o:before { + content: "" +} + +.fa-hand-pointer-o:before { + content: "" +} + +.fa-hand-peace-o:before { + content: "" +} + +.fa-trademark:before { + content: "" +} + +.fa-registered:before { + content: "" +} + +.fa-creative-commons:before { + content: "" +} + +.fa-gg:before { + content: "" +} + +.fa-gg-circle:before { + content: "" +} + +.fa-tripadvisor:before { + content: "" +} + +.fa-odnoklassniki:before { + content: "" +} + +.fa-odnoklassniki-square:before { + content: "" +} + +.fa-get-pocket:before { + content: "" +} + +.fa-wikipedia-w:before { + content: "" +} + +.fa-safari:before { + content: "" +} + +.fa-chrome:before { + content: "" +} + +.fa-firefox:before { + content: "" +} + +.fa-opera:before { + content: "" +} + +.fa-internet-explorer:before { + content: "" +} + +.fa-television:before, +.fa-tv:before { + content: "" +} + +.fa-contao:before { + content: "" +} + +.fa-500px:before { + content: "" +} + +.fa-amazon:before { + content: "" +} + +.fa-calendar-plus-o:before { + content: "" +} + +.fa-calendar-minus-o:before { + content: "" +} + +.fa-calendar-times-o:before { + content: "" +} + +.fa-calendar-check-o:before { + content: "" +} + +.fa-industry:before { + content: "" +} + +.fa-map-pin:before { + content: "" +} + +.fa-map-signs:before { + content: "" +} + +.fa-map-o:before { + content: "" +} + +.fa-map:before { + content: "" +} + +.fa-commenting:before { + content: "" +} + +.fa-commenting-o:before { + content: "" +} + +.fa-houzz:before { + content: "" +} + +.fa-vimeo:before { + content: "" +} + +.fa-black-tie:before { + content: "" +} + +.fa-fonticons:before { + content: "" +} + +.fa-reddit-alien:before { + content: "" +} + +.fa-edge:before { + content: "" +} + +.fa-credit-card-alt:before { + content: "" +} + +.fa-codiepie:before { + content: "" +} + +.fa-modx:before { + content: "" +} + +.fa-fort-awesome:before { + content: "" +} + +.fa-usb:before { + content: "" +} + +.fa-product-hunt:before { + content: "" +} + +.fa-mixcloud:before { + content: "" +} + +.fa-scribd:before { + content: "" +} + +.fa-pause-circle:before { + content: "" +} + +.fa-pause-circle-o:before { + content: "" +} + +.fa-stop-circle:before { + content: "" +} + +.fa-stop-circle-o:before { + content: "" +} + +.fa-shopping-bag:before { + content: "" +} + +.fa-shopping-basket:before { + content: "" +} + +.fa-hashtag:before { + content: "" +} + +.fa-bluetooth:before { + content: "" +} + +.fa-bluetooth-b:before { + content: "" +} + +.fa-percent:before { + content: "" +} + +.fa-gitlab:before, +.icon-gitlab:before { + content: "" +} + +.fa-wpbeginner:before { + content: "" +} + +.fa-wpforms:before { + content: "" +} + +.fa-envira:before { + content: "" +} + +.fa-universal-access:before { + content: "" +} + +.fa-wheelchair-alt:before { + content: "" +} + +.fa-question-circle-o:before { + content: "" +} + +.fa-blind:before { + content: "" +} + +.fa-audio-description:before { + content: "" +} + +.fa-volume-control-phone:before { + content: "" +} + +.fa-braille:before { + content: "" +} + +.fa-assistive-listening-systems:before { + content: "" +} + +.fa-american-sign-language-interpreting:before, +.fa-asl-interpreting:before { + content: "" +} + +.fa-deaf:before, +.fa-deafness:before, +.fa-hard-of-hearing:before { + content: "" +} + +.fa-glide:before { + content: "" +} + +.fa-glide-g:before { + content: "" +} + +.fa-sign-language:before, +.fa-signing:before { + content: "" +} + +.fa-low-vision:before { + content: "" +} + +.fa-viadeo:before { + content: "" +} + +.fa-viadeo-square:before { + content: "" +} + +.fa-snapchat:before { + content: "" +} + +.fa-snapchat-ghost:before { + content: "" +} + +.fa-snapchat-square:before { + content: "" +} + +.fa-pied-piper:before { + content: "" +} + +.fa-first-order:before { + content: "" +} + +.fa-yoast:before { + content: "" +} + +.fa-themeisle:before { + content: "" +} + +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "" +} + +.fa-fa:before, +.fa-font-awesome:before { + content: "" +} + +.fa-handshake-o:before { + content: "" +} + +.fa-envelope-open:before { + content: "" +} + +.fa-envelope-open-o:before { + content: "" +} + +.fa-linode:before { + content: "" +} + +.fa-address-book:before { + content: "" +} + +.fa-address-book-o:before { + content: "" +} + +.fa-address-card:before, +.fa-vcard:before { + content: "" +} + +.fa-address-card-o:before, +.fa-vcard-o:before { + content: "" +} + +.fa-user-circle:before { + content: "" +} + +.fa-user-circle-o:before { + content: "" +} + +.fa-user-o:before { + content: "" +} + +.fa-id-badge:before { + content: "" +} + +.fa-drivers-license:before, +.fa-id-card:before { + content: "" +} + +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "" +} + +.fa-quora:before { + content: "" +} + +.fa-free-code-camp:before { + content: "" +} + +.fa-telegram:before { + content: "" +} + +.fa-thermometer-4:before, +.fa-thermometer-full:before, +.fa-thermometer:before { + content: "" +} + +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "" +} + +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "" +} + +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "" +} + +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "" +} + +.fa-shower:before { + content: "" +} + +.fa-bath:before, +.fa-bathtub:before, +.fa-s15:before { + content: "" +} + +.fa-podcast:before { + content: "" +} + +.fa-window-maximize:before { + content: "" +} + +.fa-window-minimize:before { + content: "" +} + +.fa-window-restore:before { + content: "" +} + +.fa-times-rectangle:before, +.fa-window-close:before { + content: "" +} + +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "" +} + +.fa-bandcamp:before { + content: "" +} + +.fa-grav:before { + content: "" +} + +.fa-etsy:before { + content: "" +} + +.fa-imdb:before { + content: "" +} + +.fa-ravelry:before { + content: "" +} + +.fa-eercast:before { + content: "" +} + +.fa-microchip:before { + content: "" +} + +.fa-snowflake-o:before { + content: "" +} + +.fa-superpowers:before { + content: "" +} + +.fa-wpexplorer:before { + content: "" +} + +.fa-meetup:before { + content: "" +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0 +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto +} + +.fa, +.icon, +.rst-content .admonition-title, +.rst-content .code-block-caption .headerlink, +.rst-content .eqno .headerlink, +.rst-content code.download span:first-child, +.rst-content dl dt .headerlink, +.rst-content h1 .headerlink, +.rst-content h2 .headerlink, +.rst-content h3 .headerlink, +.rst-content h4 .headerlink, +.rst-content h5 .headerlink, +.rst-content h6 .headerlink, +.rst-content p.caption .headerlink, +.rst-content p .headerlink, +.rst-content table>caption .headerlink, +.rst-content tt.download span:first-child, +.wy-dropdown .caret, +.wy-inline-validate.wy-inline-validate-danger .wy-input-context, +.wy-inline-validate.wy-inline-validate-info .wy-input-context, +.wy-inline-validate.wy-inline-validate-success .wy-input-context, +.wy-inline-validate.wy-inline-validate-warning .wy-input-context, +.wy-menu-vertical li.current>a button.toctree-expand, +.wy-menu-vertical li.on a button.toctree-expand, +.wy-menu-vertical li button.toctree-expand { + font-family: inherit +} + +.fa:before, +.icon:before, +.rst-content .admonition-title:before, +.rst-content .code-block-caption .headerlink:before, +.rst-content .eqno .headerlink:before, +.rst-content code.download span:first-child:before, +.rst-content dl dt .headerlink:before, +.rst-content h1 .headerlink:before, +.rst-content h2 .headerlink:before, +.rst-content h3 .headerlink:before, +.rst-content h4 .headerlink:before, +.rst-content h5 .headerlink:before, +.rst-content h6 .headerlink:before, +.rst-content p.caption .headerlink:before, +.rst-content p .headerlink:before, +.rst-content table>caption .headerlink:before, +.rst-content tt.download span:first-child:before, +.wy-dropdown .caret:before, +.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-info .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-success .wy-input-context:before, +.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, +.wy-menu-vertical li.current>a button.toctree-expand:before, +.wy-menu-vertical li.on a button.toctree-expand:before, +.wy-menu-vertical li button.toctree-expand:before { + font-family: FontAwesome; + display: inline-block; + font-style: normal; + font-weight: 400; + line-height: 1; + text-decoration: inherit +} + +.rst-content .code-block-caption a .headerlink, +.rst-content .eqno a .headerlink, +.rst-content a .admonition-title, +.rst-content code.download a span:first-child, +.rst-content dl dt a .headerlink, +.rst-content h1 a .headerlink, +.rst-content h2 a .headerlink, +.rst-content h3 a .headerlink, +.rst-content h4 a .headerlink, +.rst-content h5 a .headerlink, +.rst-content h6 a .headerlink, +.rst-content p.caption a .headerlink, +.rst-content p a .headerlink, +.rst-content table>caption a .headerlink, +.rst-content tt.download a span:first-child, +.wy-menu-vertical li.current>a button.toctree-expand, +.wy-menu-vertical li.on a button.toctree-expand, +.wy-menu-vertical li a button.toctree-expand, +a .fa, +a .icon, +a .rst-content .admonition-title, +a .rst-content .code-block-caption .headerlink, +a .rst-content .eqno .headerlink, +a .rst-content code.download span:first-child, +a .rst-content dl dt .headerlink, +a .rst-content h1 .headerlink, +a .rst-content h2 .headerlink, +a .rst-content h3 .headerlink, +a .rst-content h4 .headerlink, +a .rst-content h5 .headerlink, +a .rst-content h6 .headerlink, +a .rst-content p.caption .headerlink, +a .rst-content p .headerlink, +a .rst-content table>caption .headerlink, +a .rst-content tt.download span:first-child, +a .wy-menu-vertical li button.toctree-expand { + display: inline-block; + text-decoration: inherit +} + +.btn .fa, +.btn .icon, +.btn .rst-content .admonition-title, +.btn .rst-content .code-block-caption .headerlink, +.btn .rst-content .eqno .headerlink, +.btn .rst-content code.download span:first-child, +.btn .rst-content dl dt .headerlink, +.btn .rst-content h1 .headerlink, +.btn .rst-content h2 .headerlink, +.btn .rst-content h3 .headerlink, +.btn .rst-content h4 .headerlink, +.btn .rst-content h5 .headerlink, +.btn .rst-content h6 .headerlink, +.btn .rst-content p .headerlink, +.btn .rst-content table>caption .headerlink, +.btn .rst-content tt.download span:first-child, +.btn .wy-menu-vertical li.current>a button.toctree-expand, +.btn .wy-menu-vertical li.on a button.toctree-expand, +.btn .wy-menu-vertical li button.toctree-expand, +.nav .fa, +.nav .icon, +.nav .rst-content .admonition-title, +.nav .rst-content .code-block-caption .headerlink, +.nav .rst-content .eqno .headerlink, +.nav .rst-content code.download span:first-child, +.nav .rst-content dl dt .headerlink, +.nav .rst-content h1 .headerlink, +.nav .rst-content h2 .headerlink, +.nav .rst-content h3 .headerlink, +.nav .rst-content h4 .headerlink, +.nav .rst-content h5 .headerlink, +.nav .rst-content h6 .headerlink, +.nav .rst-content p .headerlink, +.nav .rst-content table>caption .headerlink, +.nav .rst-content tt.download span:first-child, +.nav .wy-menu-vertical li.current>a button.toctree-expand, +.nav .wy-menu-vertical li.on a button.toctree-expand, +.nav .wy-menu-vertical li button.toctree-expand, +.rst-content .btn .admonition-title, +.rst-content .code-block-caption .btn .headerlink, +.rst-content .code-block-caption .nav .headerlink, +.rst-content .eqno .btn .headerlink, +.rst-content .eqno .nav .headerlink, +.rst-content .nav .admonition-title, +.rst-content code.download .btn span:first-child, +.rst-content code.download .nav span:first-child, +.rst-content dl dt .btn .headerlink, +.rst-content dl dt .nav .headerlink, +.rst-content h1 .btn .headerlink, +.rst-content h1 .nav .headerlink, +.rst-content h2 .btn .headerlink, +.rst-content h2 .nav .headerlink, +.rst-content h3 .btn .headerlink, +.rst-content h3 .nav .headerlink, +.rst-content h4 .btn .headerlink, +.rst-content h4 .nav .headerlink, +.rst-content h5 .btn .headerlink, +.rst-content h5 .nav .headerlink, +.rst-content h6 .btn .headerlink, +.rst-content h6 .nav .headerlink, +.rst-content p .btn .headerlink, +.rst-content p .nav .headerlink, +.rst-content table>caption .btn .headerlink, +.rst-content table>caption .nav .headerlink, +.rst-content tt.download .btn span:first-child, +.rst-content tt.download .nav span:first-child, +.wy-menu-vertical li .btn button.toctree-expand, +.wy-menu-vertical li.current>a .btn button.toctree-expand, +.wy-menu-vertical li.current>a .nav button.toctree-expand, +.wy-menu-vertical li .nav button.toctree-expand, +.wy-menu-vertical li.on a .btn button.toctree-expand, +.wy-menu-vertical li.on a .nav button.toctree-expand { + display: inline +} + +.btn .fa-large.icon, +.btn .fa.fa-large, +.btn .rst-content .code-block-caption .fa-large.headerlink, +.btn .rst-content .eqno .fa-large.headerlink, +.btn .rst-content .fa-large.admonition-title, +.btn .rst-content code.download span.fa-large:first-child, +.btn .rst-content dl dt .fa-large.headerlink, +.btn .rst-content h1 .fa-large.headerlink, +.btn .rst-content h2 .fa-large.headerlink, +.btn .rst-content h3 .fa-large.headerlink, +.btn .rst-content h4 .fa-large.headerlink, +.btn .rst-content h5 .fa-large.headerlink, +.btn .rst-content h6 .fa-large.headerlink, +.btn .rst-content p .fa-large.headerlink, +.btn .rst-content table>caption .fa-large.headerlink, +.btn .rst-content tt.download span.fa-large:first-child, +.btn .wy-menu-vertical li button.fa-large.toctree-expand, +.nav .fa-large.icon, +.nav .fa.fa-large, +.nav .rst-content .code-block-caption .fa-large.headerlink, +.nav .rst-content .eqno .fa-large.headerlink, +.nav .rst-content .fa-large.admonition-title, +.nav .rst-content code.download span.fa-large:first-child, +.nav .rst-content dl dt .fa-large.headerlink, +.nav .rst-content h1 .fa-large.headerlink, +.nav .rst-content h2 .fa-large.headerlink, +.nav .rst-content h3 .fa-large.headerlink, +.nav .rst-content h4 .fa-large.headerlink, +.nav .rst-content h5 .fa-large.headerlink, +.nav .rst-content h6 .fa-large.headerlink, +.nav .rst-content p .fa-large.headerlink, +.nav .rst-content table>caption .fa-large.headerlink, +.nav .rst-content tt.download span.fa-large:first-child, +.nav .wy-menu-vertical li button.fa-large.toctree-expand, +.rst-content .btn .fa-large.admonition-title, +.rst-content .code-block-caption .btn .fa-large.headerlink, +.rst-content .code-block-caption .nav .fa-large.headerlink, +.rst-content .eqno .btn .fa-large.headerlink, +.rst-content .eqno .nav .fa-large.headerlink, +.rst-content .nav .fa-large.admonition-title, +.rst-content code.download .btn span.fa-large:first-child, +.rst-content code.download .nav span.fa-large:first-child, +.rst-content dl dt .btn .fa-large.headerlink, +.rst-content dl dt .nav .fa-large.headerlink, +.rst-content h1 .btn .fa-large.headerlink, +.rst-content h1 .nav .fa-large.headerlink, +.rst-content h2 .btn .fa-large.headerlink, +.rst-content h2 .nav .fa-large.headerlink, +.rst-content h3 .btn .fa-large.headerlink, +.rst-content h3 .nav .fa-large.headerlink, +.rst-content h4 .btn .fa-large.headerlink, +.rst-content h4 .nav .fa-large.headerlink, +.rst-content h5 .btn .fa-large.headerlink, +.rst-content h5 .nav .fa-large.headerlink, +.rst-content h6 .btn .fa-large.headerlink, +.rst-content h6 .nav .fa-large.headerlink, +.rst-content p .btn .fa-large.headerlink, +.rst-content p .nav .fa-large.headerlink, +.rst-content table>caption .btn .fa-large.headerlink, +.rst-content table>caption .nav .fa-large.headerlink, +.rst-content tt.download .btn span.fa-large:first-child, +.rst-content tt.download .nav span.fa-large:first-child, +.wy-menu-vertical li .btn button.fa-large.toctree-expand, +.wy-menu-vertical li .nav button.fa-large.toctree-expand { + line-height: .9em +} + +.btn .fa-spin.icon, +.btn .fa.fa-spin, +.btn .rst-content .code-block-caption .fa-spin.headerlink, +.btn .rst-content .eqno .fa-spin.headerlink, +.btn .rst-content .fa-spin.admonition-title, +.btn .rst-content code.download span.fa-spin:first-child, +.btn .rst-content dl dt .fa-spin.headerlink, +.btn .rst-content h1 .fa-spin.headerlink, +.btn .rst-content h2 .fa-spin.headerlink, +.btn .rst-content h3 .fa-spin.headerlink, +.btn .rst-content h4 .fa-spin.headerlink, +.btn .rst-content h5 .fa-spin.headerlink, +.btn .rst-content h6 .fa-spin.headerlink, +.btn .rst-content p .fa-spin.headerlink, +.btn .rst-content table>caption .fa-spin.headerlink, +.btn .rst-content tt.download span.fa-spin:first-child, +.btn .wy-menu-vertical li button.fa-spin.toctree-expand, +.nav .fa-spin.icon, +.nav .fa.fa-spin, +.nav .rst-content .code-block-caption .fa-spin.headerlink, +.nav .rst-content .eqno .fa-spin.headerlink, +.nav .rst-content .fa-spin.admonition-title, +.nav .rst-content code.download span.fa-spin:first-child, +.nav .rst-content dl dt .fa-spin.headerlink, +.nav .rst-content h1 .fa-spin.headerlink, +.nav .rst-content h2 .fa-spin.headerlink, +.nav .rst-content h3 .fa-spin.headerlink, +.nav .rst-content h4 .fa-spin.headerlink, +.nav .rst-content h5 .fa-spin.headerlink, +.nav .rst-content h6 .fa-spin.headerlink, +.nav .rst-content p .fa-spin.headerlink, +.nav .rst-content table>caption .fa-spin.headerlink, +.nav .rst-content tt.download span.fa-spin:first-child, +.nav .wy-menu-vertical li button.fa-spin.toctree-expand, +.rst-content .btn .fa-spin.admonition-title, +.rst-content .code-block-caption .btn .fa-spin.headerlink, +.rst-content .code-block-caption .nav .fa-spin.headerlink, +.rst-content .eqno .btn .fa-spin.headerlink, +.rst-content .eqno .nav .fa-spin.headerlink, +.rst-content .nav .fa-spin.admonition-title, +.rst-content code.download .btn span.fa-spin:first-child, +.rst-content code.download .nav span.fa-spin:first-child, +.rst-content dl dt .btn .fa-spin.headerlink, +.rst-content dl dt .nav .fa-spin.headerlink, +.rst-content h1 .btn .fa-spin.headerlink, +.rst-content h1 .nav .fa-spin.headerlink, +.rst-content h2 .btn .fa-spin.headerlink, +.rst-content h2 .nav .fa-spin.headerlink, +.rst-content h3 .btn .fa-spin.headerlink, +.rst-content h3 .nav .fa-spin.headerlink, +.rst-content h4 .btn .fa-spin.headerlink, +.rst-content h4 .nav .fa-spin.headerlink, +.rst-content h5 .btn .fa-spin.headerlink, +.rst-content h5 .nav .fa-spin.headerlink, +.rst-content h6 .btn .fa-spin.headerlink, +.rst-content h6 .nav .fa-spin.headerlink, +.rst-content p .btn .fa-spin.headerlink, +.rst-content p .nav .fa-spin.headerlink, +.rst-content table>caption .btn .fa-spin.headerlink, +.rst-content table>caption .nav .fa-spin.headerlink, +.rst-content tt.download .btn span.fa-spin:first-child, +.rst-content tt.download .nav span.fa-spin:first-child, +.wy-menu-vertical li .btn button.fa-spin.toctree-expand, +.wy-menu-vertical li .nav button.fa-spin.toctree-expand { + display: inline-block +} + +.btn.fa:before, +.btn.icon:before, +.rst-content .btn.admonition-title:before, +.rst-content .code-block-caption .btn.headerlink:before, +.rst-content .eqno .btn.headerlink:before, +.rst-content code.download span.btn:first-child:before, +.rst-content dl dt .btn.headerlink:before, +.rst-content h1 .btn.headerlink:before, +.rst-content h2 .btn.headerlink:before, +.rst-content h3 .btn.headerlink:before, +.rst-content h4 .btn.headerlink:before, +.rst-content h5 .btn.headerlink:before, +.rst-content h6 .btn.headerlink:before, +.rst-content p .btn.headerlink:before, +.rst-content table>caption .btn.headerlink:before, +.rst-content tt.download span.btn:first-child:before, +.wy-menu-vertical li button.btn.toctree-expand:before { + opacity: .5; + -webkit-transition: opacity .05s ease-in; + -moz-transition: opacity .05s ease-in; + transition: opacity .05s ease-in +} + +.btn.fa:hover:before, +.btn.icon:hover:before, +.rst-content .btn.admonition-title:hover:before, +.rst-content .code-block-caption .btn.headerlink:hover:before, +.rst-content .eqno .btn.headerlink:hover:before, +.rst-content code.download span.btn:first-child:hover:before, +.rst-content dl dt .btn.headerlink:hover:before, +.rst-content h1 .btn.headerlink:hover:before, +.rst-content h2 .btn.headerlink:hover:before, +.rst-content h3 .btn.headerlink:hover:before, +.rst-content h4 .btn.headerlink:hover:before, +.rst-content h5 .btn.headerlink:hover:before, +.rst-content h6 .btn.headerlink:hover:before, +.rst-content p .btn.headerlink:hover:before, +.rst-content table>caption .btn.headerlink:hover:before, +.rst-content tt.download span.btn:first-child:hover:before, +.wy-menu-vertical li button.btn.toctree-expand:hover:before { + opacity: 1 +} + +.btn-mini .fa:before, +.btn-mini .icon:before, +.btn-mini .rst-content .admonition-title:before, +.btn-mini .rst-content .code-block-caption .headerlink:before, +.btn-mini .rst-content .eqno .headerlink:before, +.btn-mini .rst-content code.download span:first-child:before, +.btn-mini .rst-content dl dt .headerlink:before, +.btn-mini .rst-content h1 .headerlink:before, +.btn-mini .rst-content h2 .headerlink:before, +.btn-mini .rst-content h3 .headerlink:before, +.btn-mini .rst-content h4 .headerlink:before, +.btn-mini .rst-content h5 .headerlink:before, +.btn-mini .rst-content h6 .headerlink:before, +.btn-mini .rst-content p .headerlink:before, +.btn-mini .rst-content table>caption .headerlink:before, +.btn-mini .rst-content tt.download span:first-child:before, +.btn-mini .wy-menu-vertical li button.toctree-expand:before, +.rst-content .btn-mini .admonition-title:before, +.rst-content .code-block-caption .btn-mini .headerlink:before, +.rst-content .eqno .btn-mini .headerlink:before, +.rst-content code.download .btn-mini span:first-child:before, +.rst-content dl dt .btn-mini .headerlink:before, +.rst-content h1 .btn-mini .headerlink:before, +.rst-content h2 .btn-mini .headerlink:before, +.rst-content h3 .btn-mini .headerlink:before, +.rst-content h4 .btn-mini .headerlink:before, +.rst-content h5 .btn-mini .headerlink:before, +.rst-content h6 .btn-mini .headerlink:before, +.rst-content p .btn-mini .headerlink:before, +.rst-content table>caption .btn-mini .headerlink:before, +.rst-content tt.download .btn-mini span:first-child:before, +.wy-menu-vertical li .btn-mini button.toctree-expand:before { + font-size: 14px; + vertical-align: -15% +} + +.rst-content .admonition, +.rst-content .admonition-todo, +.rst-content .attention, +.rst-content .caution, +.rst-content .danger, +.rst-content .error, +.rst-content .hint, +.rst-content .important, +.rst-content .note, +.rst-content .seealso, +.rst-content .tip, +.rst-content .warning, +.wy-alert { + padding: 12px; + line-height: 24px; + margin-bottom: 24px; + background: #e7f2fa +} + +.rst-content .admonition-title, +.wy-alert-title { + font-weight: 700; + display: block; + color: #fff; + background: #6ab0de; + padding: 6px 12px; + margin: -12px -12px 12px +} + +.rst-content .danger, +.rst-content .error, +.rst-content .wy-alert-danger.admonition, +.rst-content .wy-alert-danger.admonition-todo, +.rst-content .wy-alert-danger.attention, +.rst-content .wy-alert-danger.caution, +.rst-content .wy-alert-danger.hint, +.rst-content .wy-alert-danger.important, +.rst-content .wy-alert-danger.note, +.rst-content .wy-alert-danger.seealso, +.rst-content .wy-alert-danger.tip, +.rst-content .wy-alert-danger.warning, +.wy-alert.wy-alert-danger { + background: #fdf3f2 +} + +.rst-content .danger .admonition-title, +.rst-content .danger .wy-alert-title, +.rst-content .error .admonition-title, +.rst-content .error .wy-alert-title, +.rst-content .wy-alert-danger.admonition-todo .admonition-title, +.rst-content .wy-alert-danger.admonition-todo .wy-alert-title, +.rst-content .wy-alert-danger.admonition .admonition-title, +.rst-content .wy-alert-danger.admonition .wy-alert-title, +.rst-content .wy-alert-danger.attention .admonition-title, +.rst-content .wy-alert-danger.attention .wy-alert-title, +.rst-content .wy-alert-danger.caution .admonition-title, +.rst-content .wy-alert-danger.caution .wy-alert-title, +.rst-content .wy-alert-danger.hint .admonition-title, +.rst-content .wy-alert-danger.hint .wy-alert-title, +.rst-content .wy-alert-danger.important .admonition-title, +.rst-content .wy-alert-danger.important .wy-alert-title, +.rst-content .wy-alert-danger.note .admonition-title, +.rst-content .wy-alert-danger.note .wy-alert-title, +.rst-content .wy-alert-danger.seealso .admonition-title, +.rst-content .wy-alert-danger.seealso .wy-alert-title, +.rst-content .wy-alert-danger.tip .admonition-title, +.rst-content .wy-alert-danger.tip .wy-alert-title, +.rst-content .wy-alert-danger.warning .admonition-title, +.rst-content .wy-alert-danger.warning .wy-alert-title, +.rst-content .wy-alert.wy-alert-danger .admonition-title, +.wy-alert.wy-alert-danger .rst-content .admonition-title, +.wy-alert.wy-alert-danger .wy-alert-title { + background: #f29f97 +} + +.rst-content .admonition-todo, +.rst-content .attention, +.rst-content .caution, +.rst-content .warning, +.rst-content .wy-alert-warning.admonition, +.rst-content .wy-alert-warning.danger, +.rst-content .wy-alert-warning.error, +.rst-content .wy-alert-warning.hint, +.rst-content .wy-alert-warning.important, +.rst-content .wy-alert-warning.note, +.rst-content .wy-alert-warning.seealso, +.rst-content .wy-alert-warning.tip, +.wy-alert.wy-alert-warning { + background: #ffedcc +} + +.rst-content .admonition-todo .admonition-title, +.rst-content .admonition-todo .wy-alert-title, +.rst-content .attention .admonition-title, +.rst-content .attention .wy-alert-title, +.rst-content .caution .admonition-title, +.rst-content .caution .wy-alert-title, +.rst-content .warning .admonition-title, +.rst-content .warning .wy-alert-title, +.rst-content .wy-alert-warning.admonition .admonition-title, +.rst-content .wy-alert-warning.admonition .wy-alert-title, +.rst-content .wy-alert-warning.danger .admonition-title, +.rst-content .wy-alert-warning.danger .wy-alert-title, +.rst-content .wy-alert-warning.error .admonition-title, +.rst-content .wy-alert-warning.error .wy-alert-title, +.rst-content .wy-alert-warning.hint .admonition-title, +.rst-content .wy-alert-warning.hint .wy-alert-title, +.rst-content .wy-alert-warning.important .admonition-title, +.rst-content .wy-alert-warning.important .wy-alert-title, +.rst-content .wy-alert-warning.note .admonition-title, +.rst-content .wy-alert-warning.note .wy-alert-title, +.rst-content .wy-alert-warning.seealso .admonition-title, +.rst-content .wy-alert-warning.seealso .wy-alert-title, +.rst-content .wy-alert-warning.tip .admonition-title, +.rst-content .wy-alert-warning.tip .wy-alert-title, +.rst-content .wy-alert.wy-alert-warning .admonition-title, +.wy-alert.wy-alert-warning .rst-content .admonition-title, +.wy-alert.wy-alert-warning .wy-alert-title { + background: #f0b37e +} + +.rst-content .note, +.rst-content .seealso, +.rst-content .wy-alert-info.admonition, +.rst-content .wy-alert-info.admonition-todo, +.rst-content .wy-alert-info.attention, +.rst-content .wy-alert-info.caution, +.rst-content .wy-alert-info.danger, +.rst-content .wy-alert-info.error, +.rst-content .wy-alert-info.hint, +.rst-content .wy-alert-info.important, +.rst-content .wy-alert-info.tip, +.rst-content .wy-alert-info.warning, +.wy-alert.wy-alert-info { + background: #e7f2fa +} + +.rst-content .note .admonition-title, +.rst-content .note .wy-alert-title, +.rst-content .seealso .admonition-title, +.rst-content .seealso .wy-alert-title, +.rst-content .wy-alert-info.admonition-todo .admonition-title, +.rst-content .wy-alert-info.admonition-todo .wy-alert-title, +.rst-content .wy-alert-info.admonition .admonition-title, +.rst-content .wy-alert-info.admonition .wy-alert-title, +.rst-content .wy-alert-info.attention .admonition-title, +.rst-content .wy-alert-info.attention .wy-alert-title, +.rst-content .wy-alert-info.caution .admonition-title, +.rst-content .wy-alert-info.caution .wy-alert-title, +.rst-content .wy-alert-info.danger .admonition-title, +.rst-content .wy-alert-info.danger .wy-alert-title, +.rst-content .wy-alert-info.error .admonition-title, +.rst-content .wy-alert-info.error .wy-alert-title, +.rst-content .wy-alert-info.hint .admonition-title, +.rst-content .wy-alert-info.hint .wy-alert-title, +.rst-content .wy-alert-info.important .admonition-title, +.rst-content .wy-alert-info.important .wy-alert-title, +.rst-content .wy-alert-info.tip .admonition-title, +.rst-content .wy-alert-info.tip .wy-alert-title, +.rst-content .wy-alert-info.warning .admonition-title, +.rst-content .wy-alert-info.warning .wy-alert-title, +.rst-content .wy-alert.wy-alert-info .admonition-title, +.wy-alert.wy-alert-info .rst-content .admonition-title, +.wy-alert.wy-alert-info .wy-alert-title { + background: #6ab0de +} + +.rst-content .hint, +.rst-content .important, +.rst-content .tip, +.rst-content .wy-alert-success.admonition, +.rst-content .wy-alert-success.admonition-todo, +.rst-content .wy-alert-success.attention, +.rst-content .wy-alert-success.caution, +.rst-content .wy-alert-success.danger, +.rst-content .wy-alert-success.error, +.rst-content .wy-alert-success.note, +.rst-content .wy-alert-success.seealso, +.rst-content .wy-alert-success.warning, +.wy-alert.wy-alert-success { + background: #dbfaf4 +} + +.rst-content .hint .admonition-title, +.rst-content .hint .wy-alert-title, +.rst-content .important .admonition-title, +.rst-content .important .wy-alert-title, +.rst-content .tip .admonition-title, +.rst-content .tip .wy-alert-title, +.rst-content .wy-alert-success.admonition-todo .admonition-title, +.rst-content .wy-alert-success.admonition-todo .wy-alert-title, +.rst-content .wy-alert-success.admonition .admonition-title, +.rst-content .wy-alert-success.admonition .wy-alert-title, +.rst-content .wy-alert-success.attention .admonition-title, +.rst-content .wy-alert-success.attention .wy-alert-title, +.rst-content .wy-alert-success.caution .admonition-title, +.rst-content .wy-alert-success.caution .wy-alert-title, +.rst-content .wy-alert-success.danger .admonition-title, +.rst-content .wy-alert-success.danger .wy-alert-title, +.rst-content .wy-alert-success.error .admonition-title, +.rst-content .wy-alert-success.error .wy-alert-title, +.rst-content .wy-alert-success.note .admonition-title, +.rst-content .wy-alert-success.note .wy-alert-title, +.rst-content .wy-alert-success.seealso .admonition-title, +.rst-content .wy-alert-success.seealso .wy-alert-title, +.rst-content .wy-alert-success.warning .admonition-title, +.rst-content .wy-alert-success.warning .wy-alert-title, +.rst-content .wy-alert.wy-alert-success .admonition-title, +.wy-alert.wy-alert-success .rst-content .admonition-title, +.wy-alert.wy-alert-success .wy-alert-title { + background: #1abc9c +} + +.rst-content .wy-alert-neutral.admonition, +.rst-content .wy-alert-neutral.admonition-todo, +.rst-content .wy-alert-neutral.attention, +.rst-content .wy-alert-neutral.caution, +.rst-content .wy-alert-neutral.danger, +.rst-content .wy-alert-neutral.error, +.rst-content .wy-alert-neutral.hint, +.rst-content .wy-alert-neutral.important, +.rst-content .wy-alert-neutral.note, +.rst-content .wy-alert-neutral.seealso, +.rst-content .wy-alert-neutral.tip, +.rst-content .wy-alert-neutral.warning, +.wy-alert.wy-alert-neutral { + background: #f3f6f6 +} + +.rst-content .wy-alert-neutral.admonition-todo .admonition-title, +.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, +.rst-content .wy-alert-neutral.admonition .admonition-title, +.rst-content .wy-alert-neutral.admonition .wy-alert-title, +.rst-content .wy-alert-neutral.attention .admonition-title, +.rst-content .wy-alert-neutral.attention .wy-alert-title, +.rst-content .wy-alert-neutral.caution .admonition-title, +.rst-content .wy-alert-neutral.caution .wy-alert-title, +.rst-content .wy-alert-neutral.danger .admonition-title, +.rst-content .wy-alert-neutral.danger .wy-alert-title, +.rst-content .wy-alert-neutral.error .admonition-title, +.rst-content .wy-alert-neutral.error .wy-alert-title, +.rst-content .wy-alert-neutral.hint .admonition-title, +.rst-content .wy-alert-neutral.hint .wy-alert-title, +.rst-content .wy-alert-neutral.important .admonition-title, +.rst-content .wy-alert-neutral.important .wy-alert-title, +.rst-content .wy-alert-neutral.note .admonition-title, +.rst-content .wy-alert-neutral.note .wy-alert-title, +.rst-content .wy-alert-neutral.seealso .admonition-title, +.rst-content .wy-alert-neutral.seealso .wy-alert-title, +.rst-content .wy-alert-neutral.tip .admonition-title, +.rst-content .wy-alert-neutral.tip .wy-alert-title, +.rst-content .wy-alert-neutral.warning .admonition-title, +.rst-content .wy-alert-neutral.warning .wy-alert-title, +.rst-content .wy-alert.wy-alert-neutral .admonition-title, +.wy-alert.wy-alert-neutral .rst-content .admonition-title, +.wy-alert.wy-alert-neutral .wy-alert-title { + color: #404040; + background: #e1e4e5 +} + +.rst-content .wy-alert-neutral.admonition-todo a, +.rst-content .wy-alert-neutral.admonition a, +.rst-content .wy-alert-neutral.attention a, +.rst-content .wy-alert-neutral.caution a, +.rst-content .wy-alert-neutral.danger a, +.rst-content .wy-alert-neutral.error a, +.rst-content .wy-alert-neutral.hint a, +.rst-content .wy-alert-neutral.important a, +.rst-content .wy-alert-neutral.note a, +.rst-content .wy-alert-neutral.seealso a, +.rst-content .wy-alert-neutral.tip a, +.rst-content .wy-alert-neutral.warning a, +.wy-alert.wy-alert-neutral a { + color: #2980b9 +} + +.rst-content .admonition-todo p:last-child, +.rst-content .admonition p:last-child, +.rst-content .attention p:last-child, +.rst-content .caution p:last-child, +.rst-content .danger p:last-child, +.rst-content .error p:last-child, +.rst-content .hint p:last-child, +.rst-content .important p:last-child, +.rst-content .note p:last-child, +.rst-content .seealso p:last-child, +.rst-content .tip p:last-child, +.rst-content .warning p:last-child, +.wy-alert p:last-child { + margin-bottom: 0 +} + +.wy-tray-container { + position: fixed; + bottom: 0; + left: 0; + z-index: 600 +} + +.wy-tray-container li { + display: block; + width: 300px; + background: transparent; + color: #fff; + text-align: center; + box-shadow: 0 5px 5px 0 rgba(0, 0, 0, .1); + padding: 0 24px; + min-width: 20%; + opacity: 0; + height: 0; + line-height: 56px; + overflow: hidden; + -webkit-transition: all .3s ease-in; + -moz-transition: all .3s ease-in; + transition: all .3s ease-in +} + +.wy-tray-container li.wy-tray-item-success { + background: #27ae60 +} + +.wy-tray-container li.wy-tray-item-info { + background: #2980b9 +} + +.wy-tray-container li.wy-tray-item-warning { + background: #e67e22 +} + +.wy-tray-container li.wy-tray-item-danger { + background: #e74c3c +} + +.wy-tray-container li.on { + opacity: 1; + height: 56px +} + +@media screen and (max-width:768px) { + .wy-tray-container { + bottom: auto; + top: 0; + width: 100% + } + + .wy-tray-container li { + width: 100% + } +} + +button { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; + cursor: pointer; + line-height: normal; + -webkit-appearance: button; + *overflow: visible +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0 +} + +button[disabled] { + cursor: default +} + +.btn { + display: inline-block; + border-radius: 2px; + line-height: normal; + white-space: nowrap; + text-align: center; + cursor: pointer; + font-size: 100%; + padding: 6px 12px 8px; + color: #fff; + border: 1px solid rgba(0, 0, 0, .1); + background-color: #27ae60; + text-decoration: none; + font-weight: 400; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + box-shadow: inset 0 1px 2px -1px hsla(0, 0%, 100%, .5), inset 0 -2px 0 0 rgba(0, 0, 0, .1); + outline-none: false; + vertical-align: middle; + *display: inline; + zoom: 1; + -webkit-user-drag: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-transition: all .1s linear; + -moz-transition: all .1s linear; + transition: all .1s linear +} + +.btn-hover { + background: #2e8ece; + color: #fff +} + +.btn:hover { + background: #2cc36b; + color: #fff +} + +.btn:focus { + background: #2cc36b; + outline: 0 +} + +.btn:active { + box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, .05), inset 0 2px 0 0 rgba(0, 0, 0, .1); + padding: 8px 12px 6px +} + +.btn:visited { + color: #fff +} + +.btn-disabled, +.btn-disabled:active, +.btn-disabled:focus, +.btn-disabled:hover, +.btn:disabled { + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + filter: alpha(opacity=40); + opacity: .4; + cursor: not-allowed; + box-shadow: none +} + +.btn::-moz-focus-inner { + padding: 0; + border: 0 +} + +.btn-small { + font-size: 80% +} + +.btn-info { + background-color: #2980b9 !important +} + +.btn-info:hover { + background-color: #2e8ece !important +} + +.btn-neutral { + background-color: #f3f6f6 !important; + color: #404040 !important +} + +.btn-neutral:hover { + background-color: #e5ebeb !important; + color: #404040 +} + +.btn-neutral:visited { + color: #404040 !important +} + +.btn-success { + background-color: #27ae60 !important +} + +.btn-success:hover { + background-color: #295 !important +} + +.btn-danger { + background-color: #e74c3c !important +} + +.btn-danger:hover { + background-color: #ea6153 !important +} + +.btn-warning { + background-color: #e67e22 !important +} + +.btn-warning:hover { + background-color: #e98b39 !important +} + +.btn-invert { + background-color: #222 +} + +.btn-invert:hover { + background-color: #2f2f2f !important +} + +.btn-link { + background-color: transparent !important; + color: #2980b9; + box-shadow: none; + border-color: transparent !important +} + +.btn-link:active, +.btn-link:hover { + background-color: transparent !important; + color: #409ad5 !important; + box-shadow: none +} + +.btn-link:visited { + color: #9b59b6 +} + +.wy-btn-group .btn, +.wy-control .btn { + vertical-align: middle +} + +.wy-btn-group { + margin-bottom: 24px; + *zoom: 1 +} + +.wy-btn-group:after, +.wy-btn-group:before { + display: table; + content: "" +} + +.wy-btn-group:after { + clear: both +} + +.wy-dropdown { + position: relative; + display: inline-block +} + +.wy-dropdown-active .wy-dropdown-menu { + display: block +} + +.wy-dropdown-menu { + position: absolute; + left: 0; + display: none; + float: left; + top: 100%; + min-width: 100%; + background: #fcfcfc; + z-index: 100; + border: 1px solid #cfd7dd; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .1); + padding: 12px +} + +.wy-dropdown-menu>dd>a { + display: block; + clear: both; + color: #404040; + white-space: nowrap; + font-size: 90%; + padding: 0 12px; + cursor: pointer +} + +.wy-dropdown-menu>dd>a:hover { + background: #2980b9; + color: #fff +} + +.wy-dropdown-menu>dd.divider { + border-top: 1px solid #cfd7dd; + margin: 6px 0 +} + +.wy-dropdown-menu>dd.search { + padding-bottom: 12px +} + +.wy-dropdown-menu>dd.search input[type=search] { + width: 100% +} + +.wy-dropdown-menu>dd.call-to-action { + background: #e3e3e3; + text-transform: uppercase; + font-weight: 500; + font-size: 80% +} + +.wy-dropdown-menu>dd.call-to-action:hover { + background: #e3e3e3 +} + +.wy-dropdown-menu>dd.call-to-action .btn { + color: #fff +} + +.wy-dropdown.wy-dropdown-up .wy-dropdown-menu { + bottom: 100%; + top: auto; + left: auto; + right: 0 +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { + background: #fcfcfc; + margin-top: 2px +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a { + padding: 6px 12px +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { + background: #2980b9; + color: #fff +} + +.wy-dropdown.wy-dropdown-left .wy-dropdown-menu { + right: 0; + left: auto; + text-align: right +} + +.wy-dropdown-arrow:before { + content: " "; + border-bottom: 5px solid #f5f5f5; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + position: absolute; + display: block; + top: -4px; + left: 50%; + margin-left: -3px +} + +.wy-dropdown-arrow.wy-dropdown-arrow-left:before { + left: 11px +} + +.wy-form-stacked select { + display: block +} + +.wy-form-aligned .wy-help-inline, +.wy-form-aligned input, +.wy-form-aligned label, +.wy-form-aligned select, +.wy-form-aligned textarea { + display: inline-block; + *display: inline; + *zoom: 1; + vertical-align: middle +} + +.wy-form-aligned .wy-control-group>label { + display: inline-block; + vertical-align: middle; + width: 10em; + margin: 6px 12px 0 0; + float: left +} + +.wy-form-aligned .wy-control { + float: left +} + +.wy-form-aligned .wy-control label { + display: block +} + +.wy-form-aligned .wy-control select { + margin-top: 6px +} + +fieldset { + margin: 0 +} + +fieldset, +legend { + border: 0; + padding: 0 +} + +legend { + width: 100%; + white-space: normal; + margin-bottom: 24px; + font-size: 150%; + *margin-left: -7px +} + +label, +legend { + display: block +} + +label { + margin: 0 0 .3125em; + color: #333; + font-size: 90% +} + +input, +select, +textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle +} + +.wy-control-group { + margin-bottom: 24px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + *zoom: 1 +} + +.wy-control-group:after, +.wy-control-group:before { + display: table; + content: "" +} + +.wy-control-group:after { + clear: both +} + +.wy-control-group.wy-control-group-required>label:after { + content: " *"; + color: #e74c3c +} + +.wy-control-group .wy-form-full, +.wy-control-group .wy-form-halves, +.wy-control-group .wy-form-thirds { + padding-bottom: 12px +} + +.wy-control-group .wy-form-full input[type=color], +.wy-control-group .wy-form-full input[type=date], +.wy-control-group .wy-form-full input[type=datetime-local], +.wy-control-group .wy-form-full input[type=datetime], +.wy-control-group .wy-form-full input[type=email], +.wy-control-group .wy-form-full input[type=month], +.wy-control-group .wy-form-full input[type=number], +.wy-control-group .wy-form-full input[type=password], +.wy-control-group .wy-form-full input[type=search], +.wy-control-group .wy-form-full input[type=tel], +.wy-control-group .wy-form-full input[type=text], +.wy-control-group .wy-form-full input[type=time], +.wy-control-group .wy-form-full input[type=url], +.wy-control-group .wy-form-full input[type=week], +.wy-control-group .wy-form-full select, +.wy-control-group .wy-form-halves input[type=color], +.wy-control-group .wy-form-halves input[type=date], +.wy-control-group .wy-form-halves input[type=datetime-local], +.wy-control-group .wy-form-halves input[type=datetime], +.wy-control-group .wy-form-halves input[type=email], +.wy-control-group .wy-form-halves input[type=month], +.wy-control-group .wy-form-halves input[type=number], +.wy-control-group .wy-form-halves input[type=password], +.wy-control-group .wy-form-halves input[type=search], +.wy-control-group .wy-form-halves input[type=tel], +.wy-control-group .wy-form-halves input[type=text], +.wy-control-group .wy-form-halves input[type=time], +.wy-control-group .wy-form-halves input[type=url], +.wy-control-group .wy-form-halves input[type=week], +.wy-control-group .wy-form-halves select, +.wy-control-group .wy-form-thirds input[type=color], +.wy-control-group .wy-form-thirds input[type=date], +.wy-control-group .wy-form-thirds input[type=datetime-local], +.wy-control-group .wy-form-thirds input[type=datetime], +.wy-control-group .wy-form-thirds input[type=email], +.wy-control-group .wy-form-thirds input[type=month], +.wy-control-group .wy-form-thirds input[type=number], +.wy-control-group .wy-form-thirds input[type=password], +.wy-control-group .wy-form-thirds input[type=search], +.wy-control-group .wy-form-thirds input[type=tel], +.wy-control-group .wy-form-thirds input[type=text], +.wy-control-group .wy-form-thirds input[type=time], +.wy-control-group .wy-form-thirds input[type=url], +.wy-control-group .wy-form-thirds input[type=week], +.wy-control-group .wy-form-thirds select { + width: 100% +} + +.wy-control-group .wy-form-full { + float: left; + display: block; + width: 100%; + margin-right: 0 +} + +.wy-control-group .wy-form-full:last-child { + margin-right: 0 +} + +.wy-control-group .wy-form-halves { + float: left; + display: block; + margin-right: 2.35765%; + width: 48.82117% +} + +.wy-control-group .wy-form-halves:last-child, +.wy-control-group .wy-form-halves:nth-of-type(2n) { + margin-right: 0 +} + +.wy-control-group .wy-form-halves:nth-of-type(odd) { + clear: left +} + +.wy-control-group .wy-form-thirds { + float: left; + display: block; + margin-right: 2.35765%; + width: 31.76157% +} + +.wy-control-group .wy-form-thirds:last-child, +.wy-control-group .wy-form-thirds:nth-of-type(3n) { + margin-right: 0 +} + +.wy-control-group .wy-form-thirds:nth-of-type(3n+1) { + clear: left +} + +.wy-control-group.wy-control-group-no-input .wy-control, +.wy-control-no-input { + margin: 6px 0 0; + font-size: 90% +} + +.wy-control-no-input { + display: inline-block +} + +.wy-control-group.fluid-input input[type=color], +.wy-control-group.fluid-input input[type=date], +.wy-control-group.fluid-input input[type=datetime-local], +.wy-control-group.fluid-input input[type=datetime], +.wy-control-group.fluid-input input[type=email], +.wy-control-group.fluid-input input[type=month], +.wy-control-group.fluid-input input[type=number], +.wy-control-group.fluid-input input[type=password], +.wy-control-group.fluid-input input[type=search], +.wy-control-group.fluid-input input[type=tel], +.wy-control-group.fluid-input input[type=text], +.wy-control-group.fluid-input input[type=time], +.wy-control-group.fluid-input input[type=url], +.wy-control-group.fluid-input input[type=week] { + width: 100% +} + +.wy-form-message-inline { + padding-left: .3em; + color: #666; + font-size: 90% +} + +.wy-form-message { + display: block; + color: #999; + font-size: 70%; + margin-top: .3125em; + font-style: italic +} + +.wy-form-message p { + font-size: inherit; + font-style: italic; + margin-bottom: 6px +} + +.wy-form-message p:last-child { + margin-bottom: 0 +} + +input { + line-height: normal +} + +input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + *overflow: visible +} + +input[type=color], +input[type=date], +input[type=datetime-local], +input[type=datetime], +input[type=email], +input[type=month], +input[type=number], +input[type=password], +input[type=search], +input[type=tel], +input[type=text], +input[type=time], +input[type=url], +input[type=week] { + -webkit-appearance: none; + padding: 6px; + display: inline-block; + border: 1px solid #ccc; + font-size: 80%; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 0; + -webkit-transition: border .3s linear; + -moz-transition: border .3s linear; + transition: border .3s linear +} + +input[type=datetime-local] { + padding: .34375em .625em +} + +input[disabled] { + cursor: default +} + +input[type=checkbox], +input[type=radio] { + padding: 0; + margin-right: .3125em; + *height: 13px; + *width: 13px +} + +input[type=checkbox], +input[type=radio], +input[type=search] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +input[type=color]:focus, +input[type=date]:focus, +input[type=datetime-local]:focus, +input[type=datetime]:focus, +input[type=email]:focus, +input[type=month]:focus, +input[type=number]:focus, +input[type=password]:focus, +input[type=search]:focus, +input[type=tel]:focus, +input[type=text]:focus, +input[type=time]:focus, +input[type=url]:focus, +input[type=week]:focus { + outline: 0; + outline: thin dotted\9; + border-color: #333 +} + +input.no-focus:focus { + border-color: #ccc !important +} + +input[type=checkbox]:focus, +input[type=file]:focus, +input[type=radio]:focus { + outline: thin dotted #333; + outline: 1px auto #129fea +} + +input[type=color][disabled], +input[type=date][disabled], +input[type=datetime-local][disabled], +input[type=datetime][disabled], +input[type=email][disabled], +input[type=month][disabled], +input[type=number][disabled], +input[type=password][disabled], +input[type=search][disabled], +input[type=tel][disabled], +input[type=text][disabled], +input[type=time][disabled], +input[type=url][disabled], +input[type=week][disabled] { + cursor: not-allowed; + background-color: #fafafa +} + +input:focus:invalid, +select:focus:invalid, +textarea:focus:invalid { + color: #e74c3c; + border: 1px solid #e74c3c +} + +input:focus:invalid:focus, +select:focus:invalid:focus, +textarea:focus:invalid:focus { + border-color: #e74c3c +} + +input[type=checkbox]:focus:invalid:focus, +input[type=file]:focus:invalid:focus, +input[type=radio]:focus:invalid:focus { + outline-color: #e74c3c +} + +input.wy-input-large { + padding: 12px; + font-size: 100% +} + +textarea { + overflow: auto; + vertical-align: top; + width: 100%; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif +} + +select, +textarea { + padding: .5em .625em; + display: inline-block; + border: 1px solid #ccc; + font-size: 80%; + box-shadow: inset 0 1px 3px #ddd; + -webkit-transition: border .3s linear; + -moz-transition: border .3s linear; + transition: border .3s linear +} + +select { + border: 1px solid #ccc; + background-color: #fff +} + +select[multiple] { + height: auto +} + +select:focus, +textarea:focus { + outline: 0 +} + +input[readonly], +select[disabled], +select[readonly], +textarea[disabled], +textarea[readonly] { + cursor: not-allowed; + background-color: #fafafa +} + +input[type=checkbox][disabled], +input[type=radio][disabled] { + cursor: not-allowed +} + +.wy-checkbox, +.wy-radio { + margin: 6px 0; + color: #404040; + display: block +} + +.wy-checkbox input, +.wy-radio input { + vertical-align: baseline +} + +.wy-form-message-inline { + display: inline-block; + *display: inline; + *zoom: 1; + vertical-align: middle +} + +.wy-input-prefix, +.wy-input-suffix { + white-space: nowrap; + padding: 6px +} + +.wy-input-prefix .wy-input-context, +.wy-input-suffix .wy-input-context { + line-height: 27px; + padding: 0 8px; + display: inline-block; + font-size: 80%; + background-color: #f3f6f6; + border: 1px solid #ccc; + color: #999 +} + +.wy-input-suffix .wy-input-context { + border-left: 0 +} + +.wy-input-prefix .wy-input-context { + border-right: 0 +} + +.wy-switch { + position: relative; + display: block; + height: 24px; + margin-top: 12px; + cursor: pointer +} + +.wy-switch:before { + left: 0; + top: 0; + width: 36px; + height: 12px; + background: #ccc +} + +.wy-switch:after, +.wy-switch:before { + position: absolute; + content: ""; + display: block; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -moz-transition: all .2s ease-in-out; + transition: all .2s ease-in-out +} + +.wy-switch:after { + width: 18px; + height: 18px; + background: #999; + left: -3px; + top: -3px +} + +.wy-switch span { + position: absolute; + left: 48px; + display: block; + font-size: 12px; + color: #ccc; + line-height: 1 +} + +.wy-switch.active:before { + background: #1e8449 +} + +.wy-switch.active:after { + left: 24px; + background: #27ae60 +} + +.wy-switch.disabled { + cursor: not-allowed; + opacity: .8 +} + +.wy-control-group.wy-control-group-error .wy-form-message, +.wy-control-group.wy-control-group-error>label { + color: #e74c3c +} + +.wy-control-group.wy-control-group-error input[type=color], +.wy-control-group.wy-control-group-error input[type=date], +.wy-control-group.wy-control-group-error input[type=datetime-local], +.wy-control-group.wy-control-group-error input[type=datetime], +.wy-control-group.wy-control-group-error input[type=email], +.wy-control-group.wy-control-group-error input[type=month], +.wy-control-group.wy-control-group-error input[type=number], +.wy-control-group.wy-control-group-error input[type=password], +.wy-control-group.wy-control-group-error input[type=search], +.wy-control-group.wy-control-group-error input[type=tel], +.wy-control-group.wy-control-group-error input[type=text], +.wy-control-group.wy-control-group-error input[type=time], +.wy-control-group.wy-control-group-error input[type=url], +.wy-control-group.wy-control-group-error input[type=week], +.wy-control-group.wy-control-group-error textarea { + border: 1px solid #e74c3c +} + +.wy-inline-validate { + white-space: nowrap +} + +.wy-inline-validate .wy-input-context { + padding: .5em .625em; + display: inline-block; + font-size: 80% +} + +.wy-inline-validate.wy-inline-validate-success .wy-input-context { + color: #27ae60 +} + +.wy-inline-validate.wy-inline-validate-danger .wy-input-context { + color: #e74c3c +} + +.wy-inline-validate.wy-inline-validate-warning .wy-input-context { + color: #e67e22 +} + +.wy-inline-validate.wy-inline-validate-info .wy-input-context { + color: #2980b9 +} + +.rotate-90 { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg) +} + +.rotate-180 { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg) +} + +.rotate-270 { + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -o-transform: rotate(270deg); + transform: rotate(270deg) +} + +.mirror { + -webkit-transform: scaleX(-1); + -moz-transform: scaleX(-1); + -ms-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1) +} + +.mirror.rotate-90 { + -webkit-transform: scaleX(-1) rotate(90deg); + -moz-transform: scaleX(-1) rotate(90deg); + -ms-transform: scaleX(-1) rotate(90deg); + -o-transform: scaleX(-1) rotate(90deg); + transform: scaleX(-1) rotate(90deg) +} + +.mirror.rotate-180 { + -webkit-transform: scaleX(-1) rotate(180deg); + -moz-transform: scaleX(-1) rotate(180deg); + -ms-transform: scaleX(-1) rotate(180deg); + -o-transform: scaleX(-1) rotate(180deg); + transform: scaleX(-1) rotate(180deg) +} + +.mirror.rotate-270 { + -webkit-transform: scaleX(-1) rotate(270deg); + -moz-transform: scaleX(-1) rotate(270deg); + -ms-transform: scaleX(-1) rotate(270deg); + -o-transform: scaleX(-1) rotate(270deg); + transform: scaleX(-1) rotate(270deg) +} + +@media only screen and (max-width:480px) { + .wy-form button[type=submit] { + margin: .7em 0 0 + } + + .wy-form input[type=color], + .wy-form input[type=date], + .wy-form input[type=datetime-local], + .wy-form input[type=datetime], + .wy-form input[type=email], + .wy-form input[type=month], + .wy-form input[type=number], + .wy-form input[type=password], + .wy-form input[type=search], + .wy-form input[type=tel], + .wy-form input[type=text], + .wy-form input[type=time], + .wy-form input[type=url], + .wy-form input[type=week], + .wy-form label { + margin-bottom: .3em; + display: block + } + + .wy-form input[type=color], + .wy-form input[type=date], + .wy-form input[type=datetime-local], + .wy-form input[type=datetime], + .wy-form input[type=email], + .wy-form input[type=month], + .wy-form input[type=number], + .wy-form input[type=password], + .wy-form input[type=search], + .wy-form input[type=tel], + .wy-form input[type=time], + .wy-form input[type=url], + .wy-form input[type=week] { + margin-bottom: 0 + } + + .wy-form-aligned .wy-control-group label { + margin-bottom: .3em; + text-align: left; + display: block; + width: 100% + } + + .wy-form-aligned .wy-control { + margin: 1.5em 0 0 + } + + .wy-form-message, + .wy-form-message-inline, + .wy-form .wy-help-inline { + display: block; + font-size: 80%; + padding: 6px 0 + } +} + +@media screen and (max-width:768px) { + .tablet-hide { + display: none + } +} + +@media screen and (max-width:480px) { + .mobile-hide { + display: none + } +} + +.float-left { + float: left +} + +.float-right { + float: right +} + +.full-width { + width: 100% +} + +.rst-content table.docutils, +.rst-content table.field-list, +.wy-table { + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; + margin-bottom: 24px +} + +.rst-content table.docutils caption, +.rst-content table.field-list caption, +.wy-table caption { + color: #000; + font: italic 85%/1 arial, sans-serif; + padding: 1em 0; + text-align: center +} + +.rst-content table.docutils td, +.rst-content table.docutils th, +.rst-content table.field-list td, +.rst-content table.field-list th, +.wy-table td, +.wy-table th { + font-size: 90%; + margin: 0; + overflow: visible; + padding: 8px 16px +} + +.rst-content table.docutils td:first-child, +.rst-content table.docutils th:first-child, +.rst-content table.field-list td:first-child, +.rst-content table.field-list th:first-child, +.wy-table td:first-child, +.wy-table th:first-child { + border-left-width: 0 +} + +.rst-content table.docutils thead, +.rst-content table.field-list thead, +.wy-table thead { + color: #000; + text-align: left; + vertical-align: bottom; + white-space: nowrap +} + +.rst-content table.docutils thead th, +.rst-content table.field-list thead th, +.wy-table thead th { + font-weight: 700; + border-bottom: 2px solid #e1e4e5 +} + +.rst-content table.docutils td, +.rst-content table.field-list td, +.wy-table td { + background-color: transparent; + vertical-align: middle +} + +.rst-content table.docutils td p, +.rst-content table.field-list td p, +.wy-table td p { + line-height: 18px +} + +.rst-content table.docutils td p:last-child, +.rst-content table.field-list td p:last-child, +.wy-table td p:last-child { + margin-bottom: 0 +} + +.rst-content table.docutils .wy-table-cell-min, +.rst-content table.field-list .wy-table-cell-min, +.wy-table .wy-table-cell-min { + width: 1%; + padding-right: 0 +} + +.rst-content table.docutils .wy-table-cell-min input[type=checkbox], +.rst-content table.field-list .wy-table-cell-min input[type=checkbox], +.wy-table .wy-table-cell-min input[type=checkbox] { + margin: 0 +} + +.wy-table-secondary { + color: grey; + font-size: 90% +} + +.wy-table-tertiary { + color: grey; + font-size: 80% +} + +.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td, +.wy-table-backed, +.wy-table-odd td, +.wy-table-striped tr:nth-child(2n-1) td { + background-color: #f3f6f6 +} + +.rst-content table.docutils, +.wy-table-bordered-all { + border: 1px solid #e1e4e5 +} + +.rst-content table.docutils td, +.wy-table-bordered-all td { + border-bottom: 1px solid #e1e4e5; + border-left: 1px solid #e1e4e5 +} + +.rst-content table.docutils tbody>tr:last-child td, +.wy-table-bordered-all tbody>tr:last-child td { + border-bottom-width: 0 +} + +.wy-table-bordered { + border: 1px solid #e1e4e5 +} + +.wy-table-bordered-rows td { + border-bottom: 1px solid #e1e4e5 +} + +.wy-table-bordered-rows tbody>tr:last-child td { + border-bottom-width: 0 +} + +.wy-table-horizontal td, +.wy-table-horizontal th { + border-width: 0 0 1px; + border-bottom: 1px solid #e1e4e5 +} + +.wy-table-horizontal tbody>tr:last-child td { + border-bottom-width: 0 +} + +.wy-table-responsive { + margin-bottom: 24px; + max-width: 100%; + overflow: auto +} + +.wy-table-responsive table { + margin-bottom: 0 !important +} + +.wy-table-responsive table td { + white-space: nowrap +} + +.wy-table-responsive table th { + white-space: nowrap +} + +a { + color: #2980b9; + text-decoration: none; + cursor: pointer +} + +a:hover { + color: #3091d1 +} + +a:visited { + color: #9b59b6 +} + +html { + height: 100% +} + +body, +html { + overflow-x: hidden +} + +body { + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + font-weight: 400; + color: #404040; + min-height: 100%; + background: #edf0f2 +} + +.wy-text-left { + text-align: left +} + +.wy-text-center { + text-align: center +} + +.wy-text-right { + text-align: right +} + +.wy-text-large { + font-size: 120% +} + +.wy-text-normal { + font-size: 100% +} + +.wy-text-small, +small { + font-size: 80% +} + +.wy-text-strike { + text-decoration: line-through +} + +.wy-text-warning { + color: #e67e22 !important +} + +a.wy-text-warning:hover { + color: #eb9950 !important +} + +.wy-text-info { + color: #2980b9 !important +} + +a.wy-text-info:hover { + color: #409ad5 !important +} + +.wy-text-success { + color: #27ae60 !important +} + +a.wy-text-success:hover { + color: #36d278 !important +} + +.wy-text-danger { + color: #e74c3c !important +} + +a.wy-text-danger:hover { + color: #ed7669 !important +} + +.wy-text-neutral { + color: #404040 !important +} + +a.wy-text-neutral:hover { + color: #595959 !important +} + +.rst-content .toctree-wrapper>p.caption, +h1, +h2, +h3, +h4, +h5, +h6, +legend { + margin-top: 0; + font-weight: 700; + font-family: Roboto Slab, ff-tisa-web-pro, Georgia, Arial, sans-serif +} + +p { + line-height: 24px; + font-size: 16px; + margin: 0 0 24px +} + +h1 { + font-size: 175% +} + +.rst-content .toctree-wrapper>p.caption, +h2 { + font-size: 150% +} + +h3 { + font-size: 125% +} + +h4 { + font-size: 115% +} + +h5 { + font-size: 110% +} + +h6 { + font-size: 100% +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #e1e4e5; + margin: 24px 0; + padding: 0 +} + +.rst-content code, +.rst-content tt, +code { + white-space: nowrap; + max-width: 100%; + background: #fff; + border: 1px solid #e1e4e5; + font-size: 75%; + padding: 0 5px; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + color: #e74c3c; + overflow-x: auto +} + +.rst-content tt.code-large, +code.code-large { + font-size: 90% +} + +.rst-content .section ul, +.rst-content .toctree-wrapper ul, +.rst-content section ul, +.wy-plain-list-disc, +article ul { + list-style: disc; + line-height: 24px; + margin-bottom: 24px +} + +.rst-content .section ul li, +.rst-content .toctree-wrapper ul li, +.rst-content section ul li, +.wy-plain-list-disc li, +article ul li { + list-style: disc; + margin-left: 24px +} + +.rst-content .section ul li p:last-child, +.rst-content .section ul li ul, +.rst-content .toctree-wrapper ul li p:last-child, +.rst-content .toctree-wrapper ul li ul, +.rst-content section ul li p:last-child, +.rst-content section ul li ul, +.wy-plain-list-disc li p:last-child, +.wy-plain-list-disc li ul, +article ul li p:last-child, +article ul li ul { + margin-bottom: 0 +} + +.rst-content .section ul li li, +.rst-content .toctree-wrapper ul li li, +.rst-content section ul li li, +.wy-plain-list-disc li li, +article ul li li { + list-style: circle +} + +.rst-content .section ul li li li, +.rst-content .toctree-wrapper ul li li li, +.rst-content section ul li li li, +.wy-plain-list-disc li li li, +article ul li li li { + list-style: square +} + +.rst-content .section ul li ol li, +.rst-content .toctree-wrapper ul li ol li, +.rst-content section ul li ol li, +.wy-plain-list-disc li ol li, +article ul li ol li { + list-style: decimal +} + +.rst-content .section ol, +.rst-content .section ol.arabic, +.rst-content .toctree-wrapper ol, +.rst-content .toctree-wrapper ol.arabic, +.rst-content section ol, +.rst-content section ol.arabic, +.wy-plain-list-decimal, +article ol { + list-style: decimal; + line-height: 24px; + margin-bottom: 24px +} + +.rst-content .section ol.arabic li, +.rst-content .section ol li, +.rst-content .toctree-wrapper ol.arabic li, +.rst-content .toctree-wrapper ol li, +.rst-content section ol.arabic li, +.rst-content section ol li, +.wy-plain-list-decimal li, +article ol li { + list-style: decimal; + margin-left: 24px +} + +.rst-content .section ol.arabic li ul, +.rst-content .section ol li p:last-child, +.rst-content .section ol li ul, +.rst-content .toctree-wrapper ol.arabic li ul, +.rst-content .toctree-wrapper ol li p:last-child, +.rst-content .toctree-wrapper ol li ul, +.rst-content section ol.arabic li ul, +.rst-content section ol li p:last-child, +.rst-content section ol li ul, +.wy-plain-list-decimal li p:last-child, +.wy-plain-list-decimal li ul, +article ol li p:last-child, +article ol li ul { + margin-bottom: 0 +} + +.rst-content .section ol.arabic li ul li, +.rst-content .section ol li ul li, +.rst-content .toctree-wrapper ol.arabic li ul li, +.rst-content .toctree-wrapper ol li ul li, +.rst-content section ol.arabic li ul li, +.rst-content section ol li ul li, +.wy-plain-list-decimal li ul li, +article ol li ul li { + list-style: disc +} + +.wy-breadcrumbs { + *zoom: 1 +} + +.wy-breadcrumbs:after, +.wy-breadcrumbs:before { + display: table; + content: "" +} + +.wy-breadcrumbs:after { + clear: both +} + +.wy-breadcrumbs>li { + display: inline-block; + padding-top: 5px +} + +.wy-breadcrumbs>li.wy-breadcrumbs-aside { + float: right; + padding-left: 5px; + font-size: 0.8em; +} + +.rst-content .wy-breadcrumbs>li code, +.rst-content .wy-breadcrumbs>li tt, +.wy-breadcrumbs>li .rst-content tt, +.wy-breadcrumbs>li code { + all: inherit; + color: inherit +} + +.breadcrumb-item:before { + content: "/"; + color: #bbb; + font-size: 13px; + padding: 0 6px 0 3px +} + +.wy-breadcrumbs-extra { + margin-bottom: 0; + color: #b3b3b3; + font-size: 80%; + display: inline-block +} + +@media screen and (max-width:480px) { + + .wy-breadcrumbs-extra, + .wy-breadcrumbs li.wy-breadcrumbs-aside { + display: none + } +} + +@media print { + .wy-breadcrumbs li.wy-breadcrumbs-aside { + display: none + } +} + +html { + font-size: 16px +} + +.wy-affix { + position: fixed; + top: 1.618em +} + +.wy-menu a:hover { + text-decoration: none +} + +.wy-menu-horiz { + *zoom: 1 +} + +.wy-menu-horiz:after, +.wy-menu-horiz:before { + display: table; + content: "" +} + +.wy-menu-horiz:after { + clear: both +} + +.wy-menu-horiz li, +.wy-menu-horiz ul { + display: inline-block +} + +.wy-menu-horiz li:hover { + background: hsla(0, 0%, 100%, .1) +} + +.wy-menu-horiz li.divide-left { + border-left: 1px solid #404040 +} + +.wy-menu-horiz li.divide-right { + border-right: 1px solid #404040 +} + +.wy-menu-horiz a { + height: 32px; + display: inline-block; + line-height: 32px; + padding: 0 16px +} + +.wy-menu-vertical { + width: 300px +} + +.wy-menu-vertical header, +.wy-menu-vertical p.caption { + color: #55a5d9; + height: 32px; + line-height: 32px; + padding: 0 1.618em; + margin: 12px 0 0; + display: block; + font-weight: 700; + text-transform: uppercase; + font-size: 85%; + white-space: nowrap +} + +.wy-menu-vertical ul { + margin-bottom: 0 +} + +.wy-menu-vertical li.divide-top { + border-top: 1px solid #404040 +} + +.wy-menu-vertical li.divide-bottom { + border-bottom: 1px solid #404040 +} + +.wy-menu-vertical li.current { + background: #e3e3e3 +} + +.wy-menu-vertical li.current a { + color: grey; + border-right: 1px solid #c9c9c9; + padding: .4045em 2.427em +} + +.wy-menu-vertical li.current a:hover { + background: #d6d6d6 +} + +.rst-content .wy-menu-vertical li tt, +.wy-menu-vertical li .rst-content tt, +.wy-menu-vertical li code { + border: none; + background: inherit; + color: inherit; + padding-left: 0; + padding-right: 0 +} + +.wy-menu-vertical li button.toctree-expand { + display: block; + float: left; + margin-left: -1.2em; + line-height: 18px; + color: #4d4d4d; + border: none; + background: none; + padding: 0 +} + +.wy-menu-vertical li.current>a, +.wy-menu-vertical li.on a { + color: #404040; + font-weight: 700; + position: relative; + background: #fcfcfc; + border: none; + padding: .4045em 1.618em +} + +.wy-menu-vertical li.current>a:hover, +.wy-menu-vertical li.on a:hover { + background: #fcfcfc +} + +.wy-menu-vertical li.current>a:hover button.toctree-expand, +.wy-menu-vertical li.on a:hover button.toctree-expand { + color: grey +} + +.wy-menu-vertical li.current>a button.toctree-expand, +.wy-menu-vertical li.on a button.toctree-expand { + display: block; + line-height: 18px; + color: #333 +} + +.wy-menu-vertical li.toctree-l1.current>a { + border-bottom: 1px solid #c9c9c9; + border-top: 1px solid #c9c9c9 +} + +.wy-menu-vertical .toctree-l1.current .toctree-l2>ul, +.wy-menu-vertical .toctree-l2.current .toctree-l3>ul, +.wy-menu-vertical .toctree-l3.current .toctree-l4>ul, +.wy-menu-vertical .toctree-l4.current .toctree-l5>ul, +.wy-menu-vertical .toctree-l5.current .toctree-l6>ul, +.wy-menu-vertical .toctree-l6.current .toctree-l7>ul, +.wy-menu-vertical .toctree-l7.current .toctree-l8>ul, +.wy-menu-vertical .toctree-l8.current .toctree-l9>ul, +.wy-menu-vertical .toctree-l9.current .toctree-l10>ul, +.wy-menu-vertical .toctree-l10.current .toctree-l11>ul { + display: none +} + +.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul, +.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul, +.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul, +.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul, +.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul, +.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul, +.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul, +.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul, +.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul, +.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul { + display: block +} + +.wy-menu-vertical li.toctree-l3, +.wy-menu-vertical li.toctree-l4 { + font-size: .9em +} + +.wy-menu-vertical li.toctree-l2 a, +.wy-menu-vertical li.toctree-l3 a, +.wy-menu-vertical li.toctree-l4 a, +.wy-menu-vertical li.toctree-l5 a, +.wy-menu-vertical li.toctree-l6 a, +.wy-menu-vertical li.toctree-l7 a, +.wy-menu-vertical li.toctree-l8 a, +.wy-menu-vertical li.toctree-l9 a, +.wy-menu-vertical li.toctree-l10 a { + color: #404040 +} + +.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand, +.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand { + color: grey +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a, +.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a, +.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a, +.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a, +.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a, +.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a, +.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a, +.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a, +.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a { + display: block +} + +.wy-menu-vertical li.toctree-l2.current>a { + padding: .4045em 2.427em +} + +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + padding: .4045em 1.618em .4045em 4.045em +} + +.wy-menu-vertical li.toctree-l3.current>a { + padding: .4045em 4.045em +} + +.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a { + padding: .4045em 1.618em .4045em 5.663em +} + +.wy-menu-vertical li.toctree-l4.current>a { + padding: .4045em 5.663em +} + +.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a { + padding: .4045em 1.618em .4045em 7.281em +} + +.wy-menu-vertical li.toctree-l5.current>a { + padding: .4045em 7.281em +} + +.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a { + padding: .4045em 1.618em .4045em 8.899em +} + +.wy-menu-vertical li.toctree-l6.current>a { + padding: .4045em 8.899em +} + +.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a { + padding: .4045em 1.618em .4045em 10.517em +} + +.wy-menu-vertical li.toctree-l7.current>a { + padding: .4045em 10.517em +} + +.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a { + padding: .4045em 1.618em .4045em 12.135em +} + +.wy-menu-vertical li.toctree-l8.current>a { + padding: .4045em 12.135em +} + +.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a { + padding: .4045em 1.618em .4045em 13.753em +} + +.wy-menu-vertical li.toctree-l9.current>a { + padding: .4045em 13.753em +} + +.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a { + padding: .4045em 1.618em .4045em 15.371em +} + +.wy-menu-vertical li.toctree-l10.current>a { + padding: .4045em 15.371em +} + +.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a { + padding: .4045em 1.618em .4045em 16.989em +} + +.wy-menu-vertical li.toctree-l2.current>a, +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + background: #c9c9c9 +} + +.wy-menu-vertical li.toctree-l2 button.toctree-expand { + color: #a3a3a3 +} + +.wy-menu-vertical li.toctree-l3.current>a, +.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a { + background: #bdbdbd +} + +.wy-menu-vertical li.toctree-l3 button.toctree-expand { + color: #969696 +} + +.wy-menu-vertical li.current ul { + display: block +} + +.wy-menu-vertical li ul { + margin-bottom: 0; + display: none +} + +.wy-menu-vertical li ul li a { + margin-bottom: 0; + color: #d9d9d9; + font-weight: 400 +} + +.wy-menu-vertical a { + line-height: 18px; + padding: .4045em 1.618em; + display: block; + position: relative; + font-size: 90%; + color: #d9d9d9 +} + +.wy-menu-vertical a:hover { + background-color: #4e4a4a; + cursor: pointer +} + +.wy-menu-vertical a:hover button.toctree-expand { + color: #d9d9d9 +} + +.wy-menu-vertical a:active { + background-color: #2980b9; + cursor: pointer; + color: #fff +} + +.wy-menu-vertical a:active button.toctree-expand { + color: #fff +} + +.wy-side-nav-search { + display: block; + width: 300px; + padding: .809em; + margin-bottom: .809em; + z-index: 200; + background-color: #2980b9; + text-align: center; + color: #fcfcfc +} + +.wy-side-nav-search input[type=text] { + width: 100%; + border-radius: 50px; + padding: 6px 12px; + border-color: #2472a4 +} + +.wy-side-nav-search img { + display: block; + margin: auto auto .809em; + height: 45px; + width: 45px; + background-color: #2980b9; + padding: 5px; + border-radius: 100% +} + +.wy-side-nav-search .wy-dropdown>a, +.wy-side-nav-search>a { + color: #fcfcfc; + font-size: 100%; + font-weight: 700; + display: inline-block; + padding-top: 4px; + padding-right: 6px; + /*padding-bottom: 4px;*/ + padding-left: 6px; + /* margin-bottom:0.809em */ +} + +.wy-side-nav-search .wy-dropdown>a:hover, +.wy-side-nav-search .wy-dropdown>aactive, +.wy-side-nav-search .wy-dropdown>afocus, +.wy-side-nav-search>a:hover, +.wy-side-nav-search>aactive, +.wy-side-nav-search>afocus { + background: hsla(0, 0%, 100%, .1) +} + +.wy-side-nav-search .wy-dropdown>a img.logo, +.wy-side-nav-search>a img.logo { + display: block; + margin: 0 auto; + height: auto; + width: auto; + border-radius: 0; + max-width: 100%; + background: transparent +} + +.wy-side-nav-search .wy-dropdown>a.icon, +.wy-side-nav-search>a.icon { + display: block +} + +.wy-side-nav-search .wy-dropdown>a.icon img.logo, +.wy-side-nav-search>a.icon img.logo { + margin-top: .85em +} + +.wy-side-nav-search>div.switch-menus { + position: relative; + display: block; + margin-top: -.4045em; + margin-bottom: .809em; + font-weight: 400; + color: hsla(0, 0%, 100%, .3) +} + +.wy-side-nav-search>div.switch-menus>div.language-switch, +.wy-side-nav-search>div.switch-menus>div.version-switch { + display: inline-block; + padding: .2em +} + +.wy-side-nav-search>div.switch-menus>div.language-switch select, +.wy-side-nav-search>div.switch-menus>div.version-switch select { + display: inline-block; + margin-right: -2rem; + padding-right: 2rem; + max-width: 240px; + text-align-last: center; + background: none; + border: none; + border-radius: 0; + box-shadow: none; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + font-size: 1em; + font-weight: 400; + color: hsla(0, 0%, 100%, .3); + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none +} + +.wy-side-nav-search>div.switch-menus>div.language-switch select:active, +.wy-side-nav-search>div.switch-menus>div.language-switch select:focus, +.wy-side-nav-search>div.switch-menus>div.language-switch select:hover, +.wy-side-nav-search>div.switch-menus>div.version-switch select:active, +.wy-side-nav-search>div.switch-menus>div.version-switch select:focus, +.wy-side-nav-search>div.switch-menus>div.version-switch select:hover { + background: hsla(0, 0%, 100%, .1); + color: hsla(0, 0%, 100%, .5) +} + +.wy-side-nav-search>div.switch-menus>div.language-switch select option, +.wy-side-nav-search>div.switch-menus>div.version-switch select option { + color: #000 +} + +.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after, +.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after { + display: inline-block; + width: 1.5em; + height: 100%; + padding: .1em; + content: "\f0d7"; + font-size: 1em; + line-height: 1.2em; + font-family: FontAwesome; + text-align: center; + pointer-events: none; + box-sizing: border-box +} + +.wy-nav .wy-menu-vertical header { + color: #2980b9 +} + +.wy-nav .wy-menu-vertical a { + color: #b3b3b3 +} + +.wy-nav .wy-menu-vertical a:hover { + background-color: #2980b9; + color: #fff +} + +[data-menu-wrap] { + -webkit-transition: all .2s ease-in; + -moz-transition: all .2s ease-in; + transition: all .2s ease-in; + position: absolute; + opacity: 1; + width: 100%; + opacity: 0 +} + +[data-menu-wrap].move-center { + left: 0; + right: auto; + opacity: 1 +} + +[data-menu-wrap].move-left { + right: auto; + left: -100%; + opacity: 0 +} + +[data-menu-wrap].move-right { + right: -100%; + left: auto; + opacity: 0 +} + +.wy-body-for-nav { + /* background: #fcfcfc */ +} + +.wy-grid-for-nav { + /* position:absolute; */ + position: relative; + /* Make left column full length */ + width: 100%; + height: 100% +} + +.wy-nav-side { + position: absolute; + top: 0; + bottom: 0; + left: 0; + padding-bottom: 2em; + width: 300px; + overflow-x: hidden; + overflow-y: hidden; + min-height: 100%; + color: #9b9b9b; + background: #343131; + z-index: 200 +} + +.wy-side-scroll { + width: 320px; + position: relative; + overflow-x: hidden; + overflow-y: scroll; + height: 100% +} + +.wy-nav-top { + display: none; + background: #2980b9; + color: #fff; + padding: .4045em .809em; + position: relative; + line-height: 50px; + text-align: center; + font-size: 100%; + *zoom: 1 +} + +.wy-nav-top:after, +.wy-nav-top:before { + display: table; + content: "" +} + +.wy-nav-top:after { + clear: both +} + +.wy-nav-top a { + color: #fff; + font-weight: 700 +} + +.wy-nav-top img { + margin-right: 12px; + /*height:45px; */ + /*width:45px;*/ + width: 200px; + background-color: #2980b9; + padding: 5px; + /* border-radius: 100% */ +} + +.wy-nav-top i { + font-size: 30px; + float: left; + cursor: pointer; + padding-top: inherit +} + +.wy-nav-content-wrap { + margin-left: 300px; + background: #fcfcfc; + min-height: 100% +} + +.wy-nav-content { /* padding:1.618em 3.236em; */ - /* padding-top: 1.618em; */ - padding-right: 3.236em; - padding-bottom: 1.618em; - padding-left: 3.236em; - height:100%; - min-height: 100vh; /* ensure is always full height of browser window */ - max-width:800px; + /* padding-top: 1.618em; */ + padding-right: 3.236em; + padding-bottom: 1.618em; + padding-left: 3.236em; + height: 100%; + min-height: 100vh; + /* ensure is always full height of browser window */ + max-width: 800px; /* margin:auto; */ - margin-left:0px; + margin-left: 0px; background: #fcfcfc; - } - - .wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%} - - @media screen and (max-width: 768px){ - .wy-body-for-nav{background:#fcfcfc} - .wy-nav-top{display:block}.wy-nav-side{left:-300px} - .wy-nav-side.shift{width:85%;left:0} - .wy-nav-content-wrap{margin-left:0} - .wy-nav-content-wrap .wy-nav-content { - /* padding:1.618em */ - /* padding-top: 1.618em; */ - padding-right: 1.618em; - padding-bottom: 1.618em; - padding-left: 1.618em; - } - .wy-nav-content-wrap.shift{ - position:relative; /* position:fixed; */ - min-width:100%; - left:85%; - top:0;height:100%; - overflow:hidden - } - } - - @media screen and (min-width: 1400px) { - /* .wy-nav-content-wrap{background:rgba(0,0,0,0.05)} */ - .wy-nav-content{ - /* margin:0; */ - background:#fcfcfc} - } - - @media print{.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:absolute/* previously fixed hamishw */ ;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center} - - @media screen and (max-width: 768px){ - .rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"\f0c1";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right} - - @media screen and (max-width: 480px){ - .rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040} +} + +.wy-body-mask { + position: fixed; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, .2); + display: none; + z-index: 499 +} + +.wy-body-mask.on { + display: block +} + +footer { + color: grey +} + +footer p { + margin-bottom: 12px +} + +.rst-content footer span.commit tt, +footer span.commit .rst-content tt, +footer span.commit code { + padding: 0; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + font-size: 1em; + background: none; + border: none; + color: grey +} + +.rst-footer-buttons { + *zoom: 1 +} + +.rst-footer-buttons:after, +.rst-footer-buttons:before { + width: 100%; + display: table; + content: "" +} + +.rst-footer-buttons:after { + clear: both +} + +.rst-breadcrumbs-buttons { + margin-top: 12px; + *zoom: 1 +} + +.rst-breadcrumbs-buttons:after, +.rst-breadcrumbs-buttons:before { + display: table; + content: "" +} + +.rst-breadcrumbs-buttons:after { + clear: both +} + +#search-results .search li { + margin-bottom: 24px; + border-bottom: 1px solid #e1e4e5; + padding-bottom: 24px +} + +#search-results .search li:first-child { + border-top: 1px solid #e1e4e5; + padding-top: 24px +} + +#search-results .search li a { + font-size: 120%; + margin-bottom: 12px; + display: inline-block +} + +#search-results .context { + color: grey; + font-size: 90% +} + +.genindextable li>ul { + margin-left: 24px +} + +@media screen and (max-width:768px) { + .wy-body-for-nav { + background: #fcfcfc + } + + .wy-nav-top { + display: block + } + + .wy-nav-side { + left: -300px + } + + .wy-nav-side.shift { + width: 85%; + left: 0 + } + + .wy-menu.wy-menu-vertical, + .wy-side-nav-search, + .wy-side-scroll { + width: auto + } + + .wy-nav-content-wrap { + margin-left: 0 + } + + .wy-nav-content-wrap .wy-nav-content { + /* padding:1.618em */ + /* padding-top: 1.618em; */ + padding-right: 1.618em; + padding-bottom: 1.618em; + padding-left: 1.618em; + } + + .wy-nav-content-wrap.shift { + position: relative; + /* position:fixed; */ + min-width: 100%; + left: 85%; + top: 0; + height: 100%; + overflow: hidden + } +} + +@media screen and (min-width:1100px) { + /* .wy-nav-content-wrap { + background: rgba(0, 0, 0, .05) + } */ + + .wy-nav-content { + /* margin: 0; */ + background: #fcfcfc + } +} + +@media print { + + .rst-versions, + .wy-nav-side, + footer { + display: none + } + + .wy-nav-content-wrap { + margin-left: 0 + } +} + +.rst-versions { + position: fixed; + bottom: 0; + left: 0; + width: 300px; + color: #fcfcfc; + background: #1f1d1d; + font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + z-index: 400 +} + +.rst-versions a { + color: #2980b9; + text-decoration: none +} + +.rst-versions .rst-badge-small { + display: none +} + +.rst-versions .rst-current-version { + padding: 12px; + background-color: #272525; + display: block; + text-align: right; + font-size: 90%; + cursor: pointer; + color: #27ae60; + *zoom: 1 +} + +.rst-versions .rst-current-version:after, +.rst-versions .rst-current-version:before { + display: table; + content: "" +} + +.rst-versions .rst-current-version:after { + clear: both +} + +.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, +.rst-content .eqno .rst-versions .rst-current-version .headerlink, +.rst-content .rst-versions .rst-current-version .admonition-title, +.rst-content code.download .rst-versions .rst-current-version span:first-child, +.rst-content dl dt .rst-versions .rst-current-version .headerlink, +.rst-content h1 .rst-versions .rst-current-version .headerlink, +.rst-content h2 .rst-versions .rst-current-version .headerlink, +.rst-content h3 .rst-versions .rst-current-version .headerlink, +.rst-content h4 .rst-versions .rst-current-version .headerlink, +.rst-content h5 .rst-versions .rst-current-version .headerlink, +.rst-content h6 .rst-versions .rst-current-version .headerlink, +.rst-content p .rst-versions .rst-current-version .headerlink, +.rst-content table>caption .rst-versions .rst-current-version .headerlink, +.rst-content tt.download .rst-versions .rst-current-version span:first-child, +.rst-versions .rst-current-version .fa, +.rst-versions .rst-current-version .icon, +.rst-versions .rst-current-version .rst-content .admonition-title, +.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, +.rst-versions .rst-current-version .rst-content .eqno .headerlink, +.rst-versions .rst-current-version .rst-content code.download span:first-child, +.rst-versions .rst-current-version .rst-content dl dt .headerlink, +.rst-versions .rst-current-version .rst-content h1 .headerlink, +.rst-versions .rst-current-version .rst-content h2 .headerlink, +.rst-versions .rst-current-version .rst-content h3 .headerlink, +.rst-versions .rst-current-version .rst-content h4 .headerlink, +.rst-versions .rst-current-version .rst-content h5 .headerlink, +.rst-versions .rst-current-version .rst-content h6 .headerlink, +.rst-versions .rst-current-version .rst-content p .headerlink, +.rst-versions .rst-current-version .rst-content table>caption .headerlink, +.rst-versions .rst-current-version .rst-content tt.download span:first-child, +.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand, +.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand { + color: #fcfcfc +} + +.rst-versions .rst-current-version .fa-book, +.rst-versions .rst-current-version .icon-book { + float: left +} + +.rst-versions .rst-current-version.rst-out-of-date { + background-color: #e74c3c; + color: #fff +} + +.rst-versions .rst-current-version.rst-active-old-version { + background-color: #f1c40f; + color: #000 +} + +.rst-versions.shift-up { + height: auto; + max-height: 100%; + overflow-y: scroll +} + +.rst-versions.shift-up .rst-other-versions { + display: block +} + +.rst-versions .rst-other-versions { + font-size: 90%; + padding: 12px; + color: grey; + display: none +} + +.rst-versions .rst-other-versions hr { + display: block; + height: 1px; + border: 0; + margin: 20px 0; + padding: 0; + border-top: 1px solid #413d3d +} + +.rst-versions .rst-other-versions dd { + display: inline-block; + margin: 0 +} + +.rst-versions .rst-other-versions dd a { + display: inline-block; + padding: 6px; + color: #fcfcfc +} + +.rst-versions .rst-other-versions .rtd-current-item { + font-weight: 700 +} + +.rst-versions.rst-badge { + width: auto; + bottom: 20px; + right: 20px; + left: auto; + border: none; + max-width: 300px; + max-height: 90% +} + +.rst-versions.rst-badge .fa-book, +.rst-versions.rst-badge .icon-book { + float: none; + line-height: 30px +} + +.rst-versions.rst-badge.shift-up .rst-current-version { + text-align: right +} + +.rst-versions.rst-badge.shift-up .rst-current-version .fa-book, +.rst-versions.rst-badge.shift-up .rst-current-version .icon-book { + float: left +} + +.rst-versions.rst-badge>.rst-current-version { + width: auto; + height: 30px; + line-height: 30px; + padding: 0 6px; + display: block; + text-align: center +} + +@media screen and (max-width:768px) { + .rst-versions { + width: 85%; + display: none + } + + .rst-versions.shift { + display: block + } +} + +#flyout-search-form { + padding: 6px +} + +.rst-content .toctree-wrapper>p.caption, +.rst-content h1, +.rst-content h2, +.rst-content h3, +.rst-content h4, +.rst-content h5, +.rst-content h6 { + margin-bottom: 24px +} + +.rst-content img { + height: auto +} + +.rst-content div.figure, +.rst-content figure { + margin-bottom: 24px +} + +.rst-content div.figure .caption-text, +.rst-content figure .caption-text { + font-style: italic +} + +.rst-content div.figure p:last-child.caption, +.rst-content figure p:last-child.caption { + margin-bottom: 0 +} + +.rst-content div.figure.align-center, +.rst-content figure.align-center { + text-align: center +} + +.rst-content .section>a>img, +.rst-content .section>img, +.rst-content section>a>img, +.rst-content section>img { + margin-bottom: 24px +} + +.rst-content abbr[title] { + text-decoration: none +} + +.rst-content.style-external-links a.reference.external:after { + font-family: FontAwesome; + content: "\f08e"; + color: #b3b3b3; + vertical-align: super; + font-size: 60%; + margin: 0 .2em +} + +.rst-content blockquote { + margin-left: 24px; + line-height: 24px; + margin-bottom: 24px +} + +.rst-content pre.literal-block { + white-space: pre; + margin: 0; + padding: 12px; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + display: block; + overflow: auto +} + +.rst-content div[class^=highlight], +.rst-content pre.literal-block { + border: 1px solid #e1e4e5; + overflow-x: auto; + margin: 1px 0 24px +} + +.rst-content div[class^=highlight] div[class^=highlight], +.rst-content pre.literal-block div[class^=highlight] { + padding: 0; + border: none; + margin: 0 +} + +.rst-content div[class^=highlight] td.code { + width: 100% +} + +.rst-content .linenodiv pre { + border-right: 1px solid #e6e9ea; + margin: 0; + padding: 12px; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + user-select: none; + pointer-events: none +} + +.rst-content div[class^=highlight] pre { + white-space: pre; + margin: 0; + padding: 12px; + display: block; + overflow: auto +} + +.rst-content div[class^=highlight] pre .hll { + display: block; + margin: 0 -12px; + padding: 0 12px +} + +.rst-content .linenodiv pre, +.rst-content div[class^=highlight] pre, +.rst-content pre.literal-block { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + font-size: 12px; + line-height: 1.4 +} + +.rst-content div.highlight .gp, +.rst-content div.highlight span.linenos { + user-select: none; + pointer-events: none +} + +.rst-content div.highlight span.linenos { + display: inline-block; + padding-left: 0; + padding-right: 12px; + margin-right: 12px; + border-right: 1px solid #e6e9ea +} + +.rst-content .code-block-caption { + font-style: italic; + font-size: 85%; + line-height: 1; + padding: 1em 0; + text-align: center +} + +@media print { + + .rst-content .codeblock, + .rst-content div[class^=highlight], + .rst-content div[class^=highlight] pre { + white-space: pre-wrap + } +} + +.rst-content .admonition, +.rst-content .admonition-todo, +.rst-content .attention, +.rst-content .caution, +.rst-content .danger, +.rst-content .error, +.rst-content .hint, +.rst-content .important, +.rst-content .note, +.rst-content .seealso, +.rst-content .tip, +.rst-content .warning { + clear: both +} + +.rst-content .admonition-todo .last, +.rst-content .admonition-todo>:last-child, +.rst-content .admonition .last, +.rst-content .admonition>:last-child, +.rst-content .attention .last, +.rst-content .attention>:last-child, +.rst-content .caution .last, +.rst-content .caution>:last-child, +.rst-content .danger .last, +.rst-content .danger>:last-child, +.rst-content .error .last, +.rst-content .error>:last-child, +.rst-content .hint .last, +.rst-content .hint>:last-child, +.rst-content .important .last, +.rst-content .important>:last-child, +.rst-content .note .last, +.rst-content .note>:last-child, +.rst-content .seealso .last, +.rst-content .seealso>:last-child, +.rst-content .tip .last, +.rst-content .tip>:last-child, +.rst-content .warning .last, +.rst-content .warning>:last-child { + margin-bottom: 0 +} + +.rst-content .admonition-title:before { + margin-right: 4px +} + +.rst-content .admonition table { + border-color: rgba(0, 0, 0, .1) +} + +.rst-content .admonition table td, +.rst-content .admonition table th { + background: transparent !important; + border-color: rgba(0, 0, 0, .1) !important +} + +.rst-content .section ol.loweralpha, +.rst-content .section ol.loweralpha>li, +.rst-content .toctree-wrapper ol.loweralpha, +.rst-content .toctree-wrapper ol.loweralpha>li, +.rst-content section ol.loweralpha, +.rst-content section ol.loweralpha>li { + list-style: lower-alpha +} + +.rst-content .section ol.upperalpha, +.rst-content .section ol.upperalpha>li, +.rst-content .toctree-wrapper ol.upperalpha, +.rst-content .toctree-wrapper ol.upperalpha>li, +.rst-content section ol.upperalpha, +.rst-content section ol.upperalpha>li { + list-style: upper-alpha +} + +.rst-content .section ol li>*, +.rst-content .section ul li>*, +.rst-content .toctree-wrapper ol li>*, +.rst-content .toctree-wrapper ul li>*, +.rst-content section ol li>*, +.rst-content section ul li>* { + margin-top: 12px; + margin-bottom: 12px +} + +.rst-content .section ol li>:first-child, +.rst-content .section ul li>:first-child, +.rst-content .toctree-wrapper ol li>:first-child, +.rst-content .toctree-wrapper ul li>:first-child, +.rst-content section ol li>:first-child, +.rst-content section ul li>:first-child { + margin-top: 0 +} + +.rst-content .section ol li>p, +.rst-content .section ol li>p:last-child, +.rst-content .section ul li>p, +.rst-content .section ul li>p:last-child, +.rst-content .toctree-wrapper ol li>p, +.rst-content .toctree-wrapper ol li>p:last-child, +.rst-content .toctree-wrapper ul li>p, +.rst-content .toctree-wrapper ul li>p:last-child, +.rst-content section ol li>p, +.rst-content section ol li>p:last-child, +.rst-content section ul li>p, +.rst-content section ul li>p:last-child { + margin-bottom: 12px +} + +.rst-content .section ol li>p:only-child, +.rst-content .section ol li>p:only-child:last-child, +.rst-content .section ul li>p:only-child, +.rst-content .section ul li>p:only-child:last-child, +.rst-content .toctree-wrapper ol li>p:only-child, +.rst-content .toctree-wrapper ol li>p:only-child:last-child, +.rst-content .toctree-wrapper ul li>p:only-child, +.rst-content .toctree-wrapper ul li>p:only-child:last-child, +.rst-content section ol li>p:only-child, +.rst-content section ol li>p:only-child:last-child, +.rst-content section ul li>p:only-child, +.rst-content section ul li>p:only-child:last-child { + margin-bottom: 0 +} + +.rst-content .section ol li>ol, +.rst-content .section ol li>ul, +.rst-content .section ul li>ol, +.rst-content .section ul li>ul, +.rst-content .toctree-wrapper ol li>ol, +.rst-content .toctree-wrapper ol li>ul, +.rst-content .toctree-wrapper ul li>ol, +.rst-content .toctree-wrapper ul li>ul, +.rst-content section ol li>ol, +.rst-content section ol li>ul, +.rst-content section ul li>ol, +.rst-content section ul li>ul { + margin-bottom: 12px +} + +.rst-content .section ol.simple li>*, +.rst-content .section ol.simple li ol, +.rst-content .section ol.simple li ul, +.rst-content .section ul.simple li>*, +.rst-content .section ul.simple li ol, +.rst-content .section ul.simple li ul, +.rst-content .toctree-wrapper ol.simple li>*, +.rst-content .toctree-wrapper ol.simple li ol, +.rst-content .toctree-wrapper ol.simple li ul, +.rst-content .toctree-wrapper ul.simple li>*, +.rst-content .toctree-wrapper ul.simple li ol, +.rst-content .toctree-wrapper ul.simple li ul, +.rst-content section ol.simple li>*, +.rst-content section ol.simple li ol, +.rst-content section ol.simple li ul, +.rst-content section ul.simple li>*, +.rst-content section ul.simple li ol, +.rst-content section ul.simple li ul { + margin-top: 0; + margin-bottom: 0 +} + +.rst-content .line-block { + margin-left: 0; + margin-bottom: 24px; + line-height: 24px +} + +.rst-content .line-block .line-block { + margin-left: 24px; + margin-bottom: 0 +} + +.rst-content .topic-title { + font-weight: 700; + margin-bottom: 12px +} + +.rst-content .toc-backref { + color: #404040 +} + +.rst-content .align-right { + float: right; + margin: 0 0 24px 24px +} + +.rst-content .align-left { + float: left; + margin: 0 24px 24px 0 +} + +.rst-content .align-center { + margin: auto +} + +.rst-content .align-center:not(table) { + display: block +} + +.rst-content .code-block-caption .headerlink, +.rst-content .eqno .headerlink, +.rst-content .toctree-wrapper>p.caption .headerlink, +.rst-content dl dt .headerlink, +.rst-content h1 .headerlink, +.rst-content h2 .headerlink, +.rst-content h3 .headerlink, +.rst-content h4 .headerlink, +.rst-content h5 .headerlink, +.rst-content h6 .headerlink, +.rst-content p.caption .headerlink, +.rst-content p .headerlink, +.rst-content table>caption .headerlink { + opacity: 0; + font-size: 14px; + font-family: FontAwesome; + margin-left: .5em +} + +.rst-content .code-block-caption .headerlink:focus, +.rst-content .code-block-caption:hover .headerlink, +.rst-content .eqno .headerlink:focus, +.rst-content .eqno:hover .headerlink, +.rst-content .toctree-wrapper>p.caption .headerlink:focus, +.rst-content .toctree-wrapper>p.caption:hover .headerlink, +.rst-content dl dt .headerlink:focus, +.rst-content dl dt:hover .headerlink, +.rst-content h1 .headerlink:focus, +.rst-content h1:hover .headerlink, +.rst-content h2 .headerlink:focus, +.rst-content h2:hover .headerlink, +.rst-content h3 .headerlink:focus, +.rst-content h3:hover .headerlink, +.rst-content h4 .headerlink:focus, +.rst-content h4:hover .headerlink, +.rst-content h5 .headerlink:focus, +.rst-content h5:hover .headerlink, +.rst-content h6 .headerlink:focus, +.rst-content h6:hover .headerlink, +.rst-content p.caption .headerlink:focus, +.rst-content p.caption:hover .headerlink, +.rst-content p .headerlink:focus, +.rst-content p:hover .headerlink, +.rst-content table>caption .headerlink:focus, +.rst-content table>caption:hover .headerlink { + opacity: 1 +} + +.rst-content p a { + overflow-wrap: anywhere +} + +.rst-content .wy-table td p, +.rst-content .wy-table td ul, +.rst-content .wy-table th p, +.rst-content .wy-table th ul, +.rst-content table.docutils td p, +.rst-content table.docutils td ul, +.rst-content table.docutils th p, +.rst-content table.docutils th ul, +.rst-content table.field-list td p, +.rst-content table.field-list td ul, +.rst-content table.field-list th p, +.rst-content table.field-list th ul { + font-size: inherit +} + +.rst-content .btn:focus { + outline: 2px solid +} + +.rst-content table>caption .headerlink:after { + font-size: 12px +} + +.rst-content .centered { + text-align: center +} + +.rst-content .sidebar { + float: right; + width: 40%; + display: block; + margin: 0 0 24px 24px; + padding: 24px; + background: #f3f6f6; + border: 1px solid #e1e4e5 +} + +.rst-content .sidebar dl, +.rst-content .sidebar p, +.rst-content .sidebar ul { + font-size: 90% +} + +.rst-content .sidebar .last, +.rst-content .sidebar>:last-child { + margin-bottom: 0 +} + +.rst-content .sidebar .sidebar-title { + display: block; + font-family: Roboto Slab, ff-tisa-web-pro, Georgia, Arial, sans-serif; + font-weight: 700; + background: #e1e4e5; + padding: 6px 12px; + margin: -24px -24px 24px; + font-size: 100% +} + +.rst-content .highlighted { + background: #f1c40f; + box-shadow: 0 0 0 2px #f1c40f; + display: inline; + font-weight: 700 +} + +.rst-content .citation-reference, +.rst-content .footnote-reference { + vertical-align: baseline; + position: relative; + top: -.4em; + line-height: 0; + font-size: 90% +} + +.rst-content .citation-reference>span.fn-bracket, +.rst-content .footnote-reference>span.fn-bracket { + display: none +} + +.rst-content .hlist { + width: 100% +} + +.rst-content dl dt span.classifier:before { + content: " : " +} + +.rst-content dl dt span.classifier-delimiter { + display: none !important +} + +html.writer-html4 .rst-content table.docutils.citation, +html.writer-html4 .rst-content table.docutils.footnote { + background: none; + border: none +} + +html.writer-html4 .rst-content table.docutils.citation td, +html.writer-html4 .rst-content table.docutils.citation tr, +html.writer-html4 .rst-content table.docutils.footnote td, +html.writer-html4 .rst-content table.docutils.footnote tr { + border: none; + background-color: transparent !important; + white-space: normal +} + +html.writer-html4 .rst-content table.docutils.citation td.label, +html.writer-html4 .rst-content table.docutils.footnote td.label { + padding-left: 0; + padding-right: 0; + vertical-align: top +} + +html.writer-html5 .rst-content dl.citation, +html.writer-html5 .rst-content dl.field-list, +html.writer-html5 .rst-content dl.footnote { + display: grid; + grid-template-columns: auto minmax(80%, 95%) +} + +html.writer-html5 .rst-content dl.citation>dt, +html.writer-html5 .rst-content dl.field-list>dt, +html.writer-html5 .rst-content dl.footnote>dt { + display: inline-grid; + grid-template-columns: max-content auto +} + +html.writer-html5 .rst-content aside.citation, +html.writer-html5 .rst-content aside.footnote, +html.writer-html5 .rst-content div.citation { + display: grid; + grid-template-columns: auto auto minmax(.65rem, auto) minmax(40%, 95%) +} + +html.writer-html5 .rst-content aside.citation>span.label, +html.writer-html5 .rst-content aside.footnote>span.label, +html.writer-html5 .rst-content div.citation>span.label { + grid-column-start: 1; + grid-column-end: 2 +} + +html.writer-html5 .rst-content aside.citation>span.backrefs, +html.writer-html5 .rst-content aside.footnote>span.backrefs, +html.writer-html5 .rst-content div.citation>span.backrefs { + grid-column-start: 2; + grid-column-end: 3; + grid-row-start: 1; + grid-row-end: 3 +} + +html.writer-html5 .rst-content aside.citation>p, +html.writer-html5 .rst-content aside.footnote>p, +html.writer-html5 .rst-content div.citation>p { + grid-column-start: 4; + grid-column-end: 5 +} + +html.writer-html5 .rst-content dl.citation, +html.writer-html5 .rst-content dl.field-list, +html.writer-html5 .rst-content dl.footnote { + margin-bottom: 24px +} + +html.writer-html5 .rst-content dl.citation>dt, +html.writer-html5 .rst-content dl.field-list>dt, +html.writer-html5 .rst-content dl.footnote>dt { + padding-left: 1rem +} + +html.writer-html5 .rst-content dl.citation>dd, +html.writer-html5 .rst-content dl.citation>dt, +html.writer-html5 .rst-content dl.field-list>dd, +html.writer-html5 .rst-content dl.field-list>dt, +html.writer-html5 .rst-content dl.footnote>dd, +html.writer-html5 .rst-content dl.footnote>dt { + margin-bottom: 0 +} + +html.writer-html5 .rst-content dl.citation, +html.writer-html5 .rst-content dl.footnote { + font-size: .9rem +} + +html.writer-html5 .rst-content dl.citation>dt, +html.writer-html5 .rst-content dl.footnote>dt { + margin: 0 .5rem .5rem 0; + line-height: 1.2rem; + word-break: break-all; + font-weight: 400 +} + +html.writer-html5 .rst-content dl.citation>dt>span.brackets:before, +html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before { + content: "[" +} + +html.writer-html5 .rst-content dl.citation>dt>span.brackets:after, +html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after { + content: "]" +} + +html.writer-html5 .rst-content dl.citation>dt>span.fn-backref, +html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref { + text-align: left; + font-style: italic; + margin-left: .65rem; + word-break: break-word; + word-spacing: -.1rem; + max-width: 5rem +} + +html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a, +html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a { + word-break: keep-all +} + +html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before, +html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before { + content: " " +} + +html.writer-html5 .rst-content dl.citation>dd, +html.writer-html5 .rst-content dl.footnote>dd { + margin: 0 0 .5rem; + line-height: 1.2rem +} + +html.writer-html5 .rst-content dl.citation>dd p, +html.writer-html5 .rst-content dl.footnote>dd p { + font-size: .9rem +} + +html.writer-html5 .rst-content aside.citation, +html.writer-html5 .rst-content aside.footnote, +html.writer-html5 .rst-content div.citation { + padding-left: 1rem; + padding-right: 1rem; + font-size: .9rem; + line-height: 1.2rem +} + +html.writer-html5 .rst-content aside.citation p, +html.writer-html5 .rst-content aside.footnote p, +html.writer-html5 .rst-content div.citation p { + font-size: .9rem; + line-height: 1.2rem; + margin-bottom: 12px +} + +html.writer-html5 .rst-content aside.citation span.backrefs, +html.writer-html5 .rst-content aside.footnote span.backrefs, +html.writer-html5 .rst-content div.citation span.backrefs { + text-align: left; + font-style: italic; + margin-left: .65rem; + word-break: break-word; + word-spacing: -.1rem; + max-width: 5rem +} + +html.writer-html5 .rst-content aside.citation span.backrefs>a, +html.writer-html5 .rst-content aside.footnote span.backrefs>a, +html.writer-html5 .rst-content div.citation span.backrefs>a { + word-break: keep-all +} + +html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before, +html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before, +html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before { + content: " " +} + +html.writer-html5 .rst-content aside.citation span.label, +html.writer-html5 .rst-content aside.footnote span.label, +html.writer-html5 .rst-content div.citation span.label { + line-height: 1.2rem +} + +html.writer-html5 .rst-content aside.citation-list, +html.writer-html5 .rst-content aside.footnote-list, +html.writer-html5 .rst-content div.citation-list { + margin-bottom: 24px +} + +html.writer-html5 .rst-content dl.option-list kbd { + font-size: .9rem +} + +.rst-content table.docutils.footnote, +html.writer-html4 .rst-content table.docutils.citation, +html.writer-html5 .rst-content aside.footnote, +html.writer-html5 .rst-content aside.footnote-list aside.footnote, +html.writer-html5 .rst-content div.citation-list>div.citation, +html.writer-html5 .rst-content dl.citation, +html.writer-html5 .rst-content dl.footnote { + color: grey +} + +.rst-content table.docutils.footnote code, +.rst-content table.docutils.footnote tt, +html.writer-html4 .rst-content table.docutils.citation code, +html.writer-html4 .rst-content table.docutils.citation tt, +html.writer-html5 .rst-content aside.footnote-list aside.footnote code, +html.writer-html5 .rst-content aside.footnote-list aside.footnote tt, +html.writer-html5 .rst-content aside.footnote code, +html.writer-html5 .rst-content aside.footnote tt, +html.writer-html5 .rst-content div.citation-list>div.citation code, +html.writer-html5 .rst-content div.citation-list>div.citation tt, +html.writer-html5 .rst-content dl.citation code, +html.writer-html5 .rst-content dl.citation tt, +html.writer-html5 .rst-content dl.footnote code, +html.writer-html5 .rst-content dl.footnote tt { + color: #555 +} + +.rst-content .wy-table-responsive.citation, +.rst-content .wy-table-responsive.footnote { + margin-bottom: 0 +} + +.rst-content .wy-table-responsive.citation+:not(.citation), +.rst-content .wy-table-responsive.footnote+:not(.footnote) { + margin-top: 24px +} + +.rst-content .wy-table-responsive.citation:last-child, +.rst-content .wy-table-responsive.footnote:last-child { + margin-bottom: 24px +} + +.rst-content table.docutils th { + border-color: #e1e4e5 +} + +html.writer-html5 .rst-content table.docutils th { + border: 1px solid #e1e4e5 +} + +html.writer-html5 .rst-content table.docutils td>p, +html.writer-html5 .rst-content table.docutils th>p { + line-height: 1rem; + margin-bottom: 0; + font-size: .9rem +} + +.rst-content table.docutils td .last, +.rst-content table.docutils td .last>:last-child { + margin-bottom: 0 +} + +.rst-content table.field-list, +.rst-content table.field-list td { + border: none +} + +.rst-content table.field-list td p { + line-height: inherit +} + +.rst-content table.field-list td>strong { + display: inline-block +} + +.rst-content table.field-list .field-name { + padding-right: 10px; + text-align: left; + white-space: nowrap +} + +.rst-content table.field-list .field-body { + text-align: left +} + +.rst-content code, +.rst-content tt { + color: #000; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + padding: 2px 5px +} + +.rst-content code big, +.rst-content code em, +.rst-content tt big, +.rst-content tt em { + font-size: 100% !important; + line-height: normal +} + +.rst-content code.literal, +.rst-content tt.literal { + color: #e74c3c; + white-space: normal +} + +.rst-content code.xref, +.rst-content tt.xref, +a .rst-content code, +a .rst-content tt { + font-weight: 700; + color: #404040; + overflow-wrap: normal +} + +.rst-content kbd, +.rst-content pre, +.rst-content samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace +} + +.rst-content a code, +.rst-content a tt { + color: #2980b9 +} + +.rst-content dl { + margin-bottom: 24px +} + +.rst-content dl dt { + font-weight: 700; + margin-bottom: 12px +} + +.rst-content dl ol, +.rst-content dl p, +.rst-content dl table, +.rst-content dl ul { + margin-bottom: 12px +} + +.rst-content dl dd { + margin: 0 0 12px 24px; + line-height: 24px +} + +.rst-content dl dd>ol:last-child, +.rst-content dl dd>p:last-child, +.rst-content dl dd>table:last-child, +.rst-content dl dd>ul:last-child { + margin-bottom: 0 +} + +html.writer-html4 .rst-content dl:not(.docutils), +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) { + margin-bottom: 24px +} + +html.writer-html4 .rst-content dl:not(.docutils)>dt, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt { + display: table; + margin: 6px 0; + font-size: 90%; + line-height: normal; + background: #e7f2fa; + color: #2980b9; + border-top: 3px solid #6ab0de; + padding: 6px; + position: relative +} + +html.writer-html4 .rst-content dl:not(.docutils)>dt:before, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before { + color: #6ab0de +} + +html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink { + color: #404040; + font-size: 100% !important +} + +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt { + margin-bottom: 6px; + border: none; + border-left: 3px solid #ccc; + background: #f0f0f0; + color: #555 +} + +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink { + color: #404040; + font-size: 100% !important +} + +html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child { + margin-top: 0 +} + +html.writer-html4 .rst-content dl:not(.docutils) code.descclassname, +html.writer-html4 .rst-content dl:not(.docutils) code.descname, +html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname, +html.writer-html4 .rst-content dl:not(.docutils) tt.descname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname { + background-color: transparent; + border: none; + padding: 0; + font-size: 100% !important +} + +html.writer-html4 .rst-content dl:not(.docutils) code.descname, +html.writer-html4 .rst-content dl:not(.docutils) tt.descname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname { + font-weight: 700 +} + +html.writer-html4 .rst-content dl:not(.docutils) .optional, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional { + display: inline-block; + padding: 0 4px; + color: #000; + font-weight: 700 +} + +html.writer-html4 .rst-content dl:not(.docutils) .property, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property { + display: inline-block; + padding-right: 8px; + max-width: 100% +} + +html.writer-html4 .rst-content dl:not(.docutils) .k, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k { + font-style: italic +} + +html.writer-html4 .rst-content dl:not(.docutils) .descclassname, +html.writer-html4 .rst-content dl:not(.docutils) .descname, +html.writer-html4 .rst-content dl:not(.docutils) .sig-name, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, Courier, monospace; + color: #000 +} + +.rst-content .viewcode-back, +.rst-content .viewcode-link { + display: inline-block; + color: #27ae60; + font-size: 80%; + padding-left: 24px +} + +.rst-content .viewcode-back { + display: block; + float: right +} + +.rst-content p.rubric { + margin-bottom: 12px; + font-weight: 700 +} + +.rst-content code.download, +.rst-content tt.download { + background: inherit; + padding: inherit; + font-weight: 400; + font-family: inherit; + font-size: inherit; + color: inherit; + border: inherit; + white-space: inherit +} + +.rst-content code.download span:first-child, +.rst-content tt.download span:first-child { + -webkit-font-smoothing: subpixel-antialiased +} + +.rst-content code.download span:first-child:before, +.rst-content tt.download span:first-child:before { + margin-right: 4px +} + +.rst-content .guilabel, +.rst-content .menuselection { + font-size: 80%; + font-weight: 700; + border-radius: 4px; + padding: 2.4px 6px; + margin: auto 2px +} + +.rst-content .guilabel, +.rst-content .menuselection { + border: 1px solid #7fbbe3; + background: #e7f2fa +} + +.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd, +.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd { + color: inherit; + font-size: 80%; + background-color: #fff; + border: 1px solid #a6a6a6; + border-radius: 4px; + box-shadow: 0 2px grey; + padding: 2.4px 6px; + margin: auto 0 +} + +.rst-content .versionmodified { + font-style: italic +} + +@media screen and (max-width:480px) { + .rst-content .sidebar { + width: 100% + } +} + +span[id*=MathJax-Span] { + color: #404040 +} + +.math { + text-align: center +} + +@font-face { + font-family: Lato; + src: url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"), url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff"); + font-weight: 400; + font-style: normal; + font-display: block +} + +@font-face { + font-family: Lato; + src: url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"), url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff"); + font-weight: 700; + font-style: normal; + font-display: block +} + +@font-face { + font-family: Lato; + src: url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"), url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff"); + font-weight: 700; + font-style: italic; + font-display: block +} + +@font-face { + font-family: Lato; + src: url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"), url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff"); + font-weight: 400; + font-style: italic; + font-display: block +} + +@font-face { + font-family: Roboto Slab; + font-style: normal; + font-weight: 400; + src: url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"), url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff"); + font-display: block +} + +@font-face { + font-family: Roboto Slab; + font-style: normal; + font-weight: 700; + src: url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"), url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff"); + font-display: block +} + /*! * HamishW - some CSS for nav bar */ - + .main-nav-bar { - display:block; - max-width: 1100px; - border-bottom: solid; - border-bottom-width: thin; - padding-bottom: 10px; - margin-bottom:20px; - padding-top: 10px; + display: block; + max-width: 1100px; + border-bottom: solid; + border-bottom-width: thin; + padding-bottom: 10px; + margin-bottom: 20px; + padding-top: 10px; } #menu-options { display: table; - /* background-color:#F8F8F8; */ + /* background-color:#F8F8F8; */ /*height: 87px;*/ - + width: 100%; } #menu-options li { display: table-cell; - /* padding-left: 5px; + /* padding-left: 5px; padding-right: 5px; */ - padding-top: 10px; - padding-bottom: 10px; - width: 5.0%; /*(100 / numItems)% */ + padding-top: 10px; + padding-bottom: 10px; + width: 5.0%; + /*(100 / numItems)% */ text-align: center; - font-weight:bold; + font-weight: bold; /*background: #ddd;*/ white-space: nowrap; -}​ - - +} -.navlink-long { - display:inline-block; - vertical-align: top; - padding:5px; +​ .navlink-long { + display: inline-block; + vertical-align: top; + padding: 5px; } .navlink-short { - display:none; - vertical-align: top; - padding:5px; + display: none; + vertical-align: top; + padding: 5px; } .footer-nav-bar { - display:block; - background-color:#F8F8F8; - max-width: 1100px; - /*border-bottom: solid;*/ - padding-bottom: 10px; - margin-top:15px; - border-top:solid; - /* border-top-width:thin; */ + display: block; + background-color: #F8F8F8; + max-width: 1100px; + /*border-bottom: solid;*/ + padding-bottom: 10px; + margin-top: 15px; + border-top: solid; + /* border-top-width:thin; */ } .footer-options { -/* display:block; + /* display:block; width:inherit; font-size:0.8em; font-weight:normal; */ -display:block; -text-align:justify; -font-size:0.8em; -width:inherit; + display: block; + text-align: justify; + font-size: 0.8em; + width: inherit; } .footer-navlink-long { - display: inline-block; - vertical-align: top; - padding:5px; + display: inline-block; + vertical-align: top; + padding: 5px; } .footer-navlink-short { - display:none; - padding:5px; + display: none; + padding: 5px; } .footer-options:after { content: ""; - width: 100%; - line-height:1px; - line-spacing:1px; - display: inline-block; - } - + width: 100%; + line-height: 1px; + line-spacing: 1px; + display: inline-block; +} + .copyright-box { - border-top:solid; - border-top-width:thin; - margin-top:10px; - background-color:#F8F8F8; - padding-bottom:5px; + border-top: solid; + border-top-width: thin; + margin-top: 10px; + background-color: #F8F8F8; + padding-bottom: 5px; } .copyright-box p { - font-size:0.8em; + font-size: 0.8em; } - - -/* HamishW - Attempt to wrap table columns/remove the "responsive" behaviour on some tables. */ -table.wrap-table-content td, table.wrap-table-content th { - white-space: normal; + + +/* HamishW - Attempt to wrap table columns/remove the "responsive" behaviour on some tables. */ +table.wrap-table-content td, +table.wrap-table-content th { + white-space: normal; } - -/* HamishW - add clear markup for external links */ + +/* HamishW - add clear markup for external links */ a.external:after { content: ""; display: inline-block; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAV0lEQVR4Xq2QwQ2AAAwC3cmd2Kk7sRP64CEJ9qOX8OPatMc/QKppnEPhTmJh23CLiwAqIw21CybKQ28qQi37WGFYBJcwfJQpP8LlEHKyZMF0IdmF13zlAjZ/6H4wb+mUAAAAAElFTkSuQmCC); + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAV0lEQVR4Xq2QwQ2AAAwC3cmd2Kk7sRP64CEJ9qOX8OPatMc/QKppnEPhTmJh23CLiwAqIw21CybKQ28qQi37WGFYBJcwfJQpP8LlEHKyZMF0IdmF13zlAjZ/6H4wb+mUAAAAAElFTkSuQmCC); background-repeat: no-repeat; background-position: right top; background-origin: border-box; @@ -396,18 +8004,18 @@ a.external:after { height: 16px; } - + /* HamishW - some CSS for the breadcrumb (make elements inline blocks) */ .breadcrumb-box { - margin-top:10px; - font-size:0.8em; - } + margin-top: 10px; + font-size: 0.8em; +} -.breadcrumb-box-item{ - display: inline-block; - } +.breadcrumb-box-item { + display: inline-block; +} /* indent third level item */ @@ -417,10 +8025,8 @@ a.external:after { .wy-menu-vertical li.toctree-l2.current>a, .wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>a, -.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current li.toctree-l4.current>a -.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current li.toctree-l4.current li.toctree-l5.current>a - { -background: #c9c9c9; +.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current li.toctree-l4.current>a .wy-menu-vertical li.toctree-l2.current li.toctree-l3.current li.toctree-l4.current li.toctree-l5.current>a { + background: #c9c9c9; } @@ -432,82 +8038,91 @@ background: purple; */ .wy-menu-vertical li.toctree-l2 li.toctree-l3>a { -display:none; + display: none; } .wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { -display:block; -font-size:0.8em; -/*padding-top: 0.4045em;*/ -padding-right: 2.427em; -padding-bottom: 0.4045em; -padding-left: 4.25em; -width:100%; + display: block; + font-size: 0.8em; + /*padding-top: 0.4045em;*/ + padding-right: 2.427em; + padding-bottom: 0.4045em; + padding-left: 4.25em; + width: 100%; } .wy-menu-vertical li.toctree-l2 li.toctree-l3 li.toctree-l4>a { -display:none; + display: none; } .wy-menu-vertical li.toctree-l2.current li.toctree-l3.current li.toctree-l4>a { -/* + /* background: #F0EEEE; background: purple; */ -display:block; -font-size:0.8em; -/*padding-top: 0.4045em;*/ -padding-right: 2.427em; -padding-bottom: 0.4045em; -padding-left: 5.0em; -width:100%; + display: block; + font-size: 0.8em; + /*padding-top: 0.4045em;*/ + padding-right: 2.427em; + padding-bottom: 0.4045em; + padding-left: 5.0em; + width: 100%; } - { -background: #c9c9c9; + { + background: #c9c9c9; } .wy-menu-vertical a[href^="#"] { -background:#F0EEEE; + background: #F0EEEE; } .grid-to-center-rtd-theme { - margin-left:0; - margin-right:auto; - max-width: 1100px; - } + margin-left: 0; + margin-right: auto; + max-width: 1100px; +} -@media only screen and (min-width : 1100px){ +@media only screen and (min-width : 1100px) { + + .grid-to-center-rtd-theme { + margin-left: auto; + /* max-width: 1100px; */ + } -.grid-to-center-rtd-theme { - margin-left:auto; - /* max-width: 1100px; */ - } - } - -@media only screen -and (max-width : 480px) { -/* Styles */ - .navlink-long, .footer-navlink-long { - display:none; - } - .navlink-short, .footer-navlink-short { - display:inline-block; - vertical-align: top;} - +@media only screen and (max-width : 480px) { + + /* Styles */ + .navlink-long, + .footer-navlink-long { + display: none; + } + + .navlink-short, + .footer-navlink-short { + display: inline-block; + vertical-align: top; + } + } @media screen and (min-width: 480px) and (max-width: 768px) { - .navlink-long, .footer-navlink-long { - display:inline-block; - } - .navlink-short, .footer-navlink-short { - display:none; - } + + .navlink-long, + .footer-navlink-long { + display: inline-block; + } + + .navlink-short, + .footer-navlink-short { + display: none; + } } -.cellborder { border: solid 1px black; } \ No newline at end of file +.cellborder { + border: solid 1px black; +} \ No newline at end of file diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot b/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot deleted file mode 100644 index 7c79c6a6bc9a1..0000000000000 Binary files a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg b/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg deleted file mode 100644 index 45fdf33830123..0000000000000 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,414 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf b/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf deleted file mode 100644 index e89738de5eaf8..0000000000000 Binary files a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff b/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff deleted file mode 100644 index 8c1748aab7a79..0000000000000 Binary files a/site/source/_themes/emscripten_sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/site/source/_themes/emscripten_sphinx_rtd_theme/static/js/theme.js b/site/source/_themes/emscripten_sphinx_rtd_theme/static/js/theme.js index 60520cc3adb2c..a6c836093fb67 100644 --- a/site/source/_themes/emscripten_sphinx_rtd_theme/static/js/theme.js +++ b/site/source/_themes/emscripten_sphinx_rtd_theme/static/js/theme.js @@ -1,47 +1 @@ -$( document ).ready(function() { - // Shift nav in mobile when clicking the menu. - $(document).on('click', "[data-toggle='wy-nav-top']", function() { - $("[data-toggle='wy-nav-shift']").toggleClass("shift"); - $("[data-toggle='rst-versions']").toggleClass("shift"); - }); - // Close menu when you click a link. - $(document).on('click', ".wy-menu-vertical .current ul li a", function() { - $("[data-toggle='wy-nav-shift']").removeClass("shift"); - $("[data-toggle='rst-versions']").toggleClass("shift"); - }); - $(document).on('click', "[data-toggle='rst-current-version']", function() { - $("[data-toggle='rst-versions']").toggleClass("shift-up"); - }); - // Make tables responsive - $("table.docutils:not(.field-list)").wrap("
"); -}); - -window.SphinxRtdTheme = (function (jquery) { - var stickyNav = (function () { - var navBar, - win, - stickyNavCssClass = 'stickynav', - applyStickNav = function () { - if (navBar.height() <= win.height()) { - navBar.addClass(stickyNavCssClass); - } else { - navBar.removeClass(stickyNavCssClass); - } - }, - enable = function () { - applyStickNav(); - win.on('resize', applyStickNav); - }, - init = function () { - navBar = jquery('nav.wy-nav-side:first'); - win = jquery(window); - }; - jquery(init); - return { - enable : enable - }; - }()); - return { - StickyNav : stickyNav - }; -}($)); +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");/*t[0].scrollIntoView()*/}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t - - Read the Docs - v: {{ current_version }} - - -
-
-
Versions
- {% for slug, url in versions %} -
{{ slug }}
- {% endfor %} -
-
-
Downloads
- {% for type, url in downloads %} -
{{ type }}
- {% endfor %} -
-
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
-
- Free document hosting provided by Read the Docs. - -
- -{% endif %} - diff --git a/site/source/conf.py b/site/source/conf.py index 90532815f185a..5294ecf3a16d9 100644 --- a/site/source/conf.py +++ b/site/source/conf.py @@ -56,6 +56,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. +sys.path.insert(0, os.path.abspath('_themes')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', @@ -64,18 +65,20 @@ 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', + 'sphinxcontrib.jquery', + 'emscripten_sphinx_rtd_theme', # 'breathe', #added by HamishW ] -#Build "Todo" notes into the source +# Build "Todo" notes into the source #todo_include_todos = 'True' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = {'.rst': 'restructuredtext'} # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -94,7 +97,7 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -version_path = Path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'emscripten-version.txt').resolve() +version_path = Path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'emscripten-version.txt') emscripten_version = version_path.read_text().strip().strip('"') # The short X.Y version. @@ -161,10 +164,9 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = { -# "rightsidebar": "true", -# "relbarbgcolor": "black" -#} +html_theme_options = { + 'logo_only': True +} # Add any paths that contain custom themes here, relative to this directory. @@ -390,10 +392,8 @@ # If false, no index is generated. #epub_use_index = True - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} - #highlight_language = 'default' primary_domain = 'cpp' + +smartquotes_excludes = {'builders': ['text', 'man']} diff --git a/site/source/docs/api_reference/Filesystem-API.rst b/site/source/docs/api_reference/Filesystem-API.rst index c35dba0956da7..1c5985e5a67c7 100644 --- a/site/source/docs/api_reference/Filesystem-API.rst +++ b/site/source/docs/api_reference/Filesystem-API.rst @@ -23,7 +23,7 @@ A high level overview of the way File Systems work in Emscripten-ported code is New File System: WasmFS ======================= -.. note:: Current Status: Work in Progress +.. note:: Current Status: Stable, but not yet feature-complete with the old FS. WasmFS is a high-performance, fully-multithreaded, WebAssembly-based file system layer for Emscripten that will replace the existing JavaScript version. @@ -113,6 +113,8 @@ The *IDBFS* file system implements the :js:func:`FS.syncfs` interface, which whe This is provided to overcome the limitation that browsers do not offer synchronous APIs for persistent storage, and so (by default) all writes exist only temporarily in-memory. +If the mount option `autoPersist: true` is passed when mounting IDBFS, then whenever any changes are made to the IDBFS directory tree, they will be automatically persisted to the IndexedDB backend. This lets users avoid needing to manually call `FS.syncfs` to persist changes to the IDBFS mounted directory tree. + .. _filesystem-api-workerfs: WORKERFS @@ -169,7 +171,7 @@ The device node acts as an interface between the device and the file system. Any Setting up standard I/O devices =============================== -Emscripten standard I/O works by going though the virtual ``/dev/stdin``, ``/dev/stdout`` and ``/dev/stderr`` devices. You can set them up using your own I/O functions by calling :js:func:`FS.init`. +Emscripten standard I/O works by going through the virtual ``/dev/stdin``, ``/dev/stdout`` and ``/dev/stderr`` devices. You can set them up using your own I/O functions by calling :js:func:`FS.init`. By default: @@ -295,6 +297,20 @@ File system API :param int mode: :ref:`File permissions ` for the new node. The default setting (`in octal numeric notation `_) is 0777. +.. js:function:: FS.mkdirTree(path, mode) + + Creates a new directory node and all parent directories in the file system. For example: + + .. code-block:: javascript + + FS.mkdirTree('/data/subdir1/subdir2'); + + .. note:: The underlying implementation does not support user or group permissions. The caller is always treated as the owner of the folder, and only permissions relevant to the owner apply. + + :param string path: The path name for the new directory node. + :param int mode: :ref:`File permissions ` for the new node. The default setting (`in octal numeric notation `_) is 0777. + + .. js:function:: FS.mkdev(path, mode, dev) Creates a new device node in the file system referencing the registered device driver (:js:func:`FS.registerDevice`) for ``dev``. For example: diff --git a/site/source/docs/api_reference/advanced-apis.rst b/site/source/docs/api_reference/advanced-apis.rst index 23a917defc608..1b53e46720ff5 100644 --- a/site/source/docs/api_reference/advanced-apis.rst +++ b/site/source/docs/api_reference/advanced-apis.rst @@ -91,7 +91,6 @@ example, writing a new local file system) or legacy file system compatibility. .. js:function:: FS.lookup(parent, name) .. js:function:: FS.mknod(path, mode, dev) .. js:function:: FS.create(path, mode) -.. js:function:: FS.allocate(stream, offset, length) .. js:function:: FS.mmap(stream, buffer, offset, length, position, prot, flags) .. js:function:: FS.ioctl(stream, cmd, arg) .. js:function:: FS.staticInit() diff --git a/site/source/docs/api_reference/bind.h.rst b/site/source/docs/api_reference/bind.h.rst index 6660c7d36df7a..8f0ec53472ba8 100644 --- a/site/source/docs/api_reference/bind.h.rst +++ b/site/source/docs/api_reference/bind.h.rst @@ -99,7 +99,7 @@ select_overload and select_const template typename std::add_pointer::type select_overload(typename std::add_pointer::type fn) - :param typename std\:\:add_pointer::type fn + :param typename std\:\:add_pointer::type fn: .. cpp:function:: typename internal::MemberFunctionType::type select_overload() @@ -109,7 +109,7 @@ select_overload and select_const template typename internal::MemberFunctionType::type select_overload(Signature (ClassType::*fn)) - :param Signature (ClassType::\*fn) + :param Signature (ClassType::\*fn): .. cpp:function:: auto select_const() @@ -119,7 +119,7 @@ select_overload and select_const template auto select_const(ReturnType (ClassType::*method)(Args...) const) - :param ReturnType (ClassType::\*method)(Args...) const + :param ReturnType (ClassType::\*method)(Args...) const: .. cpp:function:: typename internal::CalculateLambdaSignature::type optional_override(const LambdaType& fp) @@ -130,7 +130,7 @@ select_overload and select_const template typename internal::CalculateLambdaSignature::type optional_override(const LambdaType& fp) - :param const LambdaType& fp + :param const LambdaType& fp: Functions @@ -186,7 +186,7 @@ Value tuples Constructor. - :param const char* name + :param const char* name: .. cpp:function:: ~value_array() @@ -201,13 +201,13 @@ Value tuples .. cpp:function:: value_array& element(Getter getter, Setter setter) - :param Getter getter. Note that ``Getter`` is a typename (templated type). - :param Setter setter. Note that ``Setter`` is a typename (templated type). + :param Getter getter: Note that ``Getter`` is a typename (templated type). + :param Setter setter: Note that ``Setter`` is a typename (templated type). .. cpp:function:: value_array& element(index) - :param index:Note that ``Index`` is an integer template parameter. + :param index: Note that ``Index`` is an integer template parameter. @@ -227,7 +227,7 @@ Value structs Constructor. - :param const char* name + :param const char* name: .. cpp:function:: ~value_object() @@ -237,22 +237,22 @@ Value structs .. cpp:function:: value_object& field(const char* fieldName, FieldType InstanceType::*field) - :param const char* fieldName. - :param FieldType InstanceType\:\:\*field. + :param const char* fieldName: + :param FieldType InstanceType\:\:\*field: .. cpp:function:: value_object& field(const char* fieldName, Getter getter, Setter setter) - :param const char* fieldName. - :param Getter getter. Note that ``Getter`` is a typename (templated type). - :param Setter setter. Note that ``Setter`` is a typename (templated type). + :param const char* fieldName: + :param Getter getter: Note that ``Getter`` is a typename (templated type). + :param Setter setter: Note that ``Setter`` is a typename (templated type). .. cpp:function:: value_object& field(const char* fieldName, index) - :param const char* fieldName. - :param index. Note that ``Index`` is an integer template parameter. + :param const char* fieldName: + :param index: Note that ``Index`` is an integer template parameter. Smart pointers @@ -272,7 +272,7 @@ Smart pointers .. cpp:function:: static void* share(void* v) - :param void* v + :param void* v: .. cpp:function:: static PointerType* construct_null() @@ -325,7 +325,7 @@ Smart pointers .. cpp:function:: static element_type* get(const PointerType& ptr) - :param const PointerType& ptr + :param const PointerType& ptr: .. cpp:function:: static sharing_policy get_sharing_policy() @@ -369,7 +369,7 @@ Classes Constructor. - :param val&& wrapped + :param val&& wrapped: :param Args&&... args: Note that ``Args`` is a typename (templated type). @@ -382,14 +382,14 @@ Classes Constructor. - :param const char* name + :param const char* name: :param Args&&... args: Note that ``Args`` is a typename (templated type). :returns: Note that ``ReturnType`` is a typename (templated type). .. cpp:function:: EMSCRIPTEN_WRAPPER(T) - :param T + :param T: .. cpp:type:: base @@ -442,7 +442,7 @@ Classes template static To* convertPointer(From* ptr) - :param From* ptr + :param From* ptr: @@ -494,7 +494,7 @@ Classes Constructor. - :param const char* name + :param const char* name: .. cpp:function:: const class_& smart_ptr(const char* name) const @@ -505,7 +505,7 @@ Classes template EMSCRIPTEN_ALWAYS_INLINE const class_& smart_ptr(const char* name) const - :param const char* name. + :param const char* name: :returns: |class_-function-returns| @@ -552,7 +552,7 @@ Classes See :ref:`embind-external-constructors` for more information. - :param Callable callable Note that ``Callable`` may be either a member function pointer, function pointer, ``std::function`` or function object. + :param Callable callable: Note that ``Callable`` may be either a member function pointer, function pointer, ``std::function`` or function object. :param Policies... policies: |policies-argument| :returns: |class_-function-returns| @@ -565,8 +565,8 @@ Classes template EMSCRIPTEN_ALWAYS_INLINE const class_& smart_ptr_constructor(const char* smartPtrName, SmartPtr (*factory)(Args...), Policies...) const - :param const char* smartPtrName - :param SmartPtr (\*factory)(Args...) + :param const char* smartPtrName: + :param SmartPtr (\*factory)(Args...): :param Policies... policies: |policies-argument| :returns: |class_-function-returns| @@ -583,9 +583,9 @@ Classes ::emscripten::constructor = ::emscripten::constructor<>() ) const - :param const char* wrapperClassName - :param const char* pointerName - :param emscripten\:\:constructor constructor) + :param const char* wrapperClassName: + :param const char* pointerName: + :param emscripten\:\:constructor constructor): :returns: |class_-function-returns| @@ -600,8 +600,8 @@ Classes ::emscripten::constructor constructor = ::emscripten::constructor<>() ) const - :param const char* wrapperClassName - :param \:\:emscripten\:\:constructor constructor) + :param const char* wrapperClassName: + :param \:\:emscripten\:\:constructor constructor): :returns: |class_-function-returns| @@ -636,11 +636,43 @@ Classes .function("myFunctor", std::bind(&my_functor_taking_this, _1)); - :param const char* methodName - :param Callable callable Note that ``Callable`` may be either a member function pointer, function pointer, ``std::function`` or function object. + :param const char* methodName: + :param Callable callable: Note that ``Callable`` may be either a member function pointer, function pointer, ``std::function`` or function object. :param typename... Policies: |policies-argument| :returns: |class_-function-returns| + .. cpp:function:: const class_& iterable() const + + .. code-block:: cpp + + // prototype + template + EMSCRIPTEN_ALWAYS_INLINE const class_& iterable(const char* sizeMethodName, const char* getMethodName) const + + Makes a bound class iterable in JavaScript by installing ``Symbol.iterator``. + This enables use with ``for...of`` loops, ``Array.from()``, and spread syntax. + + :tparam ElementType: The type of elements yielded by the iterator. + + :param sizeMethodName: Name of the bound method that returns the number of elements. + + :param getMethodName: Name of the bound method that retrieves an element by index. + + :returns: |class_-function-returns| + + .. code-block:: cpp + + class_("MyContainer") + .function("size", &MyContainer::size) + .function("get", &MyContainer::get) + .iterable("size", "get"); + + .. code-block:: javascript + + const container = new Module.MyContainer(); + for (const item of container) { /* ... */ } + const arr = Array.from(container); + .. cpp:function:: const class_& property() const @@ -650,8 +682,8 @@ Classes template::value>::type> EMSCRIPTEN_ALWAYS_INLINE const class_& property(const char* fieldName, const FieldType ClassType::*field) const - :param const char* fieldName - :param const FieldType ClassType\:\:\*field + :param const char* fieldName: + :param const FieldType ClassType\:\:\*field: :returns: |class_-function-returns| @@ -664,8 +696,8 @@ Classes template::value>::type> EMSCRIPTEN_ALWAYS_INLINE const class_& property(const char* fieldName, FieldType ClassType::*field) const - :param const char* fieldName - :param FieldType ClassType\:\:\*field + :param const char* fieldName: + :param FieldType ClassType\:\:\*field: :returns: |class_-function-returns| @@ -684,8 +716,8 @@ Classes ``Getter`` is a function object, the property type must be specified as a template parameter as it cannot be deduced, e.g.: ``myClass.property("myIntProperty", MyIntGetterFunctor());`` - :param const char* fieldName - :param Getter getter Note that ``Getter`` is a function template typename. + :param const char* fieldName: + :param Getter getter: Note that ``Getter`` is a function template typename. :returns: |class_-function-returns| @@ -704,7 +736,7 @@ Classes property type must be specified as a template parameter as it cannot be deduced, e.g.: ``myClass.property("myIntProperty", MyIntGetterFunctor(), MyIntSetterFunctor());`` - :param const char* fieldName + :param const char* fieldName: :param Getter getter: Note that ``Getter`` is a function template typename. :param Setter setter: Note that ``Setter`` is a function template typename. :returns: |class_-function-returns| @@ -727,8 +759,8 @@ Classes A method name specified in the human-readable well-known symbol format (e.g., ``@@species``) is bound using the named ``Symbol`` for JavaScript (e.g., ``Symbol.species``). - :param const char* methodName - :param ReturnType (\*classMethod)(Args...) + :param const char* methodName: + :param ReturnType (\*classMethod)(Args...): :param Policies...: |policies-argument| :returns: |class_-function-returns| @@ -740,8 +772,8 @@ Classes template EMSCRIPTEN_ALWAYS_INLINE const class_& property(const char* fieldName, FieldType *field) const - :param const char* fieldName - :param FieldType ClassType\:\:\*field + :param const char* fieldName: + :param FieldType ClassType\:\:\*field: :returns: |class_-function-returns| @@ -762,7 +794,7 @@ Vectors A function to register a ``std::vector``. - :param const char* name + :param const char* name: Maps @@ -778,7 +810,7 @@ Maps A function to register a ``std::map``. - :param const char* name + :param const char* name: @@ -804,19 +836,31 @@ Enums A typedef of ``EnumType`` (a typename for the class). - .. cpp:function:: enum_(const char* name) + .. cpp:function:: enum_(const char* name, enum_value_type valueType = enum_value_type::object) Constructor. - :param const char* name + :param const char* name: Name of the enum in JavaScript. + :param enum_value_type valueType: + Determines how the enumerated values are represented in JavaScript. + + Possible values: + + - ``enum_value_type::object`` (default): + Values are JavaScript objects with a ``.value`` field. + + - ``enum_value_type::number``: + Values are plain numbers matching their corresponding C++ values. + - ``enum_value_type::string``: + Values are strings containing their name. .. cpp:function:: enum_& value(const char* name, EnumType value) Registers an enum value. :param const char* name: The name of the enumerated value. - :param EnumType value: The type of the enumerated value. + :param EnumType value: The enumerated value. :returns: A reference to the current object. This allows chaining of multiple enum values in the :cpp:func:`EMSCRIPTEN_BINDINGS` block. diff --git a/site/source/docs/api_reference/console.h.rst b/site/source/docs/api_reference/console.h.rst index 109d69ddf6cb6..076f8b16bdfde 100644 --- a/site/source/docs/api_reference/console.h.rst +++ b/site/source/docs/api_reference/console.h.rst @@ -51,6 +51,6 @@ Functions Prints the string using the `dbg()` JS function, which by will write to the console (or stdout). Just like the `dbg()` JS function this symbol is only available in debug builds (i.e. when linking with `-sASSERTIONS` or - equivelently `-O0`). + equivalently `-O0`). :param utf8String: A string encoded as UTF-8. diff --git a/site/source/docs/api_reference/emscripten.h.rst b/site/source/docs/api_reference/emscripten.h.rst index a29edbc51785a..fdb9567ffcd86 100644 --- a/site/source/docs/api_reference/emscripten.h.rst +++ b/site/source/docs/api_reference/emscripten.h.rst @@ -349,7 +349,7 @@ Guide material for the following APIs can be found in :ref:`emscripten-runtime-e Functions --------- -.. c:function:: void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop) +.. c:function:: void emscripten_set_main_loop(em_callback_func func, int fps, bool simulate_infinite_loop) Set a C function as the main event loop for the calling thread. @@ -373,10 +373,10 @@ Functions :param em_callback_func func: C function to set as main event loop for the calling thread. :param int fps: Number of frames per second that the JavaScript will call the function. Setting ``int <=0`` (recommended) uses the browser’s ``requestAnimationFrame`` mechanism to call the function. - :param int simulate_infinite_loop: If true, this function will throw an exception in order to stop execution of the caller. + :param bool simulate_infinite_loop: If true, this function will throw an exception in order to stop execution of the caller. -.. c:function:: void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, int simulate_infinite_loop) +.. c:function:: void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, bool simulate_infinite_loop) Set a C function as the main event loop for the calling thread, passing it user-defined data. @@ -385,7 +385,7 @@ Functions :param em_arg_callback_func func: C function to set as main event loop. The function signature must have a ``void*`` parameter for passing the ``arg`` value. :param void* arg: User-defined data passed to the main loop function, untouched by the API itself. :param int fps: Number of frames per second at which the JavaScript will call the function. Setting ``int <=0`` (recommended) uses the browser’s ``requestAnimationFrame`` mechanism to call the function. - :param int simulate_infinite_loop: If true, this function will throw an exception in order to stop execution of the caller. + :param bool simulate_infinite_loop: If true, this function will throw an exception in order to stop execution of the caller. .. c:function:: void emscripten_push_main_loop_blocker(em_arg_callback_func func, void *arg) @@ -399,7 +399,7 @@ Functions .. note:: - - Main loop blockers block the main loop from running, and can be counted to show progress. In contrast, ``emscripten_async_calls`` are not counted, do not block the main loop, and can fire at specific time in the future. + - Main loop blockers block the main loop from running, and can be counted to show progress. In contrast, :c:func:`emscripten_async_call` is not counted, does not block the main loop, and can fire at a specific time in the future. :param em_arg_callback_func func: The main loop blocker function. The function signature must have a ``void*`` parameter for passing the ``arg`` value. :param void* arg: User-defined arguments to pass to the blocker function. @@ -789,7 +789,7 @@ Functions :param int flags: See dlopen flags. :param void* user_data: User data passed to onsuccess, and onerror callbacks. :param em_dlopen_callback onsuccess: Called if the library was loaded successfully. - :param em_arg_callback_func onerror: Called if there as an error loading the library. + :param em_arg_callback_func onerror: Called if there is an error loading the library. Asynchronous IndexedDB API @@ -1434,22 +1434,6 @@ Functions local state all the way up the stack. As a result, it will add overhead to your program. -.. c:function:: void emscripten_lazy_load_code() - - This creates two Wasm files at compile time: the first Wasm which is - downloaded and run normally, and a second that is lazy-loaded. When an - ``emscripten_lazy_load_code()`` call is reached, we load the second Wasm - and resume execution using it. - - The idea here is that the initial download can be quite small, if you - place enough ``emscripten_lazy_load_code()`` calls in your codebase, as - the optimizer can remove code from the first Wasm if it sees it can't - be reached. The second downloaded Wasm can contain your full codebase, - including rarely-used functions, in which case the lazy-loading may - not happen at all. - - .. note:: This requires building with ``-sASYNCIFY_LAZY_LOAD_CODE``. - ABI functions ============= diff --git a/site/source/docs/api_reference/fetch.rst b/site/source/docs/api_reference/fetch.rst index 156c9a798e0f3..3c0cf98ee66f4 100644 --- a/site/source/docs/api_reference/fetch.rst +++ b/site/source/docs/api_reference/fetch.rst @@ -87,7 +87,7 @@ attributes: } For a full example, see the file -``test/fetch/example_async_xhr_to_memory_via_indexeddb.c``. +``test/fetch/test_fetch_persist.c``. Persisting data bytes from memory --------------------------------- @@ -200,55 +200,8 @@ emscripten_fetch() returns. - ``--proxy-to-worker`` + ``-pthread``: Synchronous Synchronous Fetch operations are available both on the main thread and pthreads. -Waitable Fetches -================ - -Emscripten Fetch operations can also run in a third mode, called a *waitable* -fetch. Waitable fetches start off as asynchronous, but at any point after the -fetch has started, the calling thread can issue a wait operation to either wait -for the completion of the fetch, or to just poll whether the fetch operation has -yet completed. The following code sample illustrates how this works. - -.. code-block:: cpp - - int main() { - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - strcpy(attr.requestMethod, "GET"); - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_WAITABLE; - emscripten_fetch_t *fetch = emscripten_fetch(&attr, "file.dat"); // Starts as asynchronous. - - EMSCRIPTEN_RESULT ret = EMSCRIPTEN_RESULT_TIMED_OUT; - while(ret == EMSCRIPTEN_RESULT_TIMED_OUT) { - /* possibly do some other work; */ - ret = emscripten_fetch_wait(fetch, 0/*milliseconds to wait, 0 to just poll, INFINITY=wait until completion*/); - } - // The operation has finished, safe to examine the fields of the 'fetch' pointer now. - - if (fetch->status == 200) { - printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); - // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; - } else { - printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); - } - emscripten_fetch_close(fetch); - } - -Waitable fetches allow interleaving multiple tasks in one thread so that the -issuing thread can perform some other work until the fetch completes. - -.. note:: - - Waitable fetches are available only in certain build modes: - - - **No flags** or ``--proxy-to-worker``: Waitable fetches are not available. - - ``-pthread``: Waitable fetches are available on pthreads, but not - on the main thread. - - ``--proxy-to-worker`` + ``-pthread``: Waitable fetches are - available on all threads. - Tracking Progress -==================== +================= For robust fetch management, there are several fields available to track the status of an XHR. @@ -395,6 +348,16 @@ be dealt with separately. emscripten_fetch(&attr, "myfile.dat"); } +Redirections +============ + +The XHR requests issued by the Fetch API are subject to the usual browser +redirection behavior (transparently followed except when infinitely looping). +When this happens, the `emscripten_fetch_t` structure will contain the final URL +after the redirection in the `responseUrl` field. Many HTTP status codes in the 3xx +range are used for redirection, and the Fetch API will follow these, in particular +the 301 (Moved Permanently), 302 (Found), 303 (See Other), 307 (Temporary Redirect), +and 308 (Permanent Redirect) status codes. TODO To Document ================ diff --git a/site/source/docs/api_reference/html5.h.rst b/site/source/docs/api_reference/html5.h.rst index 6e087a20a230c..6d55bd1e5c681 100644 --- a/site/source/docs/api_reference/html5.h.rst +++ b/site/source/docs/api_reference/html5.h.rst @@ -53,7 +53,7 @@ The typical format of registration functions is as follows (some methods may omi EMSCRIPTEN_RESULT emscripten_set_some_callback( const char *target, // ID of the target HTML element. void *userData, // User-defined data to be passed to the callback. - EM_BOOL useCapture, // Whether or not to use capture. + bool useCapture, // Whether or not to use capture. em_someevent_callback_func callback // Callback function. ); @@ -90,13 +90,61 @@ The ``useCapture`` parameter maps to ``useCapture`` in `EventTarget.addEventLis Most functions return the result using the type :c:data:`EMSCRIPTEN_RESULT`. Zero and positive values denote success. Negative values signal failure. None of the functions fail or abort by throwing a JavaScript or C++ exception. If a particular browser does not support the given feature, the value :c:data:`EMSCRIPTEN_RESULT_NOT_SUPPORTED` will be returned at the time the callback is registered. +Unregister function +------------------- + +In order to unregister a single event handler callback, call the following function: + + .. code-block:: cpp + + EMSCRIPTEN_RESULT emscripten_html5_remove_event_listener( + const char *target, // ID of the target HTML element. + void *userData, // User-defined data (passed to the callback). + int eventTypeId, // The event type ID (EMSCRIPTEN_EVENT_XXX). + void *callback // Callback function. + ); + + +The ``target``, ``userData`` and ``callback`` parameters are the same parameters provided in ``emscripten_set_some_callback`` with the only difference being that, since this function applies to all types of callbacks, the type of ``callback`` is ``void *``. + +Note in particular that the value of ``userData`` will need to match with the call that was used to register the callback. If you are having trouble, double check the value of ``userData``. + +The ``eventTypeId`` represents the event type, the same Id received in the callback functions. + +The function returns ``EMSCRIPTEN_RESULT_SUCCESS`` when the event handler callback is removed and ``EMSCRIPTEN_RESULT_INVALID_PARAM`` otherwise. + + .. code-block:: cpp + + // Example + + bool my_mouse_callback_1(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + // ... + } + + bool my_mouse_callback_2(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + // ... + } + + void main() { + + // 1. set callbacks for mouse down and mouse move + emscripten_set_mousedown_callback("#mydiv", 0, my_mouse_callback_1); + emscripten_set_mousedown_callback("#mydiv", (void *) 34, my_mouse_callback_2); + emscripten_set_mousemove_callback("#mydiv", 0, my_mouse_callback_1); + + // 2. remove these callbacks + emscripten_html5_remove_event_listener("#mydiv", 0, EMSCRIPTEN_EVENT_MOUSEDOWN, my_mouse_callback_1); + emscripten_html5_remove_event_listener("#mydiv", (void *) 34, EMSCRIPTEN_EVENT_MOUSEDOWN, my_mouse_callback_2); + emscripten_html5_remove_event_listener("#mydiv", 0, EMSCRIPTEN_EVENT_MOUSEMOVE, my_mouse_callback_1); + } + Callback functions ------------------ When the event occurs the callback is invoked with the relevant event "type" (for example :c:data:`EMSCRIPTEN_EVENT_CLICK`), a ``struct`` containing the details of the event that occurred, and the ``userData`` that was originally passed to the registration function. The general format of the callback function is: :: - typedef EM_BOOL (*em_someevent_callback_func) // Callback function. Return true if event is "consumed". + typedef bool (*em_someevent_callback_func) // Callback function. Return true if event is "consumed". ( int eventType, // The type of event. const EmscriptenSomeEvent *someEvent, // Information about the event. @@ -106,7 +154,7 @@ When the event occurs the callback is invoked with the relevant event "type" (fo .. _callback-handler-return-em_bool-html5-api: -Callback handlers that return an :c:data:`EM_BOOL` may specify ``true`` to signal that the handler *consumed* the event (this suppresses the default action for that event by calling its ``.preventDefault();`` member). Returning ``false`` indicates that the event was not consumed — the default browser event action is carried out and the event is allowed to pass on/bubble up as normal. +Callback handlers that return a ``bool`` may specify ``true`` to signal that the handler *consumed* the event (this suppresses the default action for that event by calling its ``.preventDefault();`` member). Returning ``false`` indicates that the event was not consumed — the default browser event action is carried out and the event is allowed to pass on/bubble up as normal. Calling a registration function with a ``null`` pointer for the callback causes a de-registration of that callback from the given ``target`` element. All event handlers are also automatically unregistered when the C ``exit()`` function is invoked during the ``atexit`` handler pass. Either use the function :c:func:`emscripten_set_main_loop` or set ``Module.noExitRuntime = true;`` to make sure that leaving ``main()`` will not immediately cause an ``exit()`` and clean up the event handlers. @@ -131,35 +179,20 @@ Test/Example code The HTML5 test code demonstrates how to use this API: - - `test_html5_core.c `_ - - `test_html5_fullscreen.c `_ - - `test_html5_mouse.c `_ + - `test_html5_core.c `_ + - `test_html5_fullscreen.c `_ + - `test_html5_mouse.c `_ General types ============= -.. c:macro:: EM_BOOL - - This is the Emscripten type for a ``bool``. - Possible values: - - .. c:macro:: EM_TRUE - - This is the Emscripten value for ``true``. - - .. c:macro:: EM_FALSE - - This is the Emscripten value for ``false``. - - .. c:macro:: EM_UTF8 This is the Emscripten type for a UTF8 string (maps to a ``char``). This is used for node names, element ids, etc. - Function result values ====================== @@ -259,18 +292,18 @@ Struct Maximum size 32 ``char`` (i.e. ``EM_UTF8 code[32]``). - .. c:member:: unsigned long location + .. c:member:: unsigned int location Indicates the location of the key on the keyboard. One of the :c:data:`DOM_KEY_LOCATION ` values. - .. c:member:: EM_BOOL ctrlKey - EM_BOOL shiftKey - EM_BOOL altKey - EM_BOOL metaKey + .. c:member:: bool ctrlKey + bool shiftKey + bool altKey + bool metaKey Specifies which modifiers were active during the key event. - .. c:member:: EM_BOOL repeat + .. c:member:: bool repeat Specifies if this keyboard event represents a repeated press. @@ -288,24 +321,24 @@ Struct .. warning:: This attribute has been dropped from DOM Level 3 events. - .. c:member:: unsigned long charCode + .. c:member:: unsigned int charCode The Unicode reference number of the key; this attribute is used only by the keypress event. For keys whose ``char`` attribute contains multiple characters, this is the Unicode value of the first character in that attribute. .. warning:: This attribute is deprecated, you should use the field ``key`` instead, if available. - .. c:member:: unsigned long keyCode + .. c:member:: unsigned int keyCode A system and implementation dependent numerical code identifying the unmodified value of the pressed key. .. warning:: This attribute is deprecated, you should use the field ``key`` instead, if available. - .. c:member:: unsigned long which + .. c:member:: unsigned int which A system and implementation dependent numeric code identifying the unmodified value of the pressed key; this is usually the same as ``keyCode``. - .. warning:: This attribute is deprecated, you should use the field ``key`` instead, if available. Note thought that while this field is deprecated, the cross-browser support for ``which`` may be better than for the other fields, so experimentation is recommended. Read issue https://github.com/emscripten-core/emscripten/issues/2817 for more information. + .. warning:: This attribute is deprecated, you should use the field ``key`` instead, if available. Note though that while this field is deprecated, the cross-browser support for ``which`` may be better than for the other fields, so experimentation is recommended. Read issue https://github.com/emscripten-core/emscripten/issues/2817 for more information. Callback functions @@ -317,29 +350,29 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_key_callback_func)(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); + typedef bool (*em_key_callback_func)(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); :param int eventType: The type of :c:data:`key event `. :param keyEvent: Information about the key event that occurred. :type keyEvent: const EmscriptenKeyboardEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_keypress_callback(const char *target, void *userData, EM_BOOL useCapture, em_key_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_keydown_callback(const char *target, void *userData, EM_BOOL useCapture, em_key_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_keyup_callback(const char *target, void *userData, EM_BOOL useCapture, em_key_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_keypress_callback(const char *target, void *userData, bool useCapture, em_key_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_keydown_callback(const char *target, void *userData, bool useCapture, em_key_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_keyup_callback(const char *target, void *userData, bool useCapture, em_key_callback_func callback) Registers a callback function for receiving browser-generated keyboard input events. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_key_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -379,21 +412,21 @@ Struct Absolute wallclock time when the data was recorded (milliseconds). - .. c:member:: long screenX - long screenY + .. c:member:: int screenX + int screenY The coordinates relative to the browser screen coordinate system. - .. c:member:: long clientX - long clientY + .. c:member:: int clientX + int clientY The coordinates relative to the viewport associated with the event. - .. c:member:: EM_BOOL ctrlKey - EM_BOOL shiftKey - EM_BOOL altKey - EM_BOOL metaKey + .. c:member:: bool ctrlKey + bool shiftKey + bool altKey + bool metaKey Specifies which modifiers were active during the mouse event. @@ -411,24 +444,24 @@ Struct A bitmask that indicates which combinations of mouse buttons were being held down at the time of the event. - .. c:member:: long movementX - long movementY; + .. c:member:: int movementX + int movementY; If pointer lock is active, these two extra fields give relative mouse movement since the last event. - .. c:member:: long targetX - long targetY + .. c:member:: int targetX + int targetY These fields give the mouse coordinates mapped relative to the coordinate space of the target DOM element receiving the input events (Emscripten-specific extension; coordinates are rounded down to the nearest integer). - .. c:member:: long canvasX - long canvasY + .. c:member:: int canvasX + int canvasY - These fields give the mouse coordinates mapped to the Emscripten canvas client area (Emscripten-specific extension; coordinates are rounded down the nearest integer). + These fields give the mouse coordinates mapped to the Emscripten canvas client area (Emscripten-specific extension; coordinates are rounded down to the nearest integer). - .. c:member:: long padding + .. c:member:: int padding Internal, and can be ignored. @@ -444,34 +477,34 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_mouse_callback_func)(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + typedef bool (*em_mouse_callback_func)(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); :param int eventType: The type of :c:data:`mouse event `. :param mouseEvent: Information about the mouse event that occurred. :type mouseEvent: const EmscriptenMouseEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_click_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_mousedown_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_mouseup_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_dblclick_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_mousemove_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_mouseenter_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_mouseleave_callback(const char *target, void *userData, EM_BOOL useCapture, em_mouse_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_click_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_mousedown_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_mouseup_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_dblclick_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_mousemove_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_mouseenter_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_mouseleave_callback(const char *target, void *userData, bool useCapture, em_mouse_callback_func callback) Registers a callback function for receiving browser-generated `mouse input events `_. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_mouse_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -482,7 +515,7 @@ Functions Returns the most recently received mouse event state. - Note that for this function call to succeed, :c:func:`emscripten_set_xxx_callback ` must have first been called with one of the mouse event types and a non-zero callback function pointer to enable the Mouse state capture. + Note that for this function call to succeed, :c:func:`emscripten_set_xxx_callback ` must have first been called with one of the mouse event types and a non-zero callback function pointer to enable the mouse state capture. :param EmscriptenMouseEvent* mouseState: The most recently received mouse event state. :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. @@ -528,11 +561,11 @@ Struct double deltaY double deltaZ - Movement of the wheel on each of the axis. Note that these values may be fractional, so you should avoid simply casting them to integer, or it might result + Movement of the wheel on each of the axes. Note that these values may be fractional, so you should avoid simply casting them to integer, or it might result in scroll values of 0. The positive Y scroll direction is when scrolling the page downwards (page CSS pixel +Y direction), which corresponds to scrolling the mouse wheel downwards (away from the screen) on Windows, Linux, and also on macOS when the 'natural scroll' option is disabled. - .. c:member:: unsigned long deltaMode + .. c:member:: unsigned int deltaMode One of the :c:data:`DOM_DELTA_` values that indicates the units of measurement for the delta values. @@ -546,28 +579,28 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_wheel_callback_func)(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); + typedef bool (*em_wheel_callback_func)(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); :param int eventType: The type of wheel event (:c:data:`EMSCRIPTEN_EVENT_WHEEL`). :param wheelEvent: Information about the wheel event that occurred. :type wheelEvent: const EmscriptenWheelEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_wheel_callback(const char *target, void *userData, EM_BOOL useCapture, em_wheel_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_wheel_callback(const char *target, void *userData, bool useCapture, em_wheel_callback_func callback) Registers a callback function for receiving browser-generated `mousewheel events `_. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_wheel_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -594,7 +627,7 @@ Struct The event structure passed in DOM element `UIEvent `_ events: `resize `_ and `scroll `_. - .. c:member:: long detail + .. c:member:: int detail For resize and scroll events this is always zero. @@ -628,21 +661,21 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_ui_callback_func)(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); + typedef bool (*em_ui_callback_func)(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); :param int eventType: The type of UI event (:c:data:`EMSCRIPTEN_EVENT_RESIZE`). :param uiEvent: Information about the UI event that occurred. :type uiEvent: const EmscriptenUiEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_resize_callback(const char *target, void *userData, EM_BOOL useCapture, em_ui_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_scroll_callback(const char *target, void *userData, EM_BOOL useCapture, em_ui_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_resize_callback(const char *target, void *userData, bool useCapture, em_ui_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_scroll_callback(const char *target, void *userData, bool useCapture, em_ui_callback_func callback) Registers a callback function for receiving DOM element `resize `_ and `scroll `_ events. @@ -654,7 +687,7 @@ Functions :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_ui_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -706,31 +739,31 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_focus_callback_func)(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); + typedef bool (*em_focus_callback_func)(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); :param int eventType: The type of focus event (:c:data:`EMSCRIPTEN_EVENT_BLUR`). :param focusEvent: Information about the focus event that occurred. :type focusEvent: const EmscriptenFocusEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_blur_callback(const char *target, void *userData, EM_BOOL useCapture, em_focus_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_focus_callback(const char *target, void *userData, EM_BOOL useCapture, em_focus_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_focusin_callback(const char *target, void *userData, EM_BOOL useCapture, em_focus_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_focusout_callback(const char *target, void *userData, EM_BOOL useCapture, em_focus_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_blur_callback(const char *target, void *userData, bool useCapture, em_focus_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_focus_callback(const char *target, void *userData, bool useCapture, em_focus_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_focusin_callback(const char *target, void *userData, bool useCapture, em_focus_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_focusout_callback(const char *target, void *userData, bool useCapture, em_focus_callback_func callback) Registers a callback function for receiving DOM element `blur `_, `focus `_, `focusin `_ and `focusout `_ events. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_focus_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -761,7 +794,7 @@ Struct The `orientation `_ of the device in terms of the transformation from a coordinate frame fixed on the Earth to a coordinate frame fixed in the device. - The image (source: `dev.opera.com `_) and definitions below illustrate the co-ordinate frame: + The image (source: `dev.opera.com `_) and definitions below illustrate the coordinate frame: - :c:type:`~EmscriptenDeviceOrientationEvent.alpha`: the rotation of the device around the Z axis. - :c:type:`~EmscriptenDeviceOrientationEvent.beta`: the rotation of the device around the X axis. @@ -772,7 +805,7 @@ Struct :alt: Image of device showing X, Y, Z axes - .. c:member:: EM_BOOL absolute + .. c:member:: bool absolute If ``false``, the orientation is only relative to some other base orientation, not to the fixed coordinate frame. @@ -786,26 +819,26 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_deviceorientation_callback_func)(int eventType, const EmscriptenDeviceOrientationEvent *deviceOrientationEvent, void *userData); + typedef bool (*em_deviceorientation_callback_func)(int eventType, const EmscriptenDeviceOrientationEvent *deviceOrientationEvent, void *userData); :param int eventType: The type of orientation event (:c:data:`EMSCRIPTEN_EVENT_DEVICEORIENTATION`). :param deviceOrientationEvent: Information about the orientation event that occurred. :type deviceOrientationEvent: const EmscriptenDeviceOrientationEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_deviceorientation_callback(void *userData, EM_BOOL useCapture, em_deviceorientation_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_deviceorientation_callback(void *userData, bool useCapture, em_deviceorientation_callback_func callback) Registers a callback function for receiving the `deviceorientation `_ event. :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_deviceorientation_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -815,7 +848,7 @@ Functions Returns the most recently received ``deviceorientation`` event state. - Note that for this function call to succeed, :c:func:`emscripten_set_deviceorientation_callback` must have first been called with one of the mouse event types and a non-zero callback function pointer to enable the ``deviceorientation`` state capture. + Note that for this function call to succeed, :c:func:`emscripten_set_deviceorientation_callback` must have first been called with a non-zero callback function pointer to enable the ``deviceorientation`` state capture. :param orientationState: The most recently received ``deviceorientation`` event state. :type orientationState: EmscriptenDeviceOrientationEvent* @@ -877,14 +910,14 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_devicemotion_callback_func)(int eventType, const EmscriptenDeviceMotionEvent *deviceMotionEvent, void *userData); + typedef bool (*em_devicemotion_callback_func)(int eventType, const EmscriptenDeviceMotionEvent *deviceMotionEvent, void *userData); :param int eventType: The type of devicemotion event (:c:data:`EMSCRIPTEN_EVENT_DEVICEMOTION`). :param deviceMotionEvent: Information about the devicemotion event that occurred. :type deviceMotionEvent: const EmscriptenDeviceMotionEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool @@ -892,12 +925,12 @@ Callback functions Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_devicemotion_callback(void *userData, EM_BOOL useCapture, em_devicemotion_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_devicemotion_callback(void *userData, bool useCapture, em_devicemotion_callback_func callback) Registers a callback function for receiving the `devicemotion `_ event. :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_devicemotion_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -907,7 +940,7 @@ Functions Returns the most recently received `devicemotion `_ event state. - Note that for this function call to succeed, :c:func:`emscripten_set_devicemotion_callback` must have first been called with one of the mouse event types and a non-zero callback function pointer to enable the ``devicemotion`` state capture. + Note that for this function call to succeed, :c:func:`emscripten_set_devicemotion_callback` must have first been called with a non-zero callback function pointer to enable the ``devicemotion`` state capture. :param motionState: The most recently received ``devicemotion`` event state. :type motionState: EmscriptenDeviceMotionEvent* @@ -976,25 +1009,25 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_orientationchange_callback_func)(int eventType, const EmscriptenOrientationChangeEvent *orientationChangeEvent, void *userData); + typedef bool (*em_orientationchange_callback_func)(int eventType, const EmscriptenOrientationChangeEvent *orientationChangeEvent, void *userData); :param int eventType: The type of orientationchange event (:c:data:`EMSCRIPTEN_EVENT_ORIENTATIONCHANGE`). :param orientationChangeEvent: Information about the orientationchange event that occurred. :type orientationChangeEvent: const EmscriptenOrientationChangeEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_orientationchange_callback(void *userData, EM_BOOL useCapture, em_orientationchange_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_orientationchange_callback(void *userData, bool useCapture, em_orientationchange_callback_func callback) Registers a callback function for receiving the `orientationchange `_ event. :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_orientationchange_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1105,12 +1138,12 @@ Struct The event structure passed in the `fullscreenchange `_ event. - .. c:member:: EM_BOOL isFullscreen + .. c:member:: bool isFullscreen Specifies whether an element on the browser page is currently fullscreen. - .. c:member:: EM_BOOL fullscreenEnabled + .. c:member:: bool fullscreenEnabled Specifies if the current page has the ability to display elements fullscreen. @@ -1176,28 +1209,28 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_fullscreenchange_callback_func)(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData); + typedef bool (*em_fullscreenchange_callback_func)(int eventType, const EmscriptenFullscreenChangeEvent *fullscreenChangeEvent, void *userData); :param int eventType: The type of fullscreen event (:c:data:`EMSCRIPTEN_EVENT_FULLSCREENCHANGE`). :param fullscreenChangeEvent: Information about the fullscreen event that occurred. :type fullscreenChangeEvent: const EmscriptenFullscreenChangeEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_fullscreenchange_callback(const char *target, void *userData, EM_BOOL useCapture, em_fullscreenchange_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_fullscreenchange_callback(const char *target, void *userData, bool useCapture, em_fullscreenchange_callback_func callback) Registers a callback function for receiving the `fullscreenchange `_ event. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_fullscreenchange_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1213,7 +1246,7 @@ Functions :rtype: |EMSCRIPTEN_RESULT| -.. c:function:: EMSCRIPTEN_RESULT emscripten_request_fullscreen(const char *target, EM_BOOL deferUntilInEventHandler) +.. c:function:: EMSCRIPTEN_RESULT emscripten_request_fullscreen(const char *target, bool deferUntilInEventHandler) Requests the given target element to transition to full screen mode. @@ -1223,12 +1256,12 @@ Functions :param target: |target-parameter-doc| :type target: const char* - :param EM_BOOL deferUntilInEventHandler: If ``true`` requests made outside of a user-generated event handler are automatically deferred until the user next presses a keyboard or mouse button. If ``false`` the request will fail if called outside of a user-generated event handler. + :param bool deferUntilInEventHandler: If ``true`` requests made outside of a user-generated event handler are automatically deferred until the user next presses a keyboard or mouse button. If ``false`` the request will fail if called outside of a user-generated event handler. :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: **EMSCRIPTEN_RESULT** -.. c:function:: EMSCRIPTEN_RESULT emscripten_request_fullscreen_strategy(const char *target, EM_BOOL deferUntilInEventHandler, const EmscriptenFullscreenStrategy *fullscreenStrategy) +.. c:function:: EMSCRIPTEN_RESULT emscripten_request_fullscreen_strategy(const char *target, bool deferUntilInEventHandler, const EmscriptenFullscreenStrategy *fullscreenStrategy) Requests the given target element to transition to full screen mode, using a custom presentation mode for the element. This function is otherwise the same as :c:func:`emscripten_request_fullscreen`, but this function adds options to control how resizing and aspect ratio, and ensures that the behavior is consistent across browsers. @@ -1278,7 +1311,7 @@ Struct The event structure passed in the `pointerlockchange `_ event. - .. c:member:: EM_BOOL isActive + .. c:member:: bool isActive Specifies whether an element on the browser page currently has pointer lock enabled. @@ -1304,14 +1337,14 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_pointerlockchange_callback_func)(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); + typedef bool (*em_pointerlockchange_callback_func)(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); :param int eventType: The type of pointerlockchange event (:c:data:`EMSCRIPTEN_EVENT_POINTERLOCKCHANGE`). :param pointerlockChangeEvent: Information about the pointerlockchange event that occurred. :type pointerlockChangeEvent: const EmscriptenPointerlockChangeEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool .. c:type:: em_pointerlockerror_callback_func @@ -1319,20 +1352,20 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_pointerlockerror_callback_func)(int eventType, const void *reserved, void *userData); + typedef bool (*em_pointerlockerror_callback_func)(int eventType, const void *reserved, void *userData); :param int eventType: The type of pointerlockerror event (:c:data:`EMSCRIPTEN_EVENT_POINTERLOCKERROR`). :param const void* reserved: Reserved for future use; pass in 0. :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_pointerlockchange_callback(const char *target, void *userData, EM_BOOL useCapture, em_pointerlockchange_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_pointerlockchange_callback(const char *target, void *userData, bool useCapture, em_pointerlockchange_callback_func callback) Registers a callback function for receiving the `pointerlockchange `_ event. @@ -1341,21 +1374,21 @@ Functions :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_pointerlockchange_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_pointerlockerror_callback(const char *target, void *userData, EM_BOOL useCapture, em_pointerlockerror_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_pointerlockerror_callback(const char *target, void *userData, bool useCapture, em_pointerlockerror_callback_func callback) Registers a callback function for receiving the `pointerlockerror `_ event. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_pointerlockerror_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1371,7 +1404,7 @@ Functions :rtype: |EMSCRIPTEN_RESULT| -.. c:function:: EMSCRIPTEN_RESULT emscripten_request_pointerlock(const char *target, EM_BOOL deferUntilInEventHandler) +.. c:function:: EMSCRIPTEN_RESULT emscripten_request_pointerlock(const char *target, bool deferUntilInEventHandler) Requests the given target element to grab pointerlock. @@ -1380,7 +1413,7 @@ Functions :param target: |target-parameter-doc| :type target: const char* - :param EM_BOOL deferUntilInEventHandler: If ``true`` requests made outside of a user-generated event handler are automatically deferred until the user next presses a keyboard or mouse button. If ``false`` the request will fail if called outside of a user-generated event handler. + :param bool deferUntilInEventHandler: If ``true`` requests made outside of a user-generated event handler are automatically deferred until the user next presses a keyboard or mouse button. If ``false`` the request will fail if called outside of a user-generated event handler. :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1428,7 +1461,7 @@ Struct The event structure passed in the `visibilitychange `__ event. - .. c:member:: EM_BOOL hidden + .. c:member:: bool hidden If true, the current browser page is now hidden. @@ -1447,25 +1480,25 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_visibilitychange_callback_func)(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); + typedef bool (*em_visibilitychange_callback_func)(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); :param int eventType: The type of ``visibilitychange`` event (:c:data:`EMSCRIPTEN_VISIBILITY_HIDDEN`). :param visibilityChangeEvent: Information about the ``visibilitychange`` event that occurred. :type visibilityChangeEvent: const EmscriptenVisibilityChangeEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_visibilitychange_callback(void *userData, EM_BOOL useCapture, em_visibilitychange_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_visibilitychange_callback(void *userData, bool useCapture, em_visibilitychange_callback_func callback) Registers a callback function for receiving the `visibilitychange `_ event. :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_visibilitychange_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1502,40 +1535,40 @@ Struct Specifies the status of a single `touch point `_ on the page. - .. c:member:: long identifier + .. c:member:: int identifier An identification number for each touch point. - .. c:member:: long screenX - long screenY + .. c:member:: int screenX + int screenY The touch coordinate relative to the whole screen origin, in pixels. - .. c:member:: long clientX - long clientY + .. c:member:: int clientX + int clientY The touch coordinate relative to the viewport, in pixels. - .. c:member:: long pageX - long pageY + .. c:member:: int pageX + int pageY The touch coordinate relative to the viewport, in pixels, and including any scroll offset. - .. c:member:: EM_BOOL isChanged + .. c:member:: bool isChanged Specifies whether the touch point changed during this event. - .. c:member:: EM_BOOL onTarget + .. c:member:: bool onTarget Specifies whether this touch point is still above the original target on which it was initially pressed. - .. c:member:: long targetX - long targetY + .. c:member:: int targetX + int targetY These fields give the touch coordinates mapped relative to the coordinate space of the target DOM element receiving the input events (Emscripten-specific extension). - .. c:member:: long canvasX - long canvasY + .. c:member:: int canvasX + int canvasY The touch coordinates mapped to the Emscripten canvas client area, in pixels (Emscripten-specific extension). @@ -1554,10 +1587,10 @@ Struct The number of valid elements in the touches array. - .. c:member:: EM_BOOL ctrlKey - EM_BOOL shiftKey - EM_BOOL altKey - EM_BOOL metaKey + .. c:member:: bool ctrlKey + bool shiftKey + bool altKey + bool metaKey Specifies which modifiers were active during the touch event. @@ -1577,31 +1610,31 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_touch_callback_func)(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); + typedef bool (*em_touch_callback_func)(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); :param int eventType: The type of touch event (:c:data:`EMSCRIPTEN_EVENT_TOUCHSTART`). :param touchEvent: Information about the touch event that occurred. :type touchEvent: const EmscriptenTouchEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_touchstart_callback(const char *target, void *userData, EM_BOOL useCapture, em_touch_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_touchend_callback(const char *target, void *userData, EM_BOOL useCapture, em_touch_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_touchmove_callback(const char *target, void *userData, EM_BOOL useCapture, em_touch_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_touchcancel_callback(const char *target, void *userData, EM_BOOL useCapture, em_touch_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_touchstart_callback(const char *target, void *userData, bool useCapture, em_touch_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_touchend_callback(const char *target, void *userData, bool useCapture, em_touch_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_touchmove_callback(const char *target, void *userData, bool useCapture, em_touch_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_touchcancel_callback(const char *target, void *userData, bool useCapture, em_touch_callback_func callback) Registers a callback function for receiving `touch events `__ : `touchstart `_, `touchend `_, `touchmove `_ and `touchcancel `_. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_touch_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1650,15 +1683,15 @@ Struct The analog state of the gamepad buttons, in the range [0, 1]. - .. c:member:: EM_BOOL digitalButton[64] + .. c:member:: bool digitalButton[64] The digital state of the gamepad buttons, either 0 or 1. - .. c:member:: EM_BOOL connected + .. c:member:: bool connected Specifies whether this gamepad is connected to the browser page. - .. c:member:: long index + .. c:member:: int index An ordinal associated with this gamepad, zero-based. @@ -1685,27 +1718,27 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_gamepad_callback_func)(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) + typedef bool (*em_gamepad_callback_func)(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) :param int eventType: The type of gamepad event (:c:data:`EMSCRIPTEN_EVENT_GAMEPADCONNECTED`). :param gamepadEvent: Information about the gamepad event that occurred. :type gamepadEvent: const EmscriptenGamepadEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_gamepadconnected_callback(void *userData, EM_BOOL useCapture, em_gamepad_callback_func callback) - EMSCRIPTEN_RESULT emscripten_set_gamepaddisconnected_callback(void *userData, EM_BOOL useCapture, em_gamepad_callback_func callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_gamepadconnected_callback(void *userData, bool useCapture, em_gamepad_callback_func callback) + EMSCRIPTEN_RESULT emscripten_set_gamepaddisconnected_callback(void *userData, bool useCapture, em_gamepad_callback_func callback) Registers a callback function for receiving the gamepad_ events: `gamepadconnected `_ and `gamepaddisconnected `_. :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_gamepad_callback_func callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| @@ -1734,7 +1767,7 @@ Functions .. note:: Gamepad API uses an array of gamepad state objects to return the state of - each device. The devices are identified via the index they are present in in + each device. The devices are identified via the index they are present in this array. Because of that, if one first connects gamepad A, then gamepad B, and then disconnects gamepad A, the gamepad B shall not take the place of gamepad A, so in this scenario, this function will still keep returning two @@ -1770,7 +1803,7 @@ Defines .. c:macro:: EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE - Emscripten `batterymanager `_ events. + Emscripten `BatteryManager `_ events. Struct @@ -1778,7 +1811,7 @@ Struct .. c:type:: EmscriptenBatteryEvent - The event structure passed in the `batterymanager `_ events: ``chargingchange`` and ``levelchange``. + The event structure passed in the `BatteryManager `_ events: ``chargingchange`` and ``levelchange``. .. c:member:: double chargingTime @@ -1793,7 +1826,7 @@ Struct Current battery level, on a scale of 0 to 1.0. - .. c:member:: EM_BOOL charging; + .. c:member:: bool charging; ``true`` if the battery is charging, ``false`` otherwise. @@ -1803,18 +1836,18 @@ Callback functions .. c:type:: em_battery_callback_func - Function pointer for the :c:func:`batterymanager event callback functions `, defined as: + Function pointer for the :c:func:`BatteryManager event callback functions `, defined as: .. code-block:: cpp - typedef EM_BOOL (*em_battery_callback_func)(int eventType, const EmscriptenBatteryEvent *batteryEvent, void *userData); + typedef bool (*em_battery_callback_func)(int eventType, const EmscriptenBatteryEvent *batteryEvent, void *userData); - :param int eventType: The type of ``batterymanager`` event (:c:data:`EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE`). - :param batteryEvent: Information about the ``batterymanager`` event that occurred. + :param int eventType: The type of ``BatteryManager`` event (:c:data:`EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE`). + :param batteryEvent: Information about the ``BatteryManager`` event that occurred. :type batteryEvent: const EmscriptenBatteryEvent* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool @@ -1824,7 +1857,7 @@ Functions .. c:function:: EMSCRIPTEN_RESULT emscripten_set_batterychargingchange_callback(void *userData, em_battery_callback_func callback) EMSCRIPTEN_RESULT emscripten_set_batterylevelchange_callback(void *userData, em_battery_callback_func callback) - Registers a callback function for receiving the `batterymanager `_ events: ``chargingchange`` and ``levelchange``. + Registers a callback function for receiving the `BatteryManager `_ events: ``chargingchange`` and ``levelchange``. :param void* userData: |userData-parameter-doc| :param em_battery_callback_func callback: |callback-function-parameter-doc| @@ -1940,29 +1973,29 @@ Struct Specifies `WebGL context creation parameters `_. - .. c:member:: EM_BOOL alpha + .. c:member:: bool alpha If ``true``, request an alpha channel for the context. If you create an alpha channel, you can blend the canvas rendering with the underlying web page contents. Default value: ``true``. - .. c:member:: EM_BOOL depth + .. c:member:: bool depth If ``true``, request a depth buffer of at least 16 bits. If ``false``, no depth buffer will be initialized. Default value: ``true``. - .. c:member:: EM_BOOL stencil + .. c:member:: bool stencil If ``true``, request a stencil buffer of at least 8 bits. If ``false``, no stencil buffer will be initialized. Default value: ``false``. - .. c:member:: EM_BOOL antialias + .. c:member:: bool antialias If ``true``, antialiasing will be initialized with a browser-specified algorithm and quality level. If ``false``, antialiasing is disabled. Default value: ``true``. - .. c:member:: EM_BOOL premultipliedAlpha + .. c:member:: bool premultipliedAlpha If ``true``, the alpha channel of the rendering context will be treated as representing premultiplied alpha values. If ``false``, the alpha channel represents non-premultiplied alpha. Default value: ``true``. - .. c:member:: EM_BOOL preserveDrawingBuffer + .. c:member:: bool preserveDrawingBuffer If ``true``, the contents of the drawing buffer are preserved between consecutive ``requestAnimationFrame()`` calls. If ``false``, color, depth and stencil are cleared at the beginning of each ``requestAnimationFrame()``. Generally setting this to ``false`` gives better performance. Default value: ``false``. @@ -1971,7 +2004,7 @@ Struct Specifies a hint to the WebGL canvas implementation to how it should choose the use of available GPU resources. One of EM_WEBGL_POWER_PREFERENCE_DEFAULT, EM_WEBGL_POWER_PREFERENCE_LOW_POWER, EM_WEBGL_POWER_PREFERENCE_HIGH_PERFORMANCE. - .. c:member:: EM_BOOL failIfMajorPerformanceCaveat + .. c:member:: bool failIfMajorPerformanceCaveat If ``true``, requests context creation to abort if the browser is only able to create a context that does not give good hardware-accelerated performance. Default value: ``false``. @@ -1986,26 +2019,26 @@ Struct Default value: ``majorVersion=1``, ``minorVersion=0`` - .. c:member:: EM_BOOL enableExtensionsByDefault + .. c:member:: bool enableExtensionsByDefault If ``true``, all GLES2-compatible non-performance-impacting WebGL extensions will automatically be enabled for you after the context has been created. If ``false``, no extensions are enabled by default, and you need to manually call :c:func:`emscripten_webgl_enable_extension` to enable each extension that you want to use. Default value: ``true``. - .. c:member:: EM_BOOL explicitSwapControl + .. c:member:: bool explicitSwapControl By default, when ``explicitSwapControl`` is in its default state ``false``, rendered WebGL content is implicitly presented (displayed to the user) on the canvas when the event handler that renders with WebGL returns back to the browser event loop. If ``explicitSwapControl`` is set to ``true``, rendered content will not be displayed on screen automatically when event handler function finishes, but the control of swapping is given to the user to manage, via the ``emscripten_webgl_commit_frame()`` function. In order to be able to set ``explicitSwapControl==true``, support for it must explicitly be enabled either 1) via adding the ``-sOFFSCREEN_FRAMEBUFFER`` Emscripten linker flag, and enabling ``renderViaOffscreenBackBuffer==1``, or 2) via adding the linker flag ``-sOFFSCREENCANVAS_SUPPORT``, and running in a browser that supports OffscreenCanvas. - .. c:member:: EM_BOOL renderViaOffscreenBackBuffer + .. c:member:: bool renderViaOffscreenBackBuffer If ``true``, an extra intermediate backbuffer (offscreen render target) is allocated to the created WebGL context, and rendering occurs to this backbuffer instead of directly onto the WebGL "default backbuffer". This is required to be enabled if 1) ``explicitSwapControl==true`` and the browser does not support OffscreenCanvas, 2) when performing WebGL rendering in a worker thread and the browser does not support OffscreenCanvas, and 3) when performing WebGL context accesses from multiple threads simultaneously (independent of whether OffscreenCanvas is supported or not). Because supporting offscreen framebuffer adds some amount of extra code to the compiled output, support for it must explicitly be enabled via the ``-sOFFSCREEN_FRAMEBUFFER`` Emscripten linker flag. When building simultaneously with both ``-sOFFSCREEN_FRAMEBUFFER`` and ``-sOFFSCREENCANVAS_SUPPORT`` linker flags enabled, offscreen backbuffer can be used as a polyfill-like compatibility fallback to enable rendering WebGL from a pthread when the browser does not support the OffscreenCanvas API. - .. c:member:: EM_BOOL proxyContextToMainThread + .. c:member:: bool proxyContextToMainThread This member specifies the threading model that will be used for the created WebGL context, when the WebGL context is created in a pthread. Three values are possible: ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW``, ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK`` or ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS``. If ``EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW`` is specified, the WebGLRenderingContext object will be created inside the pthread that is calling the ``emscripten_webgl_create_context()`` function as an OffscreenCanvas-based rendering context. This is only possible if 1) current browser supports OffscreenCanvas specification, 2) code was compiled with ``-sOFFSCREENCANVAS_SUPPORT`` linker flag enabled, 3) the Canvas object that the context is being created on was transferred over to the calling pthread with function ``emscripten_pthread_attr_settransferredcanvases()`` when the pthread was originally created, and 4) no OffscreenCanvas-based context already exists from the given Canvas at the same time. @@ -2027,14 +2060,14 @@ Callback functions .. code-block:: cpp - typedef EM_BOOL (*em_webgl_context_callback)(int eventType, const void *reserved, void *userData); + typedef bool (*em_webgl_context_callback)(int eventType, const void *reserved, void *userData); :param int eventType: The type of :c:data:`WebGL context event `. :param reserved: Reserved for future use; pass in 0. :type reserved: const void* :param void* userData: The ``userData`` originally passed to the registration function. :returns: |callback-handler-return-value-doc| - :rtype: |EM_BOOL| + :rtype: bool @@ -2042,28 +2075,28 @@ Functions --------- -.. c:function:: EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback(const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) - EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback(const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) +.. c:function:: EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback(const char *target, void *userData, bool useCapture, em_webgl_context_callback callback) + EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback(const char *target, void *userData, bool useCapture, em_webgl_context_callback callback) Registers a callback function for the canvas `WebGL context`_ events: ``webglcontextlost`` and ``webglcontextrestored``. :param target: |target-parameter-doc| :type target: const char* :param void* userData: |userData-parameter-doc| - :param EM_BOOL useCapture: |useCapture-parameter-doc| + :param bool useCapture: |useCapture-parameter-doc| :param em_webgl_context_callback callback: |callback-function-parameter-doc| :returns: :c:data:`EMSCRIPTEN_RESULT_SUCCESS`, or one of the other result values. :rtype: |EMSCRIPTEN_RESULT| -.. c:function:: EM_BOOL emscripten_is_webgl_context_lost(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context) +.. c:function:: bool emscripten_is_webgl_context_lost(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context) Queries the given WebGL context if it is in a lost context state. :param target: Specifies a handle to the context to test. :type target: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE :returns: ``true`` if the WebGL context is in a lost state (or the context does not exist) - :rtype: |EM_BOOL| + :rtype: bool .. c:function:: void emscripten_webgl_init_context_attributes(EmscriptenWebGLContextAttributes *attributes) @@ -2084,7 +2117,11 @@ Functions .. note:: - A successful call to this function will not immediately make that rendering context active. Call :c:func:`emscripten_webgl_make_context_current` after creating a context to activate it. - - This function will try to initialize the context version that was *exactly* requested. It will not e.g. initialize a newer backwards-compatible version or similar. + - A word of caution about :c:type:`EmscriptenWebGLContextAttributes.majorVersion`: + + - When no (WEBGL) linker flags are set, then this attribute is ignored and the context returned is WebGL 1.0 + - When the linker flag ``-sMIN_WEBGL_VERSION=2`` is set, then this attribute is ignored and the context returned is WebGL 2.0 + - When the linker flag ``-sMAX_WEBGL_VERSION=2`` is set, then this attribute is used and the context returned matches the value of this attribute :param target: The DOM canvas element in which to initialize the WebGL context. :type target: const char* @@ -2148,15 +2185,15 @@ Functions :rtype: |EMSCRIPTEN_RESULT| -.. c:function:: EM_BOOL emscripten_webgl_enable_extension(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, const char *extension) +.. c:function:: bool emscripten_webgl_enable_extension(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, const char *extension) Enables the given extension on the given context. :param EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context: The WebGL context on which the extension is to be enabled. :param extension: A string identifying the `WebGL extension `_. For example "OES_texture_float". :type extension: const char* - :returns: EM_TRUE if the given extension is supported by the context, and EM_FALSE if the extension was not available. - :rtype: |EM_BOOL| + :returns: true if the given extension is supported by the context, and false if the extension was not available. + :rtype: bool .. c:function:: EMSCRIPTEN_RESULT emscripten_set_canvas_element_size(const char *target, int width, int height) @@ -2216,7 +2253,6 @@ Functions .. COMMENT (not rendered): The replace function return values with links (not created automatically) .. |EMSCRIPTEN_RESULT| replace:: :c:type:`EMSCRIPTEN_RESULT` -.. |EM_BOOL| replace:: :c:type:`EM_BOOL` .. |EMSCRIPTEN_WEBGL_CONTEXT_HANDLE| replace:: :c:type:`EMSCRIPTEN_WEBGL_CONTEXT_HANDLE` @@ -2259,10 +2295,10 @@ Functions :param setTimeoutId: An ID returned by function :c:func:`emscripten_set_timeout()`. -.. c:function:: void emscripten_set_timeout_loop(EM_BOOL (*cb)(double time, void *userData), double intervalMsecs, void *userData) +.. c:function:: void emscripten_set_timeout_loop(bool (*cb)(double time, void *userData), double intervalMsecs, void *userData) Initializes a ``setTimeout()`` loop on the given function on the calling thread. The specified callback - function 'cb' needs to keep returning ``EM_TRUE`` as long as the animation loop should continue to run. + function 'cb' needs to keep returning ``true`` as long as the animation loop should continue to run. When the function returns false, the ``setTimeout()`` loop will stop. Note: The loop will start immediately with a 0 msecs delay - the passed in intervalMsecs time specifies the interval that the consecutive callback calls should fire at. @@ -2272,7 +2308,7 @@ Functions :param userData: Specifies a pointer sized field of custom data that will be passed in to the callback function. -.. c:function:: long emscripten_request_animation_frame(EM_BOOL (*cb)(double time, void *userData), void *userData) +.. c:function:: long emscripten_request_animation_frame(bool (*cb)(double time, void *userData), void *userData) Performs a single ``requestAnimationFrame()`` callback call on the given function on the calling thread. @@ -2293,10 +2329,10 @@ Functions :param requestAnimationFrameId: An ID returned by function :c:func:`emscripten_request_animation_frame()`. -.. c:function:: void emscripten_request_animation_frame_loop(EM_BOOL (*cb)(double time, void *userData), void *userData) +.. c:function:: void emscripten_request_animation_frame_loop(bool (*cb)(double time, void *userData), void *userData) Initializes a ``requestAnimationFrame()`` loop on the given function on the calling thread. The specified - callback function 'cb' needs to keep returning ``EM_TRUE`` as long as the animation loop should continue + callback function 'cb' needs to keep returning ``true`` as long as the animation loop should continue to run. When the function returns false, the animation frame loop will stop. :param cb: The callback function to call. This function will receive the current high precision timer value @@ -2324,10 +2360,10 @@ Functions :param setImmediateId: An ID returned by function :c:func:`emscripten_set_immediate()`. -.. c:function:: void emscripten_set_immediate_loop(EM_BOOL (*cb)(void *userData), void *userData) +.. c:function:: void emscripten_set_immediate_loop(bool (*cb)(void *userData), void *userData) Initializes a ``setImmediate()`` loop on the given function on the calling thread. The specified callback - function 'cb' needs to keep returning ``EM_TRUE`` as long as the loop should continue to run. + function 'cb' needs to keep returning ``true`` as long as the loop should continue to run. When the function returns false, the ``setImmediate()`` loop will stop. TODO: Currently the polyfill of ``setImmediate()`` only works in the main browser thread, but not in pthreads. diff --git a/site/source/docs/api_reference/index.rst b/site/source/docs/api_reference/index.rst index 944c8fe1c0308..983cbaa97e1cc 100644 --- a/site/source/docs/api_reference/index.rst +++ b/site/source/docs/api_reference/index.rst @@ -14,7 +14,7 @@ high level it consists of: Low level glue bindings for interfacing with HTML5 APIs from native code. - :ref:`console-h`: - Functions to writing to the console and stdout/stderr. + Functions for writing to the console and stdout/stderr. - :ref:`preamble-js`: APIs for working with compiled code from JavaScript. diff --git a/site/source/docs/api_reference/module.rst b/site/source/docs/api_reference/module.rst index c6f2bf0868f50..2c0124694f232 100644 --- a/site/source/docs/api_reference/module.rst +++ b/site/source/docs/api_reference/module.rst @@ -174,10 +174,6 @@ Other methods .. note:: Sanitizers or source map is currently not supported if overriding WebAssembly instantiation with Module.instantiateWasm. Providing Module.instantiateWasm when source map or sanitizer is enabled can prevent WebAssembly instantiation from finishing. -.. js:function:: Module.onCustomMessage - - When compiled with ``PROXY_TO_WORKER = 1`` (see `settings.js `_), this callback (which should be implemented on both the client and worker's ``Module`` object) allows sending custom messages and data between the web worker and the main thread (using the ``postCustomMessage`` function defined in `proxyClient.js `_ and `proxyWorker.js `_). - .. js:function:: Module.fetchSettings Override the default settings object used when fetching the Wasm module from diff --git a/site/source/docs/api_reference/preamble.js.rst b/site/source/docs/api_reference/preamble.js.rst index f81065f8863d0..5819f72286946 100644 --- a/site/source/docs/api_reference/preamble.js.rst +++ b/site/source/docs/api_reference/preamble.js.rst @@ -81,7 +81,7 @@ Calling compiled C functions from JavaScript Returns a native JavaScript wrapper for a C function. - This is similar to :js:func:`ccall`, but returns a JavaScript function that can be reused as many time as needed. The C function can be defined in a C file, or be a C-compatible C++ function defined using ``extern "C"`` (to prevent name mangling). + This is similar to :js:func:`ccall`, but returns a JavaScript function that can be reused as many times as needed. The C function can be defined in a C file, or be a C-compatible C++ function defined using ``extern "C"`` (to prevent name mangling). .. code-block:: javascript @@ -159,12 +159,13 @@ Accessing memory Conversion functions — strings, pointers and arrays =================================================== -.. js:function:: UTF8ToString(ptr[, maxBytesToRead]) +.. js:function:: UTF8ToString(ptr[, maxBytesToRead], [ignoreNul]) Given a pointer ``ptr`` to a null-terminated UTF8-encoded string in the Emscripten HEAP, returns a copy of that string as a JavaScript ``String`` object. :param ptr: A pointer to a null-terminated UTF8-encoded string in the Emscripten HEAP. - :param maxBytesToRead: An optional length that specifies the maximum number of bytes to read. You can omit this parameter to scan the string until the first \0 byte. If maxBytesToRead is passed, and the string at ``[ptr, ptr+maxBytesToReadr)`` contains a null byte in the middle, then the string will cut short at that byte index (i.e. maxBytesToRead will not produce a string of exact length ``[ptr, ptr+maxBytesToRead)``) N.B. mixing frequent uses of ``UTF8ToString()`` with and without maxBytesToRead may throw JS JIT optimizations off, so it is worth to consider consistently using one style or the other. + :param maxBytesToRead: An optional length that specifies the maximum number of bytes to read. You can omit this parameter to scan the string until the first \0 byte. If maxBytesToRead is passed, and the string at ``[ptr, ptr+maxBytesToReadr)`` contains a null byte in the middle, then the string will cut short at that byte index. N.B. mixing frequent uses of this function with and without maxBytesToRead may throw JS JIT optimizations off, so it is worth to consider consistently using one style or the other. + :param ignoreNul: If ``true``, the function will not stop on a NUL character, but will read the entire string up to ``maxBytesToRead``. :returns: A JavaScript ``String`` object @@ -180,11 +181,13 @@ Conversion functions — strings, pointers and arrays :param maxBytesToWrite: A limit on the number of bytes that this function can at most write out. If the string is longer than this, the output is truncated. The outputted string will always be null terminated, even if truncation occurred, as long as ``maxBytesToWrite > 0``. -.. js:function:: UTF16ToString(ptr) +.. js:function:: UTF16ToString(ptr[, maxBytesToRead], [ignoreNul]) Given a pointer ``ptr`` to a null-terminated UTF16LE-encoded string in the Emscripten HEAP, returns a copy of that string as a JavaScript ``String`` object. :param ptr: A pointer to a null-terminated UTF16LE-encoded string in the Emscripten HEAP. + :param maxBytesToRead: An optional length that specifies the maximum number of bytes to read. You can omit this parameter to scan the string until the first \0 byte. If maxBytesToRead is passed, and the string at ``[ptr, ptr+maxBytesToReadr)`` contains a null byte in the middle, then the string will cut short at that byte index. N.B. mixing frequent uses of this function with and without maxBytesToRead may throw JS JIT optimizations off, so it is worth to consider consistently using one style or the other. + :param ignoreNul: If ``true``, the function will not stop on a NUL character, but will read the entire string up to ``maxBytesToRead``. :returns: A JavaScript ``String`` object @@ -202,11 +205,13 @@ Conversion functions — strings, pointers and arrays -.. js:function:: UTF32ToString(ptr) +.. js:function:: UTF32ToString(ptr[, maxBytesToRead], [ignoreNul]) Given a pointer ``ptr`` to a null-terminated UTF32LE-encoded string in the Emscripten HEAP, returns a copy of that string as a JavaScript ``String`` object. :param ptr: A pointer to a null-terminated UTF32LE-encoded string in the Emscripten HEAP. + :param maxBytesToRead: An optional length that specifies the maximum number of bytes to read. You can omit this parameter to scan the string until the first \0 byte. If maxBytesToRead is passed, and the string at ``[ptr, ptr+maxBytesToReadr)`` contains a null byte in the middle, then the string will cut short at that byte index. N.B. mixing frequent uses of this function with and without maxBytesToRead may throw JS JIT optimizations off, so it is worth to consider consistently using one style or the other. + :param ignoreNul: If ``true``, the function will not stop on a NUL character, but will read the entire string up to ``maxBytesToRead``. :returns: A JavaScript ``String`` object. @@ -234,7 +239,7 @@ Conversion functions — strings, pointers and arrays .. js:function:: intArrayFromString(stringy, dontAddNull[, length]) - This converts a JavaScript string into a C-line array of numbers, 0-terminated. + This converts a JavaScript string into a C-like array of numbers, 0-terminated. :param stringy: The string to be converted. :type stringy: String @@ -246,7 +251,7 @@ Conversion functions — strings, pointers and arrays .. js:function:: intArrayToString(array) - This creates a JavaScript string from a zero-terminated C-line array of numbers. + This creates a JavaScript string from a zero-terminated C-like array of numbers. :param array: The array to convert. :returns: A ``String``, containing the content of ``array``. @@ -389,8 +394,6 @@ The :ref:`emscripten-memory-model` uses a typed array buffer (``ArrayBuffer``) t function SAFE_HEAP_LOAD(dest, bytes, isFloat, unsigned) function SAFE_FT_MASK(value, mask) function CHECK_OVERFLOW(value, bits, ignore, sig) - Module["preloadedImages"] - Module["preloadedAudios"] .. PRIVATE NOTES (not rendered) : diff --git a/site/source/docs/api_reference/proxying.h.rst b/site/source/docs/api_reference/proxying.h.rst index c6a42b8b00339..475845e2081fa 100644 --- a/site/source/docs/api_reference/proxying.h.rst +++ b/site/source/docs/api_reference/proxying.h.rst @@ -72,21 +72,21 @@ Functions Signal the end of a task proxied with ``emscripten_proxy_sync_with_ctx``. -.. c:function:: int emscripten_proxy_async(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void* arg) +.. c:function:: bool emscripten_proxy_async(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void* arg) Enqueue ``func`` to be called with argument ``arg`` on the given queue and thread then return immediately without waiting for ``func`` to be executed. - Returns 1 if the work was successfully enqueued or 0 otherwise. + Returns true if the work was successfully enqueued or false otherwise. -.. c:function:: int emscripten_proxy_sync(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void* arg) +.. c:function:: bool emscripten_proxy_sync(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void* arg) Enqueue ``func`` to be called with argument ``arg`` on the given queue and thread then wait for ``func`` to be executed synchronously before returning. - Returns 1 if the ``func`` was successfully completed and 0 otherwise, + Returns true if the ``func`` was successfully completed and false otherwise, including if the target thread is canceled or exits before the work is completed. -.. c:function:: int emscripten_proxy_sync_with_ctx(em_proxying_queue* q, pthread_t target_thread, void (*func)(em_proxying_ctx*, void*), void* arg) +.. c:function:: bool emscripten_proxy_sync_with_ctx(em_proxying_queue* q, pthread_t target_thread, void (*func)(em_proxying_ctx*, void*), void* arg) The same as ``emscripten_proxy_sync`` except that instead of waiting for the proxied function to return, it waits for the proxied task to be explicitly @@ -94,24 +94,24 @@ Functions ``emscripten_proxy_finish`` itself; it could instead store the context pointer and call ``emscripten_proxy_finish`` at an arbitrary later time. -.. c:function:: int emscripten_proxy_callback(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void (*callback)(void*), void (*cancel)(void*), void* arg) +.. c:function:: bool emscripten_proxy_callback(em_proxying_queue* q, pthread_t target_thread, void (*func)(void*), void (*callback)(void*), void (*cancel)(void*), void* arg) Enqueue ``func`` on the given queue and thread. Once (and if) it finishes executing, it will asynchronously proxy ``callback`` back to the current thread on the same queue, or if the target thread dies before the work can be completed, ``cancel`` will be proxied back instead. All three function will - receive the same argument, ``arg``. Returns 1 if ``func`` was successfully - enqueued and the target thread notified or 0 otherwise. + receive the same argument, ``arg``. Returns true if ``func`` was successfully + enqueued and the target thread notified or false otherwise. -.. c:function:: int emscripten_proxy_callback_with_ctx(em_proxying_queue* q, pthread_t target_thread, void (*func)(em_proxying_ctx*, void*), void (*callback)(void*), void (*cancel)(void*), void* arg) +.. c:function:: bool emscripten_proxy_callback_with_ctx(em_proxying_queue* q, pthread_t target_thread, void (*func)(em_proxying_ctx*, void*), void (*callback)(void*), void (*cancel)(void*), void* arg) Enqueue ``func`` on the given queue and thread. Once (and if) it finishes the task by calling ``emscripten_proxy_finish`` on the given ``em_proxying_ctx``, it will asynchronously proxy ``callback`` back to the current thread on the same queue, or if the target thread dies before the work can be completed, ``cancel`` will be proxied back instead. All three function will receive the - same argument, ``arg``. Returns 1 if ``func`` was successfully enqueued and - the target thread notified or 0 otherwise. + same argument, ``arg``. Returns true if ``func`` was successfully enqueued and + the target thread notified or false otherwise. C++ API ------- diff --git a/site/source/docs/api_reference/wasm_audio_worklets.rst b/site/source/docs/api_reference/wasm_audio_worklets.rst index e2587bf58b372..6aa51378fba65 100644 --- a/site/source/docs/api_reference/wasm_audio_worklets.rst +++ b/site/source/docs/api_reference/wasm_audio_worklets.rst @@ -27,6 +27,14 @@ Audio Worklets API is based on the Wasm Workers feature. It is possible to also enable the `-pthread` option while targeting Audio Worklets, but the audio worklets will always run in a Wasm Worker, and not in a Pthread. +.. note:: + If you want to load an emscripten-generated program into an AudioContext that + you have created yourself, without depending on shared memory or + :ref:`WASM_WORKERS` you can add ``worklet`` to :ref:`ENVIRONMENT`. In this + mode, because Audio Worklets do not have any kind of fetch API, you will need + either use `-sSINGLE_FILE` (to embed the Wasm file), or use a custom + `instantiateWasm` to supply the Wasm module yourself (e.g. via `postMessage`). + Development Overview ==================== @@ -44,8 +52,11 @@ and then, these processors are instantiated one or more times in the audio processing graph as AudioWorkletNodes. Once a class type is instantiated on the Web Audio graph and the graph is -running, a C/C++ function pointer callback will be invoked for each 128 -samples of the processed audio stream that flows through the node. +running, a C/C++ function pointer callback will be invoked for each N samples +of the processed audio stream that flows through the node (where N is is the +number of samples per channel, exposed as ``AudioSampleFrame``'s +``samplesPerChannel``, always 128 in the 1.0 Web Audio API, though with the 1.1 +API ``emscripten_create_audio_context()`` accepts a ``renderSizeHint`` option). This callback will be executed on a dedicated separate audio processing thread with real-time processing priority. Each Web Audio context will @@ -95,7 +106,7 @@ own noise generator AudioWorkletProcessor node type: .. code-block:: cpp - void AudioThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) + void AudioThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { if (!success) return; // Check browser console in a debug build for detailed errors WebAudioWorkletProcessorCreateOptions opts = { @@ -110,7 +121,7 @@ which resumes the audio context when the user clicks on the DOM Canvas element t .. code-block:: cpp - void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) + void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, bool success, void *userData) { if (!success) return; // Check browser console in a debug build for detailed errors @@ -126,8 +137,7 @@ which resumes the audio context when the user clicks on the DOM Canvas element t "noise-generator", &options, &GenerateNoise, 0); // Connect it to audio context destination - EM_ASM({emscriptenGetAudioObject($0).connect(emscriptenGetAudioObject($1).destination)}, - wasmAudioWorklet, audioContext); + emscripten_audio_node_connect(wasmAudioWorklet, audioContext, 0, 0); // Resume context on mouse click emscripten_set_click_callback("canvas", (void*)audioContext, 0, OnCanvasClick); @@ -137,13 +147,13 @@ which resumes the audio context when the user clicks on the DOM Canvas element t .. code-block:: cpp - EM_BOOL OnCanvasClick(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + bool OnCanvasClick(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { EMSCRIPTEN_WEBAUDIO_T audioContext = (EMSCRIPTEN_WEBAUDIO_T)userData; if (emscripten_audio_context_state(audioContext) != AUDIO_CONTEXT_STATE_RUNNING) { emscripten_resume_audio_context_sync(audioContext); } - return EM_FALSE; + return false; } 5. Finally we can implement the audio callback that is to generate the noise: @@ -152,19 +162,19 @@ which resumes the audio context when the user clicks on the DOM Canvas element t #include - EM_BOOL GenerateNoise(int numInputs, const AudioSampleFrame *inputs, + bool GenerateNoise(int numInputs, const AudioSampleFrame *inputs, int numOutputs, AudioSampleFrame *outputs, int numParams, const AudioParamFrame *params, void *userData) { for(int i = 0; i < numOutputs; ++i) - for(int j = 0; j < 128*outputs[i].numberOfChannels; ++j) + for(int j = 0; j < outputs[i].samplesPerChannel*outputs[i].numberOfChannels; ++j) outputs[i].data[j] = emscripten_random() * 0.2 - 0.1; // Warning: scale down audio volume by factor of 0.2, raw noise can be really loud otherwise - return EM_TRUE; // Keep the graph output going + return true; // Keep the graph output going } -And that's it! Compile the code with the linker flags ``-sAUDIO_WORKLET=1 -sWASM_WORKERS=1`` to enable targeting AudioWorklets. +And that's it! Compile the code with the linker flags ``-sAUDIO_WORKLET -sWASM_WORKERS`` to enable targeting AudioWorklets. Synchronizing audio thread with the main thread =============================================== diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index c99c363dcf80d..409b39ba78bd1 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -86,6 +86,7 @@ the middle. Pthreads and Wasm Workers share several similarities: * Both can use emscripten_atomic_* Atomics API, + * Both can use emscripten_futex_wait/wake API, * Both can use GCC __sync_* Atomics API, * Both can use C11 and C++11 Atomics APIs, * Both types of threads have a local stack. @@ -103,6 +104,19 @@ Pthreads and Wasm Workers share several similarities: However, the differences are more notable. +C11 thread APIs are not available under Wasm Workers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Becuase our C11 threading API is based on pthreads internally these APIs are not +available under Wasm Worker. + +Some standard C++ APIs are not available under Wasm Workers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some parts of the libc++ stndard library are not available under Wasm Workers +becuase they depend on pthreads internally. For example `std::call_once``: +https://github.com/emscripten-core/emscripten/issues/26375. + Pthreads can proxy JS functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,8 +131,6 @@ If you need to synchronously wait for the posted function to finish from within the ``emscripten_wasm_worker_*()`` thread synchronization functions to sleep the calling thread until the callee has finished the operation. -Note that Wasm Workers cannot - Pthreads have cancellation points ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -153,7 +165,7 @@ a Wasm Worker, consider which type of hierarchy you would like, and if necessary hierarchy manually by posting the Worker creation over to the main thread yourself. Note that support for nested Workers varies across browsers. As of 02/2022, nested Workers are `not -supported in Safari `_. See `here +supported in Safari `_. See `here `_ for a polyfill. Pthreads can use the Wasm Worker synchronization API, but not vice versa @@ -266,7 +278,7 @@ table. Thread stack Specify in pthread_attr_t structure. Manage thread stack area explicitly with
emscripten_create_wasm_worker_*_tls()
functions, or -
automatically allocate stack with
emscripten_malloc_wasm_worker()
API. +
automatically allocate stack+TLS area with
emscripten_malloc_wasm_worker()
API. Thread Local Storage (TLS) Supported transparently. @@ -342,6 +354,53 @@ table. +Wasm Workers stack size considerations +====================================== + +When instantiating a Wasm Worker, one has to create a memory array for the LLVM +data stack for the created Worker. This data stack will generally consist only +of local variables that have been "spilled" by LLVM into memory, e.g. to contain +large arrays, structs, or other variables that are referenced by a memory +address. This stack will not contain control flow information. + +Since WebAssembly does not support virtual memory, the size of the LLVM data +stack that is defined both for Wasm Workers but also the main thread will not be +possible to grow at runtime. So if the Worker (or the main thread) runs out of +stack space, the program behavior will be undefined. Use the Emscripten linker +flag -sSTACK_OVERFLOW_CHECK=2 to emit runtime stack overflow checks into the +program code to detect these situations during development. + +Note that to avoid the need to perform two separate allocations, the TLS memory +for the Wasm Worker will be located at the bottom end (low memory address) of +the Wasm Worker stack space. + +Wasm Workers vs the earlier Emscripten Worker API +================================================= + +Emscripten provides a second Worker API as part of the emscripten.h header. This Worker API predates the advent of SharedArrayBuffer, and is quite distinct from Wasm Workers API, just the naming of these two APIs is similar due to historical reasons. + +Both APIs allow one to spawn Web Workers from the main thread, though the semantics are different. + +With the Worker API, the user will be able to spawn a Web Worker from a custom URL. This URL can point to a completely separate JS file that was not compiled with Emscripten, to load up Workers from arbitrary URLs. With Wasm Workers, a custom URL is not specified: Wasm Workers will always spawn a Web Worker that computes in the same WebAssembly+JavaScript context as the main program. + +The Worker API does not integrate with SharedArrayBuffer, so interaction with the loaded Worker will always be asynchronous. Wasm Workers however is built on top of SharedArrayBuffer, and each Wasm Worker shares and computes in the same WebAssembly Memory address space of the main thread. + +Both the Worker API and Wasm Workers API provide the user with ability to postMessage() function calls to the Worker. In Worker API, this message posting is restricted to need to originate/initiate from the main thread towards the Worker (using the API ``emscripten_call_worker()`` and ``emscripten_worker_respond()`` in ````). With Wasm Workers however one can also postMessage() function calls to their parent (owning) thread. + +If posting function calls with the Emscripten Worker API, it is required that the target Worker URL points to an Emscripten compiled program (so it has the ``Module`` structure to locate function names). Only functions that have been exported to the ``Module`` object are callable. With Wasm Workers, any C/C++ function may be posted, and does not need to be exported. + +Use the Emscripten Worker API when: + - you want to easily spawn a Worker from a JS file that was not built using Emscripten + - you want to spawn as Worker a single separate compiled program than the main thread program represents, and the main thread and Worker programs do not share common code + - you do not want to require the use of SharedArrayBuffer, or setting up COOP+COEP headers + - you only need to communicate with the Worker asynchronously using postMessage() function calls + +Use the Wasm Workers API when: + - you want to create one or more new threads that synchronously compute in the same Wasm Module context + - you want to spawn multiple Workers from the same codebase and save memory by sharing the WebAssembly Module (object code) and Memory (address space) across the Workers + - you want to synchronously coordinate communication between threads by using atomic primitives and locks + - your web server has been configured with the needed COOP+COEP headers to enable SharedArrayBuffer capabilities on the site + Limitations =========== @@ -349,10 +408,9 @@ The following build options are not supported at the moment with Wasm Workers: - -sSINGLE_FILE - Dynamic linking (-sLINKABLE, -sMAIN_MODULE, -sSIDE_MODULE) -- -sPROXY_TO_WORKER - -sPROXY_TO_PTHREAD Example Code ============ -See the directory ``test/wasm_workers/`` for code examples on different Wasm Workers API functionality. +See the directory ``test/wasm_worker/`` for code examples on different Wasm Workers API functionality. diff --git a/site/source/docs/building_from_source/configuring_emscripten_settings.rst b/site/source/docs/building_from_source/configuring_emscripten_settings.rst index df24418b6b0e5..80ebe6fc88930 100644 --- a/site/source/docs/building_from_source/configuring_emscripten_settings.rst +++ b/site/source/docs/building_from_source/configuring_emscripten_settings.rst @@ -45,7 +45,7 @@ Locating the compiler configuration file (.emscripten) ====================================================== The settings file (``.emscripten``) is created by default within the emscripten -directory (alongsize ``emcc`` itself). In cases where the emscripten directory +directory (alongside ``emcc`` itself). In cases where the emscripten directory is read-only the user's home directory will be used: - On Linux and macOS this file is named **~/.emscripten**, where ~ is the diff --git a/site/source/docs/building_from_source/index.rst b/site/source/docs/building_from_source/index.rst index d42e17bd5492d..52aefd9762724 100644 --- a/site/source/docs/building_from_source/index.rst +++ b/site/source/docs/building_from_source/index.rst @@ -8,14 +8,19 @@ Building Emscripten yourself is an alternative to getting binaries using the emsdk. Emscripten itself is written in Python and JavaScript so it does not need to be -compiled. However, after checkout you will need to perform various steps -before it can be used (e.g. ``npm install``). The ``bootstrap`` script in the -top level of the repository takes care of running these steps and ``emcc`` will -error out if it detects that ``bootstrap`` needs to be run. - -In addition to the main emscripten repository you will also need to checkout -and build LLVM and Binaryen (as detailed below). After compiling these, you -will need to edit your ``.emscripten`` file to point to their corresponding +compiled. However, after checkout you will need to run the top level +``bootstrap`` script before the toolchain is usable. This performs +various steps including ``npm install`` and the creation of compiler entry +points (e.g. `.bat` files on windows). + +Emscripten comes with its own versions of some C/C++ system libraries which +``emcc`` builds automatically as and when needed (in the emsdk builds, these are +precompiled). You can also build them manually with the ``embuilder`` tool - see +``embuilder --help`` for more information. + +In addition to the main emscripten repository you will also need to checkout and +build LLVM and Binaryen (as detailed below). After compiling these, you will +need to edit your ``.emscripten`` file to point to their corresponding locations. Use the ``main`` branches of each of these repositories, or check the `Packaging diff --git a/site/source/docs/building_from_source/toolchain_what_is_needed.rst b/site/source/docs/building_from_source/toolchain_what_is_needed.rst index a6246732e80e6..4a35a3d1ccae8 100644 --- a/site/source/docs/building_from_source/toolchain_what_is_needed.rst +++ b/site/source/docs/building_from_source/toolchain_what_is_needed.rst @@ -22,8 +22,8 @@ Emscripten tools and dependencies In general a complete Emscripten environment requires the following tools. First test to see if they are already installed using the :ref:`instructions below `. - - :term:`Node.js` (0.8 or above; 0.10.17 or above to run websocket-using servers in node) - - :term:`Python` (3.6 or above) + - :term:`Node.js` (18.3.0 or above) + - :term:`Python` (3.8 or above) - :term:`Java` (1.6.0_31 or later). Java is optional. It can be used to run the java version of term:`Closure Compiler`. - :term:`Git` client. Git is required if building tools from source. - :term:`LLVM` (LLVM, including clang and wasm-ld) @@ -83,16 +83,10 @@ You can check which tools are already present using the following commands: # Check for Python python --version - # Check for node.js on Linux - nodejs --version - - # Check for node.js on Windows + # Check for Node.js node --version - # Check for node.js on macOS - node -v - - # Check for git + # Check for Git git --version # Check for Java diff --git a/site/source/docs/building_from_source/verify_emscripten_environment.rst b/site/source/docs/building_from_source/verify_emscripten_environment.rst index 208d97a996d07..30bef9734a0bd 100644 --- a/site/source/docs/building_from_source/verify_emscripten_environment.rst +++ b/site/source/docs/building_from_source/verify_emscripten_environment.rst @@ -31,7 +31,7 @@ could not be found: shared:INFO: (Emscripten: Running sanity checks) emcc: warning: LLVM version for clang executable "/usr/bin/clang" appears incorrect (seeing "16.0", expected "18") [-Wversion-check] -At this point you need to :ref:`Install and activate ` any missing components. When everything is set up properly, ``emcc ---check`` should give no warnings, and if you just enter ``emcc`` (without any input files), it will give an error :: +At this point you need to :ref:`Install and activate ` any missing components. When everything is set up properly, ``emcc --check`` should give no warnings, and if you just enter ``emcc`` (without any input files), it will give an error :: emcc: error: no input files diff --git a/site/source/docs/compiling/Building-Projects.rst b/site/source/docs/compiling/Building-Projects.rst index 72fdc611c3ad8..23d67267273ed 100644 --- a/site/source/docs/compiling/Building-Projects.rst +++ b/site/source/docs/compiling/Building-Projects.rst @@ -101,7 +101,7 @@ This list is not exhaustive, but illustrates most commonly used combinations. .. note:: Regardless of the name of the output file ``emcc`` will always perform linking and produce a final executable, unless a specific flags (e.g. ``-c``) - direct it do something else. This differs to previous behaviour where + direct it to do something else. This differs to previous behaviour where ``emcc`` would default to combining object files (essentially assuming ``-r``) unless given a specific executable extension (e.g. ``.js`` or ``.html``). @@ -214,52 +214,60 @@ For example, consider the case where a project "project" uses a library "libstuf Emscripten Ports ================ -Emscripten Ports is a collection of useful libraries, ported to Emscripten. They reside `on GitHub `_, and have integration support in *emcc*. When you request that a port be used, emcc will fetch it from the remote server, set it up and build it locally, then link it with your project, add necessary include to your build commands, etc. For example, SDL2 is in ports, and you can request that it be used with ``--use-port=sdl2``. For example, +Emscripten Ports is a collection of useful libraries, ported to Emscripten. They +reside in the `ports directory`_, and have integration with *emcc*. When you +request that a port be used, emcc will download, build and install it into the +emscripten sysroot. For example, to use the SDL2 port in your project you would +simply add ``--use-port=sdl2`` to your compiler and linker flags. For example: .. code-block:: bash emcc test/browser/test_sdl2_glshader.c --use-port=sdl2 -sLEGACY_GL_EMULATION -o sdl2.html -You should see some notifications about SDL2 being used, and built if it wasn't previously. You can then view ``sdl2.html`` in your browser. +If this if your first time using a given port you may see some notifications about +it being downloaded and installed as your program is being compiled. To see a list of all available ports, run ``emcc --show-ports``. -.. note:: *SDL_image* has also been added to ports, use it with - ``--use-port=sdl2_image``. For ``sdl2_image`` to be useful, you generally - need to specify the image formats you are planning on using with e.g. - ``--use-port=sdl2_image:formats=bmp,png,xpm,jpg``. This will also ensure that - ``IMG_Init`` works properly when you specify those formats. Alternatively, - you can use ``emcc --use-preload-plugins`` and ``--preload-file`` your - images, so the browser codecs decode them (see :ref:`preloading-files`). - A code path in the ``sdl2_image`` port will load through - :c:func:`emscripten_get_preloaded_image_data`, but then your calls to - ``IMG_Init`` with those image formats will fail (as while the images will - work through preloading, IMG_Init reports no support for those formats, as - it doesn't have support compiled in - in other words, ``IMG_Init`` does not - report support for formats that only work through preloading). - -.. note:: *SDL_net* has also been added to ports, use it with ``--use-port=sdl2_net``. - -.. note:: Emscripten also has support for older SDL1, which is built-in. - If you do not specify SDL2 as in the command above, then SDL1 is linked in - and the SDL1 include paths are used. SDL1 has support for *sdl-config*, - which is present in `system/bin `_. - Using the native *sdl-config* may result in compilation or missing-symbol errors. - You will need to modify the build system to look for files in - **emscripten/system** or **emscripten/system/bin** in order to use the - Emscripten *sdl-config*. - -.. note:: You can also build a library from ports in a manual way if you prefer - that, but then you will need to also apply the python logic that ports does. - That code (under ``tools/ports/``) may do things like ensure necessary JS - functions are included in the build, add exports, and so forth. In general, - it's better to use the ports version as it is what is tested and known to - work. +You can also use the standalone ``embuilder`` tools to explicitly build ports +prior to running the compiler. For example, you can build SDL2 using +``./embuilder build sdl2``. See ``embuilder --help`` for more information, +including a list of all available targets. -.. note:: Since emscripten 3.1.54, ``--use-port`` is the preferred syntax to - use a port in your project. The legacy syntax (for example ``-sUSE_SDL2``, - ``-sUSE_SDL_IMAGE=2``) remains available. +Some ports take extra options. For example, when using the ``sdl2_image`` ports +you can specify a list of image formats. e.g. +``--use-port=sdl2_image:formats=bmp,png,xpm,jpg``. See `Port-specific Notes`_ +below for more information on this. + +Emscripten also has support for the older SDL 1.3, which is built-in. Use can +use this via ``-sUSE_SDL=1``. SDL 1.3 has support for `sdl-config`_. Using the +host version *sdl-config* may result in compilation errors. You may need to +modify the build system to look for *sdl-config** in the emscripten sysroot +(``/bin/sdl-config``). +.. note:: When a port is built and installed into the sysroot it will then be + available to all following ``emcc`` commands. For example, once you have run + ``emcc`` with ``--use-port=sdl2`` or run ``./embuilder build sdl2``, future + ``emcc`` commands will be able to see the SDL2 headers and libraries. + +.. note:: Since emscripten 3.1.54, ``--use-port`` is the preferred syntax to + use a port in your project. The legacy syntax (for example ``-sUSE_SDL2``, + ``-sUSE_SDL_IMAGE=2``) remains available. + +Port-specific Notes +------------------- + +The ``sdl2_image`` port generally requires the specification of set of supported +imagine formats. For example ``--use-port=sdl2_image:formats=bmp,png,xpm,jpg``. +This ensures that ``IMG_Init`` works properly when you specify those formats. +Alternatively, you can use ``emcc --use-preload-plugins`` and ``--preload-file`` +your images, so the browser codecs decode them (see :ref:`preloading-files`). A +code path in the ``sdl2_image`` port will load through +:c:func:`emscripten_get_preloaded_image_data`, but then your calls to +``IMG_Init`` with those image formats will fail (as while the images will work +through preloading, ``IMG_Init`` reports no support for those formats, as it +doesn't have support compiled in - in other words, ``IMG_Init`` does not report +support for formats that only work through preloading). Contrib ports ------------- @@ -272,7 +280,8 @@ See :ref:`Contrib Ports ` for more information. Adding more ports ----------------- -The simplest way to add a new port is to put it under the ``contrib`` directory. Basically, the steps are: +The simplest way to add a new port is to put it under the ``contrib`` directory. +Basically, the steps are: * Make sure the port is open source and has a suitable license. * Read the ``README.md`` file under ``tools/ports/contrib`` which contains more information. @@ -381,8 +390,8 @@ Detecting Emscripten in Preprocessor Emscripten provides the following preprocessor macros that can be used to identify the compiler version and platform: * The preprocessor define ``__EMSCRIPTEN__`` is always defined when compiling programs with Emscripten. - * The preprocessor variables ``__EMSCRIPTEN_major__``, ``__EMSCRIPTEN_minor__`` - and ``__EMSCRIPTEN_tiny__`` are defined in ``emscripten/version.h`` and + * The preprocessor variables ``__EMSCRIPTEN_MAJOR__``, ``__EMSCRIPTEN_MINOR__`` + and ``__EMSCRIPTEN_TINY__`` are defined in ``emscripten/version.h`` and specify, as integers, the currently used Emscripten compiler version. * Emscripten behaves like a variant of Unix, so the preprocessor defines ``unix``, ``__unix`` and ``__unix__`` are always present when compiling code with Emscripten. * Emscripten uses Clang/LLVM as its underlying codegen compiler, so the preprocessor defines ``__llvm__`` and ``__clang__`` are defined, and the preprocessor defines ``__clang_major__``, ``__clang_minor__`` and ``__clang_patchlevel__`` indicate the version of Clang that is used. @@ -447,3 +456,6 @@ Troubleshooting One solution is to use :ref:`dynamic-linking `. This ensures that libraries are linked only once, in the final build stage. - When generating standalone Wasm, make sure to invoke the ``_start`` or (for ``--no-entry``) ``_initialize`` export before attempting to use the module. + +.. _ports directory: https://github.com/emscripten-core/emscripten/tree/main/tools/ports +.. _sdl-config: https://github.com/emscripten-core/emscripten/blob/main/system/bin/sdl-config diff --git a/site/source/docs/compiling/Contrib-Ports.rst b/site/source/docs/compiling/Contrib-Ports.rst index e387428c5c774..8934e2e748616 100644 --- a/site/source/docs/compiling/Contrib-Ports.rst +++ b/site/source/docs/compiling/Contrib-Ports.rst @@ -17,17 +17,53 @@ available in emscripten. In order to use a contrib port you use the contrib.glfw3 ============= -This project is an emscripten port of GLFW written in C++ for the +This project is an Emscripten port of GLFW written in C++ for the web/webassembly platform. .. note:: - emscripten includes support for both GLFW 2 and 3 written in Javascript. + Emscripten includes support for both GLFW 2 and 3 written in Javascript. These can be activated with the :ref:`settings ` ``-sUSE_GLFW=2`` and ``-sUSE_GLFW=3``. This non-official contribution, written in C++, provides a more extensive and up-to-date implementation of the GLFW 3 API than the built-in port. It is enabled with the option ``--use-port=contrib.glfw3``. -`Project information `_ +`Project information `__ -License: Apache 2.0 license \ No newline at end of file +License: Apache 2.0 license + +.. _contrib.lua: + +contrib.lua +=========== + +Lua is a powerful, efficient, lightweight, embeddable scripting language. + +Example usage: + +.. code-block:: text + + // main.c + #include + #include + #include + #include + + int main() { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + if (luaL_dostring(L, "print('hello world')") != LUA_OK) { + printf("Error running Lua code %s\n", lua_tostring(L, -1)); + lua_pop(L, 1); + } + lua_close(L); + return 0; + } + + // compile with + emcc --use-port=contrib.lua main.c -o /tmp/index.html + + +`Project information `__ + +License: MIT License \ No newline at end of file diff --git a/site/source/docs/compiling/Deploying-Pages.rst b/site/source/docs/compiling/Deploying-Pages.rst index 4a86d69a63945..db4419f6e7622 100644 --- a/site/source/docs/compiling/Deploying-Pages.rst +++ b/site/source/docs/compiling/Deploying-Pages.rst @@ -13,7 +13,7 @@ Emscripten build output consists of two essential parts: 1) the low level compil Additional build output files can also exist, depending on which features are used. If the Emscripten file packager is used, a binary ``out.data`` package is generated, along with an associated ``out.data.js`` loader file. Also Emscripten pthreads and Fetch APIs have their own associated Web Worker related script ``.js`` output files. -Developers can choose to output either to JavaScript or HTML. If outputting JavaScript (``emcc -o out.js``), the developer is expected to manually create the ``out.html`` main page in which the code is run in browsers. When targeting HTML with ``emcc -o out.html`` (the recommended build mode), Emscripten will generate the HTML shell file automatically. This shell file can be customized by using the ``emcc -o out.html --shell-file path/to/custom_shell.html`` linker directive. Copy the `default minimal HTML shell file `_ from Emscripten repository to your project tree to get a good starting template for a customized shell file. +Developers can choose to output either to JavaScript or HTML. If outputting JavaScript (``emcc -o out.js``), the developer is expected to manually create the ``out.html`` main page in which the code is run in browsers. When targeting HTML with ``emcc -o out.html`` (the recommended build mode), Emscripten will generate the HTML shell file automatically. This shell file can be customized by using the ``emcc -o out.html --shell-file path/to/custom_shell.html`` linker directive. Copy the `default minimal HTML shell file `_ from Emscripten repository to your project tree to get a good starting template for a customized shell file. The following sections offer tips for improving the site experience. @@ -71,8 +71,6 @@ An inherent property of asm.js and WebAssembly applications is that they need a Because this memory allocation needs to be contiguous, it can happen that the user's browser process does have enough memory, but only the address space of the process is too fragmented, and there is not enough linear address space available to satisfy the allocation. To avoid this issue, the best practice is to allocate the ``WebAssembly.Memory`` object (``ArrayBuffer`` for asm.js) up front at the top of the main page, before any other allocations or page script load actions are done. This ensures that the allocation has best chances to succeed. See the fields ``Module['buffer']`` and ``Module['wasmMemory']`` for more information. -Additionally, it is possible to opt in to content process isolation specifically for a web page that needs this kind of a large allocation. To utilize this machinery, specify the HTTP response header ``Large-Allocation: `` when serving the main html page. This support is currently implemented in Firefox 53. - Last, it is easy to accidentally cling to large unneeded blocks of memory after the page has loaded. For example, in WebAssembly, once the WebAssembly Module has been instantiated to a ``WebAssembly.Instance`` object, the original ``WebAssembly.Module`` object is no longer needed in memory, and it is best to clear all references to it so that the garbage collector can reclaim it, because the Module object can be dozens of megabytes in size. Similar, make sure that all XHRed files, asset data and large scripts are not referenced anymore when not used. Check out the browser's memory profiling tool, and the ``about:memory`` page in Firefox to perform memory profiling to ensure that memory is not being wasted. Robust Error Handling @@ -106,7 +104,7 @@ This way the page will be future compatible once support for the particular feat - Use a network limiter tool to constrain download or upload bandwidth speeds to simulate slow network connections. This can uncover bugs related to timing dependencies for network transfers. For example, a small network transfer may be implicitly assumed to finish before a large one, but that might not always be the case. -- When developing the page locally, perform testing by using a local web server and not just via ``file://`` URLs. The script ``emrun.py`` in Emscripten source tree is designed to serve as an ad hoc web server for this purpose. Emrun is preconfigured to handle serving gzip compressed files (with suffix ``.gz``), and enables support for the ``Large-Allocation`` header, and allows command line automation runs of compiled pages. +- When developing the page locally, perform testing by using a local web server and not just via ``file://`` URLs. The script ``emrun.py`` in Emscripten source tree is designed to serve as an ad hoc web server for this purpose. Emrun is preconfigured to handle serving gzip compressed files (with suffix ``.gz``), and allows command line automation runs of compiled pages. - Catch all exceptions that come from within entry points that call to compiled asm.js and WebAssembly code. There are three distinct exception classes that compiled code can throw: diff --git a/site/source/docs/compiling/Dynamic-Linking.rst b/site/source/docs/compiling/Dynamic-Linking.rst index 7ef9893e35bb3..81487447de38e 100644 --- a/site/source/docs/compiling/Dynamic-Linking.rst +++ b/site/source/docs/compiling/Dynamic-Linking.rst @@ -12,7 +12,7 @@ with little or no changes (see :ref:`Building-Projects`). In addition, Emscripten also has support for a form of **dynamic** linking of WebAssembly modules. This can add overhead, so for best performance static -linking should still be preferred. However, this overhead can can be reduced +linking should still be preferred. However, this overhead can be reduced with the use of certain command line flags. See below for details. Background @@ -109,9 +109,9 @@ along with the main module, during startup and they are linked together before your application starts to run. - Build one part of your code as the main module, linking it using - ``-sMAIN_MODULE``. + ``-sMAIN_MODULE`` (See :ref:`MAIN_MODULE`). - Build other parts of your code as side modules, linking it using - ``-sSIDE_MODULE``. + ``-sSIDE_MODULE`` (See :ref:`SIDE_MODULE`). For the main module the output suffix should be ``.js`` (the WebAssembly file will be generated alongside it just like normal). For the side @@ -127,7 +127,7 @@ the command line when you link the main module. e.g. emcc -sMAIN_MODULE main.c libsomething.wasm -At runtime, the JavaScript loading code will load ``libsomthing.wasm`` (along +At runtime, the JavaScript loading code will load ``libsomething.wasm`` (along with any other side modules) along with the main module before the application starts to run. The running application then can access code from any of the modules linked together. @@ -152,14 +152,14 @@ By default, main modules disable dead code elimination. That means that all the code compiled remains in the output, including all system libraries linked in, and also all the JS library code. -That is the default behavior since it is the least surprising. But it is -also possible to use normal dead code elimination, by building with -``-sMAIN_MODULE=2`` (instead of 1). In that mode, the main module is -built normally, with no special behavior for keeping code alive. It is -then your responsibility to make sure that code that side modules need -is kept alive. You can do this either by adding to ``EXPORTED_FUNCTIONS`` or -tagging the symbol ``EMSCRIPTEN_KEEPALIVE`` in the source code. -See ``other.test_minimal_dynamic`` for an example of this in action. +That is the default behavior since it is the least surprising. But it is also +possible to use normal dead code elimination, by building with +``-sMAIN_MODULE=2`` (instead of 1). In that mode, the main module is built +normally, with no special behavior for keeping code alive. It is then your +responsibility to make sure that code that side modules need is kept alive. You +can do this either by adding to :ref:`EXPORTED_FUNCTIONS` or tagging the symbol +``EMSCRIPTEN_KEEPALIVE`` in the source code. See ``other.test_minimal_dynamic`` +for an example of this in action. If you are doing load time dynamic linking then any symbols needed by the side modules specified on the command line will be kept alive @@ -201,25 +201,25 @@ symbols remain unresolved, and code can start to run even if there are. It will run successfully if they are not called in practice. If they are, you will get a runtime error. What went wrong should be clear from the stack trace (in an unminified build); building with -``-sASSERTIONS`` can help some more. +:ref:`ASSERTIONS` can help some more. Limitations ----------- -- Chromium does not support compiling >4kB WASM on the main thread, and that - includes side modules; you can use ``--use-preload-plugins`` (in ``emcc`` or - ``file_packager.py``) to make Emscripten compile them on startup - `[doc] `__ - `[discuss] `__. +- Chromium does not support synchronous compiling of Wasm modules over 8Mb on + the main thread. You could run into this limit when using the synchronous + ``dlopen`` API to load large side modules. Emscripten has async versions of + ``dlopen`` that can work around this issue (See :c:func:`emscripten_dlopen`). - ``EM_ASM`` and ``EM_JS`` code defined within side modules depends on ``eval`` - support and are therefore incompatible with ``-sDYNAMIC_EXECUTION=0``. + support and are therefore incompatible with ``-sDYNAMIC_EXECUTION=0`` (See + :ref:`DYNAMIC_EXECUTION`). Pthreads support ---------------- -Dynamic linking + pthreads is is still experimental. As such, linking with both -``MAIN_MODULE`` and ``-pthread`` will produce a warning. +Dynamic linking + pthreads is still experimental. As such, linking with both +:ref:`MAIN_MODULE` and ``-pthread`` will produce a warning. While load-time dynamic linking works without any complications, runtime dynamic linking via ``dlopen``/``dlsym`` can require some extra consideration. The @@ -231,13 +231,13 @@ added and these changes need to be mirrored on every thread in the process. Changes to the table are protected by a mutex, and before any thread returns from ``dlopen`` or ``dlsym`` it will wait until all other threads are sync. In order to make this synchronization as seamless as possible, we hook into the -low level primitives of `emscripten_futex_wait` and `emscirpten_yield`. +low level primitives of `emscripten_futex_wait` and `emscripten_yield`. For most use cases all this happens under hood and no special action is needed. However, there there is one class of application that currently may require modification. If your applications busy waits, or directly uses the ``atomic.waitXX`` instructions (or the clang ``__builtin_wasm_memory_atomic_waitXX`` builtins) you maybe need to switch it -to use ``emscripten_futex_wait`` or order avoid deadlocks. If you don't use +to use ``emscripten_futex_wait`` in order to avoid deadlocks. If you don't use ``emscripten_futex_wait`` while you block, you could potentially block other threads that are calling ``dlopen`` and/or ``dlsym``. diff --git a/site/source/docs/compiling/Modularized-Output.rst b/site/source/docs/compiling/Modularized-Output.rst new file mode 100644 index 0000000000000..2e14c84252130 --- /dev/null +++ b/site/source/docs/compiling/Modularized-Output.rst @@ -0,0 +1,176 @@ +.. _Modularized-Output: + +================== +Modularized Output +================== + +By default, emscripten outputs regular JS scripts which can be loaded on the Web +via a ``script`` tag, or on the command line using Node or other JS runtime. + +In this default configuration the generated :ref:`module` variable (and indeed +all of the emscripten internal symbols) are added to the global scope. This +means that only one emscripten-built program can be loaded in a given scope +(since multiple modules would clobber each other's state). + +The :ref:`modularize` setting (along with other related settings) can be used to +isolate the generated module within its own private scope and/or allow multiple +instances of the same module to be created. + +This section covers the various modularization options available and how to use +them. + + +Using -sMODULARIZE +================== + +The :ref:`modularize` setting has two primary functions. Firstly, it +puts all of the generated code inside its own scope so that the global namespace +is not polluted when the JS is loaded (for example, via ``script`` tag). +Secondly, it allows multiple instances of the module to be created. + +The generated factory function is async and it returns a promise of an instance +of the program. It can be called any number of times to create multiple +separate and isolated instances of the same program. + +By default, the factory function is called ``Module``, but it can be given a +unique name via the ``-sEXPORT_NAME`` setting. + +For example, a program built using ``-sMODULARIZE -sEXPORT_NAME=Foo`` can +be instantiated using: + +:: + + var myFoo = await Foo({ /* optional params */ }); + myFoo._nativeMethod(); + + +or: + +:: + + Foo({ /* optional params */ }).then((myFoo) => { + myFoo._nativeMethod(); + } + +If node is included in the ``-sENVIRONMENT`` setting then the generated module +also acts as a ``CommonJS`` module under node. + +Because there is no global ``Module`` object in this case any parameters to the +module creation are passed via the optional parameter to the factory function. +For example, if you wanted to use a custom ``print`` method you could write: + +:: + + var myFoo = await Foo({ print: myPrint }); + + +Generating ES6 Modules +====================== + +The :ref:`EXPORT_ES6` setting can be used to instead output JavaScript in the +form of an ES module. (The ES module format did not exist at time when the +`MODULARIZE` setting was first created otherwise this would likely be the +default). + +Using an output filename that ends in `.mjs` will automatically enable this +setting. + +In this mode the module has a single default export which is the factory +function for creating new instances of the module. For example: + +:: + + import Foo from './emcc-output.js'; + var myFoo = await Foo({ print: myPrint }); + +The name ``Foo`` here can be whatever you choose and will always be assigned to +the default export of the module. + + +Using -sMODULARIZE=instance (experimental) +========================================== + +Emscripten has experimental support for performing only the encapsulation part of +modularization, and not the ability to create multiple instances. In this +mode a module is created that exports a single instance of the program rather +than a factory function. + +This setting implicitly enables :ref:`export_es6` and will not work when +:ref:`export_es6` is explictly disabled. + +In this mode the default export of the module is an initializer function which +allows input parameters to be passed to the instance. Other elements normally +found on the module object are instead exported directly. For example: + +:: + + // Import the default init function and a named native method + import init, { _nativeMethod } from './emcc-output.js'; + await init({ print: myPrint }); + _nativeMethod(); + +Limitations +----------- + +Some major features still do not work in this mode. Many of these we hope to +fix in future releases. Current limitations include: + +* Internal usage (e.g. usage within EM_JS / JS library code) of the ``Module`` + global does not work. This is because symbols are exported directly using + ES6 module syntax rather than using the ``Module`` global. + +* The ``wasmExports`` internal global does not exist. + +* `ccall`/`cwrap` are not supported (depends on the ``Module`` global). + +* :ref:`minimal_runtime` is not supported. + +* The output of file_packager is not compatible so :ref:`emcc-preload-file` and + :ref:`emcc-embed-file` do not work. + + +Source Phase Imports (experimental) +=================================== + +`Source phase imports`_ is a JavaScript proposal that adds support for importing +Wasm modules via ES import statements. This allows Emscripten to elide some of +the auto-generated code for finding and fetching the Wasm binary. + +See :ref:`source_phase_imports`. + +This setting implicitly enables :ref:`export_es6` and will not work when +:ref:`export_es6` is explictly disabled. + + +ES Module Integration (experimental) +==================================== + +`Wasm ESM integration`_ is a WebAssembly proposal that allows Wasm instances to +be imported directly as ES modules. This allows Emscripten to elide a lot of +boilerplate code for linking up Wasm and JavaScript. + +See :ref:`wasm_esm_integration`. + +Limitations +----------- + +This setting implicitly enables :ref:`export_es6` and sets :ref:`MODULARIZE` to +``instance``. Because of this all the same limitations mentioned above for +``-sMODULARIZE=instance`` apply. + +Some additional limitations are: + +* :ref:`wasm_workers` is not yet supported. + +* :ref:`abort_on_wasm_exceptions` is not supported (requires wrapping wasm + exports). + +* :ref:`asyncify` is not supported (depends on ``wasmExports`` global) + +* Setting :ref:`wasm` to ``0`` is not supported. + +* Setting :ref:`wasm_async_compilation` to ``0`` is not supported. + + +.. _Source phase imports: https://github.com/tc39/proposal-source-phase-imports +.. _Wasm ESM integration: https://github.com/WebAssembly/esm-integration diff --git a/site/source/docs/compiling/Running-html-files-with-emrun.rst b/site/source/docs/compiling/Running-html-files-with-emrun.rst index 7324d3a000a26..8fa5aa56813a4 100644 --- a/site/source/docs/compiling/Running-html-files-with-emrun.rst +++ b/site/source/docs/compiling/Running-html-files-with-emrun.rst @@ -110,7 +110,7 @@ The following command line flags affect logging output: - ``--verbose``: Print detailed information about *emrun* internal steps. - ``--log_stdout ``: Write all ``stdout`` messages from the application to the named file (instead of printing to terminal). -- ``--lot_stderr ``: Write all ``stderr`` messages from the application to the named file (instead of printing to terminal). +- ``--log_stderr ``: Write all ``stderr`` messages from the application to the named file (instead of printing to terminal). - ``--system_info``: Print detailed information about the current system before launching. This is useful during automated runs when you want to capture hardware information to logs. - ``--browser_info``: Print information about which browser is about to be launched. - ``--no_emrun_detect``: Hide the warning message that is launched if a target **.html** file is detected to not have been built with ``--emrun``. diff --git a/site/source/docs/compiling/WebAssembly.rst b/site/source/docs/compiling/WebAssembly.rst index 3bb28b2f2f853..c7b939edbb2af 100644 --- a/site/source/docs/compiling/WebAssembly.rst +++ b/site/source/docs/compiling/WebAssembly.rst @@ -92,14 +92,57 @@ When using ``emcc`` to build to WebAssembly, you will see a ``.wasm`` file conta You may also see additional files generated, like a ``.data`` file if you are preloading files into the virtual filesystem. All that is exactly the same as when building to asm.js. One difference you may notice is the lack of a ``.mem file``, which for asm.js contains the static memory initialization data, which in WebAssembly we can pack more efficiently into the WebAssembly binary itself. -WebAssembly support in browsers -=============================== -WebAssembly is supported by all major browsers going back to Firefox 52, Chrome -57, Safari 11 and Opera 44. +WebAssembly feature extensions +============================== + +Since its original launch, WebAssembly has been expanded with various feature +extensions, which have been implemented in browsers. A list of features +(including already-shipped and in-progress) and details about browser versions +that support them can be found on +`webassembly.org `_. + +Several of these features can be used by Emscripten (or are by default) and can +be enabled or disabled individually (using either Clang or emscripten flags) +or by selecting which version of browsers Emscripten should target. + +Examples: + +* Exception handling (see :ref:`exceptions` for details). +* SIMD (see :ref:`Porting SIMD code` for details). +* Nontrapping float-to-int conversion (enabled by default, use + ``-mno-nontrapping-fptoint`` to disable). + Clang will generate nontrapping (saturating) float-to-int conversion instructions for + C typecasts. This should have no effect on programs that do not have + undefined behavior but if the casted floating-point value is outside the range + of the target integer type, the result will be a number of the max or min value + instead of a trap. This also results in a small code size improvement because + of details of the LLVM IR semantics. +* Bulk memory operations (enabled by default, use + ``-mno-bulk-memory-opt -mno-bulk-memory`` to disable). ``memory.copy`` + and ``memory.fill`` instructions are used in the implementation of C + ``memcpy`` and ``memset``, and Clang may generate them elsewhere. +* JS BigInt integration (enabled by default, use the + ``-sWASM_BIGINT=0`` :ref:`setting ` + to disable). This has the effect that Wasm i64 values are passed and returned + between Wasm and JS as BigInt values rather than being split by Binaryen into + pairs of Numbers. +* Sign-extension operators (enabled by default, use ``-mno-sign-ext`` to disable). + +For the features that are enabled by default (or will be when sufficient +browser support exists), it's also possible to control them by specifying +which browser versions you want to target. You can use the +``-sMIN_FIREFOX_VERSION`` :ref:`setting ` +(and also ``MIN_CHROME_VERSION``, ``MIN_SAFARI_VERSION`` and +``MIN_NODE_VERSION``). Setting a value lower than the default version will +disable features not supported by the specified version. Some features (e.g. +Exception handling and threads) are not enabled by default because they have +tradeoffs (e.g. binary size costs or restrictions on how the resulting wasm +can be used such as COEP headers). These are not controlled by the browser +version flags and must be enabled explicitly. +See the :ref:`settings ` page for details of the default +browser versions Emscripten targets. -For further info on WebAssembly features supported in various browsers, see the -`WebAssembly Roadmap `_ ``.wasm`` files and compilation =============================== diff --git a/site/source/docs/compiling/index.rst b/site/source/docs/compiling/index.rst index 5ab6dc48fce45..e8b03becc832d 100644 --- a/site/source/docs/compiling/index.rst +++ b/site/source/docs/compiling/index.rst @@ -9,6 +9,7 @@ This section contains topics about building projects and running the output. - :ref:`Building-Projects` shows how to use :ref:`emccdoc` as a drop-in replacement for *gcc* in your existing project. - :ref:`WebAssembly` explains how Emscripten can be used to build WebAssembly files - :ref:`Running-html-files-with-emrun` explains how to use *emrun* to run generated HTML pages in a locally launched web server. +- :ref:`Modularized-Output` covers the various options for generating modularized JS code. - :ref:`Deploying-Pages` covers topics related to hosting Emscripten compiled web pages on a CDN. - :ref:`GitLab` explains how to build and test projects on GitLab. - :ref:`Contrib-Ports` contains information about contrib ports. @@ -19,6 +20,7 @@ This section contains topics about building projects and running the output. Building-Projects WebAssembly + Modularized-Output Dynamic-Linking Running-html-files-with-emrun Deploying-Pages diff --git a/site/source/docs/contributing/AUTHORS.rst b/site/source/docs/contributing/AUTHORS.rst index 819dd4409069a..1fa08dd5586ce 100644 --- a/site/source/docs/contributing/AUTHORS.rst +++ b/site/source/docs/contributing/AUTHORS.rst @@ -7,7 +7,7 @@ AUTHORS The `AUTHORS `_ file lists everyone who has contributed to Emscripten. It is optional for a contributor to add themselves to the `AUTHORS `_ file before :doc:`contributing `. -The contributors for releases up to Emscripten |release| inclusive (|today|) are listed below. +The contributors for releases up to Emscripten |release| inclusive are listed below. .. include:: ../../../../AUTHORS :literal: diff --git a/site/source/docs/contributing/developers_guide.rst b/site/source/docs/contributing/developers_guide.rst index 9753ecb762ad4..0f00b0bf54a8d 100644 --- a/site/source/docs/contributing/developers_guide.rst +++ b/site/source/docs/contributing/developers_guide.rst @@ -26,7 +26,7 @@ get using the emsdk: emsdk install tot emsdk activate tot -This with install the latest "tip-of-tree" binaries needed to run Emscripten. +This will install the latest "tip-of-tree" binaries needed to run Emscripten. You can use these emsdk-provided binaries with a git checkout of the Emscripten repository. To do this, you can either edit your local ``.emscripten`` config file, or set ``EM_CONFIG=/path/to/emsdk/.emscripten`` in your environment. @@ -79,10 +79,10 @@ The :ref:`Emscripten Compiler Frontend (emcc) ` is a python script that - **emcc** calls :term:`Clang` to compile C++ and ``wasm-ld`` to link it. It builds and integrates with the Emscripten system libraries, both the compiled ones and the ones implemented in JS. -- **emcc** then calls `emscripten.py `_ +- **emcc** then calls `emscripten.py `_ which performs the final transformation to Wasm (including invoking **wasm-emscripten-finalize** from Binaryen) and calls the JS compiler - (see ``src/compiler.js`` and related files) which emits the JS. + (see ``tools/compiler.mjs`` and related files) which emits the JS. - If optimizing Wasm, **emcc** will then call **wasm-opt**, run meta-dce, and other useful things. It will also run JS optimizations on the JS that is emitted alongside the Wasm. diff --git a/site/source/docs/getting_started/FAQ.rst b/site/source/docs/getting_started/FAQ.rst index be6b48b437a1f..1d2653a2bc98f 100644 --- a/site/source/docs/getting_started/FAQ.rst +++ b/site/source/docs/getting_started/FAQ.rst @@ -18,7 +18,7 @@ Why do I get errors building basic code and the tests? All the tests in the :ref:`Emscripten test suite ` are known to build and pass on our test infrastructure, so if you see failures -locally it is likely that there is some problem with your environment. (Rarely, +locally, it is likely that there is some problem with your environment. (Rarely, there may be temporary breakage, but never on a tagged release version.) First call ``emcc --check``, which runs basic sanity checks and prints out @@ -105,13 +105,13 @@ compilation time). Why is my compiled code big? ============================ -Make sure you build with ``-O3`` or ``-Os`` so code is fully optimized and +Make sure you build with ``-O3`` or ``-Os``, so the code is fully optimized and minified. You should use the closure compiler, gzip compression on your webserver, etc., see the :ref:`section on code size in Optimizing code `. -Why does compiling code that works on another machine gives me errors? +Why does compiling code that works on another machine give me errors? ====================================================================== Make sure you are using the Emscripten bundled system headers. Using :ref:`emcc @@ -207,11 +207,11 @@ JavaScript that does not complete and return control to the browser. Graphical C++ apps typically have an infinite main loop in which event handling, processing and rendering is done, followed by a delay to keep the frame-rate right (``SDL_DELAY`` in :term:`SDL` apps). As the main loop does not complete -(is infinite) it cannot return control to the browser, and the app will hang. +(is infinite), it cannot return control to the browser, and the app will hang. Apps that use an infinite main loop should be re-coded to put the actions for a single iteration of the loop into a single "finite" function. In the native -build this function can be run in an infinite loop as before. In the Emscripten +build, this function can be run in an infinite loop as before. In the Emscripten build it is set as the :ref:`main loop function ` and will be called by the browser at a specified frequency. @@ -360,23 +360,16 @@ The crucial thing is that ``Module`` exists, and has the property ``onRuntimeInitialized``, before the script containing emscripten output (``my_project.js`` in this example) is loaded. -Another option is to use the ``MODULARIZE`` option, using ``-sMODULARIZE``. -That puts all of the generated JavaScript into a factory function, which you can -call to create an instance of your module. The factory function returns a -Promise that resolves with the module instance. The promise is resolved once -it's safe to call the compiled code, i.e. after the compiled code has been -downloaded and instantiated. For example, if you build with ``-sMODULARIZE -s -'EXPORT_NAME="createMyModule"'``, then you can do this: +When using the ``MODULARIZE`` is it sufficient to await the returned promise +from the factory function. For example: :: - createMyModule(/* optional default settings */).then(function(Module) { + createMyModule(/* optional default settings */).then((myModule) => { // this is reached when everything is ready, and you can call methods on Module }); -Note that in ``MODULARIZE`` mode we do not look for a global Module object for -default values. Default values must be passed as a parameter to the factory -function. (see details in settings.js) +See :ref:`Modularized-Output` for more this. .. _faq-NO_EXIT_RUNTIME: diff --git a/site/source/docs/getting_started/Tutorial.rst b/site/source/docs/getting_started/Tutorial.rst index c697159b28eb3..8ac56e1418e07 100644 --- a/site/source/docs/getting_started/Tutorial.rst +++ b/site/source/docs/getting_started/Tutorial.rst @@ -13,7 +13,7 @@ First things first ====================== Make sure you have :ref:`downloaded and installed ` -Emscripten (the exact approach for doing this will depend your operating system: +Emscripten (the exact approach for doing this will depend on your operating system: Linux, Windows, or Mac). Emscripten is accessed using the :ref:`emccdoc`. This script invokes all the @@ -28,10 +28,10 @@ using ``./emcc`` or ``./em++``. For the next section you will need to open a command prompt: - On Linux or macOS, open a *Terminal*. -- On Windows open the :ref:`Emscripten Command Prompt `, a command +- On Windows, open the :ref:`Emscripten Command Prompt `, a command prompt that has been pre-configured with the correct system paths and settings to point to the :term:`active ` Emscripten tools. To access - this prompt, type **Emscripten** in the Windows 8 start screen, and then + this prompt, type **Emscripten** in the Windows start menu, and then select the **Emscripten Command Prompt** option. Navigate with the command prompt to the emscripten directory under the SDK. This @@ -39,7 +39,7 @@ is a folder below the :term:`emsdk root directory`, typically **/upstream/emscripten/**. The examples below will depend on finding files relative to that location. -.. note:: In older emscripten versions the directory structure was different: +.. note:: In older emscripten versions, the directory structure was different: the version number appeared, and the backend (fastcomp/upstream) did not, so you would use something like **/emscripten/1.20.0/**. @@ -52,7 +52,7 @@ If you haven't run Emscripten before, run it now with: :: ./emcc -v If the output contains warnings about missing tools, see -:ref:`verifying-the-emscripten-environment` for debugging help. Otherwise +:ref:`verifying-the-emscripten-environment` for debugging help. Otherwise, continue to the next sections where we'll build some code. @@ -61,7 +61,7 @@ Running Emscripten You can now compile your first C/C++ file to JavaScript. -First, lets have a look at the file to be compiled: **hello_world.c**. This is +First, let's have a look at the file to be compiled: **hello_world.c**. This is the simplest test code in the SDK, and as you can see, all it does is print "hello, world!" to the console and then exit. @@ -86,15 +86,6 @@ execute it. You can run them using :term:`node.js`: This prints "hello, world!" to the console, as expected. -.. note:: Older node.js versions do not have WebAssembly support yet. In that - case you will see an error message suggesting that you build with - ``-sWASM=0`` to disable WebAssembly, and then emscripten will emit the compiled - code as JavaScript. In general, WebAssembly is recommended as it has - widespread browser support and is more efficient both to execute and to - download (and therefore emscripten emits it by default), but sometimes you - may need your code to run in an environment where it is not yet present and - so should disable it. - .. tip:: If an error occurs when calling *emcc*, run it with the ``-v`` option to print out a lot of useful debug information. @@ -116,11 +107,11 @@ file as the target file: :: You can now open ``hello.html`` in a web browser. -.. note:: Unfortunately several browsers (including *Chrome*, *Safari*, and - *Internet Explorer*) do not support ``file://`` :term:`XHR` requests, and - can't load extra files needed by the HTML (like a ``.wasm`` file, or packaged - file data as mentioned lower down). For these browsers you'll need to serve - the files using a :ref:`local webserver ` and then open +.. note:: Unfortunately, several browsers (including *Chrome* and *Safari*) do + not support ``file://`` :term:`XHR` requests, and can't load extra files + needed by the HTML (like a ``.wasm`` file, or packaged file data as mentioned + lower down). For these browsers, you'll need to serve the files using a + :ref:`local webserver ` and then open ``http://localhost:8000/hello.html``). Once you have the HTML loaded in your browser, you'll see a text area for @@ -128,15 +119,15 @@ displaying the output of the ``printf()`` calls in the native code. The HTML output isn't limited just to just displaying text. You can also use the SDL API to show a colored cube in a ```` element (on browsers that -support it). For an example, build the `hello_world_sdl.cpp -`_ +support it). For an example, build the `hello_world_sdl.c +`_ test code and then refresh the browser: :: - ./emcc test/hello_world_sdl.cpp -o hello.html + ./emcc test/hello_world_sdl.c -o hello.html The source code for the second example is given below: -.. include:: ../../../../test/hello_world_sdl.cpp +.. include:: ../../../../test/hello_world_sdl.c :literal: diff --git a/site/source/docs/getting_started/downloads.rst b/site/source/docs/getting_started/downloads.rst index 0f55b6461aef8..8b06c66ba5f67 100644 --- a/site/source/docs/getting_started/downloads.rst +++ b/site/source/docs/getting_started/downloads.rst @@ -8,7 +8,7 @@ Download and install ` if you prefer that to downloading binaries using the emsdk. -.. tip:: if you'd like to install emscripten using the **unofficial** packages +.. tip:: If you'd like to install emscripten using the **unofficial** packages instead of the **officially supported** emsdk, see the bottom of the page. .. _sdk-installation-instructions: @@ -16,12 +16,12 @@ Download and install Installation instructions using the emsdk (recommended) ======================================================= -First check the :ref:`Platform-specific notes +First, check the :ref:`Platform-specific notes ` below and install any prerequisites. The core Emscripten SDK (emsdk) driver is a Python script. You can get it for -the first time with +the first time with: :: @@ -52,20 +52,22 @@ GitHub and set them as :term:`active `: # Activate PATH and other environment variables in the current terminal source ./emsdk_env.sh + .. tip:: If you want to avoid executing `source ./emsdk_env.sh` every time you open a new terminal, you can follow the instructions given by the `emsdk activate` command above to add this command to your startup scripts. + .. note:: On Windows, run ``emsdk.bat`` instead of ``./emsdk``, and ``emsdk_env.bat`` instead of ``source ./emsdk_env.sh``. .. note:: On Windows, if you use the ``activate`` command, the step of ``emsdk_env.bat`` is optional. If you want to know more, see :ref:`activate SDK version `. .. note:: ``git pull`` will fetch the current list of tags, but very recent ones may not yet be present there. You can run ``./emsdk update-tags`` to update the list of tags directly. -If you change the location of the SDK (e.g. take it to another computer on an +If you change the location of the SDK (e.g. take it to another computer on a USB), re-run the ``./emsdk activate latest`` and ``source ./emsdk_env.sh`` commands. Emsdk install targets --------------------- -In the description above we asked the emsdk to install and activate ``latest``, +In the description above, we asked the emsdk to install and activate ``latest``, which is the latest tagged release. That is often what you want. You can also install a specific version by specifying it, for example, @@ -90,7 +92,7 @@ using a more careful procedure). Tip-of-tree builds may be useful for continuous integration that uses the emsdk (as Emscripten's GitHub CI does), and you may want to use it in your own CI as well, so that if you find a regression on your project you can report it and prevent it from reaching a tagged release. -Tip-of-builds may also be useful if you want to test a feature that just landed +Tip-of-tree builds may also be useful if you want to test a feature that just landed but didn't reach a release yet. To use a tip-of-tree build, use the ``tot`` target, and note that you must specify the backend explicitly, @@ -99,7 +101,7 @@ target, and note that you must specify the backend explicitly, # Get a tip-of-tree ./emsdk install tot -(In the above examples we installed the various targets; remember to also +(In the above examples, we installed the various targets; remember to also ``activate`` them as in the full example from earlier.) .. _platform-notes-installation_instructions-SDK: @@ -110,7 +112,7 @@ Platform-specific notes Windows +++++++ -#. Install Python 3.6 or newer (older versions may not work due to `a GitHub change with SSL `_). +#. Install Python 3.8 or newer. .. note:: Instead of running emscripten on Windows directly, you can use the Windows Subsystem for Linux to run it in a Linux environment. @@ -120,8 +122,8 @@ macOS .. note:: Emscripten requires macOS 10.14 Mojave or above. -If you use the Emscripten SDK it includes a bundled version of Python 3. -Otherwise you will need to manually install and use Python 3.6 or newer. +If you use the Emscripten SDK, it includes a bundled version of Python 3. +Otherwise, you will need to manually install and use Python 3.8 or newer. These instructions explain how to install **all** the :ref:`required tools `. You can :ref:`test whether some of these are already @@ -161,8 +163,6 @@ Linux # Install CMake (optional, only needed for tests and building Binaryen or LLVM) sudo apt-get install cmake -.. note:: If you want to use your system's Node.js instead of the emsdk's, it may be ``node`` instead of ``nodejs``, and you can adjust the ``NODE_JS`` attribute of your ``.emscripten`` file to point to it. - - *Git* is not installed automatically. Git is only needed if you want to use tools from a development branch. :: @@ -258,5 +258,5 @@ The following is a partial list of such unofficial emscripten packages: - maintainer: @chenrui333 **Arch Linux** - - package info: https://github.com/archlinux/svntogit-community/tree/packages/emscripten/trunk - - maintainer: Sven-Hendrik Haase + - package info: https://archlinux.org/packages/extra/x86_64/emscripten + - maintainer: Sven-Hendrik Haase diff --git a/site/source/docs/getting_started/test-suite.rst b/site/source/docs/getting_started/test-suite.rst index ce48a504df0b9..e3eb91c46a3a9 100644 --- a/site/source/docs/getting_started/test-suite.rst +++ b/site/source/docs/getting_started/test-suite.rst @@ -23,11 +23,11 @@ how best to do that. Running tests ============= -Run the test suite runner (`test/runner `_) with no arguments to see the help message: +Run the test suite runner (`test/runner `_) with ``--help`` to see the help message: .. code-block:: bash - test/runner + test/runner --help The tests are divided into *modes*. You can run either an entire mode or an individual test, or use wildcards to run some tests in some modes. For example: @@ -99,7 +99,7 @@ the first failure. that are normally run in parallel you can force them to run serially using ``-j1``. -One a test is fixed you continue where you left off using ``--start-at`` option: +Once a test is fixed, you continue where you left off using ``--start-at`` option: .. code-block:: bash @@ -181,11 +181,19 @@ As with all the test suites, you can also run a specific benchmark: # Run one specific benchmark test/runner benchmark.test_skinning -Usually you will want to customize the in `test/test_benchmark.py` to -run the benchmarks you want (there is currently no external config file). Things -you may want to modify include: +You can also specify which benchmarkers are run by using the environment +variable `EMTEST_BENCHMARKERS`. It accepts a comma separated list of named +benchmarkers (names can be found in `named_benchmarkers` in +`test/test_benchmark.py`). + +.. code-block:: bash + + # Run one specific benchmark and with clang and v8. + EMTEST_BENCHMARKERS=clang,v8 test/runner benchmark.test_skinning + +To further customize how the benchmarks are run, you will want to edit the file +`test/test_benchmark.py`. Some of the options include: -* ``benchmarkers`` is the list of VMs to run the benchmarks on. * ``DEFAULT_ARG`` is how long the benchmark should run (they all try to run for a similar amount of time for consistency). * ``TEST_REPS`` is how many times to repeat each run (more will take longer, but @@ -196,9 +204,20 @@ you may want to modify include: Debugging test failures ======================= -Setting the :ref:`debugging-EMCC_DEBUG` is useful for debugging tests, as it -emits debug output and intermediate files (the files go in -**/tmp/emscripten_temp/**): +By default the test runner will hide all test output and use a single terminal +line to show test results. + +If you want to see more information you can run with ``-v/--verbose`` to show the +stdout / stderr / logging produced during each test. + +If you add a second ``-v/--verbose`` then then test runner will output even more +information. For example it will show all the subcommands that it runs, as it +runs them. + +For even more debugging info, you can set :ref:`debugging-EMCC_DEBUG` which will +cause the emscripten compiler itself to output a lot of debug info. This will +also tell the compiler to leave all its temporary files behind after it runs +(the files go in ``/tmp/emscripten_temp/``): .. code-block:: bash @@ -214,11 +233,11 @@ emits debug output and intermediate files (the files go in EMCC_DEBUG=2 test/runner test_hello_world -You can also specify ``--save-dir`` to save the temporary directory that the -test runner uses into **/out/test/**. This is a test suite-specific -feature, and is useful for inspecting test outputs as well as temporary files -generated by the test. By default, the temporary directory will be cleaned -between each test run, but you can add ``--no-clean`` to avoid this. +If you run just one a single test (or one test at at time using ``-j1``) the test +output can always be found in ``out/test``. This is useful for inspecting test +outputs as well as temporary files generated by the test. By default, the temporary +directory will be cleaned between each test run, but you can add ``--no-clean`` to +avoid this. The :ref:`Debugging` topic provides more guidance on how to debug Emscripten-generated code. diff --git a/site/source/docs/index.rst b/site/source/docs/index.rst index 491b39b330cc4..0e587516eeaa1 100644 --- a/site/source/docs/index.rst +++ b/site/source/docs/index.rst @@ -30,7 +30,7 @@ This comprehensive documentation set contains everything you need to know to use **Reference:** - :ref:`api-reference-index` is a reference for the Emscripten toolchain. -- :ref:`settings-reference` is a reference of all the Emscripten compiler serttings. +- :ref:`settings-reference` is a reference of all the Emscripten compiler settings. - :ref:`tools-reference` is a reference for the Emscripten integration APIs. - :ref:`Sanitizers` shows how to debug with sanitizers. - :ref:`Module-Splitting` is a guide to splitting modules and deferring the diff --git a/site/source/docs/introducing_emscripten/about_emscripten.rst b/site/source/docs/introducing_emscripten/about_emscripten.rst index 9cccbe37b9510..c0c97c1508560 100644 --- a/site/source/docs/introducing_emscripten/about_emscripten.rst +++ b/site/source/docs/introducing_emscripten/about_emscripten.rst @@ -12,7 +12,7 @@ toolchain to WebAssembly. Using Emscripten you can: - Compile the C/C++ **runtimes** of other languages into WebAssembly, and then run code in those other languages in an *indirect* way (for example, this has been done for - `Python `_ and + `Python `_ and `Lua `_). Practically any **portable** C or C++ codebase can be compiled into WebAssembly diff --git a/site/source/docs/introducing_emscripten/community.rst b/site/source/docs/introducing_emscripten/community.rst index 5c6dd9c67a9ad..f6eed6967722c 100644 --- a/site/source/docs/introducing_emscripten/community.rst +++ b/site/source/docs/introducing_emscripten/community.rst @@ -12,7 +12,7 @@ Welcome to the Emscripten community! Get in touch ============ -The best ways contact the community are: +The best ways to contact the community are: - The GitHub `Issue Tracker `_ - Mailing list: `emscripten-discuss `_ @@ -22,8 +22,9 @@ Feel free to ask questions, share your ideas, or just join the conversation! Emscripten also has a presence on social media: -- `#emscripten `_ (Emscripten Hashtag on Twitter) -- `@kripken `_ (an Emscripten developer's account on Twitter, mentions Emscripten updates) +- `@emscripten on Bluesky `_ +- `@emscripten on Mastodon `_ +- `@emscripten on Twitter `_ Report a bug diff --git a/site/source/docs/introducing_emscripten/emscripten_license.rst b/site/source/docs/introducing_emscripten/emscripten_license.rst index fe78ed037f5c4..9af8232340e0d 100644 --- a/site/source/docs/introducing_emscripten/emscripten_license.rst +++ b/site/source/docs/introducing_emscripten/emscripten_license.rst @@ -11,7 +11,7 @@ There is little, if any, practical difference between the licenses. They are bot - *MIT license* is well known and understood. - *University of Illinois/NCSA Open Source License* allows Emscripten's code to be integrated upstream into LLVM, should the opportunity arise. -The license for Emscripten |release| (|today|) is reproduced below. The `current full licence `_ can be found on GitHub (and is also present in the root of the SDK). +The license for Emscripten |release| is reproduced below. The `current full license `_ can be found on GitHub (and is also present in the root of the SDK). .. include:: ../../../../LICENSE :literal: diff --git a/site/source/docs/optimizing/Optimizing-Code.rst b/site/source/docs/optimizing/Optimizing-Code.rst index 987b876390a49..cc6a8f63bd916 100644 --- a/site/source/docs/optimizing/Optimizing-Code.rst +++ b/site/source/docs/optimizing/Optimizing-Code.rst @@ -124,7 +124,7 @@ Link Time Optimization (LTO) lets the compiler do more optimizations, as it can inline across separate compilation units, and even with system libraries. LTO is enabled by compiling objects files with ``-flto``. The effect of this flag is to emit LTO object files (technically this means emitting bitcode). The -linker can handle a mix Wasm object files and LTO object files. Passing +linker can handle a mix of Wasm object files and LTO object files. Passing ``-flto`` at link time will also trigger LTO system libraries to be used. Thus, to allow maximal LTO opportunities with the LLVM Wasm backend, build all diff --git a/site/source/docs/porting/Debugging.rst b/site/source/docs/porting/Debugging.rst index 3a7c3b67bfbe2..2d47526b61b21 100644 --- a/site/source/docs/porting/Debugging.rst +++ b/site/source/docs/porting/Debugging.rst @@ -4,317 +4,438 @@ Debugging ========= -One of the main advantages of debugging cross-platform Emscripten code is that the same cross-platform source code can be debugged on either the native platform or using the web browser's increasingly powerful toolset — including debugger, profiler, and other tools. - -Emscripten provides a lot of functionality and tools to aid debugging: - -- :ref:`Compiler debug information flags ` that allow you to preserve debug information in compiled code and even create source maps so that you can step through the native C++ source when debugging in the browser. -- :ref:`Debug mode `, which emits debug logs and stores intermediate build files for analysis. -- :ref:`Compiler settings ` to enable runtime checking of memory accesses and common allocation errors. -- :ref:`debugging-manual-debugging` of Emscripten-generated code is also supported, which is in some ways even better than on native platforms. -- :ref:`debugging-autodebugger`, which automatically instruments LLVM bitcode to write out each store to memory. - -This article describes the main tools and settings provided by Emscripten for debugging, along with a section explaining how to debug a number of :ref:`debugging-emscripten-specific-issues`. - - -.. _debugging-debug-information-g: - -Debugging in the browser -======================== - -:ref:`Emcc ` can output debug information in two formats, either as -DWARF symbols or as source maps. Both allow you to view and debug the -*C/C++ source code* in a browser's debugger. DWARF offers the most precise and -detailed debugging experience and is supported as an experiment in Chrome 88 -with an `extension `. See -`here ` for a detailed -usage guide. Source maps are more widely supported in Firefox, Chrome, and -Safari, but unlike DWARF they cannot be used to inspect variables, for example. - -:ref:`Emcc ` strips out most of the debug information from -:ref:`optimized builds ` by default. DWARF can be produced with -the *emcc* :ref:`-g flag `, and source maps can be emitted with the -:ref:`-gsource-map ` option. Be aware that optimisation levels -:ref:`-O1 ` and above increasingly remove LLVM debug information, and -also disable runtime :ref:`ASSERTIONS ` checks. Passing a -``-g`` flag also affects the generated JavaScript code and preserves -white-space, function names, and variable names, - -.. tip:: Even for medium-sized projects, DWARF debug information can be of - substantial size and negatively impact the page performance, particularly - compiling and loading of the module. Debug information can also be emitted in - a file on the side instead with the - :ref:`-gseparate-dwarf ` option! The debug information - size also affects the linking time, because the debug information in all - object files needs to be linked as well. Passing the - :ref:`-gsplit-dwarf ` option can help here, which causes - clang to leave debug information scattered across object files. That debug - information needs to be linked into a DWARF package file (``.dwp``) using the - ``emdwp`` tool then, but that could happen in parallel to the linking of - the compiled output! When running it - after linking, it's as simple as ``emdwp -e foo.wasm -o foo.wasm.dwp``, or - ``emdwp -e foo.debug.wasm -o foo.debug.wasm.dwp`` when used together with - ``-gseparate-dwarf`` (the dwp file should have the same file name as the main - symbol file with an extra ``.dwp`` extension). - -The ``-g`` flag can also be specified with an integer levels: -:ref:`-g0 `, :ref:`-g1 `, :ref:`-g2 ` (default with -``-gsource-map``), and :ref:`-g3 ` (default with ``-g``). Each level -builds on the last to provide progressively more debug information in the -compiled output. - -.. note:: Because Binaryen optimization degrades the quality of DWARF info further, ``-O1 -g`` will skip running the Binaryen optimizer (``wasm-opt``) entirely unless required by other options. You can also throw in ``-sERROR_ON_WASM_CHANGES_AFTER_LINK`` option if you want to ensure the debug info is preserved. See `Skipping Binaryen `_ for more details. - -.. note:: Some optimizations may be disabled when used in conjunction with the debug flags both in the Binaryen optimizer (even if it runs) and JavaScript optimizer. For example, if you compile with ``-O3 -g``, the Binaryen optimizer will skip some of the optimization passes that do not produce valid DWARF information, and also some of the normal JavaScript optimization will be disabled in order to better provide the requested debugging information. +One of the main advantages of debugging cross-platform Emscripten code is that +the same cross-platform source code can be debugged on either the native +platform or using the web browser's increasingly powerful toolset — including a +debugger, profiler, and other tools. -.. _debugging-EMCC_DEBUG: +This article describes the main tools and settings provided by Emscripten for +debugging, organized by common developer use cases. + + +Overview: Emitting and Controlling Debug Information +==================================================== +Debugging-related information comes in several forms: in Wasm object and binary +files (as DWARF sections or Wasm name section), side output files (as source +maps, symbol maps, or DWARF sidecar or package files), and even in the code +itself (as assertions or instrumentation, or JS whitespace and comments). For +information on DWARF, see :ref:`below `. In addition to DWARF, +wasm files may contain a `name section +`_ +which includes names for each function; these function names are displayed by +browsers when they generate `stack traces +`_ and in +developer tools. Source maps are also supported by Emscripten and by browser +DevTools (see :ref:`below `). + +This document contains an overview of the flags used to emit and control +debugging behavior, and use-case-based examples. + +Unlike traditional Unix-style C toolchains, flags must be passed at link time to +preserve or generate debug information (these defaults aim to avoid unintended +bloat in production builds). The most common of these are the :ref:`-g flags +`; see the flag documentation or the use cases below for more detail. + +Flags that cause DWARF generation (e.g. ``-g3``, ``-gline-tables-only``) also +generate a name section in the binary and suppress minification of the JS glue +file (since most DWARF use cases are for interactive debugging or where the +binary will be stripped). Other flags (e.g. ``-g2``, ``-gsource-map``) should +affect only a specific behavior or type of debug info, and are generally +composable. + + +.. _debugging-interactive: + +Interactive, Source-Level Debugging +============================================= -Debug mode (EMCC_DEBUG) -======================= +For stepping through C/C++ source code in a browser's debugger, you can use +debug information in either DWARF or source map format. -The ``EMCC_DEBUG`` environment variable can be set to enable Emscripten's debug mode: +DWARF offers the best debugging experience and is supported in Chrome with an +`extension `_. See `here +`_ for a detailed usage +guide. Source maps are more widely supported, but they provide only location +mapping and cannot be used easily to inspect variables. + + +.. _debugging-dwarf: + +DWARF +----- + +In a traditional Unix-style C toolchain, flags such as ``-g`` are passed to the +compiler, placing DWARF sections in the object files. This DWARF info is +combined by the linker and appears in the output, independently of any +optimization settings. In contrast, although :ref:`emcc ` supports many +of the common `clang flags +`_ +to generate DWARF into the object files, final debug output is also controlled +by link-time flags, and is more affected by optimization. For example ``emcc`` +strips out most of the debug information after linking if a debugging-related +flag is not provided at link time, even if the input object files contain DWARF. + +DWARF can be produced at compile time with the *emcc* :ref:`-g flag `. +Optimization levels above :ref:`-O1 ` or :ref:`-Og ` +increasingly degrade LLVM debug information (as with other architectures), and +optimization flags at link time also disable Emscripten's runtime +:ref:`ASSERTIONS ` checks. Passing a ``-g`` flag at link +time also affects the generated JavaScript code (preserving white-space, +function names, and variable names, which makes the JavaScript debuggable). + +The ``-g`` flag can also be specified with integer levels: :ref:`-g0 `, +:ref:`-g1 `, :ref:`-g2 `, and :ref:`-g3 ` (equivalent +to ``-g``). At compile time these flags control the amount of DWARF in the +object files. At link time, each adds successively more kinds of information in +the wasm and JS files (DWARF is only retained after linking when using ``-g`` or +``-g3``). + +Example: .. code-block:: bash - # Linux or macOS - EMCC_DEBUG=1 emcc test/hello_world.cpp -o hello.html + emcc source.c -c -o source.o -g # source.o has DWARF sections emcc source.o -o + program.js -g # program.wasm has DWARF and a name section + + +.. tip:: Even for medium-sized projects, DWARF debug information can be large. + Debug information can be emitted in a separate file with the + :ref:`-gseparate-dwarf ` option. To speed up linking, + the :ref:`-gsplit-dwarf ` option can be used at compile + time. See `this article + `_ + for more details on debugging large files, and see :ref:`the next section + ` for more ways to reduce debug info size. + + +.. note:: Because Binaryen optimization degrades the quality of DWARF info + further, higher link-time optimization settings are + not recommended. The ``-O1`` setting will skip running the Binaryen optimizer + (``wasm-opt``) entirely unless required by other options. You can also add the + ``-sERROR_ON_WASM_CHANGES_AFTER_LINK`` option if you want to ensure the debug + info is preserved. See `Skipping Binaryen + `_ + for more details. + + +.. _debugging-symbolization: + +Symbolizing Production Crash Logs +============================================= + +Even when not using an interactive debugger, it's valuable to have source +information for compiled code locations, particularly for stack traces or crash +logs. This is also true for fully-optimized production builds. + +`Source maps `_ are commonly used for +languages that compile to JavaScript (mapping locations in the compiled JS +output to locations in the original source code), but WebAssembly is also +supported. Emscripten can emit source maps with the :ref:`-gsource-map +` link-time flag. Source maps are preserved even with full +post-link optimizations, so they work well for this use case. Source maps are +generated by Emscripten from DWARF information. Therefore the linked object +files must have DWARF. The final linked output will not have DWARF unless `-g` +is also passed at link time. + +DWARF can also be used for this purpose. Typically a binary containing DWARF +would be generated at build time, and then stripped. The stripped copy would be +served to users, and the original would be saved for symbolication purposes. For +this use case, full information about types and variables from the sources +isn't needed; the `-gline-tables-only +`_ +compile-time flag causes clang to generate only the line table information, +saving DWARF size and compile/linking time. + +Source maps are easier to parse and more widely supported by ecosystem tooling. +And as noted above, preserving DWARF inhibits some Binaryen optimizations. +However DWARF has the advantage that it includes information about inlining, +which can result in more accurate stack traces. + +Examples: - # Windows - set EMCC_DEBUG=1 - emcc test/hello_world.cpp -o hello.html - set EMCC_DEBUG=0 +.. code-block:: bash -With ``EMCC_DEBUG=1`` set, :ref:`emcc ` emits debug output and generates intermediate files for the compiler's various stages. ``EMCC_DEBUG=2`` additionally generates intermediate files for each JavaScript optimizer pass. + emcc source.c -c -o source.o -g # source.o has DWARF sections (-gsource-map also works here) + emcc source.o -o program.js -gsource-map # program.wasm.map contains a source map -The debug logs and intermediate files are output to -**TEMP_DIR/emscripten_temp**, where ``TEMP_DIR`` is the OS default temporary -directory (e.g. **/tmp** on UNIX). + emcc source.o -o program2.js -g # program2.wasm has DWARF + llvm-strip program2.wasm -o program2_stripped.wasm # program2_stripped.wasm has no debug info -The debug logs can be analysed to profile and review the changes that were made in each step. +Emscripten includes a tool called ``emsymbolizer`` that can map wasm code +addresses to sources using several different kinds of debug info, including +DWARF (in wasm object or linked files) and source maps for line/column info, and +symbol maps (see :ref:`emcc-emit-symbol-map`), name sections and object file +symbol tables for function names. -.. note:: The more limited amount of debug information can also be enabled by specifying the :ref:`verbose output ` compiler flag (``emcc -v``). +Fast Edit+Compile with minimal debug information +================================================ -.. _debugging-compilation-settings: +When you want the fastest builds, you generally want to avoid generating large +debug information during compile, because it takes time to link into the final +binary. It is still worthwhile to use the ``-g2`` flag (at link time only) +because browsers understand the name section even when devtools are not in use, +resulting in more useful stack traces at minimal cost. -Compiler settings -================== +Example: -Emscripten has a number of compiler settings that can be useful for debugging. These are set using the :ref:`emcc -s` option, and will override any optimization flags. For example: +.. code-block:: bash + + emcc source.c -c -o source.o # source.o has no debug info + emcc source.o -o program.js -g2 # program.wasm has a name section, program.js is unminified + +Sometimes the use of the ``-O1`` or ``-Og`` flag at compile time can also result +in faster builds, because optimizations early in the pipeline can reduce the +amount of IR that is processed by later phases such as instruction selection and +linking. It also of course reduces test runtime. + +.. _debugging-memory-safety: + +Detecting Memory Errors and Undefined Behavior +============================================== + +The best tools for detecting memory safety and undefined behavior issues are +Clang's sanitizers, such as the Undefined Behavior Sanitizer (UBSan) and the +Address Sanitizer (ASan). For more information, see :ref:`Sanitizers`. + + +Emscripten has several other compiler settings that can be useful for catching +errors at runtime. These are set using the :ref:`emcc -s` +option. For example: .. code-block:: bash - emcc -O1 -sASSERTIONS test/hello_world + emcc -O1 -sASSERTIONS test/hello_world.c Some important settings are: - .. _debugging-ASSERTIONS: - ``ASSERTIONS=1`` is used to enable runtime checks for common memory allocation errors (e.g. writing more memory than was allocated). It also defines how Emscripten should handle errors in program flow. The value can be set to ``ASSERTIONS=2`` in order to run additional tests. - - ``ASSERTIONS=1`` is enabled by default. Assertions are turned off for optimized code (:ref:`-O1 ` and above). + ``ASSERTIONS=1`` is used to enable runtime checks for many types of common + errors. It also defines how Emscripten should handle errors in program flow. + The value can be set to ``ASSERTIONS=2`` in order to run additional tests. + ``ASSERTIONS=1`` is enabled by default at ``-O0`` and disabled at higher + optimization levels, but can be overridden. - .. _debugging-SAFE-HEAP: - ``SAFE_HEAP=1`` adds additional memory access checks, and will give clear errors for problems like dereferencing 0 and memory alignment issues. - - You can also set ``SAFE_HEAP_LOG`` to log ``SAFE_HEAP`` operations. + ``SAFE_HEAP=1`` adds additional memory access checks with a Binaryen pass, + and will give clear errors for problems like dereferencing 0 and memory + alignment issues. You can also set ``SAFE_HEAP_LOG`` to log ``SAFE_HEAP`` + operations. :ref:`ASan` provides most of the functionality + of this pass (plus some extras) and is generally preferred to try first + unless :ref:`alignment issues` are + important for your platform. - .. _debugging-STACK_OVERFLOW_CHECK: - Passing the ``STACK_OVERFLOW_CHECK=1`` linker flag adds a runtime magic - token value at the end of the stack, which is checked in certain locations - to verify that the user code does not accidentally write past the end of the - stack. While overrunning the Emscripten stack is not a security issue for - JavaScript (which is unaffected), writing past the stack causes memory - corruption in global data and dynamically allocated memory sections in the - Emscripten HEAP, which makes the application fail in unexpected ways. The - value ``STACK_OVERFLOW_CHECK=2`` enables slightly more detailed stack guard + ``STACK_OVERFLOW_CHECK=1`` adds a runtime magic token value at the end of + the stack, which is checked in certain locations to verify that the user + code does not accidentally write past the end of the stack. While + overrunning the Emscripten stack is not a security issue for JavaScript + (which is unaffected), writing past the stack causes memory corruption in + global data and dynamically allocated memory sections in the Emscripten + HEAP, which makes the application fail in unexpected ways. The value + ``STACK_OVERFLOW_CHECK=2`` enables slightly more detailed stack guard checks, which can give a more precise callstack at the expense of some performance. Default value is 1 if ``ASSERTIONS=1`` is set, and disabled otherwise. -A number of other useful debug settings are defined in `src/settings.js `_. For more information, search that file for the keywords "check" and "debug". - -.. _debugging-sanitizers: - -Sanitizers -========== - -Emscripten also supports some of Clang's sanitizers, such as :ref:`sanitizer_ubsan` and :ref:`sanitizer_asan`. - -.. _debugging-emcc-v: - -emcc verbose output -=================== - -Compiling with the :ref:`emcc -v ` will cause Emscripten to output -the sub-command that it runs as well as passes ``-v`` to Clang. -.. _debugging-manual-debugging: -Manual print debugging -====================== +A number of other useful debug settings are defined in `src/settings.js +`_. For +more information, search that file for the keywords "check" and "debug". -You can also manually instrument the source code with ``printf()`` statements, then compile and run the code to investigate issues. Note that ``printf()`` is line-buffered, make sure to add ``\n`` to see output in the console. -If you have a good idea of the problem line you can add ``print(new Error().stack)`` to the JavaScript to get a stack trace at that point. - -Debug printouts can even execute arbitrary JavaScript. For example:: +.. _debugging-profiling: - function _addAndPrint($left, $right) { - $left = $left | 0; - $right = $right | 0; - //--- - if ($left < $right) console.log('l` using ``--profiling``, (which is currently the same as +:ref:`-g2 `), and then run the code in the browser's devtools profiler. +You should then be able to see in which functions most of the time is spent. -Chrome devtools support source-level debugging on WebAssembly files with DWARF information. To use that, you need the Wasm debugging extension plugin here: -https://goo.gle/wasm-debugging-extension +Memory +------ -See `Debugging WebAssembly with modern tools -`_ for the details. +The browser's memory profiling tools generally only understand allocations at +the JavaScript level. From that perspective, the entire linear memory that the +emscripten-compiled application uses is a single big allocation (of a +``WebAssembly.Memory``). To get information about usage inside that object, you +need other tools: + +* Emscripten supports the `mallinfo() + `_, API, which gives + you information from ``dlmalloc`` about current allocations. +* Emscripten also has a ``--memoryprofiler`` option that displays memory usage + in a visual manner. Note that you need to emit HTML (e.g. with a command like + ``emcc test/hello_world.c --memoryprofiler -o page.html``) as the memory + profiler output is rendered onto the page. To view it, load ``page.html`` in + your browser (remember to use a :ref:`local webserver `). + The display auto-updates, so you can open the devtools console and run a + command like ``_malloc(1024 * 1024)``. That will allocate 1MB of memory, which + will then show up on the memory profiler display. +.. _debugging-manual-debugging: -Handling C++ Exceptions from JavaScript -======================================= -See :ref:`handling-c-exceptions-from-javascript`. +Manual print debugging +====================== +You can also manually instrument the source code with ``printf()`` statements, +then compile and run the code to investigate issues. The output from the +`stdout` and `stderr` streams is copied to the browser console by default. Note +that ``printf()`` is line-buffered, so make sure to add ``\n`` to see output in +the console. The functions in the :ref:`console.h ` header can also +be used to access the console more directly. .. _debugging-emscripten-specific-issues: -Emscripten-specific issues +Emscripten-Specific Issues ========================== Memory Alignment Issues ----------------------- -The :ref:`Emscripten memory representation ` is compatible with C and C++. However, when undefined behavior is involved you may see differences with native architectures, and also differences between Emscripten's output for asm.js and WebAssembly: - -- In asm.js, loads and stores must be aligned, and performing a normal load or store on an unaligned address can fail silently (access the wrong address). If the compiler knows a load or store is unaligned, it can emulate it in a way that works but is slow. -- In WebAssembly, unaligned loads and stores will work. Each one is annotated with its expected alignment. If the actual alignment does not match, it will still work, but may be slow on some CPU architectures. +The :ref:`Emscripten memory representation ` is +compatible with C and C++. In WebAssembly, unaligned loads and stores will work; +each may be annotated with its expected alignment. However if the actual +alignment does not match, it may be very slow on some systems. .. tip:: :ref:`SAFE_HEAP ` can be used to reveal memory alignment issues. -Generally it is best to avoid unaligned reads and writes — often they occur as the result of undefined behavior, as mentioned above. In some cases, however, they are unavoidable — for example if the code to be ported reads an ``int`` from a packed structure in some pre-existing data format. In that case, to make things work properly in asm.js, and be fast in WebAssembly, you must be sure that the compiler knows the load or store is unaligned. To do so you can: +Generally it is best to avoid unaligned reads and writes. Often they occur as +the result of undefined behavior. In some cases, however, they are unavoidable — +for example if the code to be ported reads an ``int`` from a packed structure in +some pre-existing data format. In that case, to make it as fast as possible in +WebAssembly, you can make sure that the compiler knows the load or store is +unaligned. To do so you can: - Manually read individual bytes and reconstruct the full value -- Use the :c:type:`emscripten_align* ` typedefs, which define unaligned versions of the basic types (``short``, ``int``, ``float``, ``double``). All operations on those types are not fully aligned (use the ``1`` variants in most cases, which mean no alignment whatsoever). - +- Use the :c:type:`emscripten_align* ` typedefs, which + define unaligned versions of the basic types (``short``, ``int``, ``float``, + ``double``). All operations on those types are not fully aligned (use the + ``1`` variants in most cases, which mean no alignment whatsoever). Function Pointer Issues ----------------------- -If you get an ``abort()`` from a function pointer call to ``nullFunc`` or ``b0`` or ``b1`` (possibly with an error message saying "incorrect function pointer"), the problem is that the function pointer was not found in the expected function pointer table when called. +If you get an ``abort()`` from a function pointer call to ``nullFunc`` or ``b0`` +or ``b1`` (possibly with an error message saying "incorrect function pointer"), +the problem is that the function pointer was not found in the expected function +pointer table when called. -.. note:: ``nullFunc`` is the function used to populate empty index entries in the function pointer tables (``b0`` and ``b1`` are shorter names used for ``nullFunc`` in more optimized builds). A function pointer to an invalid index will call this function, which simply calls ``abort()``. +.. note:: ``nullFunc`` is the function used to populate empty index entries in + the function pointer tables (``b0`` and ``b1`` are shorter names used for + ``nullFunc`` in more optimized builds). A function pointer to an invalid + index will call this function, which simply calls ``abort()``. There are several possible causes: -- Your code is calling a function pointer that has been cast from another type (this is undefined behavior but it does happen in real-world code). In optimized Emscripten output, each function pointer type is stored in a separate table based on its original signature, so you *must* call a function pointer with that same signature to get the right behavior (see :ref:`portability-function-pointer-issues` in the code portability section for more information). -- Your code is calling a method on a ``NULL`` pointer or dereferencing 0. This sort of bug can be caused by any sort of coding error, but manifests as a function pointer error because the function can't be found in the expected table at runtime. - -In order to debug these sorts of issues: - -- Compile with ``-Werror``. This turns warnings into errors, which can be useful as some cases of undefined behavior would otherwise show warnings. -- Use ``-sASSERTIONS=2`` to get some useful information about the function pointer being called, and its type. -- Look at the browser stack trace to see where the error occurs and which function should have been called. -- Enable clang warnings on dangerous function pointer casts using ``-Wcast-function-type``. +- Your code is calling a function pointer that has been cast from another type + (this is undefined behavior but it does happen in real-world code). In + optimized Emscripten output, each function pointer type is stored in a + separate table based on its original signature, so you *must* call a function + pointer with that same signature to get the right behavior (see + :ref:`portability-function-pointer-issues` in the code portability section for + more information). +- Your code is calling a method on a ``NULL`` pointer or dereferencing 0. This + sort of bug can be caused by any sort of coding error, but manifests as a + function pointer error because the function can't be found in the expected + table at runtime. + + +To debug these sorts of issues: + +- Compile with ``-Werror`` (or otherwise fix warnings, many of which highlight + undefined behavior). +- Use ``-sASSERTIONS=2`` to get some useful information about the function + pointer being called, and its type. +- Look at the browser stack trace to see where the error occurs and which + function should have been called. +- Enable clang warnings on dangerous function pointer casts using + ``-Wcast-function-type``. - Build with :ref:`SAFE_HEAP=1 `. - :ref:`Sanitizers` can help here, in particular UBSan. -Another function pointer issue is when the wrong function is called. :ref:`SAFE_HEAP=1 ` can help with this as it detects some possible errors with function table accesses. - Infinite loops -------------- -Infinite loops cause your page to hang. After a period the browser will notify the user that the page is stuck and offer to halt or close it. +Infinite loops cause your page to hang. After a period the browser will notify +the user that the page is stuck and offer to halt or close it. If your code hits +an infinite loop, one easy way to find the problem code is to use a *JavaScript +profiler*. In the Firefox profiler, if the code enters an infinite loop you will +see a block of code doing the same thing repeatedly near the end of the profile. -If your code hits an infinite loop, one easy way to find the problem code is to use a *JavaScript profiler*. In the Firefox profiler, if the code enters an infinite loop you will see a block of code doing the same thing repeatedly near the end of the profile. +.. note:: The :ref:`emscripten-runtime-environment-main-loop` may need to be + re-coded if your application uses an infinite main loop. -.. note:: The :ref:`emscripten-runtime-environment-main-loop` may need to be re-coded if your application uses an infinite main loop. +.. _other-debugging-tools: -.. _debugging-profiling: +Debugging Emscripten +==================== -Profiling -========= - -Speed ------ - -To profile your code for speed, build with :ref:`profiling info `, -then run the code in the browser's devtools profiler. You should then be able to -see in which functions is most of the time spent. - -.. _debugging-profiling-memory: - -Memory ------- - -The browser's memory profiling tools generally only understand -allocations at the JavaScript level. From that perspective, the entire linear -memory that the emscripten-compiled application uses is a single big allocation -(of a ``WebAssembly.Memory``). The devtools will not show information about -usage inside that object, so you need other tools for that, which we will now -describe. - -Emscripten supports -`mallinfo() `_, which lets -you get information from ``dlmalloc`` about current allocations. For example -usage, see -`the test `_. - -Emscripten also has a ``--memoryprofiler`` option that displays memory usage -in a visual manner, letting you see how fragmented it is and so forth. To use -it, you can do something like +.. _debugging-EMCC_DEBUG: -.. code-block:: bash +Debugging the compiler driver +----------------------------- - emcc test/hello_world.c --memoryprofiler -o page.html +Compiling with the :ref:`emcc -v ` will cause emcc to output the +sub-commands that it runs as well as passes ``-v`` to Clang. The ``EMCC_DEBUG`` +environment variable can be set to emit even more debug output and generate +intermediate files for the compiler's various stages. -Note that you need to emit HTML as in that example, as the memory profiler -output is rendered onto the page. To view it, load ``page.html`` in your -browser (remember to use a :ref:`local webserver `). The display -auto-updates, so you can open the devtools console and run a command like -``_malloc(1024 * 1024)``. That will allocate 1MB of memory, which will then show -up on the memory profiler display. .. _debugging-autodebugger: AutoDebugger -============ +------------ -The *AutoDebugger* is the 'nuclear option' for debugging Emscripten code. +The *AutoDebugger* is the 'nuclear option' for debugging Emscripten code. It +will rewrite the output so it prints out each store to memory. This is useful +for comparing the output for different compiler settings in order to detect +regressions. To run the *AutoDebugger*, compile with the environment variable +``EMCC_AUTODEBUG=1`` set. .. warning:: This option is primarily intended for Emscripten core developers. -The *AutoDebugger* will rewrite the output so it prints out each store to memory. This is useful because you can compare the output for different compiler settings in order to detect regressions. +The *AutoDebugger* will rewrite the output so it prints out each store to +memory. This is useful because you can compare the output for different compiler +settings in order to detect regressions. -The *AutoDebugger* can potentially find **any** problem in the generated code, so it is strictly more powerful than the ``CHECK_*`` settings and ``SAFE_HEAP``. One use of the *AutoDebugger* is to quickly emit lots of logging output, which can then be reviewed for odd behavior. The *AutoDebugger* is also particularly useful for :ref:`debugging regressions `. +The *AutoDebugger* can potentially find **any** problem in the generated code, +so it is strictly more powerful than the ``CHECK_*`` settings and ``SAFE_HEAP``. +One use of the *AutoDebugger* is to quickly emit lots of logging output, which +can then be reviewed for odd behavior. The *AutoDebugger* is also particularly +useful for :ref:`debugging regressions `. The *AutoDebugger* has some limitations: -- It generates a lot of output. Using *diff* can be very helpful for identifying changes. -- It prints out simple numerical values rather than pointer addresses (because pointer addresses change between runs, and hence can't be compared). This is a limitation because sometimes inspection of addresses can show errors where the pointer address is 0 or impossibly large. It is possible to modify the tool to print out addresses as integers in ``tools/autodebugger.py``. +- It generates a lot of output. Using *diff* can be very helpful for + identifying changes. +- It prints out simple numerical values rather than pointer addresses (because + pointer addresses change between runs, and hence can't be compared). This is + a limitation because sometimes inspection of addresses can show errors where + the pointer address is 0 or impossibly large. It is possible to modify the + tool to print out addresses as integers in ``tools/autodebugger.py``. -To run the *AutoDebugger*, compile with the environment variable ``EMCC_AUTODEBUG=1`` set. For example: +To run the *AutoDebugger*, compile with the environment variable +``EMCC_AUTODEBUG=1`` set. For example: .. code-block:: bash # Linux or macOS EMCC_AUTODEBUG=1 emcc test/hello_world.cpp -o hello.html - # Windows set EMCC_AUTODEBUG=1 emcc test/hello_world.cpp -o hello.html @@ -329,7 +450,9 @@ AutoDebugger Regression Workflow Use the following workflow to find regressions with the *AutoDebugger*: - Compile the working code with ``EMCC_AUTODEBUG=1`` set in the environment. -- Compile the code using ``EMCC_AUTODEBUG=1`` in the environment again, but this time with the settings that cause the regression. Following this step we have one build before the regression and one after. +- Compile the code using ``EMCC_AUTODEBUG=1`` in the environment again, but this + time with the settings that cause the regression. Following this step we have + one build before the regression and one after. - Run both versions of the compiled code and save their output. - Compare the output using a *diff* tool. @@ -340,17 +463,19 @@ Any difference between the outputs is likely to be caused by the bug. and other issues don't cause false positives. + Useful Links ============ -- `Blogpost about reading compiler output `_. -- `GDC 2014: Getting started with asm.js and Emscripten `_ (Debugging slides). - `Links to Wasm debugging-related documents `_ Need help? ========== -The :ref:`Emscripten Test Suite ` contains good examples of almost all functionality offered by Emscripten. If you have a problem, it is a good idea to search the suite to determine whether test code with similar behavior is able to run. +The :ref:`Emscripten Test Suite ` contains good examples +of almost all functionality offered by Emscripten. If you have a problem, it is +a good idea to search the suite to determine whether test code with similar +behavior is able to run. If you've tried the ideas here and you need more help, please :ref:`contact`. diff --git a/site/source/docs/porting/asyncify.rst b/site/source/docs/porting/asyncify.rst index fe04be6752ef5..2209d8e990cac 100644 --- a/site/source/docs/porting/asyncify.rst +++ b/site/source/docs/porting/asyncify.rst @@ -1,23 +1,30 @@ .. _asyncify section: -======== -Asyncify -======== +================= +Asynchronous Code +================= -Asyncify lets **synchronous** C or C++ code interact with **asynchronous** -JavaScript. This allows things like: +Emscripten supports two ways (Asyncify and JSPI) that let **synchronous** C or +C++ code interact with **asynchronous** JavaScript. This allows things like: * A synchronous call in C that yields to the event loop, which allows browser events to be handled. * A synchronous call in C that waits for an asynchronous operation in JS to complete. -Asyncify automatically transforms your compiled code into a form that can be -paused and resumed, and handles pausing and resuming for you, so that it is -asynchronous (hence the name "Asyncify") even though you wrote it in a normal -synchronous way. +In general the two options are very similar, but rely on different underlying +mechanisms to work. -See the + * `Asyncify` - Asyncify automatically transforms your compiled code into a + form that can be paused and resumed, and handles pausing and resuming for + you, so that it is asynchronous (hence the name "Asyncify") even though you + wrote it in a normal synchronous way. This works in most environments, but + can cause the Wasm output to be much larger. + * `JSPI` (experimental) - Uses the VM's support for JavaScript Promise + Integration (JSPI) for interacting with async JavaScript. The code size will + remain the same, but support for this feature is still experimental. + +For more on Asyncify see the `Asyncify introduction blogpost `_ for general background and details of how it works internally (you can also view `this talk about Asyncify `_). @@ -62,11 +69,11 @@ Let's begin with the example from that blogpost: } } -You can compile that with +You can compile that using either `-sASYNCIFY` or `-sJSPI` :: - emcc -O3 example.cpp -sASYNCIFY + emcc -O3 example.cpp -s .. note:: It's very important to optimize (``-O3`` here) when using Asyncify, as unoptimized builds are very large. @@ -75,7 +82,13 @@ And you can run it with :: - nodejs a.out.js + node a.out.js + +Or with JSPI + +:: + + node --experimental-wasm-stack-switching a.out.js You should then see something like this: @@ -90,7 +103,7 @@ You should then see something like this: The code is written with a straightforward loop, which does not exit while it is running, which normally would not allow async events to be handled by the -browser. With Asyncify, those sleeps actually yield to the browser's main event +browser. With Asyncify/JSPI, those sleeps actually yield to the browser's main event loop, and the timer can happen! Making async Web APIs behave as if they were synchronous @@ -132,7 +145,7 @@ To run this example, first compile it with :: - emcc example.c -O3 -o a.html -sASYNCIFY + emcc example.c -O3 -o a.html -s To run this, you must run a :ref:`local webserver ` and then browse to ``http://localhost:8000/a.html``. @@ -148,8 +161,49 @@ You will see something like this: That shows that the C code only continued to execute after the async JS completed. -Ways to use async APIs in older engines -####################################### +.. _marking_async_functions: + +Marking JS library functions as async +##################################### + +If you mark a JS library function as async using the ``__async`` decorator then +the compiler will take a care of all the details of using the ``Asyncify`` API +for you. The function will also automatically be included in +:ref:`ASYNCIFY_IMPORTS`. All you need to do is write normal async JS function +(either using the explict ``async`` JS keyword or returning a ``Promise`` +object). For example: + +.. code-block:: js + + addToLibrary({ + fetch_v1__async: 'auto', + fetch_v1: async (url) => { + const response = await fetch(UTF8ToString(url); + const json_data = await response.json(); + return stringToNewUTF8(json_data); + }, + + fetch_v2__async: 'auto', + fetch_v2: (url) => { + return fetch(UTF8ToString(url)) + .then((rsp) => response.json()) + .then((json_data) => stringToNewUTF8(json_data)); + }, + }); + +This JS library file contains two JS functions (``fetch_v1`` and ``fetch_v2``) +which do exactly the same thing, one using the JS ``async`` keyword and one +using promise chaining. They are both marked as ``__async: 'auto'`` which means +they will automatically suspend Wasm executation when called and resume when the +resulting promise resolves. + +You can use ``__async: 1`` if you just want to include the function in +:ref:`ASYNCIFY_IMPORTS` or ``__async: 'auto'`` if you also want the function to +wrapper in ``Asyncify.handleAsync``. + + +Ways to use Asyncify APIs in older engines +########################################## If your target JS engine doesn't support the modern ``async/await`` JS syntax, you can desugar the above implementation of ``do_fetch`` to use Promises @@ -170,8 +224,9 @@ directly with ``EM_JS`` and ``Asyncify.handleAsync`` instead: When using this form, the compiler doesn't statically know that ``do_fetch`` is asynchronous anymore. Instead, you must tell the compiler that ``do_fetch()`` -can do an asynchronous operation using ``ASYNCIFY_IMPORTS``, otherwise it won't -instrument the code to allow pausing and resuming (see more details later down): +can do an asynchronous operation using :ref:`ASYNCIFY_IMPORTS`, otherwise it +won't instrument the code to allow pausing and resuming (see more details later +down): :: @@ -205,10 +260,10 @@ More on ``ASYNCIFY_IMPORTS`` As in the above example, you can add JS functions that do an async operation but look synchronous from the perspective of C. If you don't use ``EM_ASYNC_JS``, -it's vital to add such methods to ``ASYNCIFY_IMPORTS``. That list of imports is -the list of imports to the Wasm module that the Asyncify instrumentation must be -aware of. Giving it that list tells it that all other JS calls will **not** do -an async operation, which lets it not add overhead where it isn't needed. +it's vital to add such methods to :ref:`ASYNCIFY_IMPORTS`. That list of imports +is the list of imports to the Wasm module that the Asyncify instrumentation must +be aware of. Giving it that list tells it that all other JS calls will **not** +do an async operation, which lets it not add overhead where it isn't needed. .. note:: If the import is not inside ``env`` the full path must be specified, for example, ``ASYNCIFY_IMPORTS=wasi_snapshot_preview1.fd_write`` @@ -217,7 +272,7 @@ Asyncify with Dynamic Linking If you want to use Asyncify in dynamic libraries, those methods which are imported from other linked modules (and that will be on the stack in an async operation) -should be listed in ``ASYNCIFY_IMPORTS``. +should be listed in :ref:`ASYNCIFY_IMPORTS`. .. code-block:: cpp @@ -248,8 +303,8 @@ linking manner: } In the main module, the compiler doesn’t statically know that ``sleep_for_seconds`` is -asynchronous. Therefore, you must add ``sleep_for_seconds`` to the ``ASYNCIFY_IMPORTS`` -list. +asynchronous. Therefore, you must add ``sleep_for_seconds`` to the +:ref:`ASYNCIFY_IMPORTS` list. :: @@ -267,13 +322,17 @@ and want to ``await`` a dynamically retrieved ``Promise``, you can call an val my_object = /* ... */; val result = my_object.call("someAsyncMethod").await(); -In this case you don't need to worry about ``ASYNCIFY_IMPORTS``, since it's an -internal implementation detail of ``val::await`` and Emscripten takes care of it -automatically. +In this case you don't need to worry about :ref:`ASYNCIFY_IMPORTS` or +:ref:`JSPI_IMPORTS`, since it's an internal implementation detail of +``val::await`` and Emscripten takes care of it automatically. -Note that when Asyncify is used with Embind and the code is invoked from -JavaScript, then it will be implicitly treated as an ``async`` function, -returning a ``Promise`` to the return value, as demonstrated below. +Note that when using Embind exports, Asyncify and JSPI behave differently. When +Asyncify is used with Embind and the code is invoked from JavaScript, then the +function will return a ``Promise`` if the export calls any suspending functions, +otherwise the result will be returned synchronously. However, with JSPI, the +parameter ``emscripten::async()`` must be used to mark the function as +asynchronous and the export will always return a ``Promise`` regardless if the +export suspended. .. code-block:: cpp @@ -288,15 +347,18 @@ returning a ``Promise`` to the return value, as demonstrated below. } EMSCRIPTEN_BINDINGS(example) { + // Asyncify emscripten::function("delayAndReturn", &delayAndReturn); + // JSPI + emscripten::function("delayAndReturn", &delayAndReturn, emscripten::async()); } Build with :: - emcc -O3 example.cpp -lembind -sASYNCIFY + emcc -O3 example.cpp -lembind -s -Then invoke from JavaScript +Then invoke from JavaScript (using Asyncify) .. code-block:: javascript @@ -316,6 +378,19 @@ if Asyncify calls are encountered (such as ``emscripten_sleep()``, If the code path is undetermined, the caller may either check if the returned value is an ``instanceof Promise`` or simply ``await`` on the returned value. +When using JSPI the return values will always be a ``Promise`` as seen below + +.. code-block:: javascript + + let syncResult = Module.delayAndReturn(false); + console.log(syncResult); // Promise { } + console.log(await syncResult); // 42 + + let asyncResult = Module.delayAndReturn(true); + console.log(asyncResult); // Promise { } + console.log(await asyncResult); // 42 + + Usage with ``ccall`` #################### @@ -332,8 +407,25 @@ In this example, a function "func" is called which returns a Number. console.log("js_func: " + result); }); -Optimizing -########## + +Differences Between Asyncify and JSPI +##################################### + +Besides using different underlying mechanisms, Asyncify and JSPI also handle +async imports and exports differently. Asyncify will automatically determine +what exports will become async based on what could potentially call an +an async import (:ref:`ASYNCIFY_IMPORTS`). However, with JSPI, the async imports +and exports must be explicitly set using :ref:`JSPI_IMPORTS` and +:ref:`JSPI_EXPORTS` settings. + +.. note:: ``_IMPORTS`` and ``JSPI_EXPORTS`` aren't needed when + using various helpers mentioned above such as: ``EM_ASYNC_JS``, + Embind's Async support, ``ccall``, etc... + +Optimizing Asyncify +################### + +.. note:: This section does not apply to JSPI. As mentioned earlier, unoptimized builds with Asyncify can be large and slow. Build with optimizations (say, ``-O3``) to get good results. @@ -343,35 +435,39 @@ code to allow unwinding and rewinding. That overhead is usually not extreme, something like 50% or so. Asyncify achieves that by doing a whole-program analysis to find functions need to be instrumented and which do not - basically, which can call something that reaches one of -``ASYNCIFY_IMPORTS``. That analysis avoids a lot of unnecessary overhead, +:ref:`ASYNCIFY_IMPORTS`. That analysis avoids a lot of unnecessary overhead, however, it is limited by **indirect calls**, since it can't tell where they go - it could be anything in the function table (with the same type). If you know that indirect calls are never on the stack when unwinding, then you can tell Asyncify to ignore indirect calls using -``ASYNCIFY_IGNORE_INDIRECT``. +:ref:`ASYNCIFY_IGNORE_INDIRECT`. If you know that some indirect calls matter and others do not, then you can provide a manual list of functions to Asyncify: -* ``ASYNCIFY_REMOVE`` is a list of functions that do not unwind the stack. - Asyncify will do its normal whole-program analysis, then remove these - functions from the list of instrumented functions. -* ``ASYNCIFY_ADD`` is a list of functions that do unwind the stack, and - are added after doing the normal whole-program analysis. This is mostly useful - if you use ``ASYNCIFY_IGNORE_INDIRECT`` but want to also mark some additional - functions that need to unwind. -* ``ASYNCIFY_ONLY`` is a list of the **only** functions that can unwind +* :ref:`ASYNCIFY_REMOVE` is a list of functions that do not unwind the stack. + As Asyncify processes the call tree, functions in this list will be removed, + and neither they nor their callers will be instrumented (unless their callers + need to be instrumented for other reasons.) +* :ref:`ASYNCIFY_ADD` is a list of functions that do unwind the stack, and will be + processed like the imports. This is mostly useful + if you use :ref:`ASYNCIFY_IGNORE_INDIRECT` but want to also mark some additional + functions that need to unwind. If the :ref:`ASYNCIFY_PROPAGATE_ADD` setting is + disabled however, then this list will only be added after the whole-program + analysis. If :ref:`ASYNCIFY_PROPAGATE_ADD` is disabled then you must also add + their callers, their callers' callers, and so on. +* :ref:`ASYNCIFY_ONLY` is a list of the **only** functions that can unwind the stack. Asyncify will instrument exactly those and no others. -You can enable the ``ASYNCIFY_ADVISE`` setting, which will tell the compiler to +You can enable the :ref:`ASYNCIFY_ADVISE` setting, which will tell the compiler to output which functions it is currently instrumenting and why. You can then -determine whether you should add any functions to ``ASYNCIFY_REMOVE`` or -whether it would be safe to enable ``ASYNCIFY_IGNORE_INDIRECT``. Note that this +determine whether you should add any functions to :ref:`ASYNCIFY_REMOVE` or +whether it would be safe to enable :ref:`ASYNCIFY_IGNORE_INDIRECT`. Note that this phase of the compiler happens after many optimization phases, and several functions maybe be inlined already. To be safe, run it with `-O0`. -For more details see ``settings.js``. Note that the manual settings +For more details see :ref:`settings-reference`. Note that the manual settings mentioned here are error-prone - if you don't get things exactly right, your application can break. If you don't absolutely need maximal performance, it's usually ok to use the defaults. @@ -379,12 +475,12 @@ it's usually ok to use the defaults. Potential problems ################## -Stack overflows -*************** +Stack overflows (Asyncify) +************************** If you see an exception thrown from an ``asyncify_*`` API, then it may be a stack overflow. You can increase the stack size with the -``ASYNCIFY_STACK_SIZE`` option. +:ref:`ASYNCIFY_STACK_SIZE` option. Reentrancy ********** @@ -405,14 +501,14 @@ if a function uses a global and assumes nothing else can modify it until it returns, but if that function sleeps and an event causes other code to change that global, then bad things can happen. -Starting to rewind with compiled code on the stack -************************************************** +Starting to rewind with compiled code on the stack (Asyncify) +************************************************************* The examples above show `wakeUp()` being called from JS (after a callback, typically), and without any compiled code on the stack. If there *were* compiled code on the stack, then that could interfere with properly rewinding and resuming execution, in confusing ways, and therefore an assertion will be -thrown in a build with ``ASSERTIONS``. +thrown in a build with :ref:`ASSERTIONS`. (Specifically, the problem there is that while rewinding will work properly, if you later unwind again, that unwinding will also unwind through that extra @@ -422,10 +518,10 @@ A simple workaround you may find useful is to do a setTimeout of 0, replacing ``wakeUp()`` with ``setTimeout(wakeUp, 0);``. That will run ``wakeUp`` in a later callback, when nothing else is on the stack. -Migrating from older APIs -######################### +Migrating from older Asyncify APIs +################################## -If you have code uses the old Emterpreter-Async API, or the old Asyncify, then +If you have code that uses the old Emterpreter-Async API, or the old Asyncify, then almost everything should just work when you replace ``-sEMTERPRETIFY`` usage with ``-sASYNCIFY``. In particular all the things like ``emscripten_wget`` should just work as they did before. diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst index a3f8287047e88..2d3df59fb45c6 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst @@ -146,11 +146,6 @@ parameters to pass to the function: including function names. Exporting functions allows you to continue to access them using the original name through the global ``Module`` object. - - If you want to export a JS library function (something from a - ``src/library*.js`` file, for example), then in addition to - ``EXPORTED_FUNCTIONS``, you need to add it to ``DEFAULT_LIBRARY_FUNCS_TO_INCLUDE``, - as the latter will force the method to actually be included in - the build. - The compiler will remove code it does not see is used, to improve code size. If you use ``ccall`` in a place it sees, like code in a ``--pre-js`` @@ -362,20 +357,18 @@ Implement a C API in JavaScript It is possible to implement a C API in JavaScript! This is the approach used in many of Emscripten's libraries, like SDL1 and OpenGL. -You can use it to write your own APIs to call from C/C++. To do this -you define the interface, decorating with ``extern`` to mark the methods -in the API as external symbols. You then implement the symbols in -JavaScript by simply adding their definition to `library.js`_ (by -default). When compiling the C code, the compiler looks in the JavaScript -libraries for relevant external symbols. - -By default, the implementation is added to **library.js** (and this is -where you'll find parts of Emscripten's *libc*). You can put -the JavaScript implementation in your own library file and add it using -the :ref:`emcc option ` ``--js-library``. See -`test_js_libraries`_ in **test/test_other.py** for a complete working -example, including the syntax you should use inside the JavaScript library -file. +You can use it to write your own APIs to call from C/C++. To do this you define +the interface, decorating with ``extern`` to mark the methods in the API as +external symbols. You can then implement the symbols in JavaScript by simply +adding their definition to one of the `core JS library`_ files. Undefined +native symbols will be resolved by looking for them in JavaScript library files. + +The `core JS library`_ files are where you will find Emscripten internals. For +example, parts of Emscripten's *libc* are implemented there. You can also put +the JavaScript implementation in your own library file and add it using the +:ref:`emcc option ` ``--js-library``. See `test_jslib`_ in +**test/test_other.py** for a complete working example, including the syntax you +should use inside the JavaScript library file. As a simple example, consider the case where you have some C code like this: @@ -438,7 +431,7 @@ that you can't use a closure directly, for example, as ``toString`` isn't compatible with that - just like when using a string to create a Web Worker, where you also can't pass a closure. (Note that this limitation is just for the values for the keys of the object -passes to ``addToLibrary`` in the JS library, that is, the toplevel +passed to ``addToLibrary`` in the JS library, that is, the toplevel key-value pairs are special. Interior code inside a function can have arbitrary JS, of course). @@ -613,30 +606,63 @@ Calling JavaScript functions as function pointers from C ======================================================== You can use ``addFunction`` to return an integer value that represents a -function pointer. Passing that integer to C code then lets it call that value as -a function pointer, and the JavaScript function you sent to ``addFunction`` will -be called. +function pointer. Passing that integer to C code then lets it call that value +as a function pointer, and the JavaScript function you sent to ``addFunction`` +will be called. See `test_add_function in test/test_core.py`_ for an example. You should build with ``-sALLOW_TABLE_GROWTH`` to allow new functions to be added to the table. Otherwise by default the table has a fixed size. -.. note:: When using ``addFunction`` on LLVM Wasm backend, you need to provide - an additional second argument, a Wasm function signature string. Each - character within a signature string represents a type. The first character - represents the return type of a function, and remaining characters are for - parameter types. +When using ``addFunction`` with a JavaScript function, you need to provide +an additional second argument, a Wasm function signature string, explained +below. See `test/interop/test_add_function_post.js `_ for an example. + + +.. _interacting-with-code-function-signatures: + +Function Signatures +=================== + +The LLVM Wasm backend requires a Wasm function signature string when using +``addFunction`` and in JavaScript libraries. Each character within a signature +string represents a type. The first character represents the return type of a +function, and remaining characters are for parameter types. - ``'v'``: void type - ``'i'``: 32-bit integer type - - ``'j'``: 64-bit integer type (currently does not exist in JavaScript) + - ``'j'``: 64-bit integer type (see note below) - ``'f'``: 32-bit float type - ``'d'``: 64-bit float type + - ``'p'``: 32-bit or 64-bit pointer (MEMORY64) + +For example, if you add a function that takes an integer and does not return +anything, the signature is ``'vi'``. + +When ``'j'`` is used there are several ways in which the parameter value will +be passed to JavaScript. By default, the value will either be passed as a +single BigInt or a pair of JavaScript numbers (double) depending on whether +the ``WASM_BIGINT`` settings is enabled. In addition, if you only require 53 +bits of precision you can add the ``__i53abi`` decorator, which will ignore +the upper bits and the value will be received as a single JavaScript number +(double). It cannot be used with ``addFunction``. Here is an example of a +library function that sets the size of a file using a 64-bit value passed as +a 53 bit (double) and returns an integer error code: + +.. code-block:: c + + extern "C" int _set_file_size(int handle, uint64_t size); + +.. code-block:: javascript - For example, if you add a function that takes an integer and does not return - anything, you can do ``addFunction(your_function, 'vi');``. See - `test/interop/test_add_function_post.js `_ for an example. + _set_file_size__i53abi: true, // Handle 64-bit + _set_file_size__sig: 'iij', // Function signature + _set_file_size: function(handle, size) { ... return error; } + +Using ``-sWASM_BIGINT`` when linking is an alternative method of handling +64-bit types in libraries. ```Number()``` may be needed on the JavaScript +side to convert it to a usable value. See `settings reference `_. .. _interacting-with-code-access-memory: @@ -810,8 +836,8 @@ on Emscripten. If you would like to port existing Node-API addon to WebAssembly or compile the same binding code to both Node.js native addon and WebAssembly, you can give it a try. See `Emnapi documentation`_ for more details. -.. _library.js: https://github.com/emscripten-core/emscripten/blob/main/src/library.js -.. _test_js_libraries: https://github.com/emscripten-core/emscripten/blob/1.29.12/tests/test_core.py#L5043 +.. _core JS library: https://github.com/emscripten-core/emscripten/blob/main/src/lib/ +.. _test_jslib: https://github.com/emscripten-core/emscripten/blob/4.0.9/test/test_core.py#L6261 .. _tools/system_libs.py: https://github.com/emscripten-core/emscripten/blob/main/tools/system_libs.py .. _library_\*.js: https://github.com/emscripten-core/emscripten/tree/main/src .. _test_add_function in test/test_core.py: https://github.com/emscripten-core/emscripten/blob/1.29.12/tests/test_core.py#L6237 diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst b/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst index 744ed75cfbbe2..81857db241d7a 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.rst @@ -318,7 +318,7 @@ You can bind to C++ operators using ``[Operator=]``: .. note:: - The operator name can be anything (``add`` is just an example). - - Support is currently limited to operators that contain ``=``: ``+=``, ``*=``, ``-=`` etc., and to the array indexing operator ``[]``. + - Support is currently limited to the following binary operators: ``+``, ``-``, ``*``, ``/``, ``%``, ``^``, ``&``, ``|``, ``=``, ``<``, ``>``, ``+=``, ``-=``, ``*=``, ``/=``, ``%=``, ``^=``, ``&=``, ``|=``, ``<<``, ``>>``, ``>>=``, ``<<=``, ``==``, ``!=``, ``<=``, ``>=``, ``<=>``, ``&&``, ``||``, and to the array indexing operator ``[]``. enums @@ -416,6 +416,55 @@ When C++ code has a pointer to a ``Base`` instance and calls ``virtualFunc()``, - You *must* implement all the methods you mentioned in the IDL of the ``JSImplementation`` class (``ImplJS``) or compilation will fail with an error. - You will also need to provide an interface definition for the ``Base`` class in the IDL file. +Function overloads +================== + +C++ allows function overloads, where multiple member functions have the same name but different arguments. By default, the *WebIDL Binder* allows you to bind overloaded functions if they differ only in the number of arguments: + +.. code-block:: cpp + + // C++ + class OverloadTest { + public: + void test(int arg1, int arg2) { ... } + void test(int arg) { ... } + }; + +.. code-block:: idl + + // WebIDL + interface OverloadTest { + void OverloadTest(); + void test(long arg1, long arg2); + void test(long arg); + }; + +If your overloaded functions differ in some other way (say, in the types) then you can use the ``[BindTo]`` attribute to tell the tool what function name to bind to (that is, to call): + +.. code-block:: cpp + + // C++ + class BindToTest { + public: + void test(const char* arg) { ... } + void test(int arg) { ... } + }; + +.. code-block:: idl + + // WebIDL + interface BindToTest { + void BindToTest(); + [BindTo="test"] void testString([Const] DOMString arg); + [BindTo="test"] void testInt(long arg); + }; + +In this case the C++ function ``test(const char*)`` will be named ``testString`` in JavaScript and ``test(int)`` will be named ``testInt``. + +.. note:: + + You can also use ``[BindTo]`` to just rename a function, e.g. if you want to rename ``MyFunctionName`` to ``myFunctionName``. + Pointers and comparisons ========================= diff --git a/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst b/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst index 8a5a6fe980ec7..7233c19dd26ab 100644 --- a/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst +++ b/site/source/docs/porting/connecting_cpp_and_javascript/embind.rst @@ -203,15 +203,6 @@ to enable the closure compiler. Memory management ================= -JavaScript only gained support for `finalizers`_ in ECMAScript 2021, or ECMA-262 -Edition 12. The new API is called `FinalizationRegistry`_ and it still does not -offer any guarantees that the provided finalization callback will be called. -Embind uses this for cleanup if available, but only for smart pointers, -and only as a last resort. - -.. warning:: It is strongly recommended that JavaScript code explicitly deletes - any C++ object handles it has received. - The :js:func:`delete()` JavaScript method is provided to manually signal that a C++ object is no longer needed and can be deleted: @@ -226,7 +217,8 @@ a C++ object is no longer needed and can be deleted: y.delete(); .. note:: Both C++ objects constructed from the JavaScript side as well as - those returned from C++ methods must be explicitly deleted. + those returned from C++ methods must be explicitly deleted, unless a + ``reference`` return value policy is used (see below). .. tip:: The ``try`` … ``finally`` JavaScript construct can be used to guarantee @@ -248,6 +240,35 @@ a C++ object is no longer needed and can be deleted: } } +Automatic memory management +--------------------------- + +Embind integrates with the `Explicit Resource Management`_ proposal. + +It allows to automatically delete short-lived C++ objects at the end of the +scope when they're declared with a `using` keyword: + +.. code:: javascript + + using x = new Module.MyClass; + x.method(); + +At the moment of writing, this proposal is natively supported in +Chromium-based browsers as well as Babel and TypeScript via transpilation. + +Embind also supports `finalizers`_, which were added in ECMAScript 2021 under a +`FinalizationRegistry`_ API. Unlike the `using` keyword, finalizers are not +guaranteed to be called, and even if they are, there are no guarantees about +their timing or order of execution, which makes them unsuitable for general +RAII-style resource management. + +Embind uses it for cleanup if available, but only for smart pointers, and only +as a last resort. + +.. warning:: It is strongly recommended that JavaScript code explicitly deletes + any C++ object handles it has received. + + Cloning and Reference Counting ------------------------------ @@ -340,17 +361,85 @@ The JavaScript code does not need to worry about lifetime management. var person = Module.findPersonAtLocation([10.2, 156.5]); console.log('Found someone! Their name is ' + person.name + ' and they are ' + person.age + ' years old'); +.. note:: + It is not recommended to use fields that correspond to regular C++ binding + (such as ``class_``) since those properties will obey the normal lifetime + rules of their bound type and may require explicit cleanup in JavaScript. + See :ref:`Object Ownership ` for more details. Advanced class concepts ======================= +.. _embind-object-ownership: + +Object Ownership +---------------- + +JavaScript and C++ have very different memory models which can lead to it being +unclear which language owns and is responsible for deleting an object when it +moves between languages. To make object ownership more explicit, *embind* +supports smart pointers and return value policies. Return value +policies dictate what happens to a C++ object when it is returned to JavaScript. + +To use a return value policy, pass the desired policy into function, method, or +property bindings. For example: + +.. code:: cpp + + EMSCRIPTEN_BINDINGS(module) { + function("createData", &createData, return_value_policy::take_ownership()); + } + +Embind supports three return value policies that behave differently depending +on the return type of the function. The policies work as follows: + +* *default (no argument)* - For return by value and reference a new object will be allocated using the + object's copy constructor. JS then owns the object and is responsible for deleting it. Returning a + pointer is not allowed by default (use an explicit policy below). +* :cpp:type:`return_value_policy::take_ownership` - Ownership is transferred to JS. +* :cpp:type:`return_value_policy::reference` - Reference an existing object but do not take + ownership. Care must be taken to not delete the object while it is still in use in JS. + +More details below: + ++--------------------+-------------+---------------------------------------------------------------+ +| Return Type | Constructor | Cleanup | ++====================+=============+===============================================================+ +| **default** | ++--------------------+-------------+---------------------------------------------------------------+ +| Value (``T``) | copy | JS must delete the copied object. | ++--------------------+-------------+---------------------------------------------------------------+ +| Reference (``T&``) | copy | JS must delete the copied object. | ++--------------------+-------------+---------------------------------------------------------------+ +| Pointer (``T*``) | n/a | Pointers must explicitly use a return policy. | ++--------------------+-------------+---------------------------------------------------------------+ +| **take_ownership** | ++--------------------+-------------+---------------------------------------------------------------+ +| Value (``T``) | move | JS must delete the moved object. | ++--------------------+-------------+---------------------------------------------------------------+ +| Reference (``T&``) | move | JS must delete the moved object. | ++--------------------+-------------+---------------------------------------------------------------+ +| Pointer (``T*``) | none | JS must delete the object. | ++--------------------+-------------+---------------------------------------------------------------+ +| **reference** | ++--------------------+-------------+---------------------------------------------------------------+ +| Value (``T``) | n/a | Reference to a value is not allowed. | ++--------------------+-------------+---------------------------------------------------------------+ +| Reference (``T&``) | none | C++ must delete the object. | ++--------------------+-------------+---------------------------------------------------------------+ +| Pointer (``T*``) | none | C++ must delete the object. | ++--------------------+-------------+---------------------------------------------------------------+ + .. _embind-raw-pointers: Raw pointers ------------ Because raw pointers have unclear lifetime semantics, *embind* requires -their use to be marked with :cpp:type:`allow_raw_pointers`. +their use to be marked with either :cpp:type:`allow_raw_pointers` or with a +:cpp:type:`return_value_policy`. If the function returns a pointer it is +recommended to use a :cpp:type:`return_value_policy` instead of the general +:cpp:type:`allow_raw_pointers`. For example: @@ -358,17 +447,19 @@ For example: class C {}; C* passThrough(C* ptr) { return ptr; } + C* createC() { return new C(); } EMSCRIPTEN_BINDINGS(raw_pointers) { class_("C"); function("passThrough", &passThrough, allow_raw_pointers()); + function("createC", &createC, return_value_policy::take_ownership()); } .. note:: - Currently the markup serves only to allow raw pointer use, and - show that you've thought about the use of the raw pointers. Eventually - we hope to implement `Boost.Python-like raw pointer policies`_ for - managing object ownership. + Currently allow_raw_pointers for pointer arguments only serves to allow raw + pointer use, and show that you've thought about the use of the raw pointers. + Eventually we hope to implement `Boost.Python-like raw pointer policies`_ for + managing object ownership of arguments as well. .. _embind-external-constructors: @@ -527,6 +618,7 @@ implemented in JavaScript. .. code:: cpp struct Interface { + virtual ~Interface() {} virtual void invoke(const std::string& str) = 0; }; @@ -672,6 +764,8 @@ are available. .. note:: *Embind* must understand the fully-derived type for automatic downcasting to work. +.. note:: *Embind* does not support this unless RTTI is enabled. + Overloaded functions ==================== @@ -736,6 +830,88 @@ type. Module.OldStyle.ONE; Module.NewStyle.TWO; + +You can control how C++ enums are exposed to JavaScript by specifying +``enum_value_type`` when registering the enum. + +By default, enums use ``enum_value_type::object``. Enum values are bound +as JavaScript objects with a ``value`` property containing the underlying +C++ integer. + +.. code:: cpp + + enum class Enum { ONE, TWO }; + + EMSCRIPTEN_BINDINGS(my_enum_example) { + enum_("ObjectEnum", enum_value_type::object) + .value("ONE", Enum::ONE) + .value("TWO", Enum::TWO); + } + +.. code:: javascript + + Module.ObjectEnum.ONE.value === 0; + Module.ObjectEnum.TWO.value === 1; + +Alternatively, you can use: + +- ``enum_value_type::number``: Enum values are bound directly as JavaScript numbers matching their C++ + integer values. + +.. code:: cpp + + EMSCRIPTEN_BINDINGS(my_enum_example) { + enum_("NumberEnum", enum_value_type::number) + .value("ONE", Enum::ONE) + .value("TWO", Enum::TWO); + } + +.. code:: javascript + + Module.NumberEnum.ONE === 0; + Module.NumberEnum.TWO === 1; + +- ``enum_value_type::string``: Enum values are bound as JavaScript strings containing their name. + +.. code:: cpp + + EMSCRIPTEN_BINDINGS(my_enum_example) { + enum_("StringEnum", enum_value_type::string) + .value("ONE", Enum::ONE) + .value("TWO", Enum::TWO); + } + +.. code:: javascript + + Module.StringEnum.ONE === "ONE"; + Module.StringEnum.TWO === "TWO"; + +Regardless of the ``enum_value_type`` used, enum values can always be used as +arguments to functions expecting the enum type. + +.. code:: cpp + + void takesEnum(Enum e); + + EMSCRIPTEN_BINDINGS(my_enum_example) { + function("takesEnum", &takesEnum); + } + +.. code:: javascript + + // enum_value_type::object + Module.takesEnum(Module.ObjectEnum.ONE); + + // enum_value_type::number + Module.takesEnum(Module.NumberEnum.ONE); + // OR + Module.takesEnum(0); + + // enum_value_type::string + Module.takesEnum(Module.StringEnum.ONE); + // OR + Module.takesEnum("ONE"); + .. _embind-constants: Constants @@ -754,6 +930,71 @@ To expose a C++ :cpp:func:`constant` to JavaScript, simply write: .. _embind-memory-view: +Class Properties +================ + +.. warning:: By default ``property()`` bindings to objects use + ``return_value_policy::copy`` which can very easily lead to memory leaks + since each access to the property will create a new object that must be + deleted. Alternatively, use ``return_value_policy::reference``, so a new + object is not allocated and changes to the object will be reflected in the + original object. + +Class properties can be defined several ways as seen below. + +.. code:: cpp + + struct Point { + float x; + float y; + }; + + struct Person { + Point location; + Point getLocation() const { // Note: const is required on getters + return location; + } + void setLocation(Point p) { + location = p; + } + }; + + EMSCRIPTEN_BINDINGS(xxx) { + class_("Person") + .constructor<>() + // Bind directly to a class member with automatically generated getters/setters using a + // reference return policy so the object does not need to be deleted from JS. + .property("location", &Person::location, return_value_policy::reference()) + // Same as above, but this will return a copy and the object must be deleted or it will + // leak! + .property("locationCopy", &Person::location) + // Bind using a only getter method for read only access. + .property("readOnlyLocation", &Person::getLocation, return_value_policy::reference()) + // Bind using a getter and setter method. + .property("getterAndSetterLocation", &Person::getLocation, &Person::setLocation, + return_value_policy::reference()); + class_("Point") + .property("x", &Point::x) + .property("y", &Point::y); + } + + int main() { + EM_ASM( + let person = new Module.Person(); + person.location.x = 42; + console.log(person.location.x); // 42 + let locationCopy = person.locationCopy; + // This is a copy so the original person's location will not be updated. + locationCopy.x = 99; + console.log(locationCopy.x); // 99 + // Important: delete any copies! + locationCopy.delete(); + console.log(person.readOnlyLocation.x); // 42 + console.log(person.getterAndSetterLocation.x); // 42 + person.delete(); + ); + } + Memory views ============ @@ -933,7 +1174,7 @@ Out of the box, *embind* provides converters for many standard C++ types: \*\*Requires BigInt support to be enabled with the `-sWASM_BIGINT` flag. For convenience, *embind* provides factory functions to register -``std::vector`` (:cpp:func:`register_vector`), ``std::map`` +``std::vector>`` (:cpp:func:`register_vector`), ``std::map, class Allocator=std::allocator>>`` (:cpp:func:`register_map`), and ``std::optional`` (:cpp:func:`register_optional`) types: .. code:: cpp @@ -941,7 +1182,7 @@ For convenience, *embind* provides factory functions to register EMSCRIPTEN_BINDINGS(stl_wrappers) { register_vector("VectorInt"); register_map("MapIntInt"); - register_optional("Optional); + register_optional(); } A full example is shown below: @@ -998,9 +1239,12 @@ The following JavaScript can be used to interact with the above C++. // push value into vector retVector.push_back(12); - // retrieve value from the vector - for (var i = 0; i < retVector.size(); i++) { - console.log("Vector Value: ", retVector.get(i)); + // retrieve a value from the vector + console.log("Vector Value at index 0: ", retVector.get(0)); + + // iterate over vector + for (var value of retVector) { + console.log("Vector Value: ", value); } // expand vector size @@ -1041,9 +1285,9 @@ Generating Embind supports generating TypeScript definition files from :cpp:func:`EMSCRIPTEN_BINDINGS` blocks. To generate **.d.ts** files invoke *emcc* with the -:ref:`embind-emit-tsd ` option:: +:ref:`embind-emit-tsd ` option:: - emcc -lembind quick_example.cpp --embind-emit-tsd interface.d.ts + emcc -lembind quick_example.cpp --emit-tsd interface.d.ts Running this command will build the program with an instrumented version of embind that is then run in *node* to generate the definition files. @@ -1060,6 +1304,13 @@ produce `val` types. To give better type information, custom `val` types can be registered using :cpp:func:`EMSCRIPTEN_DECLARE_VAL_TYPE` in combination with :cpp:class:`emscripten::register_type`. An example below: +Two registration forms are supported: + +* Single parameter: ``register_type(definition)`` — the provided string is inlined + everywhere the type appears. +* Two parameters: ``register_type(name, definition)`` — creates a named TypeScript + type alias (``type name = definition;``) and uses ``name`` at call sites. + .. code:: cpp EMSCRIPTEN_DECLARE_VAL_TYPE(CallbackType); @@ -1072,8 +1323,21 @@ registered using :cpp:func:`EMSCRIPTEN_DECLARE_VAL_TYPE` in combination with EMSCRIPTEN_BINDINGS(custom_val) { function("function_with_callback_param", &function_with_callback_param); register_type("(message: string) => void"); + + // Named alias form (emits: type Callback = (message: string) => void;) + register_type("Callback", "(message: string) => void"); } + +``nonnull`` Pointers +-------------------- + +C++ functions that return pointers generate TS definitions with `` | +null`` to allow ``nullptr`` by default. If the C++ function is guaranteed to +return a valid object, then a policy parameter of ``nonnull()`` can be +added to the function binding to omit ``| null`` from TS. This avoids having to +handle the ``null`` case in TS. + Performance =========== @@ -1097,3 +1361,4 @@ real-world applications has proved to be more than acceptable. .. _Making sine, square, sawtooth and triangle waves: http://stuartmemo.com/making-sine-square-sawtooth-and-triangle-waves/ .. _embind_tsgen.cpp: https://github.com/emscripten-core/emscripten/blob/main/test/other/embind_tsgen.cpp .. _embind_tsgen.d.ts: https://github.com/emscripten-core/emscripten/blob/main/test/other/embind_tsgen.d.ts +.. _Explicit Resource Management: https://tc39.es/proposal-explicit-resource-management/ diff --git a/site/source/docs/porting/emscripten-runtime-environment.rst b/site/source/docs/porting/emscripten-runtime-environment.rst index 2a8e41776ba1b..6010e3ce7d203 100644 --- a/site/source/docs/porting/emscripten-runtime-environment.rst +++ b/site/source/docs/porting/emscripten-runtime-environment.rst @@ -73,11 +73,11 @@ Typically you will have a small section with ``#ifdef __EMSCRIPTEN__`` for the t // Our "main loop" function. This callback receives the current time as // reported by the browser, and the user data we provide in the call to // emscripten_request_animation_frame_loop(). - EM_BOOL one_iter(double time, void* userData) { + bool one_iter(double time, void* userData) { // Can render to the screen here, etc. puts("one iteration"); // Return true to keep the loop running. - return EM_TRUE; + return true; } int main() { diff --git a/site/source/docs/porting/exceptions.rst b/site/source/docs/porting/exceptions.rst index 08d690aaae78a..cfd96b506d7fc 100644 --- a/site/source/docs/porting/exceptions.rst +++ b/site/source/docs/porting/exceptions.rst @@ -129,9 +129,11 @@ subclass of ``std::exception``. Otherwise it will be just an empty string. .. code-block:: javascript + var sp = stackSave(); try { ... // some code that calls WebAssembly } catch (e) { + stackRestore(sp); console.log(getExceptionMessage(e).toString()); } finally { ... @@ -142,25 +144,78 @@ message part is empty. If the thrown value is an instance of ``MyException`` that is a subclass of ``std::exception`` and its ``what`` message is ``My exception thrown``, this code will print ``MyException,My exception thrown``. -To use this function, you need to pass ``-sEXPORT_EXCEPTION_HANDLING_HELPERS`` -to the options. You need to enable either of Emscripten EH or Wasm EH to use -this option. +``getExceptionMessage`` is available when exceptions are used and either +``-sASSERTIONS`` or ``-sEXCEPTION_STACK_TRACES`` is set, which are by default +true at ``-O0``. At ``-O1`` or above, you can export it separately by +``-sEXPORTED_RUNTIME_METHODS=getExceptionMessage,decrementExceptionRefcount``. + +If the stack pointer has been moved due to stack allocations within the Wasm +function before an exception is thrown, you can use ``stackSave()`` and +``stackRestore()`` to restore the stack pointer so that no stack memory is +leaked. .. note:: If you catch a Wasm exception and do not rethrow it, you need to free the storage associated with the exception in JS using - ``decrementExceptionRefcount`` method because the exception - catching code in Wasm does not have a chance to free it. But currently due to - an implementation issue that Wasm EH and Emscripten (JS-based) EH, you need - to call incrementExceptionRefcount additionally in case of Emscripten EH. See - https://github.com/emscripten-core/emscripten/issues/17115 for details and a - code example. - -.. todo:: Fix the above-mentinoed `inconsistency - `_ between Wasm - EH and Emscripten EH, on the reference counting. + ``decrementExceptionRefcount`` method because the exception catching code in + Wasm does not have a chance to free it. See ``test_getExceptionMessage`` in + ``test/test_core.py`` for an example usage. Using Exceptions and setjmp-longjmp Together ============================================ See :ref:`using-exceptions-and-setjmp-longjmp-together`. + + +Limitations regarding std::terminate() +====================================== + + * Currently `std::set_terminate + `_ is NOT supported + when a thrown exception does not have a matching handler and unwinds all the + stack up to the topmost caller and crashes the program, i.e., there is no + ``catch`` that catches it and the callers are not marked as ``noexcept``. + This applies to both Emscripten-style and WebAssembly exceptions. That + functionality requires `two-phase exception handling + `_, which neither + supports. So the following program does NOT print ``my set_terminate``: + + .. code-block:: cpp + + #include + #include + + int main() { + std::set_terminate([] { + std::cerr << "my set_terminate" << std::endl; + std::abort(); + }); + throw 3; + } + + * When the exception handling encounters a termination condition, libc++abi + spec says we call `__cxa_begin_catch()` to mark the exception as handled and + then call `std::terminate()`. But currently Wasm EH does not support calling + `__cxa_begin_catch()`. So the following program prints ``exception_ptr is + null``, where it is supposed to print ``exception_ptr is NOT null``; note + that the use of ``noexcept`` here means that the ``throw 3`` will turn into + a termination condition. + + .. code-block:: cpp + + #include + #include + + int main() noexcept { + std::set_terminate([] { + auto ptr = std::current_exception(); + if (ptr) + std::cerr << "exception_ptr is NOT null" << std::endl; + else + std::cerr << "exception_ptr is null" << std::endl; + std::abort(); + }); + throw 3; + } + + This can possibly be supported in the future. diff --git a/site/source/docs/porting/files/Synchronous-Virtual-XHR-Backed-File-System-Usage.rst b/site/source/docs/porting/files/Synchronous-Virtual-XHR-Backed-File-System-Usage.rst index c8904f3f530e1..af5f07b372dfa 100644 --- a/site/source/docs/porting/files/Synchronous-Virtual-XHR-Backed-File-System-Usage.rst +++ b/site/source/docs/porting/files/Synchronous-Virtual-XHR-Backed-File-System-Usage.rst @@ -63,6 +63,6 @@ Instructions .. include:: ../../../../../test/test_browser.py :literal: - :start-after: create_file('main.html', - :end-before: """ % (worker_filename, self.port)) + :start-after: create_file('main.html', ''' + :end-before: ''' % self.PORT) :code: html diff --git a/site/source/docs/porting/files/packaging_files.rst b/site/source/docs/porting/files/packaging_files.rst index 5520d955c3a3a..33a0f13c78b7e 100644 --- a/site/source/docs/porting/files/packaging_files.rst +++ b/site/source/docs/porting/files/packaging_files.rst @@ -68,6 +68,19 @@ The file packager generates a **.data** file and **.js** file. The **.js** file - Using the *file packager* allows you to run file packaging separately from compiling the code. - You can load multiple datafiles by running the file packager on each and loading the **.js** outputs. See `BananaBread `_ for an example of dynamic loading (`cube2/js/game-setup.js `_). +You can exclude files from packaging with ``--exclude``: + +.. code-block:: bash + + python tools/file_packager.py assets.data \ + --preload assets \ + --exclude "*.psd" "*.tmp" "!assets/keep.tmp" \ + --js-output=assets.js + +``--exclude`` patterns use Python ``fnmatch`` syntax. Patterns beginning with ``!`` +act as negative patterns (re-include matches after an earlier exclude pattern). +Patterns are checked in order. + .. _packaging-files-data-file-location: @@ -113,9 +126,15 @@ Monitoring file usage .. important:: Only package the files your app actually needs, in order to reduce download size and improve startup speed. -There is an option to log which files are actually used at runtime. To use it, define the :js:attr:`Module.logReadFiles` object. Each file that is read will be logged to stderr. +There is an option to log which files are actually used at runtime. To use it, +define the :js:attr:`Module.logReadFiles` object. Each file that is read will be +logged to stderr. To use this feautre you need to add ``logReadFiles`` to +:ref:`INCOMING_MODULE_JS_API`. -An alternative approach is to look at :js:func:`FS.readFiles` in your compiled JavaScript. This is an object with keys for all the files that were read from. You may find it easier to use than logging as it records files rather than potentially multiple file accesses. +An alternative approach is to look at :js:func:`FS.readFiles` in your compiled +JavaScript. This is an object with keys for all the files that were read from. +You may find it easier to use than logging as it records files rather than +potentially multiple file accesses. .. note:: You can also modify the :js:func:`FS.readFiles` object or remove it entirely. This can be useful, say, in order to see which files are read between two points in time in your app. diff --git a/site/source/docs/porting/guidelines/function_pointer_issues.rst b/site/source/docs/porting/guidelines/function_pointer_issues.rst index 8853679e0cd6d..b3e0182f38d4c 100644 --- a/site/source/docs/porting/guidelines/function_pointer_issues.rst +++ b/site/source/docs/porting/guidelines/function_pointer_issues.rst @@ -45,7 +45,7 @@ There are three solutions to this problem (the second is preferred): - Cast the function pointer back to the correct type before it is called. This is problematic because it requires that the caller knows the original type. - Manually write an adapter function that does not need to be cast, and calls the original function. For example, it might ignore a parameter, and in that way bridge between the different function pointer types. - - Use ``EMULATE_FUNCTION_POINTER_CASTS``. When you build with ``-sEMULATE_FUNCTION_POINTER_CASTS``, Emscripten emits code to emulate function pointer casts at runtime, adding extra arguments/dropping them/changing their type/adding or dropping a return type/etc. This can add significant runtime overhead, so it is not recommended, but is be worth trying. + - Use ``EMULATE_FUNCTION_POINTER_CASTS``. When you build with ``-sEMULATE_FUNCTION_POINTER_CASTS``, Emscripten emits code to emulate function pointer casts at runtime, adding extra arguments/dropping them/changing their type/adding or dropping a return type/etc. This can add significant runtime overhead, so it is not recommended, but is worth trying. For a real-world example, consider the code below: diff --git a/site/source/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.rst b/site/source/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.rst index 043d11dd2aa86..fd52a94dd9bd0 100644 --- a/site/source/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.rst +++ b/site/source/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.rst @@ -118,4 +118,4 @@ Currently, Emscripten does not implement any extensions in the `EGL Extension Re EGL-related bugs and todos --------------------------- -The `Emscripten issue tracker `_ lists EGL-relates issues using the label EGL. Check that page to report or find issues in Emscripten related to EGL. +The `Emscripten issue tracker `_ lists EGL-related issues using the label EGL. Check that page to report or find issues in Emscripten related to EGL. diff --git a/site/source/docs/porting/networking.rst b/site/source/docs/porting/networking.rst index 150661b8e778f..3940d52566fb9 100644 --- a/site/source/docs/porting/networking.rst +++ b/site/source/docs/porting/networking.rst @@ -96,3 +96,9 @@ Direct UDP communication is not available in browsers, but as a close alternative, the WebRTC specification provides a mechanism to perform UDP-like communication with WebRTC Data Channels. Currently Emscripten does not provide a C/C++ API for interacting with WebRTC. + +WebTransport and QUIC +===================== + +WebTransport may be used to send UDP like datagrams over QUIC. +Currently Emscripten does not provide a C/C++ API for interacting with WebTransport. diff --git a/site/source/docs/porting/pthreads.rst b/site/source/docs/porting/pthreads.rst index b35dbdde78ba9..777e921ad4617 100644 --- a/site/source/docs/porting/pthreads.rst +++ b/site/source/docs/porting/pthreads.rst @@ -34,32 +34,43 @@ Additional flags The main thread also does things like create pthreads for you, so that you can depend on them synchronously. -Note that Emscripten has the -``--proxy-to-worker`` :ref:`linker flag ` which sounds similar -but is unrelated. That flag does not use pthreads or SharedArrayBuffer, and -instead uses a plain Web Worker to run your main program (and postMessage to -proxy messages back and forth). - Proxying ======== The Web allows certain operations to only happen from the main browser thread, -like interacting with the DOM. As a result, various operations are proxied to -the main browser thread if they are called on a background thread. See -`bug 3495 `_ for -more information and how to try to work around this until then. To check which -operations are proxied, you can look for the function's implementation in -the JS library (``src/library_*``) and see if it is annotated with -``__proxy: 'sync'`` or ``__proxy: 'async'``; however, note that the browser -itself proxies certain things (like some GL operations), so there is no -general way to be safe here (aside from not blocking on the main browser -thread). - -In addition, Emscripten currently has a simple model of file I/O only happening -on the main application thread (as we support JS plugin filesystems, which -cannot share memory); this is another set of operations that are proxied. - -Proxying can cause problems in certain cases, see the section on blocking below. +like interacting with the DOM. As a result, these operations need to be proxied +to the main browser thread if they are called from a background thread. + +For JS library functions this proxying is automatic. You can see which +functions are proxied by looking for the ``__proxy:`` annotations in the JS +libraries. For example, ``getaddrinfo__proxy: 'sync'`` marks the ``getaddrinfo`` +function for automatic proxying. + +Functions can be marked using a ``__proxy`` annotation of either ``sync`` or +``async``. By far the most common (and useful) is ``sync`` which means that +the calling thread will be blocked until the main thread has performed the +proxied function and the return value will be returned synchronously on the +calling thread. ``async`` proxied functions will return immediately on the +calling thread and the JS function will be queued for execution on the main +thread at some point in the future. + +This automatic proxying mechanism is built on the :ref:`proxying-h` API which +can also be used directly for more precise control over how work is run on +different threads. + +When a JS library function is marked as both ``__proxy: 'sync'`` and ``__async: +'auto'``, it is expected to return a promise +(see :ref:`marking_async_functions`). +The calling thread will block until the async operation on the +main thread is completed (i.e. the returned promise is resolved), and the value +that the promise resolves to will be returned to the caller. This allows +functions that would require :ref:`ASYNCIFY` (when called from the main browser +thread) to be called without :ref:`ASYNCIFY` from a background thread. In other +words, background threads can block on proxied work even without :ref:`ASYNCIFY` +enabled. + +.. note:: Proxying can cause problems, such as deadlocks, in certain cases, see + the section on blocking below. Blocking on the main browser thread =================================== @@ -136,7 +147,7 @@ The Emscripten implementation for the pthreads API should follow the POSIX stand - The Emscripten implementation does also not support multiprocessing via ``fork()`` and ``join()``. -- For web security purposes, there exists a fixed limit (by default 20) of threads that can be spawned when running in Firefox Nightly. `#1052398 `_. To adjust the limit, navigate to about:config and change the value of the pref "dom.workers.maxPerDomain". +- For web security purposes, there exists a fixed limit (by default 20) of threads that can be spawned when running in Firefox Nightly. `#1052398 `_. To adjust the limit, navigate to about:config and change the value of the pref "dom.workers.maxPerDomain". - Some of the features in the pthreads specification are unsupported since the upstream musl library that Emscripten utilizes does not support them, or they are marked optional and a conformant implementation need not support them. Such unsupported features in Emscripten include prioritization of threads, and pthread_rwlock_unlock() is not performed in thread priority order. The functions pthread_mutexattr_set/getprotocol(), pthread_mutexattr_set/getprioceiling() and pthread_attr_set/getscope() are no-ops. @@ -144,9 +155,7 @@ The Emscripten implementation for the pthreads API should follow the POSIX stand - Note that the function emscripten_num_logical_cores() will always return the value of navigator.hardwareConcurrency, i.e. the number of logical cores on the system, even when shared memory is not supported. This means that it is possible for emscripten_num_logical_cores() to return a value greater than 1, while at the same time emscripten_has_threading_support() can return false. The return value of emscripten_has_threading_support() denotes whether the browser has shared memory support available. -- Pthreads + memory growth (``ALLOW_MEMORY_GROWTH``) is especially tricky, see `Wasm design issue #1271 `_. This currently causes JS accessing the Wasm memory to be slow - but this will likely only be noticeable if the JS does large amounts of memory reads and writes (Wasm runs at full speed, so moving work over can fix this). This also requires that your JS be aware that the HEAP* views may need to be updated - JS code embedded with ``--js-library`` etc will automatically be transformed to use the ``GROWABLE_HEAP_*`` helper functions where ``HEAP*`` are used, but external code that uses ``Module.HEAP*`` directly may encounter problems with views being smaller than memory. - -Also note that when compiling code that uses pthreads, an additional JavaScript file ``NAME.worker.js`` is generated alongside the output .js file (where ``NAME`` is the basename of the main file being emitted). That file must be deployed with the rest of the generated code files. By default, ``NAME.worker.js`` will be loaded relative to the main HTML page URL. If it is desirable to load the file from a different location e.g. in a CDN environment, then one can define the ``Module.locateFile(filename)`` function in the main HTML ``Module`` object to return the URL of the target location of the ``NAME.worker.js`` entry point. If this function is not defined in ``Module``, then the default location relative to the main HTML file is used. +- Pthreads + memory growth (``ALLOW_MEMORY_GROWTH``) is especially tricky, see `Wasm design issue #1271 `_. This currently causes JS accessing the Wasm memory to be slow - but this will likely only be noticeable if the JS does large amounts of memory reads and writes (Wasm runs at full speed, so moving work over can fix this). This also requires that your JS be aware that the HEAP* views may need to be updated - JS code embedded with ``--js-library`` etc will automatically be transformed to auto-update memory views before each access, but external code that uses ``Module.HEAP*`` directly may encounter problems with views being smaller than memory. .. _Allocator_performance: diff --git a/site/source/docs/porting/setjmp-longjmp.rst b/site/source/docs/porting/setjmp-longjmp.rst index d0250f4dc6288..b32a51cc898fd 100644 --- a/site/source/docs/porting/setjmp-longjmp.rst +++ b/site/source/docs/porting/setjmp-longjmp.rst @@ -10,7 +10,7 @@ by the ``SUPPORT_LONGJMP`` setting, which can take these values: * ``emscripten``: JavaScript-based support * ``wasm``: WebAssembly exception handling-based support * 0: No support -* 1: Default support, depending on the exception mode. ``wasm`` if ``-fwasm-exception`` is used, ``emscripten`` otherwise. +* 1: Default support, depending on the exception mode. ``wasm`` if ``-fwasm-exceptions`` is used, ``emscripten`` otherwise. If :ref:`native Wasm exceptions ` are used, ``SUPPORT_LONGJMP`` defaults to ``wasm``, and if :ref:`JavaScript-based @@ -69,9 +69,9 @@ Using Exceptions and setjmp-longjmp Together We also have two kinds of :ref:`exception handling support `: JavaScript-based support and the new WebAssembly EH-based support. Our -setjmp-longjmp support use the same mechanisms. Because of that, you should use -the same kind of EH and setjmp-longjmp support when using exceptions and -setjmp-longjmp together. +setjmp-longjmp support use the same mechanisms. Because of that, when they are +used together, the kind of setjmp-longjmp support will be determined by the EH +support. For example, to use the JavaScript-based EH and setjmp-longjmp support together: @@ -79,15 +79,26 @@ For example, to use the JavaScript-based EH and setjmp-longjmp support together: em++ -fexceptions test.cpp -o test.js +To use the WebAssembly EH and setjmp-longjmp support together: + +.. code-block:: bash + + em++ -fwasm-exceptions test.cpp -o test.js + ``-sSUPPORT_LONGJMP``, which defaults to ``emscripten`` or ``wasm`` depending on the exception mode, is enabled by default, so you don't need to pass it explicitly. -To use the WebAssembly EH and setjmp-longjmp support together: +But you need to pass ``-sSUPPORT_LONGJMP=wasm`` at compile time explicitly +before you link C and C++ code together, because you don't use exception flags +at C compile time, but it needs to match the setjmp-longjmp handling model of +the C++ code. .. code-block:: bash - em++ -fwasm-exceptions -sSUPPORT_LONGJMP=wasm test.cpp -o test.js + emcc -c -sSUPPORT_LONGJMP=wasm a.c -o a.o + em++ -c -fwasm-exceptions b.cpp -o b.o + em++ -fwasm-exceptions a.o b.o -o test.js There is one specific restriction for using WebAssembly EH-based support for exceptions and setjmp-longjmp at the same time. You cannot call ``setjmp`` diff --git a/site/source/docs/porting/simd.rst b/site/source/docs/porting/simd.rst index fd4cfcc6def50..cf1241a6ba942 100644 --- a/site/source/docs/porting/simd.rst +++ b/site/source/docs/porting/simd.rst @@ -1,4 +1,4 @@ -.. Porting SIMD code: +.. _Porting SIMD code: .. role:: raw-html(raw) :format: html @@ -12,7 +12,7 @@ Emscripten supports the `WebAssembly SIMD 1. Enable LLVM/Clang SIMD autovectorizer to automatically target WebAssembly SIMD, without requiring changes to C/C++ source code. 2. Write SIMD code using the GCC/Clang SIMD Vector Extensions (``__attribute__((vector_size(16)))``) 3. Write SIMD code using the WebAssembly SIMD intrinsics (``#include ``) -4. Compile existing SIMD code that uses the x86 SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 or 128-bit subset of the AVX intrinsics (``#include <*mmintrin.h>``) +4. Compile existing SIMD code that uses the x86 SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AVX or AVX2 intrinsics (``#include <*mmintrin.h>``) 5. Compile existing SIMD code that uses the ARM NEON intrinsics (``#include ``) These techniques can be freely combined in a single program. @@ -33,7 +33,7 @@ See `WebAssembly Roadmap `_ for details about An upcoming `Relaxed SIMD proposal `_ will add more SIMD instructions to WebAssembly. -================================ + GCC/Clang SIMD Vector Extensions ================================ @@ -41,7 +41,7 @@ At the source level, the GCC/Clang `SIMD Vector Extensions `_ are not available. Instead, use the WebAssembly SIMD Intrinsics functions below. -=========================== + WebAssembly SIMD Intrinsics =========================== @@ -54,9 +54,9 @@ LLVM maintains a WebAssembly SIMD Intrinsics header file that is provided with E int main() { #ifdef __wasm_simd128__ - v128 v1 = wasm_f32x4_make(1.2f, 3.4f, 5.6f, 7.8f); - v128 v2 = wasm_f32x4_make(2.1f, 4.3f, 6.5f, 8.7f); - v128 v3 = v1 + v2; + v128_t v1 = wasm_f32x4_make(1.2f, 3.4f, 5.6f, 7.8f); + v128_t v2 = wasm_f32x4_make(2.1f, 4.3f, 6.5f, 8.7f); + v128_t v3 = wasm_f32x4_add(v1, v2); // Prints "v3: [3.3, 7.7, 12.1, 16.5]" printf("v3: [%.1f, %.1f, %.1f, %.1f]\n", wasm_f32x4_extract_lane(v3, 0), @@ -72,7 +72,7 @@ Pass flag ``-msimd128`` at compile time to enable targeting WebAssembly SIMD Int Pass ``-mrelaxed-simd`` to target WebAssembly Relaxed SIMD Intrinsics. C/C++ code can use the built-in preprocessor define ``#ifdef __wasm_relaxed_simd__`` to detect when this target is active. -====================================== + Limitations and behavioral differences ====================================== @@ -88,7 +88,7 @@ When porting native SIMD code, it should be noted that because of portability co SIMD-related bug reports are tracked in the `Emscripten bug tracker with the label SIMD `_. -=========================== + Optimization considerations =========================== @@ -97,6 +97,7 @@ When developing SIMD code to use WebAssembly SIMD, implementors should be aware .. list-table:: WebAssembly SIMD instructions with performance implications :widths: 10 10 30 :header-rows: 1 + :class: wrap-table-content * - WebAssembly SIMD instruction - Arch @@ -108,11 +109,11 @@ When developing SIMD code to use WebAssembly SIMD, implementors should be aware * - i8x16.[shl|shr_s|shr_u] - x86 - - Included for orthogonality, these instructions have no equivalent x86 instruction and are emulated with `5-11 x86 instructions in v8 `_ (i.e. using 16x8 shifts). + - Included for orthogonality, these instructions have no equivalent x86 instruction and are emulated with `5-11 x86 instructions in v8 `_ (i.e. using 16x8 shifts). * - i64x2.shr_s - x86 - - Included for orthogonality, this instruction has no equivalent x86 instruction and is emulated with `6 x86 instructions in v8 `_. + - Included for orthogonality, this instruction has no equivalent x86 instruction and is emulated with `6-12 x86 instructions in v8 `_. * - i8x16.swizzle - x86 @@ -120,7 +121,7 @@ When developing SIMD code to use WebAssembly SIMD, implementors should be aware * - [f32x4|f64x2].[min|max] - x86 - - As with the scalar versions, the NaN propagation semantics force runtimes to emulate with 8+ x86 instructions (e.g., see `v8's emulation `_; if possible, use [f32x4|f64x2].[pmin|pmax] instead (1 x86 instruction). + - As with the scalar versions, the NaN propagation semantics force runtimes to emulate with 7-10 x86 instructions (e.g., see `v8's emulation `_; if possible, use [f32x4|f64x2].[pmin|pmax] instead (1 x86 instruction). * - i32x4.trunc_sat_f32x4_[u|s] - x86 @@ -139,7 +140,7 @@ When developing SIMD code to use WebAssembly SIMD, implementors should be aware - Included for orthogonality, these instructions have no equivalent x86 instruction and are `emulated with 10 x86 instructions in v8 `_. -======================================================= + Compiling SIMD code targeting x86 SSE* instruction sets ======================================================= @@ -152,8 +153,9 @@ Emscripten supports compiling existing codebases that use x86 SSE instructions b * **SSE4.1**: pass ``-msse4.1`` and ``#include ``. Use ``#ifdef __SSE4_1__`` to gate code. * **SSE4.2**: pass ``-msse4.2`` and ``#include ``. Use ``#ifdef __SSE4_2__`` to gate code. * **AVX**: pass ``-mavx`` and ``#include ``. Use ``#ifdef __AVX__`` to gate code. +* **AVX2**: pass ``-mavx2`` and ``#include ``. Use ``#ifdef __AVX2__`` to gate code. -Currently only the SSE1, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and 128-bit AVX instruction sets are supported. Each of these instruction sets add on top of the previous ones, so e.g. when targeting SSE3, the instruction sets SSE1 and SSE2 are also available. +Currently only the SSE1, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and AVX instruction sets are supported. Each of these instruction sets add on top of the previous ones, so e.g. when targeting SSE3, the instruction sets SSE1 and SSE2 are also available. The following tables highlight the availability and expected performance of different SSE* intrinsics. This can be useful for understanding the performance limitations that the Wasm SIMD specification has when running on x86 hardware. @@ -176,6 +178,7 @@ In addition to consulting the tables below, you can turn on diagnostics for slow .. list-table:: x86 SSE intrinsics available via #include and -msse :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -410,6 +413,7 @@ The following table highlights the availability and expected performance of diff .. list-table:: x86 SSE2 intrinsics available via #include and -msse2 :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -862,6 +866,7 @@ The following table highlights the availability and expected performance of diff .. list-table:: x86 SSE3 intrinsics available via #include and -msse3 :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -901,6 +906,7 @@ The following table highlights the availability and expected performance of diff .. list-table:: x86 SSSE3 intrinsics available via #include and -mssse3 :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -947,6 +953,7 @@ The following table highlights the availability and expected performance of diff .. list-table:: x86 SSE4.1 intrinsics available via #include and -msse4.1 :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -1051,9 +1058,9 @@ The following table highlights the availability and expected performance of diff * - _mm_packus_epi32 - ✅ wasm_u16x8_narrow_i32x4 * - _mm_round_pd - - ✅ wasm_f64x2_ceil/wasm_f64x2_floor/wasm_f64x2_nearest/wasm_f64x2_trunc + - ✅ wasm_f64x2_ceil, wasm_f64x2_floor, wasm_f64x2_nearest, wasm_f64x2_trunc * - _mm_round_ps - - ✅ wasm_f32x4_ceil/wasm_f32x4_floor/wasm_f32x4_nearest/wasm_f32x4_trunc + - ✅ wasm_f32x4_ceil, wasm_f32x4_floor, wasm_f32x4_nearest, wasm_f32x4_trunc * - _mm_round_sd - ⚠️ emulated with a shuffle * - _mm_round_ss @@ -1094,6 +1101,7 @@ The following table highlights the availability and expected performance of diff .. list-table:: x86 AVX intrinsics available via #include and -mavx :widths: 20 30 :header-rows: 1 + :class: wrap-table-content * - Intrinsic name - WebAssembly SIMD support @@ -1136,10 +1144,94 @@ The following table highlights the availability and expected performance of diff * - _mm_testz_ps - 💣 emulated with complex SIMD+scalar sequence -Only the 128-bit wide instructions from AVX instruction set are available. 256-bit wide AVX instructions are not provided. +Only the 128-bit wide instructions from AVX instruction set are listed. The 256-bit wide AVX instructions are emulated by two 128-bit wide instructions. + +The following table highlights the availability and expected performance of different AVX2 intrinsics. Refer to `Intel Intrinsics Guide on AVX2 `_. + +.. list-table:: x86 AVX2 intrinsics available via #include and -mavx2 + :widths: 20 30 + :header-rows: 1 + + * - Intrinsic name + - WebAssembly SIMD support + * - _mm_broadcastss_ps + - 💡 emulated with a general shuffle + * - _mm_broadcastsd_pd + - 💡 emulated with a general shuffle + * - _mm_blend_epi32 + - 💡 emulated with a general shuffle + * - _mm_broadcastb_epi8 + - 💡 emulated with a general shuffle + * - _mm_broadcastw_epi16 + - 💡 emulated with a general shuffle + * - _mm_broadcastd_epi32 + - 💡 emulated with a general shuffle + * - _mm_broadcastq_epi64 + - 💡 emulated with a general shuffle + * - _mm256_permutevar8x32_epi32 + - ❌ scalarized + * - _mm256_permute4x64_pd + - 💡 emulated with two general shuffle + * - _mm256_permutevar8x32_ps + - ❌ scalarized + * - _mm256_permute4x64_epi64 + - 💡 emulated with two general shuffle + * - _mm_maskload_epi32 + - ❌ scalarized + * - _mm_maskload_epi64 + - ❌ scalarized + * - _mm_maskstore_epi32 + - ❌ scalarized + * - _mm_maskstore_epi64 + - ❌ scalarized + * - _mm_sllv_epi32 + - ❌ scalarized + * - _mm_sllv_epi64 + - ❌ scalarized + * - _mm_srav_epi32 + - ❌ scalarized + * - _mm_srlv_epi32 + - ❌ scalarized + * - _mm_srlv_epi64 + - ❌ scalarized + * - _mm_mask_i32gather_pd + - ❌ scalarized + * - _mm_mask_i64gather_pd + - ❌ scalarized + * - _mm_mask_i32gather_ps + - ❌ scalarized + * - _mm_mask_i64gather_ps + - ❌ scalarized + * - _mm_mask_i32gather_epi32 + - ❌ scalarized + * - _mm_mask_i64gather_epi32 + - ❌ scalarized + * - _mm_mask_i32gather_epi64 + - ❌ scalarized + * - _mm_mask_i64gather_epi64 + - ❌ scalarized + * - _mm_i32gather_pd + - ❌ scalarized + * - _mm_i64gather_pd + - ❌ scalarized + * - _mm_i32gather_ps + - ❌ scalarized + * - _mm_i64gather_ps + - ❌ scalarized + * - _mm_i32gather_epi32 + - ❌ scalarized + * - _mm_i64gather_epi32 + - ❌ scalarized + * - _mm_i32gather_epi64 + - ❌ scalarized + * - _mm_i64gather_epi64 + - ❌ scalarized + +All the 128-bit wide instructions from AVX2 instruction set are listed. +Only a small part of the 256-bit AVX2 instruction set are listed, most of the +256-bit wide AVX2 instructions are emulated by two 128-bit wide instructions. -====================================================== Compiling SIMD code targeting ARM NEON instruction set ====================================================== @@ -1175,35 +1267,52 @@ status `_ (2.4.4), the open source tool used to create the official Python documentation and many other sites. This is a very mature and stable tool, and was selected for, among other reasons, its support for defining API items and linking to them from code. +The site is built using `Sphinx `_ (7.1.2), the open source tool used to create the official Python documentation and many other sites. This is a very mature and stable tool, and was selected for, among other reasons, its support for defining API items and linking to them from code. The site uses a custom theme, which is based on the :ref:`read-the-docs-theme`. diff --git a/site/source/docs/tools_reference/emcc.rst b/site/source/docs/tools_reference/emcc.rst index c3174f98801eb..a025caeee3b13 100644 --- a/site/source/docs/tools_reference/emcc.rst +++ b/site/source/docs/tools_reference/emcc.rst @@ -54,7 +54,7 @@ Options that are modified or new in *emcc* are listed below: ``-O1`` [compile+link] - Simple optimizations. During the compile step these include LLVM ``-O1`` optimizations. During the link step this does not include various runtime assertions in JS that `-O0` would do. + Simple optimizations. During the compile step these include LLVM ``-O1`` optimizations. During the link step this omits various runtime assertions in JS that `-O0` would include. .. _emcc-O2: @@ -68,7 +68,7 @@ Options that are modified or new in *emcc* are listed below: ``-O3`` [compile+link] - Like ``-O2``, but with additional optimizations that may take longer to run. + Like ``-O2``, but with additional optimizations that may take longer to run and may increase code size. .. note:: This is a good setting for a release build. @@ -76,8 +76,8 @@ Options that are modified or new in *emcc* are listed below: ``-Og`` [compile+link] - Like ``-O1``. In future versions, this option might disable different - optimizations in order to improve debuggability. + Like ``-O1``, with an additional flag to extend the liveness of variables for improved debugging. + In future versions, additional optimizations might also be disabled. .. _emcc-Os: @@ -172,21 +172,32 @@ Options that are modified or new in *emcc* are listed below: .. _emcc-gsource-map: -``-gsource-map`` - [link] +``-gsource-map[=inline]`` + [compile+link] + [same as -g3 if passed at compile time, otherwise applies at link] Generate a source map using LLVM debug information (which must - be present in object files, i.e., they should have been compiled with ``-g``). + be present in object files, i.e., they should have been compiled with ``-g`` + or ``-gsource-map``). + When this option is provided, the **.wasm** file is updated to have a ``sourceMappingURL`` section. The resulting URL will have format: ```` + ```` + ``.map``. ```` defaults to being empty (which means the source map is served from the same directory as the Wasm file). It can be changed using :ref:`--source-map-base `. + Path substitution can be applied to the referenced sources using the + ``-sSOURCE_MAP_PREFIXES`` (:ref:`link `). + If ``inline`` is specified, the sources content is embedded in the source map + (in this case you don't need path substitution, but it comes with the cost of + having a large source map file). + .. _emcc-gN: ``-g`` [compile+link] - Controls the level of debuggability. Each level builds on the previous one: + If used at compile time, adds progressively more DWARF information to the object file, + according to the underlying behavior of clang. + If used at link time, controls the level of debuggability overall. Each level builds on the previous one: - .. _emcc-g0: @@ -196,29 +207,31 @@ Options that are modified or new in *emcc* are listed below: - .. _emcc-g1: - ``-g1``: When linking, preserve whitespace in JavaScript. + ``-g1``: Preserve whitespace in JavaScript. - .. _emcc-g2: - ``-g2``: When linking, preserve function names in compiled code. + ``-g2``: Also preserve function names in compiled code (via the wasm name section). - .. _emcc-g3: - ``-g3``: When compiling to object files, keep debug info, including JS whitespace, function names, and LLVM debug info (DWARF) if any (this is the same as :ref:`-g `). + ``-g3``: Also keep LLVM debug info (DWARF) if there is any in the object files (this is the same as :ref:`-g `). .. _emcc-profiling: ``--profiling`` - [same as -g2 if passed at compile time, otherwise applies at link] - Use reasonable defaults when emitting JavaScript to make the build readable but still useful for profiling. This sets ``-g2`` (preserve whitespace and function names) and may also enable optimizations that affect performance and otherwise might not be performed in ``-g2``. + [link] + Make the output suitable for profiling. This means including function names in the wasm and JS output, and + preserving whitespace in the JS output. It does not affect optimizations (to ensure that performance profiles + reflect production builds). Currently this is the same as ``-g2``. .. _emcc-profiling-funcs: ``--profiling-funcs`` [link] - Preserve function names in profiling, but otherwise minify whitespace and names as we normally do in optimized builds. This is useful if you want to look at profiler results based on function names, but do *not* intend to read the emitted code. + Preserve wasm function names as in ``--profiling``, but otherwise minify whitespace and names as we normally do in optimized builds. This is useful if you want to look at profiler results based on function names, but do *not* intend to read the emitted code. ``--tracing`` [link] @@ -237,10 +250,20 @@ Options that are modified or new in *emcc* are listed below: Save a map file between function indexes in the Wasm and function names. By storing the names on a file on the side, you can avoid shipping the names, and can still reconstruct meaningful stack traces by translating the indexes back - to the names. + to the names. This is a simpler format than source maps, but less detailed + because it only describes function names and not source locations. .. note:: When used with ``-sWASM=2``, two symbol files are created. ``[name].js.symbols`` (with WASM symbols) and ``[name].wasm.js.symbols`` (with ASM.js symbols) +.. _emcc-emit-minification-map: + +``--emit-minification-map `` + [link] + In cases where emscripten performs import/export minification this option can + be used to output a file that maps minified names back to their original + names. The format of this file is single line per import/export of the form + ``:``. + .. _emcc-lto: ``-flto`` @@ -253,7 +276,7 @@ Options that are modified or new in *emcc* are listed below: [link] Runs the :term:`Closure Compiler`. Possible values are: - - ``0``: No closure compiler (default in ``-O2`` and below). + - ``0``: No closure compiler (default). - ``1``: Run closure compiler. This greatly reduces the size of the support JavaScript code (everything but the WebAssembly or asm.js). Note that this increases compile time significantly. - ``2``: Run closure compiler on *all* the emitted code, even on **asm.js** output in **asm.js** mode. This can further reduce code size, but does prevent a significant amount of **asm.js** optimizations, so it is not recommended unless you want to reduce code size at all costs. @@ -261,7 +284,6 @@ Options that are modified or new in *emcc* are listed below: - Consider using ``-sMODULARIZE`` when using closure, as it minifies globals to names that might conflict with others in the global scope. ``MODULARIZE`` puts all the output into a function (see ``src/settings.js``). - Closure will minify the name of `Module` itself, by default! Using ``MODULARIZE`` will solve that as well. Another solution is to make sure a global variable called `Module` already exists before the closure-compiled code runs, because then it will reuse that variable. - - Closure is only run if JavaScript opts are being done (``-O2`` or above). ``--closure-args=`` [link] @@ -361,7 +383,7 @@ Options that are modified or new in *emcc* are listed below: .. note:: - - See `src/shell.html `_ and `src/shell_minimal.html `_ for examples. + - See `html/shell.html `_ and `html/shell_minimal.html `_ for examples. - This argument is ignored if a target other than HTML is specified using the ``-o`` option. .. _emcc-source-map-base: @@ -395,8 +417,16 @@ Options that are modified or new in *emcc* are listed below: ``--embind-emit-tsd `` [link] - Generate a TypeScript definition file from the exported embind bindings. The - program will be instrumented and run in node in order to to generate the file. + Generates TypeScript definition file. Deprecated: Use ``--emit-tsd`` instead. + +.. _emcc-emit-tsd: + +``--emit-tsd `` + [link] + Generate a TypeScript definition file for the emscripten module. The definition + file will include exported Wasm functions, runtime exports, and exported + embind bindings (if used). In order to generate bindings from embind, the + program will be instrumented and run in node. ``--ignore-dynamic-linking`` [link] @@ -491,12 +521,6 @@ Options that are modified or new in *emcc* are listed below: [compile+link] Enables warnings about the use of absolute paths in ``-I`` and ``-L`` command line directives. This is used to warn against unintentional use of absolute paths, which is sometimes dangerous when referring to nonportable local system headers. -.. _proxy-to-worker: - -``--proxy-to-worker`` - [link] - Runs the main application code in a worker, proxying events to it and output from it. If emitting HTML, this emits a **.html** file, and a separate **.js** file containing the JavaScript to be run in a worker. If emitting JavaScript, the target file name contains the part to be run on the main thread, while a second **.js** file with suffix ".worker.js" will contain the worker portion. - .. _emcc-emrun: ``--emrun`` @@ -544,15 +568,19 @@ Options that are modified or new in *emcc* are listed below: These rules only apply when linking. When compiling to object code (See `-c` below) the name of the output file is irrelevant. + Note: Linking to a file with no extension (or a file ending in ``.out``, like + ``a.out``) will cause the generated JavaScript file to be exectuable, and + include a ``#!`` line to make it runnable directly. + .. _emcc-c: ``-c`` [compile] Tells *emcc* to emit an object file which can then be linked with other object files to produce an executable. -``--output_eol windows|linux`` +``--output-eol windows|linux`` [link] - Specifies the line ending to generate for the text files that are outputted. If "--output_eol windows" is passed, the final output files will have Windows \r\n line endings in them. With "--output_eol linux", the final generated files will be written with Unix \n line endings. + Specifies the line ending to generate for the text files that are outputted. If "--output-eol windows" is passed, the final output files will have Windows ``\r\n`` line endings in them. With "--output-eol linux", the final generated files will be written with Unix ``\n`` line endings. ``--cflags`` [other] diff --git a/site/source/docs/tools_reference/emsdk.rst b/site/source/docs/tools_reference/emsdk.rst index b3074179a93f0..02ed9ce812a1e 100644 --- a/site/source/docs/tools_reference/emsdk.rst +++ b/site/source/docs/tools_reference/emsdk.rst @@ -106,7 +106,7 @@ the variable names used to point to the different tools:: # .emscripten file from Linux SDK import os - NODE_JS = 'nodejs' + NODE_JS = 'node' LLVM_ROOT='/home/ubuntu/emsdk/upstream/bin' .. _emsdk_howto: @@ -121,9 +121,11 @@ The following topics explain how to perform both common and advanced maintenance .. _emsdk-get-latest-sdk: -How do I just get the latest SDK? ---------------------------------- -Use the ``update`` argument to fetch the current registry of available tools, and then specify the ``latest`` install target to get the most recent SDK: :: +How do I just get the latest SDK release? +----------------------------------------- + +Use the ``update`` argument to fetch the current registry of available tools, +and then specify the ``latest`` install target to get the most recent SDK: :: # Fetch the latest registry of available tools. ./emsdk update @@ -134,7 +136,25 @@ Use the ``update`` argument to fetch the current registry of available tools, an # Set up the compiler configuration to point to the "latest" SDK. ./emsdk activate latest +How do I install a specific version? +------------------------------------ + +Use the commands above, replacing ``latest`` with the version you want, for +example: :: + + ./emsdk install 4.0.7 + ./emsdk activate 4.0.7 + +(you may need to do ``./emsdk update`` before). + +Each release also has an *asserts version* which is built with more runtime +checks in LLVM and Binaryen. This can be useful if you think you have +encountered a bug in one of these tools. The names of asserts versions are the +same as release versions, with an added suffix of ``-asserts``, e.g., +``4.0.7-asserts``, which you can use with: :: + ./emsdk install 4.0.7-asserts + ./emsdk activate 4.0.7-asserts How do I use emsdk? ------------------- @@ -209,7 +229,7 @@ particular tool: :: On Windows, calling ``activate`` automatically sets up the required paths and environment variables. -.. note:: If you add ``./emsdk_env.sh`` to you default shell config emsdk tools (including the emsdk version of node) will be added to your PATH and this could effect the default version of node used on your system. +.. note:: If you add ``./emsdk_env.sh`` to you default shell config emsdk tools (including the emsdk version of node) will be added to your PATH and this could affect the default version of node used on your system. .. _emsdk-install-old-tools: @@ -232,44 +252,52 @@ How do I install and activate old Emscripten SDKs and tools? .. _emsdk-dev-sdk: -How do I track the latest Emscripten development with the SDK? --------------------------------------------------------------- +How do I track the latest changes with the SDK? +----------------------------------------------- -It is also possible to use the latest and greatest versions of the tools on the GitHub repositories! This allows you to obtain new features and latest fixes immediately as they are pushed to GitHub, without having to wait for release to be tagged. **No GitHub account or fork of Emscripten is required.** +To try the latest changes with emsdk you can install and activate a special +version called ``tot`` (Tip-Of-Tree) which is continuously built and usually +contains Emscripten and LLVM changes just a few hours after they are committed: -To switch to using the latest upstream git development branch (``main``), run the following: +:: + + ./emsdk install tot + ./emsdk activate tot + +If you want to build everything yourself from the very latest sources you can +use ``sdk-main-64bit``: :: - # Install git. Skip if the system already has it. + # Install git (Skip if the system already has it). ./emsdk install git-1.8.3 # Clone+pull the latest emscripten-core/emscripten/main. - ./emsdk install sdk-upstream-main-64bit + ./emsdk install sdk-main-64bit - # Set the "upstream-main SDK" as the active version. - ./emsdk activate sdk-upstream-main-64bit + # Set this as the active version. + ./emsdk activate sdk-main-64bit .. _emsdk-howto-use-own-fork: -How do I use my own Emscripten GitHub fork with the SDK? --------------------------------------------------------- +How do I use my own Emscripten fork with the SDK? +------------------------------------------------- -It is also possible to use your own fork of the Emscripten repository via the SDK. This is useful in the case when you want to make your own modifications to the Emscripten toolchain, but still keep using the SDK environment and tools. +It is also possible to use your own fork of the Emscripten repository via the +SDK. This is useful in the case when you want to make your own modifications to +the Emscripten toolchain, but still keep using the SDK environment and tools. -The way this works is that you first install the ``sdk-upstream-main`` SDK as in the :ref:`previous section `. Then you use familiar git commands to replace this branch with the information from your own fork: +To do this all you need to do is set the ``EM_CONFIG`` environment variable to +point to the emsdk emscripten config and then put your own checkout of +emscripten first in the ``PATH``: :: - cd emscripten/main - - # Add a git remote link to your own repository. - git remote add myremote https://github.com/mygituseraccount/emscripten.git - - # Obtain the changes in your link. - git fetch myremote + cd my_emscripten/ - # Switch the emscripten-main tool to use your fork. - git checkout -b mymain --track myremote/main + # Tell emscripten to use the emsdk config file + export EM_CONFIG=/path/to/emsdk/.emscripten -You can switch back and forth between remotes via the ``git checkout`` command as usual. + # Now your version of emscripten will use LLVM and binaryen + # binaries from the currently active version of emsdk. + ./emcc diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index 9300709020657..b9962d866639c 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -4,13 +4,13 @@ Emscripten Compiler Settings ============================ -The following is a complete list of settings that can be passed -to emscripten via ``-s`` on the command line. For example -``-sASSERTIONS`` or ``-sASSERTIONS=0``. For more details see the -:ref:`emcc ` documentation. +The following is a complete list of settings that can be passed to emscripten +via ``-s`` on the command line. For example ``-sASSERTIONS`` or +``-sASSERTIONS=0``. For more details see the :ref:`emcc ` +documentation. -Unless otherwise noted these settings only apply when linking -and have no effect during compilation. +Unless otherwise noted these settings only apply when linking and have no effect +during compilation. .. Auto-generated by update_settings_docs.py. **DO NOT EDIT** @@ -23,6 +23,9 @@ Whether we should add runtime assertions. This affects both JS and how system libraries are built. ASSERTIONS == 2 gives even more runtime checks, that may be very slow. That includes internal dlmalloc assertions, for example. +ASSERTIONS defaults to 0 in optimized builds (-O1 and above). + +Default value: 1 .. _stack_overflow_check: @@ -33,7 +36,7 @@ Chooses what kind of stack smash checks to emit to generated code: Building with ASSERTIONS=1 causes STACK_OVERFLOW_CHECK default to 1. Since ASSERTIONS=1 is the default at -O0, which itself is the default optimization level this means that this setting also effectively -defaults 1, absent any other settings: +defaults to 1, absent any other settings: - 0: Stack overflows are not checked. - 1: Adds a security cookie at the top of the stack, which is checked at end @@ -41,16 +44,20 @@ defaults 1, absent any other settings: - 2: Same as above, but also runs a binaryen pass which adds a check to all stack pointer assignments. Has a small performance cost. +Default value: 0 + .. _check_null_writes: CHECK_NULL_WRITES ================= -When STACK_OVERFLOW_CHECK is enabled we also check writes to address zero. -This can help detect NULL pointer usage. If you want to skip this extra -check (for example, if you want reads from the address zero to always return -zero) you can disabled this here. This setting has no effect when -STACK_OVERFLOW_CHECK is disabled. +When :ref`STACK_OVERFLOW_CHECK` is enabled we also check writes to address +zero. This can help detect NULL pointer usage. If you want to skip this +extra check (for example, if you want reads from the address zero to always +return zero) you can disable this here. This setting has no effect when +:ref:`STACK_OVERFLOW_CHECK` is disabled. + +Default value: true .. _verbose: @@ -60,6 +67,8 @@ VERBOSE When set to 1, will generate more verbose output during compilation. [general] +Default value: false + .. _invoke_run: INVOKE_RUN @@ -69,21 +78,34 @@ Whether we will run the main() function. Disable if you embed the generated code in your own, and will call main() yourself at the right time (which you can do with Module.callMain(), with an optional parameter of commandline args). +Default value: true + .. _exit_runtime: EXIT_RUNTIME ============ -If 0, the runtime is not quit when main() completes (allowing code to -run afterwards, for example from the browser main event loop). atexit()s -are also not executed, and we can avoid including code for runtime shutdown, -like flushing the stdio streams. -Set this to 1 if you do want atexit()s or stdio streams to be flushed -on exit. -This setting is controlled automatically in STANDALONE_WASM mode: +If 0, support for shutting down the runtime is not emitted into the build. +This means that the program is not quit when main() completes, but execution +will yield to the event loop of the JS environment, allowing event handlers +to run afterwards. If 0, C++ global destructors will not be emitted into the +build either, to save on code size. Calling exit() will throw an unwinding +exception, but will not shut down the runtime. + +Set this to 1 if you do want to retain the ability to shut down the program. +If 1, then completing main() will by default call exit(), unless a refcount +keeps the runtime alive. Call emscripten_exit_with_live_runtime() to finish +main() while keeping the runtime alive. Calling emscripten_force_exit() will +shut down the runtime, invoking atexit()s, and flushing stdio streams. +This setting is controlled automatically in :ref:`STANDALONE_WASM` mode: - For a command (has a main function) this is always 1 -- For a reactor (no a main function) this is always 0 +- For a reactor (no main function) this is always 0 + +For more details, see documentation for emscripten_force_exit() and +emscripten_exit_with_live_runtime(). + +Default value: false .. _stack_size: @@ -95,6 +117,8 @@ value must be large enough for the program's requirements. If assertions are on, we will assert on not exceeding this, otherwise, it will fail silently. +Default value: 64*1024 + .. _malloc: MALLOC @@ -110,7 +134,7 @@ What malloc()/free() to use, out of: - emmalloc-verbose - use emmalloc with assertions + verbose logging. - emmalloc-memvalidate-verbose - use emmalloc with assertions + heap consistency checking + verbose logging. - - mimalloc - a powerful mulithreaded allocator. This is recommended in + - mimalloc - a powerful multithreaded allocator. This is recommended in large applications that have malloc() contention, but it is larger and uses more memory. - none - no malloc() implementation is provided, but you must implement @@ -125,6 +149,8 @@ is usually worth the extra size. dlmalloc is also a good choice if you want the extra security checks it does (such as noticing metadata corruption in its internal data structures, which emmalloc does not do). +Default value: "dlmalloc" + .. _aborting_malloc: ABORTING_MALLOC @@ -148,9 +174,12 @@ But if you do specify it, it will have an effect now, which it did not previously. If you don't want that, just stop passing it in at link time. Note that this setting does not affect the behavior of operator new in C++. -This function will always abort on allocation failure if exceptions are disabled. +This function will always abort on allocation failure if exceptions are +disabled. If you want new to return 0 on failure, use it with std::nothrow. +Default value: true + .. _initial_heap: INITIAL_HEAP @@ -160,10 +189,12 @@ The initial amount of heap memory available to the program. This is the memory region available for dynamic allocations via `sbrk`, `malloc` and `new`. Unlike INITIAL_MEMORY, this setting allows the static and dynamic regions of -your programs memory to independently grow. In most cases we recommend using +your program's memory to independently grow. In most cases we recommend using this setting rather than `INITIAL_MEMORY`. However, this setting does not work for imported memories (e.g. when dynamic linking is used). +Default value: 16777216 + .. _initial_memory: INITIAL_MEMORY @@ -180,6 +211,8 @@ as well the size of static data in input modules. (This option was formerly called TOTAL_MEMORY.) +Default value: -1 + .. _maximum_memory: MAXIMUM_MEMORY @@ -205,6 +238,8 @@ To use more than 2GB, set this to something higher, like 4GB. (This option was formerly called WASM_MEM_MAX and BINARYEN_MEM_MAX.) +Default value: 2147483648 + .. _allow_memory_growth: ALLOW_MEMORY_GROWTH @@ -223,6 +258,8 @@ ALLOW_MEMORY_GROWTH enables fully standard behavior, of both malloc returning 0 when it fails, and also of being able to allocate more memory from the system as necessary. +Default value: false + .. _memory_growth_geometric_step: MEMORY_GROWTH_GEOMETRIC_STEP @@ -237,6 +274,8 @@ to reduce performance hiccups coming from memory resize, and the smaller this value is, the more memory is conserved, at the performance of more stuttering when the heap grows. (profiled to be on the order of ~20 msecs) +Default value: 0.20 + .. _memory_growth_geometric_cap: MEMORY_GROWTH_GEOMETRIC_CAP @@ -247,6 +286,8 @@ this value to constrain the geometric grow to not exceed a specific rate. Pass MEMORY_GROWTH_GEOMETRIC_CAP=0 to disable the cap and allow unbounded size increases. +Default value: 96*1024*1024 + .. _memory_growth_linear_step: MEMORY_GROWTH_LINEAR_STEP @@ -259,6 +300,8 @@ replace geometric overgrowth rate with a constant growth step size. When MEMORY_GROWTH_LINEAR_STEP is used, the variables MEMORY_GROWTH_GEOMETRIC_STEP and MEMORY_GROWTH_GEOMETRIC_CAP are ignored. +Default value: -1 + .. _memory64: MEMORY64 @@ -272,14 +315,14 @@ Assumes WASM_BIGINT. .. note:: Applicable during both linking and compilation -.. note:: This is an experimental setting +Default value: 0 .. _initial_table: INITIAL_TABLE ============= -Sets the initial size of the table when MAIN_MODULE or SIDE_MODULE is use +Sets the initial size of the table when MAIN_MODULE or SIDE_MODULE is used (and not otherwise). Normally Emscripten can determine the size of the table at link time, but in SPLIT_MODULE mode, wasm-split often needs to grow the table, so the table size baked into the JS for the instrumented build will be @@ -288,6 +331,8 @@ a large enough table size that can be consistent across both builds. This setting may be removed at any time and should not be used except in conjunction with SPLIT_MODULE and dynamic linking. +Default value: -1 + .. _allow_table_growth: ALLOW_TABLE_GROWTH @@ -296,23 +341,29 @@ ALLOW_TABLE_GROWTH If true, allows more functions to be added to the table at runtime. This is necessary for dynamic linking, and set automatically in that mode. +Default value: false + .. _global_base: GLOBAL_BASE =========== Where global data begins; the start of static memory. -A GLOBAL_BASE of 1024 or above is useful for optimizing load/store offsets, as it -enables the --low-memory-unused pass +A GLOBAL_BASE of 1024 or above is useful for optimizing load/store offsets, +as it enables the --low-memory-unused pass + +Default value: 1024 .. _table_base: TABLE_BASE ========== -Where where table slots (function addresses) are allocated. +Where table slots (function addresses) are allocated. This must be at least 1 to reserve the zero slot for the null pointer. +Default value: 1 + .. _use_closure_compiler: USE_CLOSURE_COMPILER @@ -320,6 +371,8 @@ USE_CLOSURE_COMPILER Whether closure compiling is being run on this output +Default value: false + .. _closure_warnings: CLOSURE_WARNINGS @@ -333,6 +386,8 @@ errors, similar to -Werror compiler flag. .. note:: This setting is deprecated +Default value: 'quiet' + .. _ignore_closure_compiler_errors: IGNORE_CLOSURE_COMPILER_ERRORS @@ -340,6 +395,8 @@ IGNORE_CLOSURE_COMPILER_ERRORS Ignore closure warnings and errors (like on duplicate definitions) +Default value: false + .. _declare_asm_module_exports: DECLARE_ASM_MODULE_EXPORTS @@ -356,6 +413,8 @@ modifications of the global scope can confuse external JS minifier tools, and also things can break if the scope the code is in is not the global scope (e.g. if you manually enclose them in a function scope). +Default value: true + .. _inlining_limit: INLINING_LIMIT @@ -366,16 +425,22 @@ This does not affect the inlining policy in Binaryen. .. note:: Only applicable during compilation +Default value: false + .. _support_big_endian: SUPPORT_BIG_ENDIAN ================== -If set to 1, perform acorn pass that converts each HEAP access into a +If set to 1, perform an acorn pass that converts each HEAP access into a function call that uses DataView to enforce LE byte order for HEAP buffer; This makes generated JavaScript run on BE as well as LE machines. (If 0, only LE systems are supported). Does not affect generated wasm. +.. note:: This is an experimental setting + +Default value: false + .. _safe_heap: SAFE_HEAP @@ -390,6 +455,8 @@ Wasm-only builds allow unaligned memory accesses. Note, however, that on some architectures unaligned accesses can be very slow, so it is still a good idea to verify your code with the more strict mode 1) +Default value: 0 + .. _safe_heap_log: SAFE_HEAP_LOG @@ -397,6 +464,8 @@ SAFE_HEAP_LOG Log out all SAFE_HEAP operations +Default value: false + .. _emulate_function_pointer_casts: EMULATE_FUNCTION_POINTER_CASTS @@ -404,14 +473,16 @@ EMULATE_FUNCTION_POINTER_CASTS Allows function pointers to be cast, wraps each call of an incorrect type with a runtime correction. This adds overhead and should not be used -normally. It also forces ALIASING_FUNCTION_POINTERS to 0. Aside from making -calls not fail, this tries to convert values as best it can. -We use 64 bits (i64) to represent values, as if we wrote the sent value to -memory and loaded the received type from the same memory (using +normally. Aside from making calls not fail, this tries to convert values as +best it can. We use 64 bits (i64) to represent values, as if we wrote the +sent value to memory and loaded the received type from the same memory (using truncs/extends/ reinterprets). This means that when types do not match the emulated values may not match (this is true of native too, for that matter - -this is all undefined behavior). This approaches appears good enough to -support Python, which is the main use case motivating this feature. +this is all undefined behavior). This approach appears good enough to +support Python (the original motivation for this feature) and Glib (the +continued motivation). + +Default value: false .. _exception_debug: @@ -420,14 +491,7 @@ EXCEPTION_DEBUG Print out exceptions in emscriptened code. -.. _demangle_support: - -DEMANGLE_SUPPORT -================ - -If 1, export `demangle` and `stackTrace` JS library functions. - -.. note:: This setting is deprecated +Default value: false .. _library_debug: @@ -440,6 +504,8 @@ it back. A simple way to set it in C++ is:: emscripten_run_script("runtimeDebug = ...;"); +Default value: false + .. _syscall_debug: SYSCALL_DEBUG @@ -447,9 +513,11 @@ SYSCALL_DEBUG Print out all musl syscalls, including translating their numeric index to the string name, which can be convenient for debugging. (Other system -calls are not numbered and already have clear names; use LIBRARY_DEBUG +calls are not numbered and already have clear names; use :ref:`LIBRARY_DEBUG` to get logging for all of them.) +Default value: false + .. _socket_debug: SOCKET_DEBUG @@ -457,6 +525,8 @@ SOCKET_DEBUG Log out socket/network data transfer. +Default value: false + .. _dylink_debug: DYLINK_DEBUG @@ -464,6 +534,8 @@ DYLINK_DEBUG Log dynamic linker information +Default value: 0 + .. _fs_debug: FS_DEBUG @@ -471,6 +543,8 @@ FS_DEBUG Register file system callbacks using trackingDelegate in library_fs.js +Default value: false + .. _socket_webrtc: SOCKET_WEBRTC @@ -478,31 +552,37 @@ SOCKET_WEBRTC As well as being configurable at compile time via the "-s" option the WEBSOCKET_URL and WEBSOCKET_SUBPROTOCOL -settings may configured at run time via the Module object e.g. +settings may be configured at run time via the Module object e.g. Module['websocket'] = {subprotocol: 'base64, binary, text'}; Module['websocket'] = {url: 'wss://', subprotocol: 'base64'}; -You can set 'subprotocol' to null, if you don't want to specify it +You can set 'subprotocol' to null, if you don't want to specify it. Run time configuration may be useful as it lets an application select multiple different services. +Default value: false + .. _websocket_url: WEBSOCKET_URL ============= -A string containing either a WebSocket URL prefix (ws:// or wss://) or a complete -RFC 6455 URL - "ws[s]:" "//" host [ ":" port ] path [ "?" query ]. -In the (default) case of only a prefix being specified the URL will be constructed from -prefix + addr + ':' + port +A string containing either a WebSocket URL prefix (ws:// or wss://) or a +complete RFC 6455 URL - "ws[s]:" "//" host [ ":" port ] path [ "?" query ]. +In the (default) case of only a prefix being specified the URL will be +constructed from prefix + addr + ':' + port where addr and port are derived from the socket connect/bind/accept calls. +Default value: 'ws://' + .. _proxy_posix_sockets: PROXY_POSIX_SOCKETS =================== -If 1, the POSIX sockets API uses a native bridge process server to proxy sockets calls -from browser to native world. +If 1, the POSIX sockets API uses a native bridge process server to proxy +sockets calls from browser to native world. + +Default value: false .. _websocket_subprotocol: @@ -513,6 +593,8 @@ A string containing a comma separated list of WebSocket subprotocols as would be present in the Sec-WebSocket-Protocol header. You can set 'null', if you don't want to specify it. +Default value: 'binary' + .. _openal_debug: OPENAL_DEBUG @@ -520,6 +602,8 @@ OPENAL_DEBUG Print out debugging information from our OpenAL implementation. +Default value: false + .. _websocket_debug: WEBSOCKET_DEBUG @@ -529,6 +613,8 @@ If 1, prints out debugging related to calls from ``emscripten_web_socket_*`` functions in ``emscripten/websocket.h``. If 2, additionally traces bytes communicated via the sockets. +Default value: false + .. _gl_assertions: GL_ASSERTIONS @@ -537,6 +623,8 @@ GL_ASSERTIONS Adds extra checks for error situations in the GL library. Can impact performance. +Default value: false + .. _trace_webgl_calls: TRACE_WEBGL_CALLS @@ -544,6 +632,8 @@ TRACE_WEBGL_CALLS If enabled, prints out all API calls to WebGL contexts. (*very* verbose) +Default value: false + .. _gl_debug: GL_DEBUG @@ -552,6 +642,8 @@ GL_DEBUG Enables more verbose debug printing of WebGL related operations. As with LIBRARY_DEBUG, this is toggleable at runtime with option GL.debug. +Default value: false + .. _gl_testing: GL_TESTING @@ -560,6 +652,8 @@ GL_TESTING When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead) +Default value: false + .. _gl_max_temp_buffer_size: GL_MAX_TEMP_BUFFER_SIZE @@ -567,6 +661,8 @@ GL_MAX_TEMP_BUFFER_SIZE How large GL emulation temp buffers are +Default value: 2097152 + .. _gl_unsafe_opts: GL_UNSAFE_OPTS @@ -574,6 +670,8 @@ GL_UNSAFE_OPTS Enables some potentially-unsafe optimizations in GL emulation code +Default value: true + .. _full_es2: FULL_ES2 @@ -581,6 +679,8 @@ FULL_ES2 Forces support for all GLES2 features, not just the WebGL-friendly subset. +Default value: false + .. _gl_emulate_gles_version_string_format: GL_EMULATE_GLES_VERSION_STRING_FORMAT @@ -594,15 +694,19 @@ version strings (at the expense of a little bit of added code size), and to false to make GL contexts appear like WebGL contexts and to save some bytes from the output. +Default value: true + .. _gl_extensions_in_prefixed_format: GL_EXTENSIONS_IN_PREFIXED_FORMAT ================================ -If true, all GL extensions are advertised in both unprefixed WebGL extension +If true, all GL extensions are advertised in unprefixed WebGL extension format, but also in desktop/mobile GLES/GL extension format with ``GL_`` prefix. +Default value: true + .. _gl_support_automatic_enable_extensions: GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS @@ -612,31 +716,38 @@ If true, adds support for automatically enabling all GL extensions for GLES/GL emulation purposes. This takes up code size. If you set this to 0, you will need to manually enable the extensions you need. +Default value: true + .. _gl_support_simple_enable_extensions: GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS =================================== -If true, the function ``emscripten_webgl_enable_extension()`` can be called to -enable any WebGL extension. If false, to save code size, -``emscripten_webgl_enable_extension()`` cannot be called to enable any of extensions -'ANGLE_instanced_arrays', 'OES_vertex_array_object', 'WEBGL_draw_buffers', -'WEBGL_multi_draw', 'WEBGL_draw_instanced_base_vertex_base_instance', -or 'WEBGL_multi_draw_instanced_base_vertex_base_instance', +If true, the function ``emscripten_webgl_enable_extension()`` can be called +to enable any WebGL extension. If false, to save code size, +``emscripten_webgl_enable_extension()`` cannot be called to enable any of +extensions 'ANGLE_instanced_arrays', 'OES_vertex_array_object', +'WEBGL_draw_buffers', 'WEBGL_multi_draw', +'WEBGL_draw_instanced_base_vertex_base_instance', or +'WEBGL_multi_draw_instanced_base_vertex_base_instance', but the dedicated functions ``emscripten_webgl_enable_*()`` found in html5.h are used to enable each of those extensions. This way code size is increased only for the extensions that are actually used. N.B. if setting this to 0, GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS must be set to zero as well. +Default value: true + .. _gl_track_errors: GL_TRACK_ERRORS =============== -If set to 0, Emscripten GLES2->WebGL translation layer does not track the kind -of GL errors that exist in GLES2 but do not exist in WebGL. Settings this to 0 -saves code size. (Good to keep at 1 for development) +If set to 0, Emscripten GLES2->WebGL translation layer does not track the +kind of GL errors that exist in GLES2 but do not exist in WebGL. Setting +this to 0 saves code size. (Good to keep at 1 for development) + +Default value: true .. _gl_support_explicit_swap_control: @@ -646,6 +757,8 @@ GL_SUPPORT_EXPLICIT_SWAP_CONTROL If true, GL contexts support the explicitSwapControl context creation flag. Set to 0 to save a little bit of space on projects that do not need it. +Default value: false + .. _gl_pool_temp_buffers: GL_POOL_TEMP_BUFFERS @@ -658,6 +771,8 @@ GL library a little bit, at the expense of generating garbage in WebGL 1. If you are only using WebGL 2 and do not support WebGL 1, this is not needed and you can turn it off. +Default value: true + .. _gl_explicit_uniform_location: GL_EXPLICIT_UNIFORM_LOCATION @@ -666,6 +781,8 @@ GL_EXPLICIT_UNIFORM_LOCATION If true, enables support for the EMSCRIPTEN_explicit_uniform_location WebGL extension. See docs/EMSCRIPTEN_explicit_uniform_location.txt +Default value: false + .. _gl_explicit_uniform_binding: GL_EXPLICIT_UNIFORM_BINDING @@ -674,6 +791,8 @@ GL_EXPLICIT_UNIFORM_BINDING If true, enables support for the EMSCRIPTEN_uniform_layout_binding WebGL extension. See docs/EMSCRIPTEN_explicit_uniform_binding.txt +Default value: false + .. _use_webgl2: USE_WEBGL2 @@ -681,6 +800,8 @@ USE_WEBGL2 Deprecated. Pass -sMAX_WEBGL_VERSION=2 to target WebGL 2.0. +Default value: false + .. _min_webgl_version: MIN_WEBGL_VERSION @@ -690,6 +811,8 @@ Specifies the lowest WebGL version to target. Pass -sMIN_WEBGL_VERSION=1 to enable targeting WebGL 1, and -sMIN_WEBGL_VERSION=2 to drop support for WebGL 1.0 +Default value: 1 + .. _max_webgl_version: MAX_WEBGL_VERSION @@ -704,6 +827,8 @@ support, as that may not always be what the application wants. If you want such a fallback, you can try to create a context with WebGL2, and if that fails try to create one with WebGL1. +Default value: 1 + .. _webgl2_backwards_compatibility_emulation: WEBGL2_BACKWARDS_COMPATIBILITY_EMULATION @@ -713,9 +838,11 @@ If true, emulates some WebGL 1 features on WebGL 2 contexts, meaning that applications that use WebGL 1/GLES 2 can initialize a WebGL 2/GLES3 context, but still keep using WebGL1/GLES 2 functionality that no longer is supported in WebGL2/GLES3. Currently this emulates GL_EXT_shader_texture_lod extension -in GLSLES 1.00 shaders, support for unsized internal texture formats, and the +in GLSL ES 1.00 shaders, support for unsized internal texture formats, and the GL_HALF_FLOAT_OES != GL_HALF_FLOAT mixup. +Default value: false + .. _full_es3: FULL_ES3 @@ -724,6 +851,8 @@ FULL_ES3 Forces support for all GLES3 features, not just the WebGL2-friendly subset. This automatically turns on FULL_ES2 and WebGL2 support. +Default value: false + .. _legacy_gl_emulation: LEGACY_GL_EMULATION @@ -733,6 +862,8 @@ Includes code to emulate various desktop GL features. Incomplete but useful in some cases, see http://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html +Default value: false + .. _gl_ffp_only: GL_FFP_ONLY @@ -744,6 +875,8 @@ that it can perform extra optimizations by knowing that the user code does not use shaders at all. If LEGACY_GL_EMULATION = 0, this setting has no effect. +Default value: false + .. _gl_preinitialized_context: GL_PREINITIALIZED_CONTEXT @@ -753,12 +886,7 @@ If you want to create the WebGL context up front in JS code, set this to 1 and set Module['preinitializedWebGLContext'] to a precreated WebGL context. WebGL initialization afterwards will use this GL context to render. -.. _use_webgpu: - -USE_WEBGPU -========== - -Enables support for WebGPU (via "webgpu/webgpu.h"). +Default value: false .. _stb_image: @@ -772,6 +900,8 @@ will not be as fast as the browser itself. When enabled, stb-image will be used automatically from IMG_Load and IMG_Load_RW. You can also call the ``stbi_*`` functions directly yourself. +Default value: false + .. _gl_disable_half_float_extension_if_broken: GL_DISABLE_HALF_FLOAT_EXTENSION_IF_BROKEN @@ -779,9 +909,11 @@ GL_DISABLE_HALF_FLOAT_EXTENSION_IF_BROKEN From Safari 8 (where WebGL was introduced to Safari) onwards, OES_texture_half_float and OES_texture_half_float_linear extensions are broken and do not function correctly, when used as source textures. -See https://bugs.webkit.org/show_bug.cgi?id=183321, https://bugs.webkit.org/show_bug.cgi?id=169999, +See https://webkit.org/b/183321, https://webkit.org/b/169999, https://stackoverflow.com/questions/54248633/cannot-create-half-float-oes-texture-from-uint16array-on-ipad +Default value: false + .. _gl_workaround_safari_getcontext_bug: GL_WORKAROUND_SAFARI_GETCONTEXT_BUG @@ -789,10 +921,12 @@ GL_WORKAROUND_SAFARI_GETCONTEXT_BUG Workaround Safari WebGL issue: After successfully acquiring WebGL context on a canvas, calling .getContext() will always return that context independent of which 'webgl' or 'webgl2' -context version was passed. See https://bugs.webkit.org/show_bug.cgi?id=222758 and +context version was passed. See https://webkit.org/b/222758 and https://github.com/emscripten-core/emscripten/issues/13295. Set this to 0 to force-disable the workaround if you know the issue will not affect you. +Default value: true + .. _gl_enable_get_proc_address: GL_ENABLE_GET_PROC_ADDRESS @@ -802,7 +936,9 @@ If 1, link with support to glGetProcAddress() functionality. In WebGL, glGetProcAddress() causes a substantial code size and performance impact, since WebGL does not natively provide such functionality, and it must be emulated. Using glGetProcAddress() is not recommended. If you still need to use this, e.g. when porting an existing renderer, -you can link with -sGL_ENABLE_GET_PROC_ADDRESS=1 to get support for this functionality. +you can link with -sGL_ENABLE_GET_PROC_ADDRESS to get support for this functionality. + +Default value: true .. _js_math: @@ -814,12 +950,7 @@ compiled musl code. However, it can be significantly slower as it calls out to J also may give different results as JS math is specced somewhat differently than libc, and can also vary between browsers. -.. _polyfill_old_math_functions: - -POLYFILL_OLD_MATH_FUNCTIONS -=========================== - -If set, enables polyfilling for Math.clz32, Math.trunc, Math.imul, Math.fround. +Default value: false .. _legacy_vm_support: @@ -830,27 +961,29 @@ Set this to enable compatibility emulations for old JavaScript engines. This giv the highest possible probability of the code working everywhere, even in rare old browsers and shell environments. Specifically: -- Add polyfilling for Math.clz32, Math.trunc, Math.imul, Math.fround. (-sPOLYFILL_OLD_MATH_FUNCTIONS) - Disable WebAssembly. (Must be paired with -sWASM=0) - Adjusts MIN_X_VERSION settings to 0 to include support for all browser versions. - Avoid TypedArray.fill, if necessary, in zeroMemory utility function. You can also configure the above options individually. +Default value: false + .. _environment: ENVIRONMENT =========== Specify which runtime environments the JS output will be capable of running -in. For maximum portability this can configured to support all environments -or it can be limited to reduce overall code size. The supported environments -are: +in. For maximum portability this can be configured to support all +environments or it can be limited to reduce overall code size. The supported +environments are: - 'web' - the normal web environment. - 'webview' - just like web, but in a webview like Cordova; considered to be same as "web" in almost every place - 'worker' - a web worker environment. +- 'worklet' - Audio Worklet environment. - 'node' - Node.js. - 'shell' - a JS shell like d8, js, or jsc. @@ -858,6 +991,10 @@ This setting can be a comma-separated list of these environments, e.g., "web,worker". If this is the empty string, then all environments are supported. +Certain settings will automatically add to this list. For examble, building +with pthreads will automatically add `worker` and building with +``AUDIO_WORKLET`` will automatically add `worklet`. + Note that the set of environments recognized here is not identical to the ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically: @@ -868,7 +1005,9 @@ ones we identify at runtime using ``ENVIRONMENT_IS_*``. Specifically: at compile time, there is no runtime behavior change. Note that by default we do not include the 'shell' environment since direct -usage of d8, js, jsc is extremely rare. +usage of d8, spidermonkey and jsc is extremely rare. + +Default value: ['web', 'webview', 'worker', 'node'] .. _lz4: @@ -888,6 +1027,8 @@ Limitations: preloadPlugin stuff, etc. - LZ4 files are read-only. +Default value: false + .. _disable_exception_catching: DISABLE_EXCEPTION_CATCHING @@ -907,12 +1048,14 @@ and the program will halt (so this will not introduce silent failures). as it may contain thrown exceptions that are never caught (e.g. just using std::vector can have that). -fno-rtti may help as well. -This option is mutually exclusive with EXCEPTION_CATCHING_ALLOWED. +This option is mutually exclusive with :ref:`EXCEPTION_CATCHING_ALLOWED`. This option only applies to Emscripten (JavaScript-based) exception handling and does not control the native Wasm exception handling. -[compile+link] - affects user code at compile and system libraries at link +.. note:: Applicable during both linking and compilation + +Default value: 1 .. _exception_catching_allowed: @@ -922,12 +1065,14 @@ EXCEPTION_CATCHING_ALLOWED Enables catching exception but only in the listed functions. This option acts like a more precise version of ``DISABLE_EXCEPTION_CATCHING=0``. -This option is mutually exclusive with DISABLE_EXCEPTION_CATCHING. +This option is mutually exclusive with :ref:`DISABLE_EXCEPTION_CATCHING`. This option only applies to Emscripten (JavaScript-based) exception handling and does not control the native Wasm exception handling. -[compile+link] - affects user code at compile and system libraries at link +.. note:: Applicable during both linking and compilation + +Default value: [] .. _disable_exception_throwing: @@ -944,20 +1089,22 @@ time, then you will get errors on undefined symbols, as the exception throwing code is not linked in. If so you should either unset the option (if you do want exceptions) or fix the compilation of the source files so that indeed no exceptions are used). -TODO(sbc): Move to settings_internal (current blocked due to use in test -code). This option only applies to Emscripten (JavaScript-based) exception handling and does not control the native Wasm exception handling. +.. note:: Applicable during both linking and compilation + +Default value: false + .. _export_exception_handling_helpers: EXPORT_EXCEPTION_HANDLING_HELPERS ================================= Make the exception message printing function, 'getExceptionMessage' available -in the JS library for use, by adding necessary symbols to EXPORTED_FUNCTIONS -and DEFAULT_LIBRARY_FUNCS_TO_INCLUDE. +in the JS library for use, by adding necessary symbols to +:ref:`EXPORTED_FUNCTIONS`. This works with both Emscripten EH and Wasm EH. When you catch an exception from JS, that gives you a user-thrown value in case of Emscripten EH, and a @@ -974,16 +1121,18 @@ the JS library: Given an WebAssembly.Exception object, returns the actual user-thrown C++ object address in Wasm memory. -Setting this option also adds refcount increasing and decreasing functions -('incrementExceptionRefcount' and 'decrementExceptionRefcount') in the JS -library because if you catch an exception from JS, you may need to manipulate -the refcount manually not to leak memory. What you need to do is different -depending on the kind of EH you use -(https://github.com/emscripten-core/emscripten/issues/17115). +Setting this option also adds refcount incrementing and decrementing +functions ('incrementExceptionRefcount' and 'decrementExceptionRefcount') in +the JS library because if you catch an exception from JS, you may need to +manipulate the refcount manually to avoid memory leaks. See test_EXPORT_EXCEPTION_HANDLING_HELPERS in test/test_core.py for an example usage. +.. note:: This setting is deprecated + +Default value: false + .. _exception_stack_traces: EXCEPTION_STACK_TRACES @@ -993,34 +1142,23 @@ When this is enabled, exceptions will contain stack traces and uncaught exceptions will display stack traces upon exiting. This defaults to true when ASSERTIONS is enabled. This option is for users who want exceptions' stack traces but do not want other overheads ASSERTIONS can incur. -This option implies EXPORT_EXCEPTION_HANDLING_HELPERS. +This option implies :ref:`EXPORT_EXCEPTION_HANDLING_HELPERS`. -.. _nodejs_catch_exit: +Default value: false -NODEJS_CATCH_EXIT -================= - -Emscripten throws an ExitStatus exception to unwind when exit() is called. -Without this setting enabled this can show up as a top level unhandled -exception. +.. _wasm_legacy_exceptions: -With this setting enabled a global uncaughtException handler is used to -catch and handle ExitStatus exceptions. However, this means all other -uncaught exceptions are also caught and re-thrown, which is not always -desirable. +WASM_LEGACY_EXCEPTIONS +====================== -.. _nodejs_catch_rejection: +If true, emit instructions for the legacy Wasm exception handling proposal: +https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md +If false, emit instructions for the standardized exception handling proposal: +https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md -NODEJS_CATCH_REJECTION -====================== +.. note:: Applicable during both linking and compilation -Catch unhandled rejections in node. This only effect versions of node older -than 15. Without this, old version node will print a warning, but exit -with a zero return code. With this setting enabled, we handle any unhandled -rejection and throw an exception, which will cause the process exit -immediately with a non-0 return code. -This not needed in Node 15+ so this setting will default to false if -MIN_NODE_VERSION is 150000 or above. +Default value: true .. _asyncify: @@ -1030,38 +1168,39 @@ ASYNCIFY Whether to support async operations in the compiled code. This makes it possible to call JS functions from synchronous-looking code in C/C++. -- Run binaryen's Asyncify pass to transform the code using asyncify. This - emits a normal wasm file in the end, so it works everywhere, but it has a - significant cost in terms of code size and speed. +- 1 (default): Run binaryen's Asyncify pass to transform the code using + asyncify. This emits a normal wasm file in the end, so it works everywhere, + but it has a significant cost in terms of code size and speed. See https://emscripten.org/docs/porting/asyncify.html -- Depend on VM support for the wasm stack switching proposal. This allows - async operations to happen without the overhead of modifying the wasm. - This is experimental atm while spec discussion is ongoing, see - https://github.com/WebAssembly/js-promise-integration/ - TODO: document which of the following flags are still relevant in this - mode (e.g. IGNORE_INDIRECT etc. are not needed) +- 2 (deprecated): Use ``-sJSPI`` instead. + +Default value: 0 .. _asyncify_imports: ASYNCIFY_IMPORTS ================ -Imports which can do an sync operation, in addition to the default ones that +Imports which can do an async operation, in addition to the default ones that emscripten defines like emscripten_sleep. If you add more you will need to -mention them to here, or else they will not work (in ASSERTIONS builds an -error will be shown). +mention them to here, or else they will not work (in :ref:`ASSERTIONS` builds +an error will be shown). Note that this list used to contain the default ones, which meant that you had to list them when adding your own; the default ones are now added automatically. +Default value: [] + .. _asyncify_ignore_indirect: ASYNCIFY_IGNORE_INDIRECT ======================== Whether indirect calls can be on the stack during an unwind/rewind. -If you know they cannot, then setting this can be extremely helpful, as otherwise asyncify -must assume an indirect call can reach almost everywhere. +If you know they cannot, then setting this can be extremely helpful, as +otherwise asyncify must assume an indirect call can reach almost everywhere. + +Default value: false .. _asyncify_stack_size: @@ -1069,9 +1208,11 @@ ASYNCIFY_STACK_SIZE =================== The size of the asyncify stack - the region used to store unwind/rewind -info. This must be large enough to store the call stack and locals. If it is too -small, you will see a wasm trap due to executing an "unreachable" instruction. -In that case, you should increase this size. +info. This must be large enough to store the call stack and locals. If it is +too small, you will see a wasm trap due to executing an "unreachable" +instruction. In that case, you should increase this size. + +Default value: 4096 .. _asyncify_remove: @@ -1090,8 +1231,8 @@ and size. The names in this list are names from the WebAssembly Names section. The wasm backend will emit those names in *human-readable* form instead of -typical C++ mangling. For example, you should write Struct::func() -instead of _ZN6Struct4FuncEv. C is also different from C++, as C +typical C++ mangling. For example, you should write ``Struct::func()`` +instead of ``_ZN6Struct4FuncEv``. C is also different from C++, as C names don't end with parameters; as a result foo(int) in C++ would appear as just foo in C (C++ has parameters because it needs to differentiate overloaded functions). You will see warnings in the console if a name in the @@ -1116,6 +1257,8 @@ Note: Whitespace is part of the function signature! I.e. "foo(char const *, int &)" will not match "foo(char const*, int&)", and neither would "foo(const char*, int &)". +Default value: [] + .. _asyncify_add: ASYNCIFY_ADD @@ -1128,9 +1271,22 @@ in the safest way possible, this is only useful if you use IGNORE_INDIRECT and use this list to fix up some indirect calls that *do* need to be instrumented. -See notes on ASYNCIFY_REMOVE about the names, including wildcard matching and +See :ref:`ASYNCIFY_REMOVE` about the names, including wildcard matching and character substitutions. +Default value: [] + +.. _asyncify_propagate_add: + +ASYNCIFY_PROPAGATE_ADD +====================== + +If enabled, instrumentation status will be propagated from the add-list, ie. +their callers, and their callers' callers, and so on. If disabled then all +callers must be manually added to the add-list (like the only-list). + +Default value: true + .. _asyncify_only: ASYNCIFY_ONLY @@ -1140,9 +1296,11 @@ If the Asyncify only-list is provided, then *only* the functions in the list will be instrumented. Like the remove-list, getting this wrong will break your application. -See notes on ASYNCIFY_REMOVE about the names, including wildcard matching and +See :ref:`ASYNCIFY_REMOVE` about the names, including wildcard matching and character substitutions. +Default value: [] + .. _asyncify_advise: ASYNCIFY_ADVISE @@ -1150,13 +1308,7 @@ ASYNCIFY_ADVISE If enabled will output which functions have been instrumented and why. -.. _asyncify_lazy_load_code: - -ASYNCIFY_LAZY_LOAD_CODE -======================= - -Allows lazy code loading: where emscripten_lazy_load_code() is written, we -will pause execution, load the rest of the code, and then resume. +Default value: false .. _asyncify_debug: @@ -1168,14 +1320,60 @@ Runtime debug logging from asyncify internals. - 1: Minimal logging. - 2: Verbose logging. +Default value: 0 + .. _asyncify_exports: ASYNCIFY_EXPORTS ================ -Specify which of the exports will have JSPI applied to them and return a -promise. -Only supported for ASYNCIFY==2 mode. +Deprecated, use JSPI_EXPORTS instead. + +.. note:: This setting is deprecated + +Default value: [] + +.. _jspi: + +JSPI +==== + +Use VM support for the JavaScript Promise Integration proposal. This allows +async operations to happen without the overhead of modifying the wasm. This +is experimental at the moment while spec discussion is ongoing, see +https://github.com/WebAssembly/js-promise-integration/ TODO: document which +of the following flags are still relevant in this mode (e.g. IGNORE_INDIRECT +etc. are not needed) + +Default value: 0 + +.. _jspi_exports: + +JSPI_EXPORTS +============ + +A list of exported module functions that will be asynchronous. Each export +will return a ``Promise`` that will be resolved with the result. Any exports +that will call an asynchronous import (listed in ``JSPI_IMPORTS``) must be +included here. + +By default this includes ``main``. + +Default value: [] + +.. _jspi_imports: + +JSPI_IMPORTS +============ + +A list of imported module functions that will potentially do asynchronous +work. The imported function should return a ``Promise`` when doing +asynchronous work. + +Note when using JS library files, the function can be marked with +``_async:: true`` in the library instead of this setting. + +Default value: [] .. _exported_runtime_methods: @@ -1189,14 +1387,7 @@ Note that the name may be slightly misleading, as this is for any JS library element, and not just methods. For example, we can export the FS object by having "FS" in this list. -.. _extra_exported_runtime_methods: - -EXTRA_EXPORTED_RUNTIME_METHODS -============================== - -Deprecated, use EXPORTED_RUNTIME_METHODS instead. - -.. note:: This setting is deprecated +Default value: [] .. _incoming_module_js_api: @@ -1213,8 +1404,8 @@ you have this:: preRun: [() => console.log('pre run')] }; -Then MODULE_JS_API must contain 'print' and 'preRun'; if it does not then -we may not emit code to read and use that value. In other words, this +Then INCOMING_MODULE_JS_API must contain 'print' and 'preRun'; if it does not +then we may not emit code to read and use that value. In other words, this option lets you set, statically at compile time, the list of which Module JS values you will be providing at runtime, so the compiler can better optimize. @@ -1225,15 +1416,19 @@ list contains a set of commonly used symbols. FIXME: should this just be 0 if we want everything? +Default value: (multi-line value, see settings.js) + .. _case_insensitive_fs: CASE_INSENSITIVE_FS =================== -If set to nonzero, the provided virtual filesystem if treated +If set to nonzero, the provided virtual filesystem is treated case-insensitive, like Windows and macOS do. If set to 0, the VFS is case-sensitive, like on Linux. +Default value: false + .. _filesystem: FILESYSTEM @@ -1247,6 +1442,8 @@ automatically set this if it detects that syscall usage (which is static) does not require a full filesystem. If you still want filesystem support, use FORCE_FILESYSTEM +Default value: true + .. _force_filesystem: FORCE_FILESYSTEM @@ -1256,12 +1453,14 @@ Makes full filesystem support be included, even if statically it looks like it is not used. For example, if your C code uses no files, but you include some JS that does, you might need this. +Default value: false + .. _noderawfs: NODERAWFS ========= -Enables support for the NODERAWFS filesystem backend. This is a special +Enables support for the ``NODERAWFS`` filesystem backend. This is a special backend as it replaces all normal filesystem access with direct Node.js operations, without the need to do ``FS.mount()``, and this backend only works with Node.js. The initial working directory will be same as @@ -1271,6 +1470,22 @@ necessarily be portable between OSes - it will be as portable as a Node.js program would be, which means that differences in how the underlying OS handles permissions and errors and so forth may be noticeable. +Enabling this setting will also enable :ref:`NODE_HOST_ENV` by default. + +Default value: false + +.. _node_host_env: + +NODE_HOST_ENV +============= + +When running under Node, expose the underlying OS environment variables. +This is similar to how ``NODERAWFS`` exposes the underlying FS. +This setting gets enabled by default when ``NODERAWFS`` is enabled, but can +also be controlled separately. + +Default value: false + .. _node_code_caching: NODE_CODE_CACHING @@ -1292,19 +1507,26 @@ to load ok, but we do actually recompile). as mentioned earlier. If that is in a read-only directory, you may need to place them elsewhere. You can use the locateFile() hook to do so. +Default value: false + .. _exported_functions: EXPORTED_FUNCTIONS ================== -Functions that are explicitly exported. These functions are kept alive -through LLVM dead code elimination, and also made accessible outside of the -generated code even after running closure compiler (on "Module"). The +Symbols that are explicitly exported. These symbols are kept alive through +LLVM dead code elimination, and also made accessible outside of the +generated code even after running closure compiler (on "Module"). Native symbols listed here require an ``_`` prefix. By default if this setting is not specified on the command line the -``_main`` function will be implicitly exported. In STANDALONE_WASM mode the -default export is ``__start`` (or ``__initialize`` if --no-entry is specified). +``_main`` function will be implicitly exported. In :ref:`STANDALONE_WASM` +mode the default export is ``__start`` (or ``__initialize`` if ``--no-entry`` +is specified). + +JS Library symbols can also be added to this list (without the leading `$`). + +Default value: [] .. _export_all: @@ -1318,6 +1540,8 @@ prevent DCE or cause anything to be included in linking. It only does for all X that end up in the JS file. This is useful to export the JS library functions on Module, for things like dynamic linking. +Default value: false + .. _export_keepalive: EXPORT_KEEPALIVE @@ -1327,6 +1551,8 @@ If true, we export the symbols that are present in JS onto the Module object. It only does ``Module['X'] = X;`` +Default value: true + .. _retain_compiler_settings: RETAIN_COMPILER_SETTINGS @@ -1336,19 +1562,26 @@ Remembers the values of these settings, and makes them accessible through getCompilerSetting and emscripten_get_compiler_setting. To see what is retained, look for compilerSettings in the generated code. +Default value: false + .. _default_library_funcs_to_include: DEFAULT_LIBRARY_FUNCS_TO_INCLUDE ================================ JS library elements (C functions implemented in JS) that we include by -default. If you want to make sure something is included by the JS compiler, +default. If you want to make sure something is included by the JS compiler, add it here. For example, if you do not use some ``emscripten_*`` C API call -from C, but you want to call it from JS, add it here (and in EXPORTED -FUNCTIONS with prefix "_", if you use closure compiler). Note that the name -may be slightly misleading, as this is for any JS library element, and not -just functions. For example, you can include the Browser object by adding -"$Browser" to this list. +from C, but you want to call it from JS, add it here. +Note that the name may be slightly misleading, as this is for any JS +library element, and not just functions. For example, you can include the +Browser object by adding "$Browser" to this list. + +If you want to both include and export a JS library symbol, it is enough to +simply add it to EXPORTED_FUNCTIONS, without also adding it to +DEFAULT_LIBRARY_FUNCS_TO_INCLUDE. + +Default value: [] .. _include_full_library: @@ -1364,16 +1597,7 @@ include all needed C libraries. For example, if a module uses malloc or new, you will need to use those in the main file too to pull in malloc for use by the module. -.. _relocatable: - -RELOCATABLE -=========== - -If set to 1, we emit relocatable code from the LLVM backend; both -globals and function pointers are all offset (by gb and fp, respectively) -Automatically set for SIDE_MODULE or MAIN_MODULE. - -.. note:: Applicable during both linking and compilation +Default value: false .. _main_module: @@ -1390,6 +1614,8 @@ a side module at runtime. .. note:: Applicable during both linking and compilation +Default value: 0 + .. _side_module: SIDE_MODULE @@ -1399,6 +1625,8 @@ Corresponds to MAIN_MODULE (also supports modes 1 and 2) .. note:: Applicable during both linking and compilation +Default value: 0 + .. _runtime_linked_libs: RUNTIME_LINKED_LIBS @@ -1408,6 +1636,8 @@ Deprecated, list shared libraries directly on the command line instead. .. note:: This setting is deprecated +Default value: [] + .. _build_as_worker: BUILD_AS_WORKER @@ -1416,22 +1646,7 @@ BUILD_AS_WORKER If set to 1, this is a worker library, a special kind of library that is run in a worker. See emscripten.h -.. _proxy_to_worker: - -PROXY_TO_WORKER -=============== - -If set to 1, we build the project into a js file that will run in a worker, -and generate an html file that proxies input and output to/from it. - -.. _proxy_to_worker_filename: - -PROXY_TO_WORKER_FILENAME -======================== - -If set, the script file name the main thread loads. Useful if your project -doesn't run the main emscripten- generated script immediately but does some -setup before +Default value: false .. _proxy_to_pthread: @@ -1454,6 +1669,8 @@ is enabled. This has to happen because this is the only chance - this browser main thread does the only pthread_create call that happens on that thread, so it's the only chance to transfer the canvas from there. +Default value: false + .. _linkable: LINKABLE @@ -1470,6 +1687,12 @@ to set this explicitly. Note that MAIN_MODULE and SIDE_MODULE mode 2 do *not* set this, so that we still do normal DCE on them, and in that case you must keep relevant things alive yourself using exporting. +.. note:: Applicable during both linking and compilation + +.. note:: This setting is deprecated + +Default value: false + .. _strict: STRICT @@ -1480,19 +1703,18 @@ Set the environment variable EMCC_STRICT=1 or pass -sSTRICT to test that a codebase builds nicely in forward compatible manner. Changes enabled by this: - - The C define EMSCRIPTEN is not defined (__EMSCRIPTEN__ always is, and - is the correct thing to use). - STRICT_JS is enabled. - IGNORE_MISSING_MAIN is disabled. - AUTO_JS_LIBRARIES is disabled. - AUTO_NATIVE_LIBRARIES is disabled. - DEFAULT_TO_CXX is disabled. - - USE_GLFW is set to 0 rather than 2 by default. - ALLOW_UNIMPLEMENTED_SYSCALLS is disabled. - INCOMING_MODULE_JS_API is set to empty by default. .. note:: Applicable during both linking and compilation +Default value: false + .. _ignore_missing_main: IGNORE_MISSING_MAIN @@ -1503,6 +1725,8 @@ If this is disabled then one must provide a ``main`` symbol or explicitly opt out by passing ``--no-entry`` or an EXPORTED_FUNCTIONS list that doesn't include ``_main``. +Default value: true + .. _strict_js: STRICT_JS @@ -1510,6 +1734,8 @@ STRICT_JS Add ``"use strict;"`` to generated JS +Default value: false + .. _warn_on_undefined_symbols: WARN_ON_UNDEFINED_SYMBOLS @@ -1521,9 +1747,11 @@ implement everything, when you know what is not going to actually be called (and don't want to mess with the existing buildsystem), and functions might be implemented later on, say in --pre-js, so you may want to build with -s WARN_ON_UNDEFINED_SYMBOLS=0 to disable the warnings if they annoy you. See -also ERROR_ON_UNDEFINED_SYMBOLS. Any undefined symbols that are listed in- +also ERROR_ON_UNDEFINED_SYMBOLS. Any undefined symbols that are listed in EXPORTED_FUNCTIONS will also be reported. +Default value: true + .. _error_on_undefined_symbols: ERROR_ON_UNDEFINED_SYMBOLS @@ -1535,6 +1763,8 @@ to 0, in which case if an undefined function is called a runtime error will occur. Any undefined symbols that are listed in EXPORTED_FUNCTIONS will also be reported. +Default value: true + .. _small_xhr_chunks: SMALL_XHR_CHUNKS @@ -1543,17 +1773,7 @@ SMALL_XHR_CHUNKS Use small chunk size for binary synchronous XHR's in Web Workers. Used for testing. See test_chunked_synchronous_xhr in runner.py and library.js. -.. _headless: - -HEADLESS -======== - -If 1, will include shim code that tries to 'fake' a browser environment, in -order to let you run a browser program (say, using SDL) in the shell. -Obviously nothing is rendered, but this can be useful for benchmarking and -debugging if actual rendering is not the issue. Note that the shim code is -very partial - it is hard to fake a whole browser! - so keep your -expectations low for this to work. +Default value: false .. _deterministic: @@ -1567,6 +1787,10 @@ browser's language setting (which would mean you can get different results in different browsers, or in the browser and in node). Good for comparing builds for debugging purposes (and nothing else). +.. note:: This setting is deprecated + +Default value: false + .. _modularize: MODULARIZE @@ -1575,14 +1799,9 @@ MODULARIZE By default we emit all code in a straightforward way into the output .js file. That means that if you load that in a script tag in a web page, it will use the global scope. With ``MODULARIZE`` set, we instead emit -the code wrapped in a function that returns a promise. The promise is -resolved with the module instance when it is safe to run the compiled code, -similar to the ``onRuntimeInitialized`` callback. You do not need to use the -``onRuntimeInitialized`` callback when using ``MODULARIZE``. - -(If WASM_ASYNC_COMPILATION is off, that is, if compilation is -*synchronous*, then it would not make sense to return a Promise, and instead -the Module object itself is returned, which is ready to be used.) +the code wrapped in an async function. This function returns a promise that +resolves to a module instance once it is safe to run the compiled code +(similar to the ``onRuntimeInitialized`` callback). The default name of the function is ``Module``, but can be changed using the ``EXPORT_NAME`` option. We recommend renaming it to a more typical name for a @@ -1633,6 +1852,24 @@ factory function, you can use --extern-pre-js or --extern-post-js. While intended usage is to add code that is optimized with the rest of the emitted code, allowing better dead code elimination and minification. +Experimental Feature - Instance ES Modules: + +Note this feature is still under active development and is subject to change! + +To enable this feature use -sMODULARIZE=instance. Enabling this mode will +produce an ES module that is a singleton with ES module exports. The +module will export a default value that is an async init function and will +also export named values that correspond to the Wasm exports and runtime +exports. The init function must be called before any of the exports can be +used. An example of using the module is below. + + import init, { foo, bar } from "./my_module.mjs" + await init(optionalArguments); + foo(); + bar(); + +Default value: false + .. _export_es6: EXPORT_ES6 @@ -1643,15 +1880,7 @@ be enabled for ES6 exports and is implicitly enabled if not already set. This is implicitly enabled if the output suffix is set to 'mjs'. -.. _use_es6_import_meta: - -USE_ES6_IMPORT_META -=================== - -Use the ES6 Module relative import feature 'import.meta.url' -to auto-detect WASM Module path. -It might not be supported on old browsers / toolchains. This setting -may not be disabled when Node.js is targeted (-sENVIRONMENT=*node*). +Default value: false .. _export_name: @@ -1661,6 +1890,8 @@ EXPORT_NAME Global variable to export the module as for environments without a standardized module loading system (e.g. the browser and SM shell). +Default value: 'Module' + .. _dynamic_execution: DYNAMIC_EXECUTION @@ -1697,6 +1928,8 @@ DYNAMIC_EXECUTION=0 is set, and an attempt to call them will throw an exception: When -sDYNAMIC_EXECUTION=2 is set, attempts to call to eval() are demoted to warnings instead of throwing an exception. +Default value: 1 + .. _bootstrapping_struct_info: BOOTSTRAPPING_STRUCT_INFO @@ -1704,6 +1937,8 @@ BOOTSTRAPPING_STRUCT_INFO whether we are in the generate struct_info bootstrap phase +Default value: false + .. _emscripten_tracing: EMSCRIPTEN_TRACING @@ -1713,6 +1948,8 @@ Add some calls to emscripten tracing APIs .. note:: Applicable during both linking and compilation +Default value: false + .. _use_glfw: USE_GLFW @@ -1722,6 +1959,8 @@ Specify the GLFW version that is being linked against. Only relevant, if you are linking against the GLFW library. Valid options are 2 for GLFW2 and 3 for GLFW3. +Default value: 0 + .. _wasm: WASM @@ -1738,14 +1977,16 @@ the WebAssembly file is loaded if browser/shell supports it. Otherwise the If WASM=2 is enabled and the browser fails to compile the WebAssembly module, the page will be reloaded in Wasm2JS mode. +Default value: 1 + .. _standalone_wasm: STANDALONE_WASM =============== -STANDALONE_WASM indicates that we want to emit a wasm file that can run -without JavaScript. The file will use standard APIs such as wasi as much as -possible to achieve that. +Indicates that we want to emit a wasm file that can run without JavaScript. +The file will use standard APIs such as wasi as much as possible to achieve +that. This option does not guarantee that the wasm can be used by itself - if you use APIs with no non-JS alternative, we will still use those (e.g., OpenGL @@ -1778,6 +2019,8 @@ VM). Standalone builds require a ``main`` entry point by default. If you want to build a library (also known as a reactor) instead you can pass ``--no-entry``. +Default value: false + .. _binaryen_ignore_implicit_traps: BINARYEN_IGNORE_IMPLICIT_TRAPS @@ -1793,15 +2036,19 @@ could be executed unconditionally. For that reason this option is generally not useful on large and complex projects, but in a small and simple enough codebase it may help reduce code size a little bit. +Default value: false + .. _binaryen_extra_passes: BINARYEN_EXTRA_PASSES ===================== -A comma-separated list of extra passes to run in the binaryen optimizer, +A comma-separated list of extra passes to run in the binaryen optimizer. Setting this does not override/replace the default passes. It is appended at the end of the list of passes. +Default value: "" + .. _wasm_async_compilation: WASM_ASYNC_COMPILATION @@ -1813,6 +2060,8 @@ smallest modules to run in chrome. (This option was formerly called BINARYEN_ASYNC_COMPILATION) +Default value: true + .. _dyncalls: DYNCALLS @@ -1821,6 +2070,8 @@ DYNCALLS If set to 1, the dynCall() and dynCall_sig() API is made available to caller. +Default value: false + .. _wasm_bigint: WASM_BIGINT @@ -1828,8 +2079,9 @@ WASM_BIGINT WebAssembly integration with JavaScript BigInt. When enabled we don't need to legalize i64s into pairs of i32s, as the wasm VM will use a BigInt where an -i64 is used. If WASM_BIGINT is present, the default minimum supported browser -versions will be increased to the min version that supports BigInt. +i64 is used. + +Default value: true .. _emit_producers_section: @@ -1844,6 +2096,8 @@ about their tools to be included in their builds for privacy or security reasons, see https://github.com/WebAssembly/tool-conventions/issues/93. +Default value: false + .. _emit_emscripten_license: EMIT_EMSCRIPTEN_LICENSE @@ -1851,6 +2105,8 @@ EMIT_EMSCRIPTEN_LICENSE Emits emscripten license info in the JS output. +Default value: false + .. _legalize_js_ffi: LEGALIZE_JS_FFI @@ -1858,8 +2114,12 @@ LEGALIZE_JS_FFI Whether to legalize the JS FFI interfaces (imports/exports) by wrapping them to automatically demote i64 to i32 and promote f32 to f64. This is necessary -in order to interface with JavaScript. For non-web/non-JS embeddings, setting -this to 0 may be desirable. +in order to interface with JavaScript. For non-web/non-JS embeddings, +setting this to 0 may be desirable. + +.. note:: This setting is deprecated + +Default value: true .. _use_sdl: @@ -1875,6 +2135,8 @@ Alternate syntax for using the port: --use-port=sdl2 .. note:: Applicable during both linking and compilation +Default value: 0 + .. _use_sdl_gfx: USE_SDL_GFX @@ -1884,6 +2146,8 @@ Specify the SDL_gfx version that is being linked against. Must match USE_SDL .. note:: Applicable during both linking and compilation +Default value: 0 + .. _use_sdl_image: USE_SDL_IMAGE @@ -1893,6 +2157,8 @@ Specify the SDL_image version that is being linked against. Must match USE_SDL .. note:: Applicable during both linking and compilation +Default value: 1 + .. _use_sdl_ttf: USE_SDL_TTF @@ -1902,6 +2168,8 @@ Specify the SDL_ttf version that is being linked against. Must match USE_SDL .. note:: Applicable during both linking and compilation +Default value: 1 + .. _use_sdl_net: USE_SDL_NET @@ -1911,6 +2179,8 @@ Specify the SDL_net version that is being linked against. Must match USE_SDL .. note:: Applicable during both linking and compilation +Default value: 1 + .. _use_icu: USE_ICU @@ -1921,6 +2191,8 @@ Alternate syntax: --use-port=icu .. note:: Applicable during both linking and compilation +Default value: false + .. _use_zlib: USE_ZLIB @@ -1931,6 +2203,8 @@ Alternate syntax: --use-port=zlib .. note:: Applicable during both linking and compilation +Default value: false + .. _use_bzip2: USE_BZIP2 @@ -1941,6 +2215,8 @@ Alternate syntax: --use-port=bzip2 .. note:: Applicable during both linking and compilation +Default value: false + .. _use_giflib: USE_GIFLIB @@ -1951,6 +2227,8 @@ Alternate syntax: --use-port=giflib .. note:: Applicable during both linking and compilation +Default value: false + .. _use_libjpeg: USE_LIBJPEG @@ -1961,6 +2239,8 @@ Alternate syntax: --use-port=libjpeg .. note:: Applicable during both linking and compilation +Default value: false + .. _use_libpng: USE_LIBPNG @@ -1971,6 +2251,8 @@ Alternate syntax: --use-port=libpng .. note:: Applicable during both linking and compilation +Default value: false + .. _use_regal: USE_REGAL @@ -1981,6 +2263,8 @@ Alternate syntax: --use-port=regal .. note:: Applicable during both linking and compilation +Default value: false + .. _use_boost_headers: USE_BOOST_HEADERS @@ -1991,6 +2275,8 @@ Alternate syntax: --use-port=boost_headers .. note:: Applicable during both linking and compilation +Default value: false + .. _use_bullet: USE_BULLET @@ -2001,6 +2287,8 @@ Alternate syntax: --use-port=bullet .. note:: Applicable during both linking and compilation +Default value: false + .. _use_vorbis: USE_VORBIS @@ -2011,6 +2299,8 @@ Alternate syntax: --use-port=vorbis .. note:: Applicable during both linking and compilation +Default value: false + .. _use_ogg: USE_OGG @@ -2021,6 +2311,8 @@ Alternate syntax: --use-port=ogg .. note:: Applicable during both linking and compilation +Default value: false + .. _use_mpg123: USE_MPG123 @@ -2031,6 +2323,8 @@ Alternate syntax: --use-port=mpg123 .. note:: Applicable during both linking and compilation +Default value: false + .. _use_freetype: USE_FREETYPE @@ -2041,6 +2335,8 @@ Alternate syntax: --use-port=freetype .. note:: Applicable during both linking and compilation +Default value: false + .. _use_sdl_mixer: USE_SDL_MIXER @@ -2051,6 +2347,8 @@ Doesn't *have* to match USE_SDL, but a good idea. .. note:: Applicable during both linking and compilation +Default value: 1 + .. _use_harfbuzz: USE_HARFBUZZ @@ -2061,6 +2359,8 @@ Alternate syntax: --use-port=harfbuzz .. note:: Applicable during both linking and compilation +Default value: false + .. _use_cocos2d: USE_COCOS2D @@ -2071,6 +2371,8 @@ Alternate syntax: --use-port=cocos2d .. note:: Applicable during both linking and compilation +Default value: 0 + .. _use_modplug: USE_MODPLUG @@ -2081,6 +2383,8 @@ Alternate syntax: --use-port=libmodplug .. note:: Applicable during both linking and compilation +Default value: false + .. _sdl2_image_formats: SDL2_IMAGE_FORMATS @@ -2089,6 +2393,10 @@ SDL2_IMAGE_FORMATS Formats to support in SDL2_image. Valid values: bmp, gif, lbm, pcx, png, pnm, tga, xcf, xpm, xv +.. note:: Applicable during both linking and compilation + +Default value: [] + .. _sdl2_mixer_formats: SDL2_MIXER_FORMATS @@ -2096,6 +2404,10 @@ SDL2_MIXER_FORMATS Formats to support in SDL2_mixer. Valid values: ogg, mp3, mod, mid +.. note:: Applicable during both linking and compilation + +Default value: ["ogg"] + .. _use_sqlite3: USE_SQLITE3 @@ -2106,23 +2418,31 @@ Alternate syntax: --use-port=sqlite3 .. note:: Applicable during both linking and compilation +Default value: false + .. _shared_memory: SHARED_MEMORY ============= If 1, target compiling a shared Wasm Memory. -[compile+link] - affects user code at compile and system libraries at link. + +.. note:: Applicable during both linking and compilation + +Default value: false .. _wasm_workers: WASM_WORKERS ============ -If true, enables support for Wasm Workers. Wasm Workers enable applications +Enables support for Wasm Workers. Wasm Workers enable applications to create threads using a lightweight web-specific API that builds on top of Wasm SharedArrayBuffer + Atomics API. -[compile+link] - affects user code at compile and system libraries at link. + +.. note:: Applicable during both linking and compilation + +Default value: 0 .. _audio_worklet: @@ -2132,6 +2452,25 @@ AUDIO_WORKLET If true, enables targeting Wasm Web Audio AudioWorklets. Check out the full documentation in site/source/docs/api_reference/wasm_audio_worklets.rst +Note: The setting will implicitly add ``worklet`` to the :ref:`ENVIRONMENT`, +(i.e. the resulting code and run in a worklet environment) but additionaly +depends on ``WASM_WORKERS`` and Wasm SharedArrayBuffer to run new Audio +Worklets. + +Default value: 0 + +.. _audio_worklet_support_audio_params: + +AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS +================================== + +If true, enables utilizing k- and a-rate AudioParams based properties in +Wasm Audio Worklet code. If false, AudioParams are not used. Set to false +for a tiny improvement to code size and AudioWorklet CPU performance when +audio synthesis is synchronized using custom WebAssembly Memory-based means. + +Default value: true + .. _webaudio_debug: WEBAUDIO_DEBUG @@ -2139,6 +2478,8 @@ WEBAUDIO_DEBUG If true, enables deep debugging of Web Audio backend. +Default value: 0 + .. _pthread_pool_size: PTHREAD_POOL_SIZE @@ -2165,6 +2506,8 @@ browser reports, and is how you can get exactly enough workers for a threadpool equal to the number of cores). [link] - affects generated JS runtime code at link time +Default value: 0 + .. _pthread_pool_size_strict: PTHREAD_POOL_SIZE_STRICT @@ -2186,6 +2529,8 @@ Values: - ``1`` - enable warnings on thread pool exhaustion (default) - ``2`` - make thread pool exhaustion a hard error +Default value: 1 + .. _pthread_pool_delay_load: PTHREAD_POOL_DELAY_LOAD @@ -2207,6 +2552,8 @@ If you do need to synchronously wait on the created threads promise before doing so or you're very likely to run into deadlocks. [link] - affects generated JS runtime code at link time +Default value: false + .. _default_pthread_stack_size: DEFAULT_PTHREAD_STACK_SIZE @@ -2219,6 +2566,8 @@ stack is separate from this stack. This stack only contains certain function local variables, such as those that have their addresses taken, or ones that are too large to fit as local vars in wasm code. +Default value: 0 + .. _pthreads_profiling: PTHREADS_PROFILING @@ -2226,6 +2575,8 @@ PTHREADS_PROFILING True when building with --threadprofiler +Default value: false + .. _allow_blocking_on_main_thread: ALLOW_BLOCKING_ON_MAIN_THREAD @@ -2238,6 +2589,8 @@ https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-t This may become set to 0 by default in the future; for now, this just warns in the console. +Default value: true + .. _pthreads_debug: PTHREADS_DEBUG @@ -2245,6 +2598,8 @@ PTHREADS_DEBUG If true, add in debug traces for diagnosing pthreads related issues. +Default value: false + .. _eval_ctors: EVAL_CTORS @@ -2284,16 +2639,23 @@ This allows more programs to be optimized, but you need to make sure your program does not depend on those features - even just checking the value of argc can lead to problems. +Default value: 0 + .. _textdecoder: TEXTDECODER =========== -Is enabled, use the JavaScript TextDecoder API for string marshalling. -Enabled by default, set this to 0 to disable. +The default value or 1 means the generated code will use TextDecoder if +available and fall back to custom decoder code when not available. If set to 2, we assume TextDecoder is present and usable, and do not emit -any JS code to fall back if it is missing. In single threaded -Oz build modes, -TEXTDECODER defaults to value == 2 to save code size. +any JS code to fall back if it is missing. Setting this zero to avoid even +conditional usage of TextDecoder is no longer supported. +Note: In -Oz builds, the default value of TEXTDECODER is set to 2, to save on +code size (except when AUDIO_WORKLET is specified, or when `shell` is part +of ENVIRONMENT since TextDecoder is not available in those environments). + +Default value: 1 .. _embind_std_string_is_utf8: @@ -2303,6 +2665,8 @@ EMBIND_STD_STRING_IS_UTF8 Embind specific: If enabled, assume UTF-8 encoded data in std::string binding. Disable this to support binary data transfer. +Default value: true + .. _embind_aot: EMBIND_AOT @@ -2314,6 +2678,8 @@ DYNAMIC_EXECUTION=0 this allows exported bindings to be just as fast as DYNAMIC_EXECUTION=1 mode, but without the need for eval(). If there are many bindings the JS output size may be larger though. +Default value: false + .. _offscreencanvas_support: OFFSCREENCANVAS_SUPPORT @@ -2323,6 +2689,8 @@ If set to 1, enables support for transferring canvases to pthreads and creating WebGL contexts in them, as well as explicit swap control for GL contexts. This needs browser support for the OffscreenCanvas specification. +Default value: false + .. _offscreencanvases_to_pthread: OFFSCREENCANVASES_TO_PTHREAD @@ -2332,6 +2700,8 @@ If you are using PROXY_TO_PTHREAD with OFFSCREENCANVAS_SUPPORT, then specify here a comma separated list of CSS ID selectors to canvases to proxy over to the pthread at program startup, e.g. '#canvas1, #canvas2'. +Default value: "#canvas" + .. _offscreen_framebuffer: OFFSCREEN_FRAMEBUFFER @@ -2353,6 +2723,8 @@ OffscreenCanvas and Offscreen Framebuffer support can be enabled at the same time, and allows one to utilize OffscreenCanvas where available, and to fall back to Offscreen Framebuffer otherwise. +Default value: false + .. _fetch_support_indexeddb: FETCH_SUPPORT_INDEXEDDB @@ -2362,6 +2734,8 @@ If nonzero, Fetch API supports backing to IndexedDB. If 0, IndexedDB is not utilized. Set to 0 if IndexedDB support is not interesting for target application, to save a few kBytes. +Default value: true + .. _fetch_debug: FETCH_DEBUG @@ -2369,6 +2743,8 @@ FETCH_DEBUG If nonzero, prints out debugging information in library_fetch.js +Default value: false + .. _fetch: FETCH @@ -2376,6 +2752,28 @@ FETCH If nonzero, enables emscripten_fetch API. +Default value: false + +.. _fetch_streaming: + +FETCH_STREAMING +=============== + +Enables streaming fetched data when the fetch attribute +EMSCRIPTEN_FETCH_STREAM_DATA is used. For streaming requests, the DOM Fetch +API is used otherwise XMLHttpRequest is used. +Both modes generally support the same API, but there are some key +differences: + + - XHR supports synchronous requests + - XHR supports overriding mime types + - Fetch supports streaming data using the 'onprogress' callback + +If set to a value of 2, only the DOM Fetch backend will be used. This should +only be used in testing. + +Default value: 0 + .. _wasmfs: WASMFS @@ -2387,6 +2785,8 @@ If set to 1, uses new filesystem implementation. .. note:: This is an experimental setting +Default value: false + .. _single_file: SINGLE_FILE @@ -2403,6 +2803,39 @@ child-src directive to allow blob:. If you aren't using Content Security Policy, or your CSP header doesn't include either script-src or child-src, then you can safely ignore this warning. +Note that SINGLE_FILE with binary encoding requires the HTML/JS files to be +served with UTF-8 encoding. See the details on SINGLE_FILE_BINARY_ENCODE. + +Default value: false + +.. _single_file_binary_encode: + +SINGLE_FILE_BINARY_ENCODE +========================= + +If true, binary Wasm content is encoded using a custom UTF-8 embedding +instead of base64. This generates a smaller binary that compresses well. +Set this to false to revert back to earlier base64 encoding if you run into +issues with the binary encoding. (and please let us know of any such issues) +If no issues arise, this option will permanently become the default in the +future. + +NOTE: Binary encoding requires that the HTML/JS files are served with UTF-8 +encoding, and will not work with the default legacy Windows-1252 encoding +that browsers might use on Windows. To enable UTF-8 encoding in a +hand-crafted index.html file, apply any of: +1. Add `` inside the section of HTML, or +2. Add ``` inside , or +3. Add `` inside +(if using -o foo.js with SINGLE_FILE mode to build HTML+JS), or +4. pass the header `Content-Type: text/html; charset=utf-8` and/or header +`Content-Type: application/javascript; charset=utf-8` when serving the +relevant files that contain binary encoded content. +If none of these are possible, disable binary encoding with +-sSINGLE_FILE_BINARY_ENCODE=0 to fall back to base64 encoding. + +Default value: true + .. _auto_js_libraries: AUTO_JS_LIBRARIES @@ -2413,6 +2846,8 @@ This gets set to 0 in STRICT mode (or with MINIMAL_RUNTIME) which mean you need to explicitly specify -lfoo.js in at link time in order to access library function in library_foo.js. +Default value: true + .. _auto_native_libraries: AUTO_NATIVE_LIBRARIES @@ -2422,6 +2857,8 @@ Like AUTO_JS_LIBRARIES but for the native libraries such as libgl, libal and libhtml5. If this is disabled it is necessary to explicitly add e.g. -lhtml5 and also to first build the library using ``embuilder``. +Default value: true + .. _min_firefox_version: MIN_FIREFOX_VERSION @@ -2430,10 +2867,13 @@ MIN_FIREFOX_VERSION Specifies the oldest major version of Firefox to target. I.e. all Firefox versions >= MIN_FIREFOX_VERSION are desired to work. Pass -sMIN_FIREFOX_VERSION=majorVersion to drop support -for Firefox versions older than < majorVersion. +for Firefox versions older than majorVersion. Firefox 79 was released on 2020-07-28. MAX_INT (0x7FFFFFFF, or -1) specifies that target is not supported. -Minimum supported value is 34 which was released on 2014-12-01. +Minimum supported value is 68 which was released on 2019-07-09 (see +feature_matrix.py) + +Default value: 79 .. _min_safari_version: @@ -2449,21 +2889,29 @@ bundled with macOS 10.14.0 Mojave. NOTE: Emscripten is unable to produce code that would work in iOS 9.3.5 and older, i.e. iPhone 4s, iPad 2, iPad 3, iPad Mini 1, Pod Touch 5 and older, see https://github.com/emscripten-core/emscripten/pull/7191. +Multithreaded Emscripten code will need Safari 12.2 (iPhone 5s+) at minimum, +with support for DedicatedWorkerGlobalScope.name parameter. MAX_INT (0x7FFFFFFF, or -1) specifies that target is not supported. -Minimum supported value is 90000 which was released in 2015. +Minimum supported value is 120200 which was released on 2019-03-25 (see +feature_matrix.py). + +Default value: 150000 .. _min_chrome_version: MIN_CHROME_VERSION ================== -Specifies the oldest version of Chrome. E.g. pass -sMIN_CHROME_VERSION=58 to -drop support for Chrome 57 and older. +Specifies the oldest version of Chrome. E.g. pass -sMIN_CHROME_VERSION=78 to +drop support for Chrome 77 and older. This setting also applies to modern Chromium-based Edge, which shares version numbers with Chrome. Chrome 85 was released on 2020-08-25. MAX_INT (0x7FFFFFFF, or -1) specifies that target is not supported. -Minimum supported value is 32, which was released on 2014-01-04. +Minimum supported value is 74, which was released on 2019-04-23 (see +feature_matrix.py). + +Default value: 85 .. _min_node_version: @@ -2471,20 +2919,13 @@ MIN_NODE_VERSION ================ Specifies minimum node version to target for the generated code. This is -distinct from the minimum version required run the emscripten compiler. -This version aligns with the current Ubuuntu TLS 20.04 (Focal). +distinct from the minimum version required to run the emscripten compiler. Version is encoded in MMmmVV, e.g. 181401 denotes Node 18.14.01. -Minimum supported value is 101900, which was released 2020-02-05. +Minimum supported value is 180300, which was released 2022-05-18 (see +feature_matrix.py). This version aligns with the version available in +debian/stable (bookworm). -.. _support_errno: - -SUPPORT_ERRNO -============= - -Whether we support setting errno from JS library code. -In MINIMAL_RUNTIME builds, this option defaults to 0. - -.. note:: This setting is deprecated +Default value: 180300 .. _minimal_runtime: @@ -2502,6 +2943,8 @@ be useful for really small programs). By default, no symbols will be exported on the ``Module`` object. In order to export kept alive symbols, please use ``-sEXPORT_KEEPALIVE=1``. +Default value: 0 + .. _minimal_runtime_streaming_wasm_compilation: MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION @@ -2523,6 +2966,9 @@ For large .wasm modules and production environments, this should be set to 1 for faster startup speeds. However this setting is disabled by default since it requires server side configuration and for really small pages there is no observable difference (also has a ~100 byte impact to code size) +This setting is only compatible with html output. + +Default value: false .. _minimal_runtime_streaming_wasm_instantiation: @@ -2539,6 +2985,8 @@ Which one of these two is faster depends on the size of the wasm module, the size of the JS runtime file, and the size of the preloaded data file to download, and the browser in question. +Default value: false + .. _support_longjmp: SUPPORT_LONGJMP @@ -2554,11 +3002,14 @@ little bit of code size and performance when catching exceptions. - 0: No setjmp/longjmp handling - 1: Default setjmp/longjmp/handling, depending on the mode of exceptions. - 'wasm' if '-fwasm-exception' is used, 'emscripten' otherwise. + 'wasm' if '-fwasm-exceptions' is used, 'emscripten' otherwise. -[compile+link] - at compile time this enables the transformations needed for -longjmp support at codegen time, while at link it allows linking in the -library support. +At compile time this enables the transformations needed for longjmp support +at codegen time, while at link it allows linking in the library support. + +.. note:: Applicable during both linking and compilation + +Default value: true .. _disable_deprecated_find_event_target_behavior: @@ -2570,6 +3021,8 @@ When enabled, there is no "Module.canvas" object, no magic "null" default handling, and DOM element 'target' parameters are taken to refer to CSS selectors, instead of referring to DOM IDs. +Default value: true + .. _html5_support_deferring_user_sensitive_requests: HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS @@ -2577,13 +3030,14 @@ HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS Certain browser DOM API operations, such as requesting fullscreen mode transition or pointer lock require that the request originates from within -an user initiated event, such as mouse click or keyboard press. Refactoring +a user initiated event, such as mouse click or keyboard press. Refactoring an application to follow this kind of program structure can be difficult, so -HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS=1 flag allows transparent -emulation of this by deferring synchronous fullscreen mode and pointer lock -requests until a suitable event callback is generated. Set this to 0 -to disable support for deferring to save code space if your application does -not need support for deferred calls. +HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS allows transparent emulation +of this by deferring such requests until a suitable event callback is +generated. Set this to 0 to disable support for deferring to save code +size if your application does not need support for deferred calls. + +Default value: true .. _minify_html: @@ -2597,16 +3051,7 @@ minification is done, but whitespace is retained. Minification requires at least -O1 or -Os to be used. Pass -sMINIFY_HTML=0 to explicitly choose to disable HTML minification altogether. -.. _maybe_wasm2js: - -MAYBE_WASM2JS -============= - -Whether we *may* be using wasm2js. This compiles to wasm normally, but lets -you run wasm2js *later* on the wasm, and you can pick between running the -normal wasm or that wasm2js code. For details of how to do that, see the -test_maybe_wasm2js test. This option can be useful for debugging and -bisecting. +Default value: true .. _asan_shadow_size: @@ -2617,32 +3062,33 @@ This option is no longer used. The appropriate shadow memory size is now calculated from INITIAL_MEMORY and MAXIMUM_MEMORY. Will be removed in a future release. -.. _use_offset_converter: +Default value: -1 -USE_OFFSET_CONVERTER -==================== +.. _source_map_prefixes: -Whether we should use the offset converter. This is needed for older -versions of v8 (<7.7) that does not give the hex module offset into wasm -binary in stack traces, as well as for avoiding using source map entries -across function boundaries. +SOURCE_MAP_PREFIXES +=================== -.. _load_source_map: +List of path substitutions to apply in the "sources" field of the source map. +Corresponds to the ``--prefix`` option used in ``tools/wasm-sourcemap.py``. +Must be used with ``-gsource-map``. -LOAD_SOURCE_MAP -=============== +This setting allows to map path prefixes to the proper ones so that the final +(possibly relative) URLs point to the correct locations : +``-sSOURCE_MAP_PREFIXES=/old/path=/new/path`` -Whether we should load the WASM source map at runtime. -This is enabled automatically when using -gsource-map with sanitizers. +Default value: [] .. _default_to_cxx: DEFAULT_TO_CXX ============== -Default to c++ mode even when run as ``emcc`` rather then ``emc++``. -When this is disabled ``em++`` is required linking C++ programs. Disabling -this will match the behaviour of gcc/g++ and clang/clang++. +Default to c++ mode even when run as ``emcc`` rather than ``emc++``. +When this is disabled ``em++`` is required when linking C++ programs. +Disabling this will match the behaviour of gcc/g++ and clang/clang++. + +Default value: true .. _printf_long_double: @@ -2654,6 +3100,8 @@ that at full precision by default. Instead we print as 64-bit doubles, which saves libc code size. You can flip this option on to get a libc with full long double printing precision. +Default value: false + .. _separate_dwarf_url: SEPARATE_DWARF_URL @@ -2663,6 +3111,8 @@ Setting this affects the path emitted in the wasm that refers to the DWARF file, in -gseparate-dwarf mode. This allows the debugging file to be hosted in a custom location. +Default value: '' + .. _error_on_wasm_changes_after_link: ERROR_ON_WASM_CHANGES_AFTER_LINK @@ -2678,15 +3128,18 @@ 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. 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-extension operation when targeting older browsers. +Default value: false + .. _abort_on_wasm_exceptions: ABORT_ON_WASM_EXCEPTIONS ======================== -Abort on unhandled excptions that occur when calling exported WebAssembly +Abort on unhandled exceptions that occur when calling exported WebAssembly functions. This makes the program behave more like a native program where the OS would terminate the process and no further code can be executed when an unhandled exception (e.g. out-of-bounds memory access) happens. @@ -2699,7 +3152,9 @@ overhead for the extra instrumented function indirection. Enable this if you want Emscripten to handle unhandled exceptions nicely at the cost of a few bytes extra. Exceptions that occur within the ``main`` function are already handled via an -alternative mechanimsm. +alternative mechanism. + +Default value: false .. _pure_wasi: @@ -2707,14 +3162,16 @@ PURE_WASI ========= Build binaries that use as many WASI APIs as possible, and include additional -JS support libraries for those APIs. This allows emscripten to produce binaries -are more WASI compliant and also allows it to process and execute WASI -binaries built with other SDKs (e.g. wasi-sdk). +JS support libraries for those APIs. This allows emscripten to produce +binaries that are more WASI compliant and also allows it to process and +execute WASI binaries built with other SDKs (e.g. wasi-sdk). This setting is experimental and subject to change or removal. -Implies STANDALONE_WASM. +Implies :ref:`STANDALONE_WASM`. .. note:: This is an experimental setting +Default value: false + .. _imported_memory: IMPORTED_MEMORY @@ -2725,23 +3182,30 @@ module. By default the wasm module defines the memory and exports it to JavaScript. Use of the following settings will enable this settings since they depend on being able to define the memory in JavaScript: + - -pthread - RELOCATABLE - ASYNCIFY_LAZY_LOAD_CODE - WASM2JS (WASM=0) +Default value: false + .. _split_module: SPLIT_MODULE ============ -Generate code to loading split wasm modules. +Generate code to load split wasm modules. This option will automatically generate two wasm files as output, one with the ``.orig`` suffix and one without. The default file (without -the suffix) when run will generate instrumentation data can later be +the suffix) when run will generate instrumentation data that can later be fed into wasm-split (the binaryen tool). -As well as this the generated JS code will contains help functions -to loading split modules. +As well as this the generated JS code will contain helper functions +to load split modules. + +.. note:: This is an experimental setting + +Default value: false .. _autoload_dylibs: @@ -2751,23 +3215,30 @@ AUTOLOAD_DYLIBS For MAIN_MODULE builds, automatically load any dynamic library dependencies on startup, before loading the main module. +Default value: true + .. _allow_unimplemented_syscalls: ALLOW_UNIMPLEMENTED_SYSCALLS ============================ -Include unimplemented JS syscalls to be included in the final output. This -allows programs that depend on these syscalls at runtime to be compiled, even -though these syscalls will fail (or do nothing) at runtime. +Link against stub implementations of unsupported/unimplemented syscalls. This +allows programs that depend on these syscalls to be compiled, even though +these functions will fail (or do nothing) at runtime. + +Default value: true .. _trusted_types: TRUSTED_TYPES ============= -Allow calls to Worker(...) and importScripts(...) to be Trusted Types compatible. -Trusted Types is a Web Platform feature designed to mitigate DOM XSS by restricting -the usage of DOM sink APIs. See https://w3c.github.io/webappsec-trusted-types/. +Allow calls to Worker(...) and importScripts(...) to be Trusted Types +compatible. Trusted Types is a Web Platform feature designed to mitigate DOM +XSS by restricting the usage of DOM sink APIs. +See https://w3c.github.io/webappsec-trusted-types/. + +Default value: false .. _polyfill: @@ -2781,14 +3252,19 @@ from generating these by passing ``-sNO_POLYFILL`` or ``-sPOLYFILL=0`` With default browser targets emscripten does not need any polyfills so this settings is *only* needed when also explicitly targeting older browsers. +Default value: true + .. _runtime_debug: RUNTIME_DEBUG ============= -If true, add tracing to core runtime functions. +If non-zero, add tracing to core runtime functions. Can be set to 2 for +extra tracing (for example, tracing that occurs on each turn of the event +loop or each user callback, which can flood the console). This setting is enabled by default if any of the following debugging settings are enabled: + - PTHREADS_DEBUG - DYLINK_DEBUG - LIBRARY_DEBUG @@ -2800,6 +3276,8 @@ are enabled: - SOCKET_DEBUG - FETCH_DEBUG +Default value: 0 + .. _legacy_runtime: LEGACY_RUNTIME @@ -2807,16 +3285,232 @@ LEGACY_RUNTIME Include JS library symbols that were previously part of the default runtime. Without this, such symbols can be made available by adding them to -DEFAULT_LIBRARY_FUNCS_TO_INCLUDE, or via the dependencies of another JS -library symbol. +:ref:`DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`, or via the dependencies of another +JS library symbol. + +Default value: false .. _signature_conversions: SIGNATURE_CONVERSIONS ===================== -User-defined functions to wrap with signature conversion, which take or return -pointer argument. Only affects MEMORY64=1 builds, see create_pointer_conversion_wrappers -in emscripten.py for details. -Use _ for non-pointer arguments, p for pointer/i53 arguments, and P for optional pointer/i53 values. -Example use -sSIGNATURE_CONVERSIONS=someFunction:_p,anotherFunction:p +User-defined functions to wrap with signature conversion, which take or +return pointer arguments. Only affects ``MEMORY64=1`` builds, see +``create_pointer_conversion_wrappers`` in ``emscripten.py`` for details. +Use ``_`` for non-pointer arguments, ``p`` for pointer/i53 arguments, and +``P`` for optional pointer/i53 values. +Example use ``-sSIGNATURE_CONVERSIONS=someFunction:_p,anotherFunction:p`` + +Default value: [] + +.. _source_phase_imports: + +SOURCE_PHASE_IMPORTS +==================== + +Experimental support for wasm source phase imports. +This is only currently implemented in the pre-release/nightly version of +node, and not yet supported by browsers. +Requires EXPORT_ES6 + +.. note:: This is an experimental setting + +Default value: false + +.. _wasm_esm_integration: + +WASM_ESM_INTEGRATION +==================== + +Experimental support for wasm ESM integration. +Requires :ref:`EXPORT_ES6` and ``MODULARIZE=instance`` + +.. note:: This is an experimental setting + +Default value: false + +.. _js_base64_api: + +JS_BASE64_API +============= + +Enable use of the JS arraybuffer-base64 API: +https://github.com/tc39/proposal-arraybuffer-base64 +To run the resulting code currently requires passing `--js_base_64` to node +or chrome. + +.. note:: This is an experimental setting + +Default value: false + +.. _growable_arraybuffers: + +GROWABLE_ARRAYBUFFERS +===================== + +Enable support for GrowableSharedArrayBuffer. +This features is only available behind a flag in recent versions of +node/chrome. + +.. note:: This is an experimental setting + +Default value: false + +.. _wasm_js_types: + +WASM_JS_TYPES +============= + +Experimental support for WebAssembly js-types proposal. +It's currently only available under a flag in certain browsers, +so we disable it by default to save on code size. + +.. note:: This is an experimental setting + +Default value: false + +.. _cross_origin: + +CROSS_ORIGIN +============ + +If the emscripten-generated program is hosted on separate origin then +starting new pthread worker can violate CSP rules. Enabling +CROSS_ORIGIN uses an inline worker to instead load the worker script +indirectly using `importScripts` + +Default value: false + +.. _fake_dylibs: + +FAKE_DYLIBS +=========== + +This setting changes the behaviour of the ``-shared`` flag. The default +setting of ``true`` means the ``-shared`` flag actually produces a normal +object file (i.e. ``ld -r``). Setting this to false will cause ``-shared`` +to behave like :ref:`SIDE_MODULE` and produce a dynamically linked +library. + +Default value: true + +.. _executable: + +EXECUTABLE +========== + +Add a #! line to generated JS file and make it executable. This is useful +for building command line tools that run under node. +This setting can also be set to a string value, in which case that string +will be used as the #! command to embed in the generated file. + +Default value: false + +.. _deprecated-settings: + +=================== +Deprecated Settings +=================== + +The following settings have been proposed for removal from emscripten. These settings +still function but may be removed in a future version. If your project is using one of +these settings please open a bug (or reply to one of the existing bugs). + + - ``RUNTIME_LINKED_LIBS``: you can simply list the libraries directly on the commandline now + - ``CLOSURE_WARNINGS``: use -Wclosure/-Wno-closure instead + - ``LEGALIZE_JS_FFI``: to disable JS type legalization use `-sWASM_BIGINT` or `-sSTANDALONE_WASM` + - ``ASYNCIFY_EXPORTS``: please use JSPI_EXPORTS instead + - ``LINKABLE``: under consideration for removal (https://github.com/emscripten-core/emscripten/issues/25262) + - ``EXPORT_EXCEPTION_HANDLING_HELPERS``: getExceptionMessage is exported anyway when ASSERTIONS or EXCEPTION_STACK_TRACES is set, which are set by default at -O0. At -O1 or above, you can export it separately by -sEXPORTED_RUNTIME_METHODS=getExceptionMessage,decrementExceptionRefcount. + - ``DETERMINISTIC``: under consideration for removal (https://github.com/emscripten-core/emscripten/issues/26647) + +.. _legacy-settings: + +=============== +Legacy Settings +=============== + +The following settings no longer have any effect but are still accepted by emscripten +for backwards compatibility with older versions: + + - ``BINARYEN``: Valid values: WASM + - ``TOTAL_STACK``: Valid values: STACK_SIZE + - ``BINARYEN_ASYNC_COMPILATION``: Valid values: WASM_ASYNC_COMPILATION + - ``UNALIGNED_MEMORY``: forced unaligned memory not supported in fastcomp (Valid values: [0]) + - ``FORCE_ALIGNED_MEMORY``: forced aligned memory is not supported in fastcomp (Valid values: [0]) + - ``PGO``: pgo no longer supported (Valid values: [0]) + - ``QUANTUM_SIZE``: altering the QUANTUM_SIZE is not supported (Valid values: [4]) + - ``FUNCTION_POINTER_ALIGNMENT``: Starting from Emscripten 1.37.29, no longer available (https://github.com/emscripten-core/emscripten/pull/6091) (Valid values: [2]) + - ``RESERVED_FUNCTION_POINTERS``: Valid values: ALLOW_TABLE_GROWTH + - ``BUILD_AS_SHARED_LIB``: Starting from Emscripten 1.38.16, no longer available (https://github.com/emscripten-core/emscripten/pull/7433) (Valid values: [0]) + - ``SAFE_SPLIT_MEMORY``: Starting from Emscripten 1.38.19, SAFE_SPLIT_MEMORY codegen is no longer available (https://github.com/emscripten-core/emscripten/pull/7465) (Valid values: [0]) + - ``SPLIT_MEMORY``: Starting from Emscripten 1.38.19, SPLIT_MEMORY codegen is no longer available (https://github.com/emscripten-core/emscripten/pull/7465) (Valid values: [0]) + - ``BINARYEN_METHOD``: Starting from Emscripten 1.38.23, Emscripten now always builds either to Wasm (-sWASM - default), or to JavaScript (-sWASM=0), other methods are not supported (https://github.com/emscripten-core/emscripten/pull/7836) (Valid values: ['native-wasm']) + - ``BINARYEN_TRAP_MODE``: The wasm backend does not support a trap mode (it always clamps, in effect) (Valid values: [-1]) + - ``PRECISE_I64_MATH``: Starting from Emscripten 1.38.26, PRECISE_I64_MATH is always enabled (https://github.com/emscripten-core/emscripten/pull/7935) (Valid values: [1, 2]) + - ``MEMFS_APPEND_TO_TYPED_ARRAYS``: Starting from Emscripten 1.38.26, MEMFS_APPEND_TO_TYPED_ARRAYS=0 is no longer supported. MEMFS no longer supports using JS arrays for file data (https://github.com/emscripten-core/emscripten/pull/7918) (Valid values: [1]) + - ``ERROR_ON_MISSING_LIBRARIES``: missing libraries are always an error now (Valid values: [1]) + - ``EMITTING_JS``: The new STANDALONE_WASM flag replaces this (replace EMITTING_JS=0 with STANDALONE_WASM=1) (Valid values: [1]) + - ``SKIP_STACK_IN_SMALL``: SKIP_STACK_IN_SMALL is no longer needed as the backend can optimize it directly (Valid values: [0, 1]) + - ``SAFE_STACK``: Replace SAFE_STACK=1 with STACK_OVERFLOW_CHECK=2 (Valid values: [0]) + - ``MEMORY_GROWTH_STEP``: Valid values: MEMORY_GROWTH_LINEAR_STEP + - ``ELIMINATE_DUPLICATE_FUNCTIONS``: Duplicate function elimination for wasm is handled automatically by binaryen (Valid values: [0, 1]) + - ``ELIMINATE_DUPLICATE_FUNCTIONS_DUMP_EQUIVALENT_FUNCTIONS``: Duplicate function elimination for wasm is handled automatically by binaryen (Valid values: [0]) + - ``ELIMINATE_DUPLICATE_FUNCTIONS_PASSES``: Duplicate function elimination for wasm is handled automatically by binaryen (Valid values: [5]) + - ``WASM_OBJECT_FILES``: For LTO, use -flto or -fto=thin instead. Otherwise, Wasm object files are the default (Valid values: [1]) + - ``TOTAL_MEMORY``: Valid values: INITIAL_MEMORY + - ``WASM_MEM_MAX``: Valid values: MAXIMUM_MEMORY + - ``BINARYEN_MEM_MAX``: Valid values: MAXIMUM_MEMORY + - ``BINARYEN_PASSES``: Use BINARYEN_EXTRA_PASSES to add additional passes (Valid values: ['']) + - ``SWAPPABLE_ASM_MODULE``: Fully swappable asm modules are no longer supported (Valid values: [0]) + - ``ASM_JS``: asm.js output is not supported anymore (Valid values: [1]) + - ``FINALIZE_ASM_JS``: asm.js output is not supported anymore (Valid values: [0, 1]) + - ``ASYNCIFY_WHITELIST``: Valid values: ASYNCIFY_ONLY + - ``ASYNCIFY_BLACKLIST``: Valid values: ASYNCIFY_REMOVE + - ``EXCEPTION_CATCHING_WHITELIST``: Valid values: EXCEPTION_CATCHING_ALLOWED + - ``SEPARATE_ASM``: Separate asm.js only made sense for fastcomp with asm.js output (Valid values: [0]) + - ``SEPARATE_ASM_MODULE_NAME``: Separate asm.js only made sense for fastcomp with asm.js output (Valid values: ['']) + - ``FAST_UNROLLED_MEMCPY_AND_MEMSET``: The wasm backend implements memcpy/memset in C (Valid values: [0, 1]) + - ``DOUBLE_MODE``: The wasm backend always implements doubles normally (Valid values: [0, 1]) + - ``PRECISE_F32``: The wasm backend always implements floats normally (Valid values: [0, 1, 2]) + - ``ALIASING_FUNCTION_POINTERS``: The wasm backend always uses a single index space for function pointers, in a single Table (Valid values: [0, 1]) + - ``AGGRESSIVE_VARIABLE_ELIMINATION``: Wasm ignores asm.js-specific optimization flags (Valid values: [0, 1]) + - ``SIMPLIFY_IFS``: Wasm ignores asm.js-specific optimization flags (Valid values: [1]) + - ``DEAD_FUNCTIONS``: The wasm backend does not support dead function removal (Valid values: [[]]) + - ``WASM_BACKEND``: Only the wasm backend is now supported (note that setting it as -s has never been allowed anyhow) (Valid values: [-1]) + - ``EXPORT_BINDINGS``: No longer needed (Valid values: [0, 1]) + - ``RUNNING_JS_OPTS``: Fastcomp cared about running JS which could alter asm.js validation, but not upstream (Valid values: [0]) + - ``EXPORT_FUNCTION_TABLES``: No longer needed (Valid values: [0]) + - ``BINARYEN_SCRIPTS``: No longer needed (Valid values: ['']) + - ``WARN_UNALIGNED``: No longer needed (Valid values: [0, 1]) + - ``ASM_PRIMITIVE_VARS``: No longer needed (Valid values: [[]]) + - ``WORKAROUND_IOS_9_RIGHT_SHIFT_BUG``: Wasm2JS does not support iPhone 4s, iPad 2, iPad 3, iPad Mini 1, Pod Touch 5 (devices with end-of-life at iOS 9.3.5) and older (Valid values: [0]) + - ``RUNTIME_FUNCS_TO_IMPORT``: No longer needed (Valid values: [[]]) + - ``LIBRARY_DEPS_TO_AUTOEXPORT``: No longer needed (Valid values: [[]]) + - ``EMIT_EMSCRIPTEN_METADATA``: No longer supported (Valid values: [0]) + - ``SHELL_FILE``: No longer supported (Valid values: ['']) + - ``LLD_REPORT_UNDEFINED``: Disabling is no longer supported (Valid values: [1]) + - ``MEM_INIT_METHOD``: No longer supported (Valid values: [0]) + - ``USE_PTHREADS``: No longer needed. Use -pthread instead (Valid values: [0, 1]) + - ``USES_DYNAMIC_ALLOC``: No longer supported. Use -sMALLOC=none (Valid values: [1]) + - ``REVERSE_DEPS``: No longer needed (Valid values: ['auto', 'all', 'none']) + - ``RUNTIME_LOGGING``: Valid values: RUNTIME_DEBUG + - ``MIN_EDGE_VERSION``: No longer supported (Valid values: [2147483647]) + - ``MIN_IE_VERSION``: No longer supported (Valid values: [2147483647]) + - ``WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG``: No longer supported (Valid values: [0]) + - ``AUTO_ARCHIVE_INDEXES``: No longer needed (Valid values: [0, 1]) + - ``USE_ES6_IMPORT_META``: Disabling is no longer supported (Valid values: [1]) + - ``EXTRA_EXPORTED_RUNTIME_METHODS``: No longer supported, use EXPORTED_RUNTIME_METHODS (Valid values: [[]]) + - ``SUPPORT_ERRNO``: No longer supported (Valid values: [0]) + - ``DEMANGLE_SUPPORT``: No longer supported (Valid values: [0]) + - ``MAYBE_WASM2JS``: No longer supported (use -sWASM=2) (Valid values: [0]) + - ``HEADLESS``: No longer supported, use headless browsers or Node.js with JSDOM (Valid values: [0]) + - ``USE_OFFSET_COVERTER``: No longer supported, not needed with modern v8 versions (Valid values: [0]) + - ``ASYNCIFY_LAZY_LOAD_CODE``: No longer supported (Valid values: [0]) + - ``USE_WEBGPU``: No longer supported; replaced by --use-port=emdawnwebgpu, which implements a newer (but incompatible) version of webgpu.h - see tools/ports/emdawnwebgpu.py (Valid values: [0]) + - ``PROXY_TO_WORKER``: No longer supported (Valid values: [0]) + - ``NODEJS_CATCH_EXIT``: No longer supported (Valid values: [0]) + - ``NODEJS_CATCH_REJECTION``: No longer supported (Valid values: [0]) + - ``POLYFILL_OLD_MATH_FUNCTIONS``: No longer supported (Valid values: [0]) + - ``RELOCATABLE``: No longer supported (Valid values: [0]) diff --git a/site/source/get_api_items.py b/site/source/get_api_items.py index ee94f6909e6d3..b1c8d827fb6c5 100755 --- a/site/source/get_api_items.py +++ b/site/source/get_api_items.py @@ -22,7 +22,7 @@ # if you change here, change everywhere. api_item_filename = 'api_items.py' -api_reference_items = dict() +api_reference_items = {} def parseFiles(): @@ -65,7 +65,7 @@ def addapiitems(matchobj): filepath = api_reference_directory + file print(file) # open file - with open(filepath, 'r') as infile: + with open(filepath, encoding='utf-8') as infile: for line in infile: # parse line for API items re.sub(r'^\.\.\s+((\w+)\:(\w+)\:\:(.*))', addapiitems, line) @@ -74,13 +74,11 @@ def addapiitems(matchobj): def exportItems(): """Export the API items into form for use in another script. """ - with open(api_item_filename, 'w') as infile: + with open(api_item_filename, 'w', encoding='utf-8') as infile: # write function lead in infile.write("# Auto-generated file (see get_api_items.py)\n\ndef get_mapped_items():\n mapped_wiki_inline_code = dict()\n") - items = list((key, value) for key, value in api_reference_items.items()) - items.sort() - for key, value in items: + for key, value in sorted(api_reference_items.items()): # Write out each API item to add infile.write(" mapped_wiki_inline_code['%s'] = '%s'\n" % (key, value)) diff --git a/site/source/get_wiki.py b/site/source/get_wiki.py index 899531541f222..84e2c72d4f84c 100755 --- a/site/source/get_wiki.py +++ b/site/source/get_wiki.py @@ -37,7 +37,7 @@ wiki_checkout = 'emscripten.wiki/' temp_set_of_codemarkup = set() -logfile = open(logfilename, 'w') +logfile = open(logfilename, 'w', encoding='utf-8') # snapshot_version_information = '.. note:: This is a **snapshot** of the wiki: %s\n\n' % strftime("%a, %d %b %Y %H:%M", gmtime()) snapshot_version_information = '.. note:: This article was migrated from the wiki (%s) and is now the "master copy" (the version in the wiki will be deleted). It may not be a perfect rendering of the original but we hope to fix that soon!\n\n' % time.strftime("%a, %d %b %Y %H:%M", time.gmtime()) @@ -57,7 +57,7 @@ def errorhandler(func, path, exc_info): try: shutil.rmtree(output_dir, ignore_errors=False, onerror=errorhandler) print('Old wiki clone removed') - except IOError: + except OSError: print('No directory to clean found') @@ -91,7 +91,7 @@ def ConvertFilesToRst(): continue inputfilename = wiki_checkout + file - markdown = Path(inputfilename).read_text() + markdown = Path(inputfilename).read_text(encoding='utf-8') if 'This article has moved from the wiki to the new site' in markdown: continue if 'This page has been migrated to the main site' in markdown: @@ -127,16 +127,16 @@ def ConvertFilesToRst(): textinfile += snapshot_version_information - with open(outputfilename) as infile: + with open(outputfilename, encoding='utf-8') as infile: for line in infile: textinfile += line # print textinfile - with open(outputfilename, 'w') as outfile: + with open(outputfilename, 'w', encoding='utf-8') as outfile: outfile.write(textinfile) # write the index - with open(output_dir + 'index.rst', 'w') as outfile: + with open(output_dir + 'index.rst', 'w', encoding='utf-8') as outfile: outfile.write(indexfiletext) @@ -184,7 +184,7 @@ def fixcodemarkuplinks(matchobj): input_file = output_dir + file # print input_file textinfile = '' - with open(input_file) as infile: + with open(input_file, encoding='utf-8') as infile: for line in infile: textinfile += line @@ -195,7 +195,7 @@ def fixcodemarkuplinks(matchobj): # convert codemarkup to links if possible textinfile = fixWikiCodeMarkupToCodeLinks(textinfile) - with open(input_file, 'w') as outfile: + with open(input_file, 'w', encoding='utf-8') as outfile: outfile.write(textinfile) logfile.write('\n\nCODE MARKUP THAT WONT BE LINKED (add entry to mapped_wiki_inline_code if one of these need to be linked. The tool get-api-items.py can be used to generate the list of the documented API items. \n') diff --git a/site/source/index.rst b/site/source/index.rst index 3c298717a6d30..9578868a536fd 100644 --- a/site/source/index.rst +++ b/site/source/index.rst @@ -31,3 +31,4 @@ docs/optimizing/Profiling-Toolchain docs/site/about + genindex diff --git a/src/Fetch.js b/src/Fetch.js index 27fa8a90f6aaa..5d0fd81c38af5 100644 --- a/src/Fetch.js +++ b/src/Fetch.js @@ -4,6 +4,253 @@ * SPDX-License-Identifier: MIT */ +#if FETCH_STREAMING +/** + * A class that mimics the XMLHttpRequest API using the modern Fetch API. + * This implementation is specifically tailored to only handle 'arraybuffer' + * responses. + */ +class FetchXHR { + // --- Public XHR Properties --- + + // Event Handlers + onload = null; + onerror = null; + onprogress = null; + onreadystatechange = null; + ontimeout = null; + + // Request Configuration + responseType = 'arraybuffer'; + withCredentials = false; + timeout = 0; // Standard XHR timeout property + + // Response / State Properties + readyState = 0; // 0: UNSENT + response = null; + responseURL = ''; + status = 0; + statusText = ''; + + // --- Internal Properties --- + _method = ''; + _url = ''; + _headers = {}; + _abortController = null; + _aborted = false; + _responseHeaders = null; + + // When enabled the the data will be streamed using progress events. If the full result is needed + // the data must be collected during the progress events. + _streamData = false; + + // --- Private state management --- + _changeReadyState(state) { + this.readyState = state; + this.onreadystatechange?.(); + } + + // --- Public XHR Methods --- + + /** + * Initializes a request. + * @param {string} method The HTTP request method (e.g., 'GET', 'POST'). + * @param {string} url The URL to send the request to. + * @param {boolean} [async=true] This parameter is ignored as Fetch is always async. + * @param {string|null} [user=null] The username for basic authentication. + * @param {string|null} [password=null] The password for basic authentication. + */ + open(method, url, async = true, user = null, password = null) { + if (this.readyState !== 0 && this.readyState !== 4) { + console.warn("FetchXHR.open() called while a request is in progress."); + this.abort(); + } + + // Reset internal state for the new request + this._method = method; + this._url = url; + this._headers = {}; + this._responseHeaders = null; + + // The async parameter is part of the XHR API but is an error here because + // the Fetch API is inherently asynchronous and does not support synchronous requests. + if (!async) { + throw new Error("FetchXHR does not support synchronous requests."); + } + + // Handle Basic Authentication if user/password are provided. + // This creates a base64-encoded string and sets the Authorization header. + if (user) { + const credentials = btoa(`${user}:${password || ''}`); + this._headers['Authorization'] = `Basic ${credentials}`; + } + + this._changeReadyState(1); // 1: OPENED + } + + /** + * Sets the value of an HTTP request header. + * @param {string} header The name of the header. + * @param {string} value The value of the header. + */ + setRequestHeader(header, value) { + if (this.readyState !== 1) { + throw new Error('setRequestHeader can only be called when state is OPENED.'); + } + this._headers[header] = value; + } + + /** + * This method is not effectively implemented because Fetch API relies on the + * server's Content-Type header and does not support overriding the MIME type + * on the client side in the same way as XHR. + * @param {string} mimetype The MIME type to use. + */ + overrideMimeType(mimetype) { + throw new Error("overrideMimeType is not supported by the Fetch API and has no effect."); + } + + /** + * Returns a string containing all the response headers, separated by CRLF. + * @returns {string} The response headers. + */ + getAllResponseHeaders() { + if (!this._responseHeaders) { + return ''; + } + + let headersString = ''; + // The Headers object is iterable. + for (const [key, value] of this._responseHeaders.entries()) { + headersString += `${key}: ${value}\r\n`; + } + return headersString; + } + + /** + * Sends the request. + * @param body The body of the request. + */ + async send(body = null) { + if (this.readyState !== 1) { + throw new Error('send() can only be called when state is OPENED.'); + } + + this._abortController = new AbortController(); + const signal = this._abortController.signal; + + // Handle timeout + let timeoutID; + if (this.timeout > 0) { + timeoutID = setTimeout( + () => this._abortController.abort(new DOMException('The user aborted a request.', 'TimeoutError')), + this.timeout + ); + } + + const fetchOptions = { + method: this._method, + headers: this._headers, + body: body, + signal: signal, + credentials: this.withCredentials ? 'include' : 'same-origin', + }; + + try { + const response = await fetch(this._url, fetchOptions); + + // Populate response properties once headers are received + this.status = response.status; + this.statusText = response.statusText; + this.responseURL = response.url; + this._responseHeaders = response.headers; + this._changeReadyState(2); // 2: HEADERS_RECEIVED + + // Start processing the body + this._changeReadyState(3); // 3: LOADING + + if (!response.body) { + throw new Error("Response has no body to read."); + } + + const reader = response.body.getReader(); + const contentLength = +response.headers.get('Content-Length'); + + let receivedLength = 0; + // When streaming data don't collect all of the chunks into one large chunk. It's up to the + // user to collect the data as it comes in. + const chunks = this._streamData ? null : []; + + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + + if (!this._streamData) { + chunks.push(value); + } + receivedLength += value.length; + + if (this.onprogress) { + // Convert to ArrayBuffer as requested by responseType. + this.response = value.buffer; + const progressEvent = { + lengthComputable: contentLength > 0, + loaded: receivedLength, + total: contentLength + }; + this.onprogress(progressEvent); + } + } + + if (this._streamData) { + this.response = null; + } else { + // Combine chunks into a single Uint8Array. + const allChunks = new Uint8Array(receivedLength); + let position = 0; + for (const chunk of chunks) { + allChunks.set(chunk, position); + position += chunk.length; + } + // Convert to ArrayBuffer as requested by responseType + this.response = allChunks.buffer; + } + } catch (error) { + this.statusText = error.message; + + if (error.name === 'AbortError') { + // Do nothing. + } else if (error.name === 'TimeoutError') { + this.ontimeout?.(); + } else { + // This is a network error + this.onerror?.(); + } + } finally { + clearTimeout(timeoutID); + if (!this._aborted) { + this._changeReadyState(4); // 4: DONE + // The XHR 'load' event fires for successful HTTP statuses (2xx) as well as + // unsuccessful ones (4xx, 5xx). The 'error' event is for network failures. + this.onload?.(); + } + } + } + + /** + * Aborts the request if it has already been sent. + */ + abort() { + this._aborted = true; + this.status = 0; + this._changeReadyState(4); // 4: DONE + this._abortController?.abort(); + } +} +#endif + var Fetch = { // HandleAllocator for XHR request object // xhrs: undefined, @@ -18,56 +265,55 @@ var Fetch = { // dbInstance: undefined, #if FETCH_SUPPORT_INDEXEDDB - // Be cautious that `onerror` may be run synchronously - openDatabase(dbname, dbversion, onsuccess, onerror) { - try { + async openDatabase(dbname, dbversion) { + return new Promise((resolve, reject) => { + try { #if FETCH_DEBUG - dbg(`fetch: indexedDB.open(dbname="${dbname}", dbversion="${dbversion}");`); + dbg(`fetch: indexedDB.open(dbname="${dbname}", dbversion="${dbversion}");`); #endif - var openRequest = indexedDB.open(dbname, dbversion); - } catch (e) { - return onerror(e); - } + var openRequest = indexedDB.open(dbname, dbversion); + } catch (e) { + return reject(e); + } - openRequest.onupgradeneeded = (event) => { + openRequest.onupgradeneeded = (event) => { #if FETCH_DEBUG - dbg('fetch: IndexedDB upgrade needed. Clearing database.'); + dbg('fetch: IndexedDB upgrade needed. Clearing database.'); #endif - var db = /** @type {IDBDatabase} */ (event.target.result); - if (db.objectStoreNames.contains('FILES')) { - db.deleteObjectStore('FILES'); - } - db.createObjectStore('FILES'); - }; - openRequest.onsuccess = (event) => onsuccess(event.target.result); - openRequest.onerror = onerror; + var db = /** @type {IDBDatabase} */ (event.target.result); + if (db.objectStoreNames.contains('FILES')) { + db.deleteObjectStore('FILES'); + } + db.createObjectStore('FILES'); + }; + openRequest.onsuccess = (event) => resolve(event.target.result); + openRequest.onerror = reject; + }); }, #endif - init() { + async init() { Fetch.xhrs = new HandleAllocator(); #if FETCH_SUPPORT_INDEXEDDB #if PTHREADS if (ENVIRONMENT_IS_PTHREAD) return; #endif - var onsuccess = (db) => { + + addRunDependency('library_fetch_init'); + try { + var db = await Fetch.openDatabase('emscripten_filesystem', 1); #if FETCH_DEBUG dbg('fetch: IndexedDB successfully opened.'); #endif Fetch.dbInstance = db; - removeRunDependency('library_fetch_init'); - }; - - var onerror = () => { + } catch (e) { #if FETCH_DEBUG dbg('fetch: IndexedDB open failed.'); #endif Fetch.dbInstance = false; + } finally { removeRunDependency('library_fetch_init'); - }; - - addRunDependency('library_fetch_init'); - Fetch.openDatabase('emscripten_filesystem', 1, onsuccess, onerror); + } #endif // ~FETCH_SUPPORT_INDEXEDDB } } @@ -244,7 +490,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #if FETCH_DEBUG dbg('fetch: XHR failed, no URL specified!'); #endif - onerror(fetch, 0, 'no url specified!'); + onerror(fetch, 'no url specified!'); return; } var url_ = UTF8ToString(url); @@ -268,8 +514,20 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { var userNameStr = userName ? UTF8ToString(userName) : undefined; var passwordStr = password ? UTF8ToString(password) : undefined; +#if FETCH_STREAMING == 1 + if (fetchAttrStreamData) { + var xhr = new FetchXHR(); + } else { + var xhr = new XMLHttpRequest(); + } +#elif FETCH_STREAMING == 2 + // This setting forces using FetchXHR for all requests. Used only in testing. + var xhr = new FetchXHR(); +#else var xhr = new XMLHttpRequest(); +#endif xhr.withCredentials = !!{{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.withCredentials, 'u8') }}};; + xhr._streamData = fetchAttrStreamData; #if FETCH_DEBUG dbg(`fetch: xhr.timeout: ${xhr.timeout}, xhr.withCredentials: ${xhr.withCredentials}`); dbg(`fetch: xhr.open(requestMethod="${requestMethod}", url: "${url}", userName: ${userNameStr}, password: ${passwordStr}`); @@ -277,8 +535,8 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { xhr.open(requestMethod, url_, !fetchAttrSynchronous, userNameStr, passwordStr); if (!fetchAttrSynchronous) xhr.timeout = timeoutMsecs; // XHR timeout field is only accessible in async XHRs, and must be set after .open() but before .send(). xhr.url_ = url_; // Save the url for debugging purposes (and for comparing to the responseURL that server side advertised) -#if ASSERTIONS - assert(!fetchAttrStreamData, 'streaming uses moz-chunked-arraybuffer which is no longer supported; TODO: rewrite using fetch()'); +#if ASSERTIONS && !FETCH_STREAMING + assert(!fetchAttrStreamData, 'Streaming is only supported when FETCH_STREAMING is enabled.'); #endif xhr.responseType = 'arraybuffer'; @@ -329,7 +587,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #endif // The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is // freed when emscripten_fetch_close() is called. - ptr = _malloc(ptrLen); + ptr = _realloc({{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, '*') }}}, ptrLen); HEAPU8.set(new Uint8Array(/** @type{Array} */(xhr.response)), ptr); } {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 'ptr', '*') }}} @@ -345,6 +603,12 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 'xhr.readyState', 'i16') }}} {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'xhr.status', 'i16') }}} if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64); + if (fetchAttrSynchronous) { + // The response url pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is + // freed when emscripten_fetch_close() is called. + var ruPtr = stringToNewUTF8(xhr.responseURL); + {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.responseUrl, 'ruPtr', '*') }}} + } } xhr.onload = (e) => { @@ -357,12 +621,17 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #if FETCH_DEBUG dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" succeeded with status ${xhr.status}`); #endif - onsuccess?.(fetch, xhr, e); +#if ASSERTIONS + if (fetchAttrStreamData) { + assert(xhr.response === null); + } +#endif + onsuccess(fetch, xhr, e); } else { #if FETCH_DEBUG dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" failed with status ${xhr.status}`); #endif - onerror?.(fetch, xhr, e); + onerror(fetch, e); } }; xhr.onerror = (e) => { @@ -374,7 +643,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" finished with error, readyState ${xhr.readyState} and status ${xhr.status}`); #endif saveResponseAndStatus(); - onerror?.(fetch, xhr, e); + onerror(fetch, e); }; xhr.ontimeout = (e) => { // check if xhr was aborted by user and don't try to call back @@ -384,7 +653,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #if FETCH_DEBUG dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" timed out, readyState ${xhr.readyState} and status ${xhr.status}`); #endif - onerror?.(fetch, xhr, e); + onerror(fetch, e); }; xhr.onprogress = (e) => { // check if xhr was aborted by user and don't try to call back @@ -400,8 +669,9 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #if ASSERTIONS assert(onprogress, 'When doing a streaming fetch, you should have an onprogress handler registered to receive the chunks!'); #endif - // Allocate byte data in Emscripten heap for the streamed memory block (freed immediately after onprogress call) - ptr = _malloc(ptrLen); + // The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is + // freed when emscripten_fetch_close() is called. + ptr = _realloc({{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, '*') }}}, ptrLen); HEAPU8.set(new Uint8Array(/** @type{Array} */(xhr.response)), ptr); } {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 'ptr', '*') }}} @@ -409,14 +679,12 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.dataOffset }}}, e.loaded - ptrLen); writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.totalBytes }}}, e.total); {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 'xhr.readyState', 'i16') }}} + var status = xhr.status; // If loading files from a source that does not give HTTP status code, assume success if we get data bytes - if (xhr.readyState >= 3 && xhr.status === 0 && e.loaded > 0) xhr.status = 200; - {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'xhr.status', 'i16') }}} + if (xhr.readyState >= 3 && xhr.status === 0 && e.loaded > 0) status = 200; + {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'status', 'i16') }}} if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64); - onprogress?.(fetch, xhr, e); - if (ptr) { - _free(ptr); - } + onprogress(fetch, e); }; xhr.onreadystatechange = (e) => { // check if xhr was aborted by user and don't try to call back @@ -428,7 +696,13 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { if (xhr.readyState >= 2) { {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'xhr.status', 'i16') }}} } - onreadystatechange?.(fetch, xhr, e); + if (!fetchAttrSynchronous && (xhr.readyState === 2 && xhr.responseURL.length > 0)) { + // The response url pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is + // freed when emscripten_fetch_close() is called. + var ruPtr = stringToNewUTF8(xhr.responseURL); + {{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.responseUrl, 'ruPtr', '*') }}} + } + onreadystatechange(fetch, e); }; #if FETCH_DEBUG dbg(`fetch: xhr.send(data=${data})`); @@ -439,7 +713,7 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) { #if FETCH_DEBUG dbg(`fetch: xhr failed with exception: ${e}`); #endif - onerror?.(fetch, xhr, e); + onerror(fetch, e); } } @@ -475,14 +749,14 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) { }); }; - var reportProgress = (fetch, xhr, e) => { + var reportProgress = (fetch, e) => { doCallback(() => { if (onprogress) {{{ makeDynCall('vp', 'onprogress') }}}(fetch); else progresscb?.(fetch); }); }; - var reportError = (fetch, xhr, e) => { + var reportError = (fetch, e) => { #if FETCH_DEBUG dbg(`fetch: operation failed: ${e}`); #endif @@ -493,7 +767,7 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) { }); }; - var reportReadyStateChange = (fetch, xhr, e) => { + var reportReadyStateChange = (fetch, e) => { #if FETCH_DEBUG dbg(`fetch: ready state change. e: ${e}`); #endif @@ -574,17 +848,15 @@ function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) { } function fetchGetResponseHeadersLength(id) { - return lengthBytesUTF8(Fetch.xhrs.get(id).getAllResponseHeaders()) + 1; + return lengthBytesUTF8(Fetch.xhrs.get(id).getAllResponseHeaders()); } function fetchGetResponseHeaders(id, dst, dstSizeBytes) { var responseHeaders = Fetch.xhrs.get(id).getAllResponseHeaders(); - var lengthBytes = lengthBytesUTF8(responseHeaders) + 1; - stringToUTF8(responseHeaders, dst, dstSizeBytes); - return Math.min(lengthBytes, dstSizeBytes); + return stringToUTF8(responseHeaders, dst, dstSizeBytes) + 1; } -//Delete the xhr JS object, allowing it to be garbage collected. +// Delete the xhr JS object, allowing it to be garbage collected. function fetchFree(id) { #if FETCH_DEBUG dbg(`fetch: fetchFree id:${id}`); diff --git a/src/IDBStore.js b/src/IDBStore.js index a4e9960d0a634..085a912942cd7 100644 --- a/src/IDBStore.js +++ b/src/IDBStore.js @@ -6,11 +6,10 @@ var IDBStore = { indexedDB() { - if (typeof indexedDB != 'undefined') return indexedDB; - var ret = null; - if (typeof window == 'object') ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; - assert(ret, 'IDBStore used, but indexedDB not supported'); - return ret; +#if ASSERTIONS + assert(typeof indexedDB != 'undefined', 'IDBStore used, but indexedDB not supported'); +#endif + return indexedDB; }, DB_VERSION: 22, DB_STORE_NAME: 'FILE_DATA', diff --git a/src/URIUtils.js b/src/URIUtils.js deleted file mode 100644 index 6f098fcf9f269..0000000000000 --- a/src/URIUtils.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2017 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// Prefix of data URIs emitted by SINGLE_FILE and related options. -var dataURIPrefix = 'data:application/octet-stream;base64,'; - -/** - * Indicates whether filename is a base64 data URI. - * @noinline - */ -var isDataURI = (filename) => filename.startsWith(dataURIPrefix); - -/** - * Indicates whether filename is delivered via file protocol (as opposed to http/https) - * @noinline - */ -var isFileURI = (filename) => filename.startsWith('file://'); diff --git a/src/arrayUtils.js b/src/arrayUtils.js deleted file mode 100644 index a5f7449078f7d..0000000000000 --- a/src/arrayUtils.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2017 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -/** @type {function(string, boolean=, number=)} */ -function intArrayFromString(stringy, dontAddNull, length) { - var len = length > 0 ? length : lengthBytesUTF8(stringy)+1; - var u8array = new Array(len); - var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); - if (dontAddNull) u8array.length = numBytesWritten; - return u8array; -} - -function intArrayToString(array) { - var ret = []; - for (var i = 0; i < array.length; i++) { - var chr = array[i]; - if (chr > 0xFF) { -#if ASSERTIONS - assert(false, `Character code ${chr} (${String.fromCharCode(chr)}) at offset ${i} not in 0x00-0xFF.`); -#endif - chr &= 0xFF; - } - ret.push(String.fromCharCode(chr)); - } - return ret.join(''); -} diff --git a/src/audio_worklet.js b/src/audio_worklet.js index e57792885ede7..8114b2e8a4fae 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -1,5 +1,5 @@ // This file is the main bootstrap script for Wasm Audio Worklets loaded in an -// Emscripten application. Build with -sAUDIO_WORKLET=1 linker flag to enable +// Emscripten application. Build with -sAUDIO_WORKLET linker flag to enable // targeting Audio Worklets. // AudioWorkletGlobalScope does not have a onmessage/postMessage() functionality @@ -12,102 +12,235 @@ // the node constructor's "processorOptions" field, we can share the necessary // bootstrap information from the main thread to the AudioWorkletGlobalScope. +#if MINIMAL_RUNTIME +var instantiatePromise; +#endif + +if (ENVIRONMENT_IS_AUDIO_WORKLET) { + +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS function createWasmAudioWorkletProcessor(audioParams) { +#else +function createWasmAudioWorkletProcessor() { +#endif class WasmAudioWorkletProcessor extends AudioWorkletProcessor { constructor(args) { super(); - // Copy needed stack allocation functions from the Module object - // to global scope, these will be accessed in hot paths, so maybe - // they'll be a bit faster to access directly, rather than referencing - // them as properties of the Module object. - globalThis.stackAlloc = Module['stackAlloc']; - globalThis.stackSave = Module['stackSave']; - globalThis.stackRestore = Module['stackRestore']; - globalThis.HEAPU32 = Module['HEAPU32']; - globalThis.HEAPF32 = Module['HEAPF32']; - // Capture the Wasm function callback to invoke. let opts = args.processorOptions; - this.callbackFunction = Module['wasmTable'].get(opts['cb']); - this.userData = opts['ud']; +#if ASSERTIONS + assert(opts.callback) + assert(opts.samplesPerChannel) +#endif + this.callback = {{{ makeDynCall('iipipipp', 'opts.callback') }}}; + this.userData = opts.userData; + // Then the samples per channel to process, fixed for the lifetime of the + // context that created this processor. Even though this 'render quantum + // size' is fixed at 128 samples in the 1.0 spec, it will be variable in + // the 1.1 spec. It's passed in now, just to prove it's settable, but will + // eventually be a property of the AudioWorkletGlobalScope (globalThis). + this.samplesPerChannel = opts.samplesPerChannel; + this.bytesPerChannel = this.samplesPerChannel * {{{ getNativeTypeSize('float') }}}; + + // Prepare the output views; see createOutputViews(). The 'STACK_ALIGN' + // deduction stops the STACK_OVERFLOW_CHECK failing (since the stack will + // be full if we allocate all the available space) leaving room for a + // single AudioSampleFrame as a minimum. There's an arbitrary maximum of + // 64 frames, for the case where a multi-MB stack is passed. + this.outputViews = new Array(Math.min(((wwParams.stackSize - {{{ STACK_ALIGN }}}) / this.bytesPerChannel) | 0, /*sensible limit*/ 64)); +#if ASSERTIONS + assert(this.outputViews.length > 0, `AudioWorklet needs more stack allocating (at least ${this.bytesPerChannel})`); +#endif + this.createOutputViews(); + +#if ASSERTIONS + // Explicitly verify this later in process(). Note to self, stackSave is a + // bit of a misnomer as it simply gets the stack address. + this.ctorOldStackPtr = stackSave(); +#endif + } + + /** + * Create up-front as many typed views for marshalling the output data as + * may be required, allocated at the *top* of the worklet's stack (and whose + * addresses are fixed). + */ + createOutputViews() { + // These are still alloc'd to take advantage of the overflow checks, etc. + var oldStackPtr = stackSave(); + var viewDataIdx = {{{ getHeapOffset('stackAlloc(this.outputViews.length * this.bytesPerChannel)', 'float') }}}; +#if WEBAUDIO_DEBUG + console.log(`AudioWorklet creating ${this.outputViews.length} buffer one-time views (for a stack size of ${wwParams.stackSize} at address ${ptrToString(viewDataIdx * 4)})`); +#endif + // Inserted in reverse so the lowest indices are closest to the stack top + for (var n = this.outputViews.length - 1; n >= 0; n--) { + this.outputViews[n] = HEAPF32.subarray(viewDataIdx, viewDataIdx += this.samplesPerChannel); + } + stackRestore(oldStackPtr); } +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS static get parameterDescriptors() { return audioParams; } +#endif + /** + * Marshals all inputs and parameters to the Wasm memory on the thread's + * stack, then performs the wasm audio worklet call, and finally marshals + * audio output data back. + * + * @param {Object} parameters + */ +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS process(inputList, outputList, parameters) { - // Marshal all inputs and parameters to the Wasm memory on the thread stack, - // then perform the wasm audio worklet call, - // and finally marshal audio output data back. - - let numInputs = inputList.length, - numOutputs = outputList.length, - numParams = 0, i, j, k, dataPtr, - stackMemoryNeeded = (numInputs + numOutputs) * 8, - oldStackPtr = stackSave(), - inputsPtr, outputsPtr, outputDataPtr, paramsPtr, - didProduceAudio, paramArray; - - // Calculate how much stack space is needed. - for (i of inputList) stackMemoryNeeded += i.length * 512; - for (i of outputList) stackMemoryNeeded += i.length * 512; - for (i in parameters) stackMemoryNeeded += parameters[i].byteLength + 8, ++numParams; - - // Allocate the necessary stack space. - inputsPtr = stackAlloc(stackMemoryNeeded); - - // Copy input audio descriptor structs and data to Wasm - k = inputsPtr >> 2; - dataPtr = inputsPtr + numInputs * 8; - for (i of inputList) { +#else + /** @suppress {checkTypes} */ + process(inputList, outputList) { +#endif + +#if ALLOW_MEMORY_GROWTH + // Recreate the output views if the heap has changed + // TODO: add support for GROWABLE_ARRAYBUFFERS + if (HEAPF32.buffer != this.outputViews[0].buffer) { + this.createOutputViews(); + } +#endif + + var numInputs = inputList.length; + var numOutputs = outputList.length; + + var entry; // reused list entry or index + var subentry; // reused channel or other array in each list entry or index + + // Calculate the required stack and output buffer views (stack is further + // split into aligned structs and the raw float data). + var stackMemoryStruct = (numInputs + numOutputs) * {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + var stackMemoryData = 0; + for (entry of inputList) { + stackMemoryData += entry.length; + } + stackMemoryData *= this.bytesPerChannel; + // Collect the total number of output channels (mapped to array views) + var outputViewsNeeded = 0; + for (entry of outputList) { + outputViewsNeeded += entry.length; + } + stackMemoryData += outputViewsNeeded * this.bytesPerChannel; + var numParams = 0; +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS + for (entry in parameters) { + ++numParams; + stackMemoryStruct += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; + stackMemoryData += parameters[entry].byteLength; + } +#endif + var oldStackPtr = stackSave(); +#if ASSERTIONS + assert(oldStackPtr == this.ctorOldStackPtr, 'AudioWorklet stack address has unexpectedly moved'); + assert(outputViewsNeeded <= this.outputViews.length, `Too many AudioWorklet outputs (need ${outputViewsNeeded} but have stack space for ${this.outputViews.length})`); +#endif + + // Allocate the necessary stack space. All pointer variables are in bytes; + // 'structPtr' starts at the first struct entry (all run sequentially) + // and is the working start to each record; 'dataPtr' is the same for the + // audio/params data, starting after *all* the structs. + // 'structPtr' begins 16-byte aligned, allocated from the internal + // _emscripten_stack_alloc(), as are the output views, and so to ensure + // the views fall on the correct addresses (and we finish at stacktop) we + // request additional bytes, taking this alignment into account, then + // offset `dataPtr` by the difference. + var stackMemoryAligned = (stackMemoryStruct + stackMemoryData + 15) & ~15; + var structPtr = stackAlloc(stackMemoryAligned); + var dataPtr = structPtr + (stackMemoryAligned - stackMemoryData); +#if ASSERTIONS + // TODO: look at why stackAlloc isn't tripping the assertions + assert(stackMemoryAligned <= wwParams.stackSize, `Not enough stack allocated to the AudioWorklet (need ${stackMemoryAligned}, got ${wwParams.stackSize})`); +#endif + + // Copy input audio descriptor structs and data to Wasm (recall, structs + // first, audio data after). 'inputsPtr' is the start of the C callback's + // input AudioSampleFrame. + var /*const*/ inputsPtr = structPtr; + for (entry of inputList) { // Write the AudioSampleFrame struct instance - HEAPU32[k++] = i.length; - HEAPU32[k++] = dataPtr; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; + structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; // Marshal the input audio sample data for each audio channel of this input - for (j of i) { - HEAPF32.set(j, dataPtr>>2); - dataPtr += 512; + for (subentry of entry) { + HEAPF32.set(subentry, {{{ getHeapOffset('dataPtr', 'float') }}}); + dataPtr += this.bytesPerChannel; } } - // Copy output audio descriptor structs to Wasm - outputsPtr = dataPtr; - k = outputsPtr >> 2; - outputDataPtr = (dataPtr += numOutputs * 8) >> 2; - for (i of outputList) { +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS + // Copy parameters descriptor structs and data to Wasm. 'paramsPtr' is the + // start of the C callback's input AudioParamFrame. + var /*const*/ paramsPtr = structPtr; + for (entry = 0; subentry = parameters[entry++];) { + // Write the AudioParamFrame struct instance + {{{ makeSetValue('structPtr', C_STRUCTS.AudioParamFrame.length, 'subentry.length', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioParamFrame.data, 'dataPtr', '*') }}}; + structPtr += {{{ C_STRUCTS.AudioParamFrame.__size__ }}}; + // Marshal the audio parameters array + HEAPF32.set(subentry, {{{ getHeapOffset('dataPtr', 'float') }}}); + dataPtr += subentry.length * {{{ getNativeTypeSize('float') }}}; + } +#else + var paramsPtr = 0; +#endif + + // Copy output audio descriptor structs to Wasm. 'outputsPtr' is the start + // of the C callback's output AudioSampleFrame. 'dataPtr' will now be + // aligned with the output views, ending at stacktop (which is why this + // needs to be last). + var /*const*/ outputsPtr = structPtr; + for (entry of outputList) { // Write the AudioSampleFrame struct instance - HEAPU32[k++] = i.length; - HEAPU32[k++] = dataPtr; - // Reserve space for the output data - dataPtr += 512 * i.length; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.numberOfChannels, 'entry.length', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.samplesPerChannel, 'this.samplesPerChannel', 'u32') }}}; + {{{ makeSetValue('structPtr', C_STRUCTS.AudioSampleFrame.data, 'dataPtr', '*') }}}; + structPtr += {{{ C_STRUCTS.AudioSampleFrame.__size__ }}}; + // Advance the output pointer to the next output (matching the pre-allocated views) + dataPtr += this.bytesPerChannel * entry.length; } - // Copy parameters descriptor structs and data to Wasm - paramsPtr = dataPtr; - k = paramsPtr >> 2; - dataPtr += numParams * 8; - for (i = 0; paramArray = parameters[i++];) { - // Write the AudioParamFrame struct instance - HEAPU32[k++] = paramArray.length; - HEAPU32[k++] = dataPtr; - // Marshal the audio parameters array - HEAPF32.set(paramArray, dataPtr>>2); - dataPtr += paramArray.length*4; +#if ASSERTIONS + // If all the maths worked out, we arrived at the original stack address + console.assert(dataPtr == oldStackPtr, `AudioWorklet stack mismatch (audio data finishes at ${dataPtr} instead of ${oldStackPtr})`); + + // Sanity checks. If these trip the most likely cause, beyond unforeseen + // stack shenanigans, is that the 'render quantum size' changed after + // construction (which shouldn't be possible). + if (numOutputs) { + // First that the output view addresses match the stack positions + dataPtr -= this.bytesPerChannel; + for (entry = 0; entry < outputViewsNeeded; entry++) { + console.assert(dataPtr == this.outputViews[entry].byteOffset, 'AudioWorklet internal error in addresses of the output array views'); + dataPtr -= this.bytesPerChannel; + } + // And that the views' size match the passed in output buffers + for (entry of outputList) { + for (subentry of entry) { + assert(subentry.byteLength == this.bytesPerChannel, `AudioWorklet unexpected output buffer size (expected ${this.bytesPerChannel} got ${subentry.byteLength})`); + } + } } +#endif // Call out to Wasm callback to perform audio processing - if (didProduceAudio = this.callbackFunction(numInputs, inputsPtr, numOutputs, outputsPtr, numParams, paramsPtr, this.userData)) { + var didProduceAudio = this.callback(numInputs, inputsPtr, numOutputs, outputsPtr, numParams, paramsPtr, this.userData); + if (didProduceAudio) { // Read back the produced audio data to all outputs and their channels. - // (A garbage-free function TypedArray.copy(dstTypedArray, dstOffset, - // srcTypedArray, srcOffset, count) would sure be handy.. but web does - // not have one, so manually copy all bytes in) - for (i of outputList) { - for (j of i) { - for (k = 0; k < 128; ++k) { - j[k] = HEAPF32[outputDataPtr++]; - } + // The preallocated 'outputViews' already have the correct offsets and + // sizes into the stack (recall from createOutputViews() that they run + // backwards). + for (entry of outputList) { + for (subentry of entry) { + subentry.set(this.outputViews[--outputViewsNeeded]); } } } @@ -122,66 +255,32 @@ function createWasmAudioWorkletProcessor(audioParams) { return WasmAudioWorkletProcessor; } +#if MIN_FIREFOX_VERSION < 138 || MIN_CHROME_VERSION != TARGET_NOT_SUPPORTED || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED +// If this browser does not support the up-to-date AudioWorklet standard +// that has a MessagePort over to the AudioWorklet, then polyfill that by +// a hacky AudioWorkletProcessor that provides the MessagePort. +// Firefox added support in https://hg-edge.mozilla.org/integration/autoland/rev/ab38a1796126f2b3fc06475ffc5a625059af59c1 +// Chrome ticket: https://crbug.com/446920095 +// Safari ticket: https://webkit.org/b/299386 +/** + * @suppress {duplicate, checkTypes} + */ +var port = globalThis.port || {}; + // Specify a worklet processor that will be used to receive messages to this // AudioWorkletGlobalScope. We never connect this initial AudioWorkletProcessor // to the audio graph to do any audio processing. class BootstrapMessages extends AudioWorkletProcessor { constructor(arg) { super(); - // Initialize the global Emscripten Module object that contains e.g. the - // Wasm Module and Memory objects. After this we are ready to load in the - // main application JS script, which the main thread will addModule() - // to this scope. - globalThis.Module = arg['processorOptions']; -#if !MINIMAL_RUNTIME - // Default runtime relies on an injected instantiateWasm() function to - // initialize the Wasm Module. - globalThis.Module['instantiateWasm'] = (info, receiveInstance) => { - var instance = new WebAssembly.Instance(Module['wasm'], info); - receiveInstance(instance, Module['wasm']); - return instance.exports; - }; -#endif -#if WEBAUDIO_DEBUG - console.log('AudioWorklet global scope looks like this:'); - console.dir(globalThis); -#endif + startWasmWorker(arg.processorOptions) // Listen to messages from the main thread. These messages will ask this // scope to create the real AudioWorkletProcessors that call out to Wasm to // do audio processing. - let p = globalThis['messagePort'] = this.port; - p.onmessage = (msg) => { - let d = msg.data; - if (d['_wpn']) { - // '_wpn' is short for 'Worklet Processor Node', using an identifier - // that will never conflict with user messages -#if MODULARIZE - // Instantiate the MODULARIZEd Module function, which is stored for us - // under the special global name AudioWorkletModule in - // MODULARIZE+AUDIO_WORKLET builds. - if (globalThis.AudioWorkletModule) { - // This populates the Module object with all the Wasm properties - AudioWorkletModule(Module); - // We have now instantiated the Module function, can discard it from - // global scope - delete globalThis.AudioWorkletModule; - } -#endif - // Register a real AudioWorkletProcessor that will actually do audio processing. - registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor(d['audioParams'])); -#if WEBAUDIO_DEBUG - console.log(`Registered a new WasmAudioWorkletProcessor "${d['_wpn']}" with AudioParams: ${d['audioParams']}`); -#endif - // Post a Wasm Call message back telling that we have now registered the - // AudioWorkletProcessor class, and should trigger the user onSuccess - // callback of the - // emscripten_create_wasm_audio_worklet_processor_async() call. - p.postMessage({'_wsc': d['callback'], 'x': [d['contextHandle'], 1/*EM_TRUE*/, d['userData']] }); // "WaSm Call" - } else if (d['_wsc']) { - // '_wsc' is short for 'wasm call', using an identifier that will never - // conflict with user messages - Module['wasmTable'].get(d['_wsc'])(...d['x']); - }; + if (!(port instanceof MessagePort)) { + this.port.onmessage = port.onmessage; + /** @suppress {checkTypes} */ + port = this.port; } } @@ -197,4 +296,47 @@ class BootstrapMessages extends AudioWorkletProcessor { }; // Register the dummy processor that will just receive messages. -registerProcessor('message', BootstrapMessages); +registerProcessor('em-bootstrap', BootstrapMessages); +#endif + +port.onmessage = async (msg) => { +#if MINIMAL_RUNTIME + // Wait for the module instantiation before processing messages. + await instantiatePromise; +#endif + let d = msg.data; + if (d['_boot']) { + startWasmWorker(d); +#if WEBAUDIO_DEBUG + console.log('AudioWorklet global scope looks like this:'); + console.dir(globalThis); +#endif + } else if (d['_wpn']) { + // '_wpn' is short for 'Worklet Processor Node', using an identifier + // that will never conflict with user messages + // Register a real AudioWorkletProcessor that will actually do audio processing. +#if AUDIO_WORKLET_SUPPORT_AUDIO_PARAMS + registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor(d.audioParams)); +#else + registerProcessor(d['_wpn'], createWasmAudioWorkletProcessor()); +#endif +#if WEBAUDIO_DEBUG + console.log(`Registered a new WasmAudioWorkletProcessor "${d['_wpn']}" with AudioParams: ${d.audioParams}`); +#endif + // Post a Wasm Call message back telling that we have now registered the + // AudioWorkletProcessor, and should trigger the user onSuccess callback + // of the emscripten_create_wasm_audio_worklet_processor_async() call. + // + // '_wsc' is short for 'wasm call', using an identifier that will never + // conflict with user messages. + // + // Note: we convert the pointer arg manually here since the call site + // ($_EmAudioDispatchProcessorCallback) is used with various signatures + // and we do not know the types in advance. + port.postMessage({'_wsc': d.callback, args: [d.contextHandle, 1/*EM_TRUE*/, {{{ to64('d.userData') }}}] }); + } else if (d['_wsc']) { + getWasmTableEntry(d['_wsc'])(...d.args); + }; +} + +} // ENVIRONMENT_IS_AUDIO_WORKLET diff --git a/src/base64Decode.js b/src/base64Decode.js deleted file mode 100644 index 46a15327d619e..0000000000000 --- a/src/base64Decode.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2020 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -#if WASM2JS && 0 // TODO: Figure out a way to enable this kind of sharing. - -// Binaryen defines the following function if Wasm2JS is being used: -// function base64DecodeToExistingUint8Array(uint8Array, offset, b64); -// so should reuse that when available. However that lives inside the asm module -// for the time being, so cannot access it directly from here. Hence this block -// is disabled atm. - -function base64Decode(b64) { -#if ASSERTIONS - assert(b64.length % 4 == 0); -#endif - return base64DecodeToExistingUint8Array(new Uint8Array(b64.length*3>>2), 0, b64); -} - -#else - -// Precreate a reverse lookup table from chars "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" back to bytes to make decoding fast. -for (var base64ReverseLookup = new Uint8Array(123/*'z'+1*/), i = 25; i >= 0; --i) { - base64ReverseLookup[48+i] = 52+i; // '0-9' - base64ReverseLookup[65+i] = i; // 'A-Z' - base64ReverseLookup[97+i] = 26+i; // 'a-z' -} -base64ReverseLookup[43] = 62; // '+' -base64ReverseLookup[47] = 63; // '/' - - -// Decodes a _known valid_ base64 string (without validation) and returns it as a new Uint8Array. -// Benchmarked to be around 5x faster compared to a simple -// "Uint8Array.from(atob(b64), c => c.charCodeAt(0))" (TODO: perhaps use this form in -Oz builds?) -/** @noinline */ -function base64Decode(b64) { -#if ENVIRONMENT_MAY_BE_NODE - if (typeof ENVIRONMENT_IS_NODE != 'undefined' && ENVIRONMENT_IS_NODE) { - var buf = Buffer.from(b64, 'base64'); - return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); - } -#endif - -#if ASSERTIONS - assert(b64.length % 4 == 0); -#endif - var b1, b2, i = 0, j = 0, bLength = b64.length, output = new Uint8Array((bLength*3>>2) - (b64[bLength-2] == '=') - (b64[bLength-1] == '=')); - for (; i < bLength; i += 4, j += 3) { - b1 = base64ReverseLookup[b64.charCodeAt(i+1)]; - b2 = base64ReverseLookup[b64.charCodeAt(i+2)]; - output[j] = base64ReverseLookup[b64.charCodeAt(i)] << 2 | b1 >> 4; - output[j+1] = b1 << 4 | b2 >> 2; - output[j+2] = b2 << 6 | base64ReverseLookup[b64.charCodeAt(i+3)]; - } - return output; -} - -#endif // ~WASM2JS diff --git a/src/base64Utils.js b/src/base64Utils.js deleted file mode 100644 index 20ab91b154ada..0000000000000 --- a/src/base64Utils.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright 2017 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -#if POLYFILL && (ENVIRONMENT_MAY_BE_SHELL || (ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 160000)) -#include "polyfill/atob.js" -#endif - -// Converts a string of base64 into a byte array (Uint8Array). -function intArrayFromBase64(s) { -#if ENVIRONMENT_MAY_BE_NODE - if (typeof ENVIRONMENT_IS_NODE != 'undefined' && ENVIRONMENT_IS_NODE) { - var buf = Buffer.from(s, 'base64'); - return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); - } -#endif - - var decoded = atob(s); - var bytes = new Uint8Array(decoded.length); - for (var i = 0 ; i < decoded.length ; ++i) { - bytes[i] = decoded.charCodeAt(i); - } - return bytes; -} - -// If filename is a base64 data URI, parses and returns data (Buffer on node, -// Uint8Array otherwise). If filename is not a base64 data URI, returns undefined. -function tryParseAsDataURI(filename) { - if (!isDataURI(filename)) { - return; - } - - return intArrayFromBase64(filename.slice(dataURIPrefix.length)); -} diff --git a/src/binaryDecode.js b/src/binaryDecode.js new file mode 100644 index 0000000000000..f5ef77313422b --- /dev/null +++ b/src/binaryDecode.js @@ -0,0 +1,11 @@ +// Prevent Closure from minifying the binaryDecode() function, or otherwise +// Closure may analyze through the WASM_BINARY_DATA placeholder string into this +// function, leading into incorrect results. +/** @noinline */ +function binaryDecode(bin) { + for (var i = 0, l = bin.length, o = new Uint8Array(l), c; i < l; ++i) { + c = bin.charCodeAt(i); + o[i] = ~c >> 8 & c; // Recover the null byte in a manner that is compatible with https://crbug.com/453961758 + } + return o; +} diff --git a/src/build_as_worker.js b/src/build_as_worker.js new file mode 100644 index 0000000000000..51d6bd5053cd0 --- /dev/null +++ b/src/build_as_worker.js @@ -0,0 +1,51 @@ +var workerResponded = false, workerCallbackId = -1; + +(() => { + var messageBuffer = null, buffer = 0; + + function flushMessages() { + if (!messageBuffer) return; + if (runtimeInitialized) { + var temp = messageBuffer; + messageBuffer = null; + temp.forEach((message) => onmessage(message)); + } + } + + function messageResender() { + flushMessages(); + if (messageBuffer) { + setTimeout(messageResender, 100); // still more to do + } + } + + onmessage = (msg) => { + // if main has not yet been called (mem init file, other async things), buffer messages + if (!runtimeInitialized) { + if (!messageBuffer) { + messageBuffer = []; + setTimeout(messageResender, 100); + } + messageBuffer.push(msg); + return; + } + flushMessages(); + + var func = Module['_' + msg.data['funcName']]; + if (!func) abort('invalid worker function to call: ' + msg.data['funcName']); + var data = msg.data['data']; + if (data) { + if (!data.byteLength) data = new Uint8Array(data); + buffer = _realloc(buffer, data.length); + HEAPU8.set(data, buffer); + } + + workerResponded = false; + workerCallbackId = msg.data['callbackId']; + if (data) { + func(buffer, data.length); + } else { + func(0, 0); + } + } +})(); diff --git a/src/closure-externs/audio-worklet-externs.js b/src/closure-externs/audio-worklet-externs.js new file mode 100644 index 0000000000000..45ba6da819d9b --- /dev/null +++ b/src/closure-externs/audio-worklet-externs.js @@ -0,0 +1,11 @@ +/* + * AudioWorkletGlobalScope globals + */ +var registerProcessor = function(name, obj) {}; +var currentFrame; +var currentTime; +var sampleRate; +/** + * @suppress {duplicate, checkTypes} + */ +var port; diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index 9fb433b70a6eb..774f46f847460 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -14,16 +14,11 @@ // Special placeholder for `import.meta` and `await import`. var EMSCRIPTEN$IMPORT$META; var EMSCRIPTEN$AWAIT$IMPORT; +var EMSCRIPTEN$AWAIT; // Don't minify createRequire var createRequire; -// Don't minify startWorker which we use to start workers once the runtime is ready. -/** - * @param {Object} Module - */ -var startWorker = function(Module) {}; - // Closure externs used by library_sockfs.js /** @@ -69,6 +64,11 @@ var Atomics = {}; Atomics.compareExchange = function() {}; Atomics.exchange = function() {}; Atomics.wait = function() {}; +/** + * @param {number=} maxWaitMilliseconds + * @suppress {duplicate, checkTypes} + */ +Atomics.waitAsync = function(i32a, index, value, maxWaitMilliseconds) {}; Atomics.notify = function() {}; Atomics.load = function() {}; Atomics.store = function() {}; @@ -108,10 +108,24 @@ WebAssembly.Instance.prototype.exports; * @type {!ArrayBuffer} */ WebAssembly.Memory.prototype.buffer; +/** + * @returns {ArrayBuffer} + */ +WebAssembly.Memory.prototype.toResizableBuffer = function() {}; /** * @type {number} */ WebAssembly.Table.prototype.length; +/** + * @param {!Function} func + * @returns {Function} + */ +WebAssembly.promising = function(func) {}; +/** + * @constructor + * @param {!Function} func + */ +WebAssembly.Suspending = function(func) {}; /** * @record @@ -125,26 +139,13 @@ FunctionType.prototype.parameters; * @type {Array} */ FunctionType.prototype.results; -/** - * @record - */ - function FunctionUsage() {} - /** - * @type {string|undefined} - */ -FunctionUsage.prototype.promising; - /** - * @type {string|undefined} - */ -FunctionUsage.prototype.suspending; /** * @constructor * @param {!FunctionType} type * @param {!Function} func - * @param {FunctionUsage=} usage */ -WebAssembly.Function = function(type, func, usage) {}; +WebAssembly.Function = function(type, func) {}; /** * @param {Function} func * @return {FunctionType} @@ -196,14 +197,6 @@ var removeEventListener = function (type, listener) {}; */ var close; -// Due to the way MODULARIZE works, Closure is run on generated code that does not define _scriptDir, -// but only after MODULARIZE has finished, _scriptDir is injected to the generated code. -// Therefore it cannot be minified. -/** - * @suppress {duplicate, undefinedVars} - */ -var _scriptDir; - // Closure run on asm.js uses a hack to execute only on shell code, declare externs needed for it. /** * @suppress {undefinedVars} @@ -229,25 +222,29 @@ var outerHeight; var event; var devicePixelRatio; -/* - * AudioWorkletGlobalScope globals - */ -var registerProcessor = function(name, obj) {}; -var currentFrame; -var currentTime; -var sampleRate; - /* * Avoid closure minifying anything to "id". See #13965 */ var id; +/** + * Used in MODULARIZE mode as the name of the incoming module argument. + * This is generated outside of the code we pass to closure so from closure's + * POV this is "extern". + */ var moduleArg; +/** + * Used in MODULARIZE mode. + * We need to access this after the code we pass to closure so from closure's + * POV this is "extern". + */ +var moduleRtn; + /** * This was removed from upstream closure compiler in * https://github.com/google/closure-compiler/commit/f83322c1b. - * Perhaps we should remove it do? + * Perhaps we should remove it too? * * @param {MediaStreamConstraints} constraints A MediaStreamConstraints object. * @param {function(!MediaStream)} successCallback @@ -260,3 +257,14 @@ var moduleArg; */ Navigator.prototype.webkitGetUserMedia = function( constraints, successCallback, errorCallback) {}; + +/** + * A symbol from the explicit resource management proposal that isn't yet part of Closure. + * @type {symbol} + */ +Symbol.dispose; + +// Common between node-externs and v8-externs +var os = {}; + +AudioWorkletProcessor.parameterDescriptors; diff --git a/src/closure-externs/minimal_runtime_worker_externs.js b/src/closure-externs/minimal_runtime_worker_externs.js deleted file mode 100644 index bc733f3f661ad..0000000000000 --- a/src/closure-externs/minimal_runtime_worker_externs.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @license - * Copyright 2020 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// These externs are needed for MINIMAL_RUNTIME + -pthread -// This file should go away in the future when worker.js is refactored to live inside the JS module. - -/** @suppress {duplicate} */ -var ENVIRONMENT_IS_PTHREAD; -/** @suppress {duplicate} */ -var wasmMemory; diff --git a/src/closure-externs/modularize-externs.js b/src/closure-externs/modularize-externs.js new file mode 100644 index 0000000000000..b0fec4a9dc4b0 --- /dev/null +++ b/src/closure-externs/modularize-externs.js @@ -0,0 +1,8 @@ +// In MODULARIZE mode the JS code may be executed later, after `document.currentScript` is gone, so we store +// it to `_scriptName` outside the wrapper function. Therefore, it cannot be minified. +// In EXPORT_ES6 mode we use `import.meta.url` and for Node.js CommonJS builds we use `__filename`. + +/** + * @suppress {duplicate, undefinedVars} + */ +var _scriptName; diff --git a/src/closure-externs/node-externs.js b/src/closure-externs/node-externs.js index 483a4783d8d23..35c1d058dc19a 100644 --- a/src/closure-externs/node-externs.js +++ b/src/closure-externs/node-externs.js @@ -86,6 +86,12 @@ Buffer.from = function(arrayBufferOrString, byteOffsetOrEncoding, length) {}; */ Buffer.alloc = function(size, fill, encoding) {}; +/** + * @return {boolean} + * @nosideeffects + */ +Buffer.isBuffer = function(obj) {}; + /** * @param {number=} start * @param {number=} end @@ -104,3 +110,59 @@ Buffer.prototype.toString = function(encoding, start, end) {}; Worker.prototype.ref = function() {}; Worker.prototype.unref = function() {}; + +/** + * @type {number} + */ +fs.Stats.prototype.atimeMs; + +/** + * @type {number} + */ +fs.Stats.prototype.mtimeMs; + +/** + * @type {number} + */ +fs.Stats.prototype.ctimeMs; + +/** + * @type {number} + */ +fs.Stats.prototype.blksize; + +/** + * @param {string} p + * @return {boolean} + * @nosideeffects + */ +path.isAbsolute; + +/** + * @type {Object.} + */ +path.posix; + +crypto.randomFillSync; + +/** + * @suppress {duplicate} + */ +var worker_threads = {}; + +/** + * @type {boolean} + */ +worker_threads.isMainThread; + +/** + * @type {function()} + */ +worker_threads.Worker; + +/** + * @type {Object} + */ +worker_threads.workerData; + +worker_threads.parentPort; diff --git a/src/closure-externs/v8-externs.js b/src/closure-externs/v8-externs.js index 11eda1ddff882..4f602ac2cdc82 100644 --- a/src/closure-externs/v8-externs.js +++ b/src/closure-externs/v8-externs.js @@ -32,3 +32,10 @@ var scriptArgs = []; * @suppress {duplicate} */ var quit = function(status) {}; + +/** + * @param {string} cmd + * @param {Array.=} args + * @return {string} + */ +os.system = function (cmd, args) {}; diff --git a/src/closure-externs/webgpu-externs.js b/src/closure-externs/webgpu-externs.js deleted file mode 100644 index 9a855e8e532be..0000000000000 --- a/src/closure-externs/webgpu-externs.js +++ /dev/null @@ -1,573 +0,0 @@ -/* - * WebGPU globals - * Generated using https://github.com/kainino0x/webidl-to-closure-externs - * against the spec's WebIDL: https://gpuweb.github.io/gpuweb/webgpu.idl - */ - -/** @type {?GPU} */ -Navigator.prototype.gpu; - -/** @type {?GPU} */ -WorkerNavigator.prototype.gpu; - -const GPUBufferUsage = {}; -/** @type {number} */ -GPUBufferUsage.MAP_READ; -/** @type {number} */ -GPUBufferUsage.MAP_WRITE; -/** @type {number} */ -GPUBufferUsage.COPY_SRC; -/** @type {number} */ -GPUBufferUsage.COPY_DST; -/** @type {number} */ -GPUBufferUsage.INDEX; -/** @type {number} */ -GPUBufferUsage.VERTEX; -/** @type {number} */ -GPUBufferUsage.UNIFORM; -/** @type {number} */ -GPUBufferUsage.STORAGE; -/** @type {number} */ -GPUBufferUsage.INDIRECT; -/** @type {number} */ -GPUBufferUsage.QUERY_RESOLVE; - -const GPUMapMode = {}; -/** @type {number} */ -GPUMapMode.READ; -/** @type {number} */ -GPUMapMode.WRITE; - -const GPUTextureUsage = {}; -/** @type {number} */ -GPUTextureUsage.COPY_SRC; -/** @type {number} */ -GPUTextureUsage.COPY_DST; -/** @type {number} */ -GPUTextureUsage.TEXTURE_BINDING; -/** @type {number} */ -GPUTextureUsage.STORAGE_BINDING; -/** @type {number} */ -GPUTextureUsage.RENDER_ATTACHMENT; - -const GPUShaderStage = {}; -/** @type {number} */ -GPUShaderStage.VERTEX; -/** @type {number} */ -GPUShaderStage.FRAGMENT; -/** @type {number} */ -GPUShaderStage.COMPUTE; - -const GPUColorWrite = {}; -/** @type {number} */ -GPUColorWrite.RED; -/** @type {number} */ -GPUColorWrite.GREEN; -/** @type {number} */ -GPUColorWrite.BLUE; -/** @type {number} */ -GPUColorWrite.ALPHA; -/** @type {number} */ -GPUColorWrite.ALL; - -/** @constructor */ -function GPUSupportedLimits() {} -/** @type {number} */ -GPUSupportedLimits.prototype.maxTextureDimension1D; -/** @type {number} */ -GPUSupportedLimits.prototype.maxTextureDimension2D; -/** @type {number} */ -GPUSupportedLimits.prototype.maxTextureDimension3D; -/** @type {number} */ -GPUSupportedLimits.prototype.maxTextureArrayLayers; -/** @type {number} */ -GPUSupportedLimits.prototype.maxBindGroups; -/** @type {number} */ -GPUSupportedLimits.prototype.maxBindGroupsPlusVertexBuffers; -/** @type {number} */ -GPUSupportedLimits.prototype.maxBindingsPerBindGroup; -/** @type {number} */ -GPUSupportedLimits.prototype.maxDynamicUniformBuffersPerPipelineLayout; -/** @type {number} */ -GPUSupportedLimits.prototype.maxDynamicStorageBuffersPerPipelineLayout; -/** @type {number} */ -GPUSupportedLimits.prototype.maxSampledTexturesPerShaderStage; -/** @type {number} */ -GPUSupportedLimits.prototype.maxSamplersPerShaderStage; -/** @type {number} */ -GPUSupportedLimits.prototype.maxStorageBuffersPerShaderStage; -/** @type {number} */ -GPUSupportedLimits.prototype.maxStorageTexturesPerShaderStage; -/** @type {number} */ -GPUSupportedLimits.prototype.maxUniformBuffersPerShaderStage; -/** @type {number} */ -GPUSupportedLimits.prototype.maxUniformBufferBindingSize; -/** @type {number} */ -GPUSupportedLimits.prototype.maxStorageBufferBindingSize; -/** @type {number} */ -GPUSupportedLimits.prototype.minUniformBufferOffsetAlignment; -/** @type {number} */ -GPUSupportedLimits.prototype.minStorageBufferOffsetAlignment; -/** @type {number} */ -GPUSupportedLimits.prototype.maxVertexBuffers; -/** @type {number} */ -GPUSupportedLimits.prototype.maxBufferSize; -/** @type {number} */ -GPUSupportedLimits.prototype.maxVertexAttributes; -/** @type {number} */ -GPUSupportedLimits.prototype.maxVertexBufferArrayStride; -/** @type {number} */ -GPUSupportedLimits.prototype.maxInterStageShaderComponents; -/** @type {number} */ -GPUSupportedLimits.prototype.maxInterStageShaderVariables; -/** @type {number} */ -GPUSupportedLimits.prototype.maxColorAttachments; -/** @type {number} */ -GPUSupportedLimits.prototype.maxColorAttachmentBytesPerSample; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeWorkgroupStorageSize; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeInvocationsPerWorkgroup; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeWorkgroupSizeX; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeWorkgroupSizeY; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeWorkgroupSizeZ; -/** @type {number} */ -GPUSupportedLimits.prototype.maxComputeWorkgroupsPerDimension; - -/** @constructor */ -function GPUSupportedFeatures() {} -/** @type {number} */ -GPUSupportedFeatures.prototype.size; -/** @return {!Iterable} */ -GPUSupportedFeatures.prototype.entries = function() {}; -/** @return {!Iterable} */ -GPUSupportedFeatures.prototype.keys = function() {}; -/** @return {!Iterable} */ -GPUSupportedFeatures.prototype.values = function() {}; -/** @return {undefined} */ -GPUSupportedFeatures.prototype.forEach = function() {}; -/** @return {boolean} */ -GPUSupportedFeatures.prototype.has = function() {}; - -/** @constructor */ -function WGSLLanguageFeatures() {} -/** @type {number} */ -WGSLLanguageFeatures.prototype.size; -/** @return {!Iterable} */ -WGSLLanguageFeatures.prototype.entries = function() {}; -/** @return {!Iterable} */ -WGSLLanguageFeatures.prototype.keys = function() {}; -/** @return {!Iterable} */ -WGSLLanguageFeatures.prototype.values = function() {}; -/** @return {undefined} */ -WGSLLanguageFeatures.prototype.forEach = function() {}; -/** @return {boolean} */ -WGSLLanguageFeatures.prototype.has = function() {}; - -/** @constructor */ -function GPUAdapterInfo() {} -/** @type {string} */ -GPUAdapterInfo.prototype.vendor; -/** @type {string} */ -GPUAdapterInfo.prototype.architecture; -/** @type {string} */ -GPUAdapterInfo.prototype.device; -/** @type {string} */ -GPUAdapterInfo.prototype.description; - -/** @constructor */ -function GPU() {} -/** @return {!Promise} */ -GPU.prototype.requestAdapter = function() {}; -/** @return {string} */ -GPU.prototype.getPreferredCanvasFormat = function() {}; -/** @type {!WGSLLanguageFeatures} */ -GPU.prototype.wgslLanguageFeatures; - -/** @constructor */ -function GPUAdapter() {} -/** @type {!GPUSupportedFeatures} */ -GPUAdapter.prototype.features; -/** @type {!GPUSupportedLimits} */ -GPUAdapter.prototype.limits; -/** @type {boolean} */ -GPUAdapter.prototype.isFallbackAdapter; -/** @return {!Promise} */ -GPUAdapter.prototype.requestDevice = function() {}; -/** @return {!Promise} */ -GPUAdapter.prototype.requestAdapterInfo = function() {}; - -/** @constructor */ -function GPUDevice() {} -/** @type {string} */ -GPUDevice.prototype.label; -/** @type {!GPUSupportedFeatures} */ -GPUDevice.prototype.features; -/** @type {!GPUSupportedLimits} */ -GPUDevice.prototype.limits; -/** @type {!GPUQueue} */ -GPUDevice.prototype.queue; -/** @return {undefined} */ -GPUDevice.prototype.destroy = function() {}; -/** @return {!GPUBuffer} */ -GPUDevice.prototype.createBuffer = function() {}; -/** @return {!GPUTexture} */ -GPUDevice.prototype.createTexture = function() {}; -/** @return {!GPUSampler} */ -GPUDevice.prototype.createSampler = function() {}; -/** @return {!GPUExternalTexture} */ -GPUDevice.prototype.importExternalTexture = function() {}; -/** @return {!GPUBindGroupLayout} */ -GPUDevice.prototype.createBindGroupLayout = function() {}; -/** @return {!GPUPipelineLayout} */ -GPUDevice.prototype.createPipelineLayout = function() {}; -/** @return {!GPUBindGroup} */ -GPUDevice.prototype.createBindGroup = function() {}; -/** @return {!GPUShaderModule} */ -GPUDevice.prototype.createShaderModule = function() {}; -/** @return {!GPUComputePipeline} */ -GPUDevice.prototype.createComputePipeline = function() {}; -/** @return {!GPURenderPipeline} */ -GPUDevice.prototype.createRenderPipeline = function() {}; -/** @return {!Promise} */ -GPUDevice.prototype.createComputePipelineAsync = function() {}; -/** @return {!Promise} */ -GPUDevice.prototype.createRenderPipelineAsync = function() {}; -/** @return {!GPUCommandEncoder} */ -GPUDevice.prototype.createCommandEncoder = function() {}; -/** @return {!GPURenderBundleEncoder} */ -GPUDevice.prototype.createRenderBundleEncoder = function() {}; -/** @return {!GPUQuerySet} */ -GPUDevice.prototype.createQuerySet = function() {}; -/** @type {!Promise} */ -GPUDevice.prototype.lost; -/** @return {undefined} */ -GPUDevice.prototype.pushErrorScope = function() {}; -/** @return {!Promise} */ -GPUDevice.prototype.popErrorScope = function() {}; -/** @type {!Function} */ -GPUDevice.prototype.onuncapturederror; - -/** @constructor */ -function GPUBuffer() {} -/** @type {string} */ -GPUBuffer.prototype.label; -/** @type {number} */ -GPUBuffer.prototype.size; -/** @type {number} */ -GPUBuffer.prototype.usage; -/** @type {string} */ -GPUBuffer.prototype.mapState; -/** @return {!Promise} */ -GPUBuffer.prototype.mapAsync = function() {}; -/** @return {!ArrayBuffer} */ -GPUBuffer.prototype.getMappedRange = function() {}; -/** @return {undefined} */ -GPUBuffer.prototype.unmap = function() {}; -/** @return {undefined} */ -GPUBuffer.prototype.destroy = function() {}; - -/** @constructor */ -function GPUTexture() {} -/** @type {string} */ -GPUTexture.prototype.label; -/** @return {!GPUTextureView} */ -GPUTexture.prototype.createView = function() {}; -/** @return {undefined} */ -GPUTexture.prototype.destroy = function() {}; -/** @type {number} */ -GPUTexture.prototype.width; -/** @type {number} */ -GPUTexture.prototype.height; -/** @type {number} */ -GPUTexture.prototype.depthOrArrayLayers; -/** @type {number} */ -GPUTexture.prototype.mipLevelCount; -/** @type {number} */ -GPUTexture.prototype.sampleCount; -/** @type {string} */ -GPUTexture.prototype.dimension; -/** @type {string} */ -GPUTexture.prototype.format; -/** @type {number} */ -GPUTexture.prototype.usage; - -/** @constructor */ -function GPUTextureView() {} -/** @type {string} */ -GPUTextureView.prototype.label; - -/** @constructor */ -function GPUExternalTexture() {} -/** @type {string} */ -GPUExternalTexture.prototype.label; - -/** @constructor */ -function GPUSampler() {} -/** @type {string} */ -GPUSampler.prototype.label; - -/** @constructor */ -function GPUBindGroupLayout() {} -/** @type {string} */ -GPUBindGroupLayout.prototype.label; - -/** @constructor */ -function GPUBindGroup() {} -/** @type {string} */ -GPUBindGroup.prototype.label; - -/** @constructor */ -function GPUPipelineLayout() {} -/** @type {string} */ -GPUPipelineLayout.prototype.label; - -/** @constructor */ -function GPUShaderModule() {} -/** @type {string} */ -GPUShaderModule.prototype.label; -/** @return {!Promise} */ -GPUShaderModule.prototype.getCompilationInfo = function() {}; - -/** @constructor */ -function GPUCompilationMessage() {} -/** @type {string} */ -GPUCompilationMessage.prototype.message; -/** @type {string} */ -GPUCompilationMessage.prototype.type; -/** @type {number} */ -GPUCompilationMessage.prototype.lineNum; -/** @type {number} */ -GPUCompilationMessage.prototype.linePos; -/** @type {number} */ -GPUCompilationMessage.prototype.offset; -/** @type {number} */ -GPUCompilationMessage.prototype.length; - -/** @constructor */ -function GPUCompilationInfo() {} -/** @type {!Array} */ -GPUCompilationInfo.prototype.messages; - -/** @constructor */ -function GPUPipelineError() {} -/** @type {string} */ -GPUPipelineError.prototype.reason; - -/** @constructor */ -function GPUComputePipeline() {} -/** @type {string} */ -GPUComputePipeline.prototype.label; -/** @return {!GPUBindGroupLayout} */ -GPUComputePipeline.prototype.getBindGroupLayout = function() {}; - -/** @constructor */ -function GPURenderPipeline() {} -/** @type {string} */ -GPURenderPipeline.prototype.label; -/** @return {!GPUBindGroupLayout} */ -GPURenderPipeline.prototype.getBindGroupLayout = function() {}; - -/** @constructor */ -function GPUCommandBuffer() {} -/** @type {string} */ -GPUCommandBuffer.prototype.label; - -/** @constructor */ -function GPUCommandEncoder() {} -/** @type {string} */ -GPUCommandEncoder.prototype.label; -/** @return {undefined} */ -GPUCommandEncoder.prototype.pushDebugGroup = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.popDebugGroup = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.insertDebugMarker = function() {}; -/** @return {!GPURenderPassEncoder} */ -GPUCommandEncoder.prototype.beginRenderPass = function() {}; -/** @return {!GPUComputePassEncoder} */ -GPUCommandEncoder.prototype.beginComputePass = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.copyBufferToBuffer = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.copyBufferToTexture = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.copyTextureToBuffer = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.copyTextureToTexture = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.clearBuffer = function() {}; -/** @return {undefined} */ -GPUCommandEncoder.prototype.resolveQuerySet = function() {}; -/** @return {!GPUCommandBuffer} */ -GPUCommandEncoder.prototype.finish = function() {}; - -/** @constructor */ -function GPUComputePassEncoder() {} -/** @type {string} */ -GPUComputePassEncoder.prototype.label; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.pushDebugGroup = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.popDebugGroup = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.insertDebugMarker = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.setPipeline = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.dispatchWorkgroups = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.dispatchWorkgroupsIndirect = function() {}; -/** @return {undefined} */ -GPUComputePassEncoder.prototype.end = function() {}; - -/** @constructor */ -function GPURenderPassEncoder() {} -/** @type {string} */ -GPURenderPassEncoder.prototype.label; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.pushDebugGroup = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.popDebugGroup = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.insertDebugMarker = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setPipeline = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setIndexBuffer = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setVertexBuffer = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.draw = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.drawIndexed = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.drawIndirect = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.drawIndexedIndirect = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setViewport = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setScissorRect = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setBlendConstant = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.setStencilReference = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.beginOcclusionQuery = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.endOcclusionQuery = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.executeBundles = function() {}; -/** @return {undefined} */ -GPURenderPassEncoder.prototype.end = function() {}; - -/** @constructor */ -function GPURenderBundle() {} -/** @type {string} */ -GPURenderBundle.prototype.label; - -/** @constructor */ -function GPURenderBundleEncoder() {} -/** @type {string} */ -GPURenderBundleEncoder.prototype.label; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.pushDebugGroup = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.popDebugGroup = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.insertDebugMarker = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.setBindGroup = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.setPipeline = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.setIndexBuffer = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.setVertexBuffer = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.draw = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.drawIndexed = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.drawIndirect = function() {}; -/** @return {undefined} */ -GPURenderBundleEncoder.prototype.drawIndexedIndirect = function() {}; -/** @return {!GPURenderBundle} */ -GPURenderBundleEncoder.prototype.finish = function() {}; - -/** @constructor */ -function GPUQueue() {} -/** @type {string} */ -GPUQueue.prototype.label; -/** @return {undefined} */ -GPUQueue.prototype.submit = function() {}; -/** @return {!Promise} */ -GPUQueue.prototype.onSubmittedWorkDone = function() {}; -/** @return {undefined} */ -GPUQueue.prototype.writeBuffer = function() {}; -/** @return {undefined} */ -GPUQueue.prototype.writeTexture = function() {}; -/** @return {undefined} */ -GPUQueue.prototype.copyExternalImageToTexture = function() {}; - -/** @constructor */ -function GPUQuerySet() {} -/** @type {string} */ -GPUQuerySet.prototype.label; -/** @return {undefined} */ -GPUQuerySet.prototype.destroy = function() {}; -/** @type {string} */ -GPUQuerySet.prototype.type; -/** @type {number} */ -GPUQuerySet.prototype.count; - -/** @constructor */ -function GPUCanvasContext() {} -/** @type {!HTMLCanvasElement|!OffscreenCanvas} */ -GPUCanvasContext.prototype.canvas; -/** @return {undefined} */ -GPUCanvasContext.prototype.configure = function() {}; -/** @return {undefined} */ -GPUCanvasContext.prototype.unconfigure = function() {}; -/** @return {!GPUTexture} */ -GPUCanvasContext.prototype.getCurrentTexture = function() {}; - -/** @constructor */ -function GPUDeviceLostInfo() {} -/** @type {string} */ -GPUDeviceLostInfo.prototype.reason; -/** @type {string} */ -GPUDeviceLostInfo.prototype.message; - -/** @constructor */ -function GPUError() {} -/** @type {string} */ -GPUError.prototype.message; - -/** @constructor */ -function GPUValidationError() {} - -/** @constructor */ -function GPUOutOfMemoryError() {} - -/** @constructor */ -function GPUInternalError() {} - -/** @constructor */ -function GPUUncapturedErrorEvent() {} -/** @type {!GPUError} */ -GPUUncapturedErrorEvent.prototype.error; diff --git a/src/compiler.mjs b/src/compiler.mjs deleted file mode 100755 index 10dd60ec7e6da..0000000000000 --- a/src/compiler.mjs +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env node -/** - * @license - * Copyright 2010 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// LLVM => JavaScript compiler, main entry point - -import * as fs from 'fs'; -import * as path from 'path'; -import * as vm from 'vm'; -import * as url from 'url'; -import assert from 'assert'; - -globalThis.vm = vm; -globalThis.assert = assert; -globalThis.nodePath = path; - -globalThis.print = (x) => { - process.stdout.write(x + '\n'); -}; - -globalThis.printErr = (x) => { - process.stderr.write(x + '\n'); -}; - -function find(filename) { - assert(filename); - const dirname = url.fileURLToPath(new URL('.', import.meta.url)); - const prefixes = [dirname, process.cwd()]; - for (let i = 0; i < prefixes.length; ++i) { - const combined = path.join(prefixes[i], filename); - if (fs.existsSync(combined)) { - return combined; - } - } - return filename; -} - -globalThis.read = (filename) => { - assert(filename); - const absolute = find(filename); - return fs.readFileSync(absolute).toString(); -}; - -function load(f) { - vm.runInThisContext(read(f), {filename: find(f)}); -} - -// Basic utilities -load('utility.js'); - -// Load default settings -load('./settings.js'); -load('./settings_internal.js'); - -const argv = process.argv.slice(2); -const symbolsOnlyArg = argv.indexOf('--symbols-only'); -if (symbolsOnlyArg != -1) { - argv.splice(symbolsOnlyArg, 1); -} - -// Load settings from JSON passed on the command line -const settingsFile = argv[0]; -assert(settingsFile); - -const settings = JSON.parse(read(settingsFile)); -Object.assign(global, settings); - -globalThis.symbolsOnly = symbolsOnlyArg != -1; - -// In case compiler.js is run directly (as in gen_sig_info) -// ALL_INCOMING_MODULE_JS_API might not be populated yet. -if (!ALL_INCOMING_MODULE_JS_API.length) { - ALL_INCOMING_MODULE_JS_API = INCOMING_MODULE_JS_API; -} - -EXPORTED_FUNCTIONS = new Set(EXPORTED_FUNCTIONS); -WASM_EXPORTS = new Set(WASM_EXPORTS); -SIDE_MODULE_EXPORTS = new Set(SIDE_MODULE_EXPORTS); -INCOMING_MODULE_JS_API = new Set(INCOMING_MODULE_JS_API); -ALL_INCOMING_MODULE_JS_API = new Set(ALL_INCOMING_MODULE_JS_API); -WEAK_IMPORTS = new Set(WEAK_IMPORTS); -if (symbolsOnly) { - INCLUDE_FULL_LIBRARY = 1; -} - -// Side modules are pure wasm and have no JS -assert( - !SIDE_MODULE || (ASYNCIFY && globalThis.symbolsOnly), - 'JS compiler should only run on side modules if asyncify is used.', -); - -// Load compiler code - -load('modules.js'); -load('parseTools.js'); -load('jsifier.js'); -if (!STRICT) { - load('parseTools_legacy.js'); -} - -// =============================== -// Main -// =============================== - -const B = new Benchmarker(); - -try { - runJSify(); - - B.print('glue'); -} catch (err) { - if (err.toString().includes('Aborting compilation due to previous errors')) { - // Compiler failed on user error, don't print the stacktrace in this case. - printErr(err); - } else { - // Compiler failed on internal compiler error! - printErr('Internal compiler error in src/compiler.js!'); - printErr('Please create a bug report at https://github.com/emscripten-core/emscripten/issues/'); - printErr( - 'with a log of the build and the input files used to run. Exception message: "' + - (err.stack || err), - ); - } - - // Work around a node.js bug where stdout buffer is not flushed at process exit: - // Instead of process.exit() directly, wait for stdout flush event. - // See https://github.com/joyent/node/issues/1669 and https://github.com/emscripten-core/emscripten/issues/2582 - // Workaround is based on https://github.com/RReverser/acorn/commit/50ab143cecc9ed71a2d66f78b4aec3bb2e9844f6 - process.stdout.once('drain', () => process.exit(1)); - // Make sure to print something to force the drain event to occur, in case the - // stdout buffer was empty. - console.log(' '); - // Work around another node bug where sometimes 'drain' is never fired - make - // another effort to emit the exit status, after a significant delay (if node - // hasn't fired drain by then, give up) - setTimeout(() => process.exit(1), 500); -} diff --git a/src/cpuprofiler.js b/src/cpuprofiler.js index f55ebec78df31..a4d36dee5470c 100644 --- a/src/cpuprofiler.js +++ b/src/cpuprofiler.js @@ -15,7 +15,7 @@ // That doesn't work for Chrome in turn, so need to resort to user agent // sniffing.. (sad :/) if (!performance.realNow) { - var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + var isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent); if (isSafari) { var realPerformance = performance; performance = { @@ -225,7 +225,7 @@ var emscriptenCpuProfiler = { if (i != 2) cs += ' <- '; var fn = funcs[i]; var at = fn.indexOf('@'); - if (at != -1) fn = fn.substr(0, at); + if (at != -1) fn = fn.slice(0, at); fn = fn.trim(); cs += '"' + fn + '"'; } @@ -593,7 +593,7 @@ var emscriptenCpuProfiler = { detectWebGLContext() { if (Module['canvas']?.GLctxObject?.GLctx) return Module['canvas'].GLctxObject.GLctx; else if (typeof GLctx != 'undefined') return GLctx; - else if (Module.ctx) return Module.ctx; + else if (Module['ctx']) return Module['ctx']; return null; }, @@ -655,7 +655,7 @@ var emscriptenCpuProfiler = { case 9: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9); this.endSection(section); return ret; }; break; case 10: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); this.endSection(section); return ret; }; break; case 11: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); this.endSection(section); return ret; }; break; - default: throw 'hookWebGL failed! Unexpected length ' + glCtx[realf].length; + default: throw new Error('hookWebGL failed! Unexpected length ' + glCtx[realf].length); } }, @@ -756,7 +756,7 @@ function cpuprofiler_add_hooks() { emscriptenCpuProfiler.initialize(); } -if (typeof document != 'undefined') { +if (globalThis.document) { emscriptenCpuProfiler.initialize(); } diff --git a/src/deterministic.js b/src/deterministic.js index 4a99e37487b9b..9c4b3ca5bd74f 100644 --- a/src/deterministic.js +++ b/src/deterministic.js @@ -17,27 +17,11 @@ function deterministicNow() { Date.now = deterministicNow; -// Setting performance.now to deterministicNow doesn't work so we instead -// use a helper function in parseTools (getPerformanceNow()) to call it -// directly. -// if (typeof performance == 'object') performance.now = Date.now; - -Module['thisProgram'] = 'thisProgram'; // for consistency between different builds than between runs of the same build - -function hashMemory(id) { - var ret = 0; - var len = _sbrk(0); - for (var i = 0; i < len; i++) { - ret = (ret*17 + HEAPU8[i])|0; - } - return id + ':' + ret; -} - -function hashString(s) { - var ret = 0; - for (var i = 0; i < s.length; i++) { - ret = (ret*17 + s.charCodeAt(i))|0; - } - return ret; -} - +// Note: this approach does not work on certain versions of Node.js +// Specifically it seems like its not possible to override performance.now on +// node v16 through v18. +// See getPerformanceNow in parseTools.mjs for how we deal with this. +if (globalThis.performance) performance.now = deterministicNow; + +// for consistency between different builds than between runs of the same build +Module['thisProgram'] = 'thisProgram'; diff --git a/src/embind/embind.js b/src/embind/embind.js deleted file mode 100644 index fadae6bdbf465..0000000000000 --- a/src/embind/embind.js +++ /dev/null @@ -1,2355 +0,0 @@ -// Copyright 2012 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. - -/*global addToLibrary*/ - -/*global Module, asm*/ -/*global _malloc, _free, _memcpy*/ -/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64*/ -/*global readLatin1String*/ -/*global Emval, emval_handle_array, __emval_decref*/ -/*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */ - -// -- jshint doesn't understand library syntax, so we need to specifically tell it about the symbols we define -/*global typeDependencies, flushPendingDeletes, getTypeName, getBasestPointer, throwBindingError, UnboundTypeError, embindRepr, registeredInstances, registeredTypes*/ -/*global ensureOverloadTable, embind__requireFunction, awaitingDependencies, makeLegalFunctionName, embind_charCodes:true, registerType, createNamedFunction, RegisteredPointer, throwInternalError*/ -/*global floatReadValueFromPointer, integerReadValueFromPointer, enumReadValueFromPointer, replacePublicSymbol, craftInvokerFunction, tupleRegistrations*/ -/*global finalizationRegistry, attachFinalizer, detachFinalizer, releaseClassHandle, runDestructor*/ -/*global ClassHandle, makeClassHandle, structRegistrations, whenDependentTypesAreResolved, BindingError, deletionQueue, delayFunction:true, upcastPointer*/ -/*global exposePublicSymbol, heap32VectorToArray, newFunc, char_0, char_9*/ -/*global getInheritedInstanceCount, getLiveInheritedInstances, setDelayFunction, InternalError, runDestructors*/ -/*global requireRegisteredType, unregisterInheritedInstance, registerInheritedInstance, PureVirtualError, throwUnboundTypeError*/ -/*global assert, validateThis, downcastPointer, registeredPointers, RegisteredClass, getInheritedInstance */ -/*global throwInstanceAlreadyDeleted, shallowCopyInternalPointer*/ -/*global RegisteredPointer_fromWireType, constNoSmartPtrRawPointerToWireType, nonConstNoSmartPtrRawPointerToWireType, genericPointerToWireType*/ - -#include "embind/embind_shared.js" - -var LibraryEmbind = { - $UnboundTypeError__postset: "UnboundTypeError = Module['UnboundTypeError'] = extendError(Error, 'UnboundTypeError');", - $UnboundTypeError__deps: ['$extendError'], - $UnboundTypeError: undefined, - $PureVirtualError__postset: "PureVirtualError = Module['PureVirtualError'] = extendError(Error, 'PureVirtualError');", - $PureVirtualError__deps: ['$extendError'], - $PureVirtualError: undefined, - $GenericWireTypeSize: {{{ 2 * POINTER_SIZE }}}, -#if EMBIND_AOT - $InvokerFunctions: '<<< EMBIND_AOT_OUTPUT >>>', -#endif - // If register_type is used, emval will be registered multiple times for - // different type id's, but only a single type object is needed on the JS side - // for all of them. Store the type for reuse. - $EmValType__deps: ['_emval_decref', '$Emval', '$readPointer', '$GenericWireTypeSize'], - $EmValType: `{ - name: 'emscripten::val', - 'fromWireType': (handle) => { - var rv = Emval.toValue(handle); - __emval_decref(handle); - return rv; - }, - 'toWireType': (destructors, value) => Emval.toHandle(value), - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - destructorFunction: null, // This type does not need a destructor - - // TODO: do we need a deleteObject here? write a test where - // emval is passed into JS via an interface - }`, - $init_embind__deps: [ - '$getInheritedInstanceCount', '$getLiveInheritedInstances', - '$flushPendingDeletes', '$setDelayFunction'], - $init_embind__postset: 'init_embind();', - $init_embind: () => { - Module['getInheritedInstanceCount'] = getInheritedInstanceCount; - Module['getLiveInheritedInstances'] = getLiveInheritedInstances; - Module['flushPendingDeletes'] = flushPendingDeletes; - Module['setDelayFunction'] = setDelayFunction; - }, - - $throwUnboundTypeError__deps: ['$registeredTypes', '$typeDependencies', '$UnboundTypeError', '$getTypeName'], - $throwUnboundTypeError: (message, types) => { - var unboundTypes = []; - var seen = {}; - function visit(type) { - if (seen[type]) { - return; - } - if (registeredTypes[type]) { - return; - } - if (typeDependencies[type]) { - typeDependencies[type].forEach(visit); - return; - } - unboundTypes.push(type); - seen[type] = true; - } - types.forEach(visit); - - throw new UnboundTypeError(`${message}: ` + unboundTypes.map(getTypeName).join([', '])); - }, - - // Creates a function overload resolution table to the given method 'methodName' in the given prototype, - // if the overload table doesn't yet exist. - $ensureOverloadTable__deps: ['$throwBindingError'], - $ensureOverloadTable: (proto, methodName, humanName) => { - if (undefined === proto[methodName].overloadTable) { - var prevFunc = proto[methodName]; - // Inject an overload resolver function that routes to the appropriate overload based on the number of arguments. - proto[methodName] = function(...args) { - // TODO This check can be removed in -O3 level "unsafe" optimizations. - if (!proto[methodName].overloadTable.hasOwnProperty(args.length)) { - throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`); - } - return proto[methodName].overloadTable[args.length].apply(this, args); - }; - // Move the previous function into the overload table. - proto[methodName].overloadTable = []; - proto[methodName].overloadTable[prevFunc.argCount] = prevFunc; - } - }, - - /* - Registers a symbol (function, class, enum, ...) as part of the Module JS object so that - hand-written code is able to access that symbol via 'Module.name'. - name: The name of the symbol that's being exposed. - value: The object itself to expose (function, class, ...) - numArguments: For functions, specifies the number of arguments the function takes in. For other types, unused and undefined. - - To implement support for multiple overloads of a function, an 'overload selector' function is used. That selector function chooses - the appropriate overload to call from an function overload table. This selector function is only used if multiple overloads are - actually registered, since it carries a slight performance penalty. */ - $exposePublicSymbol__deps: ['$ensureOverloadTable', '$throwBindingError'], - $exposePublicSymbol__docs: '/** @param {number=} numArguments */', - $exposePublicSymbol: (name, value, numArguments) => { - if (Module.hasOwnProperty(name)) { - if (undefined === numArguments || (undefined !== Module[name].overloadTable && undefined !== Module[name].overloadTable[numArguments])) { - throwBindingError(`Cannot register public name '${name}' twice`); - } - - // We are exposing a function with the same name as an existing function. Create an overload table and a function selector - // that routes between the two. - ensureOverloadTable(Module, name, name); - if (Module.hasOwnProperty(numArguments)) { - throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`); - } - // Add the new function into the overload table. - Module[name].overloadTable[numArguments] = value; - } - else { - Module[name] = value; - if (undefined !== numArguments) { - Module[name].numArguments = numArguments; - } - } - }, - - $replacePublicSymbol__deps: ['$throwInternalError'], - $replacePublicSymbol__docs: '/** @param {number=} numArguments */', - $replacePublicSymbol: (name, value, numArguments) => { - if (!Module.hasOwnProperty(name)) { - throwInternalError('Replacing nonexistent public symbol'); - } - // If there's an overload table for this symbol, replace the symbol in the overload table instead. - if (undefined !== Module[name].overloadTable && undefined !== numArguments) { - Module[name].overloadTable[numArguments] = value; - } - else { - Module[name] = value; - Module[name].argCount = numArguments; - } - }, - - // from https://github.com/imvu/imvujs/blob/master/src/error.js - $extendError__deps: ['$createNamedFunction'], - $extendError: (baseErrorType, errorName) => { - var errorClass = createNamedFunction(errorName, function(message) { - this.name = errorName; - this.message = message; - - var stack = (new Error(message)).stack; - if (stack !== undefined) { - this.stack = this.toString() + '\n' + - stack.replace(/^Error(:[^\n]*)?\n/, ''); - } - }); - errorClass.prototype = Object.create(baseErrorType.prototype); - errorClass.prototype.constructor = errorClass; - errorClass.prototype.toString = function() { - if (this.message === undefined) { - return this.name; - } else { - return `${this.name}: ${this.message}`; - } - }; - - return errorClass; - }, - - $createNamedFunction: (name, body) => Object.defineProperty(body, 'name', { - value: name - }), - // All browsers that support WebAssembly also support configurable function name, - // but we might be building for very old browsers via WASM2JS. -#if MIN_CHROME_VERSION < 43 || MIN_SAFARI_VERSION < 100101 || MIN_FIREFOX_VERSION < 38 - // In that case, check if configurable function name is supported at init time - // and, if not, replace with a fallback that returns function as-is as those browsers - // don't support other methods either. - $createNamedFunction__postset: ` - if (!Object.getOwnPropertyDescriptor(Function.prototype, 'name').configurable) { - createNamedFunction = (name, body) => body; - } - `, -#endif - - $embindRepr: (v) => { - if (v === null) { - return 'null'; - } - var t = typeof v; - if (t === 'object' || t === 'array' || t === 'function') { - return v.toString(); - } else { - return '' + v; - } - }, - - // raw pointer -> instance - $registeredInstances__deps: ['$init_embind'], - $registeredInstances: {}, - - $getBasestPointer__deps: ['$throwBindingError'], - $getBasestPointer: (class_, ptr) => { - if (ptr === undefined) { - throwBindingError('ptr should not be undefined'); - } - while (class_.baseClass) { - ptr = class_.upcast(ptr); - class_ = class_.baseClass; - } - return ptr; - }, - - $registerInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer', '$throwBindingError'], - $registerInheritedInstance: (class_, ptr, instance) => { - ptr = getBasestPointer(class_, ptr); - if (registeredInstances.hasOwnProperty(ptr)) { - throwBindingError(`Tried to register registered instance: ${ptr}`); - } else { - registeredInstances[ptr] = instance; - } - }, - - $unregisterInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer', '$throwBindingError'], - $unregisterInheritedInstance: (class_, ptr) => { - ptr = getBasestPointer(class_, ptr); - if (registeredInstances.hasOwnProperty(ptr)) { - delete registeredInstances[ptr]; - } else { - throwBindingError(`Tried to unregister unregistered instance: ${ptr}`); - } - }, - - $getInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer'], - $getInheritedInstance: (class_, ptr) => { - ptr = getBasestPointer(class_, ptr); - return registeredInstances[ptr]; - }, - - $getInheritedInstanceCount__deps: ['$registeredInstances'], - $getInheritedInstanceCount: () => Object.keys(registeredInstances).length, - - $getLiveInheritedInstances__deps: ['$registeredInstances'], - $getLiveInheritedInstances: () => { - var rv = []; - for (var k in registeredInstances) { - if (registeredInstances.hasOwnProperty(k)) { - rv.push(registeredInstances[k]); - } - } - return rv; - }, - - // class typeID -> {pointerType: ..., constPointerType: ...} - $registeredPointers: {}, - - $registerType__deps: ['$sharedRegisterType'], - $registerType__docs: '/** @param {Object=} options */', - $registerType: function(rawType, registeredInstance, options = {}) { - if (!('argPackAdvance' in registeredInstance)) { - throw new TypeError('registerType registeredInstance requires argPackAdvance'); - } - return sharedRegisterType(rawType, registeredInstance, options); - }, - - _embind_register_void__deps: ['$readLatin1String', '$registerType'], - _embind_register_void: (rawType, name) => { - name = readLatin1String(name); - registerType(rawType, { - isVoid: true, // void return values can be optimized out sometimes - name, - 'argPackAdvance': 0, - 'fromWireType': () => undefined, - // TODO: assert if anything else is given? - 'toWireType': (destructors, o) => undefined, - }); - }, - - _embind_register_bool__docs: '/** @suppress {globalThis} */', - _embind_register_bool__deps: ['$readLatin1String', '$registerType', '$GenericWireTypeSize'], - _embind_register_bool: (rawType, name, trueValue, falseValue) => { - name = readLatin1String(name); - registerType(rawType, { - name, - 'fromWireType': function(wt) { - // ambiguous emscripten ABI: sometimes return values are - // true or false, and sometimes integers (0 or 1) - return !!wt; - }, - 'toWireType': function(destructors, o) { - return o ? trueValue : falseValue; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': function(pointer) { - return this['fromWireType'](HEAPU8[pointer]); - }, - destructorFunction: null, // This type does not need a destructor - }); - }, - - $integerReadValueFromPointer__deps: [], - $integerReadValueFromPointer: (name, width, signed) => { - // integers are quite common, so generate very specialized functions - switch (width) { - case 1: return signed ? - (pointer) => {{{ makeGetValue('pointer', 0, 'i8') }}} : - (pointer) => {{{ makeGetValue('pointer', 0, 'u8') }}}; - case 2: return signed ? - (pointer) => {{{ makeGetValue('pointer', 0, 'i16') }}} : - (pointer) => {{{ makeGetValue('pointer', 0, 'u16') }}} - case 4: return signed ? - (pointer) => {{{ makeGetValue('pointer', 0, 'i32') }}} : - (pointer) => {{{ makeGetValue('pointer', 0, 'u32') }}} -#if WASM_BIGINT - case 8: return signed ? - (pointer) => {{{ makeGetValue('pointer', 0, 'i64') }}} : - (pointer) => {{{ makeGetValue('pointer', 0, 'u64') }}} -#endif - default: - throw new TypeError(`invalid integer width (${width}): ${name}`); - } - }, - - $enumReadValueFromPointer__deps: [], - $enumReadValueFromPointer: (name, width, signed) => { - switch (width) { - case 1: return signed ? - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'i8') }}}) } : - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'u8') }}}) }; - case 2: return signed ? - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'i16') }}}) } : - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'u16') }}}) }; - case 4: return signed ? - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'i32') }}}) } : - function(pointer) { return this['fromWireType']({{{ makeGetValue('pointer', 0, 'u32') }}}) }; - default: - throw new TypeError(`invalid integer width (${width}): ${name}`); - } - }, - - $floatReadValueFromPointer__deps: [], - $floatReadValueFromPointer: (name, width) => { - switch (width) { - case 4: return function(pointer) { - return this['fromWireType']({{{ makeGetValue('pointer', 0, 'float') }}}); - }; - case 8: return function(pointer) { - return this['fromWireType']({{{ makeGetValue('pointer', 0, 'double') }}}); - }; - default: - throw new TypeError(`invalid float width (${width}): ${name}`); - } - }, - - _embind_register_integer__docs: '/** @suppress {globalThis} */', - // When converting a number from JS to C++ side, the valid range of the number is - // [minRange, maxRange], inclusive. - _embind_register_integer__deps: [ - '$embindRepr', '$integerReadValueFromPointer', - '$readLatin1String', '$registerType'], - _embind_register_integer: (primitiveType, name, size, minRange, maxRange) => { - name = readLatin1String(name); - // LLVM doesn't have signed and unsigned 32-bit types, so u32 literals come - // out as 'i32 -1'. Always treat those as max u32. - if (maxRange === -1) { - maxRange = 4294967295; - } - - var fromWireType = (value) => value; - - if (minRange === 0) { - var bitshift = 32 - 8*size; - fromWireType = (value) => (value << bitshift) >>> bitshift; - } - - var isUnsignedType = (name.includes('unsigned')); - var checkAssertions = (value, toTypeName) => { -#if ASSERTIONS - if (typeof value != "number" && typeof value != "boolean") { - throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${toTypeName}`); - } - if (value < minRange || value > maxRange) { - throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`); - } -#endif - } - var toWireType; - if (isUnsignedType) { - toWireType = function(destructors, value) { - checkAssertions(value, this.name); - return value >>> 0; - } - } else { - toWireType = function(destructors, value) { - checkAssertions(value, this.name); - // The VM will perform JS to Wasm value conversion, according to the spec: - // https://www.w3.org/TR/wasm-js-api-1/#towebassemblyvalue - return value; - } - } - registerType(primitiveType, { - name, - 'fromWireType': fromWireType, - 'toWireType': toWireType, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': integerReadValueFromPointer(name, size, minRange !== 0), - destructorFunction: null, // This type does not need a destructor - }); - }, - -#if WASM_BIGINT - _embind_register_bigint__docs: '/** @suppress {globalThis} */', - _embind_register_bigint__deps: [ - '$embindRepr', '$readLatin1String', '$registerType', '$integerReadValueFromPointer'], - _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => { - name = readLatin1String(name); - - var isUnsignedType = (name.indexOf('u') != -1); - - // maxRange comes through as -1 for uint64_t (see issue 13902). Work around that temporarily - if (isUnsignedType) { - maxRange = (1n << 64n) - 1n; - } - - registerType(primitiveType, { - name, - 'fromWireType': (value) => value, - 'toWireType': function(destructors, value) { - if (typeof value != "bigint" && typeof value != "number") { - throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${this.name}`); - } - if (typeof value == "number") { - value = BigInt(value); - } -#if ASSERTIONS - if (value < minRange || value > maxRange) { - throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${name}", which is outside the valid range [${minRange}, ${maxRange}]!`); - } -#endif - return value; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': integerReadValueFromPointer(name, size, !isUnsignedType), - destructorFunction: null, // This type does not need a destructor - }); - }, -#else - _embind_register_bigint__deps: [], - _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => {}, -#endif - - _embind_register_float__deps: [ - '$embindRepr', '$floatReadValueFromPointer', - '$readLatin1String', '$registerType'], - _embind_register_float: (rawType, name, size) => { - name = readLatin1String(name); - registerType(rawType, { - name, - 'fromWireType': (value) => value, - 'toWireType': (destructors, value) => { -#if ASSERTIONS - if (typeof value != "number" && typeof value != "boolean") { - throw new TypeError(`Cannot convert ${embindRepr(value)} to ${this.name}`); - } -#endif - // The VM will perform JS to Wasm value conversion, according to the spec: - // https://www.w3.org/TR/wasm-js-api-1/#towebassemblyvalue - return value; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': floatReadValueFromPointer(name, size), - destructorFunction: null, // This type does not need a destructor - }); - }, - - $readPointer__docs: '/** @suppress {globalThis} */', - $readPointer: function(pointer) { - return this['fromWireType']({{{ makeGetValue('pointer', '0', '*') }}}); - }, - - _embind_register_std_string__deps: [ - '$readLatin1String', '$registerType', - '$readPointer', '$throwBindingError', - '$stringToUTF8', '$lengthBytesUTF8', 'malloc', 'free'], - _embind_register_std_string: (rawType, name) => { - name = readLatin1String(name); - var stdStringIsUTF8 -#if EMBIND_STD_STRING_IS_UTF8 - //process only std::string bindings with UTF8 support, in contrast to e.g. std::basic_string - = (name === "std::string"); -#else - = false; -#endif - - registerType(rawType, { - name, - // For some method names we use string keys here since they are part of - // the public/external API and/or used by the runtime-generated code. - 'fromWireType'(value) { - var length = {{{ makeGetValue('value', '0', SIZE_TYPE) }}}; - var payload = value + {{{ POINTER_SIZE }}}; - - var str; - if (stdStringIsUTF8) { - var decodeStartPtr = payload; - // Looping here to support possible embedded '0' bytes - for (var i = 0; i <= length; ++i) { - var currentBytePtr = payload + i; - if (i == length || HEAPU8[currentBytePtr] == 0) { - var maxRead = currentBytePtr - decodeStartPtr; - var stringSegment = UTF8ToString(decodeStartPtr, maxRead); - if (str === undefined) { - str = stringSegment; - } else { - str += String.fromCharCode(0); - str += stringSegment; - } - decodeStartPtr = currentBytePtr + 1; - } - } - } else { - var a = new Array(length); - for (var i = 0; i < length; ++i) { - a[i] = String.fromCharCode(HEAPU8[payload + i]); - } - str = a.join(''); - } - - _free(value); - - return str; - }, - 'toWireType'(destructors, value) { - if (value instanceof ArrayBuffer) { - value = new Uint8Array(value); - } - - var length; - var valueIsOfTypeString = (typeof value == 'string'); - - if (!(valueIsOfTypeString || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int8Array)) { - throwBindingError('Cannot pass non-string to std::string'); - } - if (stdStringIsUTF8 && valueIsOfTypeString) { - length = lengthBytesUTF8(value); - } else { - length = value.length; - } - - // assumes POINTER_SIZE alignment - var base = _malloc({{{ POINTER_SIZE }}} + length + 1); - var ptr = base + {{{ POINTER_SIZE }}}; - {{{ makeSetValue('base', '0', 'length', SIZE_TYPE) }}}; - if (stdStringIsUTF8 && valueIsOfTypeString) { - stringToUTF8(value, ptr, length + 1); - } else { - if (valueIsOfTypeString) { - for (var i = 0; i < length; ++i) { - var charCode = value.charCodeAt(i); - if (charCode > 255) { - _free(ptr); - throwBindingError('String has UTF-16 code units that do not fit in 8 bits'); - } - HEAPU8[ptr + i] = charCode; - } - } else { - for (var i = 0; i < length; ++i) { - HEAPU8[ptr + i] = value[i]; - } - } - } - - if (destructors !== null) { - destructors.push(_free, base); - } - return base; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - destructorFunction(ptr) { - _free(ptr); - }, - }); - }, - - _embind_register_std_wstring__deps: [ - '$readLatin1String', '$registerType', '$readPointer', - '$UTF16ToString', '$stringToUTF16', '$lengthBytesUTF16', - '$UTF32ToString', '$stringToUTF32', '$lengthBytesUTF32', - ], - _embind_register_std_wstring: (rawType, charSize, name) => { - name = readLatin1String(name); - var decodeString, encodeString, readCharAt, lengthBytesUTF; - if (charSize === 2) { - decodeString = UTF16ToString; - encodeString = stringToUTF16; - lengthBytesUTF = lengthBytesUTF16; - readCharAt = (pointer) => {{{ makeGetValue('pointer', 0, 'u16') }}}; - } else if (charSize === 4) { - decodeString = UTF32ToString; - encodeString = stringToUTF32; - lengthBytesUTF = lengthBytesUTF32; - readCharAt = (pointer) => {{{ makeGetValue('pointer', 0, 'u32') }}}; - } - registerType(rawType, { - name, - 'fromWireType': (value) => { - // Code mostly taken from _embind_register_std_string fromWireType - var length = {{{ makeGetValue('value', 0, '*') }}}; - var str; - - var decodeStartPtr = value + {{{ POINTER_SIZE }}}; - // Looping here to support possible embedded '0' bytes - for (var i = 0; i <= length; ++i) { - var currentBytePtr = value + {{{ POINTER_SIZE }}} + i * charSize; - if (i == length || readCharAt(currentBytePtr) == 0) { - var maxReadBytes = currentBytePtr - decodeStartPtr; - var stringSegment = decodeString(decodeStartPtr, maxReadBytes); - if (str === undefined) { - str = stringSegment; - } else { - str += String.fromCharCode(0); - str += stringSegment; - } - decodeStartPtr = currentBytePtr + charSize; - } - } - - _free(value); - - return str; - }, - 'toWireType': (destructors, value) => { - if (!(typeof value == 'string')) { - throwBindingError(`Cannot pass non-string to C++ string type ${name}`); - } - - // assumes POINTER_SIZE alignment - var length = lengthBytesUTF(value); - var ptr = _malloc({{{ POINTER_SIZE }}} + length + charSize); - {{{ makeSetValue('ptr', '0', 'length / charSize', SIZE_TYPE) }}}; - - encodeString(value, ptr + {{{ POINTER_SIZE }}}, length + charSize); - - if (destructors !== null) { - destructors.push(_free, ptr); - } - return ptr; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - destructorFunction(ptr) { - _free(ptr); - } - }); - }, - - _embind_register_emval__deps: [ - '$registerType', '$EmValType'], - _embind_register_emval: (rawType) => registerType(rawType, EmValType), - - _embind_register_user_type__deps: ['_embind_register_emval'], - _embind_register_user_type: (rawType, name) => { - __embind_register_emval(rawType); - }, - - _embind_register_optional__deps: ['_embind_register_emval'], - _embind_register_optional: (rawOptionalType, rawType) => { - __embind_register_emval(rawOptionalType); - }, - - _embind_register_memory_view__deps: ['$readLatin1String', '$registerType'], - _embind_register_memory_view: (rawType, dataTypeIndex, name) => { - var typeMapping = [ - Int8Array, - Uint8Array, - Int16Array, - Uint16Array, - Int32Array, - Uint32Array, - Float32Array, - Float64Array, -#if WASM_BIGINT - BigInt64Array, - BigUint64Array, -#endif - ]; - - var TA = typeMapping[dataTypeIndex]; - - function decodeMemoryView(handle) { - var size = {{{ makeGetValue('handle', 0, '*') }}}; - var data = {{{ makeGetValue('handle', POINTER_SIZE, '*') }}}; - return new TA(HEAP8.buffer, data, size); - } - - name = readLatin1String(name); - registerType(rawType, { - name, - 'fromWireType': decodeMemoryView, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': decodeMemoryView, - }, { - ignoreDuplicateRegistrations: true, - }); - }, - - $runDestructors: (destructors) => { - while (destructors.length) { - var ptr = destructors.pop(); - var del = destructors.pop(); - del(ptr); - } - }, - -#if DYNAMIC_EXECUTION - $newFunc__deps: ['$createNamedFunction'], - $newFunc: function(constructor, argumentList) { - if (!(constructor instanceof Function)) { - throw new TypeError(`new_ called with constructor type ${typeof(constructor)} which is not a function`); - } - /* - * Previously, the following line was just: - * function dummy() {}; - * Unfortunately, Chrome was preserving 'dummy' as the object's name, even - * though at creation, the 'dummy' has the correct constructor name. Thus, - * objects created with IMVU.new would show up in the debugger as 'dummy', - * which isn't very helpful. Using IMVU.createNamedFunction addresses the - * issue. Doubly-unfortunately, there's no way to write a test for this - * behavior. -NRD 2013.02.22 - */ - var dummy = createNamedFunction(constructor.name || 'unknownFunctionName', function(){}); - dummy.prototype = constructor.prototype; - var obj = new dummy; - - var r = constructor.apply(obj, argumentList); - return (r instanceof Object) ? r : obj; - }, -#endif - - // The path to interop from JS code to C++ code: - // (hand-written JS code) -> (autogenerated JS invoker) -> (template-generated C++ invoker) -> (target C++ function) - // craftInvokerFunction generates the JS invoker function for each function exposed to JS through embind. - $craftInvokerFunction__deps: [ - '$createNamedFunction', '$runDestructors', '$throwBindingError', '$usesDestructorStack', -#if DYNAMIC_EXECUTION - '$newFunc', -#if !EMBIND_AOT - '$createJsInvoker', -#endif -#endif -#if EMBIND_AOT - '$InvokerFunctions', - '$createJsInvokerSignature', -#endif -#if ASYNCIFY - '$Asyncify', -#endif - ], - $craftInvokerFunction: function(humanName, argTypes, classType, cppInvokerFunc, cppTargetFunc, /** boolean= */ isAsync) { - // humanName: a human-readable string name for the function to be generated. - // argTypes: An array that contains the embind type objects for all types in the function signature. - // argTypes[0] is the type object for the function return value. - // argTypes[1] is the type object for function this object/class type, or null if not crafting an invoker for a class method. - // argTypes[2...] are the actual function parameters. - // classType: The embind type object for the class to be bound, or null if this is not a method of a class. - // cppInvokerFunc: JS Function object to the C++-side function that interops into C++ code. - // cppTargetFunc: Function pointer (an integer to FUNCTION_TABLE) to the target C++ function the cppInvokerFunc will end up calling. - // isAsync: Optional. If true, returns an async function. Async bindings are only supported with JSPI. - var argCount = argTypes.length; - - if (argCount < 2) { - throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); - } - -#if ASSERTIONS && ASYNCIFY != 2 - assert(!isAsync, 'Async bindings are only supported with JSPI.'); -#endif - - var isClassMethodFunc = (argTypes[1] !== null && classType !== null); - - // Free functions with signature "void function()" do not need an invoker that marshalls between wire types. -// TODO: This omits argument count check - enable only at -O3 or similar. -// if (ENABLE_UNSAFE_OPTS && argCount == 2 && argTypes[0].name == "void" && !isClassMethodFunc) { -// return FUNCTION_TABLE[fn]; -// } - - - // Determine if we need to use a dynamic stack to store the destructors for the function parameters. - // TODO: Remove this completely once all function invokers are being dynamically generated. - var needsDestructorStack = usesDestructorStack(argTypes); - - var returns = (argTypes[0].name !== "void"); - -#if DYNAMIC_EXECUTION == 0 && !EMBIND_AOT - var expectedArgCount = argCount - 2; - var argsWired = new Array(expectedArgCount); - var invokerFuncArgs = []; - var destructors = []; - var invokerFn = function(...args) { - if (args.length !== expectedArgCount) { - throwBindingError(`function ${humanName} called with ${args.length} arguments, expected ${expectedArgCount}`); - } -#if EMSCRIPTEN_TRACING - Module.emscripten_trace_enter_context(`embind::${humanName}`); -#endif - destructors.length = 0; - var thisWired; - invokerFuncArgs.length = isClassMethodFunc ? 2 : 1; - invokerFuncArgs[0] = cppTargetFunc; - if (isClassMethodFunc) { - thisWired = argTypes[1]['toWireType'](destructors, this); - invokerFuncArgs[1] = thisWired; - } - for (var i = 0; i < expectedArgCount; ++i) { - argsWired[i] = argTypes[i + 2]['toWireType'](destructors, args[i]); - invokerFuncArgs.push(argsWired[i]); - } - - var rv = cppInvokerFunc(...invokerFuncArgs); - - function onDone(rv) { - if (needsDestructorStack) { - runDestructors(destructors); - } else { - for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; i++) { - var param = i === 1 ? thisWired : argsWired[i - 2]; - if (argTypes[i].destructorFunction !== null) { - argTypes[i].destructorFunction(param); - } - } - } - - #if EMSCRIPTEN_TRACING - Module.emscripten_trace_exit_context(); - #endif - - if (returns) { - return argTypes[0]['fromWireType'](rv); - } - } - -#if ASYNCIFY == 1 - if (Asyncify.currData) { - return Asyncify.whenDone().then(onDone); - } -#elif ASYNCIFY == 2 - if (isAsync) { - return rv.then(onDone); - } -#endif - - return onDone(rv); - }; -#else - // Builld the arguments that will be passed into the closure around the invoker - // function. - var closureArgs = [humanName, throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]]; -#if EMSCRIPTEN_TRACING - closureArgs.push(Module); -#endif - for (var i = 0; i < argCount - 2; ++i) { - closureArgs.push(argTypes[i+2]); - } -#if ASYNCIFY == 1 - closureArgs.push(Asyncify); -#endif - if (!needsDestructorStack) { - for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - if (argTypes[i].destructorFunction !== null) { - closureArgs.push(argTypes[i].destructorFunction); - } - } - } - -#if EMBIND_AOT - var signature = createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync); - var invokerFn = InvokerFunctions[signature](...closureArgs); -#else - let [args, invokerFnBody] = createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync); - args.push(invokerFnBody); - var invokerFn = newFunc(Function, args)(...closureArgs); -#endif -#endif - return createNamedFunction(humanName, invokerFn); - }, - - $embind__requireFunction__deps: ['$readLatin1String', '$throwBindingError' -#if DYNCALLS || !WASM_BIGINT || MEMORY64 - , '$getDynCaller' -#endif - ], - $embind__requireFunction: (signature, rawFunction) => { - signature = readLatin1String(signature); - - function makeDynCaller() { -#if DYNCALLS - return getDynCaller(signature, rawFunction); -#else -#if !WASM_BIGINT - if (signature.includes('j')) { - return getDynCaller(signature, rawFunction); - } -#elif MEMORY64 - if (signature.includes('p')) { - return getDynCaller(signature, rawFunction); - } -#endif - return getWasmTableEntry(rawFunction); -#endif - } - - var fp = makeDynCaller(); - if (typeof fp != "function") { - throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`); - } - return fp; - }, - - _embind_register_function__deps: [ - '$craftInvokerFunction', '$exposePublicSymbol', '$heap32VectorToArray', - '$readLatin1String', '$replacePublicSymbol', '$embind__requireFunction', - '$throwUnboundTypeError', '$whenDependentTypesAreResolved', '$getFunctionName'], - _embind_register_function: (name, argCount, rawArgTypesAddr, signature, rawInvoker, fn, isAsync) => { - var argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - name = readLatin1String(name); - name = getFunctionName(name); - - rawInvoker = embind__requireFunction(signature, rawInvoker); - - exposePublicSymbol(name, function() { - throwUnboundTypeError(`Cannot call ${name} due to unbound types`, argTypes); - }, argCount - 1); - - whenDependentTypesAreResolved([], argTypes, (argTypes) => { - var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); - replacePublicSymbol(name, craftInvokerFunction(name, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn, isAsync), argCount - 1); - return []; - }); - }, - - _embind_register_value_array__deps: [ - '$tupleRegistrations', '$readLatin1String', '$embind__requireFunction'], - _embind_register_value_array: ( - rawType, - name, - constructorSignature, - rawConstructor, - destructorSignature, - rawDestructor - ) => { - tupleRegistrations[rawType] = { - name: readLatin1String(name), - rawConstructor: embind__requireFunction(constructorSignature, rawConstructor), - rawDestructor: embind__requireFunction(destructorSignature, rawDestructor), - elements: [], - }; - }, - - _embind_register_value_array_element__deps: [ - '$tupleRegistrations', '$embind__requireFunction'], - _embind_register_value_array_element: ( - rawTupleType, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext - ) => { - tupleRegistrations[rawTupleType].elements.push({ - getterReturnType, - getter: embind__requireFunction(getterSignature, getter), - getterContext, - setterArgumentType, - setter: embind__requireFunction(setterSignature, setter), - setterContext, - }); - }, - - _embind_finalize_value_array__deps: [ - '$tupleRegistrations', '$runDestructors', - '$readPointer', '$whenDependentTypesAreResolved'], - _embind_finalize_value_array: (rawTupleType) => { - var reg = tupleRegistrations[rawTupleType]; - delete tupleRegistrations[rawTupleType]; - var elements = reg.elements; - var elementsLength = elements.length; - var elementTypes = elements.map((elt) => elt.getterReturnType). - concat(elements.map((elt) => elt.setterArgumentType)); - - var rawConstructor = reg.rawConstructor; - var rawDestructor = reg.rawDestructor; - - whenDependentTypesAreResolved([rawTupleType], elementTypes, (elementTypes) => { - elements.forEach((elt, i) => { - var getterReturnType = elementTypes[i]; - var getter = elt.getter; - var getterContext = elt.getterContext; - var setterArgumentType = elementTypes[i + elementsLength]; - var setter = elt.setter; - var setterContext = elt.setterContext; - elt.read = (ptr) => getterReturnType['fromWireType'](getter(getterContext, ptr)); - elt.write = (ptr, o) => { - var destructors = []; - setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); - runDestructors(destructors); - }; - }); - - return [{ - name: reg.name, - 'fromWireType': (ptr) => { - var rv = new Array(elementsLength); - for (var i = 0; i < elementsLength; ++i) { - rv[i] = elements[i].read(ptr); - } - rawDestructor(ptr); - return rv; - }, - 'toWireType': (destructors, o) => { - if (elementsLength !== o.length) { - throw new TypeError(`Incorrect number of tuple elements for ${reg.name}: expected=${elementsLength}, actual=${o.length}`); - } - var ptr = rawConstructor(); - for (var i = 0; i < elementsLength; ++i) { - elements[i].write(ptr, o[i]); - } - if (destructors !== null) { - destructors.push(rawDestructor, ptr); - } - return ptr; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - destructorFunction: rawDestructor, - }]; - }); - }, - - _embind_register_value_object__deps: [ - '$structRegistrations', '$readLatin1String', '$embind__requireFunction'], - _embind_register_value_object: ( - rawType, - name, - constructorSignature, - rawConstructor, - destructorSignature, - rawDestructor - ) => { - structRegistrations[rawType] = { - name: readLatin1String(name), - rawConstructor: embind__requireFunction(constructorSignature, rawConstructor), - rawDestructor: embind__requireFunction(destructorSignature, rawDestructor), - fields: [], - }; - }, - - _embind_register_value_object_field__deps: [ - '$structRegistrations', '$readLatin1String', '$embind__requireFunction'], - _embind_register_value_object_field: ( - structType, - fieldName, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext - ) => { - structRegistrations[structType].fields.push({ - fieldName: readLatin1String(fieldName), - getterReturnType, - getter: embind__requireFunction(getterSignature, getter), - getterContext, - setterArgumentType, - setter: embind__requireFunction(setterSignature, setter), - setterContext, - }); - }, - - _embind_finalize_value_object__deps: [ - '$structRegistrations', '$runDestructors', - '$readPointer', '$whenDependentTypesAreResolved'], - _embind_finalize_value_object: (structType) => { - var reg = structRegistrations[structType]; - delete structRegistrations[structType]; - - var rawConstructor = reg.rawConstructor; - var rawDestructor = reg.rawDestructor; - var fieldRecords = reg.fields; - var fieldTypes = fieldRecords.map((field) => field.getterReturnType). - concat(fieldRecords.map((field) => field.setterArgumentType)); - whenDependentTypesAreResolved([structType], fieldTypes, (fieldTypes) => { - var fields = {}; - fieldRecords.forEach((field, i) => { - var fieldName = field.fieldName; - var getterReturnType = fieldTypes[i]; - var getter = field.getter; - var getterContext = field.getterContext; - var setterArgumentType = fieldTypes[i + fieldRecords.length]; - var setter = field.setter; - var setterContext = field.setterContext; - fields[fieldName] = { - read: (ptr) => getterReturnType['fromWireType'](getter(getterContext, ptr)), - write: (ptr, o) => { - var destructors = []; - setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); - runDestructors(destructors); - } - }; - }); - - return [{ - name: reg.name, - 'fromWireType': (ptr) => { - var rv = {}; - for (var i in fields) { - rv[i] = fields[i].read(ptr); - } - rawDestructor(ptr); - return rv; - }, - 'toWireType': (destructors, o) => { - // todo: Here we have an opportunity for -O3 level "unsafe" optimizations: - // assume all fields are present without checking. - for (var fieldName in fields) { - if (!(fieldName in o)) { - throw new TypeError(`Missing field: "${fieldName}"`); - } - } - var ptr = rawConstructor(); - for (fieldName in fields) { - fields[fieldName].write(ptr, o[fieldName]); - } - if (destructors !== null) { - destructors.push(rawDestructor, ptr); - } - return ptr; - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - destructorFunction: rawDestructor, - }]; - }); - }, - - $genericPointerToWireType__docs: '/** @suppress {globalThis} */', - $genericPointerToWireType__deps: ['$throwBindingError', '$upcastPointer'], - $genericPointerToWireType: function(destructors, handle) { - var ptr; - if (handle === null) { - if (this.isReference) { - throwBindingError(`null is not a valid ${this.name}`); - } - - if (this.isSmartPointer) { - ptr = this.rawConstructor(); - if (destructors !== null) { - destructors.push(this.rawDestructor, ptr); - } - return ptr; - } else { - return 0; - } - } - - if (!handle || !handle.$$) { - throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); - } - if (!handle.$$.ptr) { - throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); - } - if (!this.isConst && handle.$$.ptrType.isConst) { - throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name)} to parameter type ${this.name}`); - } - var handleClass = handle.$$.ptrType.registeredClass; - ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); - - if (this.isSmartPointer) { - // TODO: this is not strictly true - // We could support BY_EMVAL conversions from raw pointers to smart pointers - // because the smart pointer can hold a reference to the handle - if (undefined === handle.$$.smartPtr) { - throwBindingError('Passing raw pointer to smart pointer is illegal'); - } - - switch (this.sharingPolicy) { - case 0: // NONE - // no upcasting - if (handle.$$.smartPtrType === this) { - ptr = handle.$$.smartPtr; - } else { - throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name)} to parameter type ${this.name}`); - } - break; - - case 1: // INTRUSIVE - ptr = handle.$$.smartPtr; - break; - - case 2: // BY_EMVAL - if (handle.$$.smartPtrType === this) { - ptr = handle.$$.smartPtr; - } else { - var clonedHandle = handle['clone'](); - ptr = this.rawShare( - ptr, - Emval.toHandle(() => clonedHandle['delete']()) - ); - if (destructors !== null) { - destructors.push(this.rawDestructor, ptr); - } - } - break; - - default: - throwBindingError('Unsupporting sharing policy'); - } - } - return ptr; - }, - - $constNoSmartPtrRawPointerToWireType__docs: '/** @suppress {globalThis} */', - // If we know a pointer type is not going to have SmartPtr logic in it, we can - // special-case optimize it a bit (compare to genericPointerToWireType) - $constNoSmartPtrRawPointerToWireType__deps: ['$throwBindingError', '$upcastPointer'], - $constNoSmartPtrRawPointerToWireType: function(destructors, handle) { - if (handle === null) { - if (this.isReference) { - throwBindingError(`null is not a valid ${this.name}`); - } - return 0; - } - - if (!handle.$$) { - throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); - } - if (!handle.$$.ptr) { - throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); - } - var handleClass = handle.$$.ptrType.registeredClass; - var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); - return ptr; - }, - - $nonConstNoSmartPtrRawPointerToWireType__docs: '/** @suppress {globalThis} */', - // An optimized version for non-const method accesses - there we must additionally restrict that - // the pointer is not a const-pointer. - $nonConstNoSmartPtrRawPointerToWireType__deps: ['$throwBindingError', '$upcastPointer'], - $nonConstNoSmartPtrRawPointerToWireType: function(destructors, handle) { - if (handle === null) { - if (this.isReference) { - throwBindingError(`null is not a valid ${this.name}`); - } - return 0; - } - - if (!handle.$$) { - throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); - } - if (!handle.$$.ptr) { - throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); - } - if (handle.$$.ptrType.isConst) { - throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`); - } - var handleClass = handle.$$.ptrType.registeredClass; - var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); - return ptr; - }, - - $init_RegisteredPointer__deps: [ - '$RegisteredPointer', - '$readPointer', - '$RegisteredPointer_fromWireType', - '$GenericWireTypeSize', - ], - $init_RegisteredPointer: () => { - Object.assign(RegisteredPointer.prototype, { - getPointee(ptr) { - if (this.rawGetPointee) { - ptr = this.rawGetPointee(ptr); - } - return ptr; - }, - destructor(ptr) { - this.rawDestructor?.(ptr); - }, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': readPointer, - 'fromWireType': RegisteredPointer_fromWireType, - }); - }, - - $RegisteredPointer__docs: `/** @constructor - @param {*=} pointeeType, - @param {*=} sharingPolicy, - @param {*=} rawGetPointee, - @param {*=} rawConstructor, - @param {*=} rawShare, - @param {*=} rawDestructor, - */`, - $RegisteredPointer__deps: [ - '$constNoSmartPtrRawPointerToWireType', '$genericPointerToWireType', - '$nonConstNoSmartPtrRawPointerToWireType', '$init_RegisteredPointer'], - $RegisteredPointer__postset: 'init_RegisteredPointer()', - $RegisteredPointer: function( - name, - registeredClass, - isReference, - isConst, - - // smart pointer properties - isSmartPointer, - pointeeType, - sharingPolicy, - rawGetPointee, - rawConstructor, - rawShare, - rawDestructor - ) { - this.name = name; - this.registeredClass = registeredClass; - this.isReference = isReference; - this.isConst = isConst; - - // smart pointer properties - this.isSmartPointer = isSmartPointer; - this.pointeeType = pointeeType; - this.sharingPolicy = sharingPolicy; - this.rawGetPointee = rawGetPointee; - this.rawConstructor = rawConstructor; - this.rawShare = rawShare; - this.rawDestructor = rawDestructor; - - if (!isSmartPointer && registeredClass.baseClass === undefined) { - if (isConst) { - this['toWireType'] = constNoSmartPtrRawPointerToWireType; - this.destructorFunction = null; - } else { - this['toWireType'] = nonConstNoSmartPtrRawPointerToWireType; - this.destructorFunction = null; - } - } else { - this['toWireType'] = genericPointerToWireType; - // Here we must leave this.destructorFunction undefined, since whether genericPointerToWireType returns - // a pointer that needs to be freed up is runtime-dependent, and cannot be evaluated at registration time. - // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in - // craftInvokerFunction altogether. - } - }, - - $RegisteredPointer_fromWireType__docs: '/** @suppress {globalThis} */', - $RegisteredPointer_fromWireType__deps: [ - '$downcastPointer', '$registeredPointers', - '$getInheritedInstance', '$makeClassHandle', -#if MEMORY64 - '$bigintToI53Checked' -#endif - ], - $RegisteredPointer_fromWireType: function(ptr) { - // ptr is a raw pointer (or a raw smartpointer) -#if MEMORY64 - ptr = bigintToI53Checked(ptr); -#if ASSERTIONS - assert(Number.isSafeInteger(ptr)); -#endif -#endif - - // rawPointer is a maybe-null raw pointer - var rawPointer = this.getPointee(ptr); - if (!rawPointer) { - this.destructor(ptr); - return null; - } - - var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer); - if (undefined !== registeredInstance) { - // JS object has been neutered, time to repopulate it - if (0 === registeredInstance.$$.count.value) { - registeredInstance.$$.ptr = rawPointer; - registeredInstance.$$.smartPtr = ptr; - return registeredInstance['clone'](); - } else { - // else, just increment reference count on existing object - // it already has a reference to the smart pointer - var rv = registeredInstance['clone'](); - this.destructor(ptr); - return rv; - } - } - - function makeDefaultHandle() { - if (this.isSmartPointer) { - return makeClassHandle(this.registeredClass.instancePrototype, { - ptrType: this.pointeeType, - ptr: rawPointer, - smartPtrType: this, - smartPtr: ptr, - }); - } else { - return makeClassHandle(this.registeredClass.instancePrototype, { - ptrType: this, - ptr, - }); - } - } - - var actualType = this.registeredClass.getActualType(rawPointer); - var registeredPointerRecord = registeredPointers[actualType]; - if (!registeredPointerRecord) { - return makeDefaultHandle.call(this); - } - - var toType; - if (this.isConst) { - toType = registeredPointerRecord.constPointerType; - } else { - toType = registeredPointerRecord.pointerType; - } - var dp = downcastPointer( - rawPointer, - this.registeredClass, - toType.registeredClass); - if (dp === null) { - return makeDefaultHandle.call(this); - } - if (this.isSmartPointer) { - return makeClassHandle(toType.registeredClass.instancePrototype, { - ptrType: toType, - ptr: dp, - smartPtrType: this, - smartPtr: ptr, - }); - } else { - return makeClassHandle(toType.registeredClass.instancePrototype, { - ptrType: toType, - ptr: dp, - }); - } - }, - - $runDestructor: ($$) => { - if ($$.smartPtr) { - $$.smartPtrType.rawDestructor($$.smartPtr); - } else { - $$.ptrType.registeredClass.rawDestructor($$.ptr); - } - }, - - $releaseClassHandle__deps: ['$runDestructor'], - $releaseClassHandle: ($$) => { - $$.count.value -= 1; - var toDelete = 0 === $$.count.value; - if (toDelete) { - runDestructor($$); - } - }, - - $finalizationRegistry: false, - - $detachFinalizer_deps: ['$finalizationRegistry'], - $detachFinalizer: (handle) => {}, - - $attachFinalizer__deps: ['$finalizationRegistry', '$detachFinalizer', - '$releaseClassHandle', '$RegisteredPointer_fromWireType'], - $attachFinalizer: (handle) => { - if ('undefined' === typeof FinalizationRegistry) { - attachFinalizer = (handle) => handle; - return handle; - } - // If the running environment has a FinalizationRegistry (see - // https://github.com/tc39/proposal-weakrefs), then attach finalizers - // for class handles. We check for the presence of FinalizationRegistry - // at run-time, not build-time. - finalizationRegistry = new FinalizationRegistry((info) => { -#if ASSERTIONS - console.warn(info.leakWarning.stack.replace(/^Error: /, '')); -#endif - releaseClassHandle(info.$$); - }); - attachFinalizer = (handle) => { - var $$ = handle.$$; - var hasSmartPtr = !!$$.smartPtr; - if (hasSmartPtr) { - // We should not call the destructor on raw pointers in case other code expects the pointee to live - var info = { $$: $$ }; -#if ASSERTIONS - // Create a warning as an Error instance in advance so that we can store - // the current stacktrace and point to it when / if a leak is detected. - // This is more useful than the empty stacktrace of `FinalizationRegistry` - // callback. - var cls = $$.ptrType.registeredClass; - info.leakWarning = new Error(`Embind found a leaked C++ instance ${cls.name} <${ptrToString($$.ptr)}>.\n` + - "We'll free it automatically in this case, but this functionality is not reliable across various environments.\n" + - "Make sure to invoke .delete() manually once you're done with the instance instead.\n" + - "Originally allocated"); // `.stack` will add "at ..." after this sentence - if ('captureStackTrace' in Error) { - Error.captureStackTrace(info.leakWarning, RegisteredPointer_fromWireType); - } -#endif - finalizationRegistry.register(handle, info, handle); - } - return handle; - }; - detachFinalizer = (handle) => finalizationRegistry.unregister(handle); - return attachFinalizer(handle); - }, - - $makeClassHandle__deps: ['$throwInternalError', '$attachFinalizer'], - $makeClassHandle: (prototype, record) => { - if (!record.ptrType || !record.ptr) { - throwInternalError('makeClassHandle requires ptr and ptrType'); - } - var hasSmartPtrType = !!record.smartPtrType; - var hasSmartPtr = !!record.smartPtr; - if (hasSmartPtrType !== hasSmartPtr) { - throwInternalError('Both smartPtrType and smartPtr must be specified'); - } - record.count = { value: 1 }; - return attachFinalizer(Object.create(prototype, { - $$: { - value: record, - writable: true, - }, - })); - }, - - $init_ClassHandle__deps: [ - '$ClassHandle', - '$shallowCopyInternalPointer', - '$throwInstanceAlreadyDeleted', - '$attachFinalizer', - '$releaseClassHandle', - '$throwBindingError', - '$detachFinalizer', - ], - $init_ClassHandle: () => { - Object.assign(ClassHandle.prototype, { - "isAliasOf"(other) { - if (!(this instanceof ClassHandle)) { - return false; - } - if (!(other instanceof ClassHandle)) { - return false; - } - - var leftClass = this.$$.ptrType.registeredClass; - var left = this.$$.ptr; - other.$$ = /** @type {Object} */ (other.$$); - var rightClass = other.$$.ptrType.registeredClass; - var right = other.$$.ptr; - - while (leftClass.baseClass) { - left = leftClass.upcast(left); - leftClass = leftClass.baseClass; - } - - while (rightClass.baseClass) { - right = rightClass.upcast(right); - rightClass = rightClass.baseClass; - } - - return leftClass === rightClass && left === right; - }, - - "clone"() { - if (!this.$$.ptr) { - throwInstanceAlreadyDeleted(this); - } - - if (this.$$.preservePointerOnDelete) { - this.$$.count.value += 1; - return this; - } else { - var clone = attachFinalizer(Object.create(Object.getPrototypeOf(this), { - $$: { - value: shallowCopyInternalPointer(this.$$), - } - })); - - clone.$$.count.value += 1; - clone.$$.deleteScheduled = false; - return clone; - } - }, - - "delete"() { - if (!this.$$.ptr) { - throwInstanceAlreadyDeleted(this); - } - - if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { - throwBindingError('Object already scheduled for deletion'); - } - - detachFinalizer(this); - releaseClassHandle(this.$$); - - if (!this.$$.preservePointerOnDelete) { - this.$$.smartPtr = undefined; - this.$$.ptr = undefined; - } - }, - - "isDeleted"() { - return !this.$$.ptr; - }, - - "deleteLater"() { - if (!this.$$.ptr) { - throwInstanceAlreadyDeleted(this); - } - if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { - throwBindingError('Object already scheduled for deletion'); - } - deletionQueue.push(this); - if (deletionQueue.length === 1 && delayFunction) { - delayFunction(flushPendingDeletes); - } - this.$$.deleteScheduled = true; - return this; - }, - }); - }, - - $ClassHandle__docs: '/** @constructor */', - $ClassHandle__deps: ['$init_ClassHandle'], - $ClassHandle__postset: 'init_ClassHandle()', - // root of all pointer and smart pointer handles in embind - $ClassHandle: function() { - }, - - $throwInstanceAlreadyDeleted__deps: ['$throwBindingError'], - $throwInstanceAlreadyDeleted: (obj) => { - function getInstanceTypeName(handle) { - return handle.$$.ptrType.registeredClass.name; - } - throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); - }, - - $deletionQueue: [], - - $flushPendingDeletes__deps: ['$deletionQueue'], - $flushPendingDeletes: () => { - while (deletionQueue.length) { - var obj = deletionQueue.pop(); - obj.$$.deleteScheduled = false; - obj['delete'](); - } - }, - - $delayFunction: undefined, - - $setDelayFunction__deps: ['$delayFunction', '$deletionQueue', '$flushPendingDeletes'], - $setDelayFunction: (fn) => { - delayFunction = fn; - if (deletionQueue.length && delayFunction) { - delayFunction(flushPendingDeletes); - } - }, - - $RegisteredClass__docs: '/** @constructor */', - $RegisteredClass: function(name, - constructor, - instancePrototype, - rawDestructor, - baseClass, - getActualType, - upcast, - downcast) { - this.name = name; - this.constructor = constructor; - this.instancePrototype = instancePrototype; - this.rawDestructor = rawDestructor; - this.baseClass = baseClass; - this.getActualType = getActualType; - this.upcast = upcast; - this.downcast = downcast; - this.pureVirtualFunctions = []; - }, - - $shallowCopyInternalPointer: (o) => { - return { - count: o.count, - deleteScheduled: o.deleteScheduled, - preservePointerOnDelete: o.preservePointerOnDelete, - ptr: o.ptr, - ptrType: o.ptrType, - smartPtr: o.smartPtr, - smartPtrType: o.smartPtrType, - }; - }, - - _embind_register_class__deps: [ - '$BindingError', '$ClassHandle', '$createNamedFunction', - '$registeredPointers', '$exposePublicSymbol', - '$makeLegalFunctionName', '$readLatin1String', - '$RegisteredClass', '$RegisteredPointer', '$replacePublicSymbol', - '$embind__requireFunction', '$throwUnboundTypeError', - '$whenDependentTypesAreResolved'], - _embind_register_class: (rawType, - rawPointerType, - rawConstPointerType, - baseClassRawType, - getActualTypeSignature, - getActualType, - upcastSignature, - upcast, - downcastSignature, - downcast, - name, - destructorSignature, - rawDestructor) => { - name = readLatin1String(name); - getActualType = embind__requireFunction(getActualTypeSignature, getActualType); - upcast &&= embind__requireFunction(upcastSignature, upcast); - downcast &&= embind__requireFunction(downcastSignature, downcast); - rawDestructor = embind__requireFunction(destructorSignature, rawDestructor); - var legalFunctionName = makeLegalFunctionName(name); - - exposePublicSymbol(legalFunctionName, function() { - // this code cannot run if baseClassRawType is zero - throwUnboundTypeError(`Cannot construct ${name} due to unbound types`, [baseClassRawType]); - }); - - whenDependentTypesAreResolved( - [rawType, rawPointerType, rawConstPointerType], - baseClassRawType ? [baseClassRawType] : [], - (base) => { - base = base[0]; - - var baseClass; - var basePrototype; - if (baseClassRawType) { - baseClass = base.registeredClass; - basePrototype = baseClass.instancePrototype; - } else { - basePrototype = ClassHandle.prototype; - } - - var constructor = createNamedFunction(name, function(...args) { - if (Object.getPrototypeOf(this) !== instancePrototype) { - throw new BindingError("Use 'new' to construct " + name); - } - if (undefined === registeredClass.constructor_body) { - throw new BindingError(name + " has no accessible constructor"); - } - var body = registeredClass.constructor_body[args.length]; - if (undefined === body) { - throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`); - } - return body.apply(this, args); - }); - - var instancePrototype = Object.create(basePrototype, { - constructor: { value: constructor }, - }); - - constructor.prototype = instancePrototype; - - var registeredClass = new RegisteredClass(name, - constructor, - instancePrototype, - rawDestructor, - baseClass, - getActualType, - upcast, - downcast); - - if (registeredClass.baseClass) { - // Keep track of class hierarchy. Used to allow sub-classes to inherit class functions. - registeredClass.baseClass.__derivedClasses ??= []; - - registeredClass.baseClass.__derivedClasses.push(registeredClass); - } - - var referenceConverter = new RegisteredPointer(name, - registeredClass, - true, - false, - false); - - var pointerConverter = new RegisteredPointer(name + '*', - registeredClass, - false, - false, - false); - - var constPointerConverter = new RegisteredPointer(name + ' const*', - registeredClass, - false, - true, - false); - - registeredPointers[rawType] = { - pointerType: pointerConverter, - constPointerType: constPointerConverter - }; - - replacePublicSymbol(legalFunctionName, constructor); - - return [referenceConverter, pointerConverter, constPointerConverter]; - } - ); - }, - - _embind_register_class_constructor__deps: [ - '$heap32VectorToArray', '$embind__requireFunction', '$runDestructors', - '$throwBindingError', '$whenDependentTypesAreResolved', '$registeredTypes', - '$craftInvokerFunction'], - _embind_register_class_constructor: ( - rawClassType, - argCount, - rawArgTypesAddr, - invokerSignature, - invoker, - rawConstructor - ) => { -#if ASSERTIONS - assert(argCount > 0); -#endif - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - invoker = embind__requireFunction(invokerSignature, invoker); - var args = [rawConstructor]; - var destructors = []; - - whenDependentTypesAreResolved([], [rawClassType], (classType) => { - classType = classType[0]; - var humanName = `constructor ${classType.name}`; - - if (undefined === classType.registeredClass.constructor_body) { - classType.registeredClass.constructor_body = []; - } - if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { - throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`); - } - classType.registeredClass.constructor_body[argCount - 1] = () => { - throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`, rawArgTypes); - }; - - whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { - // Insert empty slot for context type (argTypes[1]). - argTypes.splice(1, 0, null); - classType.registeredClass.constructor_body[argCount - 1] = craftInvokerFunction(humanName, argTypes, null, invoker, rawConstructor); - return []; - }); - return []; - }); - }, - - $downcastPointer: (ptr, ptrClass, desiredClass) => { - if (ptrClass === desiredClass) { - return ptr; - } - if (undefined === desiredClass.baseClass) { - return null; // no conversion - } - - var rv = downcastPointer(ptr, ptrClass, desiredClass.baseClass); - if (rv === null) { - return null; - } - return desiredClass.downcast(rv); - }, - - $upcastPointer__deps: ['$throwBindingError'], - $upcastPointer: (ptr, ptrClass, desiredClass) => { - while (ptrClass !== desiredClass) { - if (!ptrClass.upcast) { - throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`); - } - ptr = ptrClass.upcast(ptr); - ptrClass = ptrClass.baseClass; - } - return ptr; - }, - - $validateThis__deps: ['$throwBindingError', '$upcastPointer'], - $validateThis: (this_, classType, humanName) => { - if (!(this_ instanceof Object)) { - throwBindingError(`${humanName} with invalid "this": ${this_}`); - } - if (!(this_ instanceof classType.registeredClass.constructor)) { - throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`); - } - if (!this_.$$.ptr) { - throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`); - } - - // todo: kill this - return upcastPointer(this_.$$.ptr, - this_.$$.ptrType.registeredClass, - classType.registeredClass); - }, - - _embind_register_class_function__deps: [ - '$craftInvokerFunction', '$heap32VectorToArray', '$readLatin1String', - '$embind__requireFunction', '$throwUnboundTypeError', - '$whenDependentTypesAreResolved', '$getFunctionName'], - _embind_register_class_function: (rawClassType, - methodName, - argCount, - rawArgTypesAddr, // [ReturnType, ThisType, Args...] - invokerSignature, - rawInvoker, - context, - isPureVirtual, - isAsync) => { - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = readLatin1String(methodName); - methodName = getFunctionName(methodName); - rawInvoker = embind__requireFunction(invokerSignature, rawInvoker); - - whenDependentTypesAreResolved([], [rawClassType], (classType) => { - classType = classType[0]; - var humanName = `${classType.name}.${methodName}`; - - if (methodName.startsWith("@@")) { - methodName = Symbol[methodName.substring(2)]; - } - - if (isPureVirtual) { - classType.registeredClass.pureVirtualFunctions.push(methodName); - } - - function unboundTypesHandler() { - throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`, rawArgTypes); - } - - var proto = classType.registeredClass.instancePrototype; - var method = proto[methodName]; - if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) { - // This is the first overload to be registered, OR we are replacing a - // function in the base class with a function in the derived class. - unboundTypesHandler.argCount = argCount - 2; - unboundTypesHandler.className = classType.name; - proto[methodName] = unboundTypesHandler; - } else { - // There was an existing function with the same name registered. Set up - // a function overload routing table. - ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler; - } - - whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { - var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context, isAsync); - - // Replace the initial unbound-handler-stub function with the - // appropriate member function, now that all types are resolved. If - // multiple overloads are registered for this function, the function - // goes into an overload table. - if (undefined === proto[methodName].overloadTable) { - // Set argCount in case an overload is registered later - memberFunction.argCount = argCount - 2; - proto[methodName] = memberFunction; - } else { - proto[methodName].overloadTable[argCount - 2] = memberFunction; - } - - return []; - }); - return []; - }); - }, - - _embind_register_class_property__deps: [ - '$readLatin1String', '$embind__requireFunction', '$runDestructors', - '$throwBindingError', '$throwUnboundTypeError', - '$whenDependentTypesAreResolved', '$validateThis'], - _embind_register_class_property: (classType, - fieldName, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext) => { - fieldName = readLatin1String(fieldName); - getter = embind__requireFunction(getterSignature, getter); - - whenDependentTypesAreResolved([], [classType], (classType) => { - classType = classType[0]; - var humanName = `${classType.name}.${fieldName}`; - var desc = { - get() { - throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [getterReturnType, setterArgumentType]); - }, - enumerable: true, - configurable: true - }; - if (setter) { - desc.set = () => throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [getterReturnType, setterArgumentType]); - } else { - desc.set = (v) => throwBindingError(humanName + ' is a read-only property'); - } - - Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); - - whenDependentTypesAreResolved( - [], - (setter ? [getterReturnType, setterArgumentType] : [getterReturnType]), - (types) => { - var getterReturnType = types[0]; - var desc = { - get() { - var ptr = validateThis(this, classType, humanName + ' getter'); - return getterReturnType['fromWireType'](getter(getterContext, ptr)); - }, - enumerable: true - }; - - if (setter) { - setter = embind__requireFunction(setterSignature, setter); - var setterArgumentType = types[1]; - desc.set = function(v) { - var ptr = validateThis(this, classType, humanName + ' setter'); - var destructors = []; - setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, v)); - runDestructors(destructors); - }; - } - - Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); - return []; - }); - - return []; - }); - }, - - _embind_register_class_class_function__deps: [ - '$craftInvokerFunction', '$ensureOverloadTable', '$heap32VectorToArray', - '$readLatin1String', '$embind__requireFunction', '$throwUnboundTypeError', - '$whenDependentTypesAreResolved', '$getFunctionName'], - _embind_register_class_class_function: (rawClassType, - methodName, - argCount, - rawArgTypesAddr, - invokerSignature, - rawInvoker, - fn, - isAsync) => { - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = readLatin1String(methodName); - methodName = getFunctionName(methodName); - rawInvoker = embind__requireFunction(invokerSignature, rawInvoker); - whenDependentTypesAreResolved([], [rawClassType], (classType) => { - classType = classType[0]; - var humanName = `${classType.name}.${methodName}`; - - function unboundTypesHandler() { - throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`, rawArgTypes); - } - - if (methodName.startsWith("@@")) { - methodName = Symbol[methodName.substring(2)]; - } - - var proto = classType.registeredClass.constructor; - if (undefined === proto[methodName]) { - // This is the first function to be registered with this name. - unboundTypesHandler.argCount = argCount-1; - proto[methodName] = unboundTypesHandler; - } else { - // There was an existing function with the same name registered. Set up - // a function overload routing table. - ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; - } - - whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { - // Replace the initial unbound-types-handler stub with the proper - // function. If multiple overloads are registered, the function handlers - // go into an overload table. - var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); - var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn, isAsync); - if (undefined === proto[methodName].overloadTable) { - func.argCount = argCount-1; - proto[methodName] = func; - } else { - proto[methodName].overloadTable[argCount-1] = func; - } - - if (classType.registeredClass.__derivedClasses) { - for (const derivedClass of classType.registeredClass.__derivedClasses) { - if (!derivedClass.constructor.hasOwnProperty(methodName)) { - // TODO: Add support for overloads - derivedClass.constructor[methodName] = func; - } - } - } - - return []; - }); - return []; - }); - }, - - _embind_register_class_class_property__deps: [ - '$readLatin1String', '$embind__requireFunction', '$runDestructors', - '$throwBindingError', '$throwUnboundTypeError', - '$whenDependentTypesAreResolved', '$validateThis'], - _embind_register_class_class_property: (rawClassType, - fieldName, - rawFieldType, - rawFieldPtr, - getterSignature, - getter, - setterSignature, - setter) => { - fieldName = readLatin1String(fieldName); - getter = embind__requireFunction(getterSignature, getter); - - whenDependentTypesAreResolved([], [rawClassType], (classType) => { - classType = classType[0]; - var humanName = `${classType.name}.${fieldName}`; - var desc = { - get() { - throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [rawFieldType]); - }, - enumerable: true, - configurable: true - }; - if (setter) { - desc.set = () => { - throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [rawFieldType]); - }; - } else { - desc.set = (v) => { - throwBindingError(`${humanName} is a read-only property`); - }; - } - - Object.defineProperty(classType.registeredClass.constructor, fieldName, desc); - - whenDependentTypesAreResolved([], [rawFieldType], (fieldType) => { - fieldType = fieldType[0]; - var desc = { - get() { - return fieldType['fromWireType'](getter(rawFieldPtr)); - }, - enumerable: true - }; - - if (setter) { - setter = embind__requireFunction(setterSignature, setter); - desc.set = (v) => { - var destructors = []; - setter(rawFieldPtr, fieldType['toWireType'](destructors, v)); - runDestructors(destructors); - }; - } - - Object.defineProperty(classType.registeredClass.constructor, fieldName, desc); - return []; - }); - - return []; - }); - }, - - _embind_create_inheriting_constructor__deps: [ - '$createNamedFunction', '$Emval', - '$PureVirtualError', '$readLatin1String', - '$registerInheritedInstance', - '$requireRegisteredType', '$throwBindingError', - '$unregisterInheritedInstance', '$detachFinalizer', '$attachFinalizer'], - _embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => { - constructorName = readLatin1String(constructorName); - wrapperType = requireRegisteredType(wrapperType, 'wrapper'); - properties = Emval.toValue(properties); - - var registeredClass = wrapperType.registeredClass; - var wrapperPrototype = registeredClass.instancePrototype; - var baseClass = registeredClass.baseClass; - var baseClassPrototype = baseClass.instancePrototype; - var baseConstructor = registeredClass.baseClass.constructor; - var ctor = createNamedFunction(constructorName, function(...args) { - registeredClass.baseClass.pureVirtualFunctions.forEach(function(name) { - if (this[name] === baseClassPrototype[name]) { - throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`); - } - }.bind(this)); - - Object.defineProperty(this, '__parent', { - value: wrapperPrototype - }); - this["__construct"](...args); - }); - - // It's a little nasty that we're modifying the wrapper prototype here. - - wrapperPrototype["__construct"] = function __construct(...args) { - if (this === wrapperPrototype) { - throwBindingError("Pass correct 'this' to __construct"); - } - - var inner = baseConstructor["implement"](this, ...args); - detachFinalizer(inner); - var $$ = inner.$$; - inner["notifyOnDestruction"](); - $$.preservePointerOnDelete = true; - Object.defineProperties(this, { $$: { - value: $$ - }}); - attachFinalizer(this); - registerInheritedInstance(registeredClass, $$.ptr, this); - }; - - wrapperPrototype["__destruct"] = function __destruct() { - if (this === wrapperPrototype) { - throwBindingError("Pass correct 'this' to __destruct"); - } - - detachFinalizer(this); - unregisterInheritedInstance(registeredClass, this.$$.ptr); - }; - - ctor.prototype = Object.create(wrapperPrototype); - Object.assign(ctor.prototype, properties); - return Emval.toHandle(ctor); - }, - - $char_0: '0'.charCodeAt(0), - $char_9: '9'.charCodeAt(0), - $makeLegalFunctionName__deps: ['$char_0', '$char_9'], - $makeLegalFunctionName: (name) => { - if (undefined === name) { - return '_unknown'; - } - name = name.replace(/[^a-zA-Z0-9_]/g, '$'); - var f = name.charCodeAt(0); - if (f >= char_0 && f <= char_9) { - return `_${name}`; - } - return name; - }, - - _embind_register_smart_ptr__deps: ['$RegisteredPointer', '$embind__requireFunction', '$whenDependentTypesAreResolved'], - _embind_register_smart_ptr: (rawType, - rawPointeeType, - name, - sharingPolicy, - getPointeeSignature, - rawGetPointee, - constructorSignature, - rawConstructor, - shareSignature, - rawShare, - destructorSignature, - rawDestructor) => { - name = readLatin1String(name); - rawGetPointee = embind__requireFunction(getPointeeSignature, rawGetPointee); - rawConstructor = embind__requireFunction(constructorSignature, rawConstructor); - rawShare = embind__requireFunction(shareSignature, rawShare); - rawDestructor = embind__requireFunction(destructorSignature, rawDestructor); - - whenDependentTypesAreResolved([rawType], [rawPointeeType], (pointeeType) => { - pointeeType = pointeeType[0]; - - var registeredPointer = new RegisteredPointer(name, - pointeeType.registeredClass, - false, - false, - // smart pointer properties - true, - pointeeType, - sharingPolicy, - rawGetPointee, - rawConstructor, - rawShare, - rawDestructor); - return [registeredPointer]; - }); - }, - - _embind_register_enum__docs: '/** @suppress {globalThis} */', - _embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer', - '$readLatin1String', '$registerType'], - _embind_register_enum: (rawType, name, size, isSigned) => { - name = readLatin1String(name); - - function ctor() {} - ctor.values = {}; - - registerType(rawType, { - name, - constructor: ctor, - 'fromWireType': function(c) { - return this.constructor.values[c]; - }, - 'toWireType': (destructors, c) => c.value, - 'argPackAdvance': GenericWireTypeSize, - 'readValueFromPointer': enumReadValueFromPointer(name, size, isSigned), - destructorFunction: null, - }); - exposePublicSymbol(name, ctor); - }, - - _embind_register_enum_value__deps: ['$createNamedFunction', '$readLatin1String', '$requireRegisteredType'], - _embind_register_enum_value: (rawEnumType, name, enumValue) => { - var enumType = requireRegisteredType(rawEnumType, 'enum'); - name = readLatin1String(name); - - var Enum = enumType.constructor; - - var Value = Object.create(enumType.constructor.prototype, { - value: {value: enumValue}, - constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})}, - }); - Enum.values[enumValue] = Value; - Enum[name] = Value; - }, - - _embind_register_constant__deps: ['$readLatin1String', '$whenDependentTypesAreResolved'], - _embind_register_constant: (name, type, value) => { - name = readLatin1String(name); - whenDependentTypesAreResolved([], [type], (type) => { - type = type[0]; - Module[name] = type['fromWireType'](value); - return []; - }); - }, -}; - -addToLibrary(LibraryEmbind); diff --git a/src/embind/embind_gen.js b/src/embind/embind_gen.js deleted file mode 100644 index 3d1c962838e38..0000000000000 --- a/src/embind/embind_gen.js +++ /dev/null @@ -1,788 +0,0 @@ -// Copyright 2023 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 "embind/embind_shared.js" - -var LibraryEmbind = { - - $moduleDefinitions: [], - // Function signatures that have already been generated for JS generation. - $emittedFunctions: 'new Set()', - - $PrimitiveType: class { - constructor(typeId, name, destructorType) { - this.typeId = typeId; - this.name = name; - this.destructorType = destructorType; - } - }, - $IntegerType: class { - constructor(typeId) { - this.typeId = typeId; - this.destructorType = 'none'; - } - }, - $Argument: class { - constructor(name, type) { - this.name = name; - this.type = type; - } - }, - $UserType: class { - constructor(typeId, name) { - this.typeId = typeId; - this.name = name; - this.destructorType = 'none'; // Same as emval. - } - }, - $OptionalType: class { - constructor(type) { - this.type = type; - this.destructorType = 'none'; // Same as emval. - } - }, - $FunctionDefinition__deps: ['$createJsInvoker', '$createJsInvokerSignature', '$emittedFunctions'], - $FunctionDefinition: class { - constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isAsync = false) { - this.name = name; - this.returnType = returnType; - this.argumentTypes = argumentTypes; - this.functionIndex = functionIndex; - this.thisType = thisType; - this.isAsync = isAsync; - } - - printSignature(nameMap, out) { - out.push('('); - - const argOut = []; - for (const arg of this.argumentTypes) { - argOut.push(`${arg.name}: ${nameMap(arg.type)}`); - } - out.push(argOut.join(', ')); - out.push(`): ${nameMap(this.returnType, true)}`); - } - - printFunction(nameMap, out) { - out.push(`${this.name}`); - this.printSignature(nameMap, out); - } - - printModuleEntry(nameMap, out) { - out.push(' '); - this.printFunction(nameMap, out); - out.push(';\n'); - } - - // Convert a type definition in this file to something that matches the type - // object in embind.js `registerType()`. - convertToEmbindType(type) { - const ret = { - name: type.name, - }; - switch (type.destructorType) { - case 'none': - ret.destructorFunction = null; - break; - case 'function': - ret.destructorFunction = true; - break; - case 'stack': - // Intentionally empty since embind uses `undefined` for this type. - break; - default: - throw new Error(`Bad destructor type '${type.destructorType}'`); - } - return ret; - } - - printJs(out) { - const argTypes = [this.convertToEmbindType(this.returnType)]; - if (this.thisType) { - argTypes.push(this.convertToEmbindType(this.thisType)); - } else { - argTypes.push(null); - } - for (const argType of this.argumentTypes) { - argTypes.push(this.convertToEmbindType(argType.type)); - } - const signature = createJsInvokerSignature(argTypes, !!this.thisType, this.returnType.name !== 'void', this.isAsync) - if (emittedFunctions.has(signature)) { - return; - } - emittedFunctions.add(signature); - let [args, body] = createJsInvoker(argTypes, !!this.thisType, this.returnType.name !== 'void', this.isAsync); - out.push( - // The ${""} is hack to workaround the preprocessor replacing "function". - `'${signature}': f${""}unction(${args.join(',')}) {\n${body}},` - ); - } - }, - $PointerDefinition: class { - constructor(classType, isConst, isSmartPointer) { - this.classType = classType; - this.isConst = isConst; - this.isSmartPointer = isSmartPointer; - this.destructorType = 'none'; - if (isSmartPointer || classType.base) { - this.destructorType = 'stack'; - } - } - }, - $ClassDefinition: class { - constructor(typeId, name, base = null) { - this.typeId = typeId; - this.name = name; - this.methods = []; - this.staticMethods = []; - this.staticProperties = []; - this.constructors = []; - this.base = base; - this.properties = []; - this.destructorType = 'none'; - if (base) { - this.destructorType = 'stack'; - } - } - - print(nameMap, out) { - out.push(`export interface ${this.name}`); - if (this.base) { - out.push(` extends ${this.base.name}`); - } - out.push(' {\n'); - for (const property of this.properties) { - out.push(' '); - property.print(nameMap, out); - out.push(';\n'); - } - for (const method of this.methods) { - out.push(' '); - method.printFunction(nameMap, out); - out.push(';\n'); - } - out.push(' delete(): void;\n'); - out.push('}\n\n'); - } - - printModuleEntry(nameMap, out) { - out.push(` ${this.name}: {`); - const entries = []; - for(const construct of this.constructors) { - const entry = []; - entry.push('new'); - construct.printSignature(nameMap, entry); - entries.push(entry.join('')); - } - for (const method of this.staticMethods) { - const entry = []; - method.printFunction(nameMap, entry); - entries.push(entry.join('')); - } - for (const prop of this.staticProperties) { - const entry = []; - prop.print(nameMap, entry); - entries.push(entry.join('')); - } - out.push(entries.join('; ')); - out.push('};\n'); - } - - printJs(out) { - out.push(`// class ${this.name}\n`); - if (this.constructors.length) { - out.push(`// constructors\n`); - for (const construct of this.constructors) { - construct.printJs(out); - } - } - if (this.staticMethods.length) { - out.push(`// static methods\n`); - for (const method of this.staticMethods) { - method.printJs(out); - } - } - if (this.methods.length) { - out.push(`// methods\n`); - for (const method of this.methods) { - method.printJs(out); - } - } - out.push('\n'); - } - - }, - $ClassProperty: class { - constructor(type, name, readonly) { - this.type = type; - this.name = name; - this.readonly = readonly; - } - - print(nameMap, out) { - out.push(`${this.readonly ? 'readonly ' : ''}${this.name}: ${nameMap(this.type)}`); - } - }, - $ConstantDefinition: class { - constructor(type, name) { - this.type = type; - this.name = name; - } - - printModuleEntry(nameMap, out) { - out.push(` ${this.name}: ${nameMap(this.type)};\n`); - } - }, - $EnumDefinition: class { - constructor(typeId, name) { - this.typeId = typeId; - this.name = name; - this.items = []; - this.destructorType = 'none'; - } - - print(nameMap, out) { - out.push(`export interface ${this.name}Value {\n`); - out.push(' value: T;\n}\n'); - out.push(`export type ${this.name} = `); - if (this.items.length === 0) { - out.push('never/* Empty Enumerator */'); - } else { - const outItems = []; - for (const [name, value] of this.items) { - outItems.push(`${this.name}Value<${value}>`); - } - out.push(outItems.join('|')); - } - out.push(';\n\n'); - } - - printModuleEntry(nameMap, out) { - out.push(` ${this.name}: {`); - const outItems = []; - for (const [name, value] of this.items) { - outItems.push(`${name}: ${this.name}Value<${value}>`); - } - out.push(outItems.join(', ')); - out.push('};\n'); - } - }, - $ValueArrayDefinition: class { - constructor(typeId, name) { - this.typeId = typeId; - this.name = name; - this.elementTypeIds = []; - this.elements = []; - this.destructorType = 'function'; - } - - print(nameMap, out) { - out.push(`export type ${this.name} = [ `); - const outElements = []; - for (const type of this.elements) { - outElements.push(nameMap(type)); - } - out.push(outElements.join(', ')) - out.push(' ];\n\n'); - } - }, - $ValueObjectDefinition: class { - constructor(typeId, name) { - this.typeId = typeId; - this.name = name; - this.fieldTypeIds = []; - this.fieldNames = []; - this.fields = []; - this.destructorType = 'function'; - } - - print(nameMap, out) { - out.push(`export type ${this.name} = {\n`); - const outFields = []; - for (const {name, type} of this.fields) { - outFields.push(` ${name}: ${nameMap(type)}`); - } - out.push(outFields.join(',\n')) - out.push('\n};\n\n'); - } - }, - $TsPrinter__deps: ['$OptionalType'], - $TsPrinter: class { - constructor(definitions) { - this.definitions = definitions; - const jsString = 'ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string'; - // The mapping is in the format of '' => ['toWireType', 'fromWireType'] - // or if the to/from wire types are the same use a single element. - this.builtInToJsName = new Map([ - ['bool', ['boolean']], - ['float', ['number']], - ['double', ['number']], -#if MEMORY64 - ['long', ['bigint']], - ['unsigned long', ['bigint']], -#endif -#if WASM_BIGINT - ['int64_t', ['bigint']], - ['uint64_t', ['bigint']], -#endif - ['void', ['void']], - ['std::string', [jsString, 'string']], - ['std::basic_string', [jsString, 'string']], - ['std::wstring', ['string']], - ['std::u16string', ['string']], - ['std::u32string', ['string']], - ['emscripten::val', ['any']], - ]); - } - - typeToJsName(type, isFromWireType = false) { - if (type instanceof IntegerType) { - return 'number'; - } - if (type instanceof PrimitiveType) { - if (!this.builtInToJsName.has(type.name)) { - throw new Error(`Missing primitive type to TS type for '${type.name}'`); - } - const [toWireType, fromWireType = toWireType] = this.builtInToJsName.get(type.name); - return isFromWireType ? fromWireType : toWireType; - } - if (type instanceof PointerDefinition) { - return this.typeToJsName(type.classType); - } - if (type instanceof OptionalType) { - return `${this.typeToJsName(type.type)} | undefined`; - } - return type.name; - } - - print() { - const out = []; - for (const def of this.definitions) { - if (!def.print) { - continue; - } - def.print(this.typeToJsName.bind(this), out); - } - // Print module definitions - out.push('interface EmbindModule {\n'); - for (const def of this.definitions) { - if (!def.printModuleEntry) { - continue; - } - def.printModuleEntry(this.typeToJsName.bind(this), out); - } - out.push('}'); - console.log(out.join('')); - } - }, - - $JsPrinter: class { - constructor(definitions) { - this.definitions = definitions; - } - - print() { - const out = ['{\n']; - for (const def of this.definitions) { - if (!def.printJs) { - continue; - } - def.printJs(out); - } - out.push('}') - console.log(out.join('')); - } - }, - - $registerType__deps: ['$sharedRegisterType'], - $registerType: function(rawType, registeredInstance, options = {}) { - return sharedRegisterType(rawType, registeredInstance, options); - }, - $registerPrimitiveType__deps: ['$registerType', '$PrimitiveType'], - $registerPrimitiveType: (id, name, destructorType) => { - name = readLatin1String(name); - registerType(id, new PrimitiveType(id, name, destructorType)); - }, - $registerIntegerType__deps: ['$registerType', '$IntegerType'], - $registerIntegerType: (id) => { - registerType(id, new IntegerType(id)); - }, - $createFunctionDefinition__deps: ['$FunctionDefinition', '$heap32VectorToArray', '$readLatin1String', '$Argument', '$whenDependentTypesAreResolved', '$getFunctionName', '$getFunctionArgsName', '$PointerDefinition', '$ClassDefinition'], - $createFunctionDefinition: (name, argCount, rawArgTypesAddr, functionIndex, hasThis, isAsync, cb) => { - const argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - name = typeof name === 'string' ? name : readLatin1String(name); - - whenDependentTypesAreResolved([], argTypes, function (argTypes) { - const argsName = getFunctionArgsName(name); - name = getFunctionName(name); - const returnType = argTypes[0]; - let thisType = null; - let argStart = 1; - if (hasThis) { - thisType = argTypes[1]; - if (thisType instanceof PointerDefinition) { - thisType = argTypes[1].classType; - } - if (!(thisType instanceof ClassDefinition)) { - throw new Error('This type must be class definition for: ' + name); - } - argStart = 2; - } - if (argsName.length) - assert(argsName.length == (argTypes.length - hasThis - 1), 'Argument names should match number of parameters.'); - const args = []; - for (let i = argStart, x = 0; i < argTypes.length; i++) { - if (x < argsName.length) { - args.push(new Argument(argsName[x++], argTypes[i])); - } else { - args.push(new Argument(`_${i - argStart}`, argTypes[i])); - } - } - const funcDef = new FunctionDefinition(name, returnType, args, functionIndex, thisType, isAsync); - cb(funcDef); - return []; - }); - }, - _embind_register_void__deps: ['$registerPrimitiveType'], - _embind_register_void: (rawType, name) => { - registerPrimitiveType(rawType, name, 'none'); - }, - _embind_register_bool__deps: ['$registerPrimitiveType'], - _embind_register_bool: (rawType, name, trueValue, falseValue) => { - registerPrimitiveType(rawType, name, 'none'); - }, - _embind_register_integer__deps: ['$registerIntegerType'], - _embind_register_integer: (primitiveType, name, size, minRange, maxRange) => { - registerIntegerType(primitiveType, name); - }, - _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => { - registerPrimitiveType(primitiveType, name, 'none'); - }, - _embind_register_float__deps: ['$registerPrimitiveType'], - _embind_register_float: (rawType, name, size) => { - registerPrimitiveType(rawType, name, 'none'); - }, - _embind_register_std_string__deps: ['$registerPrimitiveType'], - _embind_register_std_string: (rawType, name) => { - registerPrimitiveType(rawType, name, 'function'); - }, - _embind_register_std_wstring: (rawType, charSize, name) => { - registerPrimitiveType(rawType, name, 'function'); - }, - _embind_register_emval__deps: ['$registerType', '$PrimitiveType'], - _embind_register_emval: (rawType) => { - registerType(rawType, new PrimitiveType(rawType, 'emscripten::val', 'none')); - }, - _embind_register_user_type__deps: ['$registerType', '$readLatin1String', '$UserType'], - _embind_register_user_type: (rawType, name) => { - name = readLatin1String(name); - registerType(rawType, new UserType(rawType, name)); - }, - _embind_register_optional__deps: ['_embind_register_emval', '$OptionalType'], - _embind_register_optional: (rawOptionalType, rawType) => { - whenDependentTypesAreResolved([rawOptionalType], [rawType], function(type) { - type = type[0]; - return [new OptionalType(type)]; - }); - }, - _embind_register_memory_view: (rawType, dataTypeIndex, name) => { - // TODO - }, - _embind_register_function__deps: ['$moduleDefinitions', '$createFunctionDefinition'], - _embind_register_function: (name, argCount, rawArgTypesAddr, signature, rawInvoker, fn, isAsync) => { - createFunctionDefinition(name, argCount, rawArgTypesAddr, fn, false, isAsync, (funcDef) => { - moduleDefinitions.push(funcDef); - }); - }, - _embind_register_class__deps: ['$readLatin1String', '$ClassDefinition', '$whenDependentTypesAreResolved', '$moduleDefinitions', '$PointerDefinition'], - _embind_register_class: function(rawType, - rawPointerType, - rawConstPointerType, - baseClassRawType, - getActualTypeSignature, - getActualType, - upcastSignature, - upcast, - downcastSignature, - downcast, - name, - destructorSignature, - rawDestructor) { - name = readLatin1String(name); - whenDependentTypesAreResolved( - [rawType, rawPointerType, rawConstPointerType], - baseClassRawType ? [baseClassRawType] : [], - function(base) { - const hasBase = base.length; - const classDef = new ClassDefinition(rawType, name, hasBase ? base[0] : null); - moduleDefinitions.push(classDef); - - const pointer = new PointerDefinition(classDef, false, false); - const constPointer = new PointerDefinition(classDef, true, false); - return [classDef, pointer, constPointer]; - } - ); - - }, - _embind_register_class_constructor__deps: ['$whenDependentTypesAreResolved', '$createFunctionDefinition'], - _embind_register_class_constructor: function( - rawClassType, - argCount, - rawArgTypesAddr, - invokerSignature, - invoker, - rawConstructor - ) { - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - createFunctionDefinition(`constructor ${classType.name}`, argCount, rawArgTypesAddr, rawConstructor, false, false, (funcDef) => { - classType.constructors.push(funcDef); - }); - return []; - }); - }, - _embind_register_class_function__deps: ['$createFunctionDefinition'], - _embind_register_class_function: function(rawClassType, - methodName, - argCount, - rawArgTypesAddr, // [ReturnType, ThisType, Args...] - invokerSignature, - rawInvoker, - context, - isPureVirtual, - isAsync) { - createFunctionDefinition(methodName, argCount, rawArgTypesAddr, context, true, isAsync, (funcDef) => { - const classDef = funcDef.thisType; - classDef.methods.push(funcDef); - }); - }, - _embind_register_class_property__deps: [ - '$readLatin1String', '$whenDependentTypesAreResolved', '$ClassProperty'], - _embind_register_class_property: function(classType, - fieldName, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext) { - fieldName = readLatin1String(fieldName); - const readonly = setter === 0; - assert(readonly || getterReturnType === setterArgumentType, 'Mismatched getter and setter types are not supported.'); - whenDependentTypesAreResolved([], [classType], function(classType) { - classType = classType[0]; - whenDependentTypesAreResolved([], [getterReturnType], function(types) { - const prop = new ClassProperty(types[0], fieldName, readonly); - classType.properties.push(prop); - return []; - }); - return []; - }); - }, - _embind_register_class_class_function__deps: ['$createFunctionDefinition'], - _embind_register_class_class_function: function(rawClassType, - methodName, - argCount, - rawArgTypesAddr, - invokerSignature, - rawInvoker, - fn, - isAsync) { - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - createFunctionDefinition(methodName, argCount, rawArgTypesAddr, fn, false, isAsync, (funcDef) => { - classType.staticMethods.push(funcDef); - }); - return []; - }); - }, - _embind_register_class_class_property__deps: [ - '$readLatin1String', '$whenDependentTypesAreResolved', '$ClassProperty'], - _embind_register_class_class_property: (rawClassType, - fieldName, - rawFieldType, - rawFieldPtr, - getterSignature, - getter, - setterSignature, - setter) => { - fieldName = readLatin1String(fieldName); - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - whenDependentTypesAreResolved([], [rawFieldType], function(types) { - const prop = new ClassProperty(types[0], fieldName); - classType.staticProperties.push(prop); - return []; - }); - return []; - }); - }, - // Stub function. This is called a when extending an object and not needed for TS generation. - _embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {}, - _embind_register_enum__deps: ['$readLatin1String', '$EnumDefinition', '$moduleDefinitions'], - _embind_register_enum: function(rawType, name, size, isSigned) { - name = readLatin1String(name); - const enumDef = new EnumDefinition(rawType, name); - registerType(rawType, enumDef); - moduleDefinitions.push(enumDef); - }, - _embind_register_enum_value__deps: ['$readLatin1String', '$requireRegisteredType'], - _embind_register_enum_value: function(rawEnumType, name, enumValue) { - name = readLatin1String(name); - const enumDef = requireRegisteredType(rawEnumType, name); - enumDef.items.push([name, enumValue]); - }, - _embind_register_constant__deps: ['$readLatin1String', '$ConstantDefinition', '$whenDependentTypesAreResolved', '$moduleDefinitions'], - _embind_register_constant: function(name, typeId, value) { - name = readLatin1String(name); - whenDependentTypesAreResolved([], [typeId], function(types) { - const def = new ConstantDefinition(types[0], name); - moduleDefinitions.push(def); - return []; - }); - }, - _embind_register_value_array__deps: [ - '$readLatin1String', '$ValueArrayDefinition', '$tupleRegistrations'], - _embind_register_value_array: function( - rawType, - name, - constructorSignature, - rawConstructor, - destructorSignature, - rawDestructor - ) { - name = readLatin1String(name); - const valueArray = new ValueArrayDefinition(rawType, name); - tupleRegistrations[rawType] = valueArray; - }, - _embind_register_value_array_element__deps: ['$tupleRegistrations'], - _embind_register_value_array_element: function( - rawTupleType, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext - ) { - const valueArray = tupleRegistrations[rawTupleType]; - assert(getterReturnType === setterArgumentType, 'Mismatched getter and setter types are not supported.'); - valueArray.elementTypeIds.push(getterReturnType); - }, - _embind_finalize_value_array__deps: ['$whenDependentTypesAreResolved', '$moduleDefinitions', '$tupleRegistrations'], - _embind_finalize_value_array: function(rawTupleType) { - const valueArray = tupleRegistrations[rawTupleType]; - delete tupleRegistrations[rawTupleType]; - whenDependentTypesAreResolved([rawTupleType], valueArray.elementTypeIds, function(types) { - moduleDefinitions.push(valueArray); - valueArray.elements = types; - return [valueArray]; - }); - }, - _embind_register_value_object__deps: ['$readLatin1String', '$ValueObjectDefinition', '$structRegistrations'], - _embind_register_value_object: function( - rawType, - name, - constructorSignature, - rawConstructor, - destructorSignature, - rawDestructor - ) { - name = readLatin1String(name); - const valueObject = new ValueObjectDefinition(rawType, name); - structRegistrations[rawType] = valueObject; - }, - _embind_register_value_object_field__deps: [ - '$readLatin1String', '$structRegistrations'], - _embind_register_value_object_field: function( - structType, - fieldName, - getterReturnType, - getterSignature, - getter, - getterContext, - setterArgumentType, - setterSignature, - setter, - setterContext - ) { - const valueObject = structRegistrations[structType]; - assert(getterReturnType === setterArgumentType, 'Mismatched getter and setter types are not supported.'); - valueObject.fieldTypeIds.push(getterReturnType); - valueObject.fieldNames.push(readLatin1String(fieldName)); - }, - _embind_finalize_value_object__deps: ['$moduleDefinitions', '$whenDependentTypesAreResolved', '$structRegistrations'], - _embind_finalize_value_object: function(structType) { - const valueObject = structRegistrations[structType]; - delete structRegistrations[structType]; - whenDependentTypesAreResolved([structType], valueObject.fieldTypeIds, function(types) { - moduleDefinitions.push(valueObject); - for (let i = 0; i < types.length; i++) { - valueObject.fields.push({ - name: valueObject.fieldNames[i], - type: types[i], - }); - } - return [valueObject]; - }); - }, - _embind_register_smart_ptr__deps: ['$whenDependentTypesAreResolved'], - _embind_register_smart_ptr: function(rawType, - rawPointeeType, - name, - sharingPolicy, - getPointeeSignature, - rawGetPointee, - constructorSignature, - rawConstructor, - shareSignature, - rawShare, - destructorSignature, - rawDestructor) { - whenDependentTypesAreResolved([rawType], [rawPointeeType], function(pointeeType) { - const smartPointer = new PointerDefinition(pointeeType[0], false, true); - return [smartPointer]; - }); - }, - -#if EMBIND_AOT - $embindEmitAotJs__deps: ['$awaitingDependencies', '$throwBindingError', '$getTypeName', '$moduleDefinitions', '$JsPrinter'], - $embindEmitAotJs__postset: 'addOnInit(embindEmitAotJs);', - $embindEmitAotJs: () => { - for (const typeId in awaitingDependencies) { - throwBindingError(`Missing binding for type: '${getTypeName(typeId)}' typeId: ${typeId}`); - } - const printer = new JsPrinter(moduleDefinitions); - printer.print(); - }, -#else // EMBIND_AOT - $embindEmitTypes__deps: ['$awaitingDependencies', '$throwBindingError', '$getTypeName', '$moduleDefinitions', '$TsPrinter'], - $embindEmitTypes__postset: 'addOnInit(embindEmitTypes);', - $embindEmitTypes: () => { - for (const typeId in awaitingDependencies) { - throwBindingError(`Missing binding for type: '${getTypeName(typeId)}' typeId: ${typeId}`); - } - const printer = new TsPrinter(moduleDefinitions); - printer.print(); - }, -#endif - - // Stub functions used by eval, but not needed for TS generation: - $makeLegalFunctionName: () => assert(false, 'stub function should not be called'), - $newFunc: () => assert(false, 'stub function should not be called'), - $runDestructors: () => assert(false, 'stub function should not be called'), - $createNamedFunction: () => assert(false, 'stub function should not be called'), -}; - -#if EMBIND_AOT -extraLibraryFuncs.push('$embindEmitAotJs'); -#else -extraLibraryFuncs.push('$embindEmitTypes'); -#endif - -addToLibrary(LibraryEmbind); diff --git a/src/embind/embind_shared.js b/src/embind/embind_shared.js deleted file mode 100644 index 3e48a6fdc68d1..0000000000000 --- a/src/embind/embind_shared.js +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2023 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. -var LibraryEmbindShared = { - $InternalError__postset: "InternalError = Module['InternalError'] = class InternalError extends Error { constructor(message) { super(message); this.name = 'InternalError'; }}", - $InternalError: undefined, - $BindingError__postset: "BindingError = Module['BindingError'] = class BindingError extends Error { constructor(message) { super(message); this.name = 'BindingError'; }}", - $BindingError: undefined, - - $throwInternalError__deps: ['$InternalError'], - $throwInternalError: (message) => { throw new InternalError(message); }, - - $throwBindingError__deps: ['$BindingError'], - $throwBindingError: (message) => { throw new BindingError(message); }, - - // typeID -> { toWireType: ..., fromWireType: ... } - $registeredTypes: {}, - - // typeID -> [callback] - $awaitingDependencies: {}, - - // typeID -> [dependentTypes] - $typeDependencies: {}, - - $tupleRegistrations: {}, - - $structRegistrations: {}, - - $sharedRegisterType__deps: [ - '$awaitingDependencies', '$registeredTypes', - '$typeDependencies', '$throwBindingError', - '$whenDependentTypesAreResolved'], - $sharedRegisterType__docs: '/** @param {Object=} options */', - $sharedRegisterType: function(rawType, registeredInstance, options = {}) { - var name = registeredInstance.name; - if (!rawType) { - throwBindingError(`type "${name}" must have a positive integer typeid pointer`); - } - if (registeredTypes.hasOwnProperty(rawType)) { - if (options.ignoreDuplicateRegistrations) { - return; - } else { - throwBindingError(`Cannot register type '${name}' twice`); - } - } - - registeredTypes[rawType] = registeredInstance; - delete typeDependencies[rawType]; - - if (awaitingDependencies.hasOwnProperty(rawType)) { - var callbacks = awaitingDependencies[rawType]; - delete awaitingDependencies[rawType]; - callbacks.forEach((cb) => cb()); - } - }, - - $whenDependentTypesAreResolved__deps: [ - '$awaitingDependencies', '$registeredTypes', - '$typeDependencies', '$throwInternalError'], - $whenDependentTypesAreResolved: (myTypes, dependentTypes, getTypeConverters) => { - myTypes.forEach(function(type) { - typeDependencies[type] = dependentTypes; - }); - - function onComplete(typeConverters) { - var myTypeConverters = getTypeConverters(typeConverters); - if (myTypeConverters.length !== myTypes.length) { - throwInternalError('Mismatched type converter count'); - } - for (var i = 0; i < myTypes.length; ++i) { - registerType(myTypes[i], myTypeConverters[i]); - } - } - - var typeConverters = new Array(dependentTypes.length); - var unregisteredTypes = []; - var registered = 0; - dependentTypes.forEach((dt, i) => { - if (registeredTypes.hasOwnProperty(dt)) { - typeConverters[i] = registeredTypes[dt]; - } else { - unregisteredTypes.push(dt); - if (!awaitingDependencies.hasOwnProperty(dt)) { - awaitingDependencies[dt] = []; - } - awaitingDependencies[dt].push(() => { - typeConverters[i] = registeredTypes[dt]; - ++registered; - if (registered === unregisteredTypes.length) { - onComplete(typeConverters); - } - }); - } - }); - if (0 === unregisteredTypes.length) { - onComplete(typeConverters); - } - }, - - $embind_charCodes__deps: ['$embind_init_charCodes'], - $embind_charCodes__postset: "embind_init_charCodes()", - $embind_charCodes: undefined, - $embind_init_charCodes: () => { - var codes = new Array(256); - for (var i = 0; i < 256; ++i) { - codes[i] = String.fromCharCode(i); - } - embind_charCodes = codes; - }, - $readLatin1String__deps: ['$embind_charCodes'], - $readLatin1String: (ptr) => { - var ret = ""; - var c = ptr; - while (HEAPU8[c]) { - ret += embind_charCodes[HEAPU8[c++]]; - } - return ret; - }, - $getTypeName__deps: ['$readLatin1String', '__getTypeName', 'free'], - $getTypeName: (type) => { - var ptr = ___getTypeName(type); - var rv = readLatin1String(ptr); - _free(ptr); - return rv; - }, - $getFunctionName__deps: [], - $getFunctionName: (signature) => { - signature = signature.trim(); - const argsIndex = signature.indexOf("("); - if (argsIndex !== -1) { -#if ASSERTIONS - assert(signature[signature.length - 1] == ")", "Parentheses for argument names should match."); -#endif - return signature.substr(0, argsIndex); - } else { - return signature; - } - }, - $getFunctionArgsName__deps: [], - $getFunctionArgsName: (signature) => { - signature = signature.trim(); - const argsIndex = signature.indexOf("(") + 1; - if (argsIndex !== 0) { -#if ASSERTIONS - assert(signature[signature.length - 1] == ")", "Parentheses for argument names should match."); -#endif - return signature.substr(argsIndex, signature.length - argsIndex - 1).replaceAll(" ", "").split(",").filter(n => n.length); - } else { - return []; - } - }, - $heap32VectorToArray: (count, firstElement) => { - var array = []; - for (var i = 0; i < count; i++) { - // TODO(https://github.com/emscripten-core/emscripten/issues/17310): - // Find a way to hoist the `>> 2` or `>> 3` out of this loop. - array.push({{{ makeGetValue('firstElement', `i * ${POINTER_SIZE}`, '*') }}}); - } - return array; - }, - - $requireRegisteredType__deps: [ - '$registeredTypes', '$getTypeName', '$throwBindingError'], - $requireRegisteredType: (rawType, humanName) => { - var impl = registeredTypes[rawType]; - if (undefined === impl) { - throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`); - } - return impl; - }, - - $usesDestructorStack(argTypes) { - // Skip return value at index 0 - it's not deleted here. - for (var i = 1; i < argTypes.length; ++i) { - // The type does not define a destructor function - must use dynamic stack - if (argTypes[i] !== null && argTypes[i].destructorFunction === undefined) { - return true; - } - } - return false; - }, - - // Many of the JS invoker functions are generic and can be reused for multiple - // function bindings. This function needs to match createJsInvoker and create - // a unique signature for any inputs that will create different invoker - // function outputs. - $createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync) { - const signature = [ - isClassMethodFunc ? 't' : 'f', - returns ? 't' : 'f', - isAsync ? 't' : 'f' - ]; - for (let i = isClassMethodFunc ? 1 : 2; i < argTypes.length; ++i) { - const arg = argTypes[i]; - let destructorSig = ''; - if (arg.destructorFunction === undefined) { - destructorSig = 'u'; - } else if (arg.destructorFunction === null) { - destructorSig = 'n'; - } else { - destructorSig = 't'; - } - signature.push(destructorSig); - } - return signature.join(''); - }, - - $createJsInvoker__deps: ['$usesDestructorStack'], - $createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync) { - var needsDestructorStack = usesDestructorStack(argTypes); - var argCount = argTypes.length; - var argsList = ""; - var argsListWired = ""; - for (var i = 0; i < argCount - 2; ++i) { - argsList += (i!==0?", ":"")+"arg"+i; - argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; - } - - var invokerFnBody = ` - return function (${argsList}) { - if (arguments.length !== ${argCount - 2}) { - throwBindingError('function ' + humanName + ' called with ' + arguments.length + ' arguments, expected ${argCount - 2}'); - }`; - -#if EMSCRIPTEN_TRACING - invokerFnBody += `Module.emscripten_trace_enter_context('embind::' + humanName );\n`; -#endif - - if (needsDestructorStack) { - invokerFnBody += "var destructors = [];\n"; - } - - var dtorStack = needsDestructorStack ? "destructors" : "null"; - var args1 = ["humanName", "throwBindingError", "invoker", "fn", "runDestructors", "retType", "classParam"]; - -#if EMSCRIPTEN_TRACING - args1.push("Module"); -#endif - - if (isClassMethodFunc) { - invokerFnBody += "var thisWired = classParam['toWireType']("+dtorStack+", this);\n"; - } - - for (var i = 0; i < argCount - 2; ++i) { - invokerFnBody += "var arg"+i+"Wired = argType"+i+"['toWireType']("+dtorStack+", arg"+i+");\n"; - args1.push("argType"+i); - } - - if (isClassMethodFunc) { - argsListWired = "thisWired" + (argsListWired.length > 0 ? ", " : "") + argsListWired; - } - - invokerFnBody += - (returns || isAsync ? "var rv = ":"") + "invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n"; - - var returnVal = returns ? "rv" : ""; -#if ASYNCIFY == 1 - args1.push("Asyncify"); -#endif -#if ASYNCIFY - invokerFnBody += `function onDone(${returnVal}) {\n`; -#endif - - if (needsDestructorStack) { - invokerFnBody += "runDestructors(destructors);\n"; - } else { - for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); - if (argTypes[i].destructorFunction !== null) { - invokerFnBody += `${paramName}_dtor(${paramName});\n`; - args1.push(`${paramName}_dtor`); - } - } - } - - if (returns) { - invokerFnBody += "var ret = retType['fromWireType'](rv);\n" + -#if EMSCRIPTEN_TRACING - "Module.emscripten_trace_exit_context();\n" + -#endif - "return ret;\n"; - } else { -#if EMSCRIPTEN_TRACING - invokerFnBody += "Module.emscripten_trace_exit_context();\n"; -#endif - } - -#if ASYNCIFY == 1 - invokerFnBody += "}\n"; - invokerFnBody += `return Asyncify.currData ? Asyncify.whenDone().then(onDone) : onDone(${returnVal});\n` -#elif ASYNCIFY == 2 - invokerFnBody += "}\n"; - invokerFnBody += "return " + (isAsync ? "rv.then(onDone)" : `onDone(${returnVal})`) + ";"; -#endif - - invokerFnBody += "}\n"; - -#if ASSERTIONS - invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`; -#endif - return [args1, invokerFnBody]; - } -}; - -addToLibrary(LibraryEmbindShared); diff --git a/src/embind/emval.js b/src/embind/emval.js deleted file mode 100644 index 8d22ae24839ad..0000000000000 --- a/src/embind/emval.js +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2012 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. - -/*global Module:true, Runtime*/ -/*global HEAP32*/ -/*global newFunc*/ -/*global createNamedFunction*/ -/*global readLatin1String, stringToUTF8*/ -/*global requireRegisteredType, throwBindingError, runDestructors*/ -/*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */ - -// -- jshint doesn't understand library syntax, so we need to mark the symbols exposed here -/*global getStringOrSymbol, emval_freelist, emval_handles, Emval, __emval_unregister, count_emval_handles, emval_symbols, __emval_decref*/ -/*global emval_addMethodCaller, emval_methodCallers, addToLibrary, global, emval_lookupTypes, makeLegalFunctionName*/ -/*global emval_get_global*/ - -// Number of handles reserved for non-use (0) or common values w/o refcount. -{{{ - globalThis.EMVAL_RESERVED_HANDLES = 5; - globalThis.EMVAL_LAST_RESERVED_HANDLE = globalThis.EMVAL_RESERVED_HANDLES * 2 - 1; - null; -}}} -var LibraryEmVal = { - // Stack of handles available for reuse. - $emval_freelist: [], - // Array of alternating pairs (value, refcount). - $emval_handles: [], - $emval_symbols: {}, // address -> string - - $init_emval__deps: ['$count_emval_handles', '$emval_handles'], - $init_emval__postset: 'init_emval();', - $init_emval: () => { - // reserve 0 and some special values. These never get de-allocated. - emval_handles.push( - 0, 1, - undefined, 1, - null, 1, - true, 1, - false, 1, - ); - #if ASSERTIONS - assert(emval_handles.length === {{{ EMVAL_RESERVED_HANDLES }}} * 2); - #endif - Module['count_emval_handles'] = count_emval_handles; - }, - - $count_emval_handles__deps: ['$emval_freelist', '$emval_handles'], - $count_emval_handles: () => { - return emval_handles.length / 2 - {{{ EMVAL_RESERVED_HANDLES }}} - emval_freelist.length; - }, - - _emval_register_symbol__deps: ['$emval_symbols', '$readLatin1String'], - _emval_register_symbol: (address) => { - emval_symbols[address] = readLatin1String(address); - }, - - $getStringOrSymbol__deps: ['$emval_symbols', '$readLatin1String'], - $getStringOrSymbol: (address) => { - var symbol = emval_symbols[address]; - if (symbol === undefined) { - return readLatin1String(address); - } - return symbol; - }, - - $Emval__deps: ['$emval_freelist', '$emval_handles', '$throwBindingError', '$init_emval'], - $Emval: { - toValue: (handle) => { - if (!handle) { - throwBindingError('Cannot use deleted val. handle = ' + handle); - } - #if ASSERTIONS - // handle 2 is supposed to be `undefined`. - assert(handle === 2 || emval_handles[handle] !== undefined && handle % 2 === 0, `invalid handle: ${handle}`); - #endif - return emval_handles[handle]; - }, - - toHandle: (value) => { - switch (value) { - case undefined: return 2; - case null: return 4; - case true: return 6; - case false: return 8; - default:{ - const handle = emval_freelist.pop() || emval_handles.length; - emval_handles[handle] = value; - emval_handles[handle + 1] = 1; - return handle; - } - } - } - }, - - _emval_incref__deps: ['$emval_handles'], - _emval_incref: (handle) => { - if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}}) { - emval_handles[handle + 1] += 1; - } - }, - - _emval_decref__deps: ['$emval_freelist', '$emval_handles'], - _emval_decref: (handle) => { - if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { - #if ASSERTIONS - assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`); - #endif - emval_handles[handle] = undefined; - emval_freelist.push(handle); - } - }, - - _emval_run_destructors__deps: ['_emval_decref', '$Emval', '$runDestructors'], - _emval_run_destructors: (handle) => { - var destructors = Emval.toValue(handle); - runDestructors(destructors); - __emval_decref(handle); - }, - - _emval_new_array__deps: ['$Emval'], - _emval_new_array: () => Emval.toHandle([]), - - _emval_new_array_from_memory_view__deps: ['$Emval'], - _emval_new_array_from_memory_view: (view) => { - view = Emval.toValue(view); - // using for..loop is faster than Array.from - var a = new Array(view.length); - for (var i = 0; i < view.length; i++) a[i] = view[i]; - return Emval.toHandle(a); - }, - - _emval_new_object__deps: ['$Emval'], - _emval_new_object: () => Emval.toHandle({}), - - _emval_new_cstring__deps: ['$getStringOrSymbol', '$Emval'], - _emval_new_cstring: (v) => Emval.toHandle(getStringOrSymbol(v)), - - _emval_new_u8string__deps: ['$Emval'], - _emval_new_u8string: (v) => Emval.toHandle(UTF8ToString(v)), - - _emval_new_u16string__deps: ['$Emval'], - _emval_new_u16string: (v) => Emval.toHandle(UTF16ToString(v)), - - _emval_take_value__deps: ['$Emval', '$requireRegisteredType'], - _emval_take_value: (type, arg) => { - type = requireRegisteredType(type, '_emval_take_value'); - var v = type['readValueFromPointer'](arg); - return Emval.toHandle(v); - }, - -#if !DYNAMIC_EXECUTION - $emval_get_global: () => { - if (typeof globalThis == 'object') { - return globalThis; - } - function testGlobal(obj) { - obj['$$$embind_global$$$'] = obj; - var success = typeof $$$embind_global$$$ == 'object' && obj['$$$embind_global$$$'] == obj; - if (!success) { - delete obj['$$$embind_global$$$']; - } - return success; - } - if (typeof $$$embind_global$$$ == 'object') { - return $$$embind_global$$$; - } - if (typeof global == 'object' && testGlobal(global)) { - $$$embind_global$$$ = global; - } else if (typeof self == 'object' && testGlobal(self)) { - $$$embind_global$$$ = self; // This works for both "window" and "self" (Web Workers) global objects - } - if (typeof $$$embind_global$$$ == 'object') { - return $$$embind_global$$$; - } - throw Error('unable to get global object.'); - }, -#else - // appease jshint (technically this code uses eval) - $emval_get_global: () => { - if (typeof globalThis == 'object') { - return globalThis; - } - return (function(){ - return Function; - })()('return this')(); - }, -#endif - _emval_get_global__deps: ['$Emval', '$getStringOrSymbol', '$emval_get_global'], - _emval_get_global: (name) => { - if (name===0) { - return Emval.toHandle(emval_get_global()); - } else { - name = getStringOrSymbol(name); - return Emval.toHandle(emval_get_global()[name]); - } - }, - - _emval_get_module_property__deps: ['$getStringOrSymbol', '$Emval'], - _emval_get_module_property: (name) => { - name = getStringOrSymbol(name); - return Emval.toHandle(Module[name]); - }, - - _emval_get_property__deps: ['$Emval'], - _emval_get_property: (handle, key) => { - handle = Emval.toValue(handle); - key = Emval.toValue(key); - return Emval.toHandle(handle[key]); - }, - - _emval_set_property__deps: ['$Emval'], - _emval_set_property: (handle, key, value) => { - handle = Emval.toValue(handle); - key = Emval.toValue(key); - value = Emval.toValue(value); - handle[key] = value; - }, - - $emval_returnValue__deps: ['$Emval'], - $emval_returnValue: (returnType, destructorsRef, handle) => { - var destructors = []; - var result = returnType['toWireType'](destructors, handle); - if (destructors.length) { - // void, primitives and any other types w/o destructors don't need to allocate a handle - {{{ makeSetValue('destructorsRef', '0', 'Emval.toHandle(destructors)', '*') }}}; - } - return result; - }, - - _emval_as__deps: ['$Emval', '$requireRegisteredType', '$emval_returnValue'], - _emval_as: (handle, returnType, destructorsRef) => { - handle = Emval.toValue(handle); - returnType = requireRegisteredType(returnType, 'emval::as'); - return emval_returnValue(returnType, destructorsRef, handle); - }, - - _emval_as_int64__deps: ['$Emval', '$requireRegisteredType'], - _emval_as_int64: (handle, returnType) => { - handle = Emval.toValue(handle); - returnType = requireRegisteredType(returnType, 'emval::as'); - return returnType['toWireType'](null, handle); - }, - - _emval_as_uint64__deps: ['$Emval', '$requireRegisteredType'], - _emval_as_uint64: (handle, returnType) => { - handle = Emval.toValue(handle); - returnType = requireRegisteredType(returnType, 'emval::as'); - return returnType['toWireType'](null, handle); - }, - - _emval_equals__deps: ['$Emval'], - _emval_equals: (first, second) => { - first = Emval.toValue(first); - second = Emval.toValue(second); - return first == second; - }, - - _emval_strictly_equals__deps: ['$Emval'], - _emval_strictly_equals: (first, second) => { - first = Emval.toValue(first); - second = Emval.toValue(second); - return first === second; - }, - - _emval_greater_than__deps: ['$Emval'], - _emval_greater_than: (first, second) => { - first = Emval.toValue(first); - second = Emval.toValue(second); - return first > second; - }, - - _emval_less_than__deps: ['$Emval'], - _emval_less_than: (first, second) => { - first = Emval.toValue(first); - second = Emval.toValue(second); - return first < second; - }, - - _emval_not__deps: ['$Emval'], - _emval_not: (object) => { - object = Emval.toValue(object); - return !object; - }, - - _emval_call__deps: ['$emval_methodCallers', '$Emval'], - _emval_call: (caller, handle, destructorsRef, args) => { - caller = emval_methodCallers[caller]; - handle = Emval.toValue(handle); - return caller(null, handle, destructorsRef, args); - }, - - $emval_lookupTypes__deps: ['$requireRegisteredType'], - $emval_lookupTypes: (argCount, argTypes) => { - var a = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - a[i] = requireRegisteredType({{{ makeGetValue('argTypes', 'i * ' + POINTER_SIZE, '*') }}}, - "parameter " + i); - } - return a; - }, - - // Leave id 0 undefined. It's not a big deal, but might be confusing - // to have null be a valid method caller. - $emval_methodCallers: [undefined], - - $emval_addMethodCaller__deps: ['$emval_methodCallers'], - $emval_addMethodCaller: (caller) => { - var id = emval_methodCallers.length; - emval_methodCallers.push(caller); - return id; - }, - -#if MIN_CHROME_VERSION < 49 || MIN_FIREFOX_VERSION < 42 || MIN_SAFARI_VERSION < 100101 - $reflectConstruct: null, - $reflectConstruct__postset: ` - if (typeof Reflect !== 'undefined') { - reflectConstruct = Reflect.construct; - } else { - reflectConstruct = function(target, args) { - // limited polyfill for Reflect.construct that handles variadic args and native objects, but not new.target - return new (target.bind.apply(target, [null].concat(args)))(); - }; - } - `, -#else - $reflectConstruct: 'Reflect.construct', -#endif - - _emval_get_method_caller__deps: [ - '$emval_addMethodCaller', '$emval_lookupTypes', - '$createNamedFunction', - '$reflectConstruct', '$emval_returnValue', -#if DYNAMIC_EXECUTION - '$newFunc', -#endif - ], - _emval_get_method_caller: (argCount, argTypes, kind) => { - var types = emval_lookupTypes(argCount, argTypes); - var retType = types.shift(); - argCount--; // remove the shifted off return type - -#if !DYNAMIC_EXECUTION - var argN = new Array(argCount); - var invokerFunction = (obj, func, destructorsRef, args) => { - var offset = 0; - for (var i = 0; i < argCount; ++i) { - argN[i] = types[i]['readValueFromPointer'](args + offset); - offset += types[i]['argPackAdvance']; - } - var rv = kind === /* CONSTRUCTOR */ 1 ? reflectConstruct(func, argN) : func.apply(obj, argN); - return emval_returnValue(retType, destructorsRef, rv); - }; -#else - var functionBody = - `return function (obj, func, destructorsRef, args) {\n`; - - var offset = 0; - var argsList = []; // 'obj?, arg0, arg1, arg2, ... , argN' - if (kind === /* FUNCTION */ 0) { - argsList.push("obj"); - } - var params = ["retType"]; - var args = [retType]; - for (var i = 0; i < argCount; ++i) { - argsList.push("arg" + i); - params.push("argType" + i); - args.push(types[i]); - functionBody += - ` var arg${i} = argType${i}.readValueFromPointer(args${offset ? "+" + offset : ""});\n`; - offset += types[i]['argPackAdvance']; - } - var invoker = kind === /* CONSTRUCTOR */ 1 ? 'new func' : 'func.call'; - functionBody += - ` var rv = ${invoker}(${argsList.join(", ")});\n`; - if (!retType.isVoid) { - params.push("emval_returnValue"); - args.push(emval_returnValue); - functionBody += - " return emval_returnValue(retType, destructorsRef, rv);\n"; - } - functionBody += - "};\n"; - - params.push(functionBody); - var invokerFunction = newFunc(Function, params)(...args); -#endif - var functionName = `methodCaller<(${types.map(t => t.name).join(', ')}) => ${retType.name}>`; - return emval_addMethodCaller(createNamedFunction(functionName, invokerFunction)); - }, - - _emval_call_method__deps: ['$getStringOrSymbol', '$emval_methodCallers', '$Emval'], - _emval_call_method: (caller, objHandle, methodName, destructorsRef, args) => { - caller = emval_methodCallers[caller]; - objHandle = Emval.toValue(objHandle); - methodName = getStringOrSymbol(methodName); - return caller(objHandle, objHandle[methodName], destructorsRef, args); - }, - - _emval_typeof__deps: ['$Emval'], - _emval_typeof: (handle) => { - handle = Emval.toValue(handle); - return Emval.toHandle(typeof handle); - }, - - _emval_instanceof__deps: ['$Emval'], - _emval_instanceof: (object, constructor) => { - object = Emval.toValue(object); - constructor = Emval.toValue(constructor); - return object instanceof constructor; - }, - - _emval_is_number__deps: ['$Emval'], - _emval_is_number: (handle) => { - handle = Emval.toValue(handle); - return typeof handle == 'number'; - }, - - _emval_is_string__deps: ['$Emval'], - _emval_is_string: (handle) => { - handle = Emval.toValue(handle); - return typeof handle == 'string'; - }, - - _emval_in__deps: ['$Emval'], - _emval_in: (item, object) => { - item = Emval.toValue(item); - object = Emval.toValue(object); - return item in object; - }, - - _emval_delete__deps: ['$Emval'], - _emval_delete: (object, property) => { - object = Emval.toValue(object); - property = Emval.toValue(property); - return delete object[property]; - }, - - _emval_throw__deps: ['$Emval'], - _emval_throw: (object) => { - object = Emval.toValue(object); - throw object; - }, - -#if ASYNCIFY - _emval_await__deps: ['$Emval', '$Asyncify'], - _emval_await__async: true, - _emval_await: (promise) => { - return Asyncify.handleAsync(() => { - promise = Emval.toValue(promise); - return promise.then(Emval.toHandle); - }); - }, -#endif - - _emval_iter_begin__deps: ['$Emval'], - _emval_iter_begin: (iterable) => { - iterable = Emval.toValue(iterable); - return Emval.toHandle(iterable[Symbol.iterator]()); - }, - - _emval_iter_next__deps: ['$Emval'], - _emval_iter_next: (iterator) => { - iterator = Emval.toValue(iterator); - var result = iterator.next(); - return result.done ? 0 : Emval.toHandle(result.value); - }, - - _emval_coro_suspend__deps: ['$Emval', '_emval_coro_resume'], - _emval_coro_suspend: (promiseHandle, awaiterPtr) => { - Emval.toValue(promiseHandle).then(result => { - __emval_coro_resume(awaiterPtr, Emval.toHandle(result)); - }); - }, - - _emval_coro_make_promise__deps: ['$Emval', '__cxa_rethrow'], - _emval_coro_make_promise: (resolveHandlePtr, rejectHandlePtr) => { - return Emval.toHandle(new Promise((resolve, reject) => { - const rejectWithCurrentException = () => { - try { - // Use __cxa_rethrow which already has mechanism for generating - // user-friendly error message and stacktrace from C++ exception - // if EXCEPTION_STACK_TRACES is enabled and numeric exception - // with metadata optimised out otherwise. - ___cxa_rethrow(); - } catch (e) { - // But catch it so that it rejects the promise instead of throwing - // in an unpredictable place during async execution. - reject(e); - } - }; - - {{{ makeSetValue('resolveHandlePtr', '0', 'Emval.toHandle(resolve)', '*') }}}; - {{{ makeSetValue('rejectHandlePtr', '0', 'Emval.toHandle(rejectWithCurrentException)', '*') }}}; - })); - }, -}; - -addToLibrary(LibraryEmVal); diff --git a/src/emrun_postjs.js b/src/emrun_postjs.js index 5aecc5e932a26..b2ede002e796f 100644 --- a/src/emrun_postjs.js +++ b/src/emrun_postjs.js @@ -3,11 +3,11 @@ * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT * - * This file gets implicatly injected as a `--post-js` file when + * This file gets implicitly injected as a `--post-js` file when * emcc is run with `--emrun` */ -if (typeof window == "object" && (typeof ENVIRONMENT_IS_PTHREAD == 'undefined' || !ENVIRONMENT_IS_PTHREAD)) { +if (globalThis.window && (typeof ENVIRONMENT_IS_PTHREAD == 'undefined' || !ENVIRONMENT_IS_PTHREAD)) { var emrun_register_handlers = () => { // When C code exit()s, we may still have remaining stdout and stderr // messages in flight. In that case, we can't close the browser until all @@ -89,7 +89,7 @@ if (typeof window == "object" && (typeof ENVIRONMENT_IS_PTHREAD == 'undefined' | http.send(data); // XXX this does not work in workers, for some odd reason (issue #2681) }; - if (typeof document != 'undefined') { + if (globalThis.document) { emrun_register_handlers(); } } diff --git a/src/emrun_prejs.js b/src/emrun_prejs.js index 3ff49965ad7b4..c23e589562580 100644 --- a/src/emrun_prejs.js +++ b/src/emrun_prejs.js @@ -3,13 +3,13 @@ * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT * - * This file gets implicatly injected as a `--pre-js` file when + * This file gets implicitly injected as a `--pre-js` file when * emcc is run with `--emrun` */ // Route URL GET parameters to argc+argv -if (typeof window == 'object') { - Module['arguments'] = window.location.search.substr(1).trim().split('&'); +if (globalThis.window) { + Module['arguments'] = window.location.search.slice(1).trim().split('&'); for (let i = 0; i < Module['arguments'].length; ++i) { Module['arguments'][i] = decodeURI(Module['arguments'][i]); } diff --git a/src/emscripten-source-map.min.js b/src/emscripten-source-map.min.js deleted file mode 100644 index ba10d77f65903..0000000000000 --- a/src/emscripten-source-map.min.js +++ /dev/null @@ -1,32 +0,0 @@ -function define(e,t,n){if(typeof e!="string")throw new TypeError("Expected string, got: "+e);arguments.length==2&&(n=t);if(e in define.modules)throw new Error("Module already defined: "+e);define.modules[e]=n}function Domain(){this.modules={},this._currentModule=null}define.modules={},function(){function e(e){var t=e.split("/"),n=1;while(nt)-(e0&&t.column>=0&&!n&&!r&&!i)return;if(t&&"line"in t&&"column"in t&&n&&"line"in n&&"column"in n&&t.line>0&&t.column>=0&&n.line>0&&n.column>=0&&r)return;throw new Error("Invalid mapping.")},o.prototype._serializeMappings=function(){var t=0,n=1,i=0,s=0,o=0,u=0,a="",l;this._mappings.sort(f);for(var c=0,h=this._mappings.length;c0){if(!f(l,this._mappings[c-1]))continue;a+=","}a+=r.encode(l.generated.column-t),t=l.generated.column,l.source&&l.original&&(a+=r.encode(this._sources.indexOf(l.source)-u),u=this._sources.indexOf(l.source),a+=r.encode(l.original.line-1-s),s=l.original.line-1,a+=r.encode(l.original.column-i),i=l.original.column,l.name&&(a+=r.encode(this._names.indexOf(l.name)-o),o=this._names.indexOf(l.name)))}return a},o.prototype.toJSON=function(){var t={version:this._version,file:this._file,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};return this._sourceRoot&&(t.sourceRoot=this._sourceRoot),this._sourcesContents&&(t.sourcesContent=t.sources.map(function(e){return t.sourceRoot&&(e=i.relative(t.sourceRoot,e)),Object.prototype.hasOwnProperty.call(this._sourcesContents,i.toSetString(e))?this._sourcesContents[i.toSetString(e)]:null},this)),t},o.prototype.toString=function(){return JSON.stringify(this)},t.SourceMapGenerator=o}),define("source-map/base64-vlq",["require","exports","module","source-map/base64"],function(e,t,n){function a(e){return e<0?(-e<<1)+1:(e<<1)+0}function f(e){var t=(e&1)===1,n=e>>1;return t?-n:n}var r=e("./base64"),i=5,s=1<>>=i,f>0&&(s|=u),n+=r.encode(s);while(f>0);return n},t.decode=function(t){var n=0,s=t.length,a=0,l=0,c,h;do{if(n>=s)throw new Error("Expected more digits in base 64 VLQ value.");h=r.decode(t.charAt(n++)),c=!!(h&u),h&=o,a+=h<=0&&t0)if(c.charAt(0)===";")r++,c=c.slice(1),i=0;else if(c.charAt(0)===",")c=c.slice(1);else{h={},h.generatedLine=r,p=o.decode(c),h.generatedColumn=i+p.value,i=h.generatedColumn,c=p.rest;if(c.length>0&&!l.test(c.charAt(0))){p=o.decode(c),h.source=this._sources.at(a+p.value),a+=p.value,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source, but no line and column");p=o.decode(c),h.originalLine=s+p.value,s=h.originalLine,h.originalLine+=1,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source and line, but no column");p=o.decode(c),h.originalColumn=u+p.value,u=h.originalColumn,c=p.rest,c.length>0&&!l.test(c.charAt(0))&&(p=o.decode(c),h.name=this._names.at(f+p.value),f+=p.value,c=p.rest)}this._generatedMappings.push(h),typeof h.originalLine=="number"&&this._originalMappings.push(h)}this._originalMappings.sort(this._compareOriginalPositions)},u.prototype._compareOriginalPositions=function(t,n){if(t.source>n.source)return 1;if(t.source0?t-o>1?r(o,t,n,i,s):i[o]:o-e>1?r(e,o,n,i,s):e<0?null:i[e]}t.search=function(t,n,i){return n.length>0?r(-1,n.length,t,n,i):null}}),define("source-map/source-node",["require","exports","module","source-map/source-map-generator","source-map/util"],function(e,t,n){function s(e,t,n,r,i){this.children=[],this.sourceContents={},this.line=e===undefined?null:e,this.column=t===undefined?null:t,this.source=n===undefined?null:n,this.name=i===undefined?null:i,r!=null&&this.add(r)}var r=e("./source-map-generator").SourceMapGenerator,i=e("./util");s.fromStringWithSourceMap=function(t,n){function f(e,t){e===null||e.source===undefined?r.add(t):r.add(new s(e.originalLine,e.originalColumn,e.source,t,e.name))}var r=new s,i=t.split("\n"),o=1,u=0,a=null;return n.eachMapping(function(e){if(a===null){while(o=0;n--)this.prepend(t[n]);else{if(!(t instanceof s||typeof t=="string"))throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+t);this.children.unshift(t)}return this},s.prototype.walk=function(t){this.children.forEach(function(e){e instanceof s?e.walk(t):e!==""&&t(e,{source:this.source,line:this.line,column:this.column,name:this.name})},this)},s.prototype.join=function(t){var n,r,i=this.children.length;if(i>0){n=[];for(r=0;r { /** * @fileoverview gl-matrix - High performance matrix and vector operations for WebGL diff --git a/src/growableHeap.js b/src/growableHeap.js deleted file mode 100644 index 6169ab5909ef6..0000000000000 --- a/src/growableHeap.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * Copyright 2019 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// Support for growable heap + pthreads, where the buffer may change, so JS views -// must be updated. -function GROWABLE_HEAP_I8() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAP8; -} -function GROWABLE_HEAP_U8() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAPU8; -} -function GROWABLE_HEAP_I16() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAP16; -} -function GROWABLE_HEAP_U16() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAPU16; -} -function GROWABLE_HEAP_I32() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAP32; -} -function GROWABLE_HEAP_U32() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAPU32; -} -function GROWABLE_HEAP_F32() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAPF32; -} -function GROWABLE_HEAP_F64() { - if (wasmMemory.buffer != HEAP8.buffer) { - updateMemoryViews(); - } - return HEAPF64; -} diff --git a/src/headless.js b/src/headless.js deleted file mode 100644 index e1b0697684ab3..0000000000000 --- a/src/headless.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * @license - * Copyright 2012 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -var headlessPrint = (x) => { - //print(x); -}; - -var window = { - // adjustable parameters - location: { - toString() { - return 'http://emscripten.org'; - }, - search: '', - pathname: null, - }, - onIdle() { - headlessPrint('triggering click'); - document.querySelector('.fullscreen-button.low-res').callEventListeners('click'); - window.onIdle = null; - }, - dirsToDrop: 0, // go back to root dir if first_js is in a subdir - - headless: true, - - stopped: false, - fakeNow: 0, // we don't use Date.now() - rafs: [], - timeouts: [], - uid: 0, - requestAnimationFrame(func) { - func.uid = window.uid++; - headlessPrint(`adding raf ${func.uid}`); - window.rafs.push(func); - }, - setTimeout(func, ms) { - func.uid = window.uid++; - headlessPrint(`adding timeout ${func.uid}`); - window.timeouts.push({ - func, - when: window.fakeNow + (ms || 0) - }); - window.timeouts.sort((x, y) => { return y.when - x.when }); - }, - runEventLoop() { - // run forever until an exception stops this replay - var iter = 0; - while (!this.stopped) { - var start = Date.realNow(); - headlessPrint(`event loop: ${(iter++)}`); - if (window.rafs.length == 0 && window.timeouts.length == 0) { - if (window.onIdle) { - window.onIdle(); - } else { - throw 'main loop is idle!'; - } - } - // rafs - var currRafs = window.rafs; - window.rafs = []; - for (var i = 0; i < currRafs.length; i++) { - var raf = currRafs[i]; - headlessPrint(`calling raf: ${raf.uid}`);// + ': ' + raf.toString().substring(0, 50)); - raf(); - } - // timeouts - var now = window.fakeNow; - var timeouts = window.timeouts; - window.timeouts = []; - while (timeouts.length && timeouts[timeouts.length-1].when <= now) { - var timeout = timeouts.pop(); - headlessPrint(`calling timeout: ${timeout.func.uid}`);// + ': ' + timeout.func.toString().substring(0, 50)); - timeout.func(); - } - // increment 'time' - window.fakeNow += 16.666; - headlessPrint(`main event loop iteration took ${Date.realNow() - start} ms`); - } - }, - eventListeners: {}, - addEventListener(id, func) { - var listeners = this.eventListeners[id]; - listeners ||= this.eventListeners[id] = []; - listeners.push(func); - }, - removeEventListener(id, func) { - var listeners = this.eventListeners[id]; - if (!listeners) return; - for (var i = 0; i < listeners.length; i++) { - if (listeners[i] === func) { - listeners.splice(i, 1); - return; - } - } - }, - callEventListeners(id) { - var listeners = this.eventListeners[id]; - listeners?.forEach((listener) => listener()); - }, - URL: { - createObjectURL(x) { - return x; // the blob itself is returned - }, - revokeObjectURL(x) {}, - }, - encodeURIComponent(x) { return x }, -}; -var setTimeout = window.setTimeout; -var document = { - headless: true, - eventListeners: {}, - addEventListener: window.addEventListener, - removeEventListener: window.removeEventListener, - callEventListeners: window.callEventListeners, - getElementById(id) { - switch (id) { - case 'canvas': { - if (this.canvas) return this.canvas; - return this.canvas = headlessCanvas(); - } - case 'status-text': case 'progress': { - return {}; - } - default: throw 'getElementById: ' + id; - } - }, - createElement(what) { - switch (what) { - case 'canvas': return document.getElementById(what); - case 'script': { - var ret = {}; - window.setTimeout(() => { - headlessPrint(`loading script: ${ret.src}`); - load(ret.src); - headlessPrint(' script loaded.'); - if (ret.onload) { - window.setTimeout(() => { - ret.onload(); // yeah yeah this might vanish - }); - } - }); - return ret; - } - case 'div': { - return { - appendChild() {}, - requestFullscreen() { - return document.getElementById('canvas').requestFullscreen(); - }, - }; - } - default: throw `createElement ${what}${new Error().stack}`; - } - }, - elements: {}, - querySelector(id) { - document.elements[id] ||= { - classList: { - add() {}, - remove() {}, - }, - eventListeners: {}, - addEventListener: document.addEventListener, - removeEventListener: document.removeEventListener, - callEventListeners: document.callEventListeners, - }; - return document.elements[id]; - }, - styleSheets: [{ - cssRules: [], - insertRule() {}, - }], - body: { - appendChild() {}, - }, - exitPointerLock() {}, - exitFullscreen() {}, -}; -var alert = function(x) { - print(x); -}; -var performance = { - now() { - return Date.now(); - }, -}; -function fixPath(path) { - if (path[0] == '/') path = path.substring(1); - for (var i = 0; i < window.dirsToDrop; i++) { - path = '../' + path; - } - return path -} -var XMLHttpRequest = function() { - return { - open(mode, path, async) { - path = fixPath(path); - this.mode = mode; - this.path = path; - this.async = async; - }, - send() { - if (!this.async) { - this.doSend(); - } else { - window.setTimeout(() => { - this.doSend(); - this.onload?.(); - }, 0); - } - }, - doSend() { - if (this.responseType == 'arraybuffer') { - this.response = read(this.path, 'binary'); - } else { - this.responseText = read(this.path); - } - }, - }; -}; -var Audio = () => ({ - play() {}, - pause() {}, - cloneNode() { - return this; - }, -}); -var Image = () => { - window.setTimeout(function() { - this.complete = true; - this.width = 64; - this.height = 64; - this.onload?.(); - }); -}; -var Worker = (workerPath) => { - workerPath = fixPath(workerPath); - var workerCode = read(workerPath); - workerCode = workerCode.replace(/Module/g, 'zzModuleyy' + (Worker.id++)). // prevent collision with the global Module object. Note that this becomes global, so we need unique ids - replace(/\nonmessage = /, '\nvar onmessage = '); // workers commonly do "onmessage = ", we need to verify that to sandbox - headlessPrint(`loading worker ${workerPath} : ${workerCode.substring(0, 50)}`); - eval(workerCode); // will implement onmessage() - - function duplicateJSON(json) { - function handleTypedArrays(key, value) { - if (value?.toString && value.toString().substring(0, 8) == '[object ' && value.length && value.byteLength) { - return Array.prototype.slice.call(value); - } - return value; - } - return JSON.parse(JSON.stringify(json, handleTypedArrays)) - } - this.terminate = () => {}; - this.postMessage = (msg) => { - msg.messageId = Worker.messageId++; - headlessPrint(`main thread sending message ${msg.messageId} to worker ${workerPath}`); - window.setTimeout(() => { - headlessPrint(`worker ${workerPath} receiving message ${msg.messageId}`); - onmessage({ data: duplicateJSON(msg) }); - }); - }; - var thisWorker = this; - var postMessage = (msg) => { - msg.messageId = Worker.messageId++; - headlessPrint(`worker ${workerPath} sending message ${msg.messageId}`); - window.setTimeout(() => { - headlessPrint(`main thread receiving message ${msg.messageId} from ${workerPath}`); - thisWorker.onmessage({ data: duplicateJSON(msg) }); - }); - }; -}; -Worker.id = 0; -Worker.messageId = 0; -var screen = { // XXX these values may need to be adjusted - width: 2100, - height: 1313, - availWidth: 2100, - availHeight: 1283, -}; -if (typeof console == 'undefined') { - console = { - log(x) { print(x); }, - }; -} - -// additional setup -Module['canvas'] ||= document.getElementById('canvas'); diff --git a/src/headlessCanvas.js b/src/headlessCanvas.js deleted file mode 100644 index a14f34e6a4f71..0000000000000 --- a/src/headlessCanvas.js +++ /dev/null @@ -1,633 +0,0 @@ -/** - * @license - * Copyright 2013 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -function headlessCanvas() { - var ret = { - headless: true, - getContext: function(which) { - switch (which) { - case 'webgl': - case 'experimental-webgl': { - return { - /* ClearBufferMask */ - DEPTH_BUFFER_BIT : 0x00000100, - STENCIL_BUFFER_BIT : 0x00000400, - COLOR_BUFFER_BIT : 0x00004000, - - /* BeginMode */ - POINTS : 0x0000, - LINES : 0x0001, - LINE_LOOP : 0x0002, - LINE_STRIP : 0x0003, - TRIANGLES : 0x0004, - TRIANGLE_STRIP : 0x0005, - TRIANGLE_FAN : 0x0006, - - /* AlphaFunction (not supported in ES20) */ - /* NEVER */ - /* LESS */ - /* EQUAL */ - /* LEQUAL */ - /* GREATER */ - /* NOTEQUAL */ - /* GEQUAL */ - /* ALWAYS */ - - /* BlendingFactorDest */ - ZERO : 0, - ONE : 1, - SRC_COLOR : 0x0300, - ONE_MINUS_SRC_COLOR : 0x0301, - SRC_ALPHA : 0x0302, - ONE_MINUS_SRC_ALPHA : 0x0303, - DST_ALPHA : 0x0304, - ONE_MINUS_DST_ALPHA : 0x0305, - - /* BlendingFactorSrc */ - /* ZERO */ - /* ONE */ - DST_COLOR : 0x0306, - ONE_MINUS_DST_COLOR : 0x0307, - SRC_ALPHA_SATURATE : 0x0308, - /* SRC_ALPHA */ - /* ONE_MINUS_SRC_ALPHA */ - /* DST_ALPHA */ - /* ONE_MINUS_DST_ALPHA */ - - /* BlendEquationSeparate */ - FUNC_ADD : 0x8006, - BLEND_EQUATION : 0x8009, - BLEND_EQUATION_RGB : 0x8009, /* same as BLEND_EQUATION */ - BLEND_EQUATION_ALPHA : 0x883D, - - /* BlendSubtract */ - FUNC_SUBTRACT : 0x800A, - FUNC_REVERSE_SUBTRACT : 0x800B, - - /* Separate Blend Functions */ - BLEND_DST_RGB : 0x80C8, - BLEND_SRC_RGB : 0x80C9, - BLEND_DST_ALPHA : 0x80CA, - BLEND_SRC_ALPHA : 0x80CB, - CONSTANT_COLOR : 0x8001, - ONE_MINUS_CONSTANT_COLOR : 0x8002, - CONSTANT_ALPHA : 0x8003, - ONE_MINUS_CONSTANT_ALPHA : 0x8004, - BLEND_COLOR : 0x8005, - - /* Buffer Objects */ - ARRAY_BUFFER : 0x8892, - ELEMENT_ARRAY_BUFFER : 0x8893, - ARRAY_BUFFER_BINDING : 0x8894, - ELEMENT_ARRAY_BUFFER_BINDING : 0x8895, - - STREAM_DRAW : 0x88E0, - STATIC_DRAW : 0x88E4, - DYNAMIC_DRAW : 0x88E8, - - BUFFER_SIZE : 0x8764, - BUFFER_USAGE : 0x8765, - - CURRENT_VERTEX_ATTRIB : 0x8626, - - /* CullFaceMode */ - FRONT : 0x0404, - BACK : 0x0405, - FRONT_AND_BACK : 0x0408, - - /* DepthFunction */ - /* NEVER */ - /* LESS */ - /* EQUAL */ - /* LEQUAL */ - /* GREATER */ - /* NOTEQUAL */ - /* GEQUAL */ - /* ALWAYS */ - - /* EnableCap */ - /* TEXTURE_2D */ - CULL_FACE : 0x0B44, - BLEND : 0x0BE2, - DITHER : 0x0BD0, - STENCIL_TEST : 0x0B90, - DEPTH_TEST : 0x0B71, - SCISSOR_TEST : 0x0C11, - POLYGON_OFFSET_FILL : 0x8037, - SAMPLE_ALPHA_TO_COVERAGE : 0x809E, - SAMPLE_COVERAGE : 0x80A0, - - /* ErrorCode */ - NO_ERROR : 0, - INVALID_ENUM : 0x0500, - INVALID_VALUE : 0x0501, - INVALID_OPERATION : 0x0502, - OUT_OF_MEMORY : 0x0505, - - /* FrontFaceDirection */ - CW : 0x0900, - CCW : 0x0901, - - /* GetPName */ - LINE_WIDTH : 0x0B21, - ALIASED_POINT_SIZE_RANGE : 0x846D, - ALIASED_LINE_WIDTH_RANGE : 0x846E, - CULL_FACE_MODE : 0x0B45, - FRONT_FACE : 0x0B46, - DEPTH_RANGE : 0x0B70, - DEPTH_WRITEMASK : 0x0B72, - DEPTH_CLEAR_VALUE : 0x0B73, - DEPTH_FUNC : 0x0B74, - STENCIL_CLEAR_VALUE : 0x0B91, - STENCIL_FUNC : 0x0B92, - STENCIL_FAIL : 0x0B94, - STENCIL_PASS_DEPTH_FAIL : 0x0B95, - STENCIL_PASS_DEPTH_PASS : 0x0B96, - STENCIL_REF : 0x0B97, - STENCIL_VALUE_MASK : 0x0B93, - STENCIL_WRITEMASK : 0x0B98, - STENCIL_BACK_FUNC : 0x8800, - STENCIL_BACK_FAIL : 0x8801, - STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802, - STENCIL_BACK_PASS_DEPTH_PASS : 0x8803, - STENCIL_BACK_REF : 0x8CA3, - STENCIL_BACK_VALUE_MASK : 0x8CA4, - STENCIL_BACK_WRITEMASK : 0x8CA5, - VIEWPORT : 0x0BA2, - SCISSOR_BOX : 0x0C10, - /* SCISSOR_TEST */ - COLOR_CLEAR_VALUE : 0x0C22, - COLOR_WRITEMASK : 0x0C23, - UNPACK_ALIGNMENT : 0x0CF5, - PACK_ALIGNMENT : 0x0D05, - MAX_TEXTURE_SIZE : 0x0D33, - MAX_VIEWPORT_DIMS : 0x0D3A, - SUBPIXEL_BITS : 0x0D50, - RED_BITS : 0x0D52, - GREEN_BITS : 0x0D53, - BLUE_BITS : 0x0D54, - ALPHA_BITS : 0x0D55, - DEPTH_BITS : 0x0D56, - STENCIL_BITS : 0x0D57, - POLYGON_OFFSET_UNITS : 0x2A00, - /* POLYGON_OFFSET_FILL */ - POLYGON_OFFSET_FACTOR : 0x8038, - TEXTURE_BINDING_2D : 0x8069, - SAMPLE_BUFFERS : 0x80A8, - SAMPLES : 0x80A9, - SAMPLE_COVERAGE_VALUE : 0x80AA, - SAMPLE_COVERAGE_INVERT : 0x80AB, - - /* GetTextureParameter */ - /* TEXTURE_MAG_FILTER */ - /* TEXTURE_MIN_FILTER */ - /* TEXTURE_WRAP_S */ - /* TEXTURE_WRAP_T */ - - COMPRESSED_TEXTURE_FORMATS : 0x86A3, - - /* HintMode */ - DONT_CARE : 0x1100, - FASTEST : 0x1101, - NICEST : 0x1102, - - /* HintTarget */ - GENERATE_MIPMAP_HINT : 0x8192, - - /* DataType */ - BYTE : 0x1400, - UNSIGNED_BYTE : 0x1401, - SHORT : 0x1402, - UNSIGNED_SHORT : 0x1403, - INT : 0x1404, - UNSIGNED_INT : 0x1405, - FLOAT : 0x1406, - - /* PixelFormat */ - DEPTH_COMPONENT : 0x1902, - ALPHA : 0x1906, - RGB : 0x1907, - RGBA : 0x1908, - LUMINANCE : 0x1909, - LUMINANCE_ALPHA : 0x190A, - - /* PixelType */ - /* UNSIGNED_BYTE */ - UNSIGNED_SHORT_4_4_4_4 : 0x8033, - UNSIGNED_SHORT_5_5_5_1 : 0x8034, - UNSIGNED_SHORT_5_6_5 : 0x8363, - - /* Shaders */ - FRAGMENT_SHADER : 0x8B30, - VERTEX_SHADER : 0x8B31, - MAX_VERTEX_ATTRIBS : 0x8869, - MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB, - MAX_VARYING_VECTORS : 0x8DFC, - MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D, - MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C, - MAX_TEXTURE_IMAGE_UNITS : 0x8872, - MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD, - SHADER_TYPE : 0x8B4F, - DELETE_STATUS : 0x8B80, - LINK_STATUS : 0x8B82, - VALIDATE_STATUS : 0x8B83, - ATTACHED_SHADERS : 0x8B85, - ACTIVE_UNIFORMS : 0x8B86, - ACTIVE_ATTRIBUTES : 0x8B89, - SHADING_LANGUAGE_VERSION : 0x8B8C, - CURRENT_PROGRAM : 0x8B8D, - - /* StencilFunction */ - NEVER : 0x0200, - LESS : 0x0201, - EQUAL : 0x0202, - LEQUAL : 0x0203, - GREATER : 0x0204, - NOTEQUAL : 0x0205, - GEQUAL : 0x0206, - ALWAYS : 0x0207, - - /* StencilOp */ - /* ZERO */ - KEEP : 0x1E00, - REPLACE : 0x1E01, - INCR : 0x1E02, - DECR : 0x1E03, - INVERT : 0x150A, - INCR_WRAP : 0x8507, - DECR_WRAP : 0x8508, - - /* StringName */ - VENDOR : 0x1F00, - RENDERER : 0x1F01, - VERSION : 0x1F02, - - /* TextureMagFilter */ - NEAREST : 0x2600, - LINEAR : 0x2601, - - /* TextureMinFilter */ - /* NEAREST */ - /* LINEAR */ - NEAREST_MIPMAP_NEAREST : 0x2700, - LINEAR_MIPMAP_NEAREST : 0x2701, - NEAREST_MIPMAP_LINEAR : 0x2702, - LINEAR_MIPMAP_LINEAR : 0x2703, - - /* TextureParameterName */ - TEXTURE_MAG_FILTER : 0x2800, - TEXTURE_MIN_FILTER : 0x2801, - TEXTURE_WRAP_S : 0x2802, - TEXTURE_WRAP_T : 0x2803, - - /* TextureTarget */ - TEXTURE_2D : 0x0DE1, - TEXTURE : 0x1702, - - TEXTURE_CUBE_MAP : 0x8513, - TEXTURE_BINDING_CUBE_MAP : 0x8514, - TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515, - TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516, - TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517, - TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518, - TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519, - TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A, - MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C, - - /* TextureUnit */ - TEXTURE0 : 0x84C0, - TEXTURE1 : 0x84C1, - TEXTURE2 : 0x84C2, - TEXTURE3 : 0x84C3, - TEXTURE4 : 0x84C4, - TEXTURE5 : 0x84C5, - TEXTURE6 : 0x84C6, - TEXTURE7 : 0x84C7, - TEXTURE8 : 0x84C8, - TEXTURE9 : 0x84C9, - TEXTURE10 : 0x84CA, - TEXTURE11 : 0x84CB, - TEXTURE12 : 0x84CC, - TEXTURE13 : 0x84CD, - TEXTURE14 : 0x84CE, - TEXTURE15 : 0x84CF, - TEXTURE16 : 0x84D0, - TEXTURE17 : 0x84D1, - TEXTURE18 : 0x84D2, - TEXTURE19 : 0x84D3, - TEXTURE20 : 0x84D4, - TEXTURE21 : 0x84D5, - TEXTURE22 : 0x84D6, - TEXTURE23 : 0x84D7, - TEXTURE24 : 0x84D8, - TEXTURE25 : 0x84D9, - TEXTURE26 : 0x84DA, - TEXTURE27 : 0x84DB, - TEXTURE28 : 0x84DC, - TEXTURE29 : 0x84DD, - TEXTURE30 : 0x84DE, - TEXTURE31 : 0x84DF, - ACTIVE_TEXTURE : 0x84E0, - - /* TextureWrapMode */ - REPEAT : 0x2901, - CLAMP_TO_EDGE : 0x812F, - MIRRORED_REPEAT : 0x8370, - - /* Uniform Types */ - FLOAT_VEC2 : 0x8B50, - FLOAT_VEC3 : 0x8B51, - FLOAT_VEC4 : 0x8B52, - INT_VEC2 : 0x8B53, - INT_VEC3 : 0x8B54, - INT_VEC4 : 0x8B55, - BOOL : 0x8B56, - BOOL_VEC2 : 0x8B57, - BOOL_VEC3 : 0x8B58, - BOOL_VEC4 : 0x8B59, - FLOAT_MAT2 : 0x8B5A, - FLOAT_MAT3 : 0x8B5B, - FLOAT_MAT4 : 0x8B5C, - SAMPLER_2D : 0x8B5E, - SAMPLER_CUBE : 0x8B60, - - /* Vertex Arrays */ - VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622, - VERTEX_ATTRIB_ARRAY_SIZE : 0x8623, - VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624, - VERTEX_ATTRIB_ARRAY_TYPE : 0x8625, - VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A, - VERTEX_ATTRIB_ARRAY_POINTER : 0x8645, - VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F, - - /* Shader Source */ - COMPILE_STATUS : 0x8B81, - - /* Shader Precision-Specified Types */ - LOW_FLOAT : 0x8DF0, - MEDIUM_FLOAT : 0x8DF1, - HIGH_FLOAT : 0x8DF2, - LOW_INT : 0x8DF3, - MEDIUM_INT : 0x8DF4, - HIGH_INT : 0x8DF5, - - /* Framebuffer Object. */ - FRAMEBUFFER : 0x8D40, - RENDERBUFFER : 0x8D41, - - RGBA4 : 0x8056, - RGB5_A1 : 0x8057, - RGB565 : 0x8D62, - DEPTH_COMPONENT16 : 0x81A5, - STENCIL_INDEX : 0x1901, - STENCIL_INDEX8 : 0x8D48, - DEPTH_STENCIL : 0x84F9, - - RENDERBUFFER_WIDTH : 0x8D42, - RENDERBUFFER_HEIGHT : 0x8D43, - RENDERBUFFER_INTERNAL_FORMAT : 0x8D44, - RENDERBUFFER_RED_SIZE : 0x8D50, - RENDERBUFFER_GREEN_SIZE : 0x8D51, - RENDERBUFFER_BLUE_SIZE : 0x8D52, - RENDERBUFFER_ALPHA_SIZE : 0x8D53, - RENDERBUFFER_DEPTH_SIZE : 0x8D54, - RENDERBUFFER_STENCIL_SIZE : 0x8D55, - - FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0, - FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1, - FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2, - FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3, - - COLOR_ATTACHMENT0 : 0x8CE0, - DEPTH_ATTACHMENT : 0x8D00, - STENCIL_ATTACHMENT : 0x8D20, - DEPTH_STENCIL_ATTACHMENT : 0x821A, - - NONE : 0, - - FRAMEBUFFER_COMPLETE : 0x8CD5, - FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6, - FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7, - FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9, - FRAMEBUFFER_UNSUPPORTED : 0x8CDD, - - FRAMEBUFFER_BINDING : 0x8CA6, - RENDERBUFFER_BINDING : 0x8CA7, - MAX_RENDERBUFFER_SIZE : 0x84E8, - - INVALID_FRAMEBUFFER_OPERATION : 0x0506, - - /* WebGL-specific enums */ - UNPACK_FLIP_Y_WEBGL : 0x9240, - UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241, - CONTEXT_LOST_WEBGL : 0x9242, - UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243, - BROWSER_DEFAULT_WEBGL : 0x9244, - - items: {}, - id: 0, - getExtension: function() { return 1 }, - createBuffer: function() { - var id = this.id++; - this.items[id] = { - which: 'buffer', - }; - return id; - }, - deleteBuffer: function(){}, - bindBuffer: function(){}, - bufferData: function(){}, - getParameter: function(pname) { - switch (pname) { - case /* GL_VENDOR */ 0x1F00: return 'FakeShellGLVendor'; - case /* GL_RENDERER */ 0x1F01: return 'FakeShellGLRenderer'; - case /* GL_VERSION */ 0x1F02: return '0.0.1'; - case /* GL_MAX_TEXTURE_SIZE */ 0x0D33: return 16384; - case /* GL_MAX_CUBE_MAP_TEXTURE_SIZE */ 0x851C: return 16384; - case /* GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT */ 0x84FF: return 16; - case /* GL_MAX_TEXTURE_IMAGE_UNITS_NV */ 0x8872: return 16; - case /* GL_MAX_VERTEX_UNIFORM_VECTORS */ 0x8DFB: return 4096; - case /* GL_MAX_FRAGMENT_UNIFORM_VECTORS */ 0x8DFD: return 4096; - case /* GL_MAX_VARYING_VECTORS */ 0x8DFC: return 32; - case /* GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS */ 0x8B4D: return 32; - case /* GL_ARRAY_BUFFER_BINDING */ 0x8894: return 0; - default: console.log('getParameter ' + pname + '?'); return 0; - } - }, - getSupportedExtensions: function() { - return ["OES_texture_float", "OES_standard_derivatives", "EXT_texture_filter_anisotropic", "MOZ_EXT_texture_filter_anisotropic", "MOZ_WEBGL_lose_context", "MOZ_WEBGL_compressed_texture_s3tc", "MOZ_WEBGL_depth_texture"]; - }, - createShader: function(type) { - var id = this.id++; - this.items[id] = { - which: 'shader', - type, - }; - return id; - }, - getShaderParameter: function(shader, pname) { - switch (pname) { - case /* GL_SHADER_TYPE */ 0x8B4F: return this.items[shader].type; - case /* GL_COMPILE_STATUS */ 0x8B81: return true; - default: throw 'getShaderParameter ' + pname; - } - }, - shaderSource: function(){}, - compileShader: function(){}, - createProgram: function() { - var id = this.id++; - this.items[id] = { - which: 'program', - shaders: [], - }; - return id; - }, - attachShader: function(program, shader) { - this.items[program].shaders.push(shader); - }, - bindAttribLocation: function(){}, - linkProgram: function(){}, - getProgramParameter: function(program, pname) { - switch (pname) { - case /* LINK_STATUS */ 0x8B82: return true; - case /* ACTIVE_UNIFORMS */ 0x8B86: return 4; - default: throw 'getProgramParameter ' + pname; - } - }, - deleteShader: function(){}, - deleteProgram: function(){}, - viewport: function(){}, - clearColor: function(){}, - clearDepth: function(){}, - depthFunc: function(){}, - enable: function(){}, - disable: function(){}, - frontFace: function(){}, - cullFace: function(){}, - activeTexture: function(){}, - createTexture: function() { - var id = this.id++; - this.items[id] = { - which: 'texture', - }; - return id; - }, - deleteTexture: function(){}, - boundTextures: {}, - bindTexture: function(target, texture) { - this.boundTextures[target] = texture; - }, - texParameteri: function(){}, - pixelStorei: function(){}, - texImage2D: function(){}, - compressedTexImage2D: function(){}, - useProgram: function(){}, - getUniformLocation: function() { - return null; - }, - getActiveUniform: function(program, index) { - return { - size: 1, - type: /* INT_VEC3 */ 0x8B54, - name: 'activeUniform' + index, - }; - }, - clear: function(){}, - uniform4fv: function(){}, - uniform1i: function(){}, - getAttribLocation: function() { return 1 }, - vertexAttribPointer: function(){}, - enableVertexAttribArray: function(){}, - disableVertexAttribArray: function(){}, - drawElements: function(){}, - drawArrays: function(){}, - depthMask: function(){}, - depthRange: function(){}, - bufferSubData: function(){}, - blendFunc: function(){}, - createFramebuffer: function() { - var id = this.id++; - this.items[id] = { - which: 'framebuffer', - shaders: [], - }; - return id; - }, - bindFramebuffer: function(){}, - framebufferTexture2D: function(){}, - checkFramebufferStatus: function() { - return /* FRAMEBUFFER_COMPLETE */ 0x8CD5; - }, - createRenderbuffer: function() { - var id = this.id++; - this.items[id] = { - which: 'renderbuffer', - shaders: [], - }; - return id; - }, - bindRenderbuffer: function(){}, - renderbufferStorage: function(){}, - framebufferRenderbuffer: function(){}, - scissor: function(){}, - colorMask: function(){}, - lineWidth: function(){}, - vertexAttrib4fv: function(){}, - }; - } - case '2d': { - return { - drawImage: function(){}, - getImageData: function(x, y, w, h) { - return { - width: w, - height: h, - data: new Uint8ClampedArray(w*h), - }; - }, - save: function(){}, - restore: function(){}, - fillRect: function(){}, - measureText: function() { return 10 }, - fillText: function(){}, - }; - } - default: throw 'canvas.getContext: ' + which; - } - }, - requestPointerLock: function() { - document.pointerLockElement = document.getElementById('canvas'); - window.setTimeout(function() { - document.callEventListeners('pointerlockchange'); - }); - }, - exitPointerLock: function(){}, - style: { - setProperty: function() {}, - removeProperty: function() {}, - }, - eventListeners: {}, - addEventListener: function(){}, - removeEventListener: function(){}, - requestFullscreen: function() { - document.fullscreenElement = document.getElementById('canvas'); - window.setTimeout(function() { - document.callEventListeners('fullscreenchange'); - }); - }, - offsetTop: 0, - offsetLeft: 0, - // generics - classList: { - add: function(){}, - remove: function(){}, - }, - insertBefore: function(){}, - }; - ret.parentNode = ret; - return ret; -} - diff --git a/src/jsifier.js b/src/jsifier.js deleted file mode 100644 index cad5b7d87204d..0000000000000 --- a/src/jsifier.js +++ /dev/null @@ -1,703 +0,0 @@ -/** - * @license - * Copyright 2010 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// "use strict"; - -// Convert analyzed data to javascript. Everything has already been calculated -// before this stage, which just does the final conversion to JavaScript. - -globalThis.addedLibraryItems = {}; - -globalThis.extraLibraryFuncs = []; - -// Some JS-implemented library functions are proxied to be called on the main -// browser thread, if the Emscripten runtime is executing in a Web Worker. -// Each such proxied function is identified via an ordinal number (this is not -// the same namespace as function pointers in general). -globalThis.proxiedFunctionTable = []; - -// Mangles the given C/JS side function name to assembly level function name (adds an underscore) -function mangleCSymbolName(f) { - if (f === '__main_argc_argv') { - f = 'main'; - } - return f[0] == '$' ? f.substr(1) : '_' + f; -} - -// Splits out items that pass filter. Returns also the original sans the filtered -function splitter(array, filter) { - const splitOut = array.filter(filter); - const leftIn = array.filter((x) => !filter(x)); - return { leftIn, splitOut }; -} - -function escapeJSONKey(x) { - if (/^[\d\w_]+$/.exec(x) || x[0] === '"' || x[0] === "'") return x; - assert(!x.includes("'"), 'cannot have internal single quotes in keys: ' + x); - return "'" + x + "'"; -} - -// JSON.stringify will completely omit function objects. This function is -// similar but preserves functions. -function stringifyWithFunctions(obj) { - if (typeof obj == 'function') return obj.toString(); - if (obj === null || typeof obj != 'object') return JSON.stringify(obj); - if (Array.isArray(obj)) { - return '[' + obj.map(stringifyWithFunctions).join(',') + ']'; - } - var rtn = '{\n'; - for (const [key, value] of Object.entries(obj)) { - var str = stringifyWithFunctions(value); - // Handle JS method syntax where the function property starts with its own - // name. e.g. foo(a) {}, - if (typeof value === 'function' && str.startsWith(key)) { - rtn += str + ',\n' - } else { - rtn += escapeJSONKey(key) + ':' + str + ',\n'; - } - } - return rtn + '}'; -} - -function isDefined(symName) { - if (WASM_EXPORTS.has(symName) || SIDE_MODULE_EXPORTS.has(symName)) { - return true; - } - if (symName == '__main_argc_argv' && SIDE_MODULE_EXPORTS.has('main')) { - return true; - } - // 'invoke_' symbols are created at runtime in library_dylink.py so can - // always be considered as defined. - if (RELOCATABLE && symName.startsWith('invoke_')) { - return true; - } - return false; -} - -function getTransitiveDeps(symbol) { - // TODO(sbc): Use some kind of cache to avoid quadratic behaviour here. - const transitiveDeps = new Set(); - const seen = new Set(); - const toVisit = [symbol]; - while (toVisit.length) { - const sym = toVisit.pop(); - if (!seen.has(sym)) { - let directDeps = LibraryManager.library[sym + '__deps'] || []; - directDeps = directDeps.filter((d) => typeof d === 'string'); - if (directDeps.length) { - directDeps.forEach(transitiveDeps.add, transitiveDeps); - toVisit.push(...directDeps); - } - seen.add(sym); - } - } - return Array.from(transitiveDeps); -} - -function shouldPreprocess(fileName) { - var content = read(fileName).trim() - return content.startsWith('#preprocess\n') || content.startsWith('#preprocess\r\n'); -} - -function getIncludeFile(fileName, needsPreprocess) { - let result = `// include: ${fileName}\n`; - if (needsPreprocess) { - result += processMacros(preprocess(fileName), fileName); - } else { - result += read(fileName); - } - result += `// end include: ${fileName}\n`; - return result; -} - -function preJS() { - let result = ''; - for (const fileName of PRE_JS_FILES) { - result += getIncludeFile(fileName, shouldPreprocess(fileName)); - } - return result; -} - -function runJSify() { - const libraryItems = []; - const symbolDeps = {}; - const asyncFuncs = []; - let postSets = []; - - LibraryManager.load(); - - const symbolsNeeded = DEFAULT_LIBRARY_FUNCS_TO_INCLUDE; - symbolsNeeded.push(...extraLibraryFuncs); - for (const sym of EXPORTED_RUNTIME_METHODS) { - if ('$' + sym in LibraryManager.library) { - symbolsNeeded.push('$' + sym); - } - } - if (INCLUDE_FULL_LIBRARY) { - for (const key of Object.keys(LibraryManager.library)) { - if (!isDecorator(key)) { - symbolsNeeded.push(key); - } - } - } - - function handleI64Signatures(symbol, snippet, sig, i53abi) { - // Handle i64 parameters and return values. - // - // When WASM_BIGINT is enabled these arrive as BigInt values which we - // convert to int53 JS numbers. If necessary, we also convert the return - // value back into a BigInt. - // - // When WASM_BIGINT is not enabled we receive i64 values as a pair of i32 - // numbers which is converted to single int53 number. In necessary, we also - // split the return value into a pair of i32 numbers. - return modifyJSFunction(snippet, (args, body, async_, oneliner) => { - let argLines = args.split('\n'); - argLines = argLines.map((line) => line.split('//')[0]); - const argNames = argLines.join(' ').split(',').map((name) => name.trim()); - const newArgs = []; - let innerArgs = []; - let argConversions = ''; - if (sig.length > argNames.length + 1) { - error(`handleI64Signatures: signature too long for ${symbol}`); - return snippet; - } - for (let i = 0; i < argNames.length; i++) { - const name = argNames[i]; - // If sig is shorter than argNames list then argType will be undefined - // here, which will result in the default case below. - const argType = sig[i + 1]; - if (WASM_BIGINT && ((MEMORY64 && argType == 'p') || (i53abi && argType == 'j'))) { - argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; - } else { - if (argType == 'j' && i53abi) { - argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; - newArgs.push(defineI64Param(name)); - } else if (argType == 'p' && CAN_ADDRESS_2GB) { - argConversions += ` ${name} >>>= 0;\n`; - newArgs.push(name); - } else { - newArgs.push(name); - } - } - } - - var origArgs = args; - if (!WASM_BIGINT) { - args = newArgs.join(','); - } - - if ((sig[0] == 'j' && i53abi) || (sig[0] == 'p' && MEMORY64)) { - // For functions that where we need to mutate the return value, we - // also need to wrap the body in an inner function. - if (oneliner) { - if (argConversions) { - return `${async_}(${args}) => { -${argConversions} - return ${makeReturn64(body)}; -}` - } - return `${async_}(${args}) => ${makeReturn64(body)};` - } - return `\ -${async_}function(${args}) { -${argConversions} - var ret = (() => { ${body} })(); - return ${makeReturn64('ret')}; -}`; - } - - // Otherwise no inner function is needed and we covert the arguments - // before executing the function body. - if (oneliner) { - body = `return ${body}`; - } - return `\ -${async_}function(${args}) { -${argConversions} - ${body}; -}`; - }); - } - - function processLibraryFunction(snippet, symbol, mangled, deps, isStub) { - // It is possible that when printing the function as a string on Windows, - // the js interpreter we are in returns the string with Windows line endings - // \r\n. This is undesirable, since line endings are managed in the form \n - // in the output for binary file writes, so make sure the endings are - // uniform. - snippet = snippet.toString().replace(/\r\n/gm, '\n'); - - // Is this a shorthand `foo() {}` method syntax? - // If so, prepend a function keyword so that it's valid syntax when extracted. - if (snippet.startsWith(symbol)) { - snippet = 'function ' + snippet; - } - - if (isStub) { - return snippet; - } - - // apply LIBRARY_DEBUG if relevant - if (LIBRARY_DEBUG && !isJsOnlySymbol(symbol)) { - snippet = modifyJSFunction(snippet, (args, body, async) => `\ -function(${args}) { - var ret = (() => { if (runtimeDebug) err("[library call:${mangled}: " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]"); - ${body} - })(); - if (runtimeDebug && typeof ret != "undefined") err(" [ return:" + prettyPrint(ret)); - return ret; -}`); - } - - const sig = LibraryManager.library[symbol + '__sig']; - const i53abi = LibraryManager.library[symbol + '__i53abi']; - if (sig && - ((i53abi && sig.includes('j')) || ((MEMORY64 || CAN_ADDRESS_2GB) && sig.includes('p')))) { - snippet = handleI64Signatures(symbol, snippet, sig, i53abi); - i53ConversionDeps.forEach((d) => deps.push(d)) - } - - const proxyingMode = LibraryManager.library[symbol + '__proxy']; - if (proxyingMode) { - if (proxyingMode !== 'sync' && proxyingMode !== 'async' && proxyingMode !== 'none') { - throw new Error(`Invalid proxyingMode ${symbol}__proxy: '${proxyingMode}' specified!`); - } - if (SHARED_MEMORY) { - const sync = proxyingMode === 'sync'; - if (PTHREADS) { - snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => { - if (oneliner) { - body = `return ${body}`; - } - const rtnType = sig && sig.length ? sig[0] : null; - const proxyFunc = (MEMORY64 && rtnType == 'p') ? 'proxyToMainThreadPtr' : 'proxyToMainThread'; - deps.push('$' + proxyFunc); - return ` -function(${args}) { -if (ENVIRONMENT_IS_PTHREAD) - return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args}); -${body} -}\n` - }); - } else if (WASM_WORKERS && ASSERTIONS) { - // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers - // (since there is no automatic proxying architecture available) - snippet = modifyJSFunction(snippet, (args, body) => ` -function(${args}) { - assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${mangled}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!"); - ${body} -}\n`); - } - proxiedFunctionTable.push(mangled); - } - } - - return snippet; - } - - function addImplicitDeps(snippet, deps) { - // There are some common dependencies that we inject automatically by - // conservatively scanning the input functions for their usage. - // Specifically, these are dependencies that are very common and would be - // burdensome to add manually to all functions. - // The first four are deps that are automatically/conditionally added - // by the {{{ makeDynCall }}}, and {{{ runtimeKeepalivePush/Pop }}} macros. - const autoDeps = [ - 'getDynCaller', - 'getWasmTableEntry', - 'runtimeKeepalivePush', - 'runtimeKeepalivePop', - 'UTF8ToString', - ]; - for (const dep of autoDeps) { - if (snippet.includes(dep + '(')) { - deps.push('$' + dep); - } - } - } - - function symbolHandler(symbol) { - // In LLVM, exceptions generate a set of functions of form - // __cxa_find_matching_catch_1(), __cxa_find_matching_catch_2(), etc. where - // the number specifies the number of arguments. In Emscripten, route all - // these to a single function 'findMatchingCatch' that takes an array - // of argument. - if (LINK_AS_CXX && !WASM_EXCEPTIONS && symbol.startsWith('__cxa_find_matching_catch_')) { - if (DISABLE_EXCEPTION_THROWING) { - error('DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but exception catching code appears. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-except (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.'); - return; - } - if (!(symbol in LibraryManager.library)) { - // Create a new __cxa_find_matching_catch variant on demand. - const num = +symbol.split('_').slice(-1)[0]; - addCxaCatch(num); - } - // Continue, with the code below emitting the proper JavaScript based on - // what we just added to the library. - } - - function addFromLibrary(symbol, dependent, force = false) { - // don't process any special identifiers. These are looked up when - // processing the base name of the identifier. - if (isDecorator(symbol)) { - return; - } - - // if the function was implemented in compiled code, there is no need to - // include the js version - if (WASM_EXPORTS.has(symbol) && !force) { - return; - } - - if (symbol in addedLibraryItems) { - return; - } - addedLibraryItems[symbol] = true; - - if (!(symbol + '__deps' in LibraryManager.library)) { - LibraryManager.library[symbol + '__deps'] = []; - } - - deps = LibraryManager.library[symbol + '__deps']; - let sig = LibraryManager.library[symbol + '__sig']; - if (!WASM_BIGINT && sig && sig[0] == 'j') { - // Without WASM_BIGINT functions that return i64 depend on setTempRet0 - // to return the upper 32-bits of the result. - // See makeReturn64 in parseTools.py. - deps.push('setTempRet0'); - } - - let isAsyncFunction = false; - if (ASYNCIFY) { - const original = LibraryManager.library[symbol]; - if (typeof original == 'function' ) { - isAsyncFunction = LibraryManager.library[symbol + '__async'] || - original.constructor.name == 'AsyncFunction' - } - if (isAsyncFunction) { - asyncFuncs.push(symbol); - } - } - - if (symbolsOnly) { - if (LibraryManager.library.hasOwnProperty(symbol)) { - var value = LibraryManager.library[symbol]; - var resolvedSymbol = symbol; - // Resolve aliases before looking up deps - if (typeof value == 'string' && value[0] != '=' && LibraryManager.library.hasOwnProperty(value)) { - resolvedSymbol = value; - } - var transtiveDeps = getTransitiveDeps(resolvedSymbol); - symbolDeps[symbol] = transtiveDeps.filter((d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library)); - } - return; - } - - // This gets set to true in the case of dynamic linking for symbols that - // are undefined in the main module. In this case we create a stub that - // will resolve the correct symbol at runtime, or assert if its missing. - let isStub = false; - - const mangled = mangleCSymbolName(symbol); - - if (!LibraryManager.library.hasOwnProperty(symbol)) { - const isWeakImport = WEAK_IMPORTS.has(symbol); - if (!isDefined(symbol) && !isWeakImport) { - if (PROXY_TO_PTHREAD && !MAIN_MODULE && symbol == '__main_argc_argv') { - error('PROXY_TO_PTHREAD proxies main() for you, but no main exists'); - return; - } - let undefinedSym = symbol; - if (symbol === '__main_argc_argv') { - undefinedSym = 'main/__main_argc_argv'; - } - let msg = 'undefined symbol: ' + undefinedSym; - if (dependent) msg += ` (referenced by ${dependent})`; - if (ERROR_ON_UNDEFINED_SYMBOLS) { - error(msg); - warnOnce('To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`'); - warnOnce(mangled + ' may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library'); - } else if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) { - warn(msg); - } - if (symbol === '__main_argc_argv' && STANDALONE_WASM) { - warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry'); - } - } - if (!RELOCATABLE) { - // emit a stub that will fail at runtime - LibraryManager.library[symbol] = new Function(`abort('missing function: ${symbol}');`); - // We have already warned/errored about this function, so for the purposes of Closure use, mute all type checks - // regarding this function, marking ot a variadic function that can take in anything and return anything. - // (not useful to warn/error multiple times) - LibraryManager.library[symbol + '__docs'] = '/** @type {function(...*):?} */'; - isStub = true; - } else { - // Create a stub for this symbol which can later be replaced by the - // dynamic linker. If this stub is called before the symbol is - // resolved assert in debug builds or trap in release builds. - if (ASYNCIFY) { - // See the definition of asyncifyStubs in preamble.js for why this - // is needed. - target = `asyncifyStubs['${symbol}']`; - } else { - target = `wasmImports['${symbol}']`; - } - let assertion = ''; - if (ASSERTIONS) { - assertion += `if (!${target} || ${target}.stub) abort("external symbol '${symbol}' is missing. perhaps a side module was not linked in? if this function was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment");\n`; - } - const functionBody = assertion + `return ${target}(...args);`; - LibraryManager.library[symbol] = new Function('...args', functionBody); - isStub = true; - } - } - - librarySymbols.push(mangled); - - const original = LibraryManager.library[symbol]; - let snippet = original; - - // Check for dependencies on `__internal` symbols from user libraries. - const isUserSymbol = LibraryManager.library[symbol + '__user']; - deps.forEach((dep) => { - if (isUserSymbol && LibraryManager.library[dep + '__internal']) { - warn(`user library symbol '${symbol}' depends on internal symbol '${dep}'`); - } - }); - - let isFunction = false; - let aliasTarget; - - if (typeof snippet == 'string') { - if (snippet[0] != '=') { - if (LibraryManager.library[snippet]) { - // Redirection for aliases. We include the parent, and at runtime - // make ourselves equal to it. This avoid having duplicate - // functions with identical content. - aliasTarget = snippet; - snippet = mangleCSymbolName(aliasTarget); - deps.push(aliasTarget); - } - } - } else if (typeof snippet == 'object') { - snippet = stringifyWithFunctions(snippet); - addImplicitDeps(snippet, deps); - } else if (typeof snippet == 'function') { - isFunction = true; - snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub); - addImplicitDeps(snippet, deps); - } - - const postsetId = symbol + '__postset'; - let postset = LibraryManager.library[postsetId]; - if (postset) { - // A postset is either code to run right now, or some text we should emit. - // If it's code, it may return some text to emit as well. - if (typeof postset == 'function') { - postset = postset(); - } - if (postset && !addedLibraryItems[postsetId]) { - addedLibraryItems[postsetId] = true; - postSets.push(postset + ';'); - } - } - - if (VERBOSE) { - printErr(`adding ${symbol} (referenced by ${dependent})`) - } - const deps_list = deps.join("','"); - const identDependents = symbol + `__deps: ['${deps_list}']`; - function addDependency(dep) { - // dependencies can be JS functions, which we just run - if (typeof dep == 'function') { - return dep(); - } - // $noExitRuntime is special since there are conditional usages of it - // in library.js and library_pthread.js. These happen before deps are - // processed so depending on it via `__deps` doesn't work. - if (dep === '$noExitRuntime') { - error('noExitRuntime cannot be referenced via __deps mechanism. Use DEFAULT_LIBRARY_FUNCS_TO_INCLUDE or EXPORTED_RUNTIME_METHODS') - } - return addFromLibrary(dep, `${symbol}, referenced by ${dependent}`, dep === aliasTarget); - } - let contentText; - if (isFunction) { - // Emit the body of a JS library function. - if ((USE_ASAN || USE_LSAN || UBSAN_RUNTIME) && LibraryManager.library[symbol + '__noleakcheck']) { - // CodeQL [js/bad-code-sanitization]: this is safe for dotnet since we don't expose ASAN/LSAN/UBSAN options and those shouldn't be used in production - contentText = modifyJSFunction(snippet, (args, body) => `(${args}) => withBuiltinMalloc(() => {${body}})`); - deps.push('$withBuiltinMalloc'); - } else { - contentText = snippet; // Regular JS function that will be executed in the context of the calling thread. - } - // Give the function the correct (mangled) name. Overwrite it if it's - // already named. This must happen after the last call to - // modifyJSFunction which could have changed or removed the name. - if (contentText.match(/^\s*([^}]*)\s*=>/s)) { - // Handle arrow functions - contentText = `var ${mangled} = ` + contentText + ';'; - } else if (contentText.startsWith('class ')) { - contentText = contentText.replace(/^class /, `class ${mangled} `); - } else { - // Handle regular (non-arrow) functions - contentText = contentText.replace(/function(?:\s+([^(]+))?\s*\(/, `function ${mangled}(`); - } - } else if (typeof snippet == 'string' && snippet.startsWith(';')) { - // In JS libraries - // foo: ';[code here verbatim]' - // emits - // 'var foo;[code here verbatim];' - contentText = 'var ' + mangled + snippet; - if (snippet[snippet.length - 1] != ';' && snippet[snippet.length - 1] != '}') contentText += ';'; - } else if (typeof snippet == 'undefined') { - contentText = `var ${mangled};`; - } else { - // In JS libraries - // foo: '=[value]' - // emits - // 'var foo = [value];' - if (typeof snippet == 'string' && snippet[0] == '=') { - snippet = snippet.substr(1); - } - contentText = `var ${mangled} = ${snippet};`; - } - // asm module exports are done in emscripten.py, after the asm module is ready. Here - // we also export library methods as necessary. - if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(mangled)) && !isStub) { - contentText += `\nModule['${mangled}'] = ${mangled};`; - } - // Relocatable code needs signatures to create proper wrappers. Stack - // switching needs signatures so we can create a proper - // WebAssembly.Function with the signature for the Promise API. - // TODO: For asyncify we could only add the signatures we actually need, - // of async imports/exports. - if (sig && (RELOCATABLE || ASYNCIFY == 2)) { - if (!WASM_BIGINT) { - sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii'); - } - contentText += `\n${mangled}.sig = '${sig}';`; - } - if (ASYNCIFY && isAsyncFunction) { - contentText += `\n${mangled}.isAsync = true;`; - } - if (isStub) { - contentText += `\n${mangled}.stub = true;`; - if (ASYNCIFY) { - contentText += `\nasyncifyStubs['${symbol}'] = undefined;`; - } - } - - let commentText = ''; - if (force) { - commentText += '/** @suppress {duplicate } */\n' - } - if (LibraryManager.library[symbol + '__docs']) { - commentText += LibraryManager.library[symbol + '__docs'] + '\n'; - } - - const depsText = (deps ? deps.map(addDependency).filter((x) => x != '').join('\n') + '\n' : ''); - return depsText + commentText + contentText; - } - - const JS = addFromLibrary(symbol, 'root reference (e.g. compiled C/C++ code)'); - libraryItems.push(JS); - } - - function includeFile(fileName, needsPreprocess = true) { - print(getIncludeFile(fileName, needsPreprocess)) - } - - function finalCombiner() { - const splitPostSets = splitter(postSets, (x) => x.symbol && x.dependencies); - postSets = splitPostSets.leftIn; - const orderedPostSets = splitPostSets.splitOut; - - let limit = orderedPostSets.length * orderedPostSets.length; - for (let i = 0; i < orderedPostSets.length; i++) { - for (let j = i + 1; j < orderedPostSets.length; j++) { - if (orderedPostSets[j].symbol in orderedPostSets[i].dependencies) { - const temp = orderedPostSets[i]; - orderedPostSets[i] = orderedPostSets[j]; - orderedPostSets[j] = temp; - i--; - limit--; - assert(limit > 0, 'Could not sort postsets!'); - break; - } - } - } - - postSets = postSets.concat(orderedPostSets); - - const shellFile = MINIMAL_RUNTIME ? 'shell_minimal.js' : 'shell.js'; - includeFile(shellFile); - - const preFile = MINIMAL_RUNTIME ? 'preamble_minimal.js' : 'preamble.js'; - includeFile(preFile); - - for (const item of libraryItems.concat(postSets)) { - print(indentify(item || '', 2)); - } - - if (PTHREADS) { - print(` -// proxiedFunctionTable specifies the list of functions that can be called -// either synchronously or asynchronously from other threads in postMessage()d -// or internally queued events. This way a pthread in a Worker can synchronously -// access e.g. the DOM on the main thread. -var proxiedFunctionTable = [ - ${proxiedFunctionTable.join(',\n ')} -]; -`); - } - - if (abortExecution) { - throw Error('Aborting compilation due to previous errors'); - } - - // This is the main 'post' pass. Print out the generated code - // that we have here, together with the rest of the output - // that we started to print out earlier (see comment on the - // "Final shape that will be created"). - print('// EMSCRIPTEN_END_FUNCS\n'); - - const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js'; - includeFile(postFile); - - for (const fileName of POST_JS_FILES) { - includeFile(fileName, shouldPreprocess(fileName)); - } - - print('//FORWARDED_DATA:' + JSON.stringify({ - librarySymbols, - warnings, - asyncFuncs, - ATINITS: ATINITS.join('\n'), - ATMAINS: STRICT ? '' : ATMAINS.join('\n'), - ATEXITS: ATEXITS.join('\n'), - })); - } - - for (const sym of symbolsNeeded) { - symbolHandler(sym); - } - - if (symbolsOnly) { - print(JSON.stringify({ - deps: symbolDeps, - asyncFuncs, - extraLibraryFuncs, - })); - } else { - finalCombiner(); - } - - if (abortExecution) { - throw Error('Aborting compilation due to previous errors'); - } -} diff --git a/src/jsifier.mjs b/src/jsifier.mjs new file mode 100644 index 0000000000000..036eccfec5e38 --- /dev/null +++ b/src/jsifier.mjs @@ -0,0 +1,980 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// Convert analyzed data to javascript. Everything has already been calculated +// before this stage, which just does the final conversion to JavaScript. + +import assert from 'node:assert'; +import * as fs from 'node:fs/promises'; +import { + ATMODULES, + ATEXITS, + ATINITS, + ATPOSTCTORS, + ATPRERUNS, + ATMAINS, + ATPOSTRUNS, + defineI64Param, + indentify, + makeReturn64, + modifyJSFunction, + preprocess, + processMacros, + receiveI64ParamAsI53, +} from './parseTools.mjs'; +import { + addToCompileTimeContext, + debugLog, + error, + errorOccured, + isDecorator, + isJsOnlySymbol, + compileTimeContext, + readFile, + runInMacroContext, + warn, + warnOnce, + warningOccured, + localFile, + timer, +} from './utility.mjs'; +import {LibraryManager, librarySymbols, nativeAliases} from './modules.mjs'; + +const addedLibraryItems = {}; + +const extraLibraryFuncs = []; + +// Experimental feature to check for invalid __deps entries. +// See `EMCC_CHECK_DEPS` in in the environment to try it out. +const CHECK_DEPS = process.env.EMCC_CHECK_DEPS; + +// Some JS-implemented library functions are proxied to be called on the main +// browser thread, if the Emscripten runtime is executing in a Web Worker. +// Each such proxied function is identified via an ordinal number (this is not +// the same namespace as function pointers in general). +const proxiedFunctionTable = []; + +// Mangles the given C/JS side function name to assembly level function name (adds an underscore) +function mangleCSymbolName(f) { + if (f === '__main_argc_argv') { + f = 'main'; + } + return f[0] == '$' ? f.slice(1) : '_' + f; +} + +// Splits out items that pass filter. Returns also the original sans the filtered +function splitter(array, filter) { + const splitOut = array.filter(filter); + const leftIn = array.filter((x) => !filter(x)); + return {leftIn, splitOut}; +} + +function escapeJSONKey(x) { + if (/^[\d\w_]+$/.exec(x) || x[0] === '"' || x[0] === "'") return x; + assert(!x.includes("'"), 'cannot have internal single quotes in keys: ' + x); + return "'" + x + "'"; +} + +// JSON.stringify will completely omit function objects. This function is +// similar but preserves functions. +function stringifyWithFunctions(obj) { + if (typeof obj == 'function') return obj.toString(); + if (obj === null || typeof obj != 'object') return JSON.stringify(obj); + if (Array.isArray(obj)) { + return '[' + obj.map(stringifyWithFunctions).join(',') + ']'; + } + + // preserve the type of the object if it is one of [Map, Set, WeakMap, WeakSet]. + const builtinContainers = runInMacroContext('[Map, Set, WeakMap, WeakSet]', { + filename: '', + }); + for (const container of builtinContainers) { + if (obj instanceof container) { + const className = container.name; + assert(!obj.size, `cannot stringify ${className} with data`); + return `new ${className}`; + } + } + + var rtn = '{\n'; + for (const [key, value] of Object.entries(obj)) { + var str = stringifyWithFunctions(value); + // Handle JS method syntax where the function property starts with its own + // name. e.g. `foo(a) {}` (or `async foo(a) {}`) + if (typeof value === 'function' && (str.startsWith(key) || str.startsWith('async ' + key))) { + rtn += str + ',\n'; + } else { + rtn += `${escapeJSONKey(key)}:${str},\n`; + } + } + return rtn + '}'; +} + +function isDefined(symName) { + if (WASM_EXPORTS.has(symName) || SIDE_MODULE_EXPORTS.has(symName)) { + return true; + } + if (symName == '__main_argc_argv' && SIDE_MODULE_EXPORTS.has('main')) { + return true; + } + // 'invoke_' symbols are created at runtime in library_dylink.py so can + // always be considered as defined. + if (MAIN_MODULE && symName.startsWith('invoke_')) { + return true; + } + return false; +} + +function getTransitiveDeps(symbol) { + // TODO(sbc): Use some kind of cache to avoid quadratic behaviour here. + const transitiveDeps = new Set(); + const seen = new Set(); + const toVisit = [symbol]; + while (toVisit.length) { + const sym = toVisit.pop(); + if (!seen.has(sym)) { + let directDeps = LibraryManager.library[sym + '__deps'] || []; + directDeps = directDeps.filter((d) => typeof d === 'string'); + for (const dep of directDeps) { + if (!transitiveDeps.has(dep)) { + debugLog(`adding dependency ${symbol} -> ${dep}`); + } + transitiveDeps.add(dep); + toVisit.push(dep); + } + seen.add(sym); + } + } + return Array.from(transitiveDeps); +} + +function shouldPreprocess(fileName) { + var content = readFile(fileName).trim(); + return content.startsWith('#preprocess\n') || content.startsWith('#preprocess\r\n'); +} + +function getIncludeFile(fileName, alwaysPreprocess, shortName) { + shortName ??= fileName; + let result = `// include: ${shortName}\n`; + const doPreprocess = alwaysPreprocess || shouldPreprocess(fileName); + if (doPreprocess) { + result += processMacros(preprocess(fileName), fileName); + } else { + result += readFile(fileName); + } + result += `// end include: ${shortName}\n`; + return result; +} + +function getSystemIncludeFile(fileName) { + return getIncludeFile(localFile(fileName), /*alwaysPreprocess=*/ true, /*shortName=*/ fileName); +} + +function preJS() { + let result = ''; + for (const fileName of PRE_JS_FILES) { + result += getIncludeFile(fileName); + } + return result; +} + +// Certain library functions have specific indirect dependencies. See the +// comments alongside eaach of these. +const checkDependenciesSkip = new Set([ + '_mmap_js', + '_emscripten_throw_longjmp', + '_emscripten_receive_on_main_thread_js', + 'emscripten_start_fetch', + 'emscripten_start_wasm_audio_worklet_thread_async', +]); + +const checkDependenciesIgnore = new Set([ + // These are added in bulk to whole library files are so are not precise + '$PThread', + '$SDL', + '$GLUT', + '$GLEW', + '$Browser', + '$AL', + '$GL', + '$IDBStore', + // These are added purely for their side effects + '$polyfillWaitAsync', + '$GLImmediateSetup', + '$emscriptenGetAudioObject', + // These get conservatively injected via i53ConversionDeps + '$bigintToI53Checked', + '$convertI32PairToI53Checked', + 'setTempRet0', +]); + +/** + * Hacky attempt to find unused `__deps` entries. This is not enabled by default + * but can be enabled by setting CHECK_DEPS above. + * TODO: Use a more precise method such as tokenising using acorn. + */ +function checkDependencies(symbol, snippet, deps, postset) { + if (checkDependenciesSkip.has(symbol)) { + return; + } + for (const dep of deps) { + if (typeof dep === 'function') { + continue; + } + if (checkDependenciesIgnore.has(dep)) { + continue; + } + const mangled = mangleCSymbolName(dep); + if (!snippet.includes(mangled) && (!postset || !postset.includes(mangled))) { + error(`${symbol}: unused dependency: ${dep}`); + } + } +} + +function addImplicitDeps(snippet, deps) { + // There are some common dependencies that we inject automatically by + // conservatively scanning the input functions for their usage. + // Specifically, these are dependencies that are very common and would be + // burdensome to add manually to all functions. + // The first four are deps that are automatically/conditionally added + // by the {{{ makeDynCall }}}, and {{{ runtimeKeepalivePush/Pop }}} macros. + const autoDeps = [ + 'getDynCaller', + 'getWasmTableEntry', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'UTF8ToString', + ]; + for (const dep of autoDeps) { + if (snippet.includes(dep + '(')) { + deps.push('$' + dep); + } + } +} + +function sigToArgs(sig) { + const args = [] + for (var i = 1; i < sig.length; i++) { + args.push(`a${i}`); + } + return args.join(','); +} + +function handleI64Signatures(symbol, snippet, sig, i53abi, isAsyncFunction) { + // Handle i64 parameters and return values. + // + // When WASM_BIGINT is enabled these arrive as BigInt values which we + // convert to int53 JS numbers. If necessary, we also convert the return + // value back into a BigInt. + // + // When WASM_BIGINT is not enabled we receive i64 values as a pair of i32 + // numbers which is converted to single int53 number. In necessary, we also + // split the return value into a pair of i32 numbers. + return modifyJSFunction(snippet, (args, body, async_, oneliner) => { + let argLines = args.split('\n'); + argLines = argLines.map((line) => line.split('//')[0]); + const argNames = argLines + .join(' ') + .split(',') + .map((name) => name.trim()); + const newArgs = []; + let argConversions = ''; + if (sig.length > argNames.length + 1) { + error(`handleI64Signatures: signature '${sig}' too long for ${symbol}(${argNames.join(', ')})`); + return snippet; + } + for (const [i, name] of argNames.entries()) { + // If sig is shorter than argNames list then argType will be undefined + // here, which will result in the default case below. + const argType = sig[i + 1]; + if (WASM_BIGINT && ((MEMORY64 && argType == 'p') || (i53abi && argType == 'j'))) { + argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; + } else { + if (argType == 'j' && i53abi) { + argConversions += ` ${receiveI64ParamAsI53(name, undefined, false)}\n`; + newArgs.push(defineI64Param(name)); + } else if (argType == 'p' && CAN_ADDRESS_2GB) { + argConversions += ` ${name} >>>= 0;\n`; + newArgs.push(name); + } else { + newArgs.push(name); + } + } + } + + if (!WASM_BIGINT) { + args = newArgs.join(','); + } + + if ((sig[0] == 'j' && i53abi) || (sig[0] == 'p' && MEMORY64)) { + // For functions that where we need to mutate the return value, we + // also need to wrap the body in an inner function. + + // If the inner function is marked as `__async` then we need to `await` + // the result before casting it to BigInt. Note that we use the `__async` + // attribute here rather than the presence of the `async` JS keyword + // because this is what tells us that the function is going to return + // a promise. i.e. we support async functions that return promises but + // are not marked with the `async` keyword (the latter is only necessary + // if the function uses the `await` keyword)) + const await_ = isAsyncFunction ? 'await ' : ''; + const orig_async_ = async_; + async_ = isAsyncFunction ? 'async ' : async_; + if (oneliner) { + // Special case for abort(), this a noreturn function and but closure + // compiler doesn't have a way to express that, so it complains if we + // do `BigInt(abort(..))`. + if (body.startsWith('abort(')) { + return snippet; + } + if (argConversions) { + return `${async_}(${args}) => { +${argConversions} +return ${makeReturn64(await_ + body)}; +}`; + } + return `${async_}(${args}) => ${makeReturn64(await_ + body)};`; + } + return `\ +${async_}function(${args}) { +${argConversions} +var ret = (${orig_async_}() => { ${body} })(); +return ${makeReturn64(await_ + 'ret')}; +}`; + } + + // Otherwise no inner function is needed and we covert the arguments + // before executing the function body. + if (oneliner) { + body = `return ${body}`; + } + return `\ +${async_}function(${args}) { +${argConversions} +${body}; +}`; + }); +} + +function handleAsyncFunction(snippet, sig) { + const return64 = sig && (MEMORY64 && sig.startsWith('p') || sig.startsWith('j')) + let handleAsync = 'Asyncify.handleAsync(innerFunc)' + if (return64 && ASYNCIFY == 1) { + handleAsync = makeReturn64(handleAsync); + } + return modifyJSFunction(snippet, (args, body, async_, oneliner) => { + if (!oneliner) { + body = `{\n${body}\n}`; + } + return `\ +function(${args}) { + let innerFunc = ${async_} () => ${body}; + return ${handleAsync}; +}\n`; + }); +} + +// The three different inter-thread proxying methods. +// See system/lib/pthread/proxying.c +const PROXY_ASYNC = 0; +const PROXY_SYNC = 1; +const PROXY_SYNC_ASYNC = 2; + +export async function runJSify(outputFile, symbolsOnly) { + const libraryItems = []; + const symbolDeps = {}; + const asyncFuncs = []; + let postSets = []; + + LibraryManager.load(); + + let outputHandle = process.stdout; + if (outputFile) { + outputHandle = await fs.open(outputFile, 'w'); + } + + async function writeOutput(str) { + await outputHandle.write(str + '\n'); + } + + const symbolsNeeded = DEFAULT_LIBRARY_FUNCS_TO_INCLUDE; + symbolsNeeded.push(...extraLibraryFuncs); + for (const sym of EXPORTED_RUNTIME_METHODS) { + if ('$' + sym in LibraryManager.library) { + symbolsNeeded.push('$' + sym); + } + } + + for (const key of Object.keys(LibraryManager.library)) { + if (!isDecorator(key)) { + if (INCLUDE_FULL_LIBRARY || EXPORTED_FUNCTIONS.has(mangleCSymbolName(key))) { + symbolsNeeded.push(key); + } + } + } + + function processLibraryFunction(snippet, symbol, mangled, deps, isStub) { + // It is possible that when printing the function as a string on Windows, + // the js interpreter we are in returns the string with Windows line endings + // \r\n. This is undesirable, since line endings are managed in the form \n + // in the output for binary file writes, so make sure the endings are + // uniform. + snippet = snippet.toString().replace(/\r\n/gm, '\n'); + + // Is this a shorthand `foo() {}` method syntax? + // If so, prepend a function keyword so that it's valid syntax when extracted. + if (snippet.startsWith(symbol)) { + snippet = 'function ' + snippet; + } + + if (isStub) { + return snippet; + } + + // apply LIBRARY_DEBUG if relevant + if (LIBRARY_DEBUG && !isJsOnlySymbol(symbol)) { + snippet = modifyJSFunction(snippet, (args, body, _async, oneliner) => { + var run_func; + if (oneliner) { + run_func = `var ret = ${body}`; + } else { + run_func = `var ret = (() => { ${body} })();`; + } + return `\ +function(${args}) { + dbg("[library call:${mangled}: " + Array.prototype.slice.call(arguments).map(prettyPrint) + "]"); + ${run_func} + dbg(" [ return:" + prettyPrint(ret)); + return ret; +}`; + }); + } + + const sig = LibraryManager.library[symbol + '__sig']; + const isAsyncFunction = ASYNCIFY && LibraryManager.library[symbol + '__async']; + + const i53abi = LibraryManager.library[symbol + '__i53abi']; + if (i53abi) { + if (!sig) { + error(`JS library error: '__i53abi' decorator requires '__sig' decorator: '${symbol}'`); + } + if (!sig.includes('j')) { + error(`JS library error: '__i53abi' only makes sense when '__sig' includes 'j' (int64): '${symbol}'`); + } + } + if ( + sig && + ((i53abi && sig.includes('j')) || ((MEMORY64 || CAN_ADDRESS_2GB) && sig.includes('p'))) + ) { + snippet = handleI64Signatures(symbol, snippet, sig, i53abi, isAsyncFunction); + compileTimeContext.i53ConversionDeps.forEach((d) => deps.push(d)); + } + + if (ASYNCIFY && isAsyncFunction == 'auto') { + snippet = handleAsyncFunction(snippet, sig); + } + + const proxyingMode = LibraryManager.library[symbol + '__proxy']; + if (proxyingMode) { + if (!['sync', 'async', 'none'].includes(proxyingMode)) { + error(`JS library error: invalid proxying mode '${symbol}__proxy: ${proxyingMode}' specified`); + } + if (SHARED_MEMORY && proxyingMode != 'none') { + if (PTHREADS) { + snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => { + if (oneliner) { + body = `return ${body}`; + } + let proxyMode = PROXY_ASYNC; + if (proxyingMode === 'sync') { + const isAsyncFunction = LibraryManager.library[symbol + '__async']; + if (isAsyncFunction) { + proxyMode = PROXY_SYNC_ASYNC; + } else { + proxyMode = PROXY_SYNC; + } + } + const rtnType = sig && sig.length ? sig[0] : null; + const proxyFunc = + MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread'; + deps.push('$' + proxyFunc); + return ` +${async_}function(${args}) { +if (ENVIRONMENT_IS_PTHREAD) + return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${proxyMode}${args ? ', ' : ''}${args}); +${body} +}\n`; + }); + } else if (WASM_WORKERS && ASSERTIONS) { + // In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers + // (since there is no automatic proxying architecture available) + snippet = modifyJSFunction( + snippet, + (args, body) => ` +function(${args}) { + assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${mangled}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!"); + ${body} +}\n`, + ); + } + proxiedFunctionTable.push(mangled); + } + } + + return snippet; + } + + function symbolHandler(symbol) { + // In LLVM, exceptions generate a set of functions of form + // __cxa_find_matching_catch_1(), __cxa_find_matching_catch_2(), etc. where + // the number specifies the number of arguments. In Emscripten, route all + // these to a single function 'findMatchingCatch' that takes an array + // of argument. + if (LINK_AS_CXX && !WASM_EXCEPTIONS && symbol.startsWith('__cxa_find_matching_catch_')) { + if (DISABLE_EXCEPTION_THROWING) { + error( + 'DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but exception catching code appears. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-exceptions (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.', + ); + return; + } + if (!(symbol in LibraryManager.library)) { + // Create a new __cxa_find_matching_catch variant on demand. + const num = +symbol.split('_').slice(-1)[0]; + compileTimeContext.addCxaCatch(num); + } + // Continue, with the code below emitting the proper JavaScript based on + // what we just added to the library. + } + + function addFromLibrary(symbol, dependent) { + // don't process any special identifiers. These are looked up when + // processing the base name of the identifier. + if (isDecorator(symbol)) { + return; + } + + // if the function was implemented in compiled code, there is no need to + // include the js version + if (WASM_EXPORTS.has(symbol)) { + return; + } + + if (symbol in addedLibraryItems) { + return; + } + addedLibraryItems[symbol] = true; + + if (!(symbol + '__deps' in LibraryManager.library)) { + LibraryManager.library[symbol + '__deps'] = []; + } + + const deps = LibraryManager.library[symbol + '__deps']; + let sig = LibraryManager.library[symbol + '__sig']; + if (!WASM_BIGINT && sig && sig[0] == 'j') { + // Without WASM_BIGINT functions that return i64 depend on setTempRet0 + // to return the upper 32-bits of the result. + // See makeReturn64 in parseTools.py. + deps.push('setTempRet0'); + } + + const isAsyncFunction = LibraryManager.library[symbol + '__async']; + if (ASYNCIFY && isAsyncFunction) { + asyncFuncs.push(symbol); + } + + if (symbolsOnly) { + if (LibraryManager.library.hasOwnProperty(symbol)) { + // Resolve aliases before looking up deps + var transitiveDeps = getTransitiveDeps(symbol); + symbolDeps[symbol] = transitiveDeps.filter( + (d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library), + ); + } + return; + } + + // This gets set to true in the case of dynamic linking for symbols that + // are undefined in the main module. In this case we create a stub that + // will resolve the correct symbol at runtime, or assert if its missing. + let isStub = false; + + const mangled = mangleCSymbolName(symbol); + + if (!LibraryManager.library.hasOwnProperty(symbol)) { + const isWeakImport = WEAK_IMPORTS.has(symbol); + if (!isDefined(symbol) && !isWeakImport) { + if (PROXY_TO_PTHREAD && !MAIN_MODULE && symbol == '__main_argc_argv') { + error('PROXY_TO_PTHREAD proxies main() for you, but no main exists'); + return; + } + let undefinedSym = symbol; + if (symbol === '__main_argc_argv') { + undefinedSym = 'main/__main_argc_argv'; + } + let msg = 'undefined symbol: ' + undefinedSym; + if (dependent) msg += ` (referenced by ${dependent})`; + if (ERROR_ON_UNDEFINED_SYMBOLS) { + error(msg); + warnOnce( + 'To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`', + ); + warnOnce( + mangled + + ' may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library', + ); + } else if (WARN_ON_UNDEFINED_SYMBOLS) { + warn(msg); + } else { + debugLog(msg); + } + if (symbol === '__main_argc_argv' && STANDALONE_WASM) { + warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry'); + } + } + + // emit a stub that will fail at runtime + var stubFunctionBody = `abort('missing function: ${symbol}');` + if (MAIN_MODULE) { + // Create a stub for this symbol which can later be replaced by the + // dynamic linker. If this stub is called before the symbol is + // resolved assert in debug builds or trap in release builds. + let target = `wasmImports['${symbol}']`; + if (ASYNCIFY) { + // See the definition of asyncifyStubs in preamble.js for why this + // is needed. + target = `asyncifyStubs['${symbol}']`; + } + let assertion = ''; + if (ASSERTIONS) { + assertion += `if (!${target} || ${target}.stub) abort("external symbol '${symbol}' is missing. perhaps a side module was not linked in? if this function was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment");\n`; + } + stubFunctionBody = assertion + `return ${target}(...args);`; + } + isStub = true; + LibraryManager.library[symbol] = new Function('...args', stubFunctionBody); + } + + librarySymbols.push(mangled); + + const original = LibraryManager.library[symbol]; + let snippet = original; + const isUserSymbol = LibraryManager.library[symbol + '__user']; + // Check for dependencies on `__internal` symbols from user libraries. + for (const dep of deps) { + if (isUserSymbol && LibraryManager.library[dep + '__internal']) { + warn(`user library symbol '${symbol}' depends on internal symbol '${dep}'`); + } + } + + let isFunction = typeof snippet == 'function'; + let isNativeAlias = false; + + const postsetId = symbol + '__postset'; + const postset = LibraryManager.library[postsetId]; + if (postset) { + // A postset is either code to run right now, or some text we should emit. + // If it's code, it may return some text to emit as well. + const postsetString = typeof postset == 'function' ? postset() : postset; + if (postsetString && !addedLibraryItems[postsetId]) { + addedLibraryItems[postsetId] = true; + postSets.push(postsetString + ';'); + } + } + + if (LibraryManager.isAlias(snippet)) { + // Redirection for aliases. We include the parent, and at runtime + // make ourselves equal to it. This avoid having duplicate + // functions with identical content. + const aliasTarget = snippet; + if (WASM_EXPORTS.has(aliasTarget)) { + debugLog(`native alias: ${mangled} -> ${aliasTarget}`); + nativeAliases[mangled] = aliasTarget; + snippet = undefined; + isNativeAlias = true; + } else { + debugLog(`js alias: ${mangled} -> ${aliasTarget}`); + snippet = mangleCSymbolName(aliasTarget); + // When we have an alias for another JS function we can normally + // point them at the same function. However, in some cases (where + // signatures are relevant and they differ between and alais and + // it's target) we need to construct a forwarding function from + // one to the other. + const isSigRelevant = MAIN_MODULE || MEMORY64 || CAN_ADDRESS_2GB || (sig && sig.includes('j')); + const targetSig = LibraryManager.library[aliasTarget + '__sig']; + if (isSigRelevant && sig && targetSig && sig != targetSig) { + debugLog(`${symbol}: Alias target (${aliasTarget}) has different signature (${sig} vs ${targetSig})`) + isFunction = true; + snippet = `(${sigToArgs(sig)}) => ${snippet}(${sigToArgs(targetSig)})`; + } + } + } else if (typeof snippet == 'object') { + snippet = stringifyWithFunctions(snippet); + addImplicitDeps(snippet, deps); + } else if (typeof snippet == 'string' && (snippet.match(/^\s*\([^}]*\)\s*=>/) || snippet.match(/^function\b/))) { + // Support functions that are already "stringified" + isFunction = true; + } + + if (isFunction) { + snippet = processLibraryFunction(snippet, symbol, mangled, deps, isStub); + addImplicitDeps(snippet, deps); + if (CHECK_DEPS && !isUserSymbol) { + checkDependencies(symbol, snippet, deps, postset?.toString()); + } + } + + debugLog(`adding ${symbol} (referenced by ${dependent})`); + function addDependency(dep) { + // dependencies can be JS functions, which we just run + if (typeof dep == 'function') { + return dep(); + } + // $noExitRuntime is special since there are conditional usages of it + // in libcore.js and libpthread.js. These happen before deps are + // processed so depending on it via `__deps` doesn't work. + if (dep === '$noExitRuntime') { + error( + 'noExitRuntime cannot be referenced via __deps mechanism. Use DEFAULT_LIBRARY_FUNCS_TO_INCLUDE or EXPORTED_RUNTIME_METHODS', + ); + } + return addFromLibrary(dep, `${symbol}, referenced by ${dependent}`); + } + let contentText; + if (isFunction) { + // Emit the body of a JS library function. + if ((USE_ASAN || USE_LSAN) && LibraryManager.library[symbol + '__noleakcheck']) { + // CodeQL [js/bad-code-sanitization]: this is safe for dotnet since we don't expose ASAN/LSAN/UBSAN options and those shouldn't be used in production + contentText = modifyJSFunction( + snippet, + (args, body) => `(${args}) => noLeakCheck(() => {${body}})`, + ); + deps.push('$noLeakCheck'); + } else { + contentText = snippet; // Regular JS function that will be executed in the context of the calling thread. + } + // Give the function the correct (mangled) name. Overwrite it if it's + // already named. This must happen after the last call to + // modifyJSFunction which could have changed or removed the name. + if (contentText.match(/^\s*([^}]*)\s*=>/s)) { + // Handle arrow functions + contentText = `var ${mangled} = ` + contentText + ';'; + } else if (contentText.startsWith('class ')) { + // Handle class declarations (which also have typeof == 'function'.) + contentText = contentText.replace(/^class(?:\s+(?!extends\b)[^{\s]+)?/, `class ${mangled}`); + } else { + // Handle regular (non-arrow) functions + contentText = contentText.replace(/function(?:\s+([^(]+))?\s*\(/, `function ${mangled}(`); + } + } else if (typeof snippet == 'string' && snippet.startsWith(';')) { + // In JS libraries + // foo: ';[code here verbatim]' + // emits + // 'var foo;[code here verbatim];' + contentText = 'var ' + mangled + snippet; + if (snippet[snippet.length - 1] != ';' && snippet[snippet.length - 1] != '}') { + contentText += ';'; + } + } else if (typeof snippet == 'undefined') { + // For JS library functions that are simply aliases of native symbols, + // we don't need to generate anything here. Instead these get included + // and exported alongside native symbols. + // See `create_receiving` in `tools/emscripten.py`. + if (isNativeAlias) { + contentText = ''; + } else { + contentText = `var ${mangled};`; + } + } else { + // In JS libraries + // foo: '=[value]' + // emits + // 'var foo = [value];' + if (typeof snippet == 'string' && snippet[0] == '=') { + snippet = snippet.slice(1); + } + contentText = `var ${mangled} = ${snippet};`; + } + + if (contentText && MODULARIZE == 'instance' && (EXPORT_ALL || EXPORTED_FUNCTIONS.has(mangled)) && !isStub) { + // In MODULARIZE=instance mode mark JS library symbols are exported at + // the point of declaration. + contentText = 'export ' + contentText; + } + + // Dynamic linking needs signatures to create proper wrappers. + if (sig && MAIN_MODULE) { + if (!WASM_BIGINT) { + sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii'); + } + contentText += `\n${mangled}.sig = '${sig}';`; + } + if (ASYNCIFY && isAsyncFunction) { + assert(isFunction); + contentText += `\n${mangled}.isAsync = true;`; + } + if (isStub) { + contentText += `\n${mangled}.stub = true;`; + if (ASYNCIFY && MAIN_MODULE) { + contentText += `\nasyncifyStubs['${symbol}'] = undefined;`; + } + } + + // Add the docs if they exist and if we are actually emitting a declaration. + // See the TODO about wasmTable above. + let docs = LibraryManager.library[symbol + '__docs']; + let commentText = ''; + if (contentText != '' && docs) { + commentText += docs + '\n'; + } + + if (EMIT_TSD) { + LibraryManager.libraryDefinitions[mangled] = { + docs: docs ?? null, + snippet: snippet ?? null, + }; + } + + const depsText = deps + ? deps + .map(addDependency) + .filter((x) => x != '') + .join('\n') + '\n' + : ''; + return depsText + commentText + contentText; + } + + const JS = addFromLibrary(symbol, 'root reference (e.g. compiled C/C++ code)'); + libraryItems.push(JS); + } + + function includeSystemFile(fileName) { + writeOutput(getSystemIncludeFile(fileName)); + } + + function includeFile(fileName) { + writeOutput(getIncludeFile(fileName)); + } + + function finalCombiner() { + const splitPostSets = splitter(postSets, (x) => x.symbol && x.dependencies); + postSets = splitPostSets.leftIn; + const orderedPostSets = splitPostSets.splitOut; + + let limit = orderedPostSets.length * orderedPostSets.length; + for (let i = 0; i < orderedPostSets.length; i++) { + for (let j = i + 1; j < orderedPostSets.length; j++) { + if (orderedPostSets[j].symbol in orderedPostSets[i].dependencies) { + const temp = orderedPostSets[i]; + orderedPostSets[i] = orderedPostSets[j]; + orderedPostSets[j] = temp; + i--; + limit--; + assert(limit > 0, 'Could not sort postsets!'); + break; + } + } + } + + postSets.push(...orderedPostSets); + + const shellFile = MINIMAL_RUNTIME ? 'shell_minimal.js' : 'shell.js'; + includeSystemFile(shellFile); + + const preFile = MINIMAL_RUNTIME ? 'preamble_minimal.js' : 'preamble.js'; + includeSystemFile(preFile); + + writeOutput('// Begin JS library code\n'); + for (const item of libraryItems.concat(postSets)) { + writeOutput(indentify(item || '', 2)); + } + writeOutput('// End JS library code\n'); + + if (!MINIMAL_RUNTIME) { + includeSystemFile('postlibrary.js'); + } + + if (PTHREADS) { + writeOutput(` +// proxiedFunctionTable specifies the list of functions that can be called +// either synchronously or asynchronously from other threads in postMessage()d +// or internally queued events. This way a pthread in a Worker can synchronously +// access e.g. the DOM on the main thread. +var proxiedFunctionTable = [ + ${proxiedFunctionTable.join(',\n ')} +]; +`); + } + + // This is the main 'post' pass. Print out the generated code + // that we have here, together with the rest of the output + // that we started to print out earlier (see comment on the + // "Final shape that will be created"). + writeOutput('// EMSCRIPTEN_END_FUNCS\n'); + + const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js'; + includeSystemFile(postFile); + + for (const fileName of POST_JS_FILES) { + includeFile(fileName); + } + + if (MODULARIZE && MODULARIZE != 'instance') { + includeSystemFile('postamble_modularize.js'); + } + + if (errorOccured()) { + throw Error('Aborting compilation due to previous errors'); + } + + writeOutput( + '//FORWARDED_DATA:' + + JSON.stringify({ + librarySymbols, + nativeAliases, + warnings: warningOccured(), + asyncFuncs, + libraryDefinitions: LibraryManager.libraryDefinitions, + ATPRERUNS: ATPRERUNS.join('\n'), + ATMODULES: ATMODULES.join('\n'), + ATINITS: ATINITS.join('\n'), + ATPOSTCTORS: ATPOSTCTORS.join('\n'), + ATMAINS: ATMAINS.join('\n'), + ATPOSTRUNS: ATPOSTRUNS.join('\n'), + ATEXITS: ATEXITS.join('\n'), + }), + ); + } + + for (const sym of symbolsNeeded) { + symbolHandler(sym); + } + + if (symbolsOnly) { + writeOutput( + JSON.stringify({ + deps: symbolDeps, + asyncFuncs, + extraLibraryFuncs, + }), + ); + } else { + timer.start('finalCombiner') + finalCombiner(); + timer.stop('finalCombiner') + } + + if (errorOccured()) { + throw Error('Aborting compilation due to previous errors'); + } + + if (outputFile) await outputHandle.close(); +} + +addToCompileTimeContext({ + extraLibraryFuncs, + addedLibraryItems, + preJS, +}); diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js new file mode 100644 index 0000000000000..ea6efd267d3f0 --- /dev/null +++ b/src/lib/libaddfunction.js @@ -0,0 +1,256 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + // This gives correct answers for everything less than 2^{14} = 16384 + // I hope nobody is contemplating functions with 16384 arguments... + $uleb128EncodeWithLen__internal: true, + $uleb128EncodeWithLen: (arr) => { + const n = arr.length; +#if ASSERTIONS + assert(n < 16384); +#endif + // Note: this LEB128 length encoding produces extra byte for n < 128, + // but we don't care as it's only used in a temporary representation. + return [(n % 128) | 128, n >> 7, ...arr]; + }, +#if WASM_JS_TYPES + // Converts a signature like 'vii' into a description of the wasm types, like + // { parameters: ['i32', 'i32'], results: [] }. + $sigToWasmTypes__internal: true, + $sigToWasmTypes: (sig) => { +#if ASSERTIONS && !WASM_BIGINT + assert(!sig.includes('j'), 'i64 not permitted in function signatures when WASM_BIGINT is disabled'); +#endif + var typeNames = { + 'i': 'i32', + 'j': 'i64', + 'f': 'f32', + 'd': 'f64', + 'e': 'externref', +#if MEMORY64 + 'p': 'i64', +#else + 'p': 'i32', +#endif + }; + var type = { + parameters: [], + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]] + }; + for (var i = 1; i < sig.length; ++i) { +#if ASSERTIONS + assert(sig[i] in typeNames, 'invalid signature char: ' + sig[i]); +#endif + type.parameters.push(typeNames[sig[i]]); + } + return type; + }, +#endif + $wasmTypeCodes__internal: true, + // Note: using template literal here instead of plain object + // because jsify serializes objects w/o quotes and Closure will then + // incorrectly mangle the properties. + $wasmTypeCodes: `{ + 'i': 0x7f, // i32 +#if MEMORY64 + 'p': 0x7e, // i64 +#else + 'p': 0x7f, // i32 +#endif + 'j': 0x7e, // i64 + 'f': 0x7d, // f32 + 'd': 0x7c, // f64 + 'e': 0x6f, // externref + }`, + + $generateTypePack__internal: true, + $generateTypePack__deps: ['$uleb128EncodeWithLen', '$wasmTypeCodes'], + $generateTypePack: (types) => uleb128EncodeWithLen(Array.from(types, (type) => { + var code = wasmTypeCodes[type]; +#if ASSERTIONS + assert(code, `invalid signature char: ${type}`); +#endif + return code; + })), + +#if !WASM2JS || WASM == 2 + // Wraps a JS function as a wasm function with a given signature. + $convertJsFunctionToWasm__deps: [ + '$uleb128EncodeWithLen', +#if WASM_JS_TYPES + '$sigToWasmTypes', +#endif + '$generateTypePack' + ], + $convertJsFunctionToWasm: (func, sig) => { +#if ASSERTIONS && !WASM_BIGINT + assert(!sig.includes('j'), 'i64 not permitted in function signatures when WASM_BIGINT is disabled'); +#endif +#if WASM_JS_TYPES + // If the type reflection proposal is available, use the new + // "WebAssembly.Function" constructor. + // Otherwise, construct a minimal wasm module importing the JS function and + // re-exporting it. + if (WebAssembly.Function) { + return new WebAssembly.Function(sigToWasmTypes(sig), func); + } +#endif + + // Rest of the module is static + var bytes = Uint8Array.of( + 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm") + 0x01, 0x00, 0x00, 0x00, // version: 1 + 0x01, // Type section code + // The module is static, with the exception of the type section, which is + // generated based on the signature passed in. + ...uleb128EncodeWithLen([ + 0x01, // count: 1 + 0x60 /* form: func */, + // param types + ...generateTypePack(sig.slice(1)), + // return types (for now only supporting [] if `void` and single [T] otherwise) + ...generateTypePack(sig[0] === 'v' ? '' : sig[0]) + ]), + // The rest of the module is static + 0x02, 0x07, // import section + // (import "e" "f" (func 0 (type 0))) + 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, + 0x07, 0x05, // export section + // (export "f" (func 0 (type 0))) + 0x01, 0x01, 0x66, 0x00, 0x00, + ); + + // We can compile this wasm module synchronously because it is very small. + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") + var module = new WebAssembly.Module(bytes); + var instance = new WebAssembly.Instance(module, { 'e': { 'f': func } }); + var wrappedFunc = instance.exports['f']; + return wrappedFunc; + }, +#endif // !WASM2JS && WASM != 2 + + $freeTableIndexes: [], + + // Weak map of functions in the table to their indexes, created on first use. + $functionsInTableMap: undefined, + + $getEmptyTableSlot__deps: ['$freeTableIndexes', '$wasmTable'], + $getEmptyTableSlot: () => { + // Reuse a free index if there is one, otherwise grow. + if (freeTableIndexes.length) { + return freeTableIndexes.pop(); + } +#if ASSERTIONS + try { + #endif + // Grow the table + return wasmTable['grow']({{{ toIndexType('1') }}}); +#if ASSERTIONS + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + abort('Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'); + } +#endif + }, + + $updateTableMap__deps: ['$getWasmTableEntry'], + $updateTableMap: (offset, count) => { + if (functionsInTableMap) { + for (var i = offset; i < offset + count; i++) { + var item = getWasmTableEntry(i); + // Ignore null values. + if (item) { + functionsInTableMap.set(item, i); + } + } + } + }, + + $getFunctionAddress__deps: ['$updateTableMap', '$functionsInTableMap', '$wasmTable'], + $getFunctionAddress: (func) => { + // First, create the map if this is the first use. + if (!functionsInTableMap) { + functionsInTableMap = new WeakMap(); + updateTableMap(0, {{{ from64Expr('wasmTable.length') }}}); + } + return functionsInTableMap.get(func) || 0; + }, + + /** + * Add a function to the table. + * 'sig' parameter is required if the function being added is a JS function. + */ + $addFunction__docs: '/** @param {string=} sig */', + $addFunction__deps: ['$getFunctionAddress', + '$functionsInTableMap', '$getEmptyTableSlot', + '$setWasmTableEntry', +#if !WASM2JS || WASM == 2 + '$convertJsFunctionToWasm', +#endif +#if ASSERTIONS >= 2 + '$getWasmTableEntry', '$wasmTable', +#endif + ], + + $addFunction: (func, sig) => { +#if ASSERTIONS + assert(typeof func != 'undefined'); +#endif // ASSERTIONS + // Check if the function is already in the table, to ensure each function + // gets a unique index. + var rtn = getFunctionAddress(func); + if (rtn) { + return rtn; + } + + // It's not in the table, add it now. + +#if ASSERTIONS >= 2 + // Make sure functionsInTableMap is actually up to date, that is, that this + // function is not actually in the wasm Table despite not being tracked in + // functionsInTableMap. + for (var i = 0; i < wasmTable.length; i++) { + assert(getWasmTableEntry(i) != func, 'function in Table but not functionsInTableMap'); + } +#endif + + var ret = getEmptyTableSlot(); + +#if WASM2JS && WASM != 2 + setWasmTableEntry(ret, func); +#else + // Set the new value. + try { + // Attempting to call this with JS function will cause table.set() to fail + setWasmTableEntry(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } +#if ASSERTIONS + assert(typeof sig != 'undefined', 'Missing signature argument to addFunction: ' + func); +#endif + var wrapped = convertJsFunctionToWasm(func, sig); + setWasmTableEntry(ret, wrapped); + } +#endif + + functionsInTableMap.set(func, ret); + + return ret; + }, + + $removeFunction__deps: ['$functionsInTableMap', '$freeTableIndexes', + '$getWasmTableEntry', '$setWasmTableEntry'], + $removeFunction: (index) => { + functionsInTableMap.delete(getWasmTableEntry(index)); + setWasmTableEntry(index, null); + freeTableIndexes.push(index); + }, +}); diff --git a/src/lib/libasync.js b/src/lib/libasync.js new file mode 100644 index 0000000000000..17ca2c0b011d4 --- /dev/null +++ b/src/lib/libasync.js @@ -0,0 +1,633 @@ +/** + * @license + * Copyright 2014 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// +// Async support via ASYNCIFY +// + +addToLibrary({ + // error handling + + $runAndAbortIfError: (func) => { + try { + return func(); + } catch (e) { + abort(e); + } + }, + +#if ASYNCIFY + $Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback', +#if ASSERTIONS + '$createNamedFunction', +#endif +#if !MINIMAL_RUNTIME + '$runtimeKeepalivePush', '$runtimeKeepalivePop', +#endif +#if ASYNCIFY == 1 + // Needed by allocateData and handleSleep respectively + 'malloc', 'free', +#endif + ], + + $Asyncify: { + // + // Asyncify code that is shared between mode 1 (original) and mode 2 (JSPI). + // +#if ASYNCIFY == 1 && MEMORY64 + rewindArguments: new Map(), +#endif + instrumentWasmImports(imports) { +#if EMBIND_GEN_MODE + // Instrumenting is not needed when generating code. + return imports; +#endif +#if ASYNCIFY_DEBUG + dbg('asyncify instrumenting imports'); +#endif +#if ASSERTIONS && ASYNCIFY == 2 + assert('Suspending' in WebAssembly, 'JSPI not supported by current environment. Perhaps it needs to be enabled via flags?'); +#endif + var importPattern = {{{ new RegExp(`^(${ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS.map(x => x.split('.')[1]).join('|').replace(/\*/g, '.*')})$`) }}}; + + for (let [x, original] of Object.entries(imports)) { + if (typeof original == 'function') { + let isAsyncifyImport = original.isAsync || importPattern.test(x); +#if ASYNCIFY == 2 + // Wrap async imports with a suspending WebAssembly function. + if (isAsyncifyImport) { +#if ASYNCIFY_DEBUG + dbg('asyncify: suspendOnReturnedPromise for', x, original); +#endif + imports[x] = original = new WebAssembly.Suspending(original); + } +#endif +#if ASSERTIONS && ASYNCIFY != 2 // We cannot apply assertions with stack switching, as the imports must not be modified from suspender.suspendOnReturnedPromise TODO find a way + imports[x] = (...args) => { + var originalAsyncifyState = Asyncify.state; + try { + return original(...args); + } finally { + // Only asyncify-declared imports are allowed to change the + // state. + // Changing the state from normal to disabled is allowed (in any + // function) as that is what shutdown does (and we don't have an + // explicit list of shutdown imports). + var changedToDisabled = + originalAsyncifyState === Asyncify.State.Normal && + Asyncify.state === Asyncify.State.Disabled; + // invoke_* functions are allowed to change the state if we do + // not ignore indirect calls. + var ignoredInvoke = x.startsWith('invoke_') && + {{{ !ASYNCIFY_IGNORE_INDIRECT }}}; + if (Asyncify.state !== originalAsyncifyState && + !isAsyncifyImport && + !changedToDisabled && + !ignoredInvoke) { + abort(`import ${x} was not in ASYNCIFY_IMPORTS, but changed the state`); + } + } + }; +#if MAIN_MODULE + // The dynamic library loader needs to be able to read .sig + // properties, so that it knows function signatures when it adds + // them to the table. + imports[x].sig = original.sig; +#endif // MAIN_MODULE +#endif // ASSERTIONS + } + } + }, +#if ASYNCIFY == 1 && MEMORY64 + saveRewindArguments(func, passedArguments) { + return Asyncify.rewindArguments.set(func, Array.from(passedArguments)); + }, + restoreRewindArguments(func) { +#if ASSERTIONS + assert(Asyncify.rewindArguments.has(func)); +#endif + return Asyncify.rewindArguments.get(func); + }, +#endif + +#if ASYNCIFY == 1 + instrumentFunction(original) { + var wrapper = (...args) => { +#if ASYNCIFY_DEBUG >= 2 + dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} try ${original}`); +#endif + Asyncify.exportCallStack.push(original); + try { +#if MEMORY64 + Asyncify.saveRewindArguments(original, args); +#endif + return original(...args); + } finally { + if (!ABORT) { + var top = Asyncify.exportCallStack.pop(); +#if ASSERTIONS + assert(top === original); +#endif +#if ASYNCIFY_DEBUG >= 2 + dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} finally ${original}`); +#endif + Asyncify.maybeStopUnwind(); + } + } + }; + Asyncify.funcWrappers.set(original, wrapper); +#if MAIN_MODULE + wrapper.orig = original; +#endif +#if ASSERTIONS + wrapper = createNamedFunction(`__asyncify_wrapper_${original.name}`, wrapper); +#endif + return wrapper; + }, +#endif // ASYNCIFY == 1 + + instrumentWasmExports(exports) { +#if EMBIND_GEN_MODE + // Instrumenting is not needed when generating code. + return exports; +#endif +#if ASYNCIFY_DEBUG + dbg('asyncify instrumenting exports'); +#endif +#if ASYNCIFY == 2 + var exportPattern = {{{ new RegExp(`^(${ASYNCIFY_EXPORTS.join('|').replace(/\*/g, '.*')})$`) }}}; + Asyncify.asyncExports = new Set(); +#endif + var ret = {}; + for (let [x, original] of Object.entries(exports)) { + if (typeof original == 'function') { + #if ASYNCIFY == 2 + // Wrap all exports with a promising WebAssembly function. + let isAsyncifyExport = exportPattern.test(x); + if (isAsyncifyExport) { + Asyncify.asyncExports.add(original); + original = Asyncify.makeAsyncFunction(original); + } + ret[x] = original; +#else + var wrapper = Asyncify.instrumentFunction(original); + ret[x] = wrapper; +#endif + } else { + ret[x] = original; + } + } + return ret; + }, + +#if ASYNCIFY == 1 + // + // Original implementation of Asyncify. + // + State: { + Normal: 0, + Unwinding: 1, + Rewinding: 2, + Disabled: 3, + }, + state: 0, + StackSize: {{{ ASYNCIFY_STACK_SIZE }}}, + currData: null, + // The return value passed to wakeUp() in + // Asyncify.handleSleep((wakeUp) => {...}) is stored here, + // so we can return it later from the C function that called + // Asyncify.handleSleep() after rewinding finishes. + handleSleepReturnValue: 0, + // We must track which wasm exports are called into and + // exited, so that we know where the call stack began, + // which is where we must call to rewind it. + // This list contains the original Wasm exports. + exportCallStack: [], + callstackFuncToId: new Map(), + callStackIdToFunc: new Map(), + // Maps wasm functions to their corresponding wrapper function. + funcWrappers: new Map(), + callStackId: 0, + asyncPromiseHandlers: null, // { resolve, reject } pair for when *all* asynchronicity is done + sleepCallbacks: [], // functions to call every time we sleep + + getCallStackId(func) { +#if ASSERTIONS + assert(func); +#endif + if (!Asyncify.callstackFuncToId.has(func)) { + var id = Asyncify.callStackId++; + Asyncify.callstackFuncToId.set(func, id); + Asyncify.callStackIdToFunc.set(id, func); + } + return Asyncify.callstackFuncToId.get(func); + }, + + maybeStopUnwind() { +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY: maybe stop unwind', Asyncify.exportCallStack); +#endif + if (Asyncify.currData && + Asyncify.state === Asyncify.State.Unwinding && + Asyncify.exportCallStack.length === 0) { + // We just finished unwinding. + // Be sure to set the state before calling any other functions to avoid + // possible infinite recursion here (For example in debug pthread builds + // the dbg() function itself can call back into WebAssembly to get the + // current pthread_self() pointer). + Asyncify.state = Asyncify.State.Normal; +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY: stop unwind'); +#endif + {{{ runtimeKeepalivePush(); }}} + // Keep the runtime alive so that a re-wind can be done later. + runAndAbortIfError(_asyncify_stop_unwind); + if (typeof Fibers != 'undefined') { + Fibers.trampoline(); + } + } + }, + + whenDone() { +#if ASSERTIONS + assert(Asyncify.currData, 'Tried to wait for an async operation when none is in progress.'); + assert(!Asyncify.asyncPromiseHandlers, 'Cannot have multiple async operations in flight at once'); +#endif + return new Promise((resolve, reject) => { + Asyncify.asyncPromiseHandlers = { resolve, reject }; + }); + }, + + allocateData() { + // An asyncify data structure has three fields: + // 0 current stack pos + // 4 max stack pos + // 8 id of function at bottom of the call stack (callStackIdToFunc[id] == wasm func) + // + // The Asyncify ABI only interprets the first two fields, the rest is for the runtime. + // We also embed a stack in the same memory region here, right next to the structure. + // This struct is also defined as asyncify_data_t in emscripten/fiber.h + var ptr = _malloc({{{ C_STRUCTS.asyncify_data_s.__size__ }}} + Asyncify.StackSize); + Asyncify.setDataHeader(ptr, ptr + {{{ C_STRUCTS.asyncify_data_s.__size__ }}}, Asyncify.StackSize); + Asyncify.setDataRewindFunc(ptr); + return ptr; + }, + + setDataHeader(ptr, stack, stackSize) { + {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_ptr, 'stack', '*') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_limit, 'stack + stackSize', '*') }}}; + }, + + setDataRewindFunc(ptr) { + var bottomOfCallStack = Asyncify.exportCallStack[0]; +#if ASYNCIFY_DEBUG >= 2 + dbg(`ASYNCIFY: setDataRewindFunc(${ptr}), bottomOfCallStack is`, bottomOfCallStack, new Error().stack); +#endif +#if ASSERTIONS + assert(bottomOfCallStack, 'exportCallStack is empty'); +#endif + var rewindId = Asyncify.getCallStackId(bottomOfCallStack); + {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}}; + }, + + getDataRewindFunc(ptr) { + var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}}; + var func = Asyncify.callStackIdToFunc.get(id); +#if ASSERTIONS + assert(func, `id ${id} not found in callStackIdToFunc`); +#endif + return func; + }, + + doRewind(ptr) { + var original = Asyncify.getDataRewindFunc(ptr); +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY: doRewind:', original); +#endif + var func = Asyncify.funcWrappers.get(original); +#if ASSERTIONS + assert(original); + assert(func); +#endif + // Once we have rewound and the stack we no longer need to artificially + // keep the runtime alive. + {{{ runtimeKeepalivePop(); }}} +#if MEMORY64 + // When re-winding, the arguments to a function are ignored. For i32 arguments we + // can just call the function with no args at all since the engine will produce zeros + // for all arguments. However, for i64 arguments we get `undefined cannot be converted to + // BigInt`. + func = func.bind(0, ...Asyncify.restoreRewindArguments(original)); +#endif + return callUserCallback(func); + }, + + // This receives a function to call to start the async operation, and + // handles everything else for the user of this API. See emscripten_sleep() + // and other async methods for simple examples of usage. + handleSleep(startAsync) { +#if ASSERTIONS + assert(Asyncify.state !== Asyncify.State.Disabled, 'Asyncify cannot be done during or after the runtime exits'); +#endif + if (ABORT) return; +#if ASYNCIFY_DEBUG + dbg(`ASYNCIFY: handleSleep ${Asyncify.state}`); +#endif + if (Asyncify.state === Asyncify.State.Normal) { + // Prepare to sleep. Call startAsync, and see what happens: + // if the code decided to call our callback synchronously, + // then no async operation was in fact begun, and we don't + // need to do anything. + var reachedCallback = false; + var reachedAfterCallback = false; + startAsync((handleSleepReturnValue = 0) => { +#if ASSERTIONS + // old emterpretify API supported other stuff + assert(['undefined', 'number', 'boolean', 'bigint'].includes(typeof handleSleepReturnValue), `invalid type for handleSleepReturnValue: '${typeof handleSleepReturnValue}'`); +#endif + if (ABORT) return; + Asyncify.handleSleepReturnValue = handleSleepReturnValue; + reachedCallback = true; + if (!reachedAfterCallback) { + // We are happening synchronously, so no need for async. + return; + } +#if ASSERTIONS + // This async operation did not happen synchronously, so we did + // unwind. In that case there can be no compiled code on the stack, + // as it might break later operations (we can rewind ok now, but if + // we unwind again, we would unwind through the extra compiled code + // too). + assert(!Asyncify.exportCallStack.length, 'Waking up (starting to rewind) must be done from JS, without compiled code on the stack.'); +#endif +#if ASYNCIFY_DEBUG + dbg(`ASYNCIFY: start rewind ${Asyncify.currData}`); +#endif + Asyncify.state = Asyncify.State.Rewinding; + runAndAbortIfError(() => _asyncify_start_rewind(Asyncify.currData)); + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.resume(); + } + var asyncWasmReturnValue, isError = false; + try { + asyncWasmReturnValue = Asyncify.doRewind(Asyncify.currData); + } catch (err) { + asyncWasmReturnValue = err; + isError = true; + } + // Track whether the return value was handled by any promise handlers. + var handled = false; + if (!Asyncify.currData) { + // All asynchronous execution has finished. + // `asyncWasmReturnValue` now contains the final + // return value of the exported async WASM function. + // + // Note: `asyncWasmReturnValue` is distinct from + // `Asyncify.handleSleepReturnValue`. + // `Asyncify.handleSleepReturnValue` contains the return + // value of the last C function to have executed + // `Asyncify.handleSleep()`, whereas `asyncWasmReturnValue` + // contains the return value of the exported WASM function + // that may have called C functions that + // call `Asyncify.handleSleep()`. + var asyncPromiseHandlers = Asyncify.asyncPromiseHandlers; + if (asyncPromiseHandlers) { + Asyncify.asyncPromiseHandlers = null; + (isError ? asyncPromiseHandlers.reject : asyncPromiseHandlers.resolve)(asyncWasmReturnValue); + handled = true; + } + } + if (isError && !handled) { + // If there was an error and it was not handled by now, we have no choice but to + // rethrow that error into the global scope where it can be caught only by + // `onerror` or `onunhandledpromiserejection`. + throw asyncWasmReturnValue; + } + }); + reachedAfterCallback = true; + if (!reachedCallback) { + // A true async operation was begun; start a sleep. + Asyncify.state = Asyncify.State.Unwinding; + // TODO: reuse, don't alloc/free every sleep + Asyncify.currData = Asyncify.allocateData(); +#if ASYNCIFY_DEBUG + dbg(`ASYNCIFY: start unwind ${Asyncify.currData}`); +#endif + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.pause(); + } + runAndAbortIfError(() => _asyncify_start_unwind(Asyncify.currData)); + } + } else if (Asyncify.state === Asyncify.State.Rewinding) { + // Stop a resume. +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY: stop rewind'); +#endif + Asyncify.state = Asyncify.State.Normal; + runAndAbortIfError(_asyncify_stop_rewind); + _free(Asyncify.currData); + Asyncify.currData = null; + // Call all sleep callbacks now that the sleep-resume is all done. + Asyncify.sleepCallbacks.forEach(callUserCallback); + } else { + abort(`invalid state: ${Asyncify.state}`); + } + return Asyncify.handleSleepReturnValue; + }, + + // Unlike `handleSleep`, accepts a function returning a `Promise` + // and uses the fulfilled value instead of passing in a separate callback. + // + // This is particularly useful for native JS `async` functions where the + // returned value will "just work" and be passed back to C++. + handleAsync: (startAsync) => Asyncify.handleSleep(async (wakeUp) => { + // TODO: add error handling as a second param when handleSleep implements it. + wakeUp(await startAsync()); + }), + +#elif ASYNCIFY == 2 + // + // JSPI implementation of Asyncify. + // + + // Stores all the exported raw Wasm functions that are wrapped with async + // WebAssembly.Functions. + asyncExports: null, + isAsyncExport(func) { + return Asyncify.asyncExports?.has(func); + }, + handleAsync: async (startAsync) => { + {{{ runtimeKeepalivePush(); }}} + try { + return await startAsync(); + } finally { + {{{ runtimeKeepalivePop(); }}} + } + }, + handleSleep: (startAsync) => Asyncify.handleAsync(() => new Promise(startAsync)), + makeAsyncFunction(original) { +#if ASYNCIFY_DEBUG + dbg('asyncify: makeAsyncFunction for', original); +#endif + return WebAssembly.promising(original); + }, +#endif + }, + + emscripten_sleep__async: 'auto', + emscripten_sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), + + emscripten_wget_data__deps: ['$asyncLoad', 'malloc'], + emscripten_wget_data__async: 'auto', + emscripten_wget_data: async (url, pbuffer, pnum, perror) => { + /* no need for run dependency, this is async but will not do any prepare etc. step */ + try { + const byteArray = await asyncLoad(UTF8ToString(url)); + // can only allocate the buffer after the wakeUp, not during an asyncing + var buffer = _malloc(byteArray.length); // must be freed by caller! + HEAPU8.set(byteArray, buffer); + {{{ makeSetValue('pbuffer', 0, 'buffer', '*') }}}; + {{{ makeSetValue('pnum', 0, 'byteArray.length', 'i32') }}}; + {{{ makeSetValue('perror', 0, '0', 'i32') }}}; + } catch (err) { + {{{ makeSetValue('perror', 0, '1', 'i32') }}}; + } + }, + + emscripten_scan_registers__deps: ['$safeSetTimeout'], + emscripten_scan_registers__async: true, + emscripten_scan_registers: (func) => { + return Asyncify.handleSleep((wakeUp) => { + // We must first unwind, so things are spilled to the stack. Then while + // we are pausing we do the actual scan. After that we can resume. Note + // how using a timeout here avoids unbounded call stack growth, which + // could happen if we tried to scan the stack immediately after unwinding. + safeSetTimeout(() => { + var stackBegin = Asyncify.currData + {{{ C_STRUCTS.asyncify_data_s.__size__ }}}; + var stackEnd = {{{ makeGetValue('Asyncify.currData', 0, '*') }}}; + {{{ makeDynCall('vpp', 'func') }}}(stackBegin, stackEnd); + wakeUp(); + }, 0); + }); + }, + + $Fibers__deps: ['$Asyncify', 'emscripten_stack_set_limits', '$stackRestore'], + $Fibers: { + nextFiber: 0, + trampolineRunning: false, + trampoline() { + if (!Fibers.trampolineRunning && Fibers.nextFiber) { + Fibers.trampolineRunning = true; + do { + var fiber = Fibers.nextFiber; + Fibers.nextFiber = 0; +#if ASYNCIFY_DEBUG >= 2 + dbg("ASYNCIFY/FIBER: trampoline jump into fiber", fiber, new Error().stack); +#endif + Fibers.finishContextSwitch(fiber); + } while (Fibers.nextFiber); + Fibers.trampolineRunning = false; + } + }, + /* + * NOTE: This function is the asynchronous part of emscripten_fiber_swap. + */ + finishContextSwitch(newFiber) { + var stack_base = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, '*') }}}; + var stack_max = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, '*') }}}; + _emscripten_stack_set_limits(stack_base, stack_max); + +#if STACK_OVERFLOW_CHECK >= 2 + ___set_stack_limits(stack_base, stack_max); +#endif + + stackRestore({{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, '*') }}}); + + var entryPoint = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, '*') }}}; + + if (entryPoint !== 0) { +#if STACK_OVERFLOW_CHECK + writeStackCookie(); +#endif +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY/FIBER: entering fiber', newFiber, 'for the first time'); +#endif + Asyncify.currData = null; + {{{ makeSetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, 0, '*') }}}; + + var userData = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.user_data, '*') }}}; + {{{ makeDynCall('vp', 'entryPoint') }}}(userData); + } else { + var asyncifyData = newFiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}}; + Asyncify.currData = asyncifyData; + +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY/FIBER: start rewind', asyncifyData, '(resuming fiber', newFiber, ')'); +#endif + Asyncify.state = Asyncify.State.Rewinding; + _asyncify_start_rewind(asyncifyData); + Asyncify.doRewind(asyncifyData); + } + }, + }, + + emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers", '$stackSave'], + emscripten_fiber_swap__async: true, + emscripten_fiber_swap: (oldFiber, newFiber) => { + if (ABORT) return; +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY/FIBER: swap', oldFiber, '->', newFiber, 'state:', Asyncify.state); +#endif + if (Asyncify.state === Asyncify.State.Normal) { + Asyncify.state = Asyncify.State.Unwinding; + + var asyncifyData = oldFiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}}; + Asyncify.setDataRewindFunc(asyncifyData); + Asyncify.currData = asyncifyData; + +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY/FIBER: start unwind', asyncifyData); +#endif + _asyncify_start_unwind(asyncifyData); + + var stackTop = stackSave(); + {{{ makeSetValue('oldFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'stackTop', '*') }}}; + + Fibers.nextFiber = newFiber; + } else { +#if ASSERTIONS + assert(Asyncify.state === Asyncify.State.Rewinding); +#endif +#if ASYNCIFY_DEBUG + dbg('ASYNCIFY/FIBER: stop rewind'); +#endif + Asyncify.state = Asyncify.State.Normal; + _asyncify_stop_rewind(); + Asyncify.currData = null; + } + }, +#else // ASYNCIFY + emscripten_sleep: () => { + abort('Please compile your program with async support in order to use asynchronous operations like emscripten_sleep'); + }, + emscripten_wget: (url, file) => { + abort('Please compile your program with async support in order to use asynchronous operations like emscripten_wget'); + }, + emscripten_wget_data: (url, pbuffer, pnum, perror) => { + abort('Please compile your program with async support in order to use asynchronous operations like emscripten_wget_data'); + }, + emscripten_scan_registers: (func) => { + abort('Please compile your program with async support in order to use asynchronous operations like emscripten_scan_registers'); + }, + emscripten_fiber_swap: (oldFiber, newFiber) => { + abort('Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_swap'); + }, +#endif // ASYNCIFY +}); + +if (ASYNCIFY) { + extraLibraryFuncs.push('$Asyncify'); +} diff --git a/src/lib/libatomic.js b/src/lib/libatomic.js new file mode 100644 index 0000000000000..c30dff83323cd --- /dev/null +++ b/src/lib/libatomic.js @@ -0,0 +1,164 @@ +/** + * @license + * Copyright 2023 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +assert(SHARED_MEMORY); + +addToLibrary({ +// Chrome 87 shipped Atomics.waitAsync: +// https://www.chromestatus.com/feature/6243382101803008 +// However its implementation is faulty: +// https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 +// Firefox Nightly 86.0a1 (2021-01-15) does not yet have it: +// https://bugzilla.mozilla.org/show_bug.cgi?id=1467846 +// And at the time of writing, no other browser has it either. +#if MIN_CHROME_VERSION < 91 || MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED || MIN_FIREFOX_VERSION != TARGET_NOT_SUPPORTED || ENVIRONMENT_MAY_BE_NODE + // Partially polyfill Atomics.waitAsync() if not available in the browser. + // Also polyfill for old Chrome-based browsers, where Atomics.waitAsync is + // broken until Chrome 91, see: + // https://bugs.chromium.org/p/chromium/issues/detail?id=1167541 + // https://github.com/tc39/proposal-atomics-wait-async/blob/master/PROPOSAL.md + // This polyfill performs polling with setTimeout() to observe a change in the + // target memory location. + $waitAsyncPolyfilled: '=(!Atomics.waitAsync || (globalThis.navigator?.userAgent && Number((navigator.userAgent.match(/Chrom(e|ium)\\/([0-9]+)\\./)||[])[2]) < 91));', + $polyfillWaitAsync__deps: ['$waitAsyncPolyfilled'], + $polyfillWaitAsync__postset: `if (waitAsyncPolyfilled) { + let __Atomics_waitAsyncAddresses = [/*[i32a, index, value, maxWaitMilliseconds, promiseResolve]*/]; + function __Atomics_pollWaitAsyncAddresses() { + let now = performance.now(); + let l = __Atomics_waitAsyncAddresses.length; + for (let i = 0; i < l; ++i) { + let a = __Atomics_waitAsyncAddresses[i]; + let expired = (now > a[3]); + let awoken = (Atomics.load(a[0], a[1]) != a[2]); + if (expired || awoken) { + __Atomics_waitAsyncAddresses[i--] = __Atomics_waitAsyncAddresses[--l]; + __Atomics_waitAsyncAddresses.length = l; + a[4](awoken ? 'ok': 'timed-out'); + } + } + if (l) { + // If we still have addresses to wait, loop the timeout handler to continue polling. + setTimeout(__Atomics_pollWaitAsyncAddresses, 10); + } + } + #if ASSERTIONS && WASM_WORKERS + if (!ENVIRONMENT_IS_WASM_WORKER) err('Current environment does not support Atomics.waitAsync(): polyfilling it, but this is going to be suboptimal.'); + #endif + /** + * @param {number=} maxWaitMilliseconds + */ + Atomics.waitAsync = (i32a, index, value, maxWaitMilliseconds) => { + let val = Atomics.load(i32a, index); + if (val != value) return { async: false, value: 'not-equal' }; + if (maxWaitMilliseconds <= 0) return { async: false, value: 'timed-out' }; + maxWaitMilliseconds = performance.now() + (maxWaitMilliseconds || Infinity); + let promiseResolve; + let promise = new Promise((resolve) => { promiseResolve = resolve; }); + if (!__Atomics_waitAsyncAddresses[0]) setTimeout(__Atomics_pollWaitAsyncAddresses, 10); + __Atomics_waitAsyncAddresses.push([i32a, index, value, maxWaitMilliseconds, promiseResolve]); + return { async: true, value: promise }; + }; +}`, +#else + $waitAsyncPolyfilled: false, +#endif + + $polyfillWaitAsync__internal: true, + $polyfillWaitAsync: () => { + // nop, used for its postset to ensure `Atomics.waitAsync()` polyfill is + // included exactly once and only included when needed. + // Any function using Atomics.waitAsync should depend on this. + }, + + $atomicWaitStates__internal: true, + $atomicWaitStates: ['ok', 'not-equal', 'timed-out'], + $liveAtomicWaitAsyncs: {}, + $liveAtomicWaitAsyncs__internal: true, + $liveAtomicWaitAsyncCounter: 0, + $liveAtomicWaitAsyncCounter__internal: true, + + emscripten_atomic_wait_async__deps: ['$atomicWaitStates', '$liveAtomicWaitAsyncs', '$liveAtomicWaitAsyncCounter', '$polyfillWaitAsync', '$callUserCallback'], + emscripten_atomic_wait_async: (addr, val, asyncWaitFinished, userData, maxWaitMilliseconds) => { + let wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds); + if (!wait.async) return atomicWaitStates.indexOf(wait.value); + // Increment waitAsync generation counter, account for wraparound in case + // application does huge amounts of waitAsyncs per second (not sure if + // possible?) + // Valid counter range: 0...2^31-1 + let counter = liveAtomicWaitAsyncCounter; + liveAtomicWaitAsyncCounter = Math.max(0, (liveAtomicWaitAsyncCounter+1)|0); + liveAtomicWaitAsyncs[counter] = addr; + {{{ runtimeKeepalivePush() }}} + wait.value.then((value) => { + if (liveAtomicWaitAsyncs[counter]) { + {{{ runtimeKeepalivePop() }}} + delete liveAtomicWaitAsyncs[counter]; + callUserCallback(() => {{{ makeDynCall('vpiip', 'asyncWaitFinished') }}}(addr, val, atomicWaitStates.indexOf(value), userData)); + } + }); + return -counter; + }, + + emscripten_atomic_cancel_wait_async__deps: ['$liveAtomicWaitAsyncs'], + emscripten_atomic_cancel_wait_async: (waitToken) => { +#if ASSERTIONS + if (waitToken == {{{ cDefs.ATOMICS_WAIT_NOT_EQUAL }}}) { + warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_NOT_EQUAL (1) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); + } else if (waitToken == {{{ cDefs.ATOMICS_WAIT_TIMED_OUT }}}) { + warnOnce('Attempted to call emscripten_atomic_cancel_wait_async() with a value ATOMICS_WAIT_TIMED_OUT (2) that is not a valid wait token! Check success in return value from call to emscripten_atomic_wait_async()'); + } else if (waitToken > 0) { + warnOnce(`Attempted to call emscripten_atomic_cancel_wait_async() with an invalid wait token value ${waitToken}`); + } +#endif + var address = liveAtomicWaitAsyncs[waitToken]; + if (address) { + // Notify the waitAsync waiters on the memory location, so that JavaScript + // garbage collection can occur. + // See https://github.com/WebAssembly/threads/issues/176 + // This has the unfortunate effect of causing spurious wakeup of all other + // waiters at the address (which causes a small performance loss). + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); + delete liveAtomicWaitAsyncs[waitToken]; + {{{ runtimeKeepalivePop() }}} + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + } + // This waitToken does not exist. + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + }, + + emscripten_atomic_cancel_all_wait_asyncs__deps: ['$liveAtomicWaitAsyncs'], + emscripten_atomic_cancel_all_wait_asyncs: () => { + let waitAsyncs = Object.values(liveAtomicWaitAsyncs); + for (var address of waitAsyncs) { + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); + } + liveAtomicWaitAsyncs = {}; + return waitAsyncs.length; + }, + + emscripten_atomic_cancel_all_wait_asyncs_at_address__deps: ['$liveAtomicWaitAsyncs'], + emscripten_atomic_cancel_all_wait_asyncs_at_address: (address) => { + let numCancelled = 0; + for (var [waitToken, waitAddress] of Object.entries(liveAtomicWaitAsyncs)) { + if (waitAddress == address) { + Atomics.notify(HEAP32, {{{ getHeapOffset('address', 'i32') }}}); + delete liveAtomicWaitAsyncs[waitToken]; + numCancelled++; + } + } + return numCancelled; + }, + + emscripten_has_threading_support: () => !!globalThis.SharedArrayBuffer, + + emscripten_num_logical_cores: () => +#if ENVIRONMENT_MAY_BE_NODE + ENVIRONMENT_IS_NODE ? require('node:os').cpus().length : +#endif + navigator['hardwareConcurrency'], + + emscripten_atomics_is_lock_free: (width) => Atomics.isLockFree(width), +}); diff --git a/src/lib/libautodebug.js b/src/lib/libautodebug.js new file mode 100644 index 0000000000000..461201c02d184 --- /dev/null +++ b/src/lib/libautodebug.js @@ -0,0 +1,166 @@ +/** + * @license + * Copyright 2022 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + + +#if !AUTODEBUG +#error "Should only be included in AUTODEBUG mode" +#endif + +addToLibrary({ + $log_execution: (loc) => dbg('log_execution ' + loc), + $get_i32: (loc, index, value) => { + dbg('get_i32 ' + [loc, index, value]); + return value; + }, + $get_i64__deps: ['setTempRet0'], + $get_i64: (loc, index, low, high) => { + dbg('get_i64 ' + [loc, index, low, high]); + _setTempRet0(high); + return low; + }, + $get_f32: (loc, index, value) => { + dbg('get_f32 ' + [loc, index, value]); + return value; + }, + $get_f64: (loc, index, value) => { + dbg('get_f64 ' + [loc, index, value]); + return value; + }, + $get_funcref: (loc, index, value) => { + dbg('get_funcref ' + [loc, index, value]); + return value; + }, + $get_externref: (loc, index, value) => { + dbg('get_externref ' + [loc, index, value]); + return value; + }, + $get_anyref: (loc, index, value) => { + dbg('get_anyref ' + [loc, index, value]); + return value; + }, + $get_exnref: (loc, index, value) => { + dbg('get_exnref ' + [loc, index, value]); + return value; + }, + $set_i32: (loc, index, value) => { + dbg('set_i32 ' + [loc, index, value]); + return value; + }, + $set_i64__deps: ['setTempRet0'], + $set_i64: (loc, index, low, high) => { + dbg('set_i64 ' + [loc, index, low, high]); + _setTempRet0(high); + return low; + }, + $set_f32: (loc, index, value) => { + dbg('set_f32 ' + [loc, index, value]); + return value; + }, + $set_f64: (loc, index, value) => { + dbg('set_f64 ' + [loc, index, value]); + return value; + }, + $set_funcref: (loc, index, value) => { + dbg('set_funcref ' + [loc, index, value]); + return value; + }, + $set_externref: (loc, index, value) => { + dbg('set_externref ' + [loc, index, value]); + return value; + }, + $set_anyref: (loc, index, value) => { + dbg('set_anyref ' + [loc, index, value]); + return value; + }, + $set_exnref: (loc, index, value) => { + dbg('set_exnref ' + [loc, index, value]); + return value; + }, + $load_ptr: (loc, bytes, offset, ptr) => { + dbg('load_ptr ' + [loc, bytes, offset, ptr]); + return ptr; + }, + $load_val_i32: (loc, value) => { + dbg('load_val_i32 ' + [loc, value]); + return value; + }, + $load_val_i64__deps: ['setTempRet0'], + $load_val_i64: (loc, low, high) => { + dbg('load_val_i64 ' + [loc, low, high]); + _setTempRet0(high); + return low; + }, + $load_val_f32: (loc, value) => { + dbg('load_val_f32 ' + [loc, value]); + return value; + }, + $load_val_f64: (loc, value) => { + dbg('load_val_f64 ' + [loc, value]); + return value; + }, + $store_ptr: (loc, bytes, offset, ptr) => { + dbg('store_ptr ' + [loc, bytes, offset, ptr]); + return ptr; + }, + $store_val_i32: (loc, value) => { + dbg('store_val_i32 ' + [loc, value]); + return value; + }, + $store_val_i64__deps: ['setTempRet0'], + $store_val_i64: (loc, low, high) => { + dbg('store_val_i64 ' + [loc, low, high]); + _setTempRet0(high); + return low; + }, + $store_val_f32: (loc, value) => { + dbg('store_val_f32 ' + [loc, value]); + return value; + }, + $store_val_f64: (loc, value) => { + dbg('store_val_f64 ' + [loc, value]); + return value; + }, + $memory_grow_pre: (loc, delta) => { + dbg('memory_grow_pre ' + [loc, delta]); + return delta; + }, + $memory_grow_post: (loc, result) => { + dbg('memory_grow_post ' + [loc, result]); + return result; + }, +}); + +extraLibraryFuncs.push( + '$log_execution', + '$get_i32', + '$get_i64', + '$get_f32', + '$get_f64', + '$get_funcref', + '$get_externref', + '$get_anyref', + '$get_exnref', + '$set_i32', + '$set_i64', + '$set_f32', + '$set_f64', + '$set_funcref', + '$set_externref', + '$set_anyref', + '$set_exnref', + '$load_ptr', + '$load_val_i32', + '$load_val_i64', + '$load_val_f32', + '$load_val_f64', + '$store_ptr', + '$store_val_i32', + '$store_val_i64', + '$store_val_f32', + '$store_val_f64', + '$memory_grow_pre', + '$memory_grow_post', +); diff --git a/src/lib/libbase64.js b/src/lib/libbase64.js new file mode 100644 index 0000000000000..3f0739e9d155c --- /dev/null +++ b/src/lib/libbase64.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + // Decodes a _known valid_ base64 string (without validation) and returns it as a new Uint8Array. + // Benchmarked to be around 5x faster compared to a simple + // "Uint8Array.from(atob(b64), c => c.charCodeAt(0))" (TODO: perhaps use this form in -Oz builds?) +#if !JS_BASE64_API + $base64Decode__postset: ` + // Precreate a reverse lookup table from chars + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" back to + // bytes to make decoding fast. + for (var base64ReverseLookup = new Uint8Array(123/*'z'+1*/), __b64i = 25; __b64i >= 0; --__b64i) { + base64ReverseLookup[48+__b64i] = 52+__b64i; // '0-9' + base64ReverseLookup[65+__b64i] = __b64i; // 'A-Z' + base64ReverseLookup[97+__b64i] = 26+__b64i; // 'a-z' + } + base64ReverseLookup[43] = 62; // '+' + base64ReverseLookup[47] = 63; // '/' +`, +#endif + $base64Decode__docs: '/** @noinline */', + $base64Decode: (b64) => { +#if JS_BASE64_API + return Uint8Array.fromBase64(b64); +#else +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + var buf = Buffer.from(b64, 'base64'); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); + } +#endif + +#if ASSERTIONS + assert(b64.length % 4 == 0); +#endif + var b1, b2, i = 0, j = 0, bLength = b64.length; + var output = new Uint8Array((bLength*3>>2) - (b64[bLength-2] == '=') - (b64[bLength-1] == '=')); + for (; i < bLength; i += 4, j += 3) { + b1 = base64ReverseLookup[b64.charCodeAt(i+1)]; + b2 = base64ReverseLookup[b64.charCodeAt(i+2)]; + output[j] = base64ReverseLookup[b64.charCodeAt(i)] << 2 | b1 >> 4; + output[j+1] = b1 << 4 | b2 >> 2; + output[j+2] = b2 << 6 | base64ReverseLookup[b64.charCodeAt(i+3)]; + } + return output; +#endif + }, +}); diff --git a/src/lib/libbootstrap.js b/src/lib/libbootstrap.js new file mode 100644 index 0000000000000..84f8ef467ac77 --- /dev/null +++ b/src/lib/libbootstrap.js @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2015 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// When bootstrapping struct info, we can't use the full library because +// it itself depends on the struct info information. + +#if !BOOTSTRAPPING_STRUCT_INFO +assert(false, "libbootstrap.js only designed for use with BOOTSTRAPPING_STRUCT_INFO") +#endif + +assert(Object.keys(LibraryManager.library).length === 0); +addToLibrary({ + $callRuntimeCallbacks: () => {}, + + $HEAP8: undefined, + $HEAPU8: undefined, + $HEAP16: undefined, + $HEAPU16: undefined, + $HEAP32: undefined, + $HEAPU32: undefined, + $HEAPF32: undefined, + $HEAPF64: undefined, +#if WASM_BIGINT + $HEAP64: undefined, + $HEAPU64: undefined, +#endif + + $wasmMemory: 'memory', + + $ExitStatus: class { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + }, + + $exitJS__deps: ['$ExitStatus'], + $exitJS: (code) => quit_(code, new ExitStatus(code)), + + $handleException: (e) => { + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + quit_(1, e); + }, + + fd_write__sig: 'iippp', + fd_write: (fd, iov, iovcnt, pnum) => { + // implementation almost copied from libwasi.js one for SYSCALLS_REQUIRE_FILESYSTEM=0 + // (the only difference is that we can't use C_STRUCTS here) + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = {{{ makeGetValue('iov', 0, '*') }}}; + var len = {{{ makeGetValue('iov', POINTER_SIZE, '*') }}}; + iov += {{{ POINTER_SIZE }}} * 2; + process.stdout.write(HEAPU8.subarray(ptr, ptr + len)); + num += len; + } + {{{ makeSetValue('pnum', 0, 'num', '*') }}}; + return 0; + }, +}); diff --git a/src/lib/libbrowser.js b/src/lib/libbrowser.js new file mode 100644 index 0000000000000..0b9a043776edb --- /dev/null +++ b/src/lib/libbrowser.js @@ -0,0 +1,886 @@ +/** + * @license + * Copyright 2011 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// Utilities for browser environments +var LibraryBrowser = { + $Browser__deps: [ + '$callUserCallback', + '$getFullscreenElement', + '$safeSetTimeout', + '$warnOnce', +#if FILESYSTEM + '$preloadPlugins', +#if MAIN_MODULE + '$preloadedWasm', +#endif +#endif + ], + + $Browser: { + useWebGL: false, + isFullscreen: false, + pointerLock: false, + moduleContextCreatedCallbacks: [], + workers: [], + preloadedImages: {}, + preloadedAudios: {}, + + getCanvas: () => Module['canvas'], + + init() { + if (Browser.initted) return; + Browser.initted = true; + +#if FILESYSTEM + // Support for plugins that can process preloaded files. You can add more of these to + // your app by creating and appending to preloadPlugins. + // + // Each plugin is asked if it can handle a file based on the file's name. If it can, + // it is given the file's raw data. When it is done, it calls a callback with the file's + // (possibly modified) data. For example, a plugin might decompress a file, or it + // might create some side data structure for use later (like an Image element, etc.). + + var imagePlugin = {}; + imagePlugin['canHandle'] = (name) => { + return !Module['noImageDecoding'] && /\.(jpg|jpeg|png|bmp|webp)$/i.test(name); + }; + imagePlugin['handle'] = async (byteArray, name) => { + var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); + if (b.size !== byteArray.length) { // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) }); + } + var url = URL.createObjectURL(b); + return new Promise((resolve, reject) => { + var img = new Image(); + img.onload = () => { +#if ASSERTIONS + assert(img.complete, `Image ${name} could not be decoded`); +#endif + var canvas = /** @type {!HTMLCanvasElement} */ (document.createElement('canvas')); + canvas.width = img.width; + canvas.height = img.height; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + Browser.preloadedImages[name] = canvas; + URL.revokeObjectURL(url); + resolve(byteArray); + }; + img.onerror = (event) => { + err(`Image ${url} could not be decoded`); + reject(); + }; + img.src = url; + }); + }; + preloadPlugins.push(imagePlugin); + + var audioPlugin = {}; + audioPlugin['canHandle'] = (name) => { + return !Module['noAudioDecoding'] && name.slice(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; + }; + audioPlugin['handle'] = async (byteArray, name) => { + return new Promise((resolve, reject) => { + var done = false; + function finish(audio) { + if (done) return; + done = true; + Browser.preloadedAudios[name] = audio; + resolve(byteArray); + } + var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); + var url = URL.createObjectURL(b); // XXX we never revoke this! + var audio = new Audio(); + audio.addEventListener('canplaythrough', () => finish(audio), false); // use addEventListener due to chromium bug 124926 + audio.onerror = (event) => { + if (done) return; + err(`warning: browser could not fully decode audio ${name}, trying slower base64 approach`); + function encode64(data) { + var BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + var PAD = '='; + var ret = ''; + var leftchar = 0; + var leftbits = 0; + for (var i = 0; i < data.length; i++) { + leftchar = (leftchar << 8) | data[i]; + leftbits += 8; + while (leftbits >= 6) { + var curr = (leftchar >> (leftbits-6)) & 0x3f; + leftbits -= 6; + ret += BASE[curr]; + } + } + if (leftbits == 2) { + ret += BASE[(leftchar&3) << 4]; + ret += PAD + PAD; + } else if (leftbits == 4) { + ret += BASE[(leftchar&0xf) << 2]; + ret += PAD; + } + return ret; + } + audio.src = 'data:audio/x-' + name.slice(-3) + ';base64,' + encode64(byteArray); + finish(audio); // we don't wait for confirmation this worked - but it's worth trying + }; + audio.src = url; + // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror + safeSetTimeout(() => { + finish(audio); // try to use it even though it is not necessarily ready to play + }, 10000); + }); + }; + preloadPlugins.push(audioPlugin); +#endif + + // Canvas event setup + + function pointerLockChange() { + var canvas = Browser.getCanvas(); + Browser.pointerLock = document.pointerLockElement === canvas; + } + var canvas = Browser.getCanvas(); + if (canvas) { + // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module + // Module['forcedAspectRatio'] = 4 / 3; + + document.addEventListener('pointerlockchange', pointerLockChange, false); + + if (Module['elementPointerLock']) { + canvas.addEventListener("click", (ev) => { + if (!Browser.pointerLock && Browser.getCanvas().requestPointerLock) { + Browser.getCanvas().requestPointerLock(); + ev.preventDefault(); + } + }, false); + } + } + }, + + createContext(/** @type {HTMLCanvasElement} */ canvas, useWebGL, setInModule, webGLContextAttributes) { + if (useWebGL && Module['ctx'] && canvas == Browser.getCanvas()) return Module['ctx']; // no need to recreate GL context if it's already been created for this canvas. + + var ctx; + var contextHandle; + if (useWebGL) { + // For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults. + var contextAttributes = { + antialias: false, + alpha: false, +#if MIN_WEBGL_VERSION >= 2 + majorVersion: 2, +#elif MAX_WEBGL_VERSION >= 2 // libbrowser.js defaults: use the WebGL version chosen at compile time (unless overridden below) + majorVersion: (typeof WebGL2RenderingContext != 'undefined') ? 2 : 1, +#else + majorVersion: 1, +#endif + }; + + if (webGLContextAttributes) { + for (var attribute in webGLContextAttributes) { + contextAttributes[attribute] = webGLContextAttributes[attribute]; + } + } + + // This check of existence of GL is here to satisfy Closure compiler, which yells if variable GL is referenced below but GL object is not + // actually compiled in because application is not doing any GL operations. TODO: Ideally if GL is not being used, this function + // Browser.createContext() should not even be emitted. + if (typeof GL != 'undefined') { + contextHandle = GL.createContext(canvas, contextAttributes); + if (contextHandle) { + ctx = GL.getContext(contextHandle).GLctx; + } + } + } else { + ctx = canvas.getContext('2d'); + } + + if (!ctx) return null; + + if (setInModule) { +#if ASSERTIONS + if (!useWebGL) assert(typeof GLctx == 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it'); +#endif + Module['ctx'] = ctx; + if (useWebGL) GL.makeContextCurrent(contextHandle); + Browser.useWebGL = useWebGL; + Browser.moduleContextCreatedCallbacks.forEach((callback) => callback()); + Browser.init(); + } + return ctx; + }, + + fullscreenHandlersInstalled: false, + lockPointer: undefined, + resizeCanvas: undefined, + requestFullscreen(lockPointer, resizeCanvas) { + Browser.lockPointer = lockPointer; + Browser.resizeCanvas = resizeCanvas; + if (typeof Browser.lockPointer == 'undefined') Browser.lockPointer = true; + if (typeof Browser.resizeCanvas == 'undefined') Browser.resizeCanvas = false; + + var canvas = Browser.getCanvas(); + function fullscreenChange() { + Browser.isFullscreen = false; + var canvasContainer = canvas.parentNode; + if (getFullscreenElement() === canvasContainer) { + canvas.exitFullscreen = Browser.exitFullscreen; + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullscreen = true; + if (Browser.resizeCanvas) { + Browser.setFullscreenCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + } + } else { + // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen + canvasContainer.parentNode.insertBefore(canvas, canvasContainer); + canvasContainer.parentNode.removeChild(canvasContainer); + + if (Browser.resizeCanvas) { + Browser.setWindowedCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + } + } + Module['onFullScreen']?.(Browser.isFullscreen); + Module['onFullscreen']?.(Browser.isFullscreen); + } + + if (!Browser.fullscreenHandlersInstalled) { + Browser.fullscreenHandlersInstalled = true; + document.addEventListener('fullscreenchange', fullscreenChange, false); + document.addEventListener('mozfullscreenchange', fullscreenChange, false); + document.addEventListener('webkitfullscreenchange', fullscreenChange, false); + document.addEventListener('MSFullscreenChange', fullscreenChange, false); + } + + // 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 + var canvasContainer = document.createElement("div"); + canvas.parentNode.insertBefore(canvasContainer, canvas); + canvasContainer.appendChild(canvas); + + // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) + canvasContainer.requestFullscreen = canvasContainer['requestFullscreen'] || + canvasContainer['mozRequestFullScreen'] || + canvasContainer['msRequestFullscreen'] || + (canvasContainer['webkitRequestFullscreen'] ? () => canvasContainer['webkitRequestFullscreen'](Element['ALLOW_KEYBOARD_INPUT']) : null) || + (canvasContainer['webkitRequestFullScreen'] ? () => canvasContainer['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) : null); + + canvasContainer.requestFullscreen(); + }, + +#if ASSERTIONS + requestFullScreen() { + abort('Module.requestFullScreen has been replaced by Module.requestFullscreen (without a capital S)'); + }, +#endif + + exitFullscreen() { + // This is workaround for chrome. Trying to exit from fullscreen + // not in fullscreen state will cause "TypeError: Document not active" + // in chrome. See https://github.com/emscripten-core/emscripten/pull/8236 + if (!Browser.isFullscreen) { + return false; + } + + var CFS = document['exitFullscreen'] || + document['cancelFullScreen'] || + document['mozCancelFullScreen'] || + document['msExitFullscreen'] || + document['webkitCancelFullScreen'] || + (() => {}); + CFS.apply(document, []); + return true; + }, + + // abort and pause-aware versions TODO: build main loop on top of this? + + safeSetTimeout(func, timeout) { + // Legacy function, this is used by the SDL2 port so we need to keep it + // around at least until that is updated. + // See https://github.com/libsdl-org/SDL/pull/6304 + return safeSetTimeout(func, timeout); + }, + + getMimetype(name) { + return { + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'bmp': 'image/bmp', + 'ogg': 'audio/ogg', + 'wav': 'audio/wav', + 'mp3': 'audio/mpeg' + }[name.slice(name.lastIndexOf('.')+1)]; + }, + + getUserMedia(func) { + window.getUserMedia ||= navigator['getUserMedia'] || + navigator['mozGetUserMedia']; + window.getUserMedia(func); + }, + + + getMovementX(event) { + return event['movementX'] || + event['mozMovementX'] || + event['webkitMovementX'] || + 0; + }, + + getMovementY(event) { + return event['movementY'] || + event['mozMovementY'] || + event['webkitMovementY'] || + 0; + }, + + // Browsers specify wheel direction according to the page CSS pixel Y direction: + // Scrolling mouse wheel down (==towards user/away from screen) on Windows/Linux (and macOS without 'natural scroll' enabled) + // is the positive wheel direction. Scrolling mouse wheel up (towards the screen) is the negative wheel direction. + // This function returns the wheel direction in the browser page coordinate system (+: down, -: up). Note that this is often the + // opposite of native code: In native APIs the positive scroll direction is to scroll up (away from the user). + // NOTE: The mouse wheel delta is a decimal number, and can be a fractional value within -1 and 1. If you need to represent + // this as an integer, don't simply cast to int, or you may receive scroll events for wheel delta == 0. + // NOTE: We convert all units returned by events into steps, i.e. individual wheel notches. + // These conversions are only approximations. Changing browsers, operating systems, or even settings can change the values. + getMouseWheelDelta(event) { + var delta = 0; + switch (event.type) { + case 'DOMMouseScroll': + // 3 lines make up a step + delta = event.detail / 3; + break; + case 'mousewheel': + // 120 units make up a step + delta = event.wheelDelta / 120; + break; + case 'wheel': + delta = event.deltaY + switch (event.deltaMode) { + case 0: + // DOM_DELTA_PIXEL: 100 pixels make up a step + delta /= 100; + break; + case 1: + // DOM_DELTA_LINE: 3 lines make up a step + delta /= 3; + break; + case 2: + // DOM_DELTA_PAGE: A page makes up 80 steps + delta *= 80; + break; + default: + abort('unrecognized mouse wheel delta mode: ' + event.deltaMode); + } + break; + default: + abort('unrecognized mouse wheel event: ' + event.type); + } + return delta; + }, + + mouseX: 0, + mouseY: 0, + mouseMovementX: 0, + mouseMovementY: 0, + touches: {}, + lastTouches: {}, + + // Return the mouse coordinates relative to the top, left of the canvas, corrected for scroll offset. + calculateMouseCoords(pageX, pageY) { + // Calculate the movement based on the changes + // in the coordinates. + var canvas = Browser.getCanvas(); + var rect = canvas.getBoundingClientRect(); + + // Neither .scrollX or .pageXOffset are defined in a spec, but + // we prefer .scrollX because it is currently in a spec draft. + // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) + var scrollX = ((typeof window.scrollX != 'undefined') ? window.scrollX : window.pageXOffset); + var scrollY = ((typeof window.scrollY != 'undefined') ? window.scrollY : window.pageYOffset); +#if ASSERTIONS + // If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset + // and we have no viable fallback. + assert((typeof scrollX != 'undefined') && (typeof scrollY != 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.'); +#endif + var adjustedX = pageX - (scrollX + rect.left); + var adjustedY = pageY - (scrollY + rect.top); + + // the canvas might be CSS-scaled compared to its backbuffer; + // SDL-using content will want mouse coordinates in terms + // of backbuffer units. + adjustedX = adjustedX * (canvas.width / rect.width); + adjustedY = adjustedY * (canvas.height / rect.height); + + return { x: adjustedX, y: adjustedY }; + }, + + // Directly set the Browser state with new mouse coordinates calculated using calculateMouseCoords. + setMouseCoords(pageX, pageY) { + const {x, y} = Browser.calculateMouseCoords(pageX, pageY); + Browser.mouseMovementX = x - Browser.mouseX; + Browser.mouseMovementY = y - Browser.mouseY; + Browser.mouseX = x; + Browser.mouseY = y; + }, + + // Unpack a "mouse" event, handling SDL touch paths and pointerlock compatibility stuff. + calculateMouseEvent(event) { // event should be mousemove, mousedown or mouseup + if (Browser.pointerLock) { + // When the pointer is locked, calculate the coordinates + // based on the movement of the mouse. + // Workaround for Firefox bug 764498 + if (event.type != 'mousemove' && + ('mozMovementX' in event)) { + Browser.mouseMovementX = Browser.mouseMovementY = 0; + } else { + Browser.mouseMovementX = Browser.getMovementX(event); + Browser.mouseMovementY = Browser.getMovementY(event); + } + + // add the mouse delta to the current absolute mouse position + Browser.mouseX += Browser.mouseMovementX; + Browser.mouseY += Browser.mouseMovementY; + } else { + if (event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchmove') { + var touch = event.touch; + if (touch === undefined) { + return; // the "touch" property is only defined in SDL + + } + var coords = Browser.calculateMouseCoords(touch.pageX, touch.pageY); + + if (event.type === 'touchstart') { + Browser.lastTouches[touch.identifier] = coords; + Browser.touches[touch.identifier] = coords; + } else if (event.type === 'touchend' || event.type === 'touchmove') { + var last = Browser.touches[touch.identifier]; + last ||= coords; + Browser.lastTouches[touch.identifier] = last; + Browser.touches[touch.identifier] = coords; + } + return; + } + + Browser.setMouseCoords(event.pageX, event.pageY); + } + }, + + resizeListeners: [], + + updateResizeListeners() { + var canvas = Browser.getCanvas(); + Browser.resizeListeners.forEach((listener) => listener(canvas.width, canvas.height)); + }, + + setCanvasSize(width, height, noUpdates) { + var canvas = Browser.getCanvas(); + Browser.updateCanvasDimensions(canvas, width, height); + if (!noUpdates) Browser.updateResizeListeners(); + }, + + windowedWidth: 0, + windowedHeight: 0, + setFullscreenCanvasSize() { + // check if SDL is available + if (typeof SDL != "undefined") { + var flags = {{{ makeGetValue('SDL.screen', '0', 'u32') }}}; + flags = flags | 0x00800000; // set SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}}; + } + Browser.updateCanvasDimensions(Browser.getCanvas()); + Browser.updateResizeListeners(); + }, + + setWindowedCanvasSize() { + // check if SDL is available + if (typeof SDL != "undefined") { + var flags = {{{ makeGetValue('SDL.screen', '0', 'u32') }}}; + flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}}; + } + Browser.updateCanvasDimensions(Browser.getCanvas()); + Browser.updateResizeListeners(); + }, + + updateCanvasDimensions(canvas, wNative, hNative) { + if (wNative && hNative) { + canvas.widthNative = wNative; + canvas.heightNative = hNative; + } else { + wNative = canvas.widthNative; + hNative = canvas.heightNative; + } + var w = wNative; + var h = hNative; + if (Module['forcedAspectRatio'] > 0) { + if (w/h < Module['forcedAspectRatio']) { + w = Math.round(h * Module['forcedAspectRatio']); + } else { + h = Math.round(w / Module['forcedAspectRatio']); + } + } + if ((getFullscreenElement() === canvas.parentNode) && (typeof screen != 'undefined')) { + var factor = Math.min(screen.width / w, screen.height / h); + w = Math.round(w * factor); + h = Math.round(h * factor); + } + if (Browser.resizeCanvas) { + if (canvas.width != w) canvas.width = w; + if (canvas.height != h) canvas.height = h; + if (typeof canvas.style != 'undefined') { + canvas.style.removeProperty( "width"); + canvas.style.removeProperty("height"); + } + } else { + if (canvas.width != wNative) canvas.width = wNative; + if (canvas.height != hNative) canvas.height = hNative; + if (typeof canvas.style != 'undefined') { + if (w != wNative || h != hNative) { + canvas.style.setProperty( "width", w + "px", "important"); + canvas.style.setProperty("height", h + "px", "important"); + } else { + canvas.style.removeProperty( "width"); + canvas.style.removeProperty("height"); + } + } + } + }, + }, + + $requestFullscreen: 'Browser.requestFullscreen', +#if ASSERTIONS + $requestFullScreen: 'Browser.requestFullScreen', +#endif + $setCanvasSize: 'Browser.setCanvasSize', + $getUserMedia: 'Browser.getUserMedia', + $createContext: 'Browser.createContext', + + emscripten_run_preload_plugins__deps: ['$PATH'], + emscripten_run_preload_plugins__proxy: 'sync', + emscripten_run_preload_plugins: (file, onload, onerror) => { + {{{ runtimeKeepalivePush() }}} + + var _file = UTF8ToString(file); + var data = FS.analyzePath(_file); + if (!data.exists) return -1; + // Here we assume data.object.contents is a TypedArray. +#if ASSERTIONS + assert(data.object.contents.subarray, 'unexpected file content') +#endif + FS.createPreloadedFile( + PATH.dirname(_file), + PATH.basename(_file), + data.object.contents, /*canRead=*/true, /*canWrite=*/true, + () => { + {{{ runtimeKeepalivePop() }}} + if (onload) {{{ makeDynCall('vp', 'onload') }}}(file); + }, + () => { + {{{ runtimeKeepalivePop() }}} + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(file); + }, + /*dontCreateFile=*/true // it's already there + ); + return 0; + }, + + $Browser_asyncPrepareDataCounter: 0, + + emscripten_run_preload_plugins_data__proxy: 'sync', + emscripten_run_preload_plugins_data__deps: ['$stringToNewUTF8', '$Browser_asyncPrepareDataCounter'], + emscripten_run_preload_plugins_data: (data, size, suffix, arg, onload, onerror) => { + {{{ runtimeKeepalivePush() }}} + + suffix = UTF8ToString(suffix); + var name = `prepare_data_${Browser_asyncPrepareDataCounter++}.${suffix}`; + var cname = stringToNewUTF8(name); + FS.createPreloadedFile( + '/', + name, + {{{ makeHEAPView('U8', 'data', 'data + size') }}}, + true, true, + () => { + {{{ runtimeKeepalivePop() }}} + if (onload) {{{ makeDynCall('vpp', 'onload') }}}(arg, cname); + }, + () => { + {{{ runtimeKeepalivePop() }}} + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + }, + true // don'tCreateFile - it's already there + ); + }, + + // Callable from pthread, executes in pthread context. + emscripten_async_run_script__deps: ['emscripten_run_script', '$safeSetTimeout'], + emscripten_async_run_script: (script, millis) => { + // TODO: cache these to avoid generating garbage + safeSetTimeout(() => _emscripten_run_script(script), millis); + }, + + // TODO: currently not callable from a pthread, but immediately calls onerror() if not on main thread. + emscripten_async_load_script__deps: ['$UTF8ToString'], + emscripten_async_load_script: async (url, onload, onerror) => { + url = UTF8ToString(url); +#if PTHREADS + if (ENVIRONMENT_IS_PTHREAD) { + err(`emscripten_async_load_script("${url}") failed, emscripten_async_load_script is currently not available in pthreads!`); + onerror && {{{ makeDynCall('v', 'onerror') }}}(); + return; + } +#endif +#if ASSERTIONS + assert(runDependencies === 0, 'async_load_script must be run when no other dependencies are active'); +#endif + {{{ runtimeKeepalivePush() }}} + + var loadDone = () => { + {{{ runtimeKeepalivePop() }}} + if (onload) { + var onloadCallback = () => callUserCallback({{{ makeDynCall('v', 'onload') }}}); + if (runDependencies > 0) { + dependenciesFulfilled = onloadCallback; + } else { + onloadCallback(); + } + } + } + + var loadError = () => { + {{{ runtimeKeepalivePop() }}} + if (onerror) { + callUserCallback({{{ makeDynCall('v', 'onerror') }}}); + } + }; + +#if ENVIRONMENT_MAY_BE_NODE && DYNAMIC_EXECUTION + if (ENVIRONMENT_IS_NODE) { + try { + var data = await readAsync(url, false); + eval(data); + loadDone(); + } catch (e) { + err(e); + loadError(); + } + return; + } +#endif + + var script = document.createElement('script'); + script.onload = loadDone; + script.onerror = loadError; + script.src = url; + document.body.appendChild(script); + }, + + emscripten_get_window_title__proxy: 'sync', + emscripten_get_window_title: () => { + var buflen = 256; + + if (!_emscripten_get_window_title.buffer) { + _emscripten_get_window_title.buffer = _malloc(buflen); + } + + stringToUTF8(document.title, _emscripten_get_window_title.buffer, buflen); + + return _emscripten_get_window_title.buffer; + }, + + emscripten_set_window_title__proxy: 'sync', + emscripten_set_window_title: (title) => document.title = UTF8ToString(title), + + emscripten_get_screen_size__proxy: 'sync', + emscripten_get_screen_size: (width, height) => { + {{{ makeSetValue('width', '0', 'screen.width', 'i32') }}}; + {{{ makeSetValue('height', '0', 'screen.height', 'i32') }}}; + }, + + emscripten_hide_mouse__proxy: 'sync', + emscripten_hide_mouse: () => { + var styleSheet = document.styleSheets[0]; + var rules = styleSheet.cssRules; + for (var i = 0; i < rules.length; i++) { + if (rules[i].cssText.startsWith('canvas')) { + styleSheet.deleteRule(i); + i--; + } + } + styleSheet.insertRule('canvas.emscripten { border: 1px solid black; cursor: none; }', 0); + }, + + emscripten_set_canvas_size__proxy: 'sync', + emscripten_set_canvas_size: (width, height) => Browser.setCanvasSize(width, height), + + emscripten_get_canvas_size__proxy: 'sync', + emscripten_get_canvas_size: (width, height, isFullscreen) => { + var canvas = Browser.getCanvas(); + {{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}}; + {{{ makeSetValue('isFullscreen', '0', 'Browser.isFullscreen ? 1 : 0', 'i32') }}}; + }, + + // To avoid creating worker parent->child chains, always proxies to execute on the main thread. + emscripten_create_worker__proxy: 'sync', + emscripten_create_worker__deps: ['$UTF8ToString', 'realloc'], + emscripten_create_worker: (url) => { + url = UTF8ToString(url); + var id = Browser.workers.length; + var info = { + worker: new Worker(url), + callbacks: [], + awaited: 0, + buffer: 0, + }; + info.worker.onmessage = (msg) => { + if (ABORT) return; + var info = Browser.workers[id]; + if (!info) return; // worker was destroyed meanwhile + var callbackId = msg.data['callbackId']; + var callbackInfo = info.callbacks[callbackId]; + if (!callbackInfo) return; // no callback or callback removed meanwhile + // Don't trash our callback state if we expect additional calls. + if (msg.data['finalResponse']) { + info.awaited--; + info.callbacks[callbackId] = null; // TODO: reuse callbackIds, compress this + {{{ runtimeKeepalivePop() }}} + } + var data = msg.data['data']; + if (data) { + if (!data.byteLength) data = new Uint8Array(data); + info.buffer = _realloc(info.buffer, data.length); + HEAPU8.set(data, info.buffer); + callbackInfo.func(info.buffer, data.length, callbackInfo.arg); + } else { + callbackInfo.func(0, 0, callbackInfo.arg); + } + }; + Browser.workers.push(info); + return id; + }, + + emscripten_destroy_worker__deps: ['free'], + emscripten_destroy_worker__proxy: 'sync', + emscripten_destroy_worker: (id) => { + var info = Browser.workers[id]; + info.worker.terminate(); + _free(info.buffer); + Browser.workers[id] = null; + }, + + emscripten_call_worker__proxy: 'sync', + emscripten_call_worker: (id, funcName, data, size, callback, arg) => { + funcName = UTF8ToString(funcName); + var info = Browser.workers[id]; + var callbackId = -1; + if (callback) { + // If we are waiting for a response from the worker we need to keep + // the runtime alive at least long enough to receive it. + // The corresponding runtimeKeepalivePop is in the `finalResponse` + // handler above. + {{{ runtimeKeepalivePush() }}} + callbackId = info.callbacks.length; + info.callbacks.push({ + func: {{{ makeDynCall('vpip', 'callback') }}}, + arg + }); + info.awaited++; + } + var transferObject = { + 'funcName': funcName, + 'callbackId': callbackId, + 'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0 + }; + if (data) { + info.worker.postMessage(transferObject, [transferObject.data.buffer]); + } else { + info.worker.postMessage(transferObject); + } + }, + +#if BUILD_AS_WORKER + emscripten_worker_respond_provisionally__proxy: 'sync', + emscripten_worker_respond_provisionally: (data, size) => { + if (workerResponded) abort('already responded with final response!'); + var transferObject = { + 'callbackId': workerCallbackId, + 'finalResponse': false, + 'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0 + }; + if (data) { + postMessage(transferObject, [transferObject.data.buffer]); + } else { + postMessage(transferObject); + } + }, + + emscripten_worker_respond__proxy: 'sync', + emscripten_worker_respond: (data, size) => { + if (workerResponded) abort('already responded with final response!'); + workerResponded = true; + var transferObject = { + 'callbackId': workerCallbackId, + 'finalResponse': true, + 'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0 + }; + if (data) { + postMessage(transferObject, [transferObject.data.buffer]); + } else { + postMessage(transferObject); + } + }, +#endif + + emscripten_get_worker_queue_size__proxy: 'sync', + emscripten_get_worker_queue_size: (id) => { + var info = Browser.workers[id]; + if (!info) return -1; + return info.awaited; + }, + + emscripten_get_preloaded_image_data__deps: ['$getPreloadedImageData', '$UTF8ToString'], + emscripten_get_preloaded_image_data__proxy: 'sync', + emscripten_get_preloaded_image_data: (path, w, h) => getPreloadedImageData(UTF8ToString(path), w, h), + + $getPreloadedImageData__internal: true, + $getPreloadedImageData__data: ['$PATH_FS', 'malloc'], + $getPreloadedImageData: (path, w, h) => { + path = PATH_FS.resolve(path); + + var canvas = /** @type {HTMLCanvasElement} */(Browser.preloadedImages[path]); + if (!canvas) return 0; + + var ctx = canvas.getContext("2d"); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + var buf = _malloc(canvas.width * canvas.height * 4); + + HEAPU8.set(image.data, buf); + + {{{ makeSetValue('w', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('h', '0', 'canvas.height', 'i32') }}}; + return buf; + }, + +#if !WASMFS // WasmFS implements this in wasm + emscripten_get_preloaded_image_data_from_FILE__deps: ['$getPreloadedImageData', 'fileno'], + emscripten_get_preloaded_image_data_from_FILE__proxy: 'sync', + emscripten_get_preloaded_image_data_from_FILE: (file, w, h) => { + var fd = _fileno(file); + var stream = FS.getStream(fd); + if (stream) { + return getPreloadedImageData(stream.path, w, h); + } + + return 0; + } +#endif +}; + +autoAddDeps(LibraryBrowser, '$Browser'); + +addToLibrary(LibraryBrowser); diff --git a/src/lib/libc_preprocessor.js b/src/lib/libc_preprocessor.js new file mode 100644 index 0000000000000..56259599f6889 --- /dev/null +++ b/src/lib/libc_preprocessor.js @@ -0,0 +1,318 @@ +addToLibrary({ + // Removes all C++ '//' and '/* */' comments from the given source string. + // N.b. will also eat comments inside strings. + $remove_cpp_comments_in_shaders: (code) => { + var i = 0, out = '', ch, next, len = code.length; + for (; i < len; ++i) { + ch = code[i]; + if (ch == '/') { + next = code[i+1]; + if (next == '/') { + while (i < len && code[i+1] != '\n') ++i; + } else if (next == '*') { + while (i < len && (code[i-1] != '*' || code[i] != '/')) ++i; + } else { + out += ch; + } + } else { + out += ch; + } + } + return out; + }, + + // Finds the index of closing parens from the opening parens at arr[i]. + // Used polymorphically for strings ("foo") and token arrays (['(', 'foo', ')']) as input. + $find_closing_parens_index: (arr, i, opening='(', closing=')') => { + for (var nesting = 0; i < arr.length; ++i) { + if (arr[i] == opening) ++nesting; + if (arr[i] == closing && --nesting == 0) { + return i; + } + } + }, + + // Runs C preprocessor algorithm on the given string 'code'. + // Supported preprocessor directives: #if, #ifdef, #ifndef, #else, #elif, #endif, #define and #undef. + // predefs: Specifies a dictionary of { 'key1': function(arg0, arg1) {...}, 'key2': ... } of predefined preprocessing variables + $preprocess_c_code__deps: ['$find_closing_parens_index'], + $preprocess_c_code: function(code, defs = {}) { + var i = 0, // iterator over the input string + len = code.length, // cache input length + out = '', // generates the preprocessed output string + stack = [1]; // preprocessing stack (state of active/inactive #ifdef/#else blocks we are currently inside) + // a mapping 'symbolname' -> function(args) which evaluates the given cpp macro, e.g. #define FOO(x) x+10. + defs['defined'] = (args) => { // built-in "#if defined(x)"" macro. +#if ASSERTIONS + assert(args.length == 1); + assert(/^[A-Za-z0-9_$]+$/.test(args[0].trim())); // Test that a C preprocessor identifier contains only valid characters (we likely parsed wrong if this fails) +#endif + return defs[args[0].trim()] ? 1 : 0; + }; + + // Returns true if str[i] is whitespace. + function isWhitespace(str, i) { + return !(str.charCodeAt(i) > 32); // Compare as negation to treat end-of-string undefined as whitespace + } + + // Returns index to the next whitespace character starting at str[i]. + function nextWhitespace(str, i) { + while (!isWhitespace(str, i)) ++i; + return i; + } + + // Returns an integer ID classification of the character at str[idx], used for tokenization purposes. + function classifyChar(str, idx) { + var cc = str.charCodeAt(idx); + #if ASSERTIONS + assert(!(cc > 127), "Only 7-bit ASCII can be used in preprocessor #if/#ifdef/#define statements!"); + #endif + if (cc > 32) { + if (cc < 48) return 1; // an operator symbol, any of !"#$%&'()*+,-./ + if (cc < 58) return 2; // a number 0123456789 + if (cc < 65) return 1; // an operator symbol, any of :;<=>?@ + if (cc < 91 || cc == 95/*_*/) return 3; // a character, any of A-Z or _ + if (cc < 97) return 1; // an operator symbol, any of [\]^` + if (cc < 123) return 3; // a character, any of a-z + return 1; // an operator symbol, any of {|}~ + } + return cc < 33 ? 0 : 4; // 0=whitespace, 4=end-of-string + } + + // Returns a tokenized array of the given string expression, i.e. "FOO > BAR && BAZ" -> ["FOO", ">", "BAR", "&&", "BAZ"] + // Optionally keeps whitespace as tokens to be able to reconstruct the original input string. + /** + * @param {string} exprString + * @param {(number|boolean)=} keepWhitespace Optional, can be omitted. Defaults to false. + */ + function tokenize(exprString, keepWhitespace) { + var out = [], len = exprString.length; + for (var i = 0; i <= len; ++i) { + var kind = classifyChar(exprString, i); + if (kind == 2/*0-9*/ || kind == 3/*a-z*/) { // a character or a number + for (var j = i+1; j <= len; ++j) { + var kind2 = classifyChar(exprString, j); + if (kind2 != kind && (kind2 != 2/*0-9*/ || kind != 3/*a-z*/)) { // parse number sequence "423410", and identifier sequence "FOO32BAR" + out.push(exprString.substring(i, j)); + i = j-1; + break; + } + } + } else if (kind == 1/*operator symbol*/) { + // Lookahead for two-character operators. + var op2 = exprString.slice(i, i + 2); + if (['<=', '>=', '==', '!=', '&&', '||'].includes(op2)) { + out.push(op2); + ++i; + } else { + out.push(exprString[i]); + } + } + } + return out; + } + + // Expands preprocessing macros on substring str[lineStart...lineEnd] + /** + * @param {string} str + * @param {number} lineStart + * @param {number=} lineEnd Optional, may be omitted. + */ + function expandMacros(str, lineStart, lineEnd=str.length) { + var len = str.length; + var out = ''; + for (var i = lineStart; i < lineEnd; ++i) { + var kind = classifyChar(str, i); + if (kind == 3/*a-z*/) { + for (var j = i + 1; j <= lineEnd; ++j) { + var kind2 = classifyChar(str, j); + if (kind2 != 2/*0-9*/ && kind2 != 3/*a-z*/) { + var symbol = str.substring(i, j); + if (Object.hasOwn(defs, symbol)) { + var pp = defs[symbol], expanded; + if (typeof pp == 'function') { // definition is a function? + if (pp.length) { // Expanding a macro? (#define FOO(X) ...) + while (str[j] && isWhitespace(str, j)) ++j; + if (str[j] == '(') { + var closeParens = find_closing_parens_index(str, j); + // N.b. this has a limitation that multiparameter macros cannot nest with other multiparameter macros + // e.g. FOO(a, BAR(b, c)) is not supported. + expanded = pp(str.substring(j+1, closeParens).split(',')); + if (expanded === !!expanded) expanded = expanded|0; // Convert boolean true/false to int 1/0 + j = closeParens+1; + } else { + var start = j; + j = nextWhitespace(str, j); + expanded = pp([str.substring(start, j)]); + } + } else { // A zero-arg function macro (#define FOO() BAR)? + expanded = pp(); + } + } else { // Definition is either a boolean, an integer or a string.. in any case, not a macro. + // Expand boolean args from defs, e.g. 'FOO': true as integer 1, + // so that further preprocessing won't attempt to search for + // a preprocessing macro 'true' as being defined. + expanded = (pp === !!pp ? pp|0 : pp); + } + return expandMacros(str.substring(lineStart, i) + expanded + str.substring(j, lineEnd), 0); + } + out += symbol; + i = j-1; + break; + } + } + } else { + out += str[i]; + } + } + return out; + } + + // Given a token list e.g. ['2', '>', '1'], returns a function that evaluates that token list. + function buildExprTree(tokens) { + // Consume tokens array into a function tree until the tokens array is exhausted + // to a single root node that evaluates it. + while (tokens.length > 1 || typeof tokens[0] != 'function') { + tokens = ((tokens) => { + // Find the index 'i' of the operator we should evaluate next: + var i, j, p, operatorAndPriority = -2; + for (j = 0; j < tokens.length; ++j) { + if ((p = ['*', '/', '+', '-', '!', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '('].indexOf(tokens[j])) > operatorAndPriority) { + i = j; + operatorAndPriority = p; + } + } + + if (operatorAndPriority == 13 /* parens '(' */) { + // Find the closing parens position + j = find_closing_parens_index(tokens, i); + if (j) { + tokens.splice(i, j+1-i, buildExprTree(tokens.slice(i+1, j))); + return tokens; + } + } + + if (operatorAndPriority == 4 /* unary ! */) { + // Special case: the unary operator ! needs to evaluate right-to-left. + i = tokens.lastIndexOf('!'); + var innerExpr = buildExprTree(tokens.slice(i+1, i+2)); + tokens.splice(i, 2, function() { return !innerExpr(); }) + return tokens; + } + + // A binary operator: + if (operatorAndPriority >= 0) { + var left = buildExprTree(tokens.slice(0, i)); + var right = buildExprTree(tokens.slice(i+1)); + switch(tokens[i]) { + case '&&': return [function() { return left() && right(); }]; + case '||': return [function() { return left() || right(); }]; + case '==': return [function() { return left() == right(); }]; + case '!=': return [function() { return left() != right(); }]; + case '<' : return [function() { return left() < right(); }]; + case '<=': return [function() { return left() <= right(); }]; + case '>' : return [function() { return left() > right(); }]; + case '>=': return [function() { return left() >= right(); }]; + case '+': return [function() { return left() + right(); }]; + case '-': return [function() { return left() - right(); }]; + case '*': return [function() { return left() * right(); }]; + case '/': return [function() { return Math.floor(left() / right()); }]; + } + } + // else a number: +#if ASSERTIONS + assert(tokens[i] !== ')', 'Parsing failure, mismatched parentheses in parsing!' + tokens.toString()); + assert(operatorAndPriority == -1); +#endif + var num = Number(tokens[i]); + return [function() { return num; }] + })(tokens); + } + return tokens[0]; + } + + // Preprocess the input one line at a time. + for (; i < len; ++i) { + // Find the start of the current line. + var lineStart = i; + + // Seek iterator to end of current line. + i = code.indexOf('\n', i); + if (i < 0) i = len; + + // Find the first non-whitespace character on the line. + for (var j = lineStart; j < i && isWhitespace(code, j);) ++j; + + // Is this a non-preprocessor directive line? + var thisLineIsInActivePreprocessingBlock = stack[stack.length-1]; + if (code[j] != '#') { // non-preprocessor line? + if (thisLineIsInActivePreprocessingBlock) { + out += expandMacros(code, lineStart, i) + '\n'; + } + continue; + } + // This is a preprocessor directive line, e.g. #ifdef or #define. + + // Parse the line as # + var space = nextWhitespace(code, j); + var directive = code.substring(j+1, space); + var expression = code.substring(space, i).trim(); + switch(directive) { + case 'if': + var tokens = tokenize(expandMacros(expression, 0)); + var exprTree = buildExprTree(tokens); + var evaluated = exprTree(); + stack.push(!!evaluated * stack[stack.length-1]); + break; + case 'elif': + var tokens = tokenize(expandMacros(expression, 0)); + var exprTree = buildExprTree(tokens); + var evaluated = exprTree(); + // If the previous #if / #elif block was executed, output NaN so that all further #elif and #else blocks will + // short to false. + stack[stack.length-1] = !!evaluated * (stack[stack.length-1] ? NaN : 1-stack[stack.length-1]); + break; + case 'ifdef': stack.push(!!defs[expression] * stack[stack.length-1]); break; + case 'ifndef': stack.push(!defs[expression] * stack[stack.length-1]); break; + case 'else': stack[stack.length-1] = (1-stack[stack.length-1]) * stack[stack.length-2]; break; + case 'endif': stack.pop(); break; + case 'define': + if (thisLineIsInActivePreprocessingBlock) { + // This could either be a macro with input args (#define MACRO(x,y) x+y), or a direct expansion #define FOO 2, + // figure out which. + var macroStart = expression.indexOf('('); + var firstWs = nextWhitespace(expression, 0); + if (firstWs < macroStart) macroStart = 0; + if (macroStart > 0) { // #define MACRO( x , y , z ) + var macroEnd = expression.indexOf(')', macroStart); + let params = expression.substring(macroStart+1, macroEnd).split(',').map(x => x.trim()); + let value = tokenize(expression.substring(macroEnd+1).trim()) + defs[expression.substring(0, macroStart)] = (args) => { + var ret = ''; + value.forEach((x) => { + var argIndex = params.indexOf(x); + ret += (argIndex >= 0) ? args[argIndex] : x; + }); + return ret; + }; + } else { // #define FOO (x + y + z) + let value = expandMacros(expression.substring(firstWs+1).trim(), 0); + defs[expression.substring(0, firstWs)] = () => value; + } + } + break; + case 'undef': if (thisLineIsInActivePreprocessingBlock) delete defs[expression]; break; + default: + if (directive != 'version' && directive != 'pragma' && directive != 'extension' && directive != 'line') { // GLSL shader compiler specific #directives. +#if ASSERTIONS + err('Unrecognized preprocessor directive #' + directive + '!'); +#endif + } + + // Unknown preprocessor macro, just pass through the line to output. + out += expandMacros(code, lineStart, i) + '\n'; + } + } + return out; + } +}); diff --git a/src/lib/libccall.js b/src/lib/libccall.js new file mode 100644 index 0000000000000..bf48d9a912d5c --- /dev/null +++ b/src/lib/libccall.js @@ -0,0 +1,156 @@ +/** + * @license + * Copyright 2022 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + // Returns the C function with a specified identifier (for C++, you need to do manual name mangling) +#if MODULARIZE == 'instance' && !INCLUDE_FULL_LIBRARY + $getCFunc__deps: [() => error('ccall is not yet compatible with MODULARIZE=instance')], +#endif + $getCFunc__internal: true, + $getCFunc: (ident) => { + var func = Module['_' + ident]; // closure exported function +#if ASSERTIONS + assert(func, `Cannot call unknown function ${ident}, make sure it is exported`); +#endif + return func; + }, + + // C calling interface. + $ccall__deps: ['$getCFunc', '$writeArrayToMemory', '$stringToUTF8OnStack', '$stackSave', '$stackRestore', '$stackAlloc'], + $ccall__docs: ` + /** + * @param {string|null=} returnType + * @param {Array=} argTypes + * @param {Array=} args + * @param {Object=} opts + */`, + $ccall: (ident, returnType, argTypes, args, opts) => { + // For fast lookup of conversion functions + var toC = { +#if MEMORY64 + 'pointer': (p) => {{{ to64('p') }}}, +#endif + 'string': (str) => { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { // null string + ret = stringToUTF8OnStack(str); + } + return {{{ to64('ret') }}}; + }, + 'array': (arr) => { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return {{{ to64('ret') }}}; + } + }; + + function convertReturnValue(ret) { + if (returnType === 'string') { + return UTF8ToString({{{ from64Expr('ret') }}}); + } +#if MEMORY64 + if (returnType === 'pointer') return Number(ret); +#elif CAN_ADDRESS_2GB + if (returnType === 'pointer') return ret >>> 0; +#endif + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; +#if ASSERTIONS + assert(returnType !== 'array', 'Return type should not be "array".'); +#endif + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } +#if ASYNCIFY == 1 + // Data for a previous async operation that was in flight before us. + var previousAsync = Asyncify.currData; +#endif + var ret = func(...cArgs); + function onDone(ret) { +#if ASYNCIFY == 1 + runtimeKeepalivePop(); +#endif + if (stack !== 0) stackRestore(stack); + return convertReturnValue(ret); + } +#if ASYNCIFY + var asyncMode = opts?.async; +#endif + +#if ASYNCIFY == 1 + // Keep the runtime alive through all calls. Note that this call might not be + // async, but for simplicity we push and pop in all calls. + runtimeKeepalivePush(); + if (Asyncify.currData != previousAsync) { +#if ASSERTIONS + // A change in async operation happened. If there was already an async + // operation in flight before us, that is an error: we should not start + // another async operation while one is active, and we should not stop one + // either. The only valid combination is to have no change in the async + // data (so we either had one in flight and left it alone, or we didn't have + // one), or to have nothing in flight and to start one. + assert(!(previousAsync && Asyncify.currData), 'We cannot start an async operation when one is already in flight'); + assert(!(previousAsync && !Asyncify.currData), 'We cannot stop an async operation in flight'); +#endif + // This is a new async operation. The wasm is paused and has unwound its stack. + // We need to return a Promise that resolves the return value + // once the stack is rewound and execution finishes. +#if ASSERTIONS + assert(asyncMode, `The call to ${ident} is running asynchronously. If this was intended, add the async option to the ccall/cwrap call.`); +#endif + return Asyncify.whenDone().then(onDone); + } +#endif + +#if ASYNCIFY == 2 + if (asyncMode) return ret.then(onDone); +#endif + + ret = onDone(ret); +#if ASYNCIFY == 1 + // If this is an async ccall, ensure we return a promise + if (asyncMode) return Promise.resolve(ret); +#endif + return ret; + }, + + $cwrap__docs: ` + /** + * @param {string=} returnType + * @param {Array=} argTypes + * @param {Object=} opts + */`, + $cwrap__deps: [ '$ccall', +#if !ASSERTIONS + '$getCFunc', +#endif + ], + $cwrap: (ident, returnType, argTypes, opts) => { +#if !ASSERTIONS + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean'); + var numericRet = returnType !== 'string'; + if (numericRet && numericArgs && !opts) { + return getCFunc(ident); + } +#endif + return (...args) => ccall(ident, returnType, argTypes, args, opts); + }, +}); diff --git a/src/lib/libcore.js b/src/lib/libcore.js new file mode 100644 index 0000000000000..ee9cfcedef751 --- /dev/null +++ b/src/lib/libcore.js @@ -0,0 +1,2615 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +//"use strict"; + +// An implementation of basic necessary libraries for the web. This integrates +// with a compiled libc and with the rest of the JS runtime. +// +// We search the Library object when there is an external function. If the +// entry in the Library is a function, we insert it. If it is a string, we +// do another lookup in the library (a simple way to write a function once, +// if it can be called by different names). We also allow dependencies, +// using __deps. Initialization code to be run after allocating all +// global constants can be defined by __postset. +// +// Note that the full function name will be '_' + the name in the Library +// object. For convenience, the short name appears here. Note that if you add a +// new function with an '_', it will not be found. + +addToLibrary({ + // HEAP definitions are here to help with TypeScript type generation. + $HEAP8__docs: '/** @type {!Int8Array} */', + $HEAP8: undefined, + $HEAPU8__docs: '/** @type {!Uint8Array} */', + $HEAPU8: undefined, + $HEAP16__docs: '/** @type {!Int16Array} */', + $HEAP16: undefined, + $HEAPU16__docs: '/** @type {!Uint16Array} */', + $HEAPU16: undefined, + $HEAP32__docs: '/** @type {!Int32Array} */', + $HEAP32: undefined, + $HEAPU32__docs: '/** @type {!Uint32Array} */', + $HEAPU32: undefined, + $HEAPF32__docs: '/** @type {!Float32Array} */', + $HEAPF32: undefined, + $HEAPF64__docs: '/** @type {!Float64Array} */', + $HEAPF64: undefined, +#if WASM_BIGINT + // BigInt64Array type is not correctly defined in closure + $HEAP64__docs: '/** not-@type {!BigInt64Array} */', + $HEAP64: undefined, + $HEAPU64__docs: '/** not-@type {!BigUint64Array} */', + $HEAPU64: undefined, +#endif + + // JS aliases for native stack manipulation functions and tempret handling + $stackSave__deps: ['emscripten_stack_get_current'], + $stackSave: () => _emscripten_stack_get_current(), + $stackRestore__deps: ['_emscripten_stack_restore'], + $stackRestore: (val) => __emscripten_stack_restore(val), + $stackAlloc__deps: ['_emscripten_stack_alloc'], + $stackAlloc: (sz) => __emscripten_stack_alloc(sz), + $getTempRet0__deps: ['_emscripten_tempret_get'], + $getTempRet0: (val) => __emscripten_tempret_get(), + $setTempRet0__deps: ['_emscripten_tempret_set'], + $setTempRet0: (val) => __emscripten_tempret_set(val), + + // Aliases that allow legacy names (without leading $) for the + // functions to continue to work in `__deps` entries. + stackAlloc: '$stackAlloc', + stackSave: '$stackSave', + stackRestore: '$stackSave', + setTempRet0: '$setTempRet0', + getTempRet0: '$getTempRet0', + + // Assign a name to a given function. This is mostly useful for debugging + // purposes in cases where new functions are created at runtime. + $createNamedFunction: (name, func) => Object.defineProperty(func, 'name', { value: name }), + + // This function is referenced *very* early on in some configurations + // (e.g WASM_WORKERS + RUNTIME_DEBUG) so we explictly use a function here + // rather than an arrow function so that it gets hoisted to the top of the + // scope. + $ptrToString: function(ptr) { +#if ASSERTIONS + assert(typeof ptr === 'number', `ptrToString expects a number, got ${typeof ptr}`); +#endif +#if MEMORY64 + // Convert to 64-bit unsigned value. We need to use BigInt here since + // Number cannot represent the full 64-bit range. + if (ptr < 0) ptr = 2n**64n + BigInt(ptr); +#else + // Convert to 32-bit unsigned value + ptr >>>= 0; +#endif + return '0x' + ptr.toString(16).padStart({{{ POINTER_SIZE * 2 }}}, '0'); + }, + + $zeroMemory: (ptr, size) => HEAPU8.fill(0, ptr, ptr + size), + +#if SAFE_HEAP + // Trivial wrappers around runtime functions that make these symbols available + // to native code. + segfault: '=segfault', + alignfault: '=alignfault', +#endif + + // ========================================================================== + // JavaScript <-> C string interop + // ========================================================================== + +#if !MINIMAL_RUNTIME + $exitJS__docs: '/** @param {boolean|number=} implicit */', + $exitJS__deps: [ + 'proc_exit', +#if ASSERTIONS || EXIT_RUNTIME + '$keepRuntimeAlive', +#endif +#if PTHREADS + '$exitOnMainThread', +#endif +#if PTHREADS_DEBUG || ASSERTIONS + '$runtimeKeepaliveCounter', +#endif + ], + $exitJS: (status, implicit) => { + EXITSTATUS = status; + +#if ASSERTIONS && !EXIT_RUNTIME + checkUnflushedContent(); +#endif // ASSERTIONS && !EXIT_RUNTIME + +#if PTHREADS + if (ENVIRONMENT_IS_PTHREAD) { + // implicit exit can never happen on a pthread +#if ASSERTIONS + assert(!implicit); +#endif +#if PTHREADS_DEBUG + dbg(`Pthread ${ptrToString(_pthread_self())} called exit(${status}), posting exitOnMainThread.`); +#endif + // When running in a pthread we propagate the exit back to the main thread + // where it can decide if the whole process should be shut down or not. + // The pthread may have decided not to exit its own runtime, for example + // because it runs a main loop, but that doesn't affect the main thread. + exitOnMainThread(status); + throw 'unwind'; + } +#if PTHREADS_DEBUG + err(`main thread called exit(${status}): keepRuntimeAlive=${keepRuntimeAlive()} (counter=${runtimeKeepaliveCounter})`); +#endif // PTHREADS_DEBUG +#endif // PTHREADS + +#if EXIT_RUNTIME + if (!keepRuntimeAlive()) { + exitRuntime(); + } +#endif + +#if ASSERTIONS + // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down + if (keepRuntimeAlive() && !implicit) { + var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; +#if MODULARIZE + readyPromiseReject?.(msg); +#endif // MODULARIZE + err(msg); + } +#endif // ASSERTIONS + + _proc_exit(status); + }, +#endif + +#if MINIMAL_RUNTIME + // minimal runtime doesn't do any exit cleanup handling so just + // map exit directly to the lower-level proc_exit syscall. + exit: 'proc_exit', +#else + exit: '$exitJS', +#endif + + // Returns a pointer ('p'), which means an i32 on wasm32 and an i64 wasm64 + // We have a separate JS version `getHeapMax()` which can be called directly + // avoiding any wrapper added for wasm64. + emscripten_get_heap_max__deps: ['$getHeapMax'], + emscripten_get_heap_max: () => getHeapMax(), + + $getHeapMax: () => +#if ALLOW_MEMORY_GROWTH +#if MEMORY64 == 1 + {{{ MAXIMUM_MEMORY }}}, +#else + // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate + // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side + // for any code that deals with heap sizes, which would require special + // casing all heap size related code to treat 0 specially. + {{{ Math.min(MAXIMUM_MEMORY, FOUR_GB - WASM_PAGE_SIZE) }}}, +#endif +#else // no growth + HEAPU8.length, +#endif + +#if ABORTING_MALLOC + $abortOnCannotGrowMemory: (requestedSize) => { +#if ASSERTIONS +#if ALLOW_MEMORY_GROWTH + abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). If you want malloc to return NULL (0) instead of this abort, do not link with -sABORTING_MALLOC (that is, the default when growth is enabled is to not abort, but you have overridden that)`); +#else // ALLOW_MEMORY_GROWTH + abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`); +#endif // ALLOW_MEMORY_GROWTH +#else // ASSERTIONS + abort('OOM'); +#endif // ASSERTIONS + }, +#endif // ABORTING_MALLOC + + // Grows the wasm memory to the given byte size, and updates the JS views to + // it. Returns 1 on success, 0 on error. + $growMemory: (size) => { + var oldHeapSize = wasmMemory.buffer.byteLength; + var pages = ((size - oldHeapSize + {{{ WASM_PAGE_SIZE - 1 }}}) / {{{ WASM_PAGE_SIZE }}}) | 0; +#if RUNTIME_DEBUG + dbg(`growMemory: ${size} (+${size - oldHeapSize} bytes / ${pages} pages)`); +#endif + try { + // round size grow request up to wasm page size (fixed 64KB per spec) + wasmMemory.grow({{{ toIndexType('pages') }}}); // .grow() takes a delta compared to the previous size +#if !GROWABLE_ARRAYBUFFERS + updateMemoryViews(); +#endif +#if MEMORYPROFILER + if (typeof emscriptenMemoryProfiler != 'undefined') { + emscriptenMemoryProfiler.onMemoryResize(oldHeapSize, wasmMemory.buffer.byteLength); + } +#endif + return 1 /*success*/; + } catch(e) { +#if ASSERTIONS + err(`growMemory: Attempted to grow heap from ${oldHeapSize} bytes to ${size} bytes, but got error: ${e}`); +#endif + } + // implicit 0 return to save code size (caller will cast "undefined" into 0 + // anyhow) + }, + + emscripten_resize_heap__deps: [ +#if ABORTING_MALLOC + '$abortOnCannotGrowMemory', +#endif +#if ALLOW_MEMORY_GROWTH +#if ASSERTIONS == 2 + 'emscripten_get_now', +#endif + '$getHeapMax', + '$alignMemory', + '$growMemory', +#endif + ], + emscripten_resize_heap: 'ip', + emscripten_resize_heap: (requestedSize) => { + var oldSize = HEAPU8.length; +#if !MEMORY64 && !CAN_ADDRESS_2GB + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; +#endif +#if ALLOW_MEMORY_GROWTH == 0 +#if ABORTING_MALLOC + abortOnCannotGrowMemory(requestedSize); +#else + return false; // malloc will report failure +#endif // ABORTING_MALLOC +#else // ALLOW_MEMORY_GROWTH == 0 + // With multithreaded builds, races can happen (another thread might increase the size + // in between), so return a failure, and let the caller retry. +#if SHARED_MEMORY + if (requestedSize <= oldSize) { + return false; + } +#elif ASSERTIONS + assert(requestedSize > oldSize); +#endif + +#if EMSCRIPTEN_TRACING + // Report old layout one last time + _emscripten_trace_report_memory_layout(); +#endif + + // Memory resize rules: + // 1. Always increase heap size to at least the requested size, rounded up + // to next page multiple. + // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap + // geometrically: increase the heap size according to + // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most + // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). + // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap + // linearly: increase the heap size by at least + // MEMORY_GROWTH_LINEAR_STEP bytes. + // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by + // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 4. If we were unable to allocate as much memory, it may be due to + // over-eager decision to excessively reserve due to (3) above. + // Hence if an allocation fails, cut down on the amount of excess + // growth, in an attempt to succeed to perform a smaller allocation. + + // A limit is set for how much we can grow. We should not exceed that + // (the wasm binary specifies it, so if we tried, we'd fail anyhow). + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { +#if ASSERTIONS + err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`); +#endif +#if ABORTING_MALLOC + abortOnCannotGrowMemory(requestedSize); +#else + return false; +#endif + } + + // Loop through potential heap size increases. If we attempt a too eager + // reservation that fails, cut down on the attempted size and reserve a + // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { +#if MEMORY_GROWTH_LINEAR_STEP == -1 + var overGrownHeapSize = oldSize * (1 + {{{ MEMORY_GROWTH_GEOMETRIC_STEP }}} / cutDown); // ensure geometric growth +#if MEMORY_GROWTH_GEOMETRIC_CAP + // but limit overreserving (default to capping at +96MB overgrowth at most) + overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + {{{ MEMORY_GROWTH_GEOMETRIC_CAP }}} ); +#endif + +#else + var overGrownHeapSize = oldSize + {{{ MEMORY_GROWTH_LINEAR_STEP }}} / cutDown; // ensure linear growth +#endif + + var newSize = Math.min(maxHeapSize, alignMemory(Math.max(requestedSize, overGrownHeapSize), {{{ WASM_PAGE_SIZE }}})); + +#if ASSERTIONS == 2 + var t0 = _emscripten_get_now(); +#endif + var replacement = growMemory(newSize); +#if ASSERTIONS == 2 + var t1 = _emscripten_get_now(); + dbg(`Heap resize call from ${oldSize} to ${newSize} took ${(t1 - t0)} msecs. Success: ${!!replacement}`); +#endif + if (replacement) { +#if ASSERTIONS && WASM2JS + err('Warning: Enlarging memory arrays, this is not fast! ' + [oldSize, newSize]); +#endif + +#if EMSCRIPTEN_TRACING + traceLogMessage("Emscripten", `Enlarging memory arrays from ${oldSize} to ${newSize}`); + // And now report the new layout + _emscripten_trace_report_memory_layout(); +#endif + return true; + } + } +#if ASSERTIONS + err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`); +#endif +#if ABORTING_MALLOC + abortOnCannotGrowMemory(requestedSize); +#else + return false; +#endif +#endif // ALLOW_MEMORY_GROWTH + }, + +#if !GROWABLE_ARRAYBUFFERS + // Called after wasm grows memory. At that time we need to update the views. + // Without this notification, we'd need to check the buffer in JS every time + // we return from any wasm, which adds overhead. See + // https://github.com/WebAssembly/WASI/issues/82 + emscripten_notify_memory_growth: (memoryIndex) => { +#if ASSERTIONS + assert(memoryIndex == 0); +#endif + updateMemoryViews(); + }, +#endif + + _emscripten_system: (command) => { +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + if (!command) return 1; // shell is available + + var cmdstr = UTF8ToString(command); + if (!cmdstr.length) return 0; // this is what glibc seems to do (shell works test?) + + var cp = require('node:child_process'); + var ret = cp.spawnSync(cmdstr, [], {shell:true, stdio:'inherit'}); + + var _W_EXITCODE = (ret, sig) => ((ret) << 8 | (sig)); + + // this really only can happen if process is killed by signal + if (ret.status === null) { + // sadly node doesn't expose such function + var signalToNumber = (sig) => { + // implement only the most common ones, and fallback to SIGINT + switch (sig) { + case 'SIGHUP': return {{{ cDefs.SIGHUP }}}; + case 'SIGQUIT': return {{{ cDefs.SIGQUIT }}}; + case 'SIGFPE': return {{{ cDefs.SIGFPE }}}; + case 'SIGKILL': return {{{ cDefs.SIGKILL }}}; + case 'SIGALRM': return {{{ cDefs.SIGALRM }}}; + case 'SIGTERM': return {{{ cDefs.SIGTERM }}}; + default: return {{{ cDefs.SIGINT }}}; + } + } + return _W_EXITCODE(0, signalToNumber(ret.signal)); + } + + return _W_EXITCODE(ret.status, 0); + } +#endif // ENVIRONMENT_MAY_BE_NODE + // int system(const char *command); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html + // Can't call external programs. + if (!command) return 0; // no shell available + return -{{{ cDefs.ENOSYS }}}; + }, + + // ========================================================================== + // stdlib.h + // ========================================================================== + +#if !STANDALONE_WASM + // Used to implement the native `abort` symbol. Note that we use the + // JavaScript `abort` helper in order to implement this function, but we use a + // distinct name here to avoid confusing the two. + _abort_js: () => +#if ASSERTIONS + abort('native code called abort()'), +#else + abort(''), +#endif +#endif + + // This object can be modified by the user during startup, which affects + // the initial values of the environment accessible by getenv. + $ENV: {}, + +#if !STANDALONE_WASM + // ========================================================================== + // assert.h + // ========================================================================== + + __assert_fail: (condition, filename, line, func) => + abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']), +#endif + +#if STACK_OVERFLOW_CHECK >= 2 + // Set stack limits used by binaryen's `StackCheck` pass. +#if MAIN_MODULE + $setStackLimits__deps: ['$setDylinkStackLimits'], +#endif + $setStackLimits: () => { + var stackLow = _emscripten_stack_get_base(); + var stackHigh = _emscripten_stack_get_end(); +#if RUNTIME_DEBUG + dbg(`setStackLimits: ${ptrToString(stackLow)}, ${ptrToString(stackHigh)}`); +#endif +#if MAIN_MODULE + // With dynamic linking we could have any number of pre-loaded libraries + // that each need to have their stack limits set. + setDylinkStackLimits(stackLow, stackHigh); +#else + ___set_stack_limits(stackLow, stackHigh); +#endif + }, +#endif + + $withStackSave__deps: ['$stackSave', '$stackRestore'], + $withStackSave: (f) => { + var stack = stackSave(); + var ret = f(); + stackRestore(stack); + return ret; + }, + + // ========================================================================== + // setjmp.h + // ========================================================================== + +#if SUPPORT_LONGJMP == 'emscripten' + // In WebAssemblyLowerEmscriptenEHSjLj pass in the LLVM backend, function + // calls that exist in the same function with setjmp are converted to a code + // sequence that includes invokes, malloc, free, saveSetjmp, and + // emscripten_longjmp. setThrew is called from invokes, but we don't have + // any way to express that dependency so we use emscripten_throw_longjmp as + // a proxy and declare the dependency here. + _emscripten_throw_longjmp__deps: ['setThrew'], + _emscripten_throw_longjmp: () => { + throw new EmscriptenSjLj; + }, +#elif !SUPPORT_LONGJMP +#if !INCLUDE_FULL_LIBRARY + // These are in order to print helpful error messages when either longjmp or + // setjmp is used. + longjmp__deps: [() => { + error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)'); + }], + get setjmp__deps() { + return this.longjmp__deps; + }, + // This is to print the correct error message when a program is built with + // SUPPORT_LONGJMP=1 but linked with SUPPORT_LONGJMP=0. When a program is + // built with SUPPORT_LONGJMP=1, the object file contains references of not + // longjmp but _emscripten_throw_longjmp, which is called from + // emscripten_longjmp. + get _emscripten_throw_longjmp__deps() { + return this.longjmp__deps; + }, +#endif + _emscripten_throw_longjmp: () => { + error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)'); + }, + // will never be emitted, as the dep errors at compile time + longjmp: (env, value) => { + abort('longjmp not supported (build with -s SUPPORT_LONGJMP)'); + }, + setjmp: (env) => { + abort('setjmp not supported (build with -s SUPPORT_LONGJMP)'); + }, +#endif + + // ========================================================================== + // errno.h + // ========================================================================== + + // We use a string literal here to avoid the string quotes on the object + // keys being removed when processed by jsifier. + $ERRNO_CODES: `{ + 'EPERM': {{{ cDefs.EPERM }}}, + 'ENOENT': {{{ cDefs.ENOENT }}}, + 'ESRCH': {{{ cDefs.ESRCH }}}, + 'EINTR': {{{ cDefs.EINTR }}}, + 'EIO': {{{ cDefs.EIO }}}, + 'ENXIO': {{{ cDefs.ENXIO }}}, + 'E2BIG': {{{ cDefs.E2BIG }}}, + 'ENOEXEC': {{{ cDefs.ENOEXEC }}}, + 'EBADF': {{{ cDefs.EBADF }}}, + 'ECHILD': {{{ cDefs.ECHILD }}}, + 'EAGAIN': {{{ cDefs.EAGAIN }}}, + 'EWOULDBLOCK': {{{ cDefs.EWOULDBLOCK }}}, + 'ENOMEM': {{{ cDefs.ENOMEM }}}, + 'EACCES': {{{ cDefs.EACCES }}}, + 'EFAULT': {{{ cDefs.EFAULT }}}, + 'ENOTBLK': {{{ cDefs.ENOTBLK }}}, + 'EBUSY': {{{ cDefs.EBUSY }}}, + 'EEXIST': {{{ cDefs.EEXIST }}}, + 'EXDEV': {{{ cDefs.EXDEV }}}, + 'ENODEV': {{{ cDefs.ENODEV }}}, + 'ENOTDIR': {{{ cDefs.ENOTDIR }}}, + 'EISDIR': {{{ cDefs.EISDIR }}}, + 'EINVAL': {{{ cDefs.EINVAL }}}, + 'ENFILE': {{{ cDefs.ENFILE }}}, + 'EMFILE': {{{ cDefs.EMFILE }}}, + 'ENOTTY': {{{ cDefs.ENOTTY }}}, + 'ETXTBSY': {{{ cDefs.ETXTBSY }}}, + 'EFBIG': {{{ cDefs.EFBIG }}}, + 'ENOSPC': {{{ cDefs.ENOSPC }}}, + 'ESPIPE': {{{ cDefs.ESPIPE }}}, + 'EROFS': {{{ cDefs.EROFS }}}, + 'EMLINK': {{{ cDefs.EMLINK }}}, + 'EPIPE': {{{ cDefs.EPIPE }}}, + 'EDOM': {{{ cDefs.EDOM }}}, + 'ERANGE': {{{ cDefs.ERANGE }}}, + 'ENOMSG': {{{ cDefs.ENOMSG }}}, + 'EIDRM': {{{ cDefs.EIDRM }}}, + 'ECHRNG': {{{ cDefs.ECHRNG }}}, + 'EL2NSYNC': {{{ cDefs.EL2NSYNC }}}, + 'EL3HLT': {{{ cDefs.EL3HLT }}}, + 'EL3RST': {{{ cDefs.EL3RST }}}, + 'ELNRNG': {{{ cDefs.ELNRNG }}}, + 'EUNATCH': {{{ cDefs.EUNATCH }}}, + 'ENOCSI': {{{ cDefs.ENOCSI }}}, + 'EL2HLT': {{{ cDefs.EL2HLT }}}, + 'EDEADLK': {{{ cDefs.EDEADLK }}}, + 'ENOLCK': {{{ cDefs.ENOLCK }}}, + 'EBADE': {{{ cDefs.EBADE }}}, + 'EBADR': {{{ cDefs.EBADR }}}, + 'EXFULL': {{{ cDefs.EXFULL }}}, + 'ENOANO': {{{ cDefs.ENOANO }}}, + 'EBADRQC': {{{ cDefs.EBADRQC }}}, + 'EBADSLT': {{{ cDefs.EBADSLT }}}, + 'EDEADLOCK': {{{ cDefs.EDEADLOCK }}}, + 'EBFONT': {{{ cDefs.EBFONT }}}, + 'ENOSTR': {{{ cDefs.ENOSTR }}}, + 'ENODATA': {{{ cDefs.ENODATA }}}, + 'ETIME': {{{ cDefs.ETIME }}}, + 'ENOSR': {{{ cDefs.ENOSR }}}, + 'ENONET': {{{ cDefs.ENONET }}}, + 'ENOPKG': {{{ cDefs.ENOPKG }}}, + 'EREMOTE': {{{ cDefs.EREMOTE }}}, + 'ENOLINK': {{{ cDefs.ENOLINK }}}, + 'EADV': {{{ cDefs.EADV }}}, + 'ESRMNT': {{{ cDefs.ESRMNT }}}, + 'ECOMM': {{{ cDefs.ECOMM }}}, + 'EPROTO': {{{ cDefs.EPROTO }}}, + 'EMULTIHOP': {{{ cDefs.EMULTIHOP }}}, + 'EDOTDOT': {{{ cDefs.EDOTDOT }}}, + 'EBADMSG': {{{ cDefs.EBADMSG }}}, + 'ENOTUNIQ': {{{ cDefs.ENOTUNIQ }}}, + 'EBADFD': {{{ cDefs.EBADFD }}}, + 'EREMCHG': {{{ cDefs.EREMCHG }}}, + 'ELIBACC': {{{ cDefs.ELIBACC }}}, + 'ELIBBAD': {{{ cDefs.ELIBBAD }}}, + 'ELIBSCN': {{{ cDefs.ELIBSCN }}}, + 'ELIBMAX': {{{ cDefs.ELIBMAX }}}, + 'ELIBEXEC': {{{ cDefs.ELIBEXEC }}}, + 'ENOSYS': {{{ cDefs.ENOSYS }}}, + 'ENOTEMPTY': {{{ cDefs.ENOTEMPTY }}}, + 'ENAMETOOLONG': {{{ cDefs.ENAMETOOLONG }}}, + 'ELOOP': {{{ cDefs.ELOOP }}}, + 'EOPNOTSUPP': {{{ cDefs.EOPNOTSUPP }}}, + 'EPFNOSUPPORT': {{{ cDefs.EPFNOSUPPORT }}}, + 'ECONNRESET': {{{ cDefs.ECONNRESET }}}, + 'ENOBUFS': {{{ cDefs.ENOBUFS }}}, + 'EAFNOSUPPORT': {{{ cDefs.EAFNOSUPPORT }}}, + 'EPROTOTYPE': {{{ cDefs.EPROTOTYPE }}}, + 'ENOTSOCK': {{{ cDefs.ENOTSOCK }}}, + 'ENOPROTOOPT': {{{ cDefs.ENOPROTOOPT }}}, + 'ESHUTDOWN': {{{ cDefs.ESHUTDOWN }}}, + 'ECONNREFUSED': {{{ cDefs.ECONNREFUSED }}}, + 'EADDRINUSE': {{{ cDefs.EADDRINUSE }}}, + 'ECONNABORTED': {{{ cDefs.ECONNABORTED }}}, + 'ENETUNREACH': {{{ cDefs.ENETUNREACH }}}, + 'ENETDOWN': {{{ cDefs.ENETDOWN }}}, + 'ETIMEDOUT': {{{ cDefs.ETIMEDOUT }}}, + 'EHOSTDOWN': {{{ cDefs.EHOSTDOWN }}}, + 'EHOSTUNREACH': {{{ cDefs.EHOSTUNREACH }}}, + 'EINPROGRESS': {{{ cDefs.EINPROGRESS }}}, + 'EALREADY': {{{ cDefs.EALREADY }}}, + 'EDESTADDRREQ': {{{ cDefs.EDESTADDRREQ }}}, + 'EMSGSIZE': {{{ cDefs.EMSGSIZE }}}, + 'EPROTONOSUPPORT': {{{ cDefs.EPROTONOSUPPORT }}}, + 'ESOCKTNOSUPPORT': {{{ cDefs.ESOCKTNOSUPPORT }}}, + 'EADDRNOTAVAIL': {{{ cDefs.EADDRNOTAVAIL }}}, + 'ENETRESET': {{{ cDefs.ENETRESET }}}, + 'EISCONN': {{{ cDefs.EISCONN }}}, + 'ENOTCONN': {{{ cDefs.ENOTCONN }}}, + 'ETOOMANYREFS': {{{ cDefs.ETOOMANYREFS }}}, + 'EUSERS': {{{ cDefs.EUSERS }}}, + 'EDQUOT': {{{ cDefs.EDQUOT }}}, + 'ESTALE': {{{ cDefs.ESTALE }}}, + 'ENOTSUP': {{{ cDefs.ENOTSUP }}}, + 'ENOMEDIUM': {{{ cDefs.ENOMEDIUM }}}, + 'EILSEQ': {{{ cDefs.EILSEQ }}}, + 'EOVERFLOW': {{{ cDefs.EOVERFLOW }}}, + 'ECANCELED': {{{ cDefs.ECANCELED }}}, + 'ENOTRECOVERABLE': {{{ cDefs.ENOTRECOVERABLE }}}, + 'EOWNERDEAD': {{{ cDefs.EOWNERDEAD }}}, + 'ESTRPIPE': {{{ cDefs.ESTRPIPE }}}, + }`, + +#if PURE_WASI + $strError: (errno) => errno + '', +#else + $strError__deps: ['strerror', '$UTF8ToString'], + $strError: (errno) => UTF8ToString(_strerror(errno)), +#endif + +#if PROXY_POSIX_SOCKETS == 0 + // ========================================================================== + // netdb.h + // ========================================================================== + + $inetPton4: (str) => { + var b = str.split('.'); + for (var i = 0; i < 4; i++) { + var tmp = Number(b[i]); + if (isNaN(tmp)) return null; + b[i] = tmp; + } + return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; + }, + $inetNtop4: (addr) => + (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff), + $inetPton6__deps: ['htons'], + $inetPton6: (str) => { + var words; + var w, offset, z, i; + /* http://home.deds.nl/~aeron/regex/ */ + var valid6regx = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i + var parts = []; + if (!valid6regx.test(str)) { + return null; + } + if (str === "::") { + return [0, 0, 0, 0, 0, 0, 0, 0]; + } + // Z placeholder to keep track of zeros when splitting the string on ":" + if (str.startsWith("::")) { + str = str.replace("::", "Z:"); // leading zeros case + } else { + str = str.replace("::", ":Z:"); + } + + if (str.indexOf(".") > 0) { + // parse IPv4 embedded address + str = str.replace(new RegExp('[.]', 'g'), ":"); + words = str.split(":"); + words[words.length-4] = Number(words[words.length-4]) + Number(words[words.length-3])*256; + words[words.length-3] = Number(words[words.length-2]) + Number(words[words.length-1])*256; + words = words.slice(0, words.length-2); + } else { + words = str.split(":"); + } + + offset = 0; z = 0; + for (w=0; w < words.length; w++) { + if (typeof words[w] == 'string') { + if (words[w] === 'Z') { + // compressed zeros - write appropriate number of zero words + for (z = 0; z < (8 - words.length+1); z++) { + parts[w+z] = 0; + } + offset = z-1; + } else { + // parse hex field to 16-bit value and write it in network byte-order + parts[w+offset] = _htons(parseInt(words[w],16)); + } + } else { + // parsed IPv4 words + parts[w+offset] = words[w]; + } + } + return [ + (parts[1] << 16) | parts[0], + (parts[3] << 16) | parts[2], + (parts[5] << 16) | parts[4], + (parts[7] << 16) | parts[6] + ]; + }, + $inetNtop6__deps: ['$inetNtop4', 'ntohs'], + $inetNtop6: (ints) => { + // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 + // Format for IPv4 compatible and mapped 128-bit IPv6 Addresses + // 128-bits are split into eight 16-bit words + // stored in network byte order (big-endian) + // | 80 bits | 16 | 32 bits | + // +-----------------------------------------------------------------+ + // | 10 bytes | 2 | 4 bytes | + // +--------------------------------------+--------------------------+ + // + 5 words | 1 | 2 words | + // +--------------------------------------+--------------------------+ + // |0000..............................0000|0000| IPv4 ADDRESS | (compatible) + // +--------------------------------------+----+---------------------+ + // |0000..............................0000|FFFF| IPv4 ADDRESS | (mapped) + // +--------------------------------------+----+---------------------+ + var str = ""; + var word = 0; + var longest = 0; + var lastzero = 0; + var zstart = 0; + var len = 0; + var i = 0; + var parts = [ + ints[0] & 0xffff, + (ints[0] >> 16), + ints[1] & 0xffff, + (ints[1] >> 16), + ints[2] & 0xffff, + (ints[2] >> 16), + ints[3] & 0xffff, + (ints[3] >> 16) + ]; + + // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses + + var hasipv4 = true; + var v4part = ""; + // check if the 10 high-order bytes are all zeros (first 5 words) + for (i = 0; i < 5; i++) { + if (parts[i] !== 0) { hasipv4 = false; break; } + } + + if (hasipv4) { + // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) + v4part = inetNtop4(parts[6] | (parts[7] << 16)); + // IPv4-mapped IPv6 address if 16-bit value (bytes 11 and 12) == 0xFFFF (6th word) + if (parts[5] === -1) { + str = "::ffff:"; + str += v4part; + return str; + } + // IPv4-compatible IPv6 address if 16-bit value (bytes 11 and 12) == 0x0000 (6th word) + if (parts[5] === 0) { + str = "::"; + // special case IPv6 addresses + if (v4part === "0.0.0.0") v4part = ""; // any/unspecified address + if (v4part === "0.0.0.1") v4part = "1";// loopback address + str += v4part; + return str; + } + } + + // Handle all other IPv6 addresses + + // first run to find the longest contiguous zero words + for (word = 0; word < 8; word++) { + if (parts[word] === 0) { + if (word - lastzero > 1) { + len = 0; + } + lastzero = word; + len++; + } + if (len > longest) { + longest = len; + zstart = word - longest + 1; + } + } + + for (word = 0; word < 8; word++) { + if (longest > 1) { + // compress contiguous zeros - to produce "::" + if (parts[word] === 0 && word >= zstart && word < (zstart + longest) ) { + if (word === zstart) { + str += ":"; + if (zstart === 0) str += ":"; //leading zeros case + } + continue; + } + } + // converts 16-bit words from big-endian to little-endian before converting to hex string + str += Number(_ntohs(parts[word] & 0xffff)).toString(16); + str += word < 7 ? ":" : ""; + } + return str; + }, + + $readSockaddr__deps: ['$inetNtop4', '$inetNtop6', 'ntohs'], + $readSockaddr: (sa, salen) => { + // family / port offsets are common to both sockaddr_in and sockaddr_in6 + var family = {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_family, 'i16') }}}; + var port = _ntohs({{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_port, 'u16') }}}); + var addr; + + switch (family) { + case {{{ cDefs.AF_INET }}}: + if (salen !== {{{ C_STRUCTS.sockaddr_in.__size__ }}}) { + return { errno: {{{ cDefs.EINVAL }}} }; + } + addr = {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_addr.s_addr, 'i32') }}}; + addr = inetNtop4(addr); + break; + case {{{ cDefs.AF_INET6 }}}: + if (salen !== {{{ C_STRUCTS.sockaddr_in6.__size__ }}}) { + return { errno: {{{ cDefs.EINVAL }}} }; + } + addr = [ + {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+0, 'i32') }}}, + {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+4, 'i32') }}}, + {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+8, 'i32') }}}, + {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+12, 'i32') }}} + ]; + addr = inetNtop6(addr); + break; + default: + return { errno: {{{ cDefs.EAFNOSUPPORT }}} }; + } + + return { family: family, addr: addr, port: port }; + }, + $writeSockaddr__docs: '/** @param {number=} addrlen */', + $writeSockaddr__deps: ['$inetPton4', '$inetPton6', '$zeroMemory', 'htons'], + $writeSockaddr: (sa, family, addr, port, addrlen) => { + switch (family) { + case {{{ cDefs.AF_INET }}}: + addr = inetPton4(addr); + zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in.__size__ }}}); + if (addrlen) { + {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in.__size__, 'i32') }}}; + } + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_family, 'family', 'i16') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_addr.s_addr, 'addr', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_port, '_htons(port)', 'i16') }}}; + break; + case {{{ cDefs.AF_INET6 }}}: + addr = inetPton6(addr); + zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in6.__size__ }}}); + if (addrlen) { + {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in6.__size__, 'i32') }}}; + } + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_family, 'family', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+0, 'addr[0]', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+4, 'addr[1]', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+8, 'addr[2]', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+12, 'addr[3]', 'i32') }}}; + {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_port, '_htons(port)', 'i16') }}}; + break; + default: + return {{{ cDefs.EAFNOSUPPORT }}}; + } + return 0; + }, + + // We can't actually resolve hostnames in the browser, so instead + // we're generating fake IP addresses with lookup_name that we can + // resolve later on with lookup_addr. + // We do the aliasing in 172.29.*.*, giving us 65536 possibilities. + $DNS__deps: ['$inetPton4', '$inetPton6'], + $DNS: { + address_map: { + id: 1, + addrs: {}, + names: {} + }, + + lookup_name(name) { + // If the name is already a valid ipv4 / ipv6 address, don't generate a fake one. + var res = inetPton4(name); + if (res !== null) { + return name; + } + res = inetPton6(name); + if (res !== null) { + return name; + } + + // See if this name is already mapped. + var addr; + + if (DNS.address_map.addrs[name]) { + addr = DNS.address_map.addrs[name]; + } else { + var id = DNS.address_map.id++; +#if ASSERTIONS + assert(id < 65535, 'exceeded max address mappings of 65535'); +#endif + + addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00); + + DNS.address_map.names[addr] = name; + DNS.address_map.addrs[name] = addr; + } + + return addr; + }, + + lookup_addr(addr) { + if (DNS.address_map.names[addr]) { + return DNS.address_map.names[addr]; + } + + return null; + } + }, + + _emscripten_lookup_name__deps: ['$UTF8ToString', '$DNS', '$inetPton4'], + _emscripten_lookup_name: (name) => { + // uint32_t _emscripten_lookup_name(const char *name); + var nameString = UTF8ToString(name); + return inetPton4(DNS.lookup_name(nameString)); + }, + + getaddrinfo__deps: ['$DNS', '$inetPton4', '$inetNtop4', '$inetPton6', '$inetNtop6', '$writeSockaddr', 'malloc', 'htonl'], + getaddrinfo__proxy: 'sync', + getaddrinfo: (node, service, hint, out) => { + // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL + // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we + // really should provide a linked list of suitable addrinfo values. + var addrs = []; + var canon = null; + var addr = 0; + var port = 0; + var flags = 0; + var family = {{{ cDefs.AF_UNSPEC }}}; + var type = 0; + var proto = 0; + var ai, last; + + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var errno; + + salen = family === {{{ cDefs.AF_INET6 }}} ? + {{{ C_STRUCTS.sockaddr_in6.__size__ }}} : + {{{ C_STRUCTS.sockaddr_in.__size__ }}}; + addr = family === {{{ cDefs.AF_INET6 }}} ? + inetNtop6(addr) : + inetNtop4(addr); + sa = _malloc(salen); + errno = writeSockaddr(sa, family, addr, port); +#if ASSERTIONS + assert(!errno); +#endif + + ai = _malloc({{{ C_STRUCTS.addrinfo.__size__ }}}); + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_family, 'family', 'i32') }}}; + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_socktype, 'type', 'i32') }}}; + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_protocol, 'proto', 'i32') }}}; + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_canonname, 'canon', '*') }}}; + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addr, 'sa', '*') }}}; + if (family === {{{ cDefs.AF_INET6 }}}) { + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addrlen, C_STRUCTS.sockaddr_in6.__size__, 'i32') }}}; + } else { + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addrlen, C_STRUCTS.sockaddr_in.__size__, 'i32') }}}; + } + {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_next, '0', 'i32') }}}; + + return ai; + } + + if (hint) { + flags = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}}; + family = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_family, 'i32') }}}; + type = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_socktype, 'i32') }}}; + proto = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_protocol, 'i32') }}}; + } + if (type && !proto) { + proto = type === {{{ cDefs.SOCK_DGRAM }}} ? {{{ cDefs.IPPROTO_UDP }}} : {{{ cDefs.IPPROTO_TCP }}}; + } + if (!type && proto) { + type = proto === {{{ cDefs.IPPROTO_UDP }}} ? {{{ cDefs.SOCK_DGRAM }}} : {{{ cDefs.SOCK_STREAM }}}; + } + + // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for + // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints. + if (proto === 0) { + proto = {{{ cDefs.IPPROTO_TCP }}}; + } + if (type === 0) { + type = {{{ cDefs.SOCK_STREAM }}}; + } + + if (!node && !service) { + return {{{ cDefs.EAI_NONAME }}}; + } + if (flags & ~({{{ cDefs.AI_PASSIVE }}}|{{{ cDefs.AI_CANONNAME }}}|{{{ cDefs.AI_NUMERICHOST }}}| + {{{ cDefs.AI_NUMERICSERV }}}|{{{ cDefs.AI_V4MAPPED }}}|{{{ cDefs.AI_ALL }}}|{{{ cDefs.AI_ADDRCONFIG }}})) { + return {{{ cDefs.EAI_BADFLAGS }}}; + } + if (hint !== 0 && ({{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}} & {{{ cDefs.AI_CANONNAME }}}) && !node) { + return {{{ cDefs.EAI_BADFLAGS }}}; + } + if (flags & {{{ cDefs.AI_ADDRCONFIG }}}) { + // TODO + return {{{ cDefs.EAI_NONAME }}}; + } + if (type !== 0 && type !== {{{ cDefs.SOCK_STREAM }}} && type !== {{{ cDefs.SOCK_DGRAM }}}) { + return {{{ cDefs.EAI_SOCKTYPE }}}; + } + if (family !== {{{ cDefs.AF_UNSPEC }}} && family !== {{{ cDefs.AF_INET }}} && family !== {{{ cDefs.AF_INET6 }}}) { + return {{{ cDefs.EAI_FAMILY }}}; + } + + if (service) { + service = UTF8ToString(service); + port = parseInt(service, 10); + + if (isNaN(port)) { + if (flags & {{{ cDefs.AI_NUMERICSERV }}}) { + return {{{ cDefs.EAI_NONAME }}}; + } + // TODO support resolving well-known service names from: + // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + return {{{ cDefs.EAI_SERVICE }}}; + } + } + + if (!node) { + if (family === {{{ cDefs.AF_UNSPEC }}}) { + family = {{{ cDefs.AF_INET }}}; + } + if ((flags & {{{ cDefs.AI_PASSIVE }}}) === 0) { + if (family === {{{ cDefs.AF_INET }}}) { + addr = _htonl({{{ cDefs.INADDR_LOOPBACK }}}); + } else { + addr = [0, 0, 0, _htonl(1)]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + + // + // try as a numeric address + // + node = UTF8ToString(node); + addr = inetPton4(node); + if (addr !== null) { + // incoming node is a valid ipv4 address + if (family === {{{ cDefs.AF_UNSPEC }}} || family === {{{ cDefs.AF_INET }}}) { + family = {{{ cDefs.AF_INET }}}; + } + else if (family === {{{ cDefs.AF_INET6 }}} && (flags & {{{ cDefs.AI_V4MAPPED }}})) { + addr = [0, 0, _htonl(0xffff), addr]; + family = {{{ cDefs.AF_INET6 }}}; + } else { + return {{{ cDefs.EAI_NONAME }}}; + } + } else { + addr = inetPton6(node); + if (addr !== null) { + // incoming node is a valid ipv6 address + if (family === {{{ cDefs.AF_UNSPEC }}} || family === {{{ cDefs.AF_INET6 }}}) { + family = {{{ cDefs.AF_INET6 }}}; + } else { + return {{{ cDefs.EAI_NONAME }}}; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + if (flags & {{{ cDefs.AI_NUMERICHOST }}}) { + return {{{ cDefs.EAI_NONAME }}}; + } + + // + // try as a hostname + // + // resolve the hostname to a temporary fake address + node = DNS.lookup_name(node); + addr = inetPton4(node); + if (family === {{{ cDefs.AF_UNSPEC }}}) { + family = {{{ cDefs.AF_INET }}}; + } else if (family === {{{ cDefs.AF_INET6 }}}) { + addr = [0, 0, _htonl(0xffff), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + }, + + getnameinfo__deps: ['$DNS', '$readSockaddr', '$stringToUTF8'], + getnameinfo: (sa, salen, node, nodelen, serv, servlen, flags) => { + var info = readSockaddr(sa, salen); + if (info.errno) { + return {{{ cDefs.EAI_FAMILY }}}; + } + var port = info.port; + var addr = info.addr; + + var overflowed = false; + + if (node && nodelen) { + var lookup; + if ((flags & {{{ cDefs.NI_NUMERICHOST }}}) || !(lookup = DNS.lookup_addr(addr))) { + if (flags & {{{ cDefs.NI_NAMEREQD }}}) { + return {{{ cDefs.EAI_NONAME }}}; + } + } else { + addr = lookup; + } + var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen); + + if (numBytesWrittenExclNull+1 >= nodelen) { + overflowed = true; + } + } + + if (serv && servlen) { + port = '' + port; + var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen); + + if (numBytesWrittenExclNull+1 >= servlen) { + overflowed = true; + } + } + + if (overflowed) { + // Note: even when we overflow, getnameinfo() is specced to write out the truncated results. + return {{{ cDefs.EAI_OVERFLOW }}}; + } + + return 0; + }, + + // Implement netdb.h protocol entry (getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent) + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/getprotobyname.html + // The Protocols object holds our 'fake' protocols 'database'. + $Protocols: { + list: [], + map: {} + }, + setprotoent__deps: ['$Protocols', '$stringToAscii', 'malloc'], + setprotoent: (stayopen) => { + // void setprotoent(int stayopen); + + // Allocate and populate a protoent structure given a name, protocol number and array of aliases + function allocprotoent(name, proto, aliases) { + // write name into buffer + var nameBuf = _malloc(name.length + 1); + stringToAscii(name, nameBuf); + + // write aliases into buffer + var j = 0; + var length = aliases.length; + var aliasListBuf = _malloc((length + 1) * 4); // Use length + 1 so we have space for the terminating NULL ptr. + + for (var i = 0; i < length; i++, j += 4) { + var alias = aliases[i]; + var aliasBuf = _malloc(alias.length + 1); + stringToAscii(alias, aliasBuf); + {{{ makeSetValue('aliasListBuf', 'j', 'aliasBuf', '*') }}}; + } + {{{ makeSetValue('aliasListBuf', 'j', '0', '*') }}}; // Terminating NULL pointer. + + // generate protoent + var pe = _malloc({{{ C_STRUCTS.protoent.__size__ }}}); + {{{ makeSetValue('pe', C_STRUCTS.protoent.p_name, 'nameBuf', '*') }}}; + {{{ makeSetValue('pe', C_STRUCTS.protoent.p_aliases, 'aliasListBuf', '*') }}}; + {{{ makeSetValue('pe', C_STRUCTS.protoent.p_proto, 'proto', 'i32') }}}; + return pe; + }; + + // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial + // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful. + var list = Protocols.list; + var map = Protocols.map; + if (list.length === 0) { + var entry = allocprotoent('tcp', 6, ['TCP']); + list.push(entry); + map['tcp'] = map['6'] = entry; + entry = allocprotoent('udp', 17, ['UDP']); + list.push(entry); + map['udp'] = map['17'] = entry; + } + + _setprotoent.index = 0; + }, + + endprotoent: () => { + // void endprotoent(void); + // We're not using a real protocol database so we don't do a real close. + }, + + getprotoent__deps: ['setprotoent', '$Protocols'], + getprotoent: (number) => { + // struct protoent *getprotoent(void); + // reads the next entry from the protocols 'database' or return NULL if 'eof' + if (_setprotoent.index === Protocols.list.length) { + return 0; + } + var result = Protocols.list[_setprotoent.index++]; + return result; + }, + + getprotobyname__deps: ['setprotoent', '$Protocols'], + getprotobyname: (name) => { + // struct protoent *getprotobyname(const char *); + name = UTF8ToString(name); + _setprotoent(true); + var result = Protocols.map[name]; + return result; + }, + + getprotobynumber__deps: ['setprotoent', '$Protocols'], + getprotobynumber: (number) => { + // struct protoent *getprotobynumber(int proto); + _setprotoent(true); + var result = Protocols.map[number]; + return result; + }, + + // ========================================================================== + // sockets. Note that the implementation assumes all sockets are always + // nonblocking + // ========================================================================== +#if SOCKET_WEBRTC + $Sockets__deps: [ + () => 'var SocketIO = ' + read('../third_party/socket.io.js') + ';\n', + () => 'var Peer = ' + read('../third_party/wrtcp.js') + ';\n' + ], +#endif + $Sockets: { + BUFFER_SIZE: 10*1024, // initial size + MAX_BUFFER_SIZE: 10*1024*1024, // maximum size we will grow the buffer + + nextFd: 1, + fds: {}, + nextport: 1, + maxport: 65535, + peer: null, + connections: {}, + portmap: {}, + localAddr: 0xfe00000a, // Local address is always 10.0.0.254 + addrPool: [ 0x0200000a, 0x0300000a, 0x0400000a, 0x0500000a, + 0x0600000a, 0x0700000a, 0x0800000a, 0x0900000a, 0x0a00000a, + 0x0b00000a, 0x0c00000a, 0x0d00000a, 0x0e00000a] /* 0x0100000a is reserved */ + }, + +#endif // PROXY_POSIX_SOCKETS == 0 + + $timers: {}, + + // Helper function for setitimer that registers timers with the eventloop. + // Timers always fire on the main thread, either directly from JS (here) or + // or when the main thread is busy waiting calling _emscripten_yield. + _setitimer_js__proxy: 'sync', + _setitimer_js__deps: ['$timers', '$callUserCallback', '_emscripten_timeout', 'emscripten_get_now'], + _setitimer_js: (which, timeout_ms) => { +#if RUNTIME_DEBUG + dbg(`setitimer_js ${which} timeout=${timeout_ms}`); +#endif + // First, clear any existing timer. + if (timers[which]) { + clearTimeout(timers[which].id); + delete timers[which]; + } + + // A timeout of zero simply cancels the current timeout so we have nothing + // more to do. + if (!timeout_ms) return 0; + + var id = setTimeout(() => { +#if ASSERTIONS + assert(which in timers); +#endif + delete timers[which]; +#if RUNTIME_DEBUG + dbg(`itimer fired: ${which}`); +#endif + callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now())); + }, timeout_ms); + timers[which] = { id, timeout_ms }; + return 0; + }, + + // Helper for raise() to avoid signature mismatch failures: + // https://github.com/emscripten-core/posixtestsuite/issues/6 + __call_sighandler: (fp, sig) => {{{ makeDynCall('vi', 'fp') }}}(sig), + + // ========================================================================== + // emscripten.h + // ========================================================================== + + emscripten_run_script: (ptr) => { + {{{ makeEval('eval(UTF8ToString(ptr));') }}} + }, + + emscripten_run_script_int__docs: '/** @suppress{checkTypes} */', + emscripten_run_script_int: (ptr) => { + {{{ makeEval('return eval(UTF8ToString(ptr))|0;') }}} + }, + + // Mark as `noleakcheck` otherwise lsan will report the last returned string + // as a leak. + emscripten_run_script_string__noleakcheck: true, + emscripten_run_script_string__deps: ['$lengthBytesUTF8', '$stringToUTF8', 'realloc'], + emscripten_run_script_string: (ptr) => { + {{{ makeEval("var s = eval(UTF8ToString(ptr));") }}} + if (s == null) { + return 0; + } + s += ''; + var me = _emscripten_run_script_string; + me.bufferSize = lengthBytesUTF8(s) + 1; + me.buffer = _realloc(me.buffer ?? 0, me.bufferSize) + stringToUTF8(s, me.buffer, me.bufferSize); + return me.buffer; + }, + + emscripten_random: () => Math.random(), + + emscripten_date_now: () => Date.now(), + + emscripten_performance_now: () => {{{ getPerformanceNow() }}}(), + +#if PTHREADS && !AUDIO_WORKLET + // Pthreads need their clocks synchronized to the execution of the main + // thread, so, when using them, make sure to adjust all timings to the + // respective time origins. + emscripten_get_now: () => performance.timeOrigin + {{{ getPerformanceNow() }}}(), +#else +#if AUDIO_WORKLET // https://github.com/WebAudio/web-audio-api/issues/2413 + emscripten_get_now: `; + // AudioWorkletGlobalScope does not have performance.now() + // (https://github.com/WebAudio/web-audio-api/issues/2527), so if building + // with + // Audio Worklets enabled, do a dynamic check for its presence. + if (globalThis.performance && {{{ getPerformanceNow() }}}) { +#if PTHREADS + _emscripten_get_now = () => performance.timeOrigin + {{{ getPerformanceNow() }}}(); +#else + _emscripten_get_now = () => {{{ getPerformanceNow() }}}(); +#endif + } else { + _emscripten_get_now = Date.now; + } +`, +#else + // Modern environment where performance.now() is supported: + // N.B. a shorter form "_emscripten_get_now = performance.now;" is + // unfortunately not allowed even in current browsers (e.g. FF Nightly 75). + emscripten_get_now: () => {{{ getPerformanceNow() }}}(), +#endif +#endif + + emscripten_get_now_res: () => { // return resolution of get_now, in nanoseconds +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + return 1; // nanoseconds + } +#endif +#if AUDIO_WORKLET // https://github.com/WebAudio/web-audio-api/issues/2413 + if (globalThis.performance?.now == 'function') { + return 1000; // microseconds (1/1000 of a millisecond) + } + return 1000*1000; // milliseconds +#else + // Modern environment where performance.now() is supported: + return 1000; // microseconds (1/1000 of a millisecond) +#endif + }, + + // Represents whether emscripten_get_now is guaranteed monotonic; the Date.now + // implementation is not :( + $nowIsMonotonic__internal: true, +#if AUDIO_WORKLET // // https://github.com/WebAudio/web-audio-api/issues/2413 + $nowIsMonotonic: `!!globalThis.performance?.now;`, +#else + // Modern environment where performance.now() is supported + $nowIsMonotonic: 1, +#endif + + _emscripten_get_now_is_monotonic__internal: true, + _emscripten_get_now_is_monotonic__deps: ['$nowIsMonotonic'], + _emscripten_get_now_is_monotonic: () => nowIsMonotonic, + + $warnOnce: (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; +#endif + err(text); + } + }, + + _emscripten_log_formatted__deps: ['$getCallstack'], + _emscripten_log_formatted: (flags, str) => { + str = UTF8ToString(str); + + if (flags & {{{ cDefs.EM_LOG_C_STACK | cDefs.EM_LOG_JS_STACK }}}) { + str = str.replace(/\s+$/, ''); // Ensure the message and the callstack are joined cleanly with exactly one newline. + str += (str.length > 0 ? '\n' : '') + getCallstack(flags); + } + + if (flags & {{{ cDefs.EM_LOG_CONSOLE }}}) { + if (flags & {{{ cDefs.EM_LOG_ERROR }}}) { + console.error(str); + } else if (flags & {{{ cDefs.EM_LOG_WARN }}}) { + console.warn(str); + } else if (flags & {{{ cDefs.EM_LOG_INFO }}}) { + console.info(str); + } else if (flags & {{{ cDefs.EM_LOG_DEBUG }}}) { + console.debug(str); + } else { + console.log(str); + } + } else if (flags & {{{ cDefs.EM_LOG_ERROR | cDefs.EM_LOG_WARN }}}) { + err(str); + } else { + out(str); + } + }, + + // We never free the return values of this function so we need to allocate + // using builtin_malloc to avoid LSan reporting these as leaks. +#if RETAIN_COMPILER_SETTINGS + emscripten_get_compiler_setting__noleakcheck: true, + emscripten_get_compiler_setting__deps: ['$stringToNewUTF8'], + emscripten_get_compiler_setting: (name) => { + name = UTF8ToString(name); + + var ret = getCompilerSetting(name); + if (typeof ret == 'number' || typeof ret == 'boolean') return ret; + + var cache = _emscripten_get_compiler_setting.cache ??= {}; + var fullret = cache[name]; + if (fullret) return fullret; + return cache[name] = stringToNewUTF8(ret); + }, +#else + emscripten_get_compiler_setting: (name) => abort('You must build with -sRETAIN_COMPILER_SETTINGS for getCompilerSetting or emscripten_get_compiler_setting to work'), +#endif + + emscripten_has_asyncify: () => {{{ ASYNCIFY }}}, + + emscripten_debugger: () => { debugger }, + + emscripten_print_double__deps: ['$stringToUTF8', '$lengthBytesUTF8'], + emscripten_print_double: (x, to, max) => { + var str = x + ''; + if (to) return stringToUTF8(str, to, max); + else return lengthBytesUTF8(str); + }, + +#if USE_ASAN || USE_LSAN + // When lsan is enabled noLeakCheck will temporarily disable leak checking + // for the duration of the function. + $noLeakCheck__deps: ['__lsan_enable', '__lsan_disable'], + $noLeakCheck__docs: '/** @suppress{checkTypes} */', + $noLeakCheck: (func) => { + if (runtimeInitialized) ___lsan_disable(); + try { + return func(); + } finally { + if (runtimeInitialized) ___lsan_enable(); + } + }, +#endif + +#if USE_ASAN || USE_LSAN || UBSAN_RUNTIME + _emscripten_sanitizer_use_colors: () => { + var setting = Module['printWithColors']; + if (setting !== undefined) { + return setting; + } + return ENVIRONMENT_IS_NODE && process.stderr.isTTY; + }, + + _emscripten_sanitizer_get_option__deps: ['$stringToNewUTF8', '$UTF8ToString'], + _emscripten_sanitizer_get_option__sig: 'pp', + _emscripten_sanitizer_get_option: (name) => stringToNewUTF8(Module[UTF8ToString(name)] || ''), +#endif + + $readEmAsmArgsArray: [], + $readEmAsmArgs__deps: ['$readEmAsmArgsArray'], + $readEmAsmArgs: (sigPtr, buf) => { +#if ASSERTIONS + // Nobody should have mutated _readEmAsmArgsArray underneath us to be something else than an array. + assert(Array.isArray(readEmAsmArgsArray)); + // The input buffer is allocated on the stack, so it must be stack-aligned. + assert(buf % {{{ STACK_ALIGN }}} == 0); +#endif + readEmAsmArgsArray.length = 0; + var ch; + // Most arguments are i32s, so shift the buffer pointer so it is a plain + // index into HEAP32. + while (ch = HEAPU8[sigPtr++]) { +#if ASSERTIONS + var chr = String.fromCharCode(ch); + var validChars = ['d', 'f', 'i', 'p']; +#if WASM_BIGINT + // In WASM_BIGINT mode we support passing i64 values as bigint. + validChars.push('j'); +#endif + assert(validChars.includes(chr), `Invalid character ${ch}("${chr}") in readEmAsmArgs! Use only [${validChars}], and do not specify "v" for void return argument.`); +#endif + // Floats are always passed as doubles, so all types except for 'i' + // are 8 bytes and require alignment. + var wide = (ch != {{{ charCode('i') }}}); +#if !MEMORY64 + wide &= (ch != {{{ charCode('p') }}}); +#endif + buf += wide && (buf % 8) ? 4 : 0; + readEmAsmArgsArray.push( + // Special case for pointers under wasm64 or CAN_ADDRESS_2GB mode. + ch == {{{ charCode('p') }}} ? {{{ makeGetValue('buf', 0, '*') }}} : +#if WASM_BIGINT + ch == {{{ charCode('j') }}} ? {{{ makeGetValue('buf', 0, 'i64') }}} : +#endif + ch == {{{ charCode('i') }}} ? + {{{ makeGetValue('buf', 0, 'i32') }}} : + {{{ makeGetValue('buf', 0, 'double') }}} + ); + buf += wide ? 8 : 4; + } + return readEmAsmArgsArray; + }, + +#if HAVE_EM_ASM + $runEmAsmFunction__deps: ['$readEmAsmArgs'], + $runEmAsmFunction: (code, sigPtr, argbuf) => { + var args = readEmAsmArgs(sigPtr, argbuf); +#if ASSERTIONS + assert(ASM_CONSTS.hasOwnProperty(code), `No EM_ASM constant found at address ${code}. The loaded WebAssembly file is likely out of sync with the generated JavaScript.`); +#endif + return ASM_CONSTS[code](...args); + }, + + emscripten_asm_const_int__deps: ['$runEmAsmFunction'], + emscripten_asm_const_int: (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }, + emscripten_asm_const_double__deps: ['$runEmAsmFunction'], + emscripten_asm_const_double: (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }, + + emscripten_asm_const_ptr__deps: ['$runEmAsmFunction'], + emscripten_asm_const_ptr: (code, sigPtr, argbuf) => { + return runEmAsmFunction(code, sigPtr, argbuf); + }, + + $runMainThreadEmAsm__deps: ['$readEmAsmArgs', +#if PTHREADS + '$proxyToMainThread' +#endif + ], + $runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => { + var args = readEmAsmArgs(sigPtr, argbuf); +#if PTHREADS + if (ENVIRONMENT_IS_PTHREAD) { + // EM_ASM functions are variadic, receiving the actual arguments as a buffer + // in memory. the last parameter (argBuf) points to that data. We need to + // always un-variadify that, *before proxying*, as in the async case this + // is a stack allocation that LLVM made, which may go away before the main + // thread gets the message. For that reason we handle proxying *after* the + // call to readEmAsmArgs, and therefore we do that manually here instead + // of using __proxy. (And for simplicity, do the same in the sync + // case as well, even though it's not strictly necessary, to keep the two + // code paths as similar as possible on both sides.) + return proxyToMainThread(0, emAsmAddr, sync, ...args); + } +#endif +#if ASSERTIONS + assert(ASM_CONSTS.hasOwnProperty(emAsmAddr), `No EM_ASM constant found at address ${emAsmAddr}. The loaded WebAssembly file is likely out of sync with the generated JavaScript.`); +#endif + return ASM_CONSTS[emAsmAddr](...args); + }, + emscripten_asm_const_int_sync_on_main_thread__deps: ['$runMainThreadEmAsm'], + emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1), + + emscripten_asm_const_ptr_sync_on_main_thread__deps: ['$runMainThreadEmAsm'], + emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1), + + emscripten_asm_const_double_sync_on_main_thread: 'emscripten_asm_const_int_sync_on_main_thread', + emscripten_asm_const_async_on_main_thread__deps: ['$runMainThreadEmAsm'], + emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0), +#endif + +#if !DECLARE_ASM_MODULE_EXPORTS + // When DECLARE_ASM_MODULE_EXPORTS is set, this function is programmatically + // created during linking. See `create_receiving` in `emscripten.py`. + // When DECLARE_ASM_MODULE_EXPORTS=0 is set, `assignWasmExports` is instead + // defined here as a normal JS library function. + $assignWasmExports__deps: ['$asmjsMangle', +#if DYNCALLS || !WASM_BIGINT + , '$dynCalls' +#endif + ], + $assignWasmExports: (wasmExports) => { + for (var [name, exportedSymbol] of Object.entries(wasmExports)) { + name = asmjsMangle(name); +#if DYNCALLS || !WASM_BIGINT + if (name.startsWith('dynCall_')) { + dynCalls[name.substr(8)] = exportedSymbol; + } +#endif + // Special handling for Wasm globals. See `create_receiving` for the + // static version of this code. + if (typeof exportedSymbol.value != 'undefined') { +#if MEMORY64 + exportedSymbol = Number(exportedSymbol.value); +#else + exportedSymbol = exportedSymbol.value +#endif + } +#if MINIMAL_RUNTIME + globalThis[name] = exportedSymbol; +#else + globalThis[name] = Module[name] = exportedSymbol; +#endif + } + exportAliases(wasmExports); + }, +#endif + + // Parses as much of the given JS string to an integer, with quiet error + // handling (returns a NaN on error). E.g. jstoi_q("123abc") returns 123. + // Note that "smart" radix handling is employed for input string: + // "0314" is parsed as octal, and "0x1234" is parsed as base-16. + $jstoi_q__docs: '/** @suppress {checkTypes} */', + $jstoi_q: (str) => parseInt(str), + +#if LINK_AS_CXX + // libunwind + + _Unwind_Backtrace__deps: ['$getCallstack'], + _Unwind_Backtrace: (func, arg) => { + var trace = getCallstack(); + var parts = trace.split('\n'); + for (var i = 0; i < parts.length; i++) { + var ret = {{{ makeDynCall('iii', 'func') }}}(0, arg); + if (ret !== 0) return; + } + }, + + _Unwind_GetIPInfo: (context, ipBefore) => abort('Unwind_GetIPInfo'), + + _Unwind_FindEnclosingFunction: (ip) => 0, // we cannot succeed + + _Unwind_RaiseException__deps: ['__cxa_throw'], + _Unwind_RaiseException: (ex) => { + err('Warning: _Unwind_RaiseException is not correctly implemented'); + return ___cxa_throw(ex, 0, 0); + }, + + _Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'), +#endif + + // special runtime support + +#if STACK_OVERFLOW_CHECK + // Used by wasm-emscripten-finalize to implement STACK_OVERFLOW_CHECK + __handle_stack_overflow__deps: ['emscripten_stack_get_base', 'emscripten_stack_get_end', '$ptrToString'], + __handle_stack_overflow: (requested) => { + var base = _emscripten_stack_get_base(); + var end = _emscripten_stack_get_end(); + abort(`stack overflow (Attempt to set SP to ${ptrToString(requested)}` + + `, with stack limits [${ptrToString(end)} - ${ptrToString(base)}` + + ']). If you require more stack space build with -sSTACK_SIZE='); + }, +#endif + +#if MINIMAL_RUNTIME // MINIMAL_RUNTIME does not have a global runtime variable thisProgram + $getExecutableName: () => { +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE && process.argv.length > 1) { + return process.argv[1].replace(/\\/g, '/'); + } +#endif + return "./this.program"; + }, +#else + $getExecutableName: () => thisProgram || './this.program', +#endif + + // Receives a Web Audio context plus a set of elements to listen for user + // input events on, and registers a context resume() for them. This lets + // audio work properly in an automatic way, as browsers won't let audio run + // without user interaction. + $autoResumeAudioContext: (ctx) => { + for (var event of ['keydown', 'mousedown', 'touchstart']) { + for (var element of [document, document.getElementById('canvas')]) { + element?.addEventListener(event, () => { + if (ctx.state === 'suspended') ctx.resume(); + }, { 'once': true }); + } + } + }, + +#if DYNCALLS || !WASM_BIGINT + $dynCalls__internal: true, + $dynCalls: {}, + $dynCallLegacy__deps: ['$dynCalls'], + $dynCallLegacy: (sig, ptr, args) => { + sig = sig.replace(/p/g, {{{ MEMORY64 ? "'j'" : "'i'" }}}) +#if ASSERTIONS + assert(sig in dynCalls, `bad function pointer type - sig is not in dynCalls: '${sig}'`); + if (args?.length) { +#if WASM_BIGINT + // j (64-bit integer) is fine, and is implemented as a BigInt. Without + // legalization, the number of parameters should match (j is not expanded + // into two i's). + assert(args.length === sig.length - 1); +#else + // j (64-bit integer) must be passed in as two numbers [low 32, high 32]. + assert(args.length === sig.substring(1).replace(/j/g, '--').length); +#endif + } else { + assert(sig.length == 1); + } +#endif + var f = dynCalls[sig]; + return f(ptr, ...args); + }, + $dynCall__deps: [ +#if DYNCALLS || !WASM_BIGINT + '$dynCallLegacy', +#endif +#if !DYNCALLS + '$getWasmTableEntry', +#endif + ], +#endif + + // Used in library code to get JS function from wasm function pointer. + // All callers should use direct table access where possible and only fall + // back to this function if needed. + $getDynCaller__deps: ['$dynCall'], + $getDynCaller: (sig, ptr, promising = false) => { +#if ASSERTIONS && !DYNCALLS + assert(sig.includes('j') || sig.includes('p'), 'getDynCaller should only be called with i64 sigs') +#endif + return (...args) => dynCall(sig, ptr, args, promising); + }, + + $dynCall: (sig, ptr, args = [], promising = false) => { +#if ASSERTIONS + assert(ptr, `null function pointer in dynCall`); +#endif +#if ASSERTIONS && (DYNCALLS || !WASM_BIGINT || !JSPI) + assert(!promising, 'async dynCall is not supported in this mode') +#endif +#if MEMORY64 + // With MEMORY64 we have an additional step to convert `p` arguments to + // bigint. This is the runtime equivalent of the wrappers we create for wasm + // exports in `emscripten.py:create_wasm64_wrappers`. + for (var i = 1; i < sig.length; ++i) { + if (sig[i] == 'p') args[i-1] = BigInt(args[i-1]); + } +#endif +#if DYNCALLS + var rtn = dynCallLegacy(sig, ptr, args); +#else +#if !WASM_BIGINT + // Without WASM_BIGINT support we cannot directly call function with i64 as + // part of their signature, so we rely on the dynCall functions generated by + // wasm-emscripten-finalize + if (sig.includes('j')) { + return dynCallLegacy(sig, ptr, args); + } +#endif +#if ASSERTIONS + assert(getWasmTableEntry(ptr), `missing table entry in dynCall: ${ptr}`); +#endif + var func = getWasmTableEntry(ptr); +#if JSPI + if (promising) { + func = WebAssembly.promising(func); + } +#endif + var rtn = func(...args); +#endif // DYNCALLS + + function convert(rtn) { +#if MEMORY64 + return sig[0] == 'p' ? Number(rtn) : rtn; +#elif CAN_ADDRESS_2GB + return sig[0] == 'p' ? rtn >>> 0 : rtn; +#else + return rtn; +#endif + } + +#if JSPI + if (promising) { + return rtn.then(convert); + } +#endif + return convert(rtn); + }, + + $callRuntimeCallbacks__internal: true, + $callRuntimeCallbacks: (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }, + +#if SHRINK_LEVEL == 0 || ASYNCIFY == 2 + // A mirror copy of contents of wasmTable in JS side, to avoid relatively + // slow wasmTable.get() call. Only used when not compiling with -Os, -Oz, or + // JSPI which needs to instrument the functions. + $wasmTableMirror__internal: true, + $wasmTableMirror: [], + + $setWasmTableEntry__internal: true, + $setWasmTableEntry__deps: ['$wasmTableMirror', '$wasmTable'], + $setWasmTableEntry: (idx, func) => { + /** @suppress {checkTypes} */ + wasmTable.set({{{ toIndexType('idx') }}}, func); + // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overridden to return wrapped + // functions so we need to call it here to retrieve the potential wrapper correctly + // instead of just storing 'func' directly into wasmTableMirror + /** @suppress {checkTypes} */ + wasmTableMirror[idx] = wasmTable.get({{{ toIndexType('idx') }}}); + }, + + $getWasmTableEntry__internal: true, + $getWasmTableEntry__deps: ['$wasmTableMirror', '$wasmTable'], + $getWasmTableEntry: (funcPtr) => { +#if MEMORY64 + // Function pointers should show up as numbers, even under wasm64, but + // we still have some places where bigint values can flow here. + // https://github.com/emscripten-core/emscripten/issues/18200 + funcPtr = Number(funcPtr); +#endif + var func = wasmTableMirror[funcPtr]; + if (!func) { + /** @suppress {checkTypes} */ + wasmTableMirror[funcPtr] = func = wasmTable.get({{{ toIndexType('funcPtr') }}}); +#if ASYNCIFY == 2 + if (Asyncify.isAsyncExport(func)) { + wasmTableMirror[funcPtr] = func = Asyncify.makeAsyncFunction(func); + } +#endif + } +#if ASSERTIONS && ASYNCIFY != 2 // With JSPI the function stored in the table will be a wrapper. + /** @suppress {checkTypes} */ + assert(wasmTable.get({{{ toIndexType('funcPtr') }}}) == func, 'JavaScript-side Wasm function table mirror is out of date!'); +#endif + return func; + }, + +#else + + $setWasmTableEntry__docs: '/** @suppress{checkTypes} */', + $setWasmTableEntry__deps: ['$wasmTable'], + $setWasmTableEntry: (idx, func) => wasmTable.set({{{ toIndexType('idx') }}}, func), + + $getWasmTableEntry__docs: '/** @suppress{checkTypes} */', + $getWasmTableEntry__deps: ['$wasmTable'], + $getWasmTableEntry: (funcPtr) => { + // In -Os and -Oz builds, do not implement a JS side wasm table mirror for small + // code size, but directly access wasmTable, which is a bit slower as uncached. + return wasmTable.get({{{ toIndexType('funcPtr') }}}); + }, +#endif // SHRINK_LEVEL == 0 + + // Callable in pthread without __proxy needed. + emscripten_exit_with_live_runtime: () => { + {{{ runtimeKeepalivePush() }}} + throw 'unwind'; + }, + +#if !MINIMAL_RUNTIME + _emscripten_runtime_keepalive_clear__deps: ['$runtimeKeepaliveCounter'], +#endif + _emscripten_runtime_keepalive_clear: () => { +#if isSymbolNeeded('$noExitRuntime') + noExitRuntime = false; +#endif +#if !MINIMAL_RUNTIME + runtimeKeepaliveCounter = 0; +#endif + }, + + emscripten_force_exit__deps: ['exit', '_emscripten_runtime_keepalive_clear', +#if !EXIT_RUNTIME && ASSERTIONS + '$warnOnce', +#endif + ], + emscripten_force_exit__proxy: 'sync', + emscripten_force_exit: (status) => { +#if RUNTIME_DEBUG + dbg('emscripten_force_exit'); +#endif +#if !EXIT_RUNTIME && ASSERTIONS + warnOnce('emscripten_force_exit cannot actually shut down the runtime, as the build does not have EXIT_RUNTIME set'); +#endif + __emscripten_runtime_keepalive_clear(); + _exit(status); + }, + + emscripten_out: (str) => out(UTF8ToString(str)), + emscripten_outn: (str, len) => out(UTF8ToString(str, len)), + + emscripten_err: (str) => err(UTF8ToString(str)), + emscripten_errn: (str, len) => err(UTF8ToString(str, len)), + +#if ASSERTIONS || RUNTIME_DEBUG + emscripten_dbg: (str) => dbg(UTF8ToString(str)), + emscripten_dbgn: (str, len) => dbg(UTF8ToString(str, len)), + + emscripten_dbg_backtrace: (str) => { + dbg(UTF8ToString(str) + '\n' + new Error().stack); + }, +#endif + + // Use program_invocation_short_name and program_invocation_name in compiled + // programs. This function is for implementing them. + _emscripten_get_progname__deps: ['$getExecutableName', '$stringToUTF8'], + _emscripten_get_progname: (str, len) => stringToUTF8(getExecutableName(), str, len), + + emscripten_console_log: (str) => { +#if ASSERTIONS + assert(typeof str == 'number'); +#endif + console.log(UTF8ToString(str)); + }, + + emscripten_console_warn: (str) => { +#if ASSERTIONS + assert(typeof str == 'number'); +#endif + console.warn(UTF8ToString(str)); + }, + + emscripten_console_error: (str) => { +#if ASSERTIONS + assert(typeof str == 'number'); +#endif + console.error(UTF8ToString(str)); + }, + + emscripten_console_trace: (str) => { +#if ASSERTIONS + assert(typeof str == 'number'); +#endif + console.trace(UTF8ToString(str)); + }, + + emscripten_throw_number: (number) => { + throw number; + }, + + emscripten_throw_string: (str) => { +#if ASSERTIONS + assert(typeof str == 'number'); +#endif + throw UTF8ToString(str); + }, + +#if !MINIMAL_RUNTIME +#if STACK_OVERFLOW_CHECK + $handleException__deps: ['emscripten_stack_get_current'], +#endif + $handleException: (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { +#if RUNTIME_DEBUG + dbg(`handleException: unwinding: EXITSTATUS=${EXITSTATUS}`); +#endif + return EXITSTATUS; + } +#if STACK_OVERFLOW_CHECK + checkStackCookie(); + if (e instanceof WebAssembly.RuntimeError) { + if (_emscripten_stack_get_current() <= 0) { + err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to {{{ STACK_SIZE }}})'); + } + } +#endif +#if RUNTIME_DEBUG + dbg("handleException: got unexpected exception, calling quit_") +#endif + quit_(1, e); + }, + + $runtimeKeepaliveCounter__internal: true, + $runtimeKeepaliveCounter: 0, + +#if isSymbolNeeded('$noExitRuntime') + // If the `noExitRuntime` symbol is included in the build then + // keepRuntimeAlive is always conditional since its state can change + // at runtime. + $keepRuntimeAlive__deps: ['$runtimeKeepaliveCounter'], + $keepRuntimeAlive: () => noExitRuntime || runtimeKeepaliveCounter > 0, +#elif !EXIT_RUNTIME && !PTHREADS + // When `noExitRuntime` is not included and EXIT_RUNTIME=0 then we know the + // runtime can never exit (i.e. should always be kept alive). + // However, since pthreads themselves always need to be able to exit we + // have to track `runtimeKeepaliveCounter` in that case. + $keepRuntimeAlive: () => true, +#else + $keepRuntimeAlive__deps: ['$runtimeKeepaliveCounter'], + $keepRuntimeAlive: () => runtimeKeepaliveCounter > 0, +#endif + + // Callable in pthread without __proxy needed. + $runtimeKeepalivePush__deps: ['$runtimeKeepaliveCounter'], + $runtimeKeepalivePush__sig: 'v', + $runtimeKeepalivePush: () => { + runtimeKeepaliveCounter += 1; +#if RUNTIME_DEBUG + dbg(`runtimeKeepalivePush -> counter=${runtimeKeepaliveCounter}`); +#endif + }, + + $runtimeKeepalivePop__deps: ['$runtimeKeepaliveCounter'], + $runtimeKeepalivePop__sig: 'v', + $runtimeKeepalivePop: () => { +#if ASSERTIONS + assert(runtimeKeepaliveCounter > 0); +#endif + runtimeKeepaliveCounter -= 1; +#if RUNTIME_DEBUG + dbg(`runtimeKeepalivePop -> counter=${runtimeKeepaliveCounter}`); +#endif + }, + + emscripten_runtime_keepalive_push: '$runtimeKeepalivePush', + emscripten_runtime_keepalive_pop: '$runtimeKeepalivePop', + emscripten_runtime_keepalive_check: '$keepRuntimeAlive', + + // Used to call user callbacks from the embedder / event loop. For example + // setTimeout or any other kind of event handler that calls into user case + // needs to use this wrapper. + // + // The job of this wrapper is the handle emscripten-specific exceptions such + // as ExitStatus and 'unwind' and prevent these from escaping to the top + // level. + $callUserCallback__deps: ['$handleException', '$maybeExit'], + $callUserCallback: (func) => { +#if EXIT_RUNTIME + if (runtimeExited || ABORT) { +#else + if (ABORT) { +#endif +#if ASSERTIONS + err('user callback triggered after runtime exited or application aborted. Ignoring.'); +#endif + return; + } + try { + return func(); + } catch (e) { + handleException(e); + } finally { + maybeExit(); + } + }, + + $maybeExit__deps: ['exit', '$handleException', '$keepRuntimeAlive', +#if PTHREADS + '_emscripten_thread_exit', +#endif +#if RUNTIME_DEBUG >= 2 + '$runtimeKeepaliveCounter', +#endif + ], + $maybeExit: () => { +#if EXIT_RUNTIME + if (runtimeExited) { + return; + } +#endif +#if RUNTIME_DEBUG >= 2 + dbg(`maybeExit: user callback done: runtimeKeepaliveCounter=${runtimeKeepaliveCounter}`); +#endif + if (!keepRuntimeAlive()) { +#if RUNTIME_DEBUG + dbg(`maybeExit: calling exit() implicitly after user callback completed: ${EXITSTATUS}`); +#endif + try { +#if PTHREADS + if (ENVIRONMENT_IS_PTHREAD) { + // exit the current thread, but only if there is one active. + // TODO(https://github.com/emscripten-core/emscripten/issues/25076): + // Unify this check with the runtimeExited check above + if (_pthread_self()) __emscripten_thread_exit(EXITSTATUS); + return; + } +#endif + _exit(EXITSTATUS); + } catch (e) { + handleException(e); + } + } + }, + + $asyncLoad: async (url) => { + var arrayBuffer = await readAsync(url); + #if ASSERTIONS + assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); + #endif + return new Uint8Array(arrayBuffer); + }, + +#else // MINIMAL_RUNTIME + $callUserCallback: (func) => { + // MINIMAL_RUNTIME doesn't support the runtimeKeepalive stuff, but under + // some circumstances it supportes `runtimeExited` +#if EXIT_RUNTIME + if (runtimeExited) { +#if ASSERTIONS + err('user callback triggered after runtime exited or application aborted. Ignoring.'); +#endif + return; + } +#endif + func(); + }, +#endif // MINIMAL_RUNTIME + + $asmjsMangle: (x) => { + if (x == '__main_argc_argv') { + x = 'main'; + } +#if DYNCALLS + return x.startsWith('dynCall_') ? x : '_' + x; +#else + return '_' + x; +#endif + }, + + $alignMemory: (size, alignment) => { +#if ASSERTIONS + assert(alignment, "alignment argument is required"); +#endif + return Math.ceil(size / alignment) * alignment; + }, + + // Allocate memory for an mmap operation. This allocates space of the right + // page-aligned size, and clears the allocated space. +#if hasExportedSymbol('emscripten_builtin_memalign') + $mmapAlloc__deps: ['$zeroMemory', '$alignMemory'], +#endif + $mmapAlloc: (size) => { +#if hasExportedSymbol('emscripten_builtin_memalign') + size = alignMemory(size, {{{ WASM_PAGE_SIZE }}}); + var ptr = _emscripten_builtin_memalign({{{ WASM_PAGE_SIZE }}}, size); + if (ptr) zeroMemory(ptr, size); + return ptr; +#elif ASSERTIONS + abort('internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported'); +#else + abort(); +#endif + }, + + _emscripten_fs_load_embedded_files__deps: ['$FS', '$PATH'], + _emscripten_fs_load_embedded_files: (ptr) => { +#if RUNTIME_DEBUG + dbg('preloading data files'); +#endif + do { + var name_addr = {{{ makeGetValue('ptr', '0', '*') }}}; + ptr += {{{ POINTER_SIZE }}}; + var len = {{{ makeGetValue('ptr', '0', '*') }}}; + ptr += {{{ POINTER_SIZE }}}; + var content = {{{ makeGetValue('ptr', '0', '*') }}}; + ptr += {{{ POINTER_SIZE }}}; + var name = UTF8ToString(name_addr) +#if RUNTIME_DEBUG + dbg(`preloading files: ${name}`); +#endif + FS.createPath('/', PATH.dirname(name), true, true); + // canOwn this data in the filesystem, it is a slice of wasm memory that will never change + FS.createDataFile(name, null, HEAP8.subarray(content, content + len), true, true, /*canOwn=*/true); + } while ({{{ makeGetValue('ptr', '0', '*') }}}); +#if RUNTIME_DEBUG + dbg('done preloading data files'); +#endif + }, + + $HandleAllocator: class { + allocated = [undefined]; + freelist = []; + get(id) { +#if ASSERTIONS + assert(this.allocated[id] !== undefined, `invalid handle: ${id}`); +#endif + return this.allocated[id]; + } + has(id) { + return this.allocated[id] !== undefined; + } + allocate(handle) { + var id = this.freelist.pop() || this.allocated.length; + this.allocated[id] = handle; + return id; + } + free(id) { +#if ASSERTIONS + assert(this.allocated[id] !== undefined); +#endif + // Set the slot to `undefined` rather than using `delete` here since + // apparently arrays with holes in them can be less efficient. + this.allocated[id] = undefined; + this.freelist.push(id); + } + }, + + // `wasmTable` is a JS alias for the Wasm `__indirect_function_table` export + $wasmTable__docs: '/** @type {WebAssembly.Table} */', + $wasmTable: '__indirect_function_table', + +#if IMPORTED_MEMORY + // This gets defined in src/runtime_init_memory.js + $wasmMemory: undefined, +#else + // `wasmMemory` is a JS alias for the Wasm `memory` export + $wasmMemory: 'memory', +#endif + + $getUniqueRunDependency: (id) => { +#if ASSERTIONS + var orig = id; + while (1) { + if (!runDependencyTracking[id]) return id; + id = orig + Math.random(); + } +#else + return id; +#endif + }, + + $noExitRuntime__postset: () => addAtModule(makeModuleReceive('noExitRuntime')), + $noExitRuntime: {{{ !EXIT_RUNTIME }}}, + +#if !MINIMAL_RUNTIME + // A counter of dependencies for calling run(). If we need to + // do asynchronous work before running, increment this and + // decrement it. Incrementing must happen in a place like + // Module.preRun (used by emcc to add file preloading). + // Note that you can add dependencies in preRun, even though + // it happens right before run - run will be postponed until + // the dependencies are met. + $runDependencies__internal: true, + $runDependencies: 0, + // overridden to take different actions when all run dependencies are fulfilled + $dependenciesFulfilled__internal: true, + $dependenciesFulfilled: null, +#if ASSERTIONS + $runDependencyTracking__internal: true, + $runDependencyTracking: {}, + $runDependencyWatcher__internal: true, + $runDependencyWatcher: null, +#endif + + $addRunDependency__deps: ['$runDependencies', '$removeRunDependency', +#if ASSERTIONS + '$runDependencyTracking', + '$runDependencyWatcher', +#endif + ], + $addRunDependency: (id) => { + runDependencies++; + +#if expectToReceiveOnModule('monitorRunDependencies') + Module['monitorRunDependencies']?.(runDependencies); +#endif + +#if ASSERTIONS +#if RUNTIME_DEBUG + dbg('addRunDependency', id); +#endif + assert(id, 'addRunDependency requires an ID') + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && globalThis.setInterval) { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); +#if ENVIRONMENT_MAY_BE_NODE + // Prevent this timer from keeping the runtime alive if nothing + // else is. + runDependencyWatcher.unref?.() +#endif + } +#endif + }, + + $removeRunDependency__deps: ['$runDependencies', '$dependenciesFulfilled', +#if ASSERTIONS + '$runDependencyTracking', + '$runDependencyWatcher', +#endif + ], + $removeRunDependency: (id) => { + runDependencies--; + +#if expectToReceiveOnModule('monitorRunDependencies') + Module['monitorRunDependencies']?.(runDependencies); +#endif + +#if ASSERTIONS +#if RUNTIME_DEBUG + dbg('removeRunDependency', id); +#endif + assert(id, 'removeRunDependency requires an ID'); + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; +#endif + if (runDependencies == 0) { +#if ASSERTIONS + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } +#endif + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } + }, +#endif + + // The following addOn functions are for adding runtime callbacks at + // various executions points. Each addOn function has a corresponding + // compiled time version named addAt that will instead inline during + // compilation (see parseTools.mjs). + // Note: if there are both runtime and compile time code, the runtime + // callbacks will be invoked before the compile time code. + + // See ATPRERUNS in parseTools.mjs for more information. + $onPreRuns: [], + $onPreRuns__internal: true, + $onPreRuns__deps: ['$callRuntimeCallbacks'], + $onPreRuns__postset: () => { + ATPRERUNS.unshift('callRuntimeCallbacks(onPreRuns);'); + }, + $addOnPreRun__deps: ['$onPreRuns'], + $addOnPreRun: (cb) => onPreRuns.push(cb), + // See ATINITS in parseTools.mjs for more information. + $onInits: [], + $onInits__internal: true, + $onInits__deps: ['$callRuntimeCallbacks'], + $onInits__postset: () => { + ATINITS.unshift('callRuntimeCallbacks(onInits);'); + }, + $addOnInit__deps: ['$onInits'], + $addOnInit: (cb) => onInits.push(cb), + // See ATPOSTCTORS in parseTools.mjs for more information. + $onPostCtors: [], + $onPostCtors__internal: true, + $onPostCtors__deps: ['$callRuntimeCallbacks'], + $onPostCtors__postset: () => { + ATPOSTCTORS.unshift('callRuntimeCallbacks(onPostCtors);'); + }, + $addOnPostCtor__deps: ['$onPostCtors'], + $addOnPostCtor: (cb) => onPostCtors.push(cb), + // See ATMAINS in parseTools.mjs for more information. + $onMains: [], + $onMains__internal: true, + $onMains__deps: ['$callRuntimeCallbacks'], + $onMains__postset: () => { + ATMAINS.unshift('callRuntimeCallbacks(onMains);'); + }, + $addOnPreMain__deps: ['$onMains'], + $addOnPreMain: (cb) => onMains.push(cb), + // See ATEXITS in parseTools.mjs for more information. + $onExits: [], + $onExits__internal: true, + $onExits__deps: ['$callRuntimeCallbacks'], + $onExits__postset: () => { + ATEXITS.unshift('callRuntimeCallbacks(onExits);'); + }, + $addOnExit__deps: ['$onExits'], + $addOnExit: (cb) => onExits.push(cb), + // See ATPOSTRUNS in parseTools.mjs for more information. + $onPostRuns: [], + $onPostRuns__internal: true, + $onPostRuns__deps: ['$callRuntimeCallbacks'], + $onPostRuns__postset: () => { + ATPOSTRUNS.unshift('callRuntimeCallbacks(onPostRuns);'); + }, + $addOnPostRun__deps: ['$onPostRuns'], + $addOnPostRun: (cb) => onPostRuns.push(cb), + + // We used to define these globals unconditionally in support code. + // Instead, we now define them here so folks can pull it in explicitly, on + // demand. + $STACK_SIZE: {{{ STACK_SIZE }}}, + $STACK_ALIGN: {{{ STACK_ALIGN }}}, + $POINTER_SIZE: {{{ POINTER_SIZE }}}, + $ASSERTIONS: {{{ ASSERTIONS }}}, +}); + +function autoAddDeps(lib, name) { + for (const item of Object.keys(lib)) { + if (!isDecorator(item)) { + lib[item + '__deps'] ??= []; + lib[item + '__deps'].push(name); + } + } +} + +#if LEGACY_RUNTIME +// Library functions that were previously included as runtime functions are +// automatically included when `LEGACY_RUNTIME` is set. +extraLibraryFuncs.push( + '$addFunction', + '$removeFunction', + '$allocate', + '$ALLOC_NORMAL', + '$ALLOC_STACK', + '$AsciiToString', + '$stringToAscii', + '$UTF16ToString', + '$stringToUTF16', + '$lengthBytesUTF16', + '$UTF32ToString', + '$stringToUTF32', + '$lengthBytesUTF32', + '$stringToNewUTF8', + '$stringToUTF8OnStack', + '$writeStringToMemory', + '$writeArrayToMemory', + '$writeAsciiToMemory', + '$intArrayFromString', + '$intArrayToString', + '$warnOnce', + '$ccall', + '$cwrap', + '$ExitStatus', + '$UTF8ArrayToString', + '$UTF8ToString', + '$stringToUTF8Array', + '$stringToUTF8', + '$lengthBytesUTF8', +); +#endif + +function wrapSyscallFunction(x, library, isWasi) { + if (isJsOnlySymbol(x) || isDecorator(x)) { + return; + } + + var t = library[x]; + if (typeof t == 'string') return; + t = t.toString(); + + // If a syscall uses FS, but !SYSCALLS_REQUIRE_FILESYSTEM, then the user + // has disabled the filesystem or we have proven some other way that this will + // not be called in practice, and do not need that code. + if (!SYSCALLS_REQUIRE_FILESYSTEM && t.includes('FS.')) { + library[x + '__deps'] = []; + t = modifyJSFunction(t, (args, body) => { + return `(${args}) => {\n` + + (ASSERTIONS ? "abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM');\n" : '') + + '}'; + }); + } + + var isVariadic = !isWasi && t.includes(', varargs'); +#if SYSCALLS_REQUIRE_FILESYSTEM + var canThrow = library[x + '__nothrow'] !== true; +#else + var canThrow = false; +#endif + + library[x + '__deps'] ??= []; + +#if PURE_WASI && !GROWABLE_ARRAYBUFFERS + // In PURE_WASI mode we can't assume the wasm binary was built by emscripten + // and politely notify us on memory growth. Instead we have to check for + // possible memory growth on each syscall. + var pre = '\nif (!HEAPU8.byteLength) _emscripten_notify_memory_growth(0);\n' + library[x + '__deps'].push('emscripten_notify_memory_growth'); +#else + var pre = ''; +#endif + var post = ''; + if (isVariadic) { + pre += 'SYSCALLS.varargs = varargs;\n'; + } + +#if SYSCALL_DEBUG + if (isVariadic) { + if (canThrow) { + post += 'finally { SYSCALLS.varargs = undefined; }\n'; + } else { + post += 'SYSCALLS.varargs = undefined;\n'; + } + } + pre += `dbg('syscall! ${x}: [' + Array.prototype.slice.call(arguments) + ']');\n`; + pre += "var canWarn = true;\n"; + pre += "var ret = (() => {"; + post += "})();\n"; + post += "if (ret && ret < 0 && canWarn) {\n"; + post += " dbg(`error: syscall may have failed with ${-ret} (${strError(-ret)})`);\n"; + post += "}\n"; + post += "dbg(`syscall return: ${ret}`);\n"; + post += "return ret;\n"; + // Emit dependency to strError() since we added use of it above. + library[x + '__deps'].push('$strError'); +#endif + delete library[x + '__nothrow']; + var handler = ''; + if (canThrow) { + pre += 'try {\n'; + handler += + "} catch (e) {\n" + + " if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;\n"; +#if SYSCALL_DEBUG + handler += + " dbg(`error: syscall failed with ${e.errno} (${strError(e.errno)})`);\n" + + " canWarn = false;\n"; +#endif + // Musl syscalls are negated. + if (isWasi) { + handler += " return e.errno;\n"; + } else { + // Musl syscalls are negated. + handler += " return -e.errno;\n"; + } + handler += "}\n"; + } + post = handler + post; + + if (pre || post) { + t = modifyJSFunction(t, (args, body, async_) => `${async_}function (${args}) {\n${pre}${body}${post}}\n`); + } + + library[x] = t; + // Automatically add dependency on `$SYSCALLS` + if (!WASMFS && t.includes('SYSCALLS')) { + library[x + '__deps'].push('$SYSCALLS'); + } +#if PTHREADS + // Most syscalls need to happen on the main JS thread (e.g. because the + // filesystem is in JS and on that thread). Proxy synchronously to there. + // There are some exceptions, syscalls that we know are ok to just run in + // any thread; those are marked as not being proxied with + // __proxy: false + // A syscall without a return value could perhaps be proxied asynchronously + // instead of synchronously, and marked with + // __proxy: 'async' + // (but essentially all syscalls do have return values). + if (library[x + '__proxy'] === undefined) { + library[x + '__proxy'] = 'sync'; + } +#endif +} diff --git a/src/lib/libdylink.js b/src/lib/libdylink.js new file mode 100644 index 0000000000000..ab8ba1dd5b293 --- /dev/null +++ b/src/lib/libdylink.js @@ -0,0 +1,1419 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + * + * Dynamic library loading + */ + +#if !MAIN_MODULE +#error "library_dylink.js requires MAIN_MODULE" +#endif + +{{{ +const UNDEFINED_ADDR = to64(-1); +}}} + +var LibraryDylink = { +#if FILESYSTEM + $registerWasmPlugin__deps: ['$preloadPlugins'], + $registerWasmPlugin: () => { + // Use string keys here for public methods to avoid minification since the + // plugin consumer also uses string keys. + var wasmPlugin = { + promiseChainEnd: Promise.resolve(), + 'canHandle': (name) => { + return !Module['noWasmDecoding'] && name.endsWith('.so') + }, + 'handle': async (byteArray, name) => + // loadWebAssemblyModule can not load modules out-of-order, so rather + // than just running the promises in parallel, this makes a chain of + // promises to run in series. + wasmPlugin.promiseChainEnd = wasmPlugin.promiseChainEnd.then(async () => { + try { + var exports = await loadWebAssemblyModule(byteArray, {loadAsync: true, nodelete: true}, name, {}); + } catch (error) { + throw new Error(`failed to instantiate wasm: ${name}: ${error}`); + } +#if DYLINK_DEBUG + dbg('registering preloadedWasm:', name); +#endif + preloadedWasm[name] = exports; + return byteArray; + }) + }; + preloadPlugins.push(wasmPlugin); + }, + + $preloadedWasm__deps: ['$registerWasmPlugin'], + $preloadedWasm__postset: ` + registerWasmPlugin(); + `, + $preloadedWasm: {}, + + $replaceORIGIN__deps: ['$PATH'], + $replaceORIGIN: (parentLibName, rpath) => { + if (rpath.startsWith('$ORIGIN')) { + // TODO: what to do if we only know the relative path of the file? It will return "." here. + var origin = PATH.dirname(parentLibName); + return rpath.replace('$ORIGIN', origin); + } + + return rpath; + }, +#endif // FILESYSTEM + + $isSymbolDefined: (symName) => { + // Ignore 'stub' symbols that are auto-generated as part of the original + // `wasmImports` used to instantiate the main module. + var existing = wasmImports[symName]; + if (!existing || existing.stub) { + return false; + } +#if ASYNCIFY + // Even if a symbol exists in wasmImports, and is not itself a stub, it + // could be an ASYNCIFY wrapper function that wraps a stub function. + if (symName in asyncifyStubs && !asyncifyStubs[symName]) { + return false; + } +#endif + return true; + }, + + // Dynamic version of shared.py:make_invoke. This is needed for invokes + // that originate from side modules since these are not known at JS + // generation time. +#if !DISABLE_EXCEPTION_CATCHING || SUPPORT_LONGJMP == 'emscripten' + $createInvokeFunction__internal: true, + $createInvokeFunction__deps: ['$dynCall', 'setThrew', '$stackSave', '$stackRestore'], + $createInvokeFunction: (sig) => (ptr, ...args) => { + var sp = stackSave(); + try { + return dynCall(sig, ptr, args); + } catch(e) { + stackRestore(sp); + // Create a try-catch guard that rethrows the Emscripten EH exception. + // Exceptions thrown from C++ and longjmps will be an instance of + // EmscriptenEH. + if (!(e instanceof EmscriptenEH)) throw e; + _setThrew(1, 0); +#if WASM_BIGINT + // In theory this if statement could be done on + // creating the function, but I just added this to + // save wasting code space as it only happens on exception. + if (sig[0] == "j") return 0n; +#endif + } + }, +#endif + + // Resolve a global symbol by name. This is used during module loading to + // resolve imports, and by `dlsym` when used with `RTLD_DEFAULT`. + // Returns both the resolved symbol (i.e. a function or a global) along with + // the canonical name of the symbol (in some cases modifying the symbol as + // part of the loop process, so that actual symbol looked up has a different + // name). + $resolveGlobalSymbol__deps: ['$isSymbolDefined', '$createNamedFunction', +#if !DISABLE_EXCEPTION_CATCHING || SUPPORT_LONGJMP == 'emscripten' + '$createInvokeFunction', +#endif + ], + $resolveGlobalSymbol__internal: true, + $resolveGlobalSymbol: (symName, direct = false) => { + var sym; +#if !WASM_BIGINT + // First look for the orig$ symbol which is the symbol without i64 + // legalization performed. + if (direct && ('orig$' + symName in wasmImports)) { + symName = 'orig$' + symName; + } +#endif + if (isSymbolDefined(symName)) { + sym = wasmImports[symName]; + } +#if !DISABLE_EXCEPTION_CATCHING || SUPPORT_LONGJMP == 'emscripten' + // Asm.js-style exception handling: invoke wrapper generation + else if (symName.startsWith('invoke_')) { + // Create (and cache) new invoke_ functions on demand. + sym = wasmImports[symName] = createNamedFunction(symName, createInvokeFunction(symName.split('_')[1])); + } +#endif +#if !DISABLE_EXCEPTION_CATCHING + else if (symName.startsWith('__cxa_find_matching_catch_')) { + // When the main module is linked we create whichever variants of + // `__cxa_find_matching_catch_` (see jsifier.js) that we know are needed, + // but a side module loaded at runtime might need different/additional + // variants so we create those dynamically. + sym = wasmImports[symName] = createNamedFunction(symName, (...args) => { +#if MEMORY64 + args = args.map(Number); +#endif + var rtn = findMatchingCatch(args); + return {{{ to64('rtn') }}}; + }); + } +#endif + return {sym, name: symName}; + }, + + $GOT: {}, + + // Proxy handler used for GOT.mem and GOT.func imports. Each of these + // imports is fulfilled dynamically via the `get` method of this proxy + // handler. We abuse the `target` of the Proxy in order to pass the set of + // weak imports to the handler. + $GOTHandler__internal: true, + $GOTHandler__deps: ['$GOT'], + $GOTHandler: { + get(weakImports, symName) { + var rtn = GOT[symName]; + if (!rtn) { +#if DYLINK_DEBUG == 2 + dbg(`new GOT entry: ${symName}`); +#endif + rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, {{{ UNDEFINED_ADDR }}}); + } + if (!weakImports.has(symName)) { + // Any non-weak reference to a symbol marks it as `required`, which + // enabled `reportUndefinedSymbols` to report undefined symbol errors + // correctly. + rtn.required = true; + } + return rtn; + } + }, + + $isInternalSym__internal: true, + $isInternalSym: (symName) => { + // TODO: find a way to mark these in the binary or avoid exporting them. + return [ + 'memory', + '__memory_base', + '__table_base', + '__stack_pointer', + '__indirect_function_table', + '__cpp_exception', + '__c_longjmp', + '__wasm_apply_data_relocs', + '__dso_handle', + '__tls_size', + '__tls_align', + '__set_stack_limits', + '_emscripten_tls_init', + '__wasm_init_tls', + '__wasm_call_ctors', + '__start_em_asm', + '__stop_em_asm', + '__start_em_js', + '__stop_em_js', + ].includes(symName) || symName.startsWith('__em_js__') +#if SPLIT_MODULE + // Exports synthesized by wasm-split should be prefixed with '%' + || symName[0] == '%' +#endif + ; + }, + + $updateGOT__internal: true, + $updateGOT__deps: ['$GOT', '$isInternalSym', '$addFunction'], + $updateGOT__docs: '/** @param {boolean=} replace */', + $updateGOT: (exports, replace) => { +#if DYLINK_DEBUG + dbg(`updateGOT: adding ${Object.keys(exports).length} symbols`); +#endif + for (var symName in exports) { + if (isInternalSym(symName)) { + continue; + } + + var value = exports[symName]; +#if !WASM_BIGINT + if (symName.startsWith('orig$')) { + symName = symName.split('$')[1]; + replace = true; + } +#endif + + var existingEntry = GOT[symName] && GOT[symName].value != {{{ UNDEFINED_ADDR }}}; + if (replace || !existingEntry) { +#if DYLINK_DEBUG == 2 + dbg(`updateGOT: before: ${symName} : ${GOT[symName]?.value}`); +#endif + var newValue; + if (typeof value == 'function') { + newValue = {{{ to64('addFunction(value)') }}}; + } else if (typeof value.value == {{{ POINTER_JS_TYPE }}}) { + newValue = value; + } else { + // The GOT can only contain addresses (i.e data addresses or function + // addresses so we currently ignore other types export here. +#if DYLINK_DEBUG + dbg(`updateGOT: ignoring ${symName} due to its type: ${typeof value}`); +#endif + continue; + } +#if DYLINK_DEBUG == 2 + dbg(`updateGOT: after: ${symName} : ${newValue} (${value})`); +#endif + GOT[symName] ??= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}); + GOT[symName].value = newValue; + } +#if DYLINK_DEBUG + else if (GOT[symName].value != value) { + dbg(`updateGOT: EXISTING SYMBOL: ${symName} : ${GOT[symName].value} (${value})`); + } +#endif + } +#if DYLINK_DEBUG + dbg("done updateGOT"); +#endif + }, + + $isImmutableGlobal__internal: true, + $isImmutableGlobal: (val) => { + if (val instanceof WebAssembly.Global) { + try { + val.value = val.value; + } catch { + return true; + } + } + return false; + }, + + // Applies relocations to exported things. + $relocateExports__internal: true, + $relocateExports__deps: ['$isImmutableGlobal'], + $relocateExports: (exports, memoryBase = 0) => { +#if DYLINK_DEBUG + dbg(`relocateExports memoryBase=${memoryBase} count=${Object.keys(exports).length}`); +#endif + + function relocateExport(name, value) { +#if SPLIT_MODULE + // Do not modify exports synthesized by wasm-split + if (name.startsWith('%')) { + return value; + } +#endif + // Detect immutable wasm global exports. These represent data addresses + // which are relative to `memoryBase` + if (isImmutableGlobal(value)) { + return new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}'}, value.value + {{{ to64('memoryBase') }}}); + } + + // Return unmodified value (no relocation required). + return value; + } + + var relocated = {}; + for (var e in exports) { + relocated[e] = relocateExport(e, exports[e]) + } + return relocated; + }, + + $reportUndefinedSymbols__internal: true, + $reportUndefinedSymbols__deps: ['$GOT', '$resolveGlobalSymbol'], + $reportUndefinedSymbols: () => { +#if DYLINK_DEBUG + dbg('reportUndefinedSymbols'); +#endif + for (var [symName, entry] of Object.entries(GOT)) { + if (entry.value == {{{ UNDEFINED_ADDR }}}) { +#if DYLINK_DEBUG + dbg(`undef GOT entry: ${symName}`); +#endif + var value = resolveGlobalSymbol(symName, true).sym; + if (!value && !entry.required) { + // Ignore undefined symbols that are imported as weak. +#if DYLINK_DEBUG + dbg('ignoring undefined weak symbol:', symName); +#endif + entry.value = {{{ to64(0) }}}; + continue; + } +#if ASSERTIONS + assert(value, `undefined symbol '${symName}'. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment`); +#endif +#if DYLINK_DEBUG == 2 + dbg(`assigning dynamic symbol from main module: ${symName} -> ${prettyPrint(value)}`); +#endif + if (typeof value == 'function') { + /** @suppress {checkTypes} */ + entry.value = {{{ to64('addFunction(value, value.sig)') }}}; +#if DYLINK_DEBUG == 2 + dbg(`assigning table entry for : ${symName} -> ${entry.value}`); +#endif + } else if (typeof value == 'number') { + entry.value = {{{ to64('value') }}}; + } else if (typeof value.value == {{{ POINTER_JS_TYPE }}}) { + entry.value = value; + } else { + throw new Error(`bad export type for '${symName}': ${typeof value} (${value})`); + } + } + } +#if DYLINK_DEBUG + dbg('done reportUndefinedSymbols'); +#endif + }, + + // dynamic linker/loader (a-la ld.so on ELF systems) + $LDSO__deps: ['$newDSO'], + $LDSO: { + // name -> dso [refcount, name, module, global]; Used by dlopen + loadedLibsByName: {}, + // handle -> dso; Used by dlsym + loadedLibsByHandle: {}, + init() { +#if ASSERTIONS + // This function needs to run after the initial wasmImports object + // as been created. + assert(wasmImports); +#endif + newDSO('__main__', {{{ cDefs.RTLD_DEFAULT }}}, wasmImports); + }, + }, + + $dlSetError__internal: true, + $dlSetError__deps: ['__dl_seterr', '$stringToUTF8OnStack', '$stackSave', '$stackRestore'], + $dlSetError: (msg) => { +#if DYLINK_DEBUG + dbg('dlSetError:', msg); +#endif + var sp = stackSave(); + var cmsg = stringToUTF8OnStack(msg); + ___dl_seterr(cmsg, 0); + stackRestore(sp); + }, + + // We support some amount of allocation during startup in the case of + // dynamic linking, which needs to allocate memory for dynamic libraries that + // are loaded. That has to happen before the main program can start to run, + // because the main program needs those linked in before it runs (so we can't + // use normally malloc from the main program to do these allocations). + // + // Allocate memory even if malloc isn't ready yet. The allocated memory here + // must be zero initialized since its used for all static data, including bss. + $getMemory__noleakcheck: true, + $getMemory__deps: ['$GOT', 'emscripten_get_sbrk_ptr', '__heap_base', '$alignMemory', 'calloc'], + $getMemory: (size) => { + // After the runtime is initialized, we must only use sbrk() normally. +#if DYLINK_DEBUG + dbg("getMemory: " + size + " runtimeInitialized=" + runtimeInitialized); +#endif + if (runtimeInitialized) { + // Currently we don't support freeing of static data when modules are + // unloaded via dlclose. This function is tagged as `noleakcheck` to + // avoid having this reported as leak. + return _calloc(size, 1); + } + var ret = ___heap_base; + // Keep __heap_base stack aligned. + var end = ret + alignMemory(size, {{{ STACK_ALIGN }}}); +#if ASSERTIONS + //dbg(ret); + //dbg(HEAP8.length); + assert(end <= HEAP8.length, 'failure to getMemory - memory growth etc. is not supported there, call malloc/sbrk directly or increase INITIAL_MEMORY'); +#endif + ___heap_base = end; + + // After allocating the memory from the start of the heap we need to ensure + // that once the program starts it doesn't use this region. In relocatable + // mode we can just update the __heap_base symbol that we are exporting to + // the main module. +#if PTHREADS + if (!ENVIRONMENT_IS_PTHREAD) { +#endif + var sbrk_ptr = _emscripten_get_sbrk_ptr(); + {{{ makeSetValue('sbrk_ptr', 0, 'end', '*') }}} +#if PTHREADS + } +#endif + return ret; + }, + + // returns the side module metadata as an object + // { memorySize, memoryAlign, tableSize, tableAlign, neededDynlibs} + $getDylinkMetadata__deps: ['$UTF8ArrayToString'], + $getDylinkMetadata__internal: true, + $getDylinkMetadata: (binary) => { + var offset = 0; + var end = 0; + + function getU8() { + return binary[offset++]; + } + + function getLEB() { + var ret = 0; + var mul = 1; + while (1) { + var byte = binary[offset++]; + ret += ((byte & 0x7f) * mul); + mul *= 0x80; + if (!(byte & 0x80)) break; + } + return ret; + } + + function getString() { + var len = getLEB(); + offset += len; + return UTF8ArrayToString(binary, offset - len, len); + } + + function getStringList() { + var count = getLEB(); + var rtn = [] + while (count--) rtn.push(getString()); + return rtn; + } + + /** @param {string=} message */ + function failIf(condition, message) { + if (condition) throw new Error(message); + } + + if (binary instanceof WebAssembly.Module) { + var dylinkSection = WebAssembly.Module.customSections(binary, 'dylink.0'); + failIf(dylinkSection.length === 0, 'need dylink section'); + binary = new Uint8Array(dylinkSection[0]); + end = binary.length + } else { + var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer); +#if SUPPORT_BIG_ENDIAN + var magicNumberFound = int32View[0] == 0x6d736100 || int32View[0] == 0x0061736d; +#else + var magicNumberFound = int32View[0] == 0x6d736100; +#endif + failIf(!magicNumberFound, 'need to see wasm magic number'); // \0asm + // we should see the dylink custom section right after the magic number and wasm version + failIf(binary[8] !== 0, 'need the dylink section to be first') + offset = 9; + var section_size = getLEB(); // section size + end = offset + section_size; + var name = getString(); + failIf(name !== 'dylink.0'); + } + + var customSection = { neededDynlibs: [], tlsExports: new Set(), weakImports: new Set(), runtimePaths: [] }; + var WASM_DYLINK_MEM_INFO = 0x1; + var WASM_DYLINK_NEEDED = 0x2; + var WASM_DYLINK_EXPORT_INFO = 0x3; + var WASM_DYLINK_IMPORT_INFO = 0x4; + var WASM_DYLINK_RUNTIME_PATH = 0x5; + var WASM_SYMBOL_TLS = 0x100; + var WASM_SYMBOL_BINDING_MASK = 0x3; + var WASM_SYMBOL_BINDING_WEAK = 0x1; + while (offset < end) { + var subsectionType = getU8(); + var subsectionSize = getLEB(); + if (subsectionType === WASM_DYLINK_MEM_INFO) { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + } else if (subsectionType === WASM_DYLINK_NEEDED) { + customSection.neededDynlibs = getStringList(); + } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { + var count = getLEB(); + while (count--) { + var symname = getString(); + var flags = getLEB(); + if (flags & WASM_SYMBOL_TLS) { + customSection.tlsExports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { + var count = getLEB(); + while (count--) { + var modname = getString(); + var symname = getString(); + var flags = getLEB(); + if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) { + customSection.weakImports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_RUNTIME_PATH) { + customSection.runtimePaths = getStringList(); + } else { +#if ASSERTIONS + err('unknown dylink.0 subsection:', subsectionType) +#endif + // unknown subsection + offset += subsectionSize; + } + } + +#if ASSERTIONS + var tableAlign = Math.pow(2, customSection.tableAlign); + assert(tableAlign === 1, `invalid tableAlign ${tableAlign}`); + assert(offset == end); +#endif + +#if DYLINK_DEBUG + dbg('dylink needed:', customSection.neededDynlibs); +#endif + + return customSection; + }, + +#if DYNCALLS || !WASM_BIGINT + $registerDynCallSymbols: (exports) => { + for (var [sym, exp] of Object.entries(exports)) { + if (sym.startsWith('dynCall_')) { + var sig = sym.substring(8); + if (!dynCalls.hasOwnProperty(sig)) { + dynCalls[sig] = exp; + } + } + } + }, +#endif + + // Module.symbols <- libModule.symbols (flags.global handler) + $mergeLibSymbols__deps: ['$isSymbolDefined'], + $mergeLibSymbols: (exports, libName) => { +#if DYNCALLS || !WASM_BIGINT + registerDynCallSymbols(exports); +#endif + // add symbols into global namespace TODO: weak linking etc. + for (var [sym, exp] of Object.entries(exports)) { +#if ASSERTIONS == 2 + if (isSymbolDefined(sym)) { + var curr = wasmImports[sym], next = exp; + // don't warn on functions - might be odr, linkonce_odr, etc. + if (!(typeof curr == 'function' && typeof next == 'function')) { + err(`warning: symbol '${sym}' from '${libName}' already exists (duplicate symbol? or weak linking, which isn't supported yet?)`); // + [curr, ' vs ', next]); + } + } +#endif + + // When RTLD_GLOBAL is enabled, the symbols defined by this shared object + // will be made available for symbol resolution of subsequently loaded + // shared objects. + // + // We should copy the symbols (which include methods and variables) from + // SIDE_MODULE to MAIN_MODULE. + const setImport = (target) => { +#if ASYNCIFY + if (target in asyncifyStubs) { + asyncifyStubs[target] = exp; + } +#endif + if (!isSymbolDefined(target)) { + wasmImports[target] = exp; + } + } + setImport(sym); + +#if !hasExportedSymbol('main') + // Special case for handling of main symbol: If a side module exports + // `main` that also acts a definition for `__main_argc_argv` and vice + // versa. + const main_alias = '__main_argc_argv'; + if (sym == 'main') { + setImport(main_alias) + } + if (sym == main_alias) { + setImport('main') + } +#endif + } + }, + +#if DYLINK_DEBUG + $dumpTable__deps: ['$wasmTable'], + $dumpTable: () => { + var len = wasmTable.length; + for (var i = {{{ toIndexType(0) }}} ; i < len; i++) { + dbg(`table: ${i} : ${wasmTable.get(i)}`); + } + }, +#endif + + // Loads a side module from binary data or compiled Module. Returns the module's exports or a + // promise that resolves to its exports if the loadAsync flag is set. + $loadWebAssemblyModule__docs: ` + /** + * @param {string=} libName + * @param {Object=} localScope + * @param {number=} handle + */`, + $loadWebAssemblyModule__deps: [ + '$loadDynamicLibrary', '$getMemory', '$updateGOT', + '$relocateExports', '$resolveGlobalSymbol', '$GOTHandler', + '$getDylinkMetadata', '$alignMemory', + '$updateTableMap', + '$wasmTable', + '$addOnPostCtor', + ], + $loadWebAssemblyModule: (binary, flags, libName, localScope, handle) => { +#if DYLINK_DEBUG + dbg('loadWebAssemblyModule:', libName, handle); +#endif + var metadata = getDylinkMetadata(binary); + + // loadModule loads the wasm module after all its dependencies have been loaded. + // can be called both sync/async. + function loadModule() { +#if ASSERTIONS + var originalTable = wasmTable; +#endif +#if PTHREADS + // The first thread to load a given module needs to allocate the static + // table and memory regions. Later threads re-use the same table region + // and can ignore the memory region (since memory is shared between + // threads already). + // If `handle` is specified then it is assumed that the calling thread has + // exclusive access to it for the duration of this function. See the + // locking in `dynlink.c`. + var firstLoad = !handle || !{{{ makeGetValue('handle', C_STRUCTS.dso.mem_allocated, 'i8') }}}; +#if DYLINK_DEBUG + dbg('firstLoad:', firstLoad); +#endif + if (firstLoad) { +#endif + // alignments are powers of 2 + var memAlign = Math.pow(2, metadata.memoryAlign); + // prepare memory + var memoryBase = metadata.memorySize ? alignMemory(getMemory(metadata.memorySize + memAlign), memAlign) : 0; // TODO: add to cleanups + var tableBase = metadata.tableSize ? {{{ from64Expr('wasmTable.length') }}} : 0; + if (handle) { + {{{ makeSetValue('handle', C_STRUCTS.dso.mem_allocated, '1', 'i8') }}}; + {{{ makeSetValue('handle', C_STRUCTS.dso.mem_addr, 'memoryBase', '*') }}}; + {{{ makeSetValue('handle', C_STRUCTS.dso.mem_size, 'metadata.memorySize', 'i32') }}}; + {{{ makeSetValue('handle', C_STRUCTS.dso.table_addr, 'tableBase', '*') }}}; + {{{ makeSetValue('handle', C_STRUCTS.dso.table_size, 'metadata.tableSize', 'i32') }}}; + } +#if PTHREADS + } else { + // Read the values for tableBase and memoryBase from shared memory. The + // thread that first loaded the DLL already set these values. + memoryBase = {{{ makeGetValue('handle', C_STRUCTS.dso.mem_addr, '*') }}}; + tableBase = {{{ makeGetValue('handle', C_STRUCTS.dso.table_addr, '*') }}}; + } +#endif + + if (metadata.tableSize) { +#if ASSERTIONS + assert({{{ from64Expr('wasmTable.length') }}} == tableBase, `unexpected table size while loading ${libName}: ${wasmTable.length}`); +#endif +#if DYLINK_DEBUG + dbg("loadModule: growing table by: " + metadata.tableSize); +#endif + wasmTable.grow({{{ toIndexType('metadata.tableSize') }}}); + } +#if DYLINK_DEBUG + dbg(`loadModule: memory[${memoryBase}:${memoryBase + metadata.memorySize}]` + + ` table[${tableBase}:${tableBase + metadata.tableSize}]`); +#endif + + // This is the export map that we ultimately return. We declare it here + // so it can be used within resolveSymbol. We resolve symbols against + // this local symbol map in the case where they are not present on the + // global Module object. We need this fallback because Modules sometime + // need to import their own symbols + var moduleExports; + + function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; + } + if (!resolved) { + resolved = moduleExports[sym]; + } +#if ASSERTIONS + assert(resolved, `undefined symbol '${sym}'. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment`); +#endif + return resolved; + } + + // TODO kill ↓↓↓ (except "symbols local to this module", it will likely be + // not needed if we require that if A wants symbols from B it has to link + // to B explicitly: similarly to -Wl,--no-undefined) + // + // wasm dynamic libraries are pure wasm, so they cannot assist in + // their own loading. When side module A wants to import something + // provided by a side module B that is loaded later, we need to + // add a layer of indirection, but worse, we can't even tell what + // to add the indirection for, without inspecting what A's imports + // are. To do that here, we use a JS proxy (another option would + // be to inspect the binary directly). + var proxyHandler = { + get(stubs, prop) { + // symbols that should be local to this module + switch (prop) { + case '__memory_base': + return {{{ to64('memoryBase') }}}; + case '__table_base': + return {{{ to64('tableBase') }}}; +#if MEMORY64 +#if MEMORY64 == 2 + case '__memory_base32': + return memoryBase; +#endif + case '__table_base32': + return tableBase; +#endif + } + if (prop in wasmImports && !wasmImports[prop].stub) { + // No stub needed, symbol already exists in symbol table + var res = wasmImports[prop]; +#if ASYNCIFY + // Asyncify wraps exports, and we need to look through those wrappers. + if (res.orig) { + res = res.orig; + } +#endif + return res; + } + // Return a stub function that will resolve the symbol + // when first called. + if (!(prop in stubs)) { + var resolved; + stubs[prop] = (...args) => { + resolved ||= resolveSymbol(prop); + return resolved(...args); + }; + } + return stubs[prop]; + } + }; + var proxy = new Proxy({}, proxyHandler); + var GOTProxy = new Proxy(metadata.weakImports, GOTHandler); + var info = { + 'GOT.mem': GOTProxy, + 'GOT.func': GOTProxy, + 'env': proxy, + '{{{ WASI_MODULE_NAME }}}': proxy, + }; + + function postInstantiation(module, instance) { +#if ASSERTIONS + // the table should be unchanged + assert(wasmTable === originalTable); +#endif +#if PTHREADS + if (!ENVIRONMENT_IS_PTHREAD && libName) { +#if DYLINK_DEBUG + dbg('registering sharedModules:', libName) +#endif + // cache all loaded modules in `sharedModules`, which gets passed + // to new workers when they are created. + sharedModules[libName] = module; + } +#endif + // add new entries to functionsInTableMap + updateTableMap(tableBase, metadata.tableSize); + moduleExports = relocateExports(instance.exports, memoryBase); + updateGOT(moduleExports); +#if ASYNCIFY + moduleExports = Asyncify.instrumentWasmExports(moduleExports); +#endif + if (!flags.allowUndefined) { + reportUndefinedSymbols(); + } +#if STACK_OVERFLOW_CHECK >= 2 + // If the runtime has already been initialized we set the stack limits + // now. Otherwise this is delayed until `setDylinkStackLimits` is + // called after initialization. + if (moduleExports['__set_stack_limits'] && runtimeInitialized) { + moduleExports['__set_stack_limits']({{{ to64('_emscripten_stack_get_base()') }}}, {{{ to64('_emscripten_stack_get_end()') }}}); + } +#endif + +#if MAIN_MODULE + function addEmAsm(addr, body) { + var args = []; + for (var arity = 0; ; arity++) { + var argName = '$' + arity; + if (!body.includes(argName)) break; + args.push(argName); + } + args = args.join(','); + var func = `(${args}) => { ${body} };`; +#if DYLINK_DEBUG + dbg('adding new EM_ASM constant at:', ptrToString(start)); +#endif + {{{ makeEval('ASM_CONSTS[start] = eval(func)') }}}; + } + + // Add any EM_ASM functions that exist in the side module + if ('__start_em_asm' in moduleExports) { + var start = moduleExports['__start_em_asm'].value; + var stop = moduleExports['__stop_em_asm'].value; +#if CAN_ADDRESS_2GB + start >>>= 0; + stop >>>= 0; +#else + {{{ from64('start') }}} + {{{ from64('stop') }}} +#endif + while (start < stop) { + var jsString = UTF8ToString(start); + addEmAsm(start, jsString); + start = HEAPU8.indexOf(0, start) + 1; + } + } + + function addEmJs(name, cSig, body) { + // The signature here is a C signature (e.g. "(int foo, char* bar)"). + // See `create_em_js` in emcc.py` for the build-time version of this + // code. + var jsArgs = []; + cSig = cSig.slice(1, -1) + if (cSig != 'void') { + cSig = cSig.split(','); + for (var arg of cSig) { + var jsArg = arg.split(' ').pop(); + jsArgs.push(jsArg.replaceAll('*', '')); + } + } + var func = `(${jsArgs}) => ${body};`; +#if DYLINK_DEBUG + dbg(`adding new EM_JS function: ${jsArgs} = ${func}`); +#endif + {{{ makeEval('moduleExports[name] = eval(func)') }}}; + } + + for (var name in moduleExports) { + if (name.startsWith('__em_js__')) { + var start = moduleExports[name].value + var jsString = UTF8ToString({{{ from64Expr('start') }}}); + // EM_JS strings are stored in the data section in the form + // SIG<::>BODY. + var [sig, body] = jsString.split('<::>'); + addEmJs(name.replace('__em_js__', ''), sig, body); + delete moduleExports[name]; + } + } +#endif + + // initialize the module +#if PTHREADS + // Only one thread should call __wasm_call_ctors, but all threads need + // to call _emscripten_tls_init + registerTLSInit(moduleExports['_emscripten_tls_init'], instance.exports, metadata) + if (firstLoad) { +#endif + var applyRelocs = moduleExports['__wasm_apply_data_relocs']; + if (applyRelocs) { + if (runtimeInitialized) { +#if DYLINK_DEBUG + dbg('running __wasm_apply_data_relocs'); +#endif + applyRelocs(); + } else { +#if DYLINK_DEBUG + dbg('delaying __wasm_apply_data_relocs'); +#endif + __RELOC_FUNCS__.push(applyRelocs); + } + } + var init = moduleExports['__wasm_call_ctors']; + if (init) { + if (runtimeInitialized) { +#if DYLINK_DEBUG + dbg('running __wasm_call_ctors'); +#endif + init(); + } else { +#if DYLINK_DEBUG + dbg('delaying __wasm_call_ctors'); +#endif + // we aren't ready to run compiled code yet + addOnPostCtor(init); + } + } +#if PTHREADS + } +#endif + return moduleExports; + } + + if (flags.loadAsync) { + return (async () => { + var instance; + if (binary instanceof WebAssembly.Module) { + instance = new WebAssembly.Instance(binary, info); + } else { + // Destructuring assignment without declaration has to be wrapped + // with parens or parser will treat the l-value as an object + // literal instead. + ({ module: binary, instance } = await WebAssembly.instantiate(binary, info)); + } + return postInstantiation(binary, instance); + })(); + } + + var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary); + var instance = new WebAssembly.Instance(module, info); + return postInstantiation(module, instance); + } + + // We need to set rpath in flags based on the current library's rpath. + // We can't mutate flags or else if a depends on b and c and b depends on d, + // then c will be loaded with b's rpath instead of a's. + flags = {...flags, rpath: { parentLibPath: libName, paths: metadata.runtimePaths }} + // now load needed libraries and the module itself. + if (flags.loadAsync) { + return metadata.neededDynlibs + .reduce((chain, needed) => chain.then(() => { +#if FILESYSTEM + needed = findLibraryFS(needed, flags.rpath) ?? needed; +#endif + return loadDynamicLibrary(needed, flags, localScope); + }), Promise.resolve()) + .then(loadModule); + } + + for (var needed of metadata.neededDynlibs) { +#if FILESYSTEM + needed = findLibraryFS(needed, flags.rpath) ?? needed; +#endif + loadDynamicLibrary(needed, flags, localScope) + } + return loadModule(); + }, + +#if STACK_OVERFLOW_CHECK >= 2 + // Sometimes we load libraries before runtime initialization. In this case + // we delay calling __set_stack_limits (which must be called for each + // module). + $setDylinkStackLimits: (stackTop, stackMax) => { + for (var name in LDSO.loadedLibsByName) { +#if DYLINK_DEBUG + dbg(`setDylinkStackLimits for '${name}'`); +#endif + var lib = LDSO.loadedLibsByName[name]; + lib.exports['__set_stack_limits']?.({{{ to64("stackTop") }}}, {{{ to64("stackMax") }}}); + } + }, +#endif + + $newDSO: (name, handle, syms) => { + var dso = { + refcount: Infinity, + name, + exports: syms, + global: true, + }; + LDSO.loadedLibsByName[name] = dso; + if (handle != undefined) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return dso; + }, + +#if FILESYSTEM + $findLibraryFS__deps: [ + '$replaceORIGIN', + '_emscripten_find_dylib', + '$withStackSave', + '$stackAlloc', + '$lengthBytesUTF8', + '$stringToUTF8OnStack', + '$stringToUTF8', + '$FS', + '$PATH', +#if WASMFS + '_wasmfs_identify', + '_wasmfs_read_file', +#endif + ], + $findLibraryFS: (libName, rpath) => { + // If we're preloading a dynamic library, the runtime is not ready to call + // __wasmfs_identify or __emscripten_find_dylib. So just quit out. + // + // This means that DT_NEEDED for the main module and transitive dependencies + // of it won't work with this code path. Similarly, it means that calling + // loadDynamicLibrary in a preRun hook can't use this code path. + if (!runtimeInitialized) { + return undefined; + } + if (PATH.isAbs(libName)) { +#if WASMFS + var result = withStackSave(() => __wasmfs_identify(stringToUTF8OnStack(libName))); + return result === {{{ cDefs.EEXIST }}} ? libName : undefined; +#else + try { + FS.lookupPath(libName); + return libName; + } catch (e) { + return undefined; + } +#endif + } + var rpathResolved = (rpath?.paths || []).map((p) => replaceORIGIN(rpath?.parentLibPath, p)); + return withStackSave(() => { + // In dylink.c we use: `char buf[2*NAME_MAX+2];` and NAME_MAX is 255. + // So we use the same size here. + var bufSize = 2*255 + 2; + var buf = stackAlloc(bufSize); + var rpathC = stringToUTF8OnStack(rpathResolved.join(':')); + var libNameC = stringToUTF8OnStack(libName); + var resLibNameC = __emscripten_find_dylib(buf, rpathC, libNameC, bufSize); + return resLibNameC ? UTF8ToString(resLibNameC) : undefined; + }); + }, +#endif // FILESYSTEM + + // loadDynamicLibrary loads dynamic library @ lib URL / path and returns + // handle for loaded DSO. + // + // Several flags affect the loading: + // + // - if flags.global=true, symbols from the loaded library are merged into global + // process namespace. Flags.global is thus similar to RTLD_GLOBAL in ELF. + // + // - if flags.nodelete=true, the library will be never unloaded. Flags.nodelete + // is thus similar to RTLD_NODELETE in ELF. + // + // - if flags.loadAsync=true, the loading is performed asynchronously and + // loadDynamicLibrary returns corresponding promise. + // + // If a library was already loaded, it is not loaded a second time. However + // flags.global and flags.nodelete are handled every time a load request is made. + // Once a library becomes "global" or "nodelete", it cannot be removed or unloaded. + $loadDynamicLibrary__deps: ['$LDSO', '$loadWebAssemblyModule', + '$mergeLibSymbols', '$newDSO', + '$asyncLoad', +#if FILESYSTEM + '$preloadedWasm', + '$findLibraryFS', +#endif +#if DYNCALLS || !WASM_BIGINT + '$registerDynCallSymbols', +#endif + ], + $loadDynamicLibrary__docs: ` + /** + * @param {number=} handle + * @param {Object=} localScope + */`, + $loadDynamicLibrary: function(libName, flags = {global: true, nodelete: true}, localScope, handle) { +#if DYLINK_DEBUG + dbg(`loadDynamicLibrary: ${libName} handle: ${handle}`); + dbg('existing:', Object.keys(LDSO.loadedLibsByName)); +#endif + // when loadDynamicLibrary did not have flags, libraries were loaded + // globally & permanently + + var dso = LDSO.loadedLibsByName[libName]; + if (dso) { + // the library is being loaded or has been loaded already. +#if ASSERTIONS + assert(dso.exports !== 'loading', `Attempt to load '${libName}' twice before the first load completed`); +#endif + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); + } +#if DYNCALLS || !WASM_BIGINT + registerDynCallSymbols(dso.exports); +#endif + } else if (!dso.global) { + // The library was previously loaded only locally but now + // we have a request with global=true. + dso.global = true; + mergeLibSymbols(dso.exports, libName) + } + // same for "nodelete" + if (flags.nodelete && dso.refcount !== Infinity) { + dso.refcount = Infinity; + } + dso.refcount++ + if (handle) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return flags.loadAsync ? Promise.resolve(true) : true; + } + + // allocate new DSO + dso = newDSO(libName, handle, 'loading'); + dso.refcount = flags.nodelete ? Infinity : 1; + dso.global = flags.global; + + // libName -> libData + function loadLibData() { +#if PTHREADS + var sharedMod = sharedModules[libName]; +#if DYLINK_DEBUG + dbg(`checking sharedModules: ${libName}: ${sharedMod ? 'found' : 'not found'}`); +#endif + if (sharedMod) { + return flags.loadAsync ? Promise.resolve(sharedMod) : sharedMod; + } +#endif + + // for wasm, we can use fetch for async, but for fs mode we can only imitate it + if (handle) { + var data = {{{ makeGetValue('handle', C_STRUCTS.dso.file_data, '*') }}}; + var dataSize = {{{ makeGetValue('handle', C_STRUCTS.dso.file_data_size, '*') }}}; + if (data && dataSize) { + var libData = HEAP8.slice(data, data + dataSize); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + } + +#if FILESYSTEM + var f = findLibraryFS(libName, flags.rpath); +#if DYLINK_DEBUG + dbg(`checking filesystem: ${libName}: ${f ? 'found' : 'not found'}`); +#endif + if (f) { + var libData = FS.readFile(f, {encoding: 'binary'}); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } +#endif + + var libFile = locateFile(libName); + if (flags.loadAsync) { + return asyncLoad(libFile); + } + + // load the binary synchronously + if (!readBinary) { + throw new Error(`${libFile}: file not found, and synchronous loading of external files is not available`); + } + return readBinary(libFile); + } + + // libName -> exports + function getExports() { +#if FILESYSTEM + // lookup preloaded cache first + var preloaded = preloadedWasm[libName]; +#if DYLINK_DEBUG + dbg(`checking preloadedWasm: ${libName}: ${preloaded ? 'found' : 'not found'}`); +#endif + if (preloaded) { + return flags.loadAsync ? Promise.resolve(preloaded) : preloaded; + } +#endif + + // module not preloaded - load lib data and create new module from it + if (flags.loadAsync) { + return loadLibData().then((libData) => loadWebAssemblyModule(libData, flags, libName, localScope, handle)); + } + + return loadWebAssemblyModule(loadLibData(), flags, libName, localScope, handle); + } + + // module for lib is loaded - update the dso & global namespace + function moduleLoaded(exports) { + if (dso.global) { + mergeLibSymbols(exports, libName); + } else if (localScope) { + Object.assign(localScope, exports); +#if DYNCALLS || !WASM_BIGINT + registerDynCallSymbols(exports); +#endif + } + dso.exports = exports; + } + + if (flags.loadAsync) { +#if DYLINK_DEBUG + dbg("loadDynamicLibrary: done (async)"); +#endif + return getExports().then((exports) => { + moduleLoaded(exports); + return true; + }); + } + + moduleLoaded(getExports()); +#if DYLINK_DEBUG + dbg("loadDynamicLibrary: done"); +#endif + return true; + }, + + $loadDylibs__internal: true, + $loadDylibs__deps: ['$loadDynamicLibrary', '$reportUndefinedSymbols', '$addRunDependency', '$removeRunDependency'], + $loadDylibs: async () => { + if (!dynamicLibraries.length) { +#if DYLINK_DEBUG + dbg('loadDylibs: no libraries to preload'); +#endif + reportUndefinedSymbols(); + return; + } + +#if DYLINK_DEBUG + dbg('loadDylibs:', dynamicLibraries); +#endif + addRunDependency('loadDylibs'); + + // Load binaries asynchronously + for (var lib of dynamicLibraries) { + await loadDynamicLibrary(lib, {loadAsync: true, global: true, nodelete: true, allowUndefined: true}) + } + // we got them all, wonderful + reportUndefinedSymbols(); + +#if DYLINK_DEBUG + dbg('loadDylibs done!'); +#endif + removeRunDependency('loadDylibs'); + }, + + // void* dlopen(const char* filename, int flags); + $dlopenInternal__deps: ['$dlSetError', '$PATH'], + $dlopenInternal: (handle, jsflags) => { + // void *dlopen(const char *file, int mode); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html + var filename = UTF8ToString(handle + {{{ C_STRUCTS.dso.name }}}); + var flags = {{{ makeGetValue('handle', C_STRUCTS.dso.flags, 'i32') }}}; +#if DYLINK_DEBUG + dbg('dlopenInternal:', filename); +#endif + filename = PATH.normalize(filename); + var searchpaths = []; + + var global = Boolean(flags & {{{ cDefs.RTLD_GLOBAL }}}); + var localScope = global ? null : {}; + + // We don't care about RTLD_NOW and RTLD_LAZY. + var combinedFlags = { + global, + nodelete: Boolean(flags & {{{ cDefs.RTLD_NODELETE }}}), + loadAsync: jsflags.loadAsync, + } + + if (jsflags.loadAsync) { + return loadDynamicLibrary(filename, combinedFlags, localScope, handle); + } + + try { + return loadDynamicLibrary(filename, combinedFlags, localScope, handle) + } catch (e) { +#if ASSERTIONS + err(`error loading dynamic library ${filename}: ${e}`); +#endif + dlSetError(`could not load dynamic lib: ${filename}\n${e}`); + return 0; + } + }, + + _dlopen_js__deps: ['$dlopenInternal'], + _dlopen_js__async: 'auto', + _dlopen_js: (handle) => +#if ASYNCIFY + dlopenInternal(handle, { loadAsync: true }), +#else + dlopenInternal(handle, { loadAsync: false }), +#endif + + // Async version of dlopen. + _emscripten_dlopen_js__deps: ['$dlopenInternal', '$callUserCallback', '$dlSetError'], + _emscripten_dlopen_js: (handle, onsuccess, onerror, user_data) => { + /** @param {Object=} e */ + function errorCallback(e) { + var filename = UTF8ToString(handle + {{{ C_STRUCTS.dso.name }}}); + dlSetError(`'Could not load dynamic lib: ${filename}\n${e}`); + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => {{{ makeDynCall('vpp', 'onerror') }}}(handle, user_data)); + } + function successCallback() { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => {{{ makeDynCall('vpp', 'onsuccess') }}}(handle, user_data)); + } + + {{{ runtimeKeepalivePush() }}} + var promise = dlopenInternal(handle, { loadAsync: true }); + if (promise) { + promise.then(successCallback, errorCallback); + } else { + errorCallback(); + } + }, + + _dlsym_catchup_js: (handle, symbolIndex) => { +#if DYLINK_DEBUG + dbg("_dlsym_catchup: handle=" + ptrToString(handle) + " symbolIndex=" + symbolIndex); +#endif + var lib = LDSO.loadedLibsByHandle[handle]; + var symDict = lib.exports; + var symName = Object.keys(symDict)[symbolIndex]; + var sym = symDict[symName]; + var result = addFunction(sym, sym.sig); +#if DYLINK_DEBUG + dbg(`_dlsym_catchup: result=${result}`); +#endif + return result; + }, + + // void* dlsym(void* handle, const char* symbol); + _dlsym_js__deps: ['$dlSetError', '$getFunctionAddress', '$addFunction'], + _dlsym_js: (handle, symbol, symbolIndex) => { + // void *dlsym(void *restrict handle, const char *restrict name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html + symbol = UTF8ToString(symbol); +#if DYLINK_DEBUG + dbg('dlsym_js:', symbol); +#endif + var result; + var newSymIndex; + + var lib = LDSO.loadedLibsByHandle[handle]; +#if ASSERTIONS + assert(lib, `Tried to dlsym() from an unopened handle: ${handle}`); +#endif + newSymIndex = Object.keys(lib.exports).indexOf(symbol); + if (newSymIndex == -1 || lib.exports[symbol].stub) { + dlSetError(`Tried to lookup unknown symbol "${symbol}" in dynamic lib: ${lib.name}`) + return 0; + } +#if !WASM_BIGINT + var origSym = 'orig$' + symbol; + result = lib.exports[origSym]; + if (result) { + newSymIndex = Object.keys(lib.exports).indexOf(origSym); + } + else +#endif + result = lib.exports[symbol]; + + if (typeof result == 'function') { +#if DYLINK_DEBUG + dbg(`dlsym_js: ${symbol} getting table slot for: ${result}`); +#endif + +#if ASYNCIFY + // Asyncify wraps exports, and we need to look through those wrappers. + if (result.orig) { + result = result.orig; + } +#endif + var addr = getFunctionAddress(result); + if (addr) { +#if DYLINK_DEBUG + dbg('symbol already exists in table:', symbol); +#endif + result = addr; + } else { + // Insert the function into the wasm table. If it's a direct wasm + // function the second argument will not be needed. If it's a JS + // function we rely on the `sig` attribute being set based on the + // `__sig` specified in library JS file. + result = addFunction(result, result.sig); +#if DYLINK_DEBUG + dbg('adding symbol to table:', symbol); +#endif + {{{ makeSetValue('symbolIndex', 0, 'newSymIndex', '*') }}}; + } + } +#if DYLINK_DEBUG + dbg(`dlsym_js: ${symbol} -> ${result}`); +#endif + return result; + }, +}; + +addToLibrary(LibraryDylink); diff --git a/src/lib/libegl.js b/src/lib/libegl.js new file mode 100644 index 0000000000000..1724f91293bef --- /dev/null +++ b/src/lib/libegl.js @@ -0,0 +1,673 @@ +/** + * @license + * Copyright 2012 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +/* + * The EGL implementation supports only one EGLNativeDisplayType, the + * EGL_DEFAULT_DISPLAY. This native display type returns the only supported + * EGLDisplay handle with the magic value 62000. There is only a single + * EGLConfig configuration supported, that has the magic value 62002. The + * implementation only allows a single EGLContext to be created, that has the + * magic value of 62004. (multiple creations silently return this same context) + * The implementation only creates a single EGLSurface, a handle with the magic + * value of 62006. (multiple creations silently return the same surface) + */ + +{{{ +// Magic ID for Emscripten 'default display' +const eglDefaultDisplay = 62000; +// Magic ID for the only EGLConfig supported by Emscripten +const eglDefaultConfig = 62002; +// Magic ID for Emscripten EGLContext +const eglDefaultContext = 62004; +}}} + +var LibraryEGL = { + $EGL__deps: ['$Browser'], + $EGL: { + // This variable tracks the success status of the most recently invoked EGL function call. + errorCode: 0x3000 /* EGL_SUCCESS */, + defaultDisplayInitialized: false, + currentContext: 0 /* EGL_NO_CONTEXT */, + currentReadSurface: 0 /* EGL_NO_SURFACE */, + currentDrawSurface: 0 /* EGL_NO_SURFACE */, + + contextAttributes: { + alpha: false, + depth: false, + stencil: false, + antialias: false + }, + + stringCache: {}, + + setErrorCode(code) { + EGL.errorCode = code; + }, + + chooseConfig(display, attribList, config, config_size, numConfigs) { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + + if (attribList) { + // read attribList if it is non-null + for (;;) { + var param = {{{ makeGetValue('attribList', '0', 'i32') }}}; + if (param == 0x3021 /*EGL_ALPHA_SIZE*/) { + var alphaSize = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.alpha = (alphaSize > 0); + } else if (param == 0x3025 /*EGL_DEPTH_SIZE*/) { + var depthSize = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.depth = (depthSize > 0); + } else if (param == 0x3026 /*EGL_STENCIL_SIZE*/) { + var stencilSize = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.stencil = (stencilSize > 0); + } else if (param == 0x3031 /*EGL_SAMPLES*/) { + var samples = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.antialias = (samples > 0); + } else if (param == 0x3032 /*EGL_SAMPLE_BUFFERS*/) { + var samples = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.antialias = (samples == 1); + } else if (param == 0x3100 /*EGL_CONTEXT_PRIORITY_LEVEL_IMG*/) { + var requestedPriority = {{{ makeGetValue('attribList', '4', 'i32') }}}; + EGL.contextAttributes.lowLatency = (requestedPriority != 0x3103 /*EGL_CONTEXT_PRIORITY_LOW_IMG*/); + } else if (param == 0x3038 /*EGL_NONE*/) { + break; + } + attribList += 8; + } + } + + if ((!config || !config_size) && !numConfigs) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + if (numConfigs) { + {{{ makeSetValue('numConfigs', '0', '1', 'i32') }}}; // Total number of supported configs: 1. + } + if (config && config_size > 0) { + {{{ makeSetValue('config', '0', eglDefaultConfig /* Magic ID for the only EGLConfig supported by Emscripten */, '*') }}}; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + }, + + // EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id); + eglGetDisplay__proxy: 'sync', + eglGetDisplay: (nativeDisplayType) => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + // Emscripten EGL implementation "emulates" X11, and eglGetDisplay is + // expected to accept/receive a pointer to an X11 Display object (or + // EGL_DEFAULT_DISPLAY). + if (nativeDisplayType != 0 /* EGL_DEFAULT_DISPLAY */ && nativeDisplayType != 1 /* see library_xlib.js */) { + return 0; // EGL_NO_DISPLAY + } + return {{{ eglDefaultDisplay }}}; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor); + eglInitialize__proxy: 'sync', + eglInitialize: (display, majorVersion, minorVersion) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (majorVersion) { + {{{ makeSetValue('majorVersion', '0', '1', 'i32') }}}; // Advertise EGL Major version: '1' + } + if (minorVersion) { + {{{ makeSetValue('minorVersion', '0', '4', 'i32') }}}; // Advertise EGL Minor version: '4' + } + EGL.defaultDisplayInitialized = true; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy); + eglTerminate__proxy: 'sync', + eglTerminate: (display) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + EGL.currentContext = 0; + EGL.currentReadSurface = 0; + EGL.currentDrawSurface = 0; + EGL.defaultDisplayInitialized = false; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); + eglGetConfigs__proxy: 'sync', + eglGetConfigs: (display, configs, config_size, numConfigs) => + EGL.chooseConfig(display, 0, configs, config_size, numConfigs), + + // EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); + eglChooseConfig__proxy: 'sync', + eglChooseConfig: (display, attrib_list, configs, config_size, numConfigs) => + EGL.chooseConfig(display, attrib_list, configs, config_size, numConfigs), + + // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); + eglGetConfigAttrib__proxy: 'sync', + eglGetConfigAttrib: (display, config, attribute, value) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != {{{ eglDefaultConfig }}}) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3020: // EGL_BUFFER_SIZE + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.alpha ? 32 : 24' /* 8 bits for each R,G,B. 8 bits for alpha if enabled*/, 'i32') }}}; + return 1; + case 0x3021: // EGL_ALPHA_SIZE + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.alpha ? 8 : 0' /* 8 bits for alpha channel if enabled. */, 'i32') }}}; + return 1; + case 0x3022: // EGL_BLUE_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for blue channel. */, 'i32') }}}; + return 1; + case 0x3023: // EGL_GREEN_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for green channel. */, 'i32') }}}; + return 1; + case 0x3024: // EGL_RED_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for red channel. */, 'i32') }}}; + return 1; + case 0x3025: // EGL_DEPTH_SIZE + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.depth ? 24 : 0' /* 24 bits for depth buffer if enabled. */, 'i32') }}}; + return 1; + case 0x3026: // EGL_STENCIL_SIZE + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.stencil ? 8 : 0' /* 8 bits for stencil buffer if enabled. */, 'i32') }}}; + return 1; + case 0x3027: // EGL_CONFIG_CAVEAT + // We can return here one of EGL_NONE (0x3038), EGL_SLOW_CONFIG (0x3050) or EGL_NON_CONFORMANT_CONFIG (0x3051). + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + return 1; + case 0x3029: // EGL_LEVEL + {{{ makeSetValue('value', '0', '0' /* Z order/depth layer for this level. Not applicable for Emscripten. */, 'i32') }}}; + return 1; + case 0x302A: // EGL_MAX_PBUFFER_HEIGHT + {{{ makeSetValue('value', '0', '4096', 'i32') }}}; + return 1; + case 0x302B: // EGL_MAX_PBUFFER_PIXELS + {{{ makeSetValue('value', '0', '16777216' /* 4096 * 4096 */, 'i32') }}}; + return 1; + case 0x302C: // EGL_MAX_PBUFFER_WIDTH + {{{ makeSetValue('value', '0', '4096', 'i32') }}}; + return 1; + case 0x302D: // EGL_NATIVE_RENDERABLE + {{{ makeSetValue('value', '0', '0' /* This config does not allow co-rendering with other 'native' rendering APIs. */, 'i32') }}}; + return 1; + case 0x302E: // EGL_NATIVE_VISUAL_ID + {{{ makeSetValue('value', '0', '0' /* N/A for Emscripten. */, 'i32') }}}; + return 1; + case 0x302F: // EGL_NATIVE_VISUAL_TYPE + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3031: // EGL_SAMPLES + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.antialias ? 4 : 0' /* 2x2 Multisampling */, 'i32') }}}; + return 1; + case 0x3032: // EGL_SAMPLE_BUFFERS + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.antialias ? 1 : 0' /* Multisampling enabled */, 'i32') }}}; + return 1; + case 0x3033: // EGL_SURFACE_TYPE + {{{ makeSetValue('value', '0', '0x4' /* EGL_WINDOW_BIT */, 'i32') }}}; + return 1; + case 0x3034: // EGL_TRANSPARENT_TYPE + // If this returns EGL_TRANSPARENT_RGB (0x3052), transparency is used through color-keying. No such thing applies to Emscripten canvas. + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3035: // EGL_TRANSPARENT_BLUE_VALUE + case 0x3036: // EGL_TRANSPARENT_GREEN_VALUE + case 0x3037: // EGL_TRANSPARENT_RED_VALUE + // "If EGL_TRANSPARENT_TYPE is EGL_NONE, then the values for EGL_TRANSPARENT_RED_VALUE, EGL_TRANSPARENT_GREEN_VALUE, and EGL_TRANSPARENT_BLUE_VALUE are undefined." + {{{ makeSetValue('value', '0', '-1' /* Report a "does not apply" value. */, 'i32') }}}; + return 1; + case 0x3039: // EGL_BIND_TO_TEXTURE_RGB + case 0x303A: // EGL_BIND_TO_TEXTURE_RGBA + {{{ makeSetValue('value', '0', '0' /* Only pbuffers would be bindable, but these are not supported. */, 'i32') }}}; + return 1; + case 0x303B: // EGL_MIN_SWAP_INTERVAL + {{{ makeSetValue('value', '0', '0', 'i32') }}}; + return 1; + case 0x303C: // EGL_MAX_SWAP_INTERVAL + {{{ makeSetValue('value', '0', '1' /* TODO: Currently this is not strictly true, since user can specify custom presentation interval in JS requestAnimationFrame/emscripten_set_main_loop. */, 'i32') }}}; + return 1; + case 0x303D: // EGL_LUMINANCE_SIZE + case 0x303E: // EGL_ALPHA_MASK_SIZE + {{{ makeSetValue('value', '0', '0' /* N/A in this config. */, 'i32') }}}; + return 1; + case 0x303F: // EGL_COLOR_BUFFER_TYPE + // EGL has two types of buffers: EGL_RGB_BUFFER and EGL_LUMINANCE_BUFFER. + {{{ makeSetValue('value', '0', '0x308E' /* EGL_RGB_BUFFER */, 'i32') }}}; + return 1; + case 0x3040: // EGL_RENDERABLE_TYPE + // A bit combination of EGL_OPENGL_ES_BIT,EGL_OPENVG_BIT,EGL_OPENGL_ES2_BIT and EGL_OPENGL_BIT. + {{{ makeSetValue('value', '0', '0x4' /* EGL_OPENGL_ES2_BIT */, 'i32') }}}; + return 1; + case 0x3042: // EGL_CONFORMANT + // "EGL_CONFORMANT is a mask indicating if a client API context created with respect to the corresponding EGLConfig will pass the required conformance tests for that API." + {{{ makeSetValue('value', '0', '0' /* EGL_OPENGL_ES2_BIT */, 'i32') }}}; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + + // EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); + eglCreateWindowSurface__proxy: 'sync', + eglCreateWindowSurface: (display, config, win, attrib_list) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != {{{ eglDefaultConfig }}}) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + // TODO: Examine attrib_list! Parameters that can be present there are: + // - EGL_RENDER_BUFFER (must be EGL_BACK_BUFFER) + // - EGL_VG_COLORSPACE (can't be set) + // - EGL_VG_ALPHA_FORMAT (can't be set) + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 62006; /* Magic ID for Emscripten 'default surface' */ + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface(EGLDisplay display, EGLSurface surface); + eglDestroySurface__proxy: 'sync', + eglDestroySurface: (display, surface) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (surface != 62006 /* Magic ID for the only EGLSurface supported by Emscripten */) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 1; + } + if (EGL.currentReadSurface == surface) { + EGL.currentReadSurface = 0; + } + if (EGL.currentDrawSurface == surface) { + EGL.currentDrawSurface = 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; /* Magic ID for Emscripten 'default surface' */ + }, + + eglCreateContext__deps: ['$GL'], + + // EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); + eglCreateContext__proxy: 'sync', + eglCreateContext: (display, config, hmm, contextAttribs) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + + // EGL 1.4 spec says default EGL_CONTEXT_CLIENT_VERSION is GLES1, but this is not supported by Emscripten. + // So user must pass EGL_CONTEXT_CLIENT_VERSION == 2 to initialize EGL. + var glesContextVersion = 1; + for (;;) { + var param = {{{ makeGetValue('contextAttribs', '0', 'i32') }}}; + if (param == 0x3098 /*EGL_CONTEXT_CLIENT_VERSION*/) { + glesContextVersion = {{{ makeGetValue('contextAttribs', '4', 'i32') }}}; + } else if (param == 0x3038 /*EGL_NONE*/) { + break; + } else { + /* EGL1.4 specifies only EGL_CONTEXT_CLIENT_VERSION as supported attribute */ + EGL.setErrorCode(0x3004 /*EGL_BAD_ATTRIBUTE*/); + return 0; + } + contextAttribs += 8; + } +#if MAX_WEBGL_VERSION >= 2 + if (glesContextVersion < 2 || glesContextVersion > 3) { +#else + if (glesContextVersion != 2) { +#endif +#if GL_ASSERTIONS + if (glesContextVersion == 3) { + err('When initializing GLES3/WebGL2 via EGL, one must build with -sMAX_WEBGL_VERSION=2!'); + } else { + err(`When initializing GLES2/WebGL1 via EGL, one must pass EGL_CONTEXT_CLIENT_VERSION = 2 to GL context attributes! GLES version ${glesContextVersion} is not supported!`); + } +#endif + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; /* EGL_NO_CONTEXT */ + } + + EGL.contextAttributes.majorVersion = glesContextVersion - 1; // WebGL 1 is GLES 2, WebGL2 is GLES3 + EGL.contextAttributes.minorVersion = 0; + + EGL.context = GL.createContext(Browser.getCanvas(), EGL.contextAttributes); + + if (EGL.context != 0) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + + // Run callbacks so that GL emulation works + GL.makeContextCurrent(EGL.context); + Browser.useWebGL = true; + Browser.moduleContextCreatedCallbacks.forEach((callback) => callback()); + + // Note: This function only creates a context, but it shall not make it active. + GL.makeContextCurrent(null); + return {{{ eglDefaultContext }}}; + } else { + EGL.setErrorCode(0x3009 /* EGL_BAD_MATCH */); // By the EGL 1.4 spec, an implementation that does not support GLES2 (WebGL in this case), this error code is set. + return 0; /* EGL_NO_CONTEXT */ + } + }, + + eglDestroyContext__deps: ['$GL'], + + // EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext context); + eglDestroyContext__proxy: 'sync', + eglDestroyContext: (display, context) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (context != {{{ eglDefaultContext }}}) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + + GL.deleteContext(EGL.context); + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + if (EGL.currentContext == context) { + EGL.currentContext = 0; + } + return 1 /* EGL_TRUE */; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); + eglQuerySurface__proxy: 'sync', + eglQuerySurface: (display, surface, attribute, value) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (surface != 62006 /* Magic ID for Emscripten 'default surface' */) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + return 1; + case 0x3058: // EGL_LARGEST_PBUFFER + // Odd EGL API: If surface is not a pbuffer surface, 'value' should not be written to. It's not specified as an error, so true should(?) be returned. + // Existing Android implementation seems to do so at least. + return 1; + case 0x3057: // EGL_WIDTH + {{{ makeSetValue('value', '0', "Browser.getCanvas().width", 'i32') }}}; + return 1; + case 0x3056: // EGL_HEIGHT + {{{ makeSetValue('value', '0', "Browser.getCanvas().height", 'i32') }}}; + return 1; + case 0x3090: // EGL_HORIZONTAL_RESOLUTION + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3091: // EGL_VERTICAL_RESOLUTION + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3092: // EGL_PIXEL_ASPECT_RATIO + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The main surface is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + {{{ makeSetValue('value', '0', '0x3084' /* EGL_BACK_BUFFER */, 'i32') }}}; + return 1; + case 0x3099: // EGL_MULTISAMPLE_RESOLVE + {{{ makeSetValue('value', '0', '0x309A' /* EGL_MULTISAMPLE_RESOLVE_DEFAULT */, 'i32') }}}; + return 1; + case 0x3093: // EGL_SWAP_BEHAVIOR + // The two possibilities are EGL_BUFFER_PRESERVED and EGL_BUFFER_DESTROYED. Slightly unsure which is the + // case for browser environment, but advertise the 'weaker' behavior to be sure. + {{{ makeSetValue('value', '0', '0x3095' /* EGL_BUFFER_DESTROYED */, 'i32') }}}; + return 1; + case 0x3080: // EGL_TEXTURE_FORMAT + case 0x3081: // EGL_TEXTURE_TARGET + case 0x3082: // EGL_MIPMAP_TEXTURE + case 0x3083: // EGL_MIPMAP_LEVEL + // This is a window surface, not a pbuffer surface. Spec: + // "Querying EGL_TEXTURE_FORMAT, EGL_TEXTURE_TARGET, EGL_MIPMAP_TEXTURE, or EGL_MIPMAP_LEVEL for a non-pbuffer surface is not an error, but value is not modified." + // So pass-through. + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); + eglQueryContext__proxy: 'sync', + eglQueryContext: (display, context, attribute, value) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != {{{ eglDefaultContext }}}) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch (attribute) { + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', eglDefaultConfig, 'i32') }}}; + return 1; + case 0x3097: // EGL_CONTEXT_CLIENT_TYPE + {{{ makeSetValue('value', '0', '0x30A0' /* EGL_OPENGL_ES_API */, 'i32') }}}; + return 1; + case 0x3098: // EGL_CONTEXT_CLIENT_VERSION + {{{ makeSetValue('value', '0', 'EGL.contextAttributes.majorVersion + 1', 'i32') }}}; + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The context is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + {{{ makeSetValue('value', '0', '0x3084' /* EGL_BACK_BUFFER */, 'i32') }}}; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + + // EGLAPI EGLint EGLAPIENTRY eglGetError(void); + eglGetError__proxy: 'sync', + eglGetError: () => EGL.errorCode, + + // EGLAPI const char * EGLAPIENTRY eglQueryString(EGLDisplay dpy, EGLint name); + // The allocated strings are cached and never freed. + eglQueryString__noleakcheck: true, + eglQueryString__deps: ['$stringToNewUTF8'], + eglQueryString__proxy: 'sync', + eglQueryString: (display, name) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + if (EGL.stringCache[name]) return EGL.stringCache[name]; + var ret; + switch (name) { + case 0x3053 /* EGL_VENDOR */: ret = stringToNewUTF8("Emscripten"); break; + case 0x3054 /* EGL_VERSION */: ret = stringToNewUTF8("1.4 Emscripten EGL"); break; + case 0x3055 /* EGL_EXTENSIONS */: ret = stringToNewUTF8(""); break; // Currently not supporting any EGL extensions. + case 0x308D /* EGL_CLIENT_APIS */: ret = stringToNewUTF8("OpenGL_ES"); break; + default: + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.stringCache[name] = ret; + return ret; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api); + eglBindAPI__proxy: 'sync', + eglBindAPI: (api) => { + if (api == 0x30A0 /* EGL_OPENGL_ES_API */) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + } + // if (api == 0x30A1 /* EGL_OPENVG_API */ || api == 0x30A2 /* EGL_OPENGL_API */) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + }, + + // EGLAPI EGLenum EGLAPIENTRY eglQueryAPI(void); + eglQueryAPI__proxy: 'sync', + eglQueryAPI: () => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 0x30A0; // EGL_OPENGL_ES_API + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient(void); + eglWaitClient__proxy: 'sync', + eglWaitClient: () => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative(EGLint engine); + eglWaitNative__proxy: 'sync', + eglWaitNative: (nativeEngineId) => { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL(void); + eglWaitGL: 'eglWaitClient', + + // EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval); + eglSwapInterval__deps: ['emscripten_set_main_loop_timing'], + eglSwapInterval__proxy: 'sync', + eglSwapInterval: (display, interval) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (interval == 0) _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_SETTIMEOUT }}}, 0); + else _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_RAF }}}, interval); + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); + eglMakeCurrent__deps: ['$GL'], + eglMakeCurrent__proxy: 'sync', + eglMakeCurrent: (display, draw, read, context) => { + if (display != {{{ eglDefaultDisplay }}}) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0 /* EGL_FALSE */; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != 0 && context != {{{ eglDefaultContext }}}) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if ((read != 0 && read != 62006) || (draw != 0 && draw != 62006 /* Magic ID for Emscripten 'default surface' */)) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 0; + } + + GL.makeContextCurrent(context ? EGL.context : null); + + EGL.currentContext = context; + EGL.currentDrawSurface = draw; + EGL.currentReadSurface = read; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + }, + + // EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext(void); + eglGetCurrentContext__proxy: 'sync', + eglGetCurrentContext: () => EGL.currentContext, + + // EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface(EGLint readdraw); + eglGetCurrentSurface__proxy: 'sync', + eglGetCurrentSurface: (readdraw) => { + if (readdraw == 0x305A /* EGL_READ */) { + return EGL.currentReadSurface; + } else if (readdraw == 0x3059 /* EGL_DRAW */) { + return EGL.currentDrawSurface; + } else { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0 /* EGL_NO_SURFACE */; + } + }, + + // EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void); + eglGetCurrentDisplay__proxy: 'sync', + eglGetCurrentDisplay: () => EGL.currentContext ? {{{ eglDefaultDisplay }}} : 0, + + // EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay dpy, EGLSurface surface); + eglSwapBuffers__deps: ['$GLctx'], + eglSwapBuffers__proxy: 'sync', + eglSwapBuffers: (dpy, surface) => { + if (!EGL.defaultDisplayInitialized) { + EGL.setErrorCode(0x3001 /* EGL_NOT_INITIALIZED */); + } else if (!GLctx) { + EGL.setErrorCode(0x3002 /* EGL_BAD_ACCESS */); + } else if (GLctx.isContextLost()) { + EGL.setErrorCode(0x300E /* EGL_CONTEXT_LOST */); + } else { + // According to documentation this does an implicit flush. + // Due to discussion at https://github.com/emscripten-core/emscripten/pull/1871 + // the flush was removed since this _may_ result in slowing code down. + //_glFlush(); + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + } + return 0 /* EGL_FALSE */; + }, + + eglReleaseThread__proxy: 'sync', + eglReleaseThread: () => { + // Equivalent to eglMakeCurrent with EGL_NO_CONTEXT and EGL_NO_SURFACE. + EGL.currentContext = 0; + EGL.currentReadSurface = 0; + EGL.currentDrawSurface = 0; + // EGL spec v1.4 p.55: + // "calling eglGetError immediately following a successful call to eglReleaseThread should not be done. + // Such a call will return EGL_SUCCESS - but will also result in reallocating per-thread state." + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1 /* EGL_TRUE */; + } +}; + +autoAddDeps(LibraryEGL, '$EGL'); + +addToLibrary(LibraryEGL); diff --git a/src/lib/libembind.js b/src/lib/libembind.js new file mode 100644 index 0000000000000..19080626392f3 --- /dev/null +++ b/src/lib/libembind.js @@ -0,0 +1,2365 @@ +// Copyright 2012 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 "libembind_shared.js" + +var LibraryEmbind = { + $UnboundTypeError: class extends Error {}, + $PureVirtualError: class extends Error {}, +#if EMBIND_AOT + $InvokerFunctions: '<<< EMBIND_AOT_INVOKERS >>>', +#endif + // If register_type is used, emval will be registered multiple times for + // different type ids, but only a single type object is needed on the JS side + // for all of them. Store the type for reuse. + $EmValType__deps: ['_emval_decref', '$Emval', '$readPointer'], + $EmValType: `{ + name: 'emscripten::val', + fromWireType: (handle) => { + var rv = Emval.toValue(handle); + __emval_decref(handle); + return rv; + }, + toWireType: (destructors, value) => Emval.toHandle(value), + readValueFromPointer: readPointer, + destructorFunction: null, // This type does not need a destructor + + // TODO: do we need a deleteObject here? write a test where + // emval is passed into JS via an interface + }`, + $EmValOptionalType__deps: ['$EmValType'], + $EmValOptionalType: '=Object.assign({optional: true}, EmValType);', + + $throwUnboundTypeError__deps: ['$registeredTypes', '$typeDependencies', '$UnboundTypeError', '$getTypeName'], + $throwUnboundTypeError: (message, types) => { + var unboundTypes = []; + var seen = {}; + function visit(type) { + if (seen[type]) { + return; + } + if (registeredTypes[type]) { + return; + } + if (typeDependencies[type]) { + typeDependencies[type].forEach(visit); + return; + } + unboundTypes.push(type); + seen[type] = true; + } + types.forEach(visit); + + throw new UnboundTypeError(`${message}: ` + unboundTypes.map(getTypeName).join([', '])); + }, + + // Creates a function overload resolution table to the given method 'methodName' in the given prototype, + // if the overload table doesn't yet exist. + $ensureOverloadTable__deps: ['$throwBindingError'], + $ensureOverloadTable: (proto, methodName, humanName) => { + if (undefined === proto[methodName].overloadTable) { + var prevFunc = proto[methodName]; + // Inject an overload resolver function that routes to the appropriate overload based on the number of arguments. + proto[methodName] = function(...args) { + // TODO This check can be removed in -O3 level "unsafe" optimizations. + if (!proto[methodName].overloadTable.hasOwnProperty(args.length)) { + throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`); + } + return proto[methodName].overloadTable[args.length].apply(this, args); + }; + // Move the previous function into the overload table. + proto[methodName].overloadTable = []; + proto[methodName].overloadTable[prevFunc.argCount] = prevFunc; + } + }, + + /* + Registers a symbol (function, class, enum, ...) as part of the Module JS object so that + hand-written code is able to access that symbol via 'Module.name'. + name: The name of the symbol that's being exposed. + value: The object itself to expose (function, class, ...) + numArguments: For functions, specifies the number of arguments the function takes in. For other types, unused and undefined. + + To implement support for multiple overloads of a function, an 'overload selector' function is used. That selector function chooses + the appropriate overload to call from an function overload table. This selector function is only used if multiple overloads are + actually registered, since it carries a slight performance penalty. */ + $exposePublicSymbol__deps: ['$ensureOverloadTable', '$throwBindingError'], + $exposePublicSymbol__docs: '/** @param {number=} numArguments */', + $exposePublicSymbol: (name, value, numArguments) => { + if (Module.hasOwnProperty(name)) { + if (undefined === numArguments || (undefined !== Module[name].overloadTable && undefined !== Module[name].overloadTable[numArguments])) { + throwBindingError(`Cannot register public name '${name}' twice`); + } + + // We are exposing a function with the same name as an existing function. Create an overload table and a function selector + // that routes between the two. + ensureOverloadTable(Module, name, name); + if (Module[name].overloadTable.hasOwnProperty(numArguments)) { + throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`); + } + // Add the new function into the overload table. + Module[name].overloadTable[numArguments] = value; + } else { + Module[name] = value; + Module[name].argCount = numArguments; + } + }, + + $replacePublicSymbol__deps: ['$throwInternalError'], + $replacePublicSymbol__docs: '/** @param {number=} numArguments */', + $replacePublicSymbol: (name, value, numArguments) => { + if (!Module.hasOwnProperty(name)) { + throwInternalError('Replacing nonexistent public symbol'); + } + // If there's an overload table for this symbol, replace the symbol in the overload table instead. + if (undefined !== Module[name].overloadTable && undefined !== numArguments) { + Module[name].overloadTable[numArguments] = value; + } else { + Module[name] = value; + Module[name].argCount = numArguments; + } + }, + + $embindRepr: (v) => { + if (v === null) { + return 'null'; + } + var t = typeof v; + if (t === 'object' || t === 'array' || t === 'function') { + return v.toString(); + } else { + return '' + v; + } + }, + + // raw pointer -> instance + $registeredInstances: {}, + + $getBasestPointer__deps: ['$throwBindingError'], + $getBasestPointer: (class_, ptr) => { + if (ptr === undefined) { + throwBindingError('ptr should not be undefined'); + } + while (class_.baseClass) { + ptr = class_.upcast(ptr); + class_ = class_.baseClass; + } + return ptr; + }, + + $registerInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer', '$throwBindingError'], + $registerInheritedInstance: (class_, ptr, instance) => { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + throwBindingError(`Tried to register registered instance: ${ptr}`); + } else { + registeredInstances[ptr] = instance; + } + }, + + $unregisterInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer', '$throwBindingError'], + $unregisterInheritedInstance: (class_, ptr) => { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + delete registeredInstances[ptr]; + } else { + throwBindingError(`Tried to unregister unregistered instance: ${ptr}`); + } + }, + + $getInheritedInstance__deps: ['$registeredInstances', '$getBasestPointer'], + $getInheritedInstance: (class_, ptr) => { + ptr = getBasestPointer(class_, ptr); + return registeredInstances[ptr]; + }, + + $getInheritedInstanceCount__deps: ['$registeredInstances'], + $getInheritedInstanceCount: () => Object.keys(registeredInstances).length, + + $getLiveInheritedInstances__deps: ['$registeredInstances'], + $getLiveInheritedInstances: () => { + var rv = []; + for (var k in registeredInstances) { + if (registeredInstances.hasOwnProperty(k)) { + rv.push(registeredInstances[k]); + } + } + return rv; + }, + + // class typeID -> {pointerType: ..., constPointerType: ...} + $registeredPointers: {}, + + $registerType__deps: ['$sharedRegisterType'], + $registerType__docs: '/** @param {Object=} options */', + $registerType: function(rawType, registeredInstance, options = {}) { + return sharedRegisterType(rawType, registeredInstance, options); + }, + + _embind_register_void__deps: ['$AsciiToString', '$registerType'], + _embind_register_void: (rawType, name) => { + name = AsciiToString(name); + registerType(rawType, { + isVoid: true, // void return values can be optimized out sometimes + name, + fromWireType: () => undefined, + // TODO: assert if anything else is given? + toWireType: (destructors, o) => undefined, + }); + }, + + _embind_register_bool__docs: '/** @suppress {globalThis} */', + _embind_register_bool__deps: ['$AsciiToString', '$registerType'], + _embind_register_bool: (rawType, name, trueValue, falseValue) => { + name = AsciiToString(name); + registerType(rawType, { + name, + fromWireType: function(wt) { + // ambiguous emscripten ABI: sometimes return values are + // true or false, and sometimes integers (0 or 1) + return !!wt; + }, + toWireType: function(destructors, o) { + return o ? trueValue : falseValue; + }, + readValueFromPointer: function(pointer) { + return this.fromWireType(HEAPU8[pointer]); + }, + destructorFunction: null, // This type does not need a destructor + }); + }, + + $integerReadValueFromPointer__deps: [], + $integerReadValueFromPointer: (name, width, signed) => { + // integers are quite common, so generate very specialized functions + switch (width) { + case 1: return signed ? + (pointer) => {{{ makeGetValue('pointer', 0, 'i8') }}} : + (pointer) => {{{ makeGetValue('pointer', 0, 'u8') }}}; + case 2: return signed ? + (pointer) => {{{ makeGetValue('pointer', 0, 'i16') }}} : + (pointer) => {{{ makeGetValue('pointer', 0, 'u16') }}} + case 4: return signed ? + (pointer) => {{{ makeGetValue('pointer', 0, 'i32') }}} : + (pointer) => {{{ makeGetValue('pointer', 0, 'u32') }}} +#if WASM_BIGINT + case 8: return signed ? + (pointer) => {{{ makeGetValue('pointer', 0, 'i64') }}} : + (pointer) => {{{ makeGetValue('pointer', 0, 'u64') }}} +#endif + default: + throw new TypeError(`invalid integer width (${width}): ${name}`); + } + }, + + $enumReadValueFromPointer__deps: [], + $enumReadValueFromPointer: (name, width, signed) => { + switch (width) { + case 1: return signed ? + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'i8') }}}) } : + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'u8') }}}) }; + case 2: return signed ? + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'i16') }}}) } : + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'u16') }}}) }; + case 4: return signed ? + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'i32') }}}) } : + function(pointer) { return this.fromWireType({{{ makeGetValue('pointer', 0, 'u32') }}}) }; + default: + throw new TypeError(`invalid integer width (${width}): ${name}`); + } + }, + + $floatReadValueFromPointer__deps: [], + $floatReadValueFromPointer: (name, width) => { + switch (width) { + case 4: return function(pointer) { + return this.fromWireType({{{ makeGetValue('pointer', 0, 'float') }}}); + }; + case 8: return function(pointer) { + return this.fromWireType({{{ makeGetValue('pointer', 0, 'double') }}}); + }; + default: + throw new TypeError(`invalid float width (${width}): ${name}`); + } + }, + +#if ASSERTIONS + $assertIntegerRange__deps: ['$embindRepr'], + $assertIntegerRange: (typeName, value, minRange, maxRange) => { + if (value < minRange || value > maxRange) { + throw new TypeError(`Passing a number "${embindRepr(value)}" from JS side to C/C++ side to an argument of type "${typeName}", which is outside the valid range [${minRange}, ${maxRange}]!`); + } + }, +#endif + + _embind_register_integer__docs: '/** @suppress {globalThis} */', + // When converting a number from JS to C++ side, the valid range of the number is + // [minRange, maxRange], inclusive. + _embind_register_integer__deps: [ + '$integerReadValueFromPointer', '$AsciiToString', '$registerType', +#if ASSERTIONS + '$embindRepr', + '$assertIntegerRange', +#endif + ], + _embind_register_integer: (primitiveType, name, size, minRange, maxRange) => { + name = AsciiToString(name); + + const isUnsignedType = minRange === 0; + + let fromWireType = (value) => value; + if (isUnsignedType) { + var bitshift = 32 - 8*size; + fromWireType = (value) => (value << bitshift) >>> bitshift; + maxRange = fromWireType(maxRange); + } + + registerType(primitiveType, { + name, + fromWireType: fromWireType, + toWireType: (destructors, value) => { +#if ASSERTIONS + if (typeof value != "number" && typeof value != "boolean") { + throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${name}`); + } + assertIntegerRange(name, value, minRange, maxRange); + #endif + // The VM will perform JS to Wasm value conversion, according to the spec: + // https://www.w3.org/TR/wasm-js-api-1/#towebassemblyvalue + return value; + }, + readValueFromPointer: integerReadValueFromPointer(name, size, minRange !== 0), + destructorFunction: null, // This type does not need a destructor + }); + }, + +#if WASM_BIGINT + _embind_register_bigint__docs: '/** @suppress {globalThis} */', + _embind_register_bigint__deps: [ + '$AsciiToString', '$registerType', '$integerReadValueFromPointer', +#if ASSERTIONS + '$embindRepr', + '$assertIntegerRange', +#endif + ], + _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => { + name = AsciiToString(name); + + const isUnsignedType = minRange === 0n; + + let fromWireType = (value) => value; + if (isUnsignedType) { + // uint64 get converted to int64 in ABI, fix them up like we do for 32-bit integers. + const bitSize = size * 8; + fromWireType = (value) => { +#if MEMORY64 + // FIXME(https://github.com/emscripten-core/emscripten/issues/16975) + // `size_t` ends up here, but it's transferred in the ABI as a plain number instead of a bigint. + if (typeof value == 'number') { + return value >>> 0; + } +#endif + return BigInt.asUintN(bitSize, value); + } + maxRange = fromWireType(maxRange); + } + + registerType(primitiveType, { + name, + fromWireType: fromWireType, + toWireType: (destructors, value) => { + if (typeof value == "number") { + value = BigInt(value); + } +#if ASSERTIONS + else if (typeof value != "bigint") { + throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${name}`); + } + assertIntegerRange(name, value, minRange, maxRange); +#endif + return value; + }, + readValueFromPointer: integerReadValueFromPointer(name, size, !isUnsignedType), + destructorFunction: null, // This type does not need a destructor + }); + }, +#else + _embind_register_bigint__deps: [], + _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => {}, +#endif + + _embind_register_float__deps: [ + '$floatReadValueFromPointer', '$AsciiToString', '$registerType', +#if ASSERTIONS + '$embindRepr', +#endif + ], + _embind_register_float: (rawType, name, size) => { + name = AsciiToString(name); + registerType(rawType, { + name, + fromWireType: (value) => value, + toWireType: (destructors, value) => { +#if ASSERTIONS + if (typeof value != "number" && typeof value != "boolean") { + throw new TypeError(`Cannot convert ${embindRepr(value)} to ${name}`); + } +#endif + // The VM will perform JS to Wasm value conversion, according to the spec: + // https://www.w3.org/TR/wasm-js-api-1/#towebassemblyvalue + return value; + }, + readValueFromPointer: floatReadValueFromPointer(name, size), + destructorFunction: null, // This type does not need a destructor + }); + }, + + $readPointer__docs: '/** @suppress {globalThis} */', + $readPointer: function(pointer) { + return this.fromWireType({{{ makeGetValue('pointer', '0', '*') }}}); + }, + + $installIndexedIterator: (proto, sizeMethodName, getMethodName) => { + const makeIterator = (size, getValue) => { +#if MEMORY64 + // size can be either a number or a bigint on wasm64 + const useBigInt = typeof size === 'bigint'; + const one = useBigInt ? 1n : 1; + let index = useBigInt ? 0n : 0; +#else + let index = 0; +#endif + return { + next() { + if (index >= size) { + return { done: true }; + } + const current = index; +#if MEMORY64 + index += one; +#else + index++; +#endif + const value = getValue(current); + return { value, done: false }; + }, + [Symbol.iterator]() { + return this; + }, + }; + }; + + if (!proto[Symbol.iterator]) { + proto[Symbol.iterator] = function() { + const size = this[sizeMethodName](); + return makeIterator(size, (i) => this[getMethodName](i)); + }; + } + }, + + _embind_register_std_string__deps: [ + '$AsciiToString', '$registerType', + '$readPointer', '$throwBindingError', + '$stringToUTF8', '$lengthBytesUTF8', 'malloc', 'free'], + _embind_register_std_string: (rawType, name) => { + name = AsciiToString(name); + var stdStringIsUTF8 = {{{ EMBIND_STD_STRING_IS_UTF8 }}}; + + registerType(rawType, { + name, + // For some method names we use string keys here since they are part of + // the public/external API and/or used by the runtime-generated code. + fromWireType(value) { + var length = {{{ makeGetValue('value', '0', '*') }}}; + var payload = value + {{{ POINTER_SIZE }}}; + + var str; + if (stdStringIsUTF8) { + str = UTF8ToString(payload, length, true); + } else { + str = ''; + for (var i = 0; i < length; ++i) { + str += String.fromCharCode(HEAPU8[payload + i]); + } + } + + _free(value); + + return str; + }, + toWireType(destructors, value) { + if (value instanceof ArrayBuffer) { + value = new Uint8Array(value); + } + + var length; + var valueIsOfTypeString = (typeof value == 'string'); + + // We accept `string` or array views with single byte elements + if (!(valueIsOfTypeString || (ArrayBuffer.isView(value) && value.BYTES_PER_ELEMENT == 1))) { + throwBindingError('Cannot pass non-string to std::string'); + } + if (stdStringIsUTF8 && valueIsOfTypeString) { + length = lengthBytesUTF8(value); + } else { + length = value.length; + } + + // assumes POINTER_SIZE alignment + var base = _malloc({{{ POINTER_SIZE }}} + length + 1); + var ptr = base + {{{ POINTER_SIZE }}}; + {{{ makeSetValue('base', '0', 'length', SIZE_TYPE) }}}; + if (valueIsOfTypeString) { + if (stdStringIsUTF8) { + stringToUTF8(value, ptr, length + 1); + } else { + for (var i = 0; i < length; ++i) { + var charCode = value.charCodeAt(i); + if (charCode > 255) { + _free(base); + throwBindingError('String has UTF-16 code units that do not fit in 8 bits'); + } + HEAPU8[ptr + i] = charCode; + } + } + } else { + HEAPU8.set(value, ptr); + } + + if (destructors !== null) { + destructors.push(_free, base); + } + return base; + }, + readValueFromPointer: readPointer, + destructorFunction(ptr) { + _free(ptr); + }, + }); + }, + + _embind_register_std_wstring__deps: [ + '$AsciiToString', '$registerType', '$readPointer', + '$UTF16ToString', '$stringToUTF16', '$lengthBytesUTF16', + '$UTF32ToString', '$stringToUTF32', '$lengthBytesUTF32', + ], + _embind_register_std_wstring: (rawType, charSize, name) => { + name = AsciiToString(name); + var decodeString, encodeString, lengthBytesUTF; + if (charSize === 2) { + decodeString = UTF16ToString; + encodeString = stringToUTF16; + lengthBytesUTF = lengthBytesUTF16; + } else { +#if ASSERTIONS + assert(charSize === 4, 'only 2-byte and 4-byte strings are currently supported'); +#endif + decodeString = UTF32ToString; + encodeString = stringToUTF32; + lengthBytesUTF = lengthBytesUTF32; + } + registerType(rawType, { + name, + fromWireType: (value) => { + // Code mostly taken from _embind_register_std_string fromWireType + var length = {{{ makeGetValue('value', 0, '*') }}}; + var str = decodeString(value + {{{ POINTER_SIZE }}}, length * charSize, true); + + _free(value); + + return str; + }, + toWireType: (destructors, value) => { + if (!(typeof value == 'string')) { + throwBindingError(`Cannot pass non-string to C++ string type ${name}`); + } + + // assumes POINTER_SIZE alignment + var length = lengthBytesUTF(value); + var ptr = _malloc({{{ POINTER_SIZE }}} + length + charSize); + {{{ makeSetValue('ptr', '0', 'length / charSize', SIZE_TYPE) }}}; + + encodeString(value, ptr + {{{ POINTER_SIZE }}}, length + charSize); + + if (destructors !== null) { + destructors.push(_free, ptr); + } + return ptr; + }, + readValueFromPointer: readPointer, + destructorFunction(ptr) { + _free(ptr); + } + }); + }, + + _embind_register_emval__deps: [ + '$registerType', '$EmValType'], + _embind_register_emval: (rawType) => registerType(rawType, EmValType), + + _embind_register_user_type__deps: ['_embind_register_emval'], + _embind_register_user_type: (rawType, name) => { + __embind_register_emval(rawType); + }, + + _embind_register_user_type_definition__deps: ['_embind_register_emval'], + _embind_register_user_type_definition: (rawType, name, definition) => { + __embind_register_emval(rawType); + }, + + _embind_register_optional__deps: ['$registerType', '$EmValOptionalType'], + _embind_register_optional: (rawOptionalType, rawType) => { + registerType(rawOptionalType, EmValOptionalType); + }, + + _embind_register_memory_view__deps: ['$AsciiToString', '$registerType'], + _embind_register_memory_view: (rawType, dataTypeIndex, name) => { + var typeMapping = [ + Int8Array, + Uint8Array, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, +#if WASM_BIGINT + BigInt64Array, + BigUint64Array, +#endif + ]; + + var TA = typeMapping[dataTypeIndex]; + + function decodeMemoryView(handle) { + var size = {{{ makeGetValue('handle', 0, '*') }}}; + var data = {{{ makeGetValue('handle', POINTER_SIZE, '*') }}}; + return new TA(HEAP8.buffer, data, size); + } + + name = AsciiToString(name); + registerType(rawType, { + name, + fromWireType: decodeMemoryView, + readValueFromPointer: decodeMemoryView, + }, { + ignoreDuplicateRegistrations: true, + }); + }, + + $runDestructors: (destructors) => { + while (destructors.length) { + var ptr = destructors.pop(); + var del = destructors.pop(); + del(ptr); + } + }, + + // The path to interop from JS code to C++ code: + // (hand-written JS code) -> (autogenerated JS invoker) -> (template-generated C++ invoker) -> (target C++ function) + // craftInvokerFunction generates the JS invoker function for each function exposed to JS through embind. + $craftInvokerFunction__deps: [ + '$createNamedFunction', '$runDestructors', '$throwBindingError', '$usesDestructorStack', +#if DYNAMIC_EXECUTION && !EMBIND_AOT + '$createJsInvoker', +#endif +#if EMBIND_AOT + '$InvokerFunctions', + '$createJsInvokerSignature', +#endif +#if ASYNCIFY == 1 + '$Asyncify', +#endif +#if ASSERTIONS + '$getRequiredArgCount', + '$checkArgCount', +#endif + ], + $craftInvokerFunction: function(humanName, argTypes, classType, cppInvokerFunc, cppTargetFunc, /** boolean= */ isAsync) { + // humanName: a human-readable string name for the function to be generated. + // argTypes: An array that contains the embind type objects for all types in the function signature. + // argTypes[0] is the type object for the function return value. + // argTypes[1] is the type object for function this object/class type, or null if not crafting an invoker for a class method. + // argTypes[2...] are the actual function parameters. + // classType: The embind type object for the class to be bound, or null if this is not a method of a class. + // cppInvokerFunc: JS Function object to the C++-side function that interops into C++ code. + // cppTargetFunc: Function pointer (an integer to FUNCTION_TABLE) to the target C++ function the cppInvokerFunc will end up calling. + // isAsync: Optional. If true, returns an async function. Async bindings are only supported with JSPI. + var argCount = argTypes.length; + + if (argCount < 2) { + throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); + } + +#if ASSERTIONS && ASYNCIFY != 2 + assert(!isAsync, 'Async bindings are only supported with JSPI.'); +#endif + var isClassMethodFunc = (argTypes[1] !== null && classType !== null); + + // Free functions with signature "void function()" do not need an invoker that marshalls between wire types. + // TODO: This omits argument count check - enable only at -O3 or similar. + // if (ENABLE_UNSAFE_OPTS && argCount == 2 && argTypes[0].name == "void" && !isClassMethodFunc) { + // return FUNCTION_TABLE[fn]; + // } + + + // Determine if we need to use a dynamic stack to store the destructors for the function parameters. + // TODO: Remove this completely once all function invokers are being dynamically generated. + var needsDestructorStack = usesDestructorStack(argTypes); + + var returns = !argTypes[0].isVoid; + + var expectedArgCount = argCount - 2; +#if ASSERTIONS + var minArgs = getRequiredArgCount(argTypes); +#endif +#if DYNAMIC_EXECUTION == 0 && !EMBIND_AOT + var argsWired = new Array(expectedArgCount); + var invokerFuncArgs = []; + var destructors = []; + var invokerFn = function(...args) { +#if ASSERTIONS + checkArgCount(args.length, minArgs, expectedArgCount, humanName, throwBindingError); +#endif +#if EMSCRIPTEN_TRACING + Module.emscripten_trace_enter_context(`embind::${humanName}`); +#endif + destructors.length = 0; + var thisWired; + invokerFuncArgs.length = isClassMethodFunc ? 2 : 1; + invokerFuncArgs[0] = cppTargetFunc; + if (isClassMethodFunc) { + thisWired = argTypes[1].toWireType(destructors, this); + invokerFuncArgs[1] = thisWired; + } + for (var i = 0; i < expectedArgCount; ++i) { + argsWired[i] = argTypes[i + 2].toWireType(destructors, args[i]); + invokerFuncArgs.push(argsWired[i]); + } + + var rv = cppInvokerFunc(...invokerFuncArgs); + + function onDone(rv) { + if (needsDestructorStack) { + runDestructors(destructors); + } else { + for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; i++) { + var param = i === 1 ? thisWired : argsWired[i - 2]; + if (argTypes[i].destructorFunction !== null) { + argTypes[i].destructorFunction(param); + } + } + } + + #if EMSCRIPTEN_TRACING + Module.emscripten_trace_exit_context(); + #endif + + if (returns) { + return argTypes[0].fromWireType(rv); + } + } + +#if ASYNCIFY == 1 + if (Asyncify.currData) { + return Asyncify.whenDone().then(onDone); + } +#elif ASYNCIFY == 2 + if (isAsync) { + return rv.then(onDone); + } +#endif + + return onDone(rv); + }; +#else + // Build the arguments that will be passed into the closure around the invoker + // function. + var retType = argTypes[0]; + var instType = argTypes[1]; + var closureArgs = [humanName, throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, retType.fromWireType.bind(retType), instType?.toWireType.bind(instType)]; +#if EMSCRIPTEN_TRACING + closureArgs.push(Module); +#endif + for (var i = 2; i < argCount; ++i) { + var argType = argTypes[i]; + closureArgs.push(argType.toWireType.bind(argType)); + } +#if ASYNCIFY == 1 + closureArgs.push(Asyncify); +#endif + if (!needsDestructorStack) { + // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. + for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { + if (argTypes[i].destructorFunction !== null) { + closureArgs.push(argTypes[i].destructorFunction); + } + } + } +#if ASSERTIONS + closureArgs.push(checkArgCount, minArgs, expectedArgCount); +#endif + +#if EMBIND_AOT + var signature = createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync); + var invokerFn = InvokerFunctions[signature](...closureArgs); +#else + + let invokerFactory = createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync); + var invokerFn = invokerFactory(...closureArgs); +#endif +#endif + return createNamedFunction(humanName, invokerFn); + }, + + $embind__requireFunction__deps: ['$AsciiToString', '$throwBindingError' +#if DYNCALLS || !WASM_BIGINT || MEMORY64 || CAN_ADDRESS_2GB + , '$getDynCaller' +#endif + ], + $embind__requireFunction: (signature, rawFunction, isAsync = false) => { +#if ASSERTIONS && ASYNCIFY != 2 + assert(!isAsync, 'Async bindings are only supported with JSPI.'); +#endif + + signature = AsciiToString(signature); + + function makeDynCaller() { +#if DYNCALLS + return getDynCaller(signature, rawFunction); +#else +#if !WASM_BIGINT + if (signature.includes('j')) { + return getDynCaller(signature, rawFunction); + } +#endif +#if MEMORY64 || CAN_ADDRESS_2GB + if (signature.includes('p')) { + return getDynCaller(signature, rawFunction, isAsync); + } +#endif + var rtn = getWasmTableEntry(rawFunction); +#if JSPI + if (isAsync) { + rtn = WebAssembly.promising(rtn); + } +#endif + return rtn; +#endif + } + + var fp = makeDynCaller(); + if (typeof fp != 'function') { + throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`); + } + return fp; + }, + + _embind_register_function__deps: [ + '$craftInvokerFunction', '$exposePublicSymbol', '$heap32VectorToArray', + '$AsciiToString', '$replacePublicSymbol', '$embind__requireFunction', + '$throwUnboundTypeError', '$whenDependentTypesAreResolved', '$getFunctionName'], + _embind_register_function: (name, argCount, rawArgTypesAddr, signature, rawInvoker, fn, isAsync, isNonnullReturn) => { + var argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + name = AsciiToString(name); + name = getFunctionName(name); + + rawInvoker = embind__requireFunction(signature, rawInvoker, isAsync); + + exposePublicSymbol(name, function() { + throwUnboundTypeError(`Cannot call ${name} due to unbound types`, argTypes); + }, argCount - 1); + + whenDependentTypesAreResolved([], argTypes, (argTypes) => { + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + replacePublicSymbol(name, craftInvokerFunction(name, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn, isAsync), argCount - 1); + return []; + }); + }, + + _embind_register_value_array__deps: [ + '$tupleRegistrations', '$AsciiToString', '$embind__requireFunction'], + _embind_register_value_array: ( + rawType, + name, + constructorSignature, + rawConstructor, + destructorSignature, + rawDestructor + ) => { + tupleRegistrations[rawType] = { + name: AsciiToString(name), + rawConstructor: embind__requireFunction(constructorSignature, rawConstructor), + rawDestructor: embind__requireFunction(destructorSignature, rawDestructor), + elements: [], + }; + }, + + _embind_register_value_array_element__deps: [ + '$tupleRegistrations', '$embind__requireFunction'], + _embind_register_value_array_element: ( + rawTupleType, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext + ) => { + tupleRegistrations[rawTupleType].elements.push({ + getterReturnType, + getter: embind__requireFunction(getterSignature, getter), + getterContext, + setterArgumentType, + setter: embind__requireFunction(setterSignature, setter), + setterContext, + }); + }, + + _embind_finalize_value_array__deps: [ + '$tupleRegistrations', '$runDestructors', + '$readPointer', '$whenDependentTypesAreResolved'], + _embind_finalize_value_array: (rawTupleType) => { + var reg = tupleRegistrations[rawTupleType]; + delete tupleRegistrations[rawTupleType]; + var elements = reg.elements; + var elementsLength = elements.length; + var elementTypes = elements.map((elt) => elt.getterReturnType). + concat(elements.map((elt) => elt.setterArgumentType)); + + var rawConstructor = reg.rawConstructor; + var rawDestructor = reg.rawDestructor; + + whenDependentTypesAreResolved([rawTupleType], elementTypes, (elementTypes) => { + for (const [i, elt] of elements.entries()) { + const getterReturnType = elementTypes[i]; + const getter = elt.getter; + const getterContext = elt.getterContext; + const setterArgumentType = elementTypes[i + elementsLength]; + const setter = elt.setter; + const setterContext = elt.setterContext; + elt.read = (ptr) => getterReturnType.fromWireType(getter(getterContext, ptr)); + elt.write = (ptr, o) => { + var destructors = []; + setter(setterContext, ptr, setterArgumentType.toWireType(destructors, o)); + runDestructors(destructors); + }; + } + + return [{ + name: reg.name, + fromWireType: (ptr) => { + var rv = new Array(elementsLength); + for (var i = 0; i < elementsLength; ++i) { + rv[i] = elements[i].read(ptr); + } + rawDestructor(ptr); + return rv; + }, + toWireType: (destructors, o) => { + if (elementsLength !== o.length) { + throw new TypeError(`Incorrect number of tuple elements for ${reg.name}: expected=${elementsLength}, actual=${o.length}`); + } + var ptr = rawConstructor(); + for (var i = 0; i < elementsLength; ++i) { + elements[i].write(ptr, o[i]); + } + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } + return ptr; + }, + readValueFromPointer: readPointer, + destructorFunction: rawDestructor, + }]; + }); + }, + + _embind_register_value_object__deps: [ + '$structRegistrations', '$AsciiToString', '$embind__requireFunction'], + _embind_register_value_object: ( + rawType, + name, + constructorSignature, + rawConstructor, + destructorSignature, + rawDestructor + ) => { + structRegistrations[rawType] = { + name: AsciiToString(name), + rawConstructor: embind__requireFunction(constructorSignature, rawConstructor), + rawDestructor: embind__requireFunction(destructorSignature, rawDestructor), + fields: [], + }; + }, + + _embind_register_value_object_field__deps: [ + '$structRegistrations', '$AsciiToString', '$embind__requireFunction'], + _embind_register_value_object_field: ( + structType, + fieldName, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext + ) => { + structRegistrations[structType].fields.push({ + fieldName: AsciiToString(fieldName), + getterReturnType, + getter: embind__requireFunction(getterSignature, getter), + getterContext, + setterArgumentType, + setter: embind__requireFunction(setterSignature, setter), + setterContext, + }); + }, + + _embind_finalize_value_object__deps: [ + '$structRegistrations', '$runDestructors', + '$readPointer', '$whenDependentTypesAreResolved'], + _embind_finalize_value_object: (structType) => { + var reg = structRegistrations[structType]; + delete structRegistrations[structType]; + + var rawConstructor = reg.rawConstructor; + var rawDestructor = reg.rawDestructor; + var fieldRecords = reg.fields; + var fieldTypes = fieldRecords.map((field) => field.getterReturnType). + concat(fieldRecords.map((field) => field.setterArgumentType)); + whenDependentTypesAreResolved([structType], fieldTypes, (fieldTypes) => { + var fields = {}; + for (var [i, field] of fieldRecords.entries()) { + const getterReturnType = fieldTypes[i]; + const getter = field.getter; + const getterContext = field.getterContext; + const setterArgumentType = fieldTypes[i + fieldRecords.length]; + const setter = field.setter; + const setterContext = field.setterContext; + fields[field.fieldName] = { + read: (ptr) => getterReturnType.fromWireType(getter(getterContext, ptr)), + write: (ptr, o) => { + var destructors = []; + setter(setterContext, ptr, setterArgumentType.toWireType(destructors, o)); + runDestructors(destructors); + }, + optional: getterReturnType.optional, + }; + } + + return [{ + name: reg.name, + fromWireType: (ptr) => { + var rv = {}; + for (var i in fields) { + rv[i] = fields[i].read(ptr); + } + rawDestructor(ptr); + return rv; + }, + toWireType: (destructors, o) => { + // todo: Here we have an opportunity for -O3 level "unsafe" optimizations: + // assume all fields are present without checking. + for (var fieldName in fields) { + if (!(fieldName in o) && !fields[fieldName].optional) { + throw new TypeError(`Missing field: "${fieldName}"`); + } + } + var ptr = rawConstructor(); + for (fieldName in fields) { + fields[fieldName].write(ptr, o[fieldName]); + } + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } + return ptr; + }, + readValueFromPointer: readPointer, + destructorFunction: rawDestructor, + }]; + }); + }, + + $genericPointerToWireType__docs: '/** @suppress {globalThis} */', + $genericPointerToWireType__deps: ['$throwBindingError', '$upcastPointer'], + $genericPointerToWireType: function(destructors, handle) { + var ptr; + if (handle === null) { + if (this.isReference) { + throwBindingError(`null is not a valid ${this.name}`); + } + + if (this.isSmartPointer) { + ptr = this.rawConstructor(); + if (destructors !== null) { + destructors.push(this.rawDestructor, ptr); + } + return ptr; + } else { + return 0; + } + } + + if (!handle || !handle.$$) { + throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); + } + if (!handle.$$.ptr) { + throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); + } + if (!this.isConst && handle.$$.ptrType.isConst) { + throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name)} to parameter type ${this.name}`); + } + var handleClass = handle.$$.ptrType.registeredClass; + ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + + if (this.isSmartPointer) { + // TODO: this is not strictly true + // We could support BY_EMVAL conversions from raw pointers to smart pointers + // because the smart pointer can hold a reference to the handle + if (undefined === handle.$$.smartPtr) { + throwBindingError('Passing raw pointer to smart pointer is illegal'); + } + + switch (this.sharingPolicy) { + case 0: // NONE + // no upcasting + if (handle.$$.smartPtrType === this) { + ptr = handle.$$.smartPtr; + } else { + throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name)} to parameter type ${this.name}`); + } + break; + + case 1: // INTRUSIVE + ptr = handle.$$.smartPtr; + break; + + case 2: // BY_EMVAL + if (handle.$$.smartPtrType === this) { + ptr = handle.$$.smartPtr; + } else { + var clonedHandle = handle['clone'](); + ptr = this.rawShare( + ptr, + Emval.toHandle(() => clonedHandle['delete']()) + ); + if (destructors !== null) { + destructors.push(this.rawDestructor, ptr); + } + } + break; + + default: + throwBindingError('Unsupported sharing policy'); + } + } + return ptr; + }, + + $constNoSmartPtrRawPointerToWireType__docs: '/** @suppress {globalThis} */', + // If we know a pointer type is not going to have SmartPtr logic in it, we can + // special-case optimize it a bit (compare to genericPointerToWireType) + $constNoSmartPtrRawPointerToWireType__deps: ['$throwBindingError', '$upcastPointer', '$embindRepr'], + $constNoSmartPtrRawPointerToWireType: function(destructors, handle) { + if (handle === null) { + if (this.isReference) { + throwBindingError(`null is not a valid ${this.name}`); + } + return 0; + } + + if (!handle.$$) { + throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); + } + if (!handle.$$.ptr) { + throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); + } + var handleClass = handle.$$.ptrType.registeredClass; + var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + return ptr; + }, + + $nonConstNoSmartPtrRawPointerToWireType__docs: '/** @suppress {globalThis} */', + // An optimized version for non-const method accesses - there we must additionally restrict that + // the pointer is not a const-pointer. + $nonConstNoSmartPtrRawPointerToWireType__deps: ['$throwBindingError', '$upcastPointer', '$embindRepr'], + $nonConstNoSmartPtrRawPointerToWireType: function(destructors, handle) { + if (handle === null) { + if (this.isReference) { + throwBindingError(`null is not a valid ${this.name}`); + } + return 0; + } + + if (!handle.$$) { + throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`); + } + if (!handle.$$.ptr) { + throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`); + } + if (handle.$$.ptrType.isConst) { + throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`); + } + var handleClass = handle.$$.ptrType.registeredClass; + var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + return ptr; + }, + + $init_RegisteredPointer__deps: [ + '$RegisteredPointer', + '$readPointer', + '$RegisteredPointer_fromWireType', + ], + $init_RegisteredPointer: () => { + Object.assign(RegisteredPointer.prototype, { + getPointee(ptr) { + if (this.rawGetPointee) { + ptr = this.rawGetPointee(ptr); + } + return ptr; + }, + destructor(ptr) { + this.rawDestructor?.(ptr); + }, + readValueFromPointer: readPointer, + fromWireType: RegisteredPointer_fromWireType, + }); + }, + + $RegisteredPointer__docs: `/** @constructor + @param {*=} pointeeType, + @param {*=} sharingPolicy, + @param {*=} rawGetPointee, + @param {*=} rawConstructor, + @param {*=} rawShare, + @param {*=} rawDestructor, + */`, + $RegisteredPointer__deps: [ + '$constNoSmartPtrRawPointerToWireType', '$genericPointerToWireType', + '$nonConstNoSmartPtrRawPointerToWireType', '$init_RegisteredPointer'], + $RegisteredPointer__postset: 'init_RegisteredPointer()', + $RegisteredPointer: function( + name, + registeredClass, + isReference, + isConst, + + // smart pointer properties + isSmartPointer, + pointeeType, + sharingPolicy, + rawGetPointee, + rawConstructor, + rawShare, + rawDestructor + ) { + this.name = name; + this.registeredClass = registeredClass; + this.isReference = isReference; + this.isConst = isConst; + + // smart pointer properties + this.isSmartPointer = isSmartPointer; + this.pointeeType = pointeeType; + this.sharingPolicy = sharingPolicy; + this.rawGetPointee = rawGetPointee; + this.rawConstructor = rawConstructor; + this.rawShare = rawShare; + this.rawDestructor = rawDestructor; + + if (!isSmartPointer && registeredClass.baseClass === undefined) { + if (isConst) { + this.toWireType = constNoSmartPtrRawPointerToWireType; + this.destructorFunction = null; + } else { + this.toWireType = nonConstNoSmartPtrRawPointerToWireType; + this.destructorFunction = null; + } + } else { + this.toWireType = genericPointerToWireType; + // Here we must leave this.destructorFunction undefined, since whether genericPointerToWireType returns + // a pointer that needs to be freed up is runtime-dependent, and cannot be evaluated at registration time. + // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in + // craftInvokerFunction altogether. + } + }, + + $RegisteredPointer_fromWireType__docs: '/** @suppress {globalThis} */', + $RegisteredPointer_fromWireType__deps: [ + '$downcastPointer', '$registeredPointers', + '$getInheritedInstance', '$makeClassHandle', +#if MEMORY64 + '$bigintToI53Checked' +#endif + ], + $RegisteredPointer_fromWireType: function(ptr) { + // ptr is a raw pointer (or a raw smartpointer) +#if MEMORY64 + ptr = bigintToI53Checked(ptr); +#if ASSERTIONS + assert(Number.isSafeInteger(ptr)); +#endif +#endif + + // rawPointer is a maybe-null raw pointer + var rawPointer = this.getPointee(ptr); + if (!rawPointer) { + this.destructor(ptr); + return null; + } + + var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer); + if (undefined !== registeredInstance) { + // JS object has been neutered, time to repopulate it + if (0 === registeredInstance.$$.count.value) { + registeredInstance.$$.ptr = rawPointer; + registeredInstance.$$.smartPtr = ptr; + return registeredInstance['clone'](); + } else { + // else, just increment reference count on existing object + // it already has a reference to the smart pointer + var rv = registeredInstance['clone'](); + this.destructor(ptr); + return rv; + } + } + + function makeDefaultHandle() { + if (this.isSmartPointer) { + return makeClassHandle(this.registeredClass.instancePrototype, { + ptrType: this.pointeeType, + ptr: rawPointer, + smartPtrType: this, + smartPtr: ptr, + }); + } else { + return makeClassHandle(this.registeredClass.instancePrototype, { + ptrType: this, + ptr, + }); + } + } + + var actualType = this.registeredClass.getActualType(rawPointer); + var registeredPointerRecord = registeredPointers[actualType]; + if (!registeredPointerRecord) { + return makeDefaultHandle.call(this); + } + + var toType; + if (this.isConst) { + toType = registeredPointerRecord.constPointerType; + } else { + toType = registeredPointerRecord.pointerType; + } + var dp = downcastPointer( + rawPointer, + this.registeredClass, + toType.registeredClass); + if (dp === null) { + return makeDefaultHandle.call(this); + } + if (this.isSmartPointer) { + return makeClassHandle(toType.registeredClass.instancePrototype, { + ptrType: toType, + ptr: dp, + smartPtrType: this, + smartPtr: ptr, + }); + } else { + return makeClassHandle(toType.registeredClass.instancePrototype, { + ptrType: toType, + ptr: dp, + }); + } + }, + + $runDestructor: ($$) => { + if ($$.smartPtr) { + $$.smartPtrType.rawDestructor($$.smartPtr); + } else { + $$.ptrType.registeredClass.rawDestructor($$.ptr); + } + }, + + $releaseClassHandle__deps: ['$runDestructor'], + $releaseClassHandle: ($$) => { + $$.count.value -= 1; + var toDelete = 0 === $$.count.value; + if (toDelete) { + runDestructor($$); + } + }, + + $finalizationRegistry: false, + + $detachFinalizer_deps: ['$finalizationRegistry'], + $detachFinalizer: (handle) => {}, + + $attachFinalizer__deps: [ + '$finalizationRegistry', '$detachFinalizer', '$releaseClassHandle', +#if ASSERTIONS + '$RegisteredPointer_fromWireType' +#endif + ], + $attachFinalizer: (handle) => { + if (!globalThis.FinalizationRegistry) { + attachFinalizer = (handle) => handle; + return handle; + } + // If the running environment has a FinalizationRegistry (see + // https://github.com/tc39/proposal-weakrefs), then attach finalizers + // for class handles. We check for the presence of FinalizationRegistry + // at run-time, not build-time. + finalizationRegistry = new FinalizationRegistry((info) => { +#if ASSERTIONS + console.warn(info.leakWarning); +#endif + releaseClassHandle(info.$$); + }); + attachFinalizer = (handle) => { + var $$ = handle.$$; + var hasSmartPtr = !!$$.smartPtr; + if (hasSmartPtr) { + // We should not call the destructor on raw pointers in case other code expects the pointee to live + var info = { $$: $$ }; +#if ASSERTIONS + // Create a warning as an Error instance in advance so that we can store + // the current stacktrace and point to it when / if a leak is detected. + // This is more useful than the empty stacktrace of `FinalizationRegistry` + // callback. + var cls = $$.ptrType.registeredClass; + var err = new Error(`Embind found a leaked C++ instance ${cls.name} <${ptrToString($$.ptr)}>.\n` + + "We'll free it automatically in this case, but this functionality is not reliable across various environments.\n" + + "Make sure to invoke .delete() manually once you're done with the instance instead.\n" + + "Originally allocated"); // `.stack` will add "at ..." after this sentence + if ('captureStackTrace' in Error) { + Error.captureStackTrace(err, RegisteredPointer_fromWireType); + } + info.leakWarning = err.stack.replace(/^Error: /, ''); +#endif + finalizationRegistry.register(handle, info, handle); + } + return handle; + }; + detachFinalizer = (handle) => finalizationRegistry.unregister(handle); + return attachFinalizer(handle); + }, + + $makeClassHandle__deps: ['$throwInternalError', '$attachFinalizer'], + $makeClassHandle: (prototype, record) => { + if (!record.ptrType || !record.ptr) { + throwInternalError('makeClassHandle requires ptr and ptrType'); + } + var hasSmartPtrType = !!record.smartPtrType; + var hasSmartPtr = !!record.smartPtr; + if (hasSmartPtrType !== hasSmartPtr) { + throwInternalError('Both smartPtrType and smartPtr must be specified'); + } + record.count = { value: 1 }; + return attachFinalizer(Object.create(prototype, { + $$: { + value: record, + writable: true, + }, + })); + }, + + $init_ClassHandle__deps: [ + '$ClassHandle', + '$shallowCopyInternalPointer', + '$throwInstanceAlreadyDeleted', + '$attachFinalizer', + '$releaseClassHandle', + '$throwBindingError', + '$detachFinalizer', + '$flushPendingDeletes', + '$delayFunction', + ], + $init_ClassHandle: () => { + let proto = ClassHandle.prototype; + + Object.assign(proto, { + "isAliasOf"(other) { + if (!(this instanceof ClassHandle)) { + return false; + } + if (!(other instanceof ClassHandle)) { + return false; + } + + var leftClass = this.$$.ptrType.registeredClass; + var left = this.$$.ptr; + other.$$ = /** @type {Object} */ (other.$$); + var rightClass = other.$$.ptrType.registeredClass; + var right = other.$$.ptr; + + while (leftClass.baseClass) { + left = leftClass.upcast(left); + leftClass = leftClass.baseClass; + } + + while (rightClass.baseClass) { + right = rightClass.upcast(right); + rightClass = rightClass.baseClass; + } + + return leftClass === rightClass && left === right; + }, + + "clone"() { + if (!this.$$.ptr) { + throwInstanceAlreadyDeleted(this); + } + + if (this.$$.preservePointerOnDelete) { + this.$$.count.value += 1; + return this; + } else { + var clone = attachFinalizer(Object.create(Object.getPrototypeOf(this), { + $$: { + value: shallowCopyInternalPointer(this.$$), + } + })); + + clone.$$.count.value += 1; + clone.$$.deleteScheduled = false; + return clone; + } + }, + + "delete"() { + if (!this.$$.ptr) { + throwInstanceAlreadyDeleted(this); + } + + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { + throwBindingError('Object already scheduled for deletion'); + } + + detachFinalizer(this); + releaseClassHandle(this.$$); + + if (!this.$$.preservePointerOnDelete) { + this.$$.smartPtr = undefined; + this.$$.ptr = undefined; + } + }, + + "isDeleted"() { + return !this.$$.ptr; + }, + + "deleteLater"() { + if (!this.$$.ptr) { + throwInstanceAlreadyDeleted(this); + } + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { + throwBindingError('Object already scheduled for deletion'); + } + deletionQueue.push(this); + if (deletionQueue.length === 1 && delayFunction) { + delayFunction(flushPendingDeletes); + } + this.$$.deleteScheduled = true; + return this; + }, + }); + + // Support `using ...` from https://github.com/tc39/proposal-explicit-resource-management. + const symbolDispose = Symbol.dispose; + if (symbolDispose) { + proto[symbolDispose] = proto['delete']; + } + }, + + $ClassHandle__docs: '/** @constructor */', + $ClassHandle__deps: ['$init_ClassHandle'], + $ClassHandle__postset: 'init_ClassHandle()', + // root of all pointer and smart pointer handles in embind + $ClassHandle: function() { + }, + + $throwInstanceAlreadyDeleted__deps: ['$throwBindingError'], + $throwInstanceAlreadyDeleted: (obj) => { + function getInstanceTypeName(handle) { + return handle.$$.ptrType.registeredClass.name; + } + throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); + }, + + $deletionQueue: [], + + $flushPendingDeletes__deps: ['$deletionQueue'], + $flushPendingDeletes: () => { + while (deletionQueue.length) { + var obj = deletionQueue.pop(); + obj.$$.deleteScheduled = false; + obj['delete'](); + } + }, + + $delayFunction: undefined, + + $setDelayFunction__deps: ['$delayFunction', '$deletionQueue', '$flushPendingDeletes'], + $setDelayFunction: (fn) => { + delayFunction = fn; + if (deletionQueue.length && delayFunction) { + delayFunction(flushPendingDeletes); + } + }, + + $RegisteredClass__docs: '/** @constructor */', + $RegisteredClass: function(name, + constructor, + instancePrototype, + rawDestructor, + baseClass, + getActualType, + upcast, + downcast) { + this.name = name; + this.constructor = constructor; + this.instancePrototype = instancePrototype; + this.rawDestructor = rawDestructor; + this.baseClass = baseClass; + this.getActualType = getActualType; + this.upcast = upcast; + this.downcast = downcast; + this.pureVirtualFunctions = []; + }, + + $shallowCopyInternalPointer: (o) => { + return { + count: o.count, + deleteScheduled: o.deleteScheduled, + preservePointerOnDelete: o.preservePointerOnDelete, + ptr: o.ptr, + ptrType: o.ptrType, + smartPtr: o.smartPtr, + smartPtrType: o.smartPtrType, + }; + }, + + _embind_register_class__deps: [ + '$BindingError', '$ClassHandle', '$createNamedFunction', + '$registeredPointers', '$exposePublicSymbol', + '$makeLegalFunctionName', '$AsciiToString', + '$RegisteredClass', '$RegisteredPointer', '$replacePublicSymbol', + '$embind__requireFunction', '$throwUnboundTypeError', + '$whenDependentTypesAreResolved'], + _embind_register_class: (rawType, + rawPointerType, + rawConstPointerType, + baseClassRawType, + getActualTypeSignature, + getActualType, + upcastSignature, + upcast, + downcastSignature, + downcast, + name, + destructorSignature, + rawDestructor) => { + name = AsciiToString(name); + getActualType = embind__requireFunction(getActualTypeSignature, getActualType); + upcast &&= embind__requireFunction(upcastSignature, upcast); + downcast &&= embind__requireFunction(downcastSignature, downcast); + rawDestructor = embind__requireFunction(destructorSignature, rawDestructor); + var legalFunctionName = makeLegalFunctionName(name); + + exposePublicSymbol(legalFunctionName, function() { + // this code cannot run if baseClassRawType is zero + throwUnboundTypeError(`Cannot construct ${name} due to unbound types`, [baseClassRawType]); + }); + + whenDependentTypesAreResolved( + [rawType, rawPointerType, rawConstPointerType], + baseClassRawType ? [baseClassRawType] : [], + (base) => { + base = base[0]; + + var baseClass; + var basePrototype; + if (baseClassRawType) { + baseClass = base.registeredClass; + basePrototype = baseClass.instancePrototype; + } else { + basePrototype = ClassHandle.prototype; + } + + var constructor = createNamedFunction(name, function(...args) { + if (Object.getPrototypeOf(this) !== instancePrototype) { + throw new BindingError(`Use 'new' to construct ${name}`); + } + if (undefined === registeredClass.constructor_body) { + throw new BindingError(`${name} has no accessible constructor`); + } + var body = registeredClass.constructor_body[args.length]; + if (undefined === body) { + throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`); + } + return body.apply(this, args); + }); + + var instancePrototype = Object.create(basePrototype, { + constructor: { value: constructor }, + }); + + constructor.prototype = instancePrototype; + + var registeredClass = new RegisteredClass(name, + constructor, + instancePrototype, + rawDestructor, + baseClass, + getActualType, + upcast, + downcast); + + if (registeredClass.baseClass) { + // Keep track of class hierarchy. Used to allow sub-classes to inherit class functions. + registeredClass.baseClass.__derivedClasses ??= []; + + registeredClass.baseClass.__derivedClasses.push(registeredClass); + } + + var referenceConverter = new RegisteredPointer(name, + registeredClass, + true, + false, + false); + + var pointerConverter = new RegisteredPointer(name + '*', + registeredClass, + false, + false, + false); + + var constPointerConverter = new RegisteredPointer(name + ' const*', + registeredClass, + false, + true, + false); + + registeredPointers[rawType] = { + pointerType: pointerConverter, + constPointerType: constPointerConverter + }; + + replacePublicSymbol(legalFunctionName, constructor); + + return [referenceConverter, pointerConverter, constPointerConverter]; + } + ); + }, + + _embind_register_iterable__deps: [ + '$whenDependentTypesAreResolved', '$installIndexedIterator', '$AsciiToString', + ], + _embind_register_iterable: (rawClassType, rawElementType, sizeMethodName, getMethodName) => { + sizeMethodName = AsciiToString(sizeMethodName); + getMethodName = AsciiToString(getMethodName); + whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => { + const classType = types[0]; + installIndexedIterator(classType.registeredClass.instancePrototype, sizeMethodName, getMethodName); + return []; + }); + }, + + _embind_register_class_constructor__deps: [ + '$heap32VectorToArray', '$embind__requireFunction', + '$whenDependentTypesAreResolved', + '$craftInvokerFunction'], + _embind_register_class_constructor: ( + rawClassType, + argCount, + rawArgTypesAddr, + invokerSignature, + invoker, + rawConstructor + ) => { +#if ASSERTIONS + assert(argCount > 0); +#endif + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + invoker = embind__requireFunction(invokerSignature, invoker); + var args = [rawConstructor]; + var destructors = []; + + whenDependentTypesAreResolved([], [rawClassType], (classType) => { + classType = classType[0]; + var humanName = `constructor ${classType.name}`; + + if (undefined === classType.registeredClass.constructor_body) { + classType.registeredClass.constructor_body = []; + } + if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { + throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`); + } + classType.registeredClass.constructor_body[argCount - 1] = () => { + throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`, rawArgTypes); + }; + + whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { + // Insert empty slot for context type (argTypes[1]). + argTypes.splice(1, 0, null); + classType.registeredClass.constructor_body[argCount - 1] = craftInvokerFunction(humanName, argTypes, null, invoker, rawConstructor); + return []; + }); + return []; + }); + }, + + $downcastPointer: (ptr, ptrClass, desiredClass) => { + if (ptrClass === desiredClass) { + return ptr; + } + if (undefined === desiredClass.baseClass) { + return null; // no conversion + } + + var rv = downcastPointer(ptr, ptrClass, desiredClass.baseClass); + if (rv === null) { + return null; + } + return desiredClass.downcast(rv); + }, + + $upcastPointer__deps: ['$throwBindingError'], + $upcastPointer: (ptr, ptrClass, desiredClass) => { + while (ptrClass !== desiredClass) { + if (!ptrClass.upcast) { + throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`); + } + ptr = ptrClass.upcast(ptr); + ptrClass = ptrClass.baseClass; + } + return ptr; + }, + + $validateThis__deps: ['$throwBindingError', '$upcastPointer'], + $validateThis: (this_, classType, humanName) => { + if (!(this_ instanceof Object)) { + throwBindingError(`${humanName} with invalid "this": ${this_}`); + } + if (!(this_ instanceof classType.registeredClass.constructor)) { + throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`); + } + if (!this_.$$.ptr) { + throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`); + } + + // todo: kill this + return upcastPointer(this_.$$.ptr, + this_.$$.ptrType.registeredClass, + classType.registeredClass); + }, + + _embind_register_class_function__deps: [ + '$craftInvokerFunction', '$heap32VectorToArray', '$AsciiToString', + '$embind__requireFunction', '$throwUnboundTypeError', + '$whenDependentTypesAreResolved', '$getFunctionName'], + _embind_register_class_function: (rawClassType, + methodName, + argCount, + rawArgTypesAddr, // [ReturnType, ThisType, Args...] + invokerSignature, + rawInvoker, + context, + isPureVirtual, + isAsync, + isNonnullReturn) => { + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = AsciiToString(methodName); + methodName = getFunctionName(methodName); + rawInvoker = embind__requireFunction(invokerSignature, rawInvoker, isAsync); + + whenDependentTypesAreResolved([], [rawClassType], (classType) => { + classType = classType[0]; + var humanName = `${classType.name}.${methodName}`; + + if (methodName.startsWith("@@")) { + methodName = Symbol[methodName.substring(2)]; + } + + if (isPureVirtual) { + classType.registeredClass.pureVirtualFunctions.push(methodName); + } + + function unboundTypesHandler() { + throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`, rawArgTypes); + } + + var proto = classType.registeredClass.instancePrototype; + var method = proto[methodName]; + if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) { + // This is the first overload to be registered, OR we are replacing a + // function in the base class with a function in the derived class. + unboundTypesHandler.argCount = argCount - 2; + unboundTypesHandler.className = classType.name; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up + // a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler; + } + + whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { + var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context, isAsync); + + // Replace the initial unbound-handler-stub function with the + // appropriate member function, now that all types are resolved. If + // multiple overloads are registered for this function, the function + // goes into an overload table. + if (undefined === proto[methodName].overloadTable) { + // Set argCount in case an overload is registered later + memberFunction.argCount = argCount - 2; + proto[methodName] = memberFunction; + } else { + proto[methodName].overloadTable[argCount - 2] = memberFunction; + } + + return []; + }); + return []; + }); + }, + + _embind_register_class_property__deps: [ + '$AsciiToString', '$embind__requireFunction', '$runDestructors', + '$throwBindingError', '$throwUnboundTypeError', + '$whenDependentTypesAreResolved', '$validateThis'], + _embind_register_class_property: (classType, + fieldName, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext) => { + fieldName = AsciiToString(fieldName); + getter = embind__requireFunction(getterSignature, getter); + + whenDependentTypesAreResolved([], [classType], (classType) => { + classType = classType[0]; + var humanName = `${classType.name}.${fieldName}`; + var desc = { + get() { + throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [getterReturnType, setterArgumentType]); + }, + enumerable: true, + configurable: true + }; + if (setter) { + desc.set = () => throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [getterReturnType, setterArgumentType]); + } else { + desc.set = (v) => throwBindingError(humanName + ' is a read-only property'); + } + + Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); + + whenDependentTypesAreResolved( + [], + (setter ? [getterReturnType, setterArgumentType] : [getterReturnType]), + (types) => { + var getterReturnType = types[0]; + var desc = { + get() { + var ptr = validateThis(this, classType, humanName + ' getter'); + return getterReturnType.fromWireType(getter(getterContext, ptr)); + }, + enumerable: true + }; + + if (setter) { + setter = embind__requireFunction(setterSignature, setter); + var setterArgumentType = types[1]; + desc.set = function(v) { + var ptr = validateThis(this, classType, humanName + ' setter'); + var destructors = []; + setter(setterContext, ptr, setterArgumentType.toWireType(destructors, v)); + runDestructors(destructors); + }; + } + + Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); + return []; + }); + + return []; + }); + }, + + _embind_register_class_class_function__deps: [ + '$craftInvokerFunction', '$ensureOverloadTable', '$heap32VectorToArray', + '$AsciiToString', '$embind__requireFunction', '$throwUnboundTypeError', + '$whenDependentTypesAreResolved', '$getFunctionName'], + _embind_register_class_class_function: (rawClassType, + methodName, + argCount, + rawArgTypesAddr, + invokerSignature, + rawInvoker, + fn, + isAsync, + isNonnullReturn) => { + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = AsciiToString(methodName); + methodName = getFunctionName(methodName); + rawInvoker = embind__requireFunction(invokerSignature, rawInvoker, isAsync); + whenDependentTypesAreResolved([], [rawClassType], (classType) => { + classType = classType[0]; + var humanName = `${classType.name}.${methodName}`; + + function unboundTypesHandler() { + throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`, rawArgTypes); + } + + if (methodName.startsWith('@@')) { + methodName = Symbol[methodName.substring(2)]; + } + + var proto = classType.registeredClass.constructor; + if (undefined === proto[methodName]) { + // This is the first function to be registered with this name. + unboundTypesHandler.argCount = argCount-1; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up + // a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; + } + + whenDependentTypesAreResolved([], rawArgTypes, (argTypes) => { + // Replace the initial unbound-types-handler stub with the proper + // function. If multiple overloads are registered, the function handlers + // go into an overload table. + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn, isAsync); + if (undefined === proto[methodName].overloadTable) { + func.argCount = argCount-1; + proto[methodName] = func; + } else { + proto[methodName].overloadTable[argCount-1] = func; + } + + if (classType.registeredClass.__derivedClasses) { + for (const derivedClass of classType.registeredClass.__derivedClasses) { + if (!derivedClass.constructor.hasOwnProperty(methodName)) { + // TODO: Add support for overloads + derivedClass.constructor[methodName] = func; + } + } + } + + return []; + }); + return []; + }); + }, + + _embind_register_class_class_property__deps: [ + '$AsciiToString', '$embind__requireFunction', '$runDestructors', + '$throwBindingError', '$throwUnboundTypeError', + '$whenDependentTypesAreResolved'], + _embind_register_class_class_property: (rawClassType, + fieldName, + rawFieldType, + rawFieldPtr, + getterSignature, + getter, + setterSignature, + setter) => { + fieldName = AsciiToString(fieldName); + getter = embind__requireFunction(getterSignature, getter); + + whenDependentTypesAreResolved([], [rawClassType], (classType) => { + classType = classType[0]; + var humanName = `${classType.name}.${fieldName}`; + var desc = { + get() { + throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [rawFieldType]); + }, + enumerable: true, + configurable: true + }; + if (setter) { + desc.set = () => { + throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`, [rawFieldType]); + }; + } else { + desc.set = (v) => { + throwBindingError(`${humanName} is a read-only property`); + }; + } + + Object.defineProperty(classType.registeredClass.constructor, fieldName, desc); + + whenDependentTypesAreResolved([], [rawFieldType], (fieldType) => { + fieldType = fieldType[0]; + var desc = { + get() { + return fieldType.fromWireType(getter(rawFieldPtr)); + }, + enumerable: true + }; + + if (setter) { + setter = embind__requireFunction(setterSignature, setter); + desc.set = (v) => { + var destructors = []; + setter(rawFieldPtr, fieldType.toWireType(destructors, v)); + runDestructors(destructors); + }; + } + + Object.defineProperty(classType.registeredClass.constructor, fieldName, desc); + return []; + }); + + return []; + }); + }, + + _embind_create_inheriting_constructor__deps: [ + '$createNamedFunction', '$Emval', + '$PureVirtualError', '$AsciiToString', + '$registerInheritedInstance', + '$requireRegisteredType', '$throwBindingError', + '$unregisterInheritedInstance', '$detachFinalizer', '$attachFinalizer'], + _embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => { + constructorName = AsciiToString(constructorName); + wrapperType = requireRegisteredType(wrapperType, 'wrapper'); + properties = Emval.toValue(properties); + + var registeredClass = wrapperType.registeredClass; + var wrapperPrototype = registeredClass.instancePrototype; + var baseClass = registeredClass.baseClass; + var baseClassPrototype = baseClass.instancePrototype; + var baseConstructor = registeredClass.baseClass.constructor; + var ctor = createNamedFunction(constructorName, function(...args) { + for (var name of registeredClass.baseClass.pureVirtualFunctions) { + if (this[name] === baseClassPrototype[name]) { + throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`); + } + } + + Object.defineProperty(this, '__parent', { + value: wrapperPrototype + }); + this['__construct'](...args); + }); + + // It's a little nasty that we're modifying the wrapper prototype here. + + wrapperPrototype['__construct'] = function __construct(...args) { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __construct"); + } + + var inner = baseConstructor['implement'](this, ...args); + detachFinalizer(inner); + var $$ = inner.$$; + inner['notifyOnDestruction'](); + $$.preservePointerOnDelete = true; + Object.defineProperties(this, { $$: { + value: $$ + }}); + attachFinalizer(this); + registerInheritedInstance(registeredClass, $$.ptr, this); + }; + + wrapperPrototype['__destruct'] = function __destruct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __destruct"); + } + + detachFinalizer(this); + unregisterInheritedInstance(registeredClass, this.$$.ptr); + }; + + ctor.prototype = Object.create(wrapperPrototype); + Object.assign(ctor.prototype, properties); + return Emval.toHandle(ctor); + }, + + $char_0: '0'.charCodeAt(0), + $char_9: '9'.charCodeAt(0), + $makeLegalFunctionName__deps: ['$char_0', '$char_9'], + $makeLegalFunctionName: (name) => { +#if ASSERTIONS + assert(typeof name === 'string'); +#endif + name = name.replace(/[^a-zA-Z0-9_]/g, '$'); + var f = name.charCodeAt(0); + if (f >= char_0 && f <= char_9) { + return `_${name}`; + } + return name; + }, + + _embind_register_smart_ptr__deps: ['$RegisteredPointer', '$embind__requireFunction', '$whenDependentTypesAreResolved'], + _embind_register_smart_ptr: (rawType, + rawPointeeType, + name, + sharingPolicy, + getPointeeSignature, + rawGetPointee, + constructorSignature, + rawConstructor, + shareSignature, + rawShare, + destructorSignature, + rawDestructor) => { + name = AsciiToString(name); + rawGetPointee = embind__requireFunction(getPointeeSignature, rawGetPointee); + rawConstructor = embind__requireFunction(constructorSignature, rawConstructor); + rawShare = embind__requireFunction(shareSignature, rawShare); + rawDestructor = embind__requireFunction(destructorSignature, rawDestructor); + + whenDependentTypesAreResolved([rawType], [rawPointeeType], (pointeeType) => { + pointeeType = pointeeType[0]; + + var registeredPointer = new RegisteredPointer(name, + pointeeType.registeredClass, + false, + false, + // smart pointer properties + true, + pointeeType, + sharingPolicy, + rawGetPointee, + rawConstructor, + rawShare, + rawDestructor); + return [registeredPointer]; + }); + }, + + _embind_register_enum__docs: '/** @suppress {globalThis} */', + _embind_register_enum__deps: ['$exposePublicSymbol', '$enumReadValueFromPointer', + '$AsciiToString', '$registerType', '$getEnumValueType'], + _embind_register_enum: (rawType, name, size, isSigned, rawValueType) => { + name = AsciiToString(name); + const valueType = getEnumValueType(rawValueType); + + switch (valueType) { + case 'object': { + function ctor() {} + ctor.values = {}; + + registerType(rawType, { + name, + constructor: ctor, + valueType, + fromWireType: function(c) { + return this.constructor.values[c]; + }, + toWireType: (destructors, c) => c.value, + readValueFromPointer: enumReadValueFromPointer(name, size, isSigned), + destructorFunction: null, + }); + + exposePublicSymbol(name, ctor); + break; + } + case 'number': { + var keysMap = {}; + + registerType(rawType, { + name: name, + keysMap, + valueType, + fromWireType: (c) => c, + toWireType: (destructors, c) => c, + readValueFromPointer: enumReadValueFromPointer(name, size, isSigned), + destructorFunction: null, + }); + + exposePublicSymbol(name, keysMap); + // Just exposes a simple dict. argCount is meaningless here, + delete Module[name].argCount; + break; + } + case 'string': { + var valuesMap = {}; + var reverseMap = {}; + var keysMap = {}; + + registerType(rawType, { + name: name, + valuesMap, + reverseMap, + keysMap, + valueType, + fromWireType: function(c) { + return this.reverseMap[c]; + }, + toWireType: function(destructors, c) { + return this.valuesMap[c]; + }, + readValueFromPointer: enumReadValueFromPointer(name, size, isSigned), + destructorFunction: null, + }); + + exposePublicSymbol(name, keysMap); + // Just exposes a simple dict. argCount is meaningless here, + delete Module[name].argCount; + break; + } + } + }, + + _embind_register_enum_value__deps: ['$createNamedFunction', '$AsciiToString', '$requireRegisteredType'], + _embind_register_enum_value: (rawEnumType, name, enumValue) => { + var enumType = requireRegisteredType(rawEnumType, 'enum'); + name = AsciiToString(name); + + switch (enumType.valueType) { + case 'object': { + var Enum = enumType.constructor; + var Value = Object.create(enumType.constructor.prototype, { + value: {value: enumValue}, + constructor: {value: createNamedFunction(`${enumType.name}_${name}`, function() {})}, + }); + Enum.values[enumValue] = Value; + Enum[name] = Value; + break; + } + case 'number': { + enumType.keysMap[name] = enumValue; + break; + } + case 'string': { + enumType.valuesMap[name] = enumValue; + enumType.reverseMap[enumValue] = name; + enumType.keysMap[name] = name; + break; + } + } + }, + + _embind_register_constant__deps: ['$AsciiToString', '$whenDependentTypesAreResolved'], + _embind_register_constant: (name, type, value) => { + name = AsciiToString(name); + whenDependentTypesAreResolved([], [type], (type) => { + type = type[0]; + Module[name] = type.fromWireType(value); + return []; + }); + }, +}; + +addToLibrary(LibraryEmbind); diff --git a/src/lib/libembind_gen.js b/src/lib/libembind_gen.js new file mode 100644 index 0000000000000..9ae86581a23ef --- /dev/null +++ b/src/lib/libembind_gen.js @@ -0,0 +1,939 @@ +// Copyright 2023 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 "libembind_shared.js" + +var LibraryEmbind = { + + $moduleDefinitions: [], + // Function signatures that have already been generated for JS generation. + $emittedFunctions: 'new Set()', + + $PrimitiveType: class { + constructor(typeId, name, destructorType) { + this.typeId = typeId; + this.name = name; + this.destructorType = destructorType; + } + }, + $IntegerType: class { + constructor(typeId) { + this.typeId = typeId; + this.destructorType = 'none'; + } + }, + $Argument: class { + constructor(name, type) { + this.name = name; + this.type = type; + } + }, + $UserType: class { + constructor(typeId, name) { + this.typeId = typeId; + this.name = name; + this.destructorType = 'none'; // Same as emval. + } + }, + $UserTypeDefinition: class { + constructor(typeId, name, definition) { + this.typeId = typeId; + this.name = name; + this.definition = definition; + this.destructorType = 'none'; // Same as emval. + } + + print(nameMap, out) { + out.push(`export type ${this.name} = ${this.definition};\n\n`); + } + }, + $OptionalType: class { + constructor(type) { + this.type = type; + this.destructorType = 'none'; // Same as emval. + } + }, + $FunctionDefinition__deps: ['$createJsInvoker', '$createJsInvokerSignature', '$emittedFunctions'], + $FunctionDefinition: class { + hasPublicSymbol = true; + constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isNonnullReturn = false, isAsync = false) { + this.name = name; + this.returnType = returnType; + this.argumentTypes = argumentTypes; + this.functionIndex = functionIndex; + this.thisType = thisType; + this.isNonnullReturn = isNonnullReturn; + this.isAsync = isAsync; + } + + printSignature(nameMap, out) { + out.push('('); + const argOut = []; + // Work backwards on the arguments, so optional types can be replaced + // with TS optional params until we see the first non-optional argument. + let seenNonOptional = false; + for (let i = this.argumentTypes.length - 1; i >= 0; i--) { + const arg = this.argumentTypes[i]; + let argType; + let argName; + if (arg.type instanceof OptionalType && !seenNonOptional) { + argType = nameMap(arg.type.type); + argName = arg.name + '?'; + } else { + seenNonOptional = true; + argType = nameMap(arg.type); + argName = arg.name; + } + argOut.unshift(`${argName}: ${argType}`); + } + + out.push(argOut.join(', ')); + let returnType = this.returnType; + // Constructors can return a pointer, but it will be a non-null pointer. + // Change the return type to the class type so the TS output doesn't + // have `| null`. + if (this.isNonnullReturn && this.returnType instanceof PointerDefinition) { + returnType = this.returnType.classType; + } + returnType = nameMap(returnType, true); + if (this.isAsync) { + returnType = `Promise<${returnType}>`; + } + out.push(`): ${returnType}`); + } + + printFunction(nameMap, out) { + out.push(`${this.name}`); + this.printSignature(nameMap, out); + } + + printModuleEntry(nameMap, out) { + out.push(' '); + this.printFunction(nameMap, out); + out.push(';\n'); + } + + // Convert a type definition in this file to something that matches the type + // object in embind.js `registerType()`. + convertToEmbindType(type) { + const ret = { + name: type.name, + }; + switch (type.destructorType) { + case 'none': + ret.destructorFunction = null; + break; + case 'function': + ret.destructorFunction = true; + break; + case 'stack': + // Intentionally empty since embind uses `undefined` for this type. + break; + default: + throw new Error(`Bad destructor type '${type.destructorType}'`); + } + return ret; + } + + printJs(out) { + const argTypes = [this.convertToEmbindType(this.returnType)]; + if (this.thisType) { + argTypes.push(this.convertToEmbindType(this.thisType)); + } else { + argTypes.push(null); + } + for (const argType of this.argumentTypes) { + argTypes.push(this.convertToEmbindType(argType.type)); + } + const signature = createJsInvokerSignature(argTypes, !!this.thisType, !this.returnType.isVoid, this.isAsync) + if (emittedFunctions.has(signature)) { + return; + } + emittedFunctions.add(signature); + let invokerFactory = createJsInvoker(argTypes, !!this.thisType, !this.returnType.isVoid, this.isAsync); + out.push(`'${signature}': ${invokerFactory},`); + } + }, + $PointerDefinition: class { + constructor(classType, isConst, isSmartPointer) { + this.classType = classType; + this.isConst = isConst; + this.isSmartPointer = isSmartPointer; + this.destructorType = 'none'; + if (isSmartPointer || classType.base) { + this.destructorType = 'stack'; + } + } + }, + $ClassDefinition: class { + hasPublicSymbol = true; + constructor(typeId, name, base = null) { + this.typeId = typeId; + this.name = name; + this.methods = []; + this.staticMethods = []; + this.staticProperties = []; + this.constructors = []; + this.base = base; + this.properties = []; + this.iterableElementType = null; + this.destructorType = 'none'; + if (base) { + this.destructorType = 'stack'; + } + } + + print(nameMap, out) { + out.push(`export interface ${this.name}`); + const extendsParts = []; + if (this.base) { + extendsParts.push(this.base.name); + } else { + extendsParts.push('ClassHandle'); + } + if (this.iterableElementType) { + extendsParts.push(`Iterable<${nameMap(this.iterableElementType, true)}>`); + } + out.push(` extends ${extendsParts.join(', ')}`); + out.push(' {\n'); + for (const property of this.properties) { + const props = []; + property.print(nameMap, props); + for (const formattedProp of props) { + out.push(` ${formattedProp};\n`); + } + } + for (const method of this.methods) { + out.push(' '); + method.printFunction(nameMap, out); + out.push(';\n'); + } + out.push('}\n\n'); + } + + printModuleEntry(nameMap, out) { + out.push(` ${this.name}: {`); + const entries = []; + for (const construct of this.constructors) { + const entry = []; + entry.push('new'); + construct.printSignature(nameMap, entry); + entries.push(entry.join('')); + } + for (const method of this.staticMethods) { + const entry = []; + method.printFunction(nameMap, entry); + entries.push(entry.join('')); + } + for (const prop of this.staticProperties) { + const entry = []; + prop.print(nameMap, entry); + entries.push(...entry); + } + if (entries.length) { + out.push('\n'); + for (const entry of entries) { + out.push(` ${entry};\n`); + } + out.push(' '); + } + out.push('};\n'); + } + + printJs(out) { + out.push(`// class ${this.name}\n`); + if (this.constructors.length) { + out.push(`// constructors\n`); + for (const construct of this.constructors) { + construct.printJs(out); + } + } + if (this.staticMethods.length) { + out.push(`// static methods\n`); + for (const method of this.staticMethods) { + method.printJs(out); + } + } + if (this.methods.length) { + out.push(`// methods\n`); + for (const method of this.methods) { + method.printJs(out); + } + } + out.push('\n'); + } + + }, + $ClassProperty: class { + constructor(type, name, readonly) { + this.type = type; + this.name = name; + this.readonly = readonly; + } + + print(nameMap, out) { + const setType = nameMap(this.type, false); + const getType = nameMap(this.type, true); + if (this.readonly || setType === getType) { + out.push(`${this.readonly ? 'readonly ' : ''}${this.name}: ${getType}`); + return; + } + // The getter/setter types don't match, so generate each get/set definition. + out.push(`get ${this.name}(): ${getType}`); + out.push(`set ${this.name}(value: ${setType})`); + } + }, + $ConstantDefinition: class { + hasPublicSymbol = true; + constructor(type, name) { + this.type = type; + this.name = name; + } + + printModuleEntry(nameMap, out) { + out.push(` ${this.name}: ${nameMap(this.type)};\n`); + } + }, + $EnumDefinition: class { + hasPublicSymbol = true; + constructor(typeId, name, valueType) { + this.typeId = typeId; + this.name = name; + this.items = []; + this.destructorType = 'none'; + this.valueType = valueType; + } + + print(nameMap, out) { + if (this.valueType === 'object') { + out.push(`export interface ${this.name}Value {\n`); + out.push(' value: T;\n}\n'); + } + out.push(`export type ${this.name} = `); + if (this.items.length === 0) { + out.push('never/* Empty Enumerator */'); + } else { + const outItems = []; + for (const [name, value] of this.items) { + switch (this.valueType) { + case 'object': + outItems.push(`${this.name}Value<${value}>`); + break; + case 'number': + outItems.push(`${value}`); + break; + case 'string': + outItems.push(`'${name}'`); + break; + } + } + out.push(outItems.join('|')); + } + out.push(';\n\n'); + } + + printModuleEntry(nameMap, out) { + out.push(` ${this.name}: {`); + const outItems = []; + for (const [name, value] of this.items) { + switch (this.valueType) { + case 'object': + outItems.push(`${name}: ${this.name}Value<${value}>`); + break; + case 'number': + outItems.push(`${name}: ${value}`); + break; + case 'string': + outItems.push(`${name}: '${name}'`); + break; + } + } + out.push(outItems.join(', ')); + out.push('};\n'); + } + }, + $ValueArrayDefinition: class { + constructor(typeId, name) { + this.typeId = typeId; + this.name = name; + this.elementTypeIds = []; + this.elements = []; + this.destructorType = 'function'; + } + + print(nameMap, out) { + out.push(`export type ${this.name} = [ `); + const outElements = []; + for (const type of this.elements) { + outElements.push(nameMap(type)); + } + out.push(outElements.join(', ')) + out.push(' ];\n\n'); + } + }, + $ValueObjectDefinition: class { + constructor(typeId, name) { + this.typeId = typeId; + this.name = name; + this.fieldTypeIds = []; + this.fieldNames = []; + this.fields = []; + this.destructorType = 'function'; + } + + print(nameMap, out) { + out.push(`export type ${this.name} = {\n`); + const outFields = []; + for (const {name, type} of this.fields) { + outFields.push(` ${name}${type instanceof OptionalType ? '?' : ''}: ${nameMap(type)}`); + } + out.push(outFields.join(',\n')) + out.push('\n};\n\n'); + } + }, + $TsPrinter__deps: ['$OptionalType', '$ClassDefinition'], + $TsPrinter: class { + constructor(definitions) { + this.definitions = definitions; + const jsString = 'EmbindString'; // Type alias for multiple types. + // The mapping is in the format of '' => ['toWireType', 'fromWireType'] + // or if the to/from wire types are the same use a single element. + this.builtInToJsName = new Map([ + ['bool', ['boolean']], + ['float', ['number']], + ['double', ['number']], +#if MEMORY64 + ['long', ['bigint']], + ['unsigned long', ['bigint']], +#endif +#if WASM_BIGINT + ['long long', ['bigint']], + ['unsigned long long', ['bigint']], +#endif + ['void', ['void']], + ['std::string', [jsString, 'string']], + ['std::basic_string', [jsString, 'string']], + ['std::wstring', ['string']], + ['std::u16string', ['string']], + ['std::u32string', ['string']], + ['emscripten::val', ['any']], + ]); + // Signal that the type alias for EmbindString is needed. + this.usedEmbindString = false; + } + + typeToJsName(type, isFromWireType = false) { + if (type instanceof IntegerType) { + return 'number'; + } + if (type instanceof PrimitiveType) { + if (!this.builtInToJsName.has(type.name)) { + throw new Error(`Missing primitive type to TS type for '${type.name}'`); + } + const [toWireType, fromWireType = toWireType] = this.builtInToJsName.get(type.name); + const tsName = isFromWireType ? fromWireType : toWireType; + if (tsName === 'EmbindString') { + this.usedEmbindString = true; + } + return tsName; + } + if (type instanceof PointerDefinition) { + return `${this.typeToJsName(type.classType, isFromWireType)} | null`; + } + if (type instanceof OptionalType) { + return `${this.typeToJsName(type.type, isFromWireType)} | undefined`; + } + return type.name; + } + + print() { + const out = []; + let hadClass = false; + for (const def of this.definitions) { + if (def instanceof ClassDefinition) { + hadClass = true; + break; + } + } + if (hadClass) { + out.push( + 'export interface ClassHandle {\n', + ' isAliasOf(other: ClassHandle): boolean;\n', + ' delete(): void;\n', + ' deleteLater(): this;\n', + ' isDeleted(): boolean;\n', + ' // @ts-ignore - If targeting lower than ESNext, this symbol might not exist.\n', + ' [Symbol.dispose](): void;\n', + ' clone(): this;\n', + '}\n', + ); + } + for (const def of this.definitions) { + if (!def.print) { + continue; + } + def.print(this.typeToJsName.bind(this), out); + } + // Print module definitions + out.push('interface EmbindModule {\n'); + for (const def of this.definitions) { + if (!def.printModuleEntry) { + continue; + } + def.printModuleEntry(this.typeToJsName.bind(this), out); + } + out.push('}\n'); + if (this.usedEmbindString) { + out.unshift('type EmbindString = ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string;\n'); + } + return out.join(''); + } + }, + + $JsPrinter: class { + constructor(definitions) { + this.definitions = definitions; + } + + print() { + const out = ['{\n']; + const publicSymbols = []; + for (const def of this.definitions) { + if (def.hasPublicSymbol) { + publicSymbols.push(def.name); + } + if (!def.printJs) { + continue; + } + def.printJs(out); + } + out.push('}\n'); + return JSON.stringify({ + 'invokers': out.join(''), + publicSymbols, + }); + } + }, + + $registerType__deps: ['$sharedRegisterType'], + $registerType: function(rawType, registeredInstance, options = {}) { + return sharedRegisterType(rawType, registeredInstance, options); + }, + $registerPrimitiveType__deps: ['$registerType', '$PrimitiveType'], + $registerPrimitiveType: (id, name, destructorType) => { + name = AsciiToString(name); + registerType(id, new PrimitiveType(id, name, destructorType)); + }, + $registerIntegerType__deps: ['$registerType', '$IntegerType'], + $registerIntegerType: (id) => { + registerType(id, new IntegerType(id)); + }, + $createFunctionDefinition__deps: ['$FunctionDefinition', '$heap32VectorToArray', '$AsciiToString', '$Argument', '$whenDependentTypesAreResolved', '$getFunctionName', '$getFunctionArgsName', '$PointerDefinition', '$ClassDefinition'], + $createFunctionDefinition: (name, argCount, rawArgTypesAddr, functionIndex, hasThis, isNonnullReturn, isAsync, cb) => { + const argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + name = typeof name === 'string' ? name : AsciiToString(name); + + whenDependentTypesAreResolved([], argTypes, function (argTypes) { + const argsName = getFunctionArgsName(name); + name = getFunctionName(name); + const returnType = argTypes[0]; + let thisType = null; + let argStart = 1; + if (hasThis) { + thisType = argTypes[1]; + if (thisType instanceof PointerDefinition) { + thisType = argTypes[1].classType; + } + if (!(thisType instanceof ClassDefinition)) { + throw new Error('This type must be class definition for: ' + name); + } + argStart = 2; + } + if (argsName && argsName.length != (argTypes.length - hasThis - 1)) { + throw new Error('Argument names should match number of parameters.'); + } + + const args = []; + for (let i = argStart, x = 0; i < argTypes.length; i++) { + if (argsName) { + args.push(new Argument(argsName[x++], argTypes[i])); + } else { + args.push(new Argument(`_${i - argStart}`, argTypes[i])); + } + } + const funcDef = new FunctionDefinition(name, returnType, args, functionIndex, thisType, isNonnullReturn, isAsync); + cb(funcDef); + return []; + }); + }, + _embind_register_void__deps: ['$registerPrimitiveType'], + _embind_register_void: (rawType, name) => { + const voidType = new PrimitiveType(rawType, 'void', 'none'); + voidType.isVoid = true; // Match the marker property from the non-AOT mode. + registerType(rawType, voidType); + }, + _embind_register_bool__deps: ['$registerPrimitiveType'], + _embind_register_bool: (rawType, name, trueValue, falseValue) => { + registerPrimitiveType(rawType, name, 'none'); + }, + _embind_register_integer__deps: ['$registerIntegerType'], + _embind_register_integer: (primitiveType, name, size, minRange, maxRange) => { + registerIntegerType(primitiveType, name); + }, + _embind_register_bigint: (primitiveType, name, size, minRange, maxRange) => { + registerPrimitiveType(primitiveType, name, 'none'); + }, + _embind_register_float__deps: ['$registerPrimitiveType'], + _embind_register_float: (rawType, name, size) => { + registerPrimitiveType(rawType, name, 'none'); + }, + _embind_register_std_string__deps: ['$registerPrimitiveType'], + _embind_register_std_string: (rawType, name) => { + registerPrimitiveType(rawType, name, 'function'); + }, + _embind_register_std_wstring: (rawType, charSize, name) => { + registerPrimitiveType(rawType, name, 'function'); + }, + _embind_register_emval__deps: ['$registerType', '$PrimitiveType'], + _embind_register_emval: (rawType) => { + registerType(rawType, new PrimitiveType(rawType, 'emscripten::val', 'none')); + }, + _embind_register_user_type__deps: ['$registerType', '$AsciiToString', '$UserType'], + _embind_register_user_type: (rawType, name) => { + name = AsciiToString(name); + registerType(rawType, new UserType(rawType, name)); + }, + _embind_register_user_type_definition__deps: ['$registerType', '$AsciiToString', '$UserTypeDefinition'], + _embind_register_user_type_definition: (rawType, name, definition) => { + name = AsciiToString(name); + definition = AsciiToString(definition); + const userTypeDef = new UserTypeDefinition(rawType, name, definition); + registerType(rawType, userTypeDef); + moduleDefinitions.push(userTypeDef); + }, + _embind_register_optional__deps: ['$OptionalType'], + _embind_register_optional: (rawOptionalType, rawType) => { + whenDependentTypesAreResolved([rawOptionalType], [rawType], function(type) { + type = type[0]; + return [new OptionalType(type)]; + }); + }, + _embind_register_memory_view: (rawType, dataTypeIndex, name) => { + // TODO + }, + _embind_register_function__deps: ['$moduleDefinitions', '$createFunctionDefinition'], + _embind_register_function: (name, argCount, rawArgTypesAddr, signature, rawInvoker, fn, isAsync, isNonnullReturn) => { + createFunctionDefinition(name, argCount, rawArgTypesAddr, fn, false, isNonnullReturn, isAsync, (funcDef) => { + moduleDefinitions.push(funcDef); + }); + }, + _embind_register_class__deps: ['$AsciiToString', '$ClassDefinition', '$whenDependentTypesAreResolved', '$moduleDefinitions', '$PointerDefinition'], + _embind_register_class: function(rawType, + rawPointerType, + rawConstPointerType, + baseClassRawType, + getActualTypeSignature, + getActualType, + upcastSignature, + upcast, + downcastSignature, + downcast, + name, + destructorSignature, + rawDestructor) { + name = AsciiToString(name); + whenDependentTypesAreResolved( + [rawType, rawPointerType, rawConstPointerType], + baseClassRawType ? [baseClassRawType] : [], + function(base) { + const hasBase = base.length; + const classDef = new ClassDefinition(rawType, name, hasBase ? base[0] : null); + moduleDefinitions.push(classDef); + + const pointer = new PointerDefinition(classDef, false, false); + const constPointer = new PointerDefinition(classDef, true, false); + return [classDef, pointer, constPointer]; + } + ); + + }, + _embind_register_iterable__deps: ['$whenDependentTypesAreResolved'], + _embind_register_iterable: (rawClassType, rawElementType, sizeMethodName, getMethodName) => { + whenDependentTypesAreResolved([], [rawClassType, rawElementType], (types) => { + const classType = types[0]; + const elementType = types[1]; + classType.iterableElementType = elementType; + return []; + }); + }, + _embind_register_class_constructor__deps: ['$whenDependentTypesAreResolved', '$createFunctionDefinition'], + _embind_register_class_constructor: function( + rawClassType, + argCount, + rawArgTypesAddr, + invokerSignature, + invoker, + rawConstructor + ) { + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + createFunctionDefinition(`constructor ${classType.name}`, argCount, rawArgTypesAddr, rawConstructor, false, true, false, (funcDef) => { + classType.constructors.push(funcDef); + }); + return []; + }); + }, + _embind_register_class_function__deps: ['$createFunctionDefinition'], + _embind_register_class_function: function(rawClassType, + methodName, + argCount, + rawArgTypesAddr, // [ReturnType, ThisType, Args...] + invokerSignature, + rawInvoker, + context, + isPureVirtual, + isAsync, + isNonnullReturn) { + createFunctionDefinition(methodName, argCount, rawArgTypesAddr, context, true, isNonnullReturn, isAsync, (funcDef) => { + const classDef = funcDef.thisType; + classDef.methods.push(funcDef); + }); + }, + _embind_register_class_property__deps: [ + '$AsciiToString', '$whenDependentTypesAreResolved', '$ClassProperty'], + _embind_register_class_property: function(classType, + fieldName, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext) { + fieldName = AsciiToString(fieldName); + const readonly = setter === 0; + if (!(readonly || getterReturnType === setterArgumentType)) { + throw new error('Mismatched getter and setter types are not supported.'); + } + + whenDependentTypesAreResolved([], [classType], function(classType) { + classType = classType[0]; + whenDependentTypesAreResolved([], [getterReturnType], function(types) { + const prop = new ClassProperty(types[0], fieldName, readonly); + classType.properties.push(prop); + return []; + }); + return []; + }); + }, + _embind_register_class_class_function__deps: ['$createFunctionDefinition'], + _embind_register_class_class_function: function(rawClassType, + methodName, + argCount, + rawArgTypesAddr, + invokerSignature, + rawInvoker, + fn, + isAsync, + isNonnullReturn) { + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + createFunctionDefinition(methodName, argCount, rawArgTypesAddr, fn, false, isNonnullReturn, isAsync, (funcDef) => { + classType.staticMethods.push(funcDef); + }); + return []; + }); + }, + _embind_register_class_class_property__deps: [ + '$AsciiToString', '$whenDependentTypesAreResolved', '$ClassProperty'], + _embind_register_class_class_property: (rawClassType, + fieldName, + rawFieldType, + rawFieldPtr, + getterSignature, + getter, + setterSignature, + setter) => { + fieldName = AsciiToString(fieldName); + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + whenDependentTypesAreResolved([], [rawFieldType], function(types) { + const prop = new ClassProperty(types[0], fieldName); + classType.staticProperties.push(prop); + return []; + }); + return []; + }); + }, + // Stub function. This is called when extending an object and not needed for TS generation. + _embind_create_inheriting_constructor: (constructorName, wrapperType, properties) => {}, + _embind_register_enum__deps: ['$AsciiToString', '$EnumDefinition', '$moduleDefinitions', '$getEnumValueType'], + _embind_register_enum: function(rawType, name, size, isSigned, rawValueType) { + name = AsciiToString(name); + const valueType = getEnumValueType(rawValueType); + const enumDef = new EnumDefinition(rawType, name, valueType); + registerType(rawType, enumDef); + moduleDefinitions.push(enumDef); + }, + _embind_register_enum_value__deps: ['$AsciiToString', '$requireRegisteredType'], + _embind_register_enum_value: function(rawEnumType, name, enumValue) { + name = AsciiToString(name); + const enumDef = requireRegisteredType(rawEnumType, name); + enumDef.items.push([name, enumValue]); + }, + _embind_register_constant__deps: ['$AsciiToString', '$ConstantDefinition', '$whenDependentTypesAreResolved', '$moduleDefinitions'], + _embind_register_constant: function(name, typeId, value) { + name = AsciiToString(name); + whenDependentTypesAreResolved([], [typeId], function(types) { + const def = new ConstantDefinition(types[0], name); + moduleDefinitions.push(def); + return []; + }); + }, + _embind_register_value_array__deps: [ + '$AsciiToString', '$ValueArrayDefinition', '$tupleRegistrations'], + _embind_register_value_array: function( + rawType, + name, + constructorSignature, + rawConstructor, + destructorSignature, + rawDestructor + ) { + name = AsciiToString(name); + const valueArray = new ValueArrayDefinition(rawType, name); + tupleRegistrations[rawType] = valueArray; + }, + _embind_register_value_array_element__deps: ['$tupleRegistrations'], + _embind_register_value_array_element: function( + rawTupleType, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext + ) { + const valueArray = tupleRegistrations[rawTupleType]; + if (getterReturnType !== setterArgumentType) { + throw new Error('Mismatched getter and setter types are not supported.'); + } + + valueArray.elementTypeIds.push(getterReturnType); + }, + _embind_finalize_value_array__deps: ['$whenDependentTypesAreResolved', '$moduleDefinitions', '$tupleRegistrations'], + _embind_finalize_value_array: function(rawTupleType) { + const valueArray = tupleRegistrations[rawTupleType]; + delete tupleRegistrations[rawTupleType]; + whenDependentTypesAreResolved([rawTupleType], valueArray.elementTypeIds, function(types) { + moduleDefinitions.push(valueArray); + valueArray.elements = types; + return [valueArray]; + }); + }, + _embind_register_value_object__deps: ['$AsciiToString', '$ValueObjectDefinition', '$structRegistrations'], + _embind_register_value_object: function( + rawType, + name, + constructorSignature, + rawConstructor, + destructorSignature, + rawDestructor + ) { + name = AsciiToString(name); + const valueObject = new ValueObjectDefinition(rawType, name); + structRegistrations[rawType] = valueObject; + }, + _embind_register_value_object_field__deps: [ + '$AsciiToString', '$structRegistrations'], + _embind_register_value_object_field: function( + structType, + fieldName, + getterReturnType, + getterSignature, + getter, + getterContext, + setterArgumentType, + setterSignature, + setter, + setterContext + ) { + const valueObject = structRegistrations[structType]; + if (getterReturnType !== setterArgumentType) { + throw new Error('Mismatched getter and setter types are not supported.'); + } + + valueObject.fieldTypeIds.push(getterReturnType); + valueObject.fieldNames.push(AsciiToString(fieldName)); + }, + _embind_finalize_value_object__deps: ['$moduleDefinitions', '$whenDependentTypesAreResolved', '$structRegistrations'], + _embind_finalize_value_object: function(structType) { + const valueObject = structRegistrations[structType]; + delete structRegistrations[structType]; + whenDependentTypesAreResolved([structType], valueObject.fieldTypeIds, function(types) { + moduleDefinitions.push(valueObject); + for (let i = 0; i < types.length; i++) { + valueObject.fields.push({ + name: valueObject.fieldNames[i], + type: types[i], + }); + } + return [valueObject]; + }); + }, + _embind_register_smart_ptr__deps: ['$whenDependentTypesAreResolved'], + _embind_register_smart_ptr: function(rawType, + rawPointeeType, + name, + sharingPolicy, + getPointeeSignature, + rawGetPointee, + constructorSignature, + rawConstructor, + shareSignature, + rawShare, + destructorSignature, + rawDestructor) { + whenDependentTypesAreResolved([rawType], [rawPointeeType], function(pointeeType) { + const smartPointer = new PointerDefinition(pointeeType[0], false, true); + return [smartPointer]; + }); + }, + + $emitOutput__deps: ['$awaitingDependencies', '$throwBindingError', '$getTypeName', '$moduleDefinitions', +#if EMBIND_AOT + '$JsPrinter', +#else + '$TsPrinter', +#endif + ], + $emitOutput__postset: () => { addAtPostCtor('emitOutput()'); }, + $emitOutput: () => { + for (const typeId in awaitingDependencies) { + throwBindingError(`Missing binding for type: '${getTypeName(typeId)}' typeId: ${typeId}`); + } +#if EMBIND_AOT + const printer = new JsPrinter(moduleDefinitions); +#else + const printer = new TsPrinter(moduleDefinitions); +#endif + const output = printer.print(); + var fs = require('node:fs'); + fs.writeFileSync(process.argv[2], output + '\n'); + }, + + // Stub functions used by eval, but not needed for TS generation: + $makeLegalFunctionName: () => { throw new Error('stub function should not be called'); }, + $runDestructors: () => { throw new Error('stub function should not be called'); }, + $flushPendingDeletes: () => { throw new Error('stub function should not be called'); }, + $setDelayFunction: () => { throw new Error('stub function should not be called'); }, + $PureVirtualError: () => { throw new Error('stub function should not be called'); }, +}; + +extraLibraryFuncs.push('$emitOutput'); + +addToLibrary(LibraryEmbind); diff --git a/src/lib/libembind_shared.js b/src/lib/libembind_shared.js new file mode 100644 index 0000000000000..87d6d36be8939 --- /dev/null +++ b/src/lib/libembind_shared.js @@ -0,0 +1,305 @@ +// Copyright 2023 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. +var LibraryEmbindShared = { + $InternalError: "= class InternalError extends Error { constructor(message) { super(message); this.name = 'InternalError'; }}", + $BindingError: "= class BindingError extends Error { constructor(message) { super(message); this.name = 'BindingError'; }}", + + $throwInternalError__deps: ['$InternalError'], + $throwInternalError: (message) => { throw new InternalError(message); }, + + $throwBindingError__deps: ['$BindingError'], + $throwBindingError: (message) => { throw new BindingError(message); }, + + // typeID -> { toWireType: ..., fromWireType: ... } + $registeredTypes: {}, + + // typeID -> [callback] + $awaitingDependencies: {}, + + // typeID -> [dependentTypes] + $typeDependencies: {}, + + $tupleRegistrations: {}, + + $structRegistrations: {}, + + $sharedRegisterType__deps: [ + '$awaitingDependencies', '$registeredTypes', + '$typeDependencies', '$throwBindingError' ], + $sharedRegisterType__docs: '/** @param {Object=} options */', + $sharedRegisterType: function(rawType, registeredInstance, options = {}) { + var name = registeredInstance.name; + if (!rawType) { + throwBindingError(`type "${name}" must have a positive integer typeid pointer`); + } + if (registeredTypes.hasOwnProperty(rawType)) { + if (options.ignoreDuplicateRegistrations) { + return; + } else { + throwBindingError(`Cannot register type '${name}' twice`); + } + } + + registeredTypes[rawType] = registeredInstance; + delete typeDependencies[rawType]; + + if (awaitingDependencies.hasOwnProperty(rawType)) { + var callbacks = awaitingDependencies[rawType]; + delete awaitingDependencies[rawType]; + callbacks.forEach((cb) => cb()); + } + }, + + $whenDependentTypesAreResolved__deps: [ + '$awaitingDependencies', '$registeredTypes', + '$typeDependencies', '$throwInternalError'], + $whenDependentTypesAreResolved: (myTypes, dependentTypes, getTypeConverters) => { + myTypes.forEach((type) => typeDependencies[type] = dependentTypes); + + function onComplete(typeConverters) { + var myTypeConverters = getTypeConverters(typeConverters); + if (myTypeConverters.length !== myTypes.length) { + throwInternalError('Mismatched type converter count'); + } + for (var i = 0; i < myTypes.length; ++i) { + registerType(myTypes[i], myTypeConverters[i]); + } + } + + var typeConverters = new Array(dependentTypes.length); + var unregisteredTypes = []; + var registered = 0; + for (let [i, dt] of dependentTypes.entries()) { + if (registeredTypes.hasOwnProperty(dt)) { + typeConverters[i] = registeredTypes[dt]; + } else { + unregisteredTypes.push(dt); + if (!awaitingDependencies.hasOwnProperty(dt)) { + awaitingDependencies[dt] = []; + } + awaitingDependencies[dt].push(() => { + typeConverters[i] = registeredTypes[dt]; + ++registered; + if (registered === unregisteredTypes.length) { + onComplete(typeConverters); + } + }); + } + } + if (0 === unregisteredTypes.length) { + onComplete(typeConverters); + } + }, + + $getTypeName__deps: ['$AsciiToString', '__getTypeName', 'free'], + $getTypeName: (type) => { + var ptr = ___getTypeName(type); + var rv = AsciiToString(ptr); + _free(ptr); + return rv; + }, + $getFunctionName__deps: [], + $getFunctionName: (signature) => { + signature = signature.trim(); + const argsIndex = signature.indexOf("("); + if (argsIndex === -1) return signature; +#if ASSERTIONS + assert(signature.endsWith(")"), "Parentheses for argument names should match."); +#endif + return signature.slice(0, argsIndex); + }, + $getFunctionArgsName__deps: [], + $getFunctionArgsName: (signature) => { + signature = signature.trim(); + const argsIndex = signature.indexOf("("); + if (argsIndex == -1) return; // Return undefined to mean we don't have any argument names +#if ASSERTIONS + assert(signature.endsWith(")"), "Parentheses for argument names should match."); +#endif + return signature.slice(argsIndex + 1, -1).replaceAll(" ", "").split(",").filter(n => n.length); + }, + $heap32VectorToArray: (count, firstElement) => { + var array = []; + for (var i = 0; i < count; i++) { + // TODO(https://github.com/emscripten-core/emscripten/issues/17310): + // Find a way to hoist the `>> 2` or `>> 3` out of this loop. + array.push({{{ makeGetValue('firstElement', `i * ${POINTER_SIZE}`, '*') }}}); + } + return array; + }, + + $requireRegisteredType__deps: [ + '$registeredTypes', '$getTypeName', '$throwBindingError'], + $requireRegisteredType: (rawType, humanName) => { + var impl = registeredTypes[rawType]; + if (undefined === impl) { + throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`); + } + return impl; + }, + + $usesDestructorStack(argTypes) { + // Skip return value at index 0 - it's not deleted here. + for (var i = 1; i < argTypes.length; ++i) { + // The type does not define a destructor function - must use dynamic stack + if (argTypes[i] !== null && argTypes[i].destructorFunction === undefined) { + return true; + } + } + return false; + }, + + // Many of the JS invoker functions are generic and can be reused for multiple + // function bindings. This function needs to match createJsInvoker and create + // a unique signature for any inputs that will create different invoker + // function outputs. + $createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync) { + const signature = [ + isClassMethodFunc ? 't' : 'f', + returns ? 't' : 'f', + isAsync ? 't' : 'f' + ]; + for (let i = isClassMethodFunc ? 1 : 2; i < argTypes.length; ++i) { + const arg = argTypes[i]; + let destructorSig = ''; + if (arg.destructorFunction === undefined) { + destructorSig = 'u'; + } else if (arg.destructorFunction === null) { + destructorSig = 'n'; + } else { + destructorSig = 't'; + } + signature.push(destructorSig); + } + return signature.join(''); + }, + + $checkArgCount(numArgs, minArgs, maxArgs, humanName, throwBindingError) { + if (numArgs < minArgs || numArgs > maxArgs) { + var argCountMessage = minArgs == maxArgs ? minArgs : `${minArgs} to ${maxArgs}`; + throwBindingError(`function ${humanName} called with ${numArgs} arguments, expected ${argCountMessage}`); + } + }, + + $getEnumValueType(rawValueType) { + // This must match the values of enum_value_type in wire.h + return rawValueType === 0 ? 'object' : (rawValueType === 1 ? 'number' : 'string'); + }, + + $getRequiredArgCount(argTypes) { + var requiredArgCount = argTypes.length - 2; + for (var i = argTypes.length - 1; i >= 2; --i) { + if (!argTypes[i].optional) { + break; + } + requiredArgCount--; + } + return requiredArgCount; + }, + + $createJsInvoker__deps: ['$usesDestructorStack', +#if ASSERTIONS + '$checkArgCount', +#endif + ], + $createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync) { + var needsDestructorStack = usesDestructorStack(argTypes); + var argCount = argTypes.length - 2; + var argsList = []; + var argsListWired = ['fn']; + if (isClassMethodFunc) { + argsListWired.push('thisWired'); + } + for (var i = 0; i < argCount; ++i) { + argsList.push(`arg${i}`) + argsListWired.push(`arg${i}Wired`) + } + argsList = argsList.join(',') + argsListWired = argsListWired.join(',') + + var invokerFnBody = `return function (${argsList}) {\n`; + +#if ASSERTIONS + invokerFnBody += "checkArgCount(arguments.length, minArgs, maxArgs, humanName, throwBindingError);\n"; +#endif + +#if EMSCRIPTEN_TRACING + invokerFnBody += `Module.emscripten_trace_enter_context('embind::' + humanName );\n`; +#endif + + if (needsDestructorStack) { + invokerFnBody += "var destructors = [];\n"; + } + + var dtorStack = needsDestructorStack ? "destructors" : "null"; + var args1 = ["humanName", "throwBindingError", "invoker", "fn", "runDestructors", "fromRetWire", "toClassParamWire"]; + +#if EMSCRIPTEN_TRACING + args1.push("Module"); +#endif + + if (isClassMethodFunc) { + invokerFnBody += `var thisWired = toClassParamWire(${dtorStack}, this);\n`; + } + + for (var i = 0; i < argCount; ++i) { + var argName = `toArg${i}Wire`; + invokerFnBody += `var arg${i}Wired = ${argName}(${dtorStack}, arg${i});\n`; + args1.push(argName); + } + + invokerFnBody += (returns || isAsync ? "var rv = ":"") + `invoker(${argsListWired});\n`; + + var returnVal = returns ? "rv" : ""; +#if ASYNCIFY == 1 + args1.push("Asyncify"); +#endif +#if ASYNCIFY + invokerFnBody += `function onDone(${returnVal}) {\n`; +#endif + + if (needsDestructorStack) { + invokerFnBody += "runDestructors(destructors);\n"; + } else { + for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. + var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); + if (argTypes[i].destructorFunction !== null) { + invokerFnBody += `${paramName}_dtor(${paramName});\n`; + args1.push(`${paramName}_dtor`); + } + } + } + + if (returns) { + invokerFnBody += "var ret = fromRetWire(rv);\n" + +#if EMSCRIPTEN_TRACING + "Module.emscripten_trace_exit_context();\n" + +#endif + "return ret;\n"; + } else { +#if EMSCRIPTEN_TRACING + invokerFnBody += "Module.emscripten_trace_exit_context();\n"; +#endif + } + +#if ASYNCIFY == 1 + invokerFnBody += "}\n"; + invokerFnBody += `return Asyncify.currData ? Asyncify.whenDone().then(onDone) : onDone(${returnVal});\n` +#elif ASYNCIFY == 2 + invokerFnBody += "}\n"; + invokerFnBody += "return " + (isAsync ? "rv.then(onDone)" : `onDone(${returnVal})`) + ";"; +#endif + + invokerFnBody += "}\n"; + +#if ASSERTIONS + args1.push('checkArgCount', 'minArgs', 'maxArgs'); + invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`; +#endif + return new Function(args1, invokerFnBody); + } +}; + +addToLibrary(LibraryEmbindShared); diff --git a/src/lib/libemval.js b/src/lib/libemval.js new file mode 100644 index 0000000000000..ada9d19e87529 --- /dev/null +++ b/src/lib/libemval.js @@ -0,0 +1,519 @@ +// Copyright 2012 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. + +// Number of handles reserved for non-use (0) or common values w/o refcount. +{{{ + const EMVAL_RESERVED_HANDLES = 5; + const EMVAL_LAST_RESERVED_HANDLE = EMVAL_RESERVED_HANDLES * 2 - 1; +}}} +var LibraryEmVal = { + // Stack of handles available for reuse. + $emval_freelist: [], +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + $emval_exception_decrefs: [], +#endif + // Array of alternating pairs (value, refcount). + // reserve 0 and some special values. These never get de-allocated. + $emval_handles: [ + 0, 1, + undefined, 1, + null, 1, + true, 1, + false, 1, + ], +#if ASSERTIONS + $emval_handles__postset: 'assert(emval_handles.length === {{{ EMVAL_RESERVED_HANDLES }}} * 2)', +#endif + $emval_symbols: {}, // address -> string + + $count_emval_handles__deps: ['$emval_freelist', '$emval_handles'], + $count_emval_handles: () => { + return emval_handles.length / 2 - {{{ EMVAL_RESERVED_HANDLES }}} - emval_freelist.length; + }, + + _emval_register_symbol__deps: ['$emval_symbols', '$AsciiToString'], + _emval_register_symbol: (address) => { + emval_symbols[address] = AsciiToString(address); + }, + + $getStringOrSymbol__deps: ['$emval_symbols', '$AsciiToString'], + $getStringOrSymbol: (address) => { + var symbol = emval_symbols[address]; + if (symbol === undefined) { + return AsciiToString(address); + } + return symbol; + }, + + $Emval__deps: ['$emval_freelist', '$emval_handles', '$throwBindingError'], + $Emval: { + toValue: (handle) => { + if (!handle) { + throwBindingError(`Cannot use deleted val. handle = ${handle}`); + } + #if ASSERTIONS + // handle 2 is supposed to be `undefined`. + assert(handle === 2 || emval_handles[handle] !== undefined && handle % 2 === 0, `invalid handle: ${handle}`); + #endif + return emval_handles[handle]; + }, + + toHandle: (value) => { + switch (value) { + case undefined: return 2; + case null: return 4; + case true: return 6; + case false: return 8; + default:{ + const handle = emval_freelist.pop() || emval_handles.length; + emval_handles[handle] = value; + emval_handles[handle + 1] = 1; + return handle; + } + } + } + }, + + _emval_incref__deps: ['$emval_handles'], + _emval_incref: (handle) => { + if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}}) { + emval_handles[handle + 1] += 1; + } + }, + + _emval_decref__deps: ['$emval_freelist', '$emval_handles', +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$emval_exception_decrefs', +#endif + ], + _emval_decref: (handle) => { + if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { + #if ASSERTIONS + assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`); + #endif + var value = emval_handles[handle]; + emval_handles[handle] = undefined; +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + // In case the value is a C++ exception, decrement the refcount, so the + // memory can be freed correctly + var destructor = emval_exception_decrefs[handle]; + if (destructor) { + emval_exception_decrefs[handle] = undefined; + destructor(value); + } +#endif + emval_freelist.push(handle); + } + }, + + _emval_run_destructors__deps: ['_emval_decref', '$Emval', '$runDestructors'], + _emval_run_destructors: (handle) => { + var destructors = Emval.toValue(handle); + runDestructors(destructors); + __emval_decref(handle); + }, + + _emval_new_array__deps: ['$Emval'], + _emval_new_array: () => Emval.toHandle([]), + +#if !SUPPORT_BIG_ENDIAN + _emval_new_array_from_memory_view__deps: ['$Emval'], + _emval_new_array_from_memory_view: (view) => { + view = Emval.toValue(view); + // using for..loop is faster than Array.from + var a = new Array(view.length); + for (var i = 0; i < view.length; i++) a[i] = view[i]; + return Emval.toHandle(a); + }, + _emval_array_to_memory_view__deps: ['$Emval'], + _emval_array_to_memory_view: (dst, src) => { + dst = Emval.toValue(dst); + src = Emval.toValue(src); + dst.set(src); + }, +#else + _emval_new_array_from_memory_view__deps: ['$Emval'], + _emval_new_array_from_memory_view: (view) => { + view = Emval.toValue(view); + const dv = new DataView(view.buffer, view.byteOffset); + const reader = { + Int8Array: dv.getInt8, + Uint8Array: dv.getUint8, + Int16Array: dv.getInt16, + Uint16Array: dv.getUint16, + Int32Array: dv.getInt32, + Uint32Array: dv.getUint32, + BigInt64Array: dv.getBigInt64, + BigUint64Array: dv.getBigUint64, + Float32Array: dv.getFloat32, + Float64Array: dv.getFloat64, + }[view[Symbol.toStringTag]]; + var a = new Array(view.length); + for (var i = 0; i < view.length; i++) a[i] = reader.call(dv, i * view.BYTES_PER_ELEMENT, true); + return Emval.toHandle(a); + }, + _emval_array_to_memory_view__deps: ['$Emval'], + _emval_array_to_memory_view: (dst, src) => { + dst = Emval.toValue(dst); + src = Emval.toValue(src); + const dv = new DataView(dst.buffer, dst.byteOffset); + const writer = { + Int8Array: dv.setInt8, + Uint8Array: dv.setUint8, + Int16Array: dv.setInt16, + Uint16Array: dv.setUint16, + Int32Array: dv.setInt32, + Uint32Array: dv.setUint32, + BigInt64Array: dv.setBigInt64, + BigUint64Array: dv.setBigUint64, + Float32Array: dv.setFloat32, + Float64Array: dv.setFloat64, + }[dst[Symbol.toStringTag]]; + for (var i = 0; i < src.length; i++) writer.call(dv, i * dst.BYTES_PER_ELEMENT, src[i], true); + }, +#endif + + _emval_new_object__deps: ['$Emval'], + _emval_new_object: () => Emval.toHandle({}), + + _emval_new_cstring__deps: ['$getStringOrSymbol', '$Emval'], + _emval_new_cstring: (v) => Emval.toHandle(getStringOrSymbol(v)), + + _emval_new_u8string__deps: ['$Emval'], + _emval_new_u8string: (v) => Emval.toHandle(UTF8ToString(v)), + + _emval_new_u16string__deps: ['$Emval'], + _emval_new_u16string: (v) => Emval.toHandle(UTF16ToString(v)), + + _emval_get_global__deps: ['$Emval', '$getStringOrSymbol'], + _emval_get_global: (name) => { + if (!name) { + return Emval.toHandle(globalThis); + } + name = getStringOrSymbol(name); + return Emval.toHandle(globalThis[name]); + }, + + _emval_get_module_property__deps: ['$getStringOrSymbol', '$Emval'], + _emval_get_module_property: (name) => { + name = getStringOrSymbol(name); + return Emval.toHandle(Module[name]); + }, + + _emval_get_property__deps: ['$Emval'], + _emval_get_property: (handle, key) => { + handle = Emval.toValue(handle); + key = Emval.toValue(key); + return Emval.toHandle(handle[key]); + }, + + _emval_set_property__deps: ['$Emval'], + _emval_set_property: (handle, key, value) => { + handle = Emval.toValue(handle); + key = Emval.toValue(key); + value = Emval.toValue(value); + handle[key] = value; + }, + + $emval_returnValue__deps: ['$Emval'], + $emval_returnValue: (toReturnWire, destructorsRef, handle) => { + var destructors = []; + var result = toReturnWire(destructors, handle); + if (destructors.length) { + // void, primitives and any other types w/o destructors don't need to allocate a handle + {{{ makeSetValue('destructorsRef', '0', 'Emval.toHandle(destructors)', '*') }}}; + } + return result; + }, + + _emval_equals__deps: ['$Emval'], + _emval_equals: (first, second) => { + first = Emval.toValue(first); + second = Emval.toValue(second); + return first == second; + }, + + _emval_strictly_equals__deps: ['$Emval'], + _emval_strictly_equals: (first, second) => { + first = Emval.toValue(first); + second = Emval.toValue(second); + return first === second; + }, + + _emval_greater_than__deps: ['$Emval'], + _emval_greater_than: (first, second) => { + first = Emval.toValue(first); + second = Emval.toValue(second); + return first > second; + }, + + _emval_less_than__deps: ['$Emval'], + _emval_less_than: (first, second) => { + first = Emval.toValue(first); + second = Emval.toValue(second); + return first < second; + }, + + _emval_not__deps: ['$Emval'], + _emval_not: (object) => { + object = Emval.toValue(object); + return !object; + }, + + $emval_lookupTypes__deps: ['$requireRegisteredType'], + $emval_lookupTypes: (argCount, argTypes) => { + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + a[i] = requireRegisteredType({{{ makeGetValue('argTypes', `i*${POINTER_SIZE}`, '*') }}}, + `parameter ${i}`); + } + return a; + }, + + // Leave id 0 undefined. It's not a big deal, but might be confusing + // to have null be a valid method caller. + $emval_methodCallers: [undefined], + + $emval_addMethodCaller__deps: ['$emval_methodCallers'], + $emval_addMethodCaller: (caller) => { + var id = emval_methodCallers.length; + emval_methodCallers.push(caller); + return id; + }, + + _emval_create_invoker__deps: [ + '$emval_addMethodCaller', '$emval_lookupTypes', + '$createNamedFunction', '$emval_returnValue', + '$Emval', '$getStringOrSymbol', + ], + _emval_create_invoker: (argCount, argTypesPtr, kind) => { + var GenericWireTypeSize = {{{ 2 * POINTER_SIZE }}}; + + var [retType, ...argTypes] = emval_lookupTypes(argCount, argTypesPtr); + var toReturnWire = retType.toWireType.bind(retType); + var argFromPtr = argTypes.map(type => type.readValueFromPointer.bind(type)); + argCount--; // remove the extracted return type + +#if DYNAMIC_EXECUTION + var captures = {'toValue': Emval.toValue}; + var args = argFromPtr.map((argFromPtr, i) => { + var captureName = `argFromPtr${i}`; + captures[captureName] = argFromPtr; + return `${captureName}(args${i ? '+' + i * GenericWireTypeSize : ''})`; + }); + var functionBody; + switch (kind){ + case {{{ cDefs['internal::EM_INVOKER_KIND::FUNCTION'] }}}: + functionBody = 'toValue(handle)'; + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::CONSTRUCTOR'] }}}: + functionBody = 'new (toValue(handle))'; + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::CAST'] }}}: + functionBody = ''; + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::METHOD'] }}}: + captures['getStringOrSymbol'] = getStringOrSymbol; + functionBody = 'toValue(handle)[getStringOrSymbol(methodName)]'; + break; + } + functionBody += `(${args})`; + if (!retType.isVoid) { + captures['toReturnWire'] = toReturnWire; + captures['emval_returnValue'] = emval_returnValue; + functionBody = `return emval_returnValue(toReturnWire, destructorsRef, ${functionBody})`; + } + functionBody = `return function (handle, methodName, destructorsRef, args) { +${functionBody} +}`; + + var invokerFunction = new Function(Object.keys(captures), functionBody)(...Object.values(captures)); +#else + var argN = new Array(argCount); + var invokerFunction = (handle, methodName, destructorsRef, args) => { + var offset = 0; + for (var i = 0; i < argCount; ++i) { + argN[i] = argFromPtr[i](args + offset); + offset += GenericWireTypeSize; + } + var rv; + switch (kind) { + case {{{ cDefs['internal::EM_INVOKER_KIND::FUNCTION'] }}}: + rv = Emval.toValue(handle).apply(null, argN); + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::CONSTRUCTOR'] }}}: + rv = Reflect.construct(Emval.toValue(handle), argN); + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::CAST'] }}}: + // no-op, just return the argument + rv = argN[0]; + break; + case {{{ cDefs['internal::EM_INVOKER_KIND::METHOD'] }}}: + rv = Emval.toValue(handle)[getStringOrSymbol(methodName)](...argN); + break; + } + return emval_returnValue(toReturnWire, destructorsRef, rv); + }; +#endif + var functionName = `methodCaller<(${argTypes.map(t => t.name)}) => ${retType.name}>`; + return emval_addMethodCaller(createNamedFunction(functionName, invokerFunction)); + }, + + _emval_invoke__deps: ['$getStringOrSymbol', '$emval_methodCallers', '$Emval'], + _emval_invoke: (caller, handle, methodName, destructorsRef, args) => { + return emval_methodCallers[caller](handle, methodName, destructorsRef, args); + }, + + // Same as `_emval_invoke`, just imported into Wasm under a different return type. + // TODO: remove this if/when https://github.com/emscripten-core/emscripten/issues/20478 is fixed. + _emval_invoke_i64: '_emval_invoke', + + _emval_typeof__deps: ['$Emval'], + _emval_typeof: (handle) => { + handle = Emval.toValue(handle); + return Emval.toHandle(typeof handle); + }, + + _emval_instanceof__deps: ['$Emval'], + _emval_instanceof: (object, constructor) => { + object = Emval.toValue(object); + constructor = Emval.toValue(constructor); + return object instanceof constructor; + }, + + _emval_is_number__deps: ['$Emval'], + _emval_is_number: (handle) => { + handle = Emval.toValue(handle); + return typeof handle == 'number'; + }, + + _emval_is_string__deps: ['$Emval'], + _emval_is_string: (handle) => { + handle = Emval.toValue(handle); + return typeof handle == 'string'; + }, + + _emval_in__deps: ['$Emval'], + _emval_in: (item, object) => { + item = Emval.toValue(item); + object = Emval.toValue(object); + return item in object; + }, + + _emval_delete__deps: ['$Emval'], + _emval_delete: (object, property) => { + object = Emval.toValue(object); + property = Emval.toValue(property); + return delete object[property]; + }, + +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + $isCppExceptionObject__deps: ['$Emval'], + $isCppExceptionObject: (object) => { +#if !DISABLE_EXCEPTION_CATCHING + return object instanceof CppException; +#else // WASM_EXCEPTIONS + return object instanceof WebAssembly.Exception; +#endif + }, +#endif + + _emval_throw__deps: ['$Emval', +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', + '$ExceptionInfo', +#endif + '$incrementExceptionRefcount', + '$incrementUncaughtExceptionCount', + '$isCppExceptionObject', +#endif + ], + _emval_throw: (object) => { + object = Emval.toValue(object); +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + if (isCppExceptionObject(object)) { +#if !DISABLE_EXCEPTION_CATCHING + var info = new ExceptionInfo(object.excPtr); + info.set_caught(false); + info.set_rethrown(false); + exceptionLast = object; +#endif + incrementUncaughtExceptionCount(); + incrementExceptionRefcount(object); + } +#endif + throw object; + }, + +#if ASYNCIFY + _emval_await__deps: ['$Emval', '$Asyncify'], + _emval_await__async: 'auto', + _emval_await: async (promise) => { + var value = await Emval.toValue(promise); + return Emval.toHandle(value); + }, +#endif + + _emval_iter_begin__deps: ['$Emval'], + _emval_iter_begin: (iterable) => { + iterable = Emval.toValue(iterable); + return Emval.toHandle(iterable[Symbol.iterator]()); + }, + + _emval_iter_next__deps: ['$Emval'], + _emval_iter_next: (iterator) => { + iterator = Emval.toValue(iterator); + var result = iterator.next(); + return result.done ? 0 : Emval.toHandle(result.value); + }, + + _emval_coro_suspend__deps: ['$Emval', '_emval_coro_resume', '_emval_coro_reject'], + _emval_coro_suspend: (promiseHandle, awaiterPtr) => { + Emval.toValue(promiseHandle) + .then((result) => __emval_coro_resume(awaiterPtr, Emval.toHandle(result)), + (error) => __emval_coro_reject(awaiterPtr, Emval.toHandle(error))); + }, + + _emval_coro_make_promise__deps: ['$Emval'], + _emval_coro_make_promise: (resolveHandlePtr, rejectHandlePtr) => { + return Emval.toHandle(new Promise((resolve, reject) => { + {{{ makeSetValue('resolveHandlePtr', '0', 'Emval.toHandle(resolve)', '*') }}}; + {{{ makeSetValue('rejectHandlePtr', '0', 'Emval.toHandle(reject)', '*') }}}; + })); + }, + + _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + '$decrementUncaughtExceptionCount', +#endif +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$decrementExceptionRefcount', + '$emval_exception_decrefs', +#endif + ], + _emval_from_current_cxa_exception: () => { + try { + // Use __cxa_rethrow which already has mechanism for generating + // user-friendly error message and stacktrace from C++ exception + // if EXCEPTION_STACK_TRACES is enabled and numeric exception + // with metadata optimised out otherwise. + ___cxa_rethrow(); + } catch (e) { +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + // ___cxa_rethrow incremented uncaughtExceptionCount. + // Since we caught it in JS, we need to manually decrement it to balance. + decrementUncaughtExceptionCount(); +#endif + var handle = Emval.toHandle(e); +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + emval_exception_decrefs[handle] = decrementExceptionRefcount; +#endif + return handle; + } + }, +}; + +addToLibrary(LibraryEmVal); diff --git a/src/lib/libeventloop.js b/src/lib/libeventloop.js new file mode 100644 index 0000000000000..8e0a84474edd6 --- /dev/null +++ b/src/lib/libeventloop.js @@ -0,0 +1,538 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// Implementation of functions from emscripten/eventloop.h. + +LibraryJSEventLoop = { + emscripten_unwind_to_js_event_loop: () => { + throw 'unwind'; + }, + + $safeSetTimeout__deps: ['$callUserCallback'], + $safeSetTimeout__docs: '/** @param {number=} timeout */', + $safeSetTimeout: (func, timeout) => { + {{{ runtimeKeepalivePush() }}} + return setTimeout(() => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(func); + }, timeout); + }, + + // Just like setImmediate but returns an i32 that can be passed back + // to wasm rather than a JS object. + $setImmediateWrapped: (func) => { + setImmediateWrapped.mapping ||= []; + var id = setImmediateWrapped.mapping.length; + setImmediateWrapped.mapping[id] = setImmediate(() => { + setImmediateWrapped.mapping[id] = undefined; + func(); + }); + return id; + }, + + $safeRequestAnimationFrame__deps: ['$MainLoop'], + $safeRequestAnimationFrame: (func) => { + {{{ runtimeKeepalivePush() }}} + return MainLoop.requestAnimationFrame(() => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(func); + }); + }, + + // Just like clearImmediate but takes an i32 rather than an object. + $clearImmediateWrapped: (id) => { +#if ASSERTIONS + assert(id); + assert(setImmediateWrapped.mapping[id]); +#endif + clearImmediate(setImmediateWrapped.mapping[id]); + setImmediateWrapped.mapping[id] = undefined; + }, + + $emSetImmediate__deps: ['$setImmediateWrapped', '$clearImmediateWrapped', '$emClearImmediate'], + $emSetImmediate__postset: ` + if (globalThis.setImmediate) { + emSetImmediate = setImmediateWrapped; + emClearImmediate = clearImmediateWrapped; + } else if (globalThis.addEventListener) { + var __setImmediate_id_counter = 0; + var __setImmediate_queue = []; + var __setImmediate_message_id = "_si"; + /** @param {Event} e */ + var __setImmediate_cb = (e) => { + if (e.data === __setImmediate_message_id) { + e.stopPropagation(); + __setImmediate_queue.shift()(); + ++__setImmediate_id_counter; + } + } + addEventListener("message", __setImmediate_cb, true); + emSetImmediate = (func) => { + postMessage(__setImmediate_message_id, "*"); + return __setImmediate_id_counter + __setImmediate_queue.push(func) - 1; + } + emClearImmediate = /**@type{function(number=)}*/((id) => { + var index = id - __setImmediate_id_counter; + // must preserve the order and count of elements in the queue, so replace the pending callback with an empty function + if (index >= 0 && index < __setImmediate_queue.length) __setImmediate_queue[index] = () => {}; + }) + }`, + $emSetImmediate: undefined, + + $emClearImmediate_deps: ['$emSetImmediate'], + $emClearImmediate: undefined, + + emscripten_set_immediate__deps: ['$emSetImmediate', '$callUserCallback'], + emscripten_set_immediate: (cb, userData) => { + {{{ runtimeKeepalivePush(); }}} + return emSetImmediate(() => { + {{{ runtimeKeepalivePop(); }}} + callUserCallback(() => {{{ makeDynCall('vp', 'cb') }}}(userData)); + }); + }, + + emscripten_clear_immediate__deps: ['$emClearImmediate'], + emscripten_clear_immediate: (id) => { + {{{ runtimeKeepalivePop(); }}} + emClearImmediate(id); + }, + + emscripten_set_immediate_loop__deps: ['$emSetImmediate', '$callUserCallback'], + emscripten_set_immediate_loop: (cb, userData) => { + function tick() { + callUserCallback(() => { + if ({{{ makeDynCall('ip', 'cb') }}}(userData)) { + emSetImmediate(tick); + } else { + {{{ runtimeKeepalivePop(); }}} + } + }); + } + {{{ runtimeKeepalivePush(); }}} + emSetImmediate(tick); + }, + + emscripten_set_timeout__deps: ['$safeSetTimeout'], + emscripten_set_timeout: (cb, msecs, userData) => + safeSetTimeout(() => {{{ makeDynCall('vp', 'cb') }}}(userData), msecs), + +#if AUDIO_WORKLET + // Use a wrapper function here since simply aliasing `clearTimeout` would + // cause the module to fail to load in the audio worklet context. + emscripten_clear_timeout: (id) => clearTimeout(id), +#else + emscripten_clear_timeout: 'clearTimeout', +#endif + + emscripten_set_timeout_loop__deps: ['$callUserCallback', 'emscripten_get_now'], + emscripten_set_timeout_loop: (cb, msecs, userData) => { + function tick() { + var t = _emscripten_get_now(); + var n = t + msecs; + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if ({{{ makeDynCall('idp', 'cb') }}}(t, userData)) { + {{{ runtimeKeepalivePush() }}} + // Save a little bit of code space: modern browsers should treat + // negative setTimeout as timeout of 0 + // (https://stackoverflow.com/questions/8430966/is-calling-settimeout-with-a-negative-delay-ok) + var remaining = n - _emscripten_get_now(); +#if ENVIRONMENT_MAY_BE_NODE + // Recent revisions of node, however, give TimeoutNegativeWarning + remaining = Math.max(0, remaining); +#endif + setTimeout(tick, remaining); + } + }); + } + {{{ runtimeKeepalivePush() }}} + return setTimeout(tick, 0); + }, + + emscripten_set_interval__deps: ['$callUserCallback'], + emscripten_set_interval: (cb, msecs, userData) => { + {{{ runtimeKeepalivePush() }}} + return setInterval(() => { + callUserCallback(() => {{{ makeDynCall('vp', 'cb') }}}(userData)); + }, msecs); + }, + + emscripten_clear_interval: (id) => { + {{{ runtimeKeepalivePop() }}} + clearInterval(id); + }, + + emscripten_async_call__deps: ['$safeSetTimeout', '$safeRequestAnimationFrame'], + emscripten_async_call: (func, arg, millis) => { + var wrapper = () => {{{ makeDynCall('vp', 'func') }}}(arg); + + if (millis >= 0 +#if ENVIRONMENT_MAY_BE_NODE + // node does not support requestAnimationFrame + || ENVIRONMENT_IS_NODE +#endif + ) { + safeSetTimeout(wrapper, millis); + } else { + safeRequestAnimationFrame(wrapper); + } + }, + + $registerPostMainLoop: (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f); + }, + + $registerPreMainLoop: (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.preMainLoop.push(f); + }, + + $MainLoop__internal: true, + $MainLoop__deps: ['$setMainLoop', '$callUserCallback', 'emscripten_set_main_loop_timing'], + $MainLoop__postset: ` + Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; + Module['pauseMainLoop'] = MainLoop.pause; + Module['resumeMainLoop'] = MainLoop.resume; + MainLoop.init();`, + $MainLoop: { + running: false, + scheduler: null, + // Each main loop is numbered with a ID in sequence order. Only one main + // loop can run at a time. This variable stores the ordinal number of the + // main loop that is currently allowed to run. All previous main loops + // will quit themselves. This is incremented whenever a new main loop is + // created. + currentlyRunningMainloop: 0, + // The main loop tick function that will be called at each iteration. + func: null, + // The argument that will be passed to the main loop. (of type void*) + arg: 0, + timingMode: 0, + timingValue: 0, + currentFrameNumber: 0, + queue: [], + preMainLoop: [], + postMainLoop: [], + + pause() { + MainLoop.scheduler = null; + // Incrementing this signals the previous main loop that it's now become old, and it must return. + MainLoop.currentlyRunningMainloop++; + }, + + resume() { + MainLoop.currentlyRunningMainloop++; + var timingMode = MainLoop.timingMode; + var timingValue = MainLoop.timingValue; + var func = MainLoop.func; + MainLoop.func = null; + // do not set timing and call scheduler, we will do it on the next lines + setMainLoop(func, 0, false, MainLoop.arg, true); + _emscripten_set_main_loop_timing(timingMode, timingValue); + MainLoop.scheduler(); + }, + + updateStatus() { +#if expectToReceiveOnModule('setStatus') + if (Module['setStatus']) { + var message = Module['statusMessage'] || 'Please wait...'; + var remaining = MainLoop.remainingBlockers ?? 0; + var expected = MainLoop.expectedBlockers ?? 0; + if (remaining) { + if (remaining < expected) { + Module['setStatus'](`{message} ({expected - remaining}/{expected})`); + } else { + Module['setStatus'](message); + } + } else { + Module['setStatus'](''); + } + } +#endif + }, + + init() { +#if expectToReceiveOnModule('preMainLoop') + Module['preMainLoop'] && MainLoop.preMainLoop.push(Module['preMainLoop']); +#endif +#if expectToReceiveOnModule('postMainLoop') + Module['postMainLoop'] && MainLoop.postMainLoop.push(Module['postMainLoop']); +#endif + }, + + runIter(func) { + if (ABORT) return; + for (var pre of MainLoop.preMainLoop) { + if (pre() === false) { + return; // |return false| skips a frame + } + } + callUserCallback(func); + for (var post of MainLoop.postMainLoop) { + post(); + } +#if STACK_OVERFLOW_CHECK + checkStackCookie(); +#endif + }, + + nextRAF: 0, + + fakeRequestAnimationFrame(func) { + // try to keep 60fps between calls to here + var now = Date.now(); + if (MainLoop.nextRAF === 0) { + MainLoop.nextRAF = now + 1000/60; + } else { + while (now + 2 >= MainLoop.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0 + MainLoop.nextRAF += 1000/60; + } + } + var delay = Math.max(MainLoop.nextRAF - now, 0); + setTimeout(func, delay); + }, + + requestAnimationFrame(func) { + if (globalThis.requestAnimationFrame) { + requestAnimationFrame(func); + } else { + MainLoop.fakeRequestAnimationFrame(func); + } + }, + }, + + emscripten_get_main_loop_timing__deps: ['$MainLoop'], + emscripten_get_main_loop_timing: (mode, value) => { + if (mode) {{{ makeSetValue('mode', 0, 'MainLoop.timingMode', 'i32') }}}; + if (value) {{{ makeSetValue('value', 0, 'MainLoop.timingValue', 'i32') }}}; + }, + + emscripten_set_main_loop_timing__deps: ['$MainLoop'], + emscripten_set_main_loop_timing: (mode, value) => { + MainLoop.timingMode = mode; + MainLoop.timingValue = value; + + if (!MainLoop.func) { +#if ASSERTIONS + err('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.'); +#endif + return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. + } + + if (!MainLoop.running) { + {{{ runtimeKeepalivePush() }}} + MainLoop.running = true; + } + if (mode == {{{ cDefs.EM_TIMING_SETTIMEOUT }}}) { + MainLoop.scheduler = function MainLoop_scheduler_setTimeout() { + var timeUntilNextTick = Math.max(0, MainLoop.tickStartTime + value - _emscripten_get_now())|0; + setTimeout(MainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop + }; + } else if (mode == {{{ cDefs.EM_TIMING_RAF }}}) { + MainLoop.scheduler = function MainLoop_scheduler_rAF() { + MainLoop.requestAnimationFrame(MainLoop.runner); + }; + } else { +#if ASSERTIONS + assert(mode == {{{ cDefs.EM_TIMING_SETIMMEDIATE}}}); +#endif + if (!MainLoop.setImmediate) { + if (globalThis.setImmediate) { + MainLoop.setImmediate = setImmediate; + } else { + // Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed) + var setImmediates = []; + var emscriptenMainLoopMessageId = 'setimmediate'; + /** @param {Event} event */ + var MainLoop_setImmediate_messageHandler = (event) => { + // When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events, + // so check for both cases. + if (event.data === emscriptenMainLoopMessageId || event.data.target === emscriptenMainLoopMessageId) { + event.stopPropagation(); + setImmediates.shift()(); + } + }; + addEventListener("message", MainLoop_setImmediate_messageHandler, true); + MainLoop.setImmediate = /** @type{function(function(): ?, ...?): number} */((func) => { + setImmediates.push(func); + if (ENVIRONMENT_IS_WORKER) { + Module['setImmediates'] ??= []; + Module['setImmediates'].push(func); + postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js + } else postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself. + }); + } + } + MainLoop.scheduler = function MainLoop_scheduler_setImmediate() { + MainLoop.setImmediate(MainLoop.runner); + }; + } + return 0; + }, + + emscripten_set_main_loop__deps: ['$setMainLoop'], + emscripten_set_main_loop: (func, fps, simulateInfiniteLoop) => { + var iterFunc = {{{ makeDynCall('v', 'func') }}}; + setMainLoop(iterFunc, fps, simulateInfiniteLoop); + }, + + $setMainLoop__internal: true, + $setMainLoop__deps: [ + '$MainLoop', + 'emscripten_set_main_loop_timing', 'emscripten_get_now', +#if !MINIMAL_RUNTIME + '$maybeExit', +#endif + ], + $setMainLoop__docs: ` + /** + * @param {number=} arg + * @param {boolean=} noSetTiming + */`, + $setMainLoop: (iterFunc, fps, simulateInfiniteLoop, arg, noSetTiming) => { +#if ASSERTIONS + assert(!MainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.'); +#endif + MainLoop.func = iterFunc; + MainLoop.arg = arg; + + var thisMainLoopId = MainLoop.currentlyRunningMainloop; + function checkIsRunning() { + if (thisMainLoopId < MainLoop.currentlyRunningMainloop) { +#if RUNTIME_DEBUG + dbg('main loop exiting'); +#endif + {{{ runtimeKeepalivePop() }}} +#if !MINIMAL_RUNTIME + maybeExit(); +#endif + return false; + } + return true; + } + + // We create the loop runner here but it is not actually running until + // _emscripten_set_main_loop_timing is called (which might happen at a + // later time). This member signifies that the current runner has not + // yet been started so that we can call runtimeKeepalivePush when it + // gets its timing set for the first time. + MainLoop.running = false; + MainLoop.runner = function MainLoop_runner() { + if (ABORT) return; + if (MainLoop.queue.length > 0) { + var start = Date.now(); + var blocker = MainLoop.queue.shift(); + blocker.func(blocker.arg); + if (MainLoop.remainingBlockers) { + var remaining = MainLoop.remainingBlockers; + var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining); + if (blocker.counted) { + MainLoop.remainingBlockers = next; + } else { + // not counted, but move the progress along a tiny bit + next = next + 0.5; // do not steal all the next one's progress + MainLoop.remainingBlockers = (8*remaining + next)/9; + } + } +#if RUNTIME_DEBUG + dbg(`main loop blocker "${blocker.name}" took '${Date.now() - start} ms`); //, left: ' + MainLoop.remainingBlockers); +#endif + MainLoop.updateStatus(); + + // catches pause/resume main loop from blocker execution + if (!checkIsRunning()) return; + + setTimeout(MainLoop.runner, 0); + return; + } + + // catch pauses from non-main loop sources + if (!checkIsRunning()) return; + + // Implement very basic swap interval control + MainLoop.currentFrameNumber = MainLoop.currentFrameNumber + 1 | 0; + if (MainLoop.timingMode == {{{ cDefs.EM_TIMING_RAF }}} && MainLoop.timingValue > 1 && MainLoop.currentFrameNumber % MainLoop.timingValue != 0) { + // Not the scheduled time to render this frame - skip. + MainLoop.scheduler(); + return; + } else if (MainLoop.timingMode == {{{ cDefs.EM_TIMING_SETTIMEOUT }}}) { + MainLoop.tickStartTime = _emscripten_get_now(); +#if ASSERTIONS + if (Module['ctx']) { + warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); + } +#endif + } + + MainLoop.runIter(iterFunc); + + // catch pauses from the main loop itself + if (!checkIsRunning()) return; + + MainLoop.scheduler(); + } + + if (!noSetTiming) { + if (fps > 0) { + _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_SETTIMEOUT }}}, 1000.0 / fps); + } else { + // Do rAF by rendering each frame (no decimating) + _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_RAF }}}, 1); + } + + MainLoop.scheduler(); + } + + if (simulateInfiniteLoop) { + throw 'unwind'; + } + }, + + emscripten_set_main_loop_arg__deps: ['$setMainLoop'], + emscripten_set_main_loop_arg: (func, arg, fps, simulateInfiniteLoop) => { + var iterFunc = () => {{{ makeDynCall('vp', 'func') }}}(arg); + setMainLoop(iterFunc, fps, simulateInfiniteLoop, arg); + }, + + emscripten_cancel_main_loop__deps: ['$MainLoop'], + emscripten_cancel_main_loop: () => { + MainLoop.pause(); + MainLoop.func = null; + }, + + emscripten_pause_main_loop__deps: ['$MainLoop'], + emscripten_pause_main_loop: () => MainLoop.pause(), + + emscripten_resume_main_loop__deps: ['$MainLoop'], + emscripten_resume_main_loop: () => MainLoop.resume(), + + _emscripten_push_main_loop_blocker__deps: ['$MainLoop'], + _emscripten_push_main_loop_blocker: (func, arg, name) => { + MainLoop.queue.push({ func: () => { + {{{ makeDynCall('vp', 'func') }}}(arg); + }, name: UTF8ToString(name), counted: true }); + MainLoop.updateStatus(); + }, + + _emscripten_push_uncounted_main_loop_blocker__deps: ['$MainLoop'], + _emscripten_push_uncounted_main_loop_blocker: (func, arg, name) => { + MainLoop.queue.push({ func: () => { + {{{ makeDynCall('vp', 'func') }}}(arg); + }, name: UTF8ToString(name), counted: false }); + MainLoop.updateStatus(); + }, + + emscripten_set_main_loop_expected_blockers__deps: ['$MainLoop'], + emscripten_set_main_loop_expected_blockers: (num) => { + MainLoop.expectedBlockers = num; + MainLoop.remainingBlockers = num; + MainLoop.updateStatus(); + }, + +}; + +addToLibrary(LibraryJSEventLoop); diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js new file mode 100644 index 0000000000000..b04ebf7d3ad4e --- /dev/null +++ b/src/lib/libexceptions.js @@ -0,0 +1,456 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryExceptions = { +#if !WASM_EXCEPTIONS + $uncaughtExceptionCount: '0', +#if !DISABLE_EXCEPTION_CATCHING + $exceptionLast: null, +#endif + $exceptionCaught: ' []', + + // This class is the exception metadata which is prepended to each thrown object (in WASM memory). + // It is allocated in one block along with a thrown object in __cxa_allocate_exception and freed + // in ___cxa_free_exception. It roughly corresponds to __cxa_exception structure in libcxxabi. The + // class itself is just a native pointer wrapper, and contains all the necessary accessors for the + // fields in the native structure. + // TODO: Unfortunately this approach still cannot be considered thread-safe because single + // exception object can be simultaneously thrown in several threads and its state (except + // reference counter) is not protected from that. Also protection is not enough, separate state + // should be allocated. libcxxabi has concept of dependent exception which is used for that + // purpose, it references the primary exception. + $ExceptionInfo: class { + // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. + constructor(excPtr) { + this.excPtr = excPtr; + this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}}; + } + + set_type(type) { + {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, 'type', '*') }}}; + } + + get_type() { + return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionType, '*') }}}; + } + + set_destructor(destructor) { + {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, 'destructor', '*') }}}; + } + + get_destructor() { + return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, '*') }}}; + } + + set_caught(caught) { + caught = caught ? 1 : 0; + {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'caught', 'i8') }}}; + } + + get_caught() { + return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'i8') }}} != 0; + } + + set_rethrown(rethrown) { + rethrown = rethrown ? 1 : 0; + {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'rethrown', 'i8') }}}; + } + + get_rethrown() { + return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.rethrown, 'i8') }}} != 0; + } + + // Initialize native structure fields. Should be called once after allocated. + init(type, destructor) { +#if EXCEPTION_DEBUG + dbg('ExceptionInfo init: ' + [type, destructor]); +#endif + this.set_adjusted_ptr(0); + this.set_type(type); + this.set_destructor(destructor); + } + + set_adjusted_ptr(adjustedPtr) { + {{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, 'adjustedPtr', '*') }}}; + } + + get_adjusted_ptr() { + return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, '*') }}}; + } + }, + + // Here, we throw an exception after recording a couple of values that we need to remember + // We also remember that it was the last exception thrown as we need to know that later. + __cxa_throw__deps: ['$ExceptionInfo', '$uncaughtExceptionCount', +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', + '__cxa_increment_exception_refcount', +#endif +#if EXCEPTION_STACK_TRACES + // When EXCEPTION_STACK_TRACES is enabled, the 'CppException' constructor + // calls getExceptionMessage. We can't track the dependency there, so we + // track it here. + '$getExceptionMessage', + // These functions can be necessary to prevent memory leaks from the JS + // side. Even though they are not used it here directly, we export them when + // 'throw' is used here. + '$decrementExceptionRefcount', '$incrementExceptionRefcount', +#endif + ], + __cxa_throw: (ptr, type, destructor) => { +#if EXCEPTION_DEBUG + dbg('__cxa_throw: ' + [ptrToString(ptr), type, ptrToString(destructor)]); +#endif + var info = new ExceptionInfo(ptr); + // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. + info.init(type, destructor); +#if !DISABLE_EXCEPTION_CATCHING + ___cxa_increment_exception_refcount(ptr); + exceptionLast = new CppException(ptr); +#endif + uncaughtExceptionCount++; + {{{ makeThrow() }}} + }, + + // This exception will be caught twice, but while begin_catch runs twice, + // we early-exit from end_catch when the exception has been rethrown, so + // pop that here from the caught exceptions. + __cxa_rethrow__deps: ['$exceptionCaught', '$uncaughtExceptionCount', +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', + '__cxa_increment_exception_refcount', +#endif + ], + __cxa_rethrow: () => { + if (!exceptionCaught.length) { + abort('no exception to throw'); + } + var info = exceptionCaught.at(-1); + var ptr = info.excPtr; + info.set_rethrown(true); + info.set_caught(false); + uncaughtExceptionCount++; +#if !DISABLE_EXCEPTION_CATCHING + ___cxa_increment_exception_refcount(ptr); +#if EXCEPTION_DEBUG + dbg('__cxa_rethrow: ' + + [ptrToString(ptr), exceptionLast, 'stack', exceptionCaught]); +#endif + exceptionLast = new CppException(ptr); +#endif + {{{ makeThrow() }}} + }, + + llvm_eh_typeid_for: (type) => type, + + __cxa_begin_catch__deps: ['$exceptionCaught', '__cxa_get_exception_ptr', + '$uncaughtExceptionCount'], + __cxa_begin_catch: (ptr) => { + var info = new ExceptionInfo(ptr); + if (!info.get_caught()) { + info.set_caught(true); + uncaughtExceptionCount--; + } + info.set_rethrown(false); + exceptionCaught.push(info); +#if EXCEPTION_DEBUG + dbg('__cxa_begin_catch ' + [ptrToString(ptr), 'stack', exceptionCaught]); +#endif + return ___cxa_get_exception_ptr(ptr); + }, + + // We're done with a catch. Now, we can run the destructor if there is one + // and free the exception. Note that if the dynCall on the destructor fails + // due to calling apply on undefined, that means that the destructor is + // an invalid index into the FUNCTION_TABLE, so something has gone wrong. + __cxa_end_catch__deps: ['$exceptionCaught', '__cxa_decrement_exception_refcount', 'setThrew', +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', +#endif + ], + __cxa_end_catch: () => { + // Clear state flag. + _setThrew(0, 0); +#if ASSERTIONS + assert(exceptionCaught.length > 0); +#endif + // Call destructor if one is registered then clear it. + var info = exceptionCaught.pop(); + +#if EXCEPTION_DEBUG + dbg('__cxa_end_catch popped ' + [info, 'stack', exceptionCaught]); +#endif + ___cxa_decrement_exception_refcount(info.excPtr); +#if !DISABLE_EXCEPTION_CATCHING + exceptionLast = null; // XXX in decRef? +#endif + }, + + __cxa_uncaught_exceptions__deps: ['$uncaughtExceptionCount'], + __cxa_uncaught_exceptions: () => uncaughtExceptionCount, + + __cxa_call_unexpected: (exception) => abort('Unexpected exception thrown, this is not properly supported - aborting'), + + __cxa_current_primary_exception__deps: ['$exceptionCaught', '__cxa_increment_exception_refcount'], + __cxa_current_primary_exception: () => { + if (!exceptionCaught.length) { + return 0; + } + var info = exceptionCaught[exceptionCaught.length - 1]; + ___cxa_increment_exception_refcount(info.excPtr); + return info.excPtr; + }, + + __cxa_current_exception_type() { + if (!exceptionCaught.length) { + return 0; + } + var info = exceptionCaught[exceptionCaught.length - 1]; + return info.get_type(); + }, + + __cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', '$uncaughtExceptionCount', +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', + '__cxa_increment_exception_refcount', +#endif + ], + __cxa_rethrow_primary_exception: (ptr) => { + if (!ptr) return; +#if EXCEPTION_DEBUG + dbg('__cxa_rethrow_primary_exception: ' + ptrToString(ptr)); +#endif + var info = new ExceptionInfo(ptr); + info.set_rethrown(true); + info.set_caught(false); + uncaughtExceptionCount++; +#if !DISABLE_EXCEPTION_CATCHING + ___cxa_increment_exception_refcount(ptr); + exceptionLast = new CppException(ptr); +#endif + {{{ makeThrow('exceptionLast') }}} + }, + + // Finds a suitable catch clause for when an exception is thrown. + // In normal compilers, this functionality is handled by the C++ + // 'personality' routine. This is passed a fairly complex structure + // relating to the context of the exception and makes judgements + // about how to handle it. Some of it is about matching a suitable + // catch clause, and some of it is about unwinding. We already handle + // unwinding using 'if' blocks around each function, so the remaining + // functionality boils down to picking a suitable 'catch' block. + // We'll do that here, instead, to keep things simpler. +#if !DISABLE_EXCEPTION_CATCHING + $findMatchingCatch__deps: ['$exceptionLast', '$ExceptionInfo', '__cxa_can_catch', '$setTempRet0'], +#endif + $findMatchingCatch: (args) => { +#if DISABLE_EXCEPTION_CATCHING + setTempRet0(0); + return 0; +#else + var thrown = exceptionLast?.excPtr; + if (!thrown) { + // just pass through the null ptr + setTempRet0(0); + return 0; + } + var info = new ExceptionInfo(thrown); + info.set_adjusted_ptr(thrown); + var thrownType = info.get_type(); + if (!thrownType) { + // just pass through the thrown ptr + setTempRet0(0); + return thrown; + } + + // can_catch receives a **, add indirection +#if EXCEPTION_DEBUG + dbg("findMatchingCatch on " + ptrToString(thrown)); +#endif + // The different catch blocks are denoted by different types. + // Due to inheritance, those types may not precisely match the + // type of the thrown object. Find one which matches, and + // return the type of the catch block which should be called. + for (var caughtType of args) { + if (caughtType === 0 || caughtType === thrownType) { + // Catch all clause matched or exactly the same type is caught + break; + } + var adjusted_ptr_addr = info.ptr + {{{ C_STRUCTS.__cxa_exception.adjustedPtr }}}; + if (___cxa_can_catch(caughtType, thrownType, adjusted_ptr_addr)) { +#if EXCEPTION_DEBUG + dbg(" findMatchingCatch found " + [ptrToString(info.get_adjusted_ptr()), caughtType]); +#endif + setTempRet0(caughtType); + return thrown; + } + } + setTempRet0(thrownType); + return thrown; +#endif + }, + +#if !DISABLE_EXCEPTION_CATCHING + __resumeException__deps: ['$exceptionLast'], +#endif + __resumeException: (ptr) => { +#if !DISABLE_EXCEPTION_CATCHING +#if EXCEPTION_DEBUG + dbg("__resumeException " + [ptrToString(ptr), exceptionLast]); +#endif + if (!exceptionLast) { + exceptionLast = new CppException(ptr); + } +#endif + {{{ makeThrow() }}} + }, + +#endif +#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_CATCHING + $getExceptionMessageCommon__deps: ['__get_exception_message', 'free', '$stackSave', '$stackRestore', '$stackAlloc'], + $getExceptionMessageCommon: (ptr) => { + var sp = stackSave(); + var type_addr_addr = stackAlloc({{{ POINTER_SIZE }}}); + var message_addr_addr = stackAlloc({{{ POINTER_SIZE }}}); + ___get_exception_message(ptr, type_addr_addr, message_addr_addr); + var type_addr = {{{ makeGetValue('type_addr_addr', 0, '*') }}}; + var message_addr = {{{ makeGetValue('message_addr_addr', 0, '*') }}}; + var type = UTF8ToString(type_addr); + _free(type_addr); + var message; + if (message_addr) { + message = UTF8ToString(message_addr); + _free(message_addr); + } + stackRestore(sp); + return [type, message]; + }, +#endif +#if WASM_EXCEPTIONS + $getCppExceptionTag__deps: ['__cpp_exception'], + // In static linking, tags are defined within the wasm module and are + // exported, whereas in dynamic linking, tags are defined in libcore.js in + // JS code and wasm modules import them. + $getCppExceptionTag: () => ___cpp_exception, + +#if EXCEPTION_STACK_TRACES + // Throw a WebAssembly.Exception object with the C++ tag with a stack trace + // embedded. WebAssembly.Exception is a JS object representing a Wasm + // exception, provided by Wasm JS API: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Exception + // In release builds, this function is not needed and the native + // _Unwind_RaiseException in libunwind is used instead. + __throw_exception_with_stack_trace__deps: [ + '$getCppExceptionTag', '$getExceptionMessage', + // These functions can be necessary to prevent memory leaks from the JS + // side. Even though they are not used it here directly, we export them + // when 'throw' is used here. + '$decrementExceptionRefcount', '$incrementExceptionRefcount'], + __throw_exception_with_stack_trace: (ex) => { + var e = new WebAssembly.Exception(getCppExceptionTag(), [ex], {traceStack: true}); + e.message = getExceptionMessage(e); + throw e; + }, +#endif + + // Given an WebAssembly.Exception object, returns the actual user-thrown + // C++ object address in the Wasm memory. + $getCppExceptionThrownObjectFromWebAssemblyException__deps: ['$getCppExceptionTag', '__thrown_object_from_unwind_exception'], + $getCppExceptionThrownObjectFromWebAssemblyException: (ex) => { + // In Wasm EH, the value extracted from WebAssembly.Exception is a pointer + // to the unwind header. Convert it to the actual thrown value. + var unwind_header = ex.getArg(getCppExceptionTag(), 0); + return ___thrown_object_from_unwind_exception(unwind_header); + }, + + $incrementUncaughtExceptionCount__deps: ['__increment_uncaught_exception'], + $incrementUncaughtExceptionCount: '__increment_uncaught_exception', + + $decrementUncaughtExceptionCount__deps: ['__decrement_uncaught_exception'], + $decrementUncaughtExceptionCount: '__decrement_uncaught_exception', + + $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], + $incrementExceptionRefcount: (ex) => { + var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); + ___cxa_increment_exception_refcount(ptr); + }, + + $decrementExceptionRefcount__deps: ['__cxa_decrement_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], + $decrementExceptionRefcount: (ex) => { + var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); + ___cxa_decrement_exception_refcount(ptr); + }, + + $getExceptionMessage__deps: ['$getCppExceptionThrownObjectFromWebAssemblyException', '$getExceptionMessageCommon'], + $getExceptionMessage: (ex) => { + var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); + return getExceptionMessageCommon(ptr); + }, + +#else +#if !DISABLE_EXCEPTION_THROWING + $incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $incrementUncaughtExceptionCount: () => { + uncaughtExceptionCount++; + }, + + $decrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $decrementUncaughtExceptionCount: () => { + uncaughtExceptionCount--; + }, +#endif + +#if !DISABLE_EXCEPTION_CATCHING + $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount'], + $incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exn.excPtr), + + $decrementExceptionRefcount__deps: ['__cxa_decrement_exception_refcount'], + $decrementExceptionRefcount: (exn) => ___cxa_decrement_exception_refcount(exn.excPtr), + + $getExceptionMessage__deps: ['$getExceptionMessageCommon'], + $getExceptionMessage: (exn) => getExceptionMessageCommon(exn.excPtr), + +#endif +#endif +}; + +#if !WASM_EXCEPTIONS +// In LLVM, exceptions generate a set of functions of form +// __cxa_find_matching_catch_2(), __cxa_find_matching_catch_3(), etc. where the +// number specifies the number of arguments. In Emscripten, route all these to +// a single function '__cxa_find_matching_catch' that variadically processes all +// of these functions using JS 'arguments' object. +addCxaCatch = (n) => { + const args = []; + // Confusingly, the actual number of argument is n - 2. According to the llvm + // code in WebAssemblyLowerEmscriptenEHSjLj.cpp: + // This is because a landingpad instruction contains two more arguments, a + // personality function and a cleanup bit, and __cxa_find_matching_catch_N + // functions are named after the number of arguments in the original landingpad + // instruction. + let sig = 'p'; + for (let i = 0; i < n - 2; i++) { + args.push(`arg${i}`); + sig += 'p'; + } + const argString = args.join(','); + LibraryManager.library[`__cxa_find_matching_catch_${n}__sig`] = sig; + LibraryManager.library[`__cxa_find_matching_catch_${n}__deps`] = ['$findMatchingCatch']; + LibraryManager.library[`__cxa_find_matching_catch_${n}`] = eval(`(${args}) => findMatchingCatch([${argString}])`); +}; + +// Add the first 2-5 catch handlers preemptively. Others get added on demand in +// jsifier. This is done here primarily so that these symbols end up with the +// correct deps in the stub library that we pass to wasm-ld. +// Note: __cxa_find_matching_catch_N function uses N = NumClauses + 2 so +// __cxa_find_matching_catch_2 is the first such function with zero clauses. +// See WebAssemblyLowerEmscriptenEHSjLj.cpp. +for (let i = 2; i < 5; i++) { + addCxaCatch(i) +} +#endif + +addToLibrary(LibraryExceptions); diff --git a/src/lib/libexceptions_stub.js b/src/lib/libexceptions_stub.js new file mode 100644 index 0000000000000..8feb95f1c1860 --- /dev/null +++ b/src/lib/libexceptions_stub.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2019 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryExceptions = {}; + +[ + '__cxa_throw', + '__cxa_rethrow', + 'llvm_eh_typeid_for', + '__cxa_begin_catch', + '__cxa_end_catch', + '__cxa_get_exception_ptr', + '_ZSt18uncaught_exceptionv', + '__cxa_call_unexpected', + '__cxa_current_primary_exception', + '__cxa_rethrow_primary_exception', + '__cxa_find_matching_catch', + '__resumeException', +].forEach((name) => { + LibraryExceptions[name] = () => abort(); +#if !INCLUDE_FULL_LIBRARY + // This method of link-time error generation is not compatible with INCLUDE_FULL_LIBRARY + LibraryExceptions[name + '__deps'] = [() => { + error(`DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but such support is required by symbol '${name}'. Either do not set DISABLE_EXCEPTION_THROWING (if you do want exception throwing) or compile all source files with -fno-exceptions (so that no exceptions support code is required); also make sure DISABLE_EXCEPTION_CATCHING is set to the right value - if you want exceptions, it should be off, and vice versa.`); + }]; +#endif +}); + +addToLibrary(LibraryExceptions); diff --git a/src/lib/libexports.js b/src/lib/libexports.js new file mode 100644 index 0000000000000..0ccf93e436249 --- /dev/null +++ b/src/lib/libexports.js @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + emscripten_get_exported_function__deps: ['$addFunction', '$UTF8ToString'], + emscripten_get_exported_function: (name) => { + name = UTF8ToString(name); + // Wasm backend does not use C name mangling on exports, + // so adjust for that manually. + if (name[0] == '_') name = name.slice(1); + var exportedFunc = wasmExports[name]; + if (exportedFunc) { + // Note: addFunction automatically caches the created function pointer. + return addFunction(exportedFunc); + } +#if ASSERTIONS + err(`No exported function found by name "{exportedFunc}"`); +#endif + // implicit return 0; + } +}); diff --git a/src/lib/libfetch.js b/src/lib/libfetch.js new file mode 100644 index 0000000000000..03dba8681e6be --- /dev/null +++ b/src/lib/libfetch.js @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2016 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +#include Fetch.js + +var LibraryFetch = { + $Fetch__postset: 'Fetch.init();', + $Fetch__deps: ['$HandleAllocator'], + $Fetch: Fetch, + _emscripten_fetch_get_response_headers_length__deps: ['$lengthBytesUTF8'], + _emscripten_fetch_get_response_headers_length: fetchGetResponseHeadersLength, + _emscripten_fetch_get_response_headers__deps: ['$lengthBytesUTF8', '$stringToUTF8'], + _emscripten_fetch_get_response_headers: fetchGetResponseHeaders, + emscripten_fetch_free: fetchFree, + +#if FETCH_SUPPORT_INDEXEDDB + $fetchDeleteCachedData: fetchDeleteCachedData, + $fetchLoadCachedData: fetchLoadCachedData, + $fetchCacheData: fetchCacheData, +#endif + $fetchXHR: fetchXHR, +#if FETCH_STREAMING + $FetchXHR: FetchXHR, +#endif + + emscripten_start_fetch: startFetch, + emscripten_start_fetch__deps: [ + 'malloc', + 'realloc', + '$Fetch', + '$fetchXHR', + '$callUserCallback', + '$writeI53ToI64', + '$stringToUTF8', + '$stringToNewUTF8', +#if FETCH_SUPPORT_INDEXEDDB + '$fetchCacheData', + '$fetchLoadCachedData', + '$fetchDeleteCachedData', +#endif +#if FETCH_STREAMING + '$FetchXHR', +#endif + ], +}; + +addToLibrary(LibraryFetch); diff --git a/src/lib/libfetchfs.js b/src/lib/libfetchfs.js new file mode 100644 index 0000000000000..d0b704034d5f6 --- /dev/null +++ b/src/lib/libfetchfs.js @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2023 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $FETCHFS__deps: ['$stringToUTF8OnStack', 'wasmfs_create_fetch_backend'], + $FETCHFS: { + createBackend(opts) { + return withStackSave( + () => _wasmfs_create_fetch_backend( + stringToUTF8OnStack(opts.base_url ?? ""), + opts.chunkSize | 0 + ) + ); + }, + }, +}); + +if (!WASMFS) { + error("using -lfetchfs.js requires using WasmFS (-sWASMFS)"); +} diff --git a/src/lib/libfs.js b/src/lib/libfs.js new file mode 100644 index 0000000000000..d148c84f5a2d2 --- /dev/null +++ b/src/lib/libfs.js @@ -0,0 +1,1900 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryFS = { + $FS__deps: ['$randomFill', '$PATH', '$PATH_FS', '$TTY', '$MEMFS', + '$FS_modeStringToFlags', + '$FS_fileDataToTypedArray', + '$FS_getMode', + '$intArrayFromString', +#if LibraryManager.has('libidbfs.js') + '$IDBFS', +#endif +#if LibraryManager.has('libnodefs.js') + '$NODEFS', +#endif +#if LibraryManager.has('libworkerfs.js') + '$WORKERFS', +#endif +#if LibraryManager.has('libnoderawfs.js') + '$NODERAWFS', +#endif +#if LibraryManager.has('libproxyfs.js') + '$PROXYFS', +#endif +#if ASSERTIONS + '$strError', '$ERRNO_CODES', +#endif +#if !MINIMAL_RUNTIME + '$FS_createPreloadedFile', +#endif + ], + $FS__postset: () => { + // TODO: do we need noFSInit? + addAtInit(`if (!Module['noFSInit'] && !FS.initialized) FS.init();`); + addAtPostCtor('FS.ignorePermissions = false;'); + addAtExit('FS.quit();'); + return ` +#if !MINIMAL_RUNTIME +FS.createPreloadedFile = FS_createPreloadedFile; +FS.preloadFile = FS_preloadFile; +#endif +FS.staticInit();`; + }, + $FS: { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + // Whether we are currently ignoring permissions. Useful when preparing the + // filesystem and creating files inside read-only folders. + // This is set to false during `preInit`, allowing you to modify the + // filesystem freely up until that point (e.g. during `preRun`). + ignorePermissions: true, +#if FS_DEBUG + trackingDelegate: {}, +#endif + filesystems: null, + syncFSRequests: 0, // we warn if there are multiple in flight at once +#if expectToReceiveOnModule('logReadFiles') + readFiles: {}, +#endif +#if ASSERTIONS + ErrnoError: class extends Error { +#else + ErrnoError: class { +#endif + name = 'ErrnoError'; + // We set the `name` property to be able to identify `FS.ErrnoError` + // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. + // - when using PROXYFS, an error can come from an underlying FS + // as different FS objects have their own FS.ErrnoError each, + // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. + // we'll use the reliable test `err.name == "ErrnoError"` instead + constructor(errno) { +#if ASSERTIONS + super(runtimeInitialized ? strError(errno) : ''); +#endif + this.errno = errno; +#if ASSERTIONS + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } +#endif + } + }, + + FSStream: class { + shared = {}; +#if USE_CLOSURE_COMPILER + // Closure compiler requires us to declare all properties ahead of time + node = null; +#endif + get object() { + return this.node; + } + set object(val) { + this.node = val; + } + get isRead() { + return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_WRONLY }}}; + } + get isWrite() { + return (this.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDONLY }}}; + } + get isAppend() { + return (this.flags & {{{ cDefs.O_APPEND }}}); + } + get flags() { + return this.shared.flags; + } + set flags(val) { + this.shared.flags = val; + } + get position() { + return this.shared.position; + } + set position(val) { + this.shared.position = val; + } + }, + FSNode: class { + node_ops = {}; + stream_ops = {}; + readMode = {{{ cDefs.S_IRUGO }}} | {{{ cDefs.S_IXUGO }}}; + writeMode = {{{ cDefs.S_IWUGO }}}; + mounted = null; + constructor(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.rdev = rdev; + this.atime = this.mtime = this.ctime = Date.now(); + } + get read() { + return (this.mode & this.readMode) === this.readMode; + } + set read(val) { + val ? this.mode |= this.readMode : this.mode &= ~this.readMode; + } + get write() { + return (this.mode & this.writeMode) === this.writeMode; + } + set write(val) { + val ? this.mode |= this.writeMode : this.mode &= ~this.writeMode; + } + get isFolder() { + return FS.isDir(this.mode); + } + get isDevice() { + return FS.isChrdev(this.mode); + } + }, + + // + // paths + // + lookupPath(path, opts = {}) { + if (!path) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + opts.follow_mount ??= true + + if (!PATH.isAbs(path)) { + path = FS.cwd() + '/' + path; + } + + // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { + // split the absolute path + var parts = path.split('/').filter((p) => !!p); + + // start at the root + var current = FS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = (i === parts.length-1); + if (islast && opts.parent) { + // stop resolving + break; + } + + if (parts[i] === '.') { + continue; + } + + if (parts[i] === '..') { + current_path = PATH.dirname(current_path); + if (FS.isRoot(current)) { + path = current_path + '/' + parts.slice(i + 1).join('/'); + // We're making progress here, don't let many consecutive ..'s + // lead to ELOOP + nlinks--; + continue linkloop; + } else { + current = current.parent; + } + continue; + } + + current_path = PATH.join2(current_path, parts[i]); + try { + current = FS.lookupNode(current, parts[i]); + } catch (e) { + // if noent_okay is true, suppress a ENOENT in the last component + // and return an object with an undefined node. This is needed for + // resolving symlinks in the path when creating a file. + if ((e?.errno === {{{ cDefs.ENOENT }}}) && islast && opts.noent_okay) { + return { path: current_path }; + } + throw e; + } + + // jump to the mount's root node if this is a mountpoint + if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) { + current = current.mounted.root; + } + + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (FS.isLink(current.mode) && (!islast || opts.follow)) { + if (!current.node_ops.readlink) { + throw new FS.ErrnoError({{{ cDefs.ENOSYS }}}); + } + var link = current.node_ops.readlink(current); + if (!PATH.isAbs(link)) { + link = PATH.dirname(current_path) + '/' + link; + } + path = link + '/' + parts.slice(i + 1).join('/'); + continue linkloop; + } + } + return { path: current_path, node: current }; + } + throw new FS.ErrnoError({{{ cDefs.ELOOP }}}); + }, + getPath(node) { + var path; + while (true) { + if (FS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length-1] !== '/' ? `${mount}/${path}` : mount + path; + } + path = path ? `${node.name}/${path}` : node.name; + node = node.parent; + } + }, + + // + // nodes + // + hashName(parentid, name) { + var hash = 0; + +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode(parent, name) { + var errCode = FS.mayLookup(parent); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + var hash = FS.hashName(parent.id, name); +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; +#if CASE_INSENSITIVE_FS + nodeName = nodeName.toLowerCase(); +#endif + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return FS.lookup(parent, name); + }, + createNode(parent, name, mode, rdev) { +#if ASSERTIONS + assert(typeof parent == 'object') +#endif + var node = new FS.FSNode(parent, name, mode, rdev); + + FS.hashAddNode(node); + + return node; + }, + destroyNode(node) { + FS.hashRemoveNode(node); + }, + isRoot(node) { + return node === node.parent; + }, + isMountpoint(node) { + return !!node.mounted; + }, + isFile(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFREG }}}; + }, + isDir(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFDIR }}}; + }, + isLink(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFLNK }}}; + }, + isChrdev(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFCHR }}}; + }, + isBlkdev(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFBLK }}}; + }, + isFIFO(mode) { + return (mode & {{{ cDefs.S_IFMT }}}) === {{{ cDefs.S_IFIFO }}}; + }, + isSocket(mode) { + return (mode & {{{ cDefs.S_IFSOCK }}}) === {{{ cDefs.S_IFSOCK }}}; + }, + + // + // permissions + // + // convert O_* bitmask to a string for nodePermissions + flagsToPermissionString(flag) { + var perms = ['r', 'w', 'rw'][flag & 3]; + if ((flag & {{{ cDefs.O_TRUNC }}})) { + perms += 'w'; + } + return perms; + }, + nodePermissions(node, perms) { + if (FS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.includes('r') && !(node.mode & {{{ cDefs.S_IRUGO }}})) { + return {{{ cDefs.EACCES }}}; + } + if (perms.includes('w') && !(node.mode & {{{ cDefs.S_IWUGO }}})) { + return {{{ cDefs.EACCES }}}; + } + if (perms.includes('x') && !(node.mode & {{{ cDefs.S_IXUGO }}})) { + return {{{ cDefs.EACCES }}}; + } + return 0; + }, + mayLookup(dir) { + if (!FS.isDir(dir.mode)) return {{{ cDefs.ENOTDIR }}}; + var errCode = FS.nodePermissions(dir, 'x'); + if (errCode) return errCode; + if (!dir.node_ops.lookup) return {{{ cDefs.EACCES }}}; + return 0; + }, + mayCreate(dir, name) { + if (!FS.isDir(dir.mode)) { + return {{{ cDefs.ENOTDIR }}}; + } + try { + var node = FS.lookupNode(dir, name); + return {{{ cDefs.EEXIST }}}; + } catch (e) { + } + return FS.nodePermissions(dir, 'wx'); + }, + mayDelete(dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var errCode = FS.nodePermissions(dir, 'wx'); + if (errCode) { + return errCode; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return {{{ cDefs.ENOTDIR }}}; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { + return {{{ cDefs.EBUSY }}}; + } + } else if (FS.isDir(node.mode)) { + return {{{ cDefs.EISDIR }}}; + } + return 0; + }, + mayOpen(node, flags) { + if (!node) { + return {{{ cDefs.ENOENT }}}; + } + if (FS.isLink(node.mode)) { + return {{{ cDefs.ELOOP }}}; + } + var mode = FS.flagsToPermissionString(flags); + if (FS.isDir(node.mode)) { + // opening for write + // TODO: check for O_SEARCH? (== search for dir only) + if (mode !== 'r' || (flags & ({{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_CREAT }}}))) { + return {{{ cDefs.EISDIR }}}; + } + } + return FS.nodePermissions(node, mode); + }, + checkOpExists(op, err) { + if (!op) { + throw new FS.ErrnoError(err); + } + return op; + }, + + // + // streams + // + MAX_OPEN_FDS: 4096, + nextfd() { + for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) { + if (!FS.streams[fd]) { + return fd; + } + } + throw new FS.ErrnoError({{{ cDefs.EMFILE }}}); + }, + getStreamChecked(fd) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + return stream; + }, + getStream: (fd) => FS.streams[fd], + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. + createStream(stream, fd = -1) { +#if ASSERTIONS + assert(fd >= -1); +#endif + + // clone it, so we can return an instance of FSStream + stream = Object.assign(new FS.FSStream(), stream); + if (fd == -1) { + fd = FS.nextfd(); + } + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream(fd) { + FS.streams[fd] = null; + }, + dupStream(origStream, fd = -1) { + var stream = FS.createStream(origStream, fd); + stream.stream_ops?.dup?.(stream); + return stream; + }, + doSetAttr(stream, node, attr) { + var setattr = stream?.stream_ops.setattr; + var arg = setattr ? stream : node; + setattr ??= node.node_ops.setattr; + FS.checkOpExists(setattr, {{{ cDefs.EPERM }}}) + setattr(arg, attr); + }, + + // + // devices + // + // each character device consists of a device id + stream operations. + // when a character device node is created (e.g. /dev/stdin) it is + // assigned a device id that lets us map back to the actual device. + // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. + // however, once opened, the stream's operations are overridden with + // the operations of the device its underlying node maps back to. + chrdev_stream_ops: { + open(stream) { + var device = FS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + stream.stream_ops.open?.(stream); + }, + llseek() { + throw new FS.ErrnoError({{{ cDefs.ESPIPE }}}); + } + }, + major: (dev) => ((dev) >> 8), + minor: (dev) => ((dev) & 0xff), + makedev: (ma, mi) => ((ma) << 8 | (mi)), + registerDevice(dev, ops) { + FS.devices[dev] = { stream_ops: ops }; + }, + getDevice: (dev) => FS.devices[dev], + + // + // core + // + getMounts(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push(...m.mounts); + } + + return mounts; + }, + syncfs(populate, callback) { + if (typeof populate == 'function') { + callback = populate; + populate = false; + } + + FS.syncFSRequests++; + + if (FS.syncFSRequests > 1) { + err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`); + } + + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + + function doCallback(errCode) { +#if ASSERTIONS + assert(FS.syncFSRequests > 0); +#endif + FS.syncFSRequests--; + return callback(errCode); + } + + function done(errCode) { + if (errCode) { + if (!done.errored) { + done.errored = true; + return doCallback(errCode); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + }; + + // sync all mounts + for (var mount of mounts) { + if (mount.type.syncfs) { + mount.type.syncfs(mount, populate, done); + } else { + done(null); + } + } + }, + mount(type, opts, mountpoint) { +#if ASSERTIONS + if (typeof type == 'string') { + // The filesystem was not included, and instead we have an error + // message stored in the variable. + throw type; + } +#endif + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && FS.root) { + throw new FS.ErrnoError({{{ cDefs.EBUSY }}}); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError({{{ cDefs.EBUSY }}}); + } + + if (!FS.isDir(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}}); + } + } + + var mount = { + type, + opts, + mountpoint, + mounts: [] + }; + + // create a root node for the fs + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + FS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + + return mountRoot; + }, + unmount(mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + + for (var [hash, current] of Object.entries(FS.nameTable)) { + while (current) { + var next = current.name_next; + + if (mounts.includes(current.mount)) { + FS.destroyNode(current); + } + + current = next; + } + } + + // no longer a mountpoint + node.mounted = null; + + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); +#if ASSERTIONS + assert(idx !== -1); +#endif + node.mount.mounts.splice(idx, 1); + }, + lookup(parent, name) { + return parent.node_ops.lookup(parent, name); + }, + // generic function for all node creation + mknod(path, mode, dev) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + if (name === '.' || name === '..') { + throw new FS.ErrnoError({{{ cDefs.EEXIST }}}); + } + var errCode = FS.mayCreate(parent, name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.mknod) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } + return parent.node_ops.mknod(parent, name, mode, dev); + }, + statfs(path) { + return FS.statfsNode(FS.lookupPath(path, {follow: true}).node); + }, + statfsStream(stream) { + // We keep a separate statfsStream function because noderawfs overrides + // it. In noderawfs, stream.node is sometimes null. Instead, we need to + // look at stream.path. + return FS.statfsNode(stream.node); + }, + statfsNode(node) { + // NOTE: None of the defaults here are true. We're just returning safe and + // sane values. Currently nodefs and rawfs replace these defaults, + // other file systems leave them alone. + var rtn = { + bsize: 4096, + frsize: 4096, + blocks: 1e6, + bfree: 5e5, + bavail: 5e5, + files: FS.nextInode, + ffree: FS.nextInode - 1, + fsid: 42, + flags: 2, + namelen: 255, + }; + + if (node.node_ops.statfs) { + Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)); + } + return rtn; + }, + // helpers to create specific types of nodes + create(path, mode = 0o666) { + mode &= {{{ cDefs.S_IALLUGO }}}; + mode |= {{{ cDefs.S_IFREG }}}; + return FS.mknod(path, mode, 0); + }, + mkdir(path, mode = 0o777) { + mode &= {{{ cDefs.S_IRWXUGO }}} | {{{ cDefs.S_ISVTX }}}; + mode |= {{{ cDefs.S_IFDIR }}}; +#if FS_DEBUG + FS.trackingDelegate['onMakeDirectory']?.(path, mode); +#endif + return FS.mknod(path, mode, 0); + }, + // Creates a whole directory tree chain if it doesn't yet exist + mkdirTree(path, mode) { + var dirs = path.split('/'); + var d = ''; + for (var dir of dirs) { + if (!dir) continue; + if (d || PATH.isAbs(path)) d += '/'; + d += dir; + try { + FS.mkdir(d, mode); + } catch(e) { + if (e.errno != {{{ cDefs.EEXIST }}}) throw e; + } + } + }, + mkdev(path, mode, dev) { + if (typeof dev == 'undefined') { + dev = mode; + mode = 0o666; + } + mode |= {{{ cDefs.S_IFCHR }}}; + return FS.mknod(path, mode, dev); + }, + symlink(oldpath, newpath) { + if (!PATH_FS.resolve(oldpath)) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + var lookup = FS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + var newname = PATH.basename(newpath); + var errCode = FS.mayCreate(parent, newname); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.symlink) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } +#if FS_DEBUG + FS.trackingDelegate['onMakeSymlink']?.(oldpath, newpath); +#endif + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + // parents must exist + var lookup, old_dir, new_dir; + + // let the errors from non existent directories percolate up + lookup = FS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = FS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + + if (!old_dir || !new_dir) throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + // need to be part of the same mount + if (old_dir.mount !== new_dir.mount) { + throw new FS.ErrnoError({{{ cDefs.EXDEV }}}); + } + // source must exist + var old_node = FS.lookupNode(old_dir, old_name); + // old path should not be an ancestor of the new path + var relative = PATH_FS.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + // new path should not be an ancestor of the old path + relative = PATH_FS.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}}); + } + // see if the new path already exists + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) { + // not fatal + } + // early out if nothing needs to change + if (old_node === new_node) { + return; + } + // we'll need to delete the old entry + var isdir = FS.isDir(old_node.mode); + var errCode = FS.mayDelete(old_dir, old_name, isdir); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + // need delete permissions if we'll be overwriting. + // need create permissions if new doesn't already exist. + errCode = new_node ? + FS.mayDelete(new_dir, new_name, isdir) : + FS.mayCreate(new_dir, new_name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!old_dir.node_ops.rename) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } + if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) { + throw new FS.ErrnoError({{{ cDefs.EBUSY }}}); + } + // if we are going to change the parent, check write permissions + if (new_dir !== old_dir) { + errCode = FS.nodePermissions(old_dir, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } +#if FS_DEBUG + FS.trackingDelegate['willMovePath']?.(old_path, new_path); +#endif + // remove the node from the lookup hash + FS.hashRemoveNode(old_node); + // do the underlying fs rename + try { + old_dir.node_ops.rename(old_node, new_dir, new_name); + // update old node (we do this here to avoid each backend + // needing to) + old_node.parent = new_dir; + } catch (e) { + throw e; + } finally { + // add the node back to the hash (in case node_ops.rename + // changed its name) + FS.hashAddNode(old_node); + } +#if FS_DEBUG + FS.trackingDelegate['onMovePath']?.(old_path, new_path); +#endif + }, + rmdir(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, true); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.rmdir) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError({{{ cDefs.EBUSY }}}); + } +#if FS_DEBUG + FS.trackingDelegate['willDeletePath']?.(path); +#endif + parent.node_ops.rmdir(parent, name); + FS.destroyNode(node); +#if FS_DEBUG + FS.trackingDelegate['onDeletePath']?.(path); +#endif + }, + readdir(path) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + var readdir = FS.checkOpExists(node.node_ops.readdir, {{{ cDefs.ENOTDIR }}}); + return readdir(node); + }, + unlink(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, false); + if (errCode) { + // According to POSIX, we should map EISDIR to EPERM, but + // we instead do what Linux does (and we must, as we use + // the musl linux libc). + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.unlink) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError({{{ cDefs.EBUSY }}}); + } +#if FS_DEBUG + FS.trackingDelegate['willDeletePath']?.(path); +#endif + parent.node_ops.unlink(parent, name); + FS.destroyNode(node); +#if FS_DEBUG + FS.trackingDelegate['onDeletePath']?.(path); +#endif + }, + readlink(path) { + var lookup = FS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + if (!link.node_ops.readlink) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return link.node_ops.readlink(link); + }, + stat(path, dontFollow) { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + var getattr = FS.checkOpExists(node.node_ops.getattr, {{{ cDefs.EPERM }}}); + return getattr(node); + }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + var node = stream.node; + var getattr = stream.stream_ops.getattr; + var arg = getattr ? stream : node; + getattr ??= node.node_ops.getattr; + FS.checkOpExists(getattr, {{{ cDefs.EPERM }}}) + return getattr(arg); + }, + lstat(path) { + return FS.stat(path, true); + }, + doChmod(stream, node, mode, dontFollow) { + FS.doSetAttr(stream, node, { + mode: (mode & {{{ cDefs.S_IALLUGO }}}) | (node.mode & ~{{{ cDefs.S_IALLUGO }}}), + ctime: Date.now(), + dontFollow + }); + }, + chmod(path, mode, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + FS.doChmod(null, node, mode, dontFollow); + }, + lchmod(path, mode) { + FS.chmod(path, mode, true); + }, + fchmod(fd, mode) { + var stream = FS.getStreamChecked(fd); + FS.doChmod(stream, stream.node, mode, false); + }, + doChown(stream, node, dontFollow) { + FS.doSetAttr(stream, node, { + timestamp: Date.now(), + dontFollow + // we ignore the uid / gid for now + }); + }, + chown(path, uid, gid, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + FS.doChown(null, node, dontFollow); + }, + lchown(path, uid, gid) { + FS.chown(path, uid, gid, true); + }, + fchown(fd, uid, gid) { + var stream = FS.getStreamChecked(fd); + FS.doChown(stream, stream.node, false); + }, + doTruncate(stream, node, len) { + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var errCode = FS.nodePermissions(node, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.doSetAttr(stream, node, { + size: len, + timestamp: Date.now() + }); + }, + truncate(path, len) { + if (len < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + FS.doTruncate(null, node, len); + }, + ftruncate(fd, len) { + var stream = FS.getStreamChecked(fd); + if (len < 0 || (stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + FS.doTruncate(stream, stream.node, len); + }, + utime(path, atime, mtime) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + var setattr = FS.checkOpExists(node.node_ops.setattr, {{{ cDefs.EPERM }}}); + setattr(node, { + atime: atime, + mtime: mtime + }); + }, + open(path, flags, mode = 0o666) { + if (path === "") { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + flags = FS_modeStringToFlags(flags); + if ((flags & {{{ cDefs.O_CREAT }}})) { + mode = (mode & {{{ cDefs.S_IALLUGO }}}) | {{{ cDefs.S_IFREG }}}; + } else { + mode = 0; + } + var node; + var isDirPath; + if (typeof path == 'object') { + node = path; + } else { + isDirPath = path.endsWith("/"); + // noent_okay makes it so that if the final component of the path + // doesn't exist, lookupPath returns `node: undefined`. `path` will be + // updated to point to the target of all symlinks. + var lookup = FS.lookupPath(path, { + follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}), + noent_okay: true + }); + node = lookup.node; + path = lookup.path; + } + // perhaps we need to create the node + var created = false; + if ((flags & {{{ cDefs.O_CREAT }}})) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if ((flags & {{{ cDefs.O_EXCL }}})) { + throw new FS.ErrnoError({{{ cDefs.EEXIST }}}); + } + } else if (isDirPath) { + throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); + } else { + // node doesn't exist, try to create it + // Ignore the permission bits here to ensure we can `open` this new + // file below. We use chmod below to apply the permissions once the + // file is open. + node = FS.mknod(path, mode | 0o777, 0); + created = true; + } + } + if (!node) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + // can't truncate a device + if (FS.isChrdev(node.mode)) { + flags &= ~{{{ cDefs.O_TRUNC }}}; + } + // if asked only for a directory, then this must be one + if ((flags & {{{ cDefs.O_DIRECTORY }}}) && !FS.isDir(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}}); + } + // check permissions, if this is not a file we just created now (it is ok to + // create and write to a file with read-only permissions; it is read-only + // for later use) + if (!created) { + var errCode = FS.mayOpen(node, flags); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } + // do truncation if necessary + if ((flags & {{{ cDefs.O_TRUNC}}}) && !created) { + FS.truncate(node, 0); + } +#if FS_DEBUG + var origFlags = flags +#endif + // we've already handled these, don't pass down to the underlying vfs + flags &= ~({{{ cDefs.O_EXCL }}} | {{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_NOFOLLOW }}}); + + // register the stream with the filesystem + var stream = FS.createStream({ + node, + path: FS.getPath(node), // we want the absolute path to the node + flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false + }); + // call the new stream's open function + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + if (created) { + FS.chmod(node, mode & 0o777); + } +#if expectToReceiveOnModule('logReadFiles') + if (Module['logReadFiles'] && !(flags & {{{ cDefs.O_WRONLY}}})) { + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + err(`read file: ${path}`); + } + } +#endif +#if FS_DEBUG + FS.trackingDelegate['onOpenFile']?.(path, origFlags); +#endif + return stream; + }, + close(stream) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if (stream.getdents) stream.getdents = null; // free readdir state + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + stream.fd = null; +#if FS_DEBUG + if (stream.path) { + FS.trackingDelegate['onCloseFile']?.(stream.path); + } +#endif + }, + isClosed(stream) { + return stream.fd === null; + }, + llseek(stream, offset, whence) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError({{{ cDefs.ESPIPE }}}); + } + if (whence != {{{ cDefs.SEEK_SET }}} && whence != {{{ cDefs.SEEK_CUR }}} && whence != {{{ cDefs.SEEK_END }}}) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + stream.position = stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; +#if FS_DEBUG + if (stream.path) { + FS.trackingDelegate['onSeekFile']?.(stream.path, stream.position, whence); + } +#endif + return stream.position; + }, + read(stream, buffer, offset, length, position) { +#if ASSERTIONS + assert(offset >= 0); +#endif + if (length < 0 || position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY}}}) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError({{{ cDefs.ESPIPE }}}); + } + var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesRead; +#if FS_DEBUG + if (stream.path) { + FS.trackingDelegate['onReadFile']?.(stream.path, bytesRead); + } +#endif + return bytesRead; + }, + /** + * @param {TypedArray} buffer + */ + write(stream, buffer, offset, length, position, canOwn) { +#if ASSERTIONS + assert(offset >= 0); + assert(buffer.subarray, 'FS.write expects a TypedArray'); +#endif + if (length < 0 || position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_RDONLY}}}) { + throw new FS.ErrnoError({{{ cDefs.EBADF }}}); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EISDIR }}}); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + if (stream.seekable && stream.flags & {{{ cDefs.O_APPEND }}}) { + // seek to the end before writing in append mode + FS.llseek(stream, 0, {{{ cDefs.SEEK_END }}}); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError({{{ cDefs.ESPIPE }}}); + } + var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); + if (!seeking) stream.position += bytesWritten; +#if FS_DEBUG + if (stream.path) { + FS.trackingDelegate['onWriteToFile']?.(stream.path, bytesWritten); + } +#endif + return bytesWritten; + }, + mmap(stream, length, position, prot, flags) { + // User requests writing to file (prot & PROT_WRITE != 0). + // Checking if we have permissions to write to the file unless + // MAP_PRIVATE flag is set. According to POSIX spec it is possible + // to write to file opened in read-only mode with MAP_PRIVATE flag, + // as all modifications will be visible only in the memory of + // the current process. + if ((prot & {{{ cDefs.PROT_WRITE }}}) !== 0 + && (flags & {{{ cDefs.MAP_PRIVATE}}}) === 0 + && (stream.flags & {{{ cDefs.O_ACCMODE }}}) !== {{{ cDefs.O_RDWR}}}) { + throw new FS.ErrnoError({{{ cDefs.EACCES }}}); + } + if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY}}}) { + throw new FS.ErrnoError({{{ cDefs.EACCES }}}); + } + if (!stream.stream_ops.mmap) { + throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); + } + if (!length) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return stream.stream_ops.mmap(stream, length, position, prot, flags); + }, + msync(stream, buffer, offset, length, mmapFlags) { +#if ASSERTIONS + assert(offset >= 0); +#endif + if (!stream.stream_ops.msync) { + return 0; + } + return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); + }, + ioctl(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError({{{ cDefs.ENOTTY }}}); + } + return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile(path, opts = {}) { + opts.flags = opts.flags || {{{ cDefs.O_RDONLY }}}; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + abort(`Invalid encoding type "${opts.encoding}"`); + } + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + buf = UTF8ArrayToString(buf); + } + FS.close(stream); + return buf; + }, + /** + * @param {TypedArray|Array|string} data + */ + writeFile(path, data, opts = {}) { + opts.flags = opts.flags || {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}; + var stream = FS.open(path, opts.flags, opts.mode); + data = FS_fileDataToTypedArray(data); + FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); + FS.close(stream); + }, + + // + // module-level FS code + // + cwd: () => FS.currentPath, + chdir(path) { + var lookup = FS.lookupPath(path, { follow: true }); + if (lookup.node === null) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + } + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError({{{ cDefs.ENOTDIR }}}); + } + var errCode = FS.nodePermissions(lookup.node, 'x'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.currentPath = lookup.path; + }, + createDefaultDirectories() { + FS.mkdir('/tmp'); + FS.mkdir('/home'); + FS.mkdir('/home/web_user'); + }, + createDefaultDevices() { + // create /dev + FS.mkdir('/dev'); + // setup /dev/null + FS.registerDevice(FS.makedev(1, 3), { + read: () => 0, + write: (stream, buffer, offset, length, pos) => length, + llseek: () => 0, + }); + FS.mkdev('/dev/null', FS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using err() rather than out() + // so we register a second tty just for it. + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev('/dev/tty', FS.makedev(5, 0)); + FS.mkdev('/dev/tty1', FS.makedev(6, 0)); + // setup /dev/[u]random + // use a buffer to avoid overhead of individual crypto calls per byte + var randomBuffer = new Uint8Array(1024), randomLeft = 0; + var randomByte = () => { + if (randomLeft === 0) { + randomFill(randomBuffer); + randomLeft = randomBuffer.byteLength; + } + return randomBuffer[--randomLeft]; + }; + FS.createDevice('/dev', 'random', randomByte); + FS.createDevice('/dev', 'urandom', randomByte); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + FS.mkdir('/dev/shm'); + FS.mkdir('/dev/shm/tmp'); + }, + createSpecialDirectories() { + // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the + // name of the stream for fd 6 (see test_unistd_ttyname) + FS.mkdir('/proc'); + var proc_self = FS.mkdir('/proc/self'); + FS.mkdir('/proc/self/fd'); + FS.mount({ + mount() { + var node = FS.createNode(proc_self, 'fd', {{{ cDefs.S_IFDIR | 0o777 }}}, {{{ cDefs.S_IXUGO }}}); + node.stream_ops = { + llseek: MEMFS.stream_ops.llseek, + }; + node.node_ops = { + lookup(parent, name) { + var fd = +name; + var stream = FS.getStreamChecked(fd); + var ret = { + parent: null, + mount: { mountpoint: 'fake' }, + node_ops: { readlink: () => stream.path }, + id: fd + 1, + }; + ret.parent = ret; // make it look like a simple root node + return ret; + }, + readdir() { + return Array.from(FS.streams.entries()) + .filter(([k, v]) => v) + .map(([k, v]) => k.toString()); + } + }; + return node; + } + }, {}, '/proc/self/fd'); + }, + createStandardStreams(input, output, error) { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes FS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (input) { + FS.createDevice('/dev', 'stdin', input); + } else { + FS.symlink('/dev/tty', '/dev/stdin'); + } + if (output) { + FS.createDevice('/dev', 'stdout', null, output); + } else { + FS.symlink('/dev/tty', '/dev/stdout'); + } + if (error) { + FS.createDevice('/dev', 'stderr', null, error); + } else { + FS.symlink('/dev/tty1', '/dev/stderr'); + } + + // open default streams for the stdin, stdout and stderr devices + var stdin = FS.open('/dev/stdin', {{{ cDefs.O_RDONLY }}}); + var stdout = FS.open('/dev/stdout', {{{ cDefs.O_WRONLY }}}); + var stderr = FS.open('/dev/stderr', {{{ cDefs.O_WRONLY }}}); +#if ASSERTIONS + assert(stdin.fd === 0, `invalid handle for stdin (${stdin.fd})`); + assert(stdout.fd === 1, `invalid handle for stdout (${stdout.fd})`); + assert(stderr.fd === 2, `invalid handle for stderr (${stderr.fd})`); +#endif + }, + staticInit() { + FS.nameTable = new Array(4096); + + FS.mount(MEMFS, {}, '/'); + + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + FS.createSpecialDirectories(); + + FS.filesystems = { + 'MEMFS': MEMFS, +#if LibraryManager.has('libidbfs.js') + 'IDBFS': IDBFS, +#endif +#if LibraryManager.has('libnodefs.js') + 'NODEFS': NODEFS, +#endif +#if LibraryManager.has('libworkerfs.js') + 'WORKERFS': WORKERFS, +#endif +#if LibraryManager.has('libproxyfs.js') + 'PROXYFS': PROXYFS, +#endif + }; + }, + init(input, output, error) { +#if ASSERTIONS + assert(!FS.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); +#endif + FS.initialized = true; + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here +#if expectToReceiveOnModule('stdin') + input ??= Module['stdin']; +#endif +#if expectToReceiveOnModule('stdout') + output ??= Module['stdout']; +#endif +#if expectToReceiveOnModule('stderr') + error ??= Module['stderr']; +#endif + + FS.createStandardStreams(input, output, error); + }, + quit() { + FS.initialized = false; + // force-flush all streams, so we get musl std streams printed out +#if hasExportedSymbol('fflush') + _fflush(0); +#endif + // close all of our streams + for (var stream of FS.streams) { + if (stream) { + FS.close(stream); + } + } + }, + + // + // old v1 compatibility functions + // + findObject(path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (!ret.exists) { + return null; + } + return ret.object; + }, + analyzePath(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) { + } + var ret = { + isRoot: false, exists: false, error: 0, name: null, path: null, object: null, + parentExists: false, parentPath: null, parentObject: null + }; + try { + var lookup = FS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + }; + return ret; + }, + createPath(parent, path, canRead, canWrite) { + parent = typeof parent == 'string' ? parent : FS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + FS.mkdir(current); + } catch (e) { + if (e.errno != {{{ cDefs.EEXIST }}}) throw e; + } + parent = current; + } + return current; + }, + createFile(parent, name, properties, canRead, canWrite) { + var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); + var mode = FS_getMode(canRead, canWrite); + return FS.create(path, mode); + }, + /** + * @param {TypedArray|Array|string=} data + */ + createDataFile(parent, name, data, canRead, canWrite, canOwn) { + var path = name; + if (parent) { + parent = typeof parent == 'string' ? parent : FS.getPath(parent); + path = name ? PATH.join2(parent, name) : parent; + } + var mode = FS_getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + data = FS_fileDataToTypedArray(data); + // make sure we can write to the file + FS.chmod(node, mode | {{{ cDefs.S_IWUGO }}}); + var stream = FS.open(node, {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(node, mode); + } + }, + createDevice(parent, name, input, output) { + var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); + var mode = FS_getMode(!!input, !!output); + FS.createDevice.major ??= 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + FS.registerDevice(dev, { + open(stream) { + stream.seekable = false; + }, + close(stream) { + // flush any pending line data + if (output?.buffer?.length) { + output({{{ charCode('\n') }}}); + } + }, + read(stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError({{{ cDefs.EAGAIN }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset+i]); + } catch (e) { + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + } + } + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; + } + }); + return FS.mkdev(path, mode, dev); + }, + // Makes sure a file's contents are loaded. Returns whether the file has + // been loaded successfully. No-op for files that have been loaded already. + forceLoadFile(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + #if FS_DEBUG + dbg(`forceLoadFile: ${obj.url}`) + #endif + if (globalThis.XMLHttpRequest) { + abort("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + } else { // Command-line. + try { + obj.contents = readBinary(obj.url); + } catch (e) { + #if FS_DEBUG + dbg(`forceLoadFile exception: ${e}`); + #endif + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + } + } + }, + // Creates a file record for lazy-loading from a URL. XXX This requires a synchronous + // XHR, which is not possible in browsers except in a web worker! Use preloading, + // either --preload-file in emcc or FS.createPreloadedFile + createLazyFile(parent, name, url, canRead, canWrite) { + // Lazy chunked Uint8Array (implements get and length from Uint8Array). + // Actual getting is abstracted away for eventual reuse. + class LazyUint8Array { + lengthKnown = false; + chunks = []; // Loaded chunks. Index is the chunk number +#if USE_CLOSURE_COMPILER + // Closure compiler requires us to declare all properties ahead of time. + getter = undefined; + _length = 0; + _chunkSize = 0; +#endif + get(idx) { + if (idx > this.length-1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = (idx / this.chunkSize)|0; + return this.getter(chunkNum)[chunkOffset]; + } + setDataGetter(getter) { + this.getter = getter; + } + cacheLength() { + // Find length + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status); + var datalength = Number(xhr.getResponseHeader("Content-length")); + var header; + var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; + var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; + + #if SMALL_XHR_CHUNKS + var chunkSize = 1024; // Chunk size in bytes + #else + var chunkSize = 1024*1024; // Chunk size in bytes + #endif + + if (!hasByteServing) chunkSize = datalength; + + // Function to get a range from the remote URL. + var doXHR = (from, to) => { + if (from > to) abort("invalid range (" + from + ", " + to + ") or no bytes requested!"); + if (to > datalength-1) abort("only " + datalength + " bytes available! programmer error!"); + + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); + + // Some hints to the browser that we want binary data. + xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status); + if (xhr.response !== undefined) { + return new Uint8Array(/** @type{Array} */(xhr.response || [])); + } + return intArrayFromString(xhr.responseText || '', true); + }; + var lazyArray = this; + lazyArray.setDataGetter((chunkNum) => { + var start = chunkNum * chunkSize; + var end = (chunkNum+1) * chunkSize - 1; // including this byte + end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block + if (typeof lazyArray.chunks[chunkNum] == 'undefined') { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof lazyArray.chunks[chunkNum] == 'undefined') abort('doXHR failed!'); + return lazyArray.chunks[chunkNum]; + }); + + if (usesGzip || !datalength) { + // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length + chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file + datalength = this.getter(0).length; + chunkSize = datalength; + out("LazyFiles on gzip forces download of the whole file when length is accessed"); + } + + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + } + get length() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + } + get chunkSize() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + } + } + + if (globalThis.XMLHttpRequest) { + if (!ENVIRONMENT_IS_WORKER) abort('Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'); + var lazyArray = new LazyUint8Array(); + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url: url }; + } + + var node = FS.createFile(parent, name, properties, canRead, canWrite); + // This is a total hack, but I want to get this lazy file code out of the + // core of MEMFS. If we want to keep this lazy file concept I feel it should + // be its own thin LAZYFS proxying calls to MEMFS. + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + // Add a function that defers querying the file size until it is asked the first time. + Object.defineProperties(node, { + usedBytes: { + get: function() { return this.contents.length; } + } + }); + // override each stream op with one that tries to force load the lazy file first + var stream_ops = {}; + for (const [key, fn] of Object.entries(node.stream_ops)) { + stream_ops[key] = (...args) => { + FS.forceLoadFile(node); + return fn(...args); + }; + } + function writeChunks(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= contents.length) + return 0; + var size = Math.min(contents.length - position, length); +#if ASSERTIONS + assert(size >= 0); +#endif + if (contents.slice) { // normal array + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR + buffer[offset + i] = contents.get(position + i); + } + } + return size; + } + // use a custom read function + stream_ops.read = (stream, buffer, offset, length, position) => { + FS.forceLoadFile(node); + return writeChunks(stream, buffer, offset, length, position) + }; + // use a custom mmap function + stream_ops.mmap = (stream, length, position, prot, flags) => { + FS.forceLoadFile(node); + var ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError({{{ cDefs.ENOMEM }}}); + } + writeChunks(stream, HEAP8, ptr, length, position); + return { ptr, allocated: true }; + }; + node.stream_ops = stream_ops; + return node; + }, + }, + + $FS_mkdirTree__docs: ` + /** + * @param {number=} mode Optionally, the mode to create in. Uses mkdir's + * default if not set. + */`, + $FS_mkdirTree__deps: ['$FS'], + $FS_mkdirTree: (path, mode) => FS.mkdirTree(path, mode), +}; + +// Add library aliases for all the FS. as FS_. +for (let key in LibraryFS.$FS) { + const alias = `$FS_${key}`; + // Skip defining the alias if it already exists or if it's not an API function. + if (LibraryFS[alias] || key[0] !== key[0].toLowerCase()) { + continue; + } + LibraryFS[alias] = `(...args) => FS.${key}(...args)`; + LibraryFS[`${alias}__deps`] = ['$FS']; +} +addToLibrary(LibraryFS); diff --git a/src/lib/libfs_shared.js b/src/lib/libfs_shared.js new file mode 100644 index 0000000000000..838b52309701a --- /dev/null +++ b/src/lib/libfs_shared.js @@ -0,0 +1,209 @@ +/** + * @license + * Copyright 2032 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $preloadPlugins__postset: () => addAtModule(makeModuleReceive('preloadPlugins')), + $preloadPlugins: [], + +#if !MINIMAL_RUNTIME + // Tries to handle an input byteArray using preload plugins. Returns true if + // it was handled. + $FS_handledByPreloadPlugin__internal: true, + $FS_handledByPreloadPlugin__deps: ['$preloadPlugins'], + $FS_handledByPreloadPlugin: async (byteArray, fullname) => { +#if LibraryManager.has('libbrowser.js') + // Ensure plugins are ready. + if (typeof Browser != 'undefined') Browser.init(); +#endif + + for (var plugin of preloadPlugins) { + if (plugin['canHandle'](fullname)) { +#if ASSERTIONS + assert(plugin['handle'].constructor.name === 'AsyncFunction', 'Filesystem plugin handlers must be async functions (See #24914)') +#endif + return plugin['handle'](byteArray, fullname); + } + } + // If no plugin handled this file then return the original/unmodified + // byteArray. + return byteArray; + }, + + // Legacy version of FS_preloadFile that uses callback rather than async + $FS_createPreloadedFile__deps: ['$FS_preloadFile'], + $FS_createPreloadedFile: (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => { + FS_preloadFile(parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish).then(onload).catch(onerror); + }, + + // Preloads a file asynchronously. You can call this before run, for example in + // preRun. run will be delayed until this file arrives and is set up. + // If you call it after run(), you may want to pause the main loop until it + // completes, if so, you can use the onload parameter to be notified when + // that happens. + // In addition to normally creating the file, we also asynchronously preload + // the browser-friendly versions of it: For an image, we preload an Image + // element and for an audio, and Audio. These are necessary for SDL_Image + // and _Mixer to find the files in preloadedImages/Audios. + // You can also call this with a typed array instead of a url. It will then + // do preloading for the Image/Audio part, as if the typed array were the + // result of an XHR that you did manually. + $FS_preloadFile__deps: [ + '$asyncLoad', + '$PATH_FS', + '$FS_createDataFile', + '$getUniqueRunDependency', + '$addRunDependency', + '$removeRunDependency', + '$FS_handledByPreloadPlugin', + ], + $FS_preloadFile: async (parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish) => { + // TODO we should allow people to just pass in a complete filename instead + // of parent and name being that we just join them anyways + var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; + var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname + addRunDependency(dep); + + try { + var byteArray = url; + if (typeof url == 'string') { + byteArray = await asyncLoad(url); + } + + byteArray = await FS_handledByPreloadPlugin(byteArray, fullname); + preFinish?.(); + if (!dontCreateFile) { + FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); + } + } finally { + removeRunDependency(dep); + } + }, +#endif + + // convert the 'r', 'r+', etc. to its corresponding set of O_* flags + $FS_modeStringToFlags: (str) => { + if (typeof str != 'string') return str; + var flagModes = { + 'r': {{{ cDefs.O_RDONLY }}}, + 'r+': {{{ cDefs.O_RDWR }}}, + 'w': {{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_CREAT }}} | {{{ cDefs.O_WRONLY }}}, + 'w+': {{{ cDefs.O_TRUNC }}} | {{{ cDefs.O_CREAT }}} | {{{ cDefs.O_RDWR }}}, + 'a': {{{ cDefs.O_APPEND }}} | {{{ cDefs.O_CREAT }}} | {{{ cDefs.O_WRONLY }}}, + 'a+': {{{ cDefs.O_APPEND }}} | {{{ cDefs.O_CREAT }}} | {{{ cDefs.O_RDWR }}}, + }; + var flags = flagModes[str]; + if (typeof flags == 'undefined') { + throw new Error(`Unknown file open mode: ${str}`); + } + return flags; + }, + $FS_getMode: (canRead, canWrite) => { + var mode = 0; + if (canRead) mode |= {{{ cDefs.S_IRUGO }}} | {{{ cDefs.S_IXUGO }}}; + if (canWrite) mode |= {{{ cDefs.S_IWUGO }}}; + return mode; + }, + + $FS_fileDataToTypedArray: (data) => { + if (typeof data == 'string') { + data = intArrayFromString(data, true); + } + if (!data.subarray) { + data = new Uint8Array(data); + } + return data; + }, + + $FS_stdin_getChar_buffer: [], + + // getChar has 3 particular return values: + // a.) the next character represented as an integer + // b.) undefined to signal that no data is currently available + // c.) null to signal an EOF + $FS_stdin_getChar__deps: [ + '$FS_stdin_getChar_buffer', + '$intArrayFromString', + ], + $FS_stdin_getChar: () => { + if (!FS_stdin_getChar_buffer.length) { + var result = null; +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + // we will read data by chunks of BUFSIZE + var BUFSIZE = 256; + var buf = Buffer.alloc(BUFSIZE); + var bytesRead = 0; + + // For some reason we must suppress a closure warning here, even though + // fd definitely exists on process.stdin, and is even the proper way to + // get the fd of stdin, + // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 + // This started to happen after moving this logic out of library_tty.js, + // so it is related to the surrounding code in some unclear manner. + /** @suppress {missingProperties} */ + var fd = process.stdin.fd; + + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch(e) { + // Cross-platform differences: on Windows, reading EOF throws an + // exception, but on other OSes, reading EOF returns 0. Uniformize + // behavior by treating the EOF exception to return 0. + if (e.toString().includes('EOF')) bytesRead = 0; + else throw e; + } + + if (bytesRead > 0) { + result = buf.slice(0, bytesRead).toString('utf-8'); + } + } else +#endif +#if ENVIRONMENT_MAY_BE_WEB + if (globalThis.window?.prompt) { + // Browser. + result = window.prompt('Input: '); // returns null on cancel + if (result !== null) { + result += '\n'; + } + } else +#endif +#if ENVIRONMENT_MAY_BE_SHELL + if (globalThis.readline) { + /** @suppress{checkTypes, undefinedVars} */ + result = readline(); + if (result) { + result += '\n'; + } + } else +#endif + {} + if (!result) { + return null; + } + FS_stdin_getChar_buffer = intArrayFromString(result, true); + } + return FS_stdin_getChar_buffer.shift(); + }, + + $FS_unlink__deps: ['$FS'], + $FS_unlink: 'FS.unlink', + + $FS_createPath__deps: ['$FS'], + $FS_createPath: 'FS.createPath', + + $FS_createDevice__deps: ['$FS'], + $FS_createDevice: 'FS.createDevice', + + $FS_readFile__deps: ['$FS'], + $FS_readFile: 'FS.readFile', +}); + +// Normally only the FS things that the compiler sees are needed are included. +// FORCE_FILESYSTEM makes us always include the FS object, which lets the user +// call APIs on it from JS freely. +if (FORCE_FILESYSTEM) { + extraLibraryFuncs.push('$FS'); +} diff --git a/src/lib/libgetvalue.js b/src/lib/libgetvalue.js new file mode 100644 index 0000000000000..9a35b2cb1f6c6 --- /dev/null +++ b/src/lib/libgetvalue.js @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2022 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// These functions are defined once here, then included in the library below +// under two different names. +function setValueImpl(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': {{{ makeSetValue('ptr', '0', 'value', 'i1') }}}; break; + case 'i8': {{{ makeSetValue('ptr', '0', 'value', 'i8') }}}; break; + case 'i16': {{{ makeSetValue('ptr', '0', 'value', 'i16') }}}; break; + case 'i32': {{{ makeSetValue('ptr', '0', 'value', 'i32') }}}; break; +#if WASM_BIGINT + case 'i64': {{{ makeSetValue('ptr', '0', 'value', 'i64') }}}; break; +#else + case 'i64': abort('to do setValue(i64) use WASM_BIGINT'); +#endif + case 'float': {{{ makeSetValue('ptr', '0', 'value', 'float') }}}; break; + case 'double': {{{ makeSetValue('ptr', '0', 'value', 'double') }}}; break; + case '*': {{{ makeSetValue('ptr', '0', 'value', '*') }}}; break; + default: abort(`invalid type for setValue: ${type}`); + } +} + +function getValueImpl(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': return {{{ makeGetValue('ptr', '0', 'i1') }}}; + case 'i8': return {{{ makeGetValue('ptr', '0', 'i8') }}}; + case 'i16': return {{{ makeGetValue('ptr', '0', 'i16') }}}; + case 'i32': return {{{ makeGetValue('ptr', '0', 'i32') }}}; +#if WASM_BIGINT + case 'i64': return {{{ makeGetValue('ptr', '0', 'i64') }}}; +#else + case 'i64': abort('to do getValue(i64) use WASM_BIGINT'); +#endif + case 'float': return {{{ makeGetValue('ptr', '0', 'float') }}}; + case 'double': return {{{ makeGetValue('ptr', '0', 'double') }}}; + case '*': return {{{ makeGetValue('ptr', '0', '*') }}}; + default: abort(`invalid type for getValue: ${type}`); + } +} + +var LibraryMemOps = { + $setValue__docs: ` + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */`, + $setValue: setValueImpl, + + $getValue__docs: ` + /** + * @param {number} ptr + * @param {string} type + */`, + $getValue: getValueImpl, +}; + +addToLibrary(LibraryMemOps); diff --git a/src/lib/libglemu.js b/src/lib/libglemu.js new file mode 100644 index 0000000000000..256221838c13f --- /dev/null +++ b/src/lib/libglemu.js @@ -0,0 +1,3947 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +assert(LEGACY_GL_EMULATION, 'libglemu.js should only be included with LEGACY_GL_EMULATION set') +assert(!FULL_ES2, 'cannot emulate both ES2 and legacy GL'); +assert(!FULL_ES3, 'cannot emulate both ES3 and legacy GL'); + +{{{ + const copySigs = (func) => { + if (!MAIN_MODULE) return ''; + return ` _${func}.sig = _emscripten_${func}.sig = orig_${func}.sig;`; + }; + const fromPtr = (arg) => { + if (CAN_ADDRESS_2GB) { + return `${arg} >>>= 0`; + } else if (MEMORY64) { + return `${arg} = Number(${arg})`; + } + return ''; + }; +}}} + +var LibraryGLEmulation = { + // GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL + $GLEmulation__deps: ['$GLImmediateSetup', 'glEnable', 'glDisable', + 'glIsEnabled', 'glGetBooleanv', 'glGetIntegerv', 'glGetString', + 'glCreateShader', 'glShaderSource', 'glCompileShader', 'glAttachShader', + 'glDetachShader', 'glUseProgram', 'glDeleteProgram', 'glBindAttribLocation', + 'glLinkProgram', 'glBindBuffer', 'glGetFloatv', 'glHint', + 'glEnableVertexAttribArray', 'glDisableVertexAttribArray', + 'glVertexAttribPointer', 'glActiveTexture', '$stringToNewUTF8', + '$ptrToString', '$getEmscriptenSupportedExtensions', + ], + $GLEmulation__postset: ` + // Forward declare GL functions that are overridden by GLEmulation. + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDrawArrays; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDrawElements; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glActiveTexture; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glEnable; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDisable; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvf; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvi; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glTexEnvfv; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetIntegerv; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glIsEnabled; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetBooleanv; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetString; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glCreateShader; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glShaderSource; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glCompileShader; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glAttachShader; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDetachShader; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glUseProgram; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDeleteProgram; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glBindAttribLocation; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glLinkProgram; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glBindBuffer; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glGetFloatv; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glHint; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glEnableVertexAttribArray; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glDisableVertexAttribArray; + /**@suppress {duplicate, undefinedVars}*/var _emscripten_glVertexAttribPointer; + /**@suppress {duplicate, undefinedVars}*/var _glTexEnvf; + /**@suppress {duplicate, undefinedVars}*/var _glTexEnvi; + /**@suppress {duplicate, undefinedVars}*/var _glTexEnvfv; + /**@suppress {duplicate, undefinedVars}*/var _glGetTexEnviv; + /**@suppress {duplicate, undefinedVars}*/var _glGetTexEnvfv; + GLEmulation.init();`, + $GLEmulation: { + // Fog support. Partial, we assume shaders are used that implement fog. We just pass them uniforms + fogStart: 0, + fogEnd: 1, + fogDensity: 1.0, + fogColor: null, + fogMode: 0x800, // GL_EXP + fogEnabled: false, + + // GL_CLIP_PLANE support + MAX_CLIP_PLANES: 6, + clipPlaneEnabled: [false, false, false, false, false, false], + clipPlaneEquation: [], + + // GL_LIGHTING support + lightingEnabled: false, + + lightModelAmbient: null, + lightModelLocalViewer: false, + lightModelTwoSide: false, + + materialAmbient: null, + materialDiffuse: null, + materialSpecular: null, + materialShininess: null, + materialEmission: null, + + MAX_LIGHTS: 8, + lightEnabled: [false, false, false, false, false, false, false, false], + lightAmbient: [], + lightDiffuse: [], + lightSpecular: [], + lightPosition: [], + // TODO attenuation modes of lights + + // GL_ALPHA_TEST support + alphaTestEnabled: false, + alphaTestFunc: 0x207, // GL_ALWAYS + alphaTestRef: 0.0, + + // GL_POINTS support. + pointSize: 1.0, + + // VAO support + vaos: [], + currentVao: null, + enabledVertexAttribArrays: {}, // helps with vao cleanups + + hasRunInit: false, + + // Find a token in a shader source string + findToken(source, token) { + function isIdentChar(ch) { + if (ch >= 48 && ch <= 57) // 0-9 + return true; + if (ch >= 65 && ch <= 90) // A-Z + return true; + if (ch >= 97 && ch <= 122) // a-z + return true; + return false; + } + var i = -1; + do { + i = source.indexOf(token, i + 1); + if (i < 0) { + break; + } + if (i > 0 && isIdentChar(source[i - 1])) { + continue; + } + i += token.length; + if (i < source.length - 1 && isIdentChar(source[i + 1])) { + continue; + } + return true; + } while (true); + return false; + }, + + init() { + // Do not activate immediate/emulation code (e.g. replace glDrawElements) + // when in FULL_ES2 mode. We do not need full emulation, we instead + // emulate client-side arrays etc. in FULL_ES2 code in a straightforward + // manner, and avoid not having a bound buffer be ambiguous between es2 + // emulation code and legacy gl emulation code. +#if FULL_ES2 + return; +#endif + + if (GLEmulation.hasRunInit) { + return; + } + GLEmulation.hasRunInit = true; + + GLEmulation.fogColor = new Float32Array(4); + + for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { + GLEmulation.clipPlaneEquation[clipPlaneId] = new Float32Array(4); + } + + // set defaults for GL_LIGHTING + GLEmulation.lightModelAmbient = new Float32Array([0.2, 0.2, 0.2, 1.0]); + GLEmulation.materialAmbient = new Float32Array([0.2, 0.2, 0.2, 1.0]); + GLEmulation.materialDiffuse = new Float32Array([0.8, 0.8, 0.8, 1.0]); + GLEmulation.materialSpecular = new Float32Array([0.0, 0.0, 0.0, 1.0]); + GLEmulation.materialShininess = new Float32Array([0.0]); + GLEmulation.materialEmission = new Float32Array([0.0, 0.0, 0.0, 1.0]); + + for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { + GLEmulation.lightAmbient[lightId] = new Float32Array([0.0, 0.0, 0.0, 1.0]); + GLEmulation.lightDiffuse[lightId] = lightId ? new Float32Array([0.0, 0.0, 0.0, 1.0]) : new Float32Array([1.0, 1.0, 1.0, 1.0]); + GLEmulation.lightSpecular[lightId] = lightId ? new Float32Array([0.0, 0.0, 0.0, 1.0]) : new Float32Array([1.0, 1.0, 1.0, 1.0]); + GLEmulation.lightPosition[lightId] = new Float32Array([0.0, 0.0, 1.0, 0.0]); + } + + + // Add some emulation workarounds + err('WARNING: using emscripten GL emulation. This is a collection of limited workarounds, do not expect it to work.'); +#if GL_UNSAFE_OPTS == 1 + err('WARNING: using emscripten GL emulation unsafe opts. If weirdness happens, try -sGL_UNSAFE_OPTS=0'); +#endif + + // XXX some of the capabilities we don't support may lead to incorrect rendering, if we do not emulate them in shaders + var validCapabilities = { + 0xB44: 1, // GL_CULL_FACE + 0xBE2: 1, // GL_BLEND + 0xBD0: 1, // GL_DITHER, + 0xB90: 1, // GL_STENCIL_TEST + 0xB71: 1, // GL_DEPTH_TEST + 0xC11: 1, // GL_SCISSOR_TEST + 0x8037: 1, // GL_POLYGON_OFFSET_FILL + 0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE + 0x80A0: 1 // GL_SAMPLE_COVERAGE + }; + + var orig_glEnable = _glEnable; + _glEnable = _emscripten_glEnable = (cap) => { + // Clean up the renderer on any change to the rendering state. The optimization of + // skipping renderer setup is aimed at the case of multiple glDraw* right after each other + GLImmediate.lastRenderer?.cleanup(); + if (cap == 0xB60 /* GL_FOG */) { + if (GLEmulation.fogEnabled != true) { + GLImmediate.currentRenderer = null; // Fog parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.fogEnabled = true; + } + return; + } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { + var clipPlaneId = cap - 0x3000; + if (GLEmulation.clipPlaneEnabled[clipPlaneId] != true) { + GLImmediate.currentRenderer = null; // clip plane parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.clipPlaneEnabled[clipPlaneId] = true; + } + return; + } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { + var lightId = cap - 0x4000; + if (GLEmulation.lightEnabled[lightId] != true) { + GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.lightEnabled[lightId] = true; + } + return; + } else if (cap == 0xB50 /* GL_LIGHTING */) { + if (GLEmulation.lightingEnabled != true) { + GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.lightingEnabled = true; + } + return; + } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { + if (GLEmulation.alphaTestEnabled != true) { + GLImmediate.currentRenderer = null; // alpha testing is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.alphaTestEnabled = true; + } + return; + } else if (cap == 0xDE1 /* GL_TEXTURE_2D */) { + // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support + // it by forwarding to glEnableClientState + /* Actually, let's not, for now. (This sounds exceedingly broken) + * This is in gl_ps_workaround2.c. + _glEnableClientState(cap); + */ + return; + } else if (!(cap in validCapabilities)) { + return; + } + orig_glEnable(cap); + }; + {{{ copySigs('glEnable') }}} + + var orig_glDisable = _glDisable; + _glDisable = _emscripten_glDisable = (cap) => { + GLImmediate.lastRenderer?.cleanup(); + if (cap == 0xB60 /* GL_FOG */) { + if (GLEmulation.fogEnabled != false) { + GLImmediate.currentRenderer = null; // Fog parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.fogEnabled = false; + } + return; + } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { + var clipPlaneId = cap - 0x3000; + if (GLEmulation.clipPlaneEnabled[clipPlaneId] != false) { + GLImmediate.currentRenderer = null; // clip plane parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.clipPlaneEnabled[clipPlaneId] = false; + } + return; + } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { + var lightId = cap - 0x4000; + if (GLEmulation.lightEnabled[lightId] != false) { + GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.lightEnabled[lightId] = false; + } + return; + } else if (cap == 0xB50 /* GL_LIGHTING */) { + if (GLEmulation.lightingEnabled != false) { + GLImmediate.currentRenderer = null; // light parameter is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.lightingEnabled = false; + } + return; + } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { + if (GLEmulation.alphaTestEnabled != false) { + GLImmediate.currentRenderer = null; // alpha testing is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.alphaTestEnabled = false; + } + return; + } else if (cap == 0xDE1 /* GL_TEXTURE_2D */) { + // XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support + // it by forwarding to glDisableClientState + /* Actually, let's not, for now. (This sounds exceedingly broken) + * This is in gl_ps_workaround2.c. + _glDisableClientState(cap); + */ + return; + } else if (!(cap in validCapabilities)) { + return; + } + orig_glDisable(cap); + }; + {{{ copySigs('glDisable') }}} + + var orig_glIsEnabled = _glIsEnabled; + _glIsEnabled = _emscripten_glIsEnabled = (cap) => { + if (cap == 0xB60 /* GL_FOG */) { + return GLEmulation.fogEnabled ? 1 : 0; + } else if ((cap >= 0x3000) && (cap < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { + var clipPlaneId = cap - 0x3000; + return GLEmulation.clipPlaneEnabled[clipPlaneId] ? 1 : 0; + } else if ((cap >= 0x4000) && (cap < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { + var lightId = cap - 0x4000; + return GLEmulation.lightEnabled[lightId] ? 1 : 0; + } else if (cap == 0xB50 /* GL_LIGHTING */) { + return GLEmulation.lightingEnabled ? 1 : 0; + } else if (cap == 0xBC0 /* GL_ALPHA_TEST */) { + return GLEmulation.alphaTestEnabled ? 1 : 0; + } else if (!(cap in validCapabilities)) { + return 0; + } + return GLctx.isEnabled(cap); + }; + {{{ copySigs('glIsEnabled') }}} + + var orig_glGetBooleanv = _glGetBooleanv; + _glGetBooleanv = _emscripten_glGetBooleanv = (pname, p) => { + var attrib = GLEmulation.getAttributeFromCapability(pname); + if (attrib !== null) { + {{{ fromPtr('p') }}} + var result = GLImmediate.enabledClientAttributes[attrib]; + {{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}}; + return; + } + orig_glGetBooleanv(pname, p); + }; + {{{ copySigs('glGetBooleanv') }}} + + var orig_glGetIntegerv = _glGetIntegerv; + _glGetIntegerv = _emscripten_glGetIntegerv = (pname, params) => { + {{{ fromPtr('params') }}} + switch (pname) { + case 0x84E2: pname = GLctx.MAX_TEXTURE_IMAGE_UNITS /* fake it */; break; // GL_MAX_TEXTURE_UNITS + case 0x8B4A: { // GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB + var result = GLctx.getParameter(GLctx.MAX_VERTEX_UNIFORM_VECTORS); + {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply + return; + } + case 0x8B49: { // GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB + var result = GLctx.getParameter(GLctx.MAX_FRAGMENT_UNIFORM_VECTORS); + {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply + return; + } + case 0x8B4B: { // GL_MAX_VARYING_FLOATS_ARB + var result = GLctx.getParameter(GLctx.MAX_VARYING_VECTORS); + {{{ makeSetValue('params', '0', 'result*4', 'i32') }}}; // GLES gives num of 4-element vectors, GL wants individual components, so multiply + return; + } + case 0x8871: pname = GLctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS /* close enough */; break; // GL_MAX_TEXTURE_COORDS + case 0x807A: { // GL_VERTEX_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x807B: { // GL_VERTEX_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x807C: { // GL_VERTEX_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } + case 0x8081: { // GL_COLOR_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x8082: { // GL_COLOR_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x8083: { // GL_COLOR_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } + case 0x8088: { // GL_TEXTURE_COORD_ARRAY_SIZE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.size : 0', 'i32') }}}; + return; + } + case 0x8089: { // GL_TEXTURE_COORD_ARRAY_TYPE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.type : 0', 'i32') }}}; + return; + } + case 0x808A: { // GL_TEXTURE_COORD_ARRAY_STRIDE + var attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; + {{{ makeSetValue('params', '0', 'attribute ? attribute.stride : 0', 'i32') }}}; + return; + } + case 0x0D32: { // GL_MAX_CLIP_PLANES + {{{ makeSetValue('params', '0', 'GLEmulation.MAX_CLIP_PLANES', 'i32') }}}; // all implementations need to support at least 6 + return; + } + case 0x0BA0: { // GL_MATRIX_MODE + {{{ makeSetValue('params', '0', 'GLImmediate.currentMatrix + 0x1700', 'i32') }}}; + return; + } + case 0x0BC1: { // GL_ALPHA_TEST_FUNC + {{{ makeSetValue('params', '0', 'GLEmulation.alphaTestFunc', 'i32') }}}; + return; + } + } + orig_glGetIntegerv(pname, params); + }; + {{{ copySigs('glGetIntegerv') }}} + + var orig_glGetString = _glGetString; + _glGetString = _emscripten_glGetString = (name_) => { + if (GL.stringCache[name_]) return GL.stringCache[name_]; + switch (name_) { + case 0x1F03 /* GL_EXTENSIONS */: // Add various extensions that we can support + var ret = stringToNewUTF8(getEmscriptenSupportedExtensions(GLctx).join(' ') + + ' GL_EXT_texture_env_combine GL_ARB_texture_env_crossbar GL_ATI_texture_env_combine3 GL_NV_texture_env_combine4 GL_EXT_texture_env_dot3 GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_framebuffer_object GL_ARB_vertex_program GL_ARB_fragment_program GL_ARB_shading_language_100 GL_ARB_shader_objects GL_ARB_vertex_shader GL_ARB_fragment_shader GL_ARB_texture_cube_map GL_EXT_draw_range_elements' + + (GL.currentContext.compressionExt ? ' GL_ARB_texture_compression GL_EXT_texture_compression_s3tc' : '') + + (GL.currentContext.anisotropicExt ? ' GL_EXT_texture_filter_anisotropic' : '') + ); + return GL.stringCache[name_] = {{{ to64('ret') }}}; + } + return orig_glGetString(name_); + }; + {{{ copySigs('glGetString') }}} + + // Do some automatic rewriting to work around GLSL differences. Note that this must be done in + // tandem with the rest of the program, by itself it cannot suffice. + // Note that we need to remember shader types for this rewriting, saving sources makes it easier to debug. + GL.shaderInfos = {}; +#if GL_DEBUG + GL.shaderSources = {}; + GL.shaderOriginalSources = {}; +#endif + var orig_glCreateShader = _glCreateShader; + _glCreateShader = _emscripten_glCreateShader = (shaderType) => { + var id = orig_glCreateShader(shaderType); + GL.shaderInfos[id] = { + type: shaderType, + ftransform: false + }; + return id; + }; + {{{ copySigs('glCreateShader') }}} + + function ensurePrecision(source) { + if (!/precision +(low|medium|high)p +float *;/.test(source)) { + source = '#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n' + source; + } + return source; + } + + var orig_glShaderSource = _glShaderSource; + _glShaderSource = _emscripten_glShaderSource = (shader, count, string, length) => { + {{{ fromPtr('string') }}} + {{{ fromPtr('length') }}} + var source = GL.getSource(shader, count, string, length); +#if GL_DEBUG + dbg("glShaderSource: Input: \n" + source); + GL.shaderOriginalSources[shader] = source; +#endif + // XXX We add attributes and uniforms to shaders. The program can ask for the # of them, and see the + // ones we generated, potentially confusing it? Perhaps we should hide them. + if (GL.shaderInfos[shader].type == GLctx.VERTEX_SHADER) { + // Replace ftransform() with explicit project/modelview transforms, and add position and matrix info. + var has_pm = source.search(/u_projection/) >= 0; + var has_mm = source.search(/u_modelView/) >= 0; + var has_pv = source.search(/a_position/) >= 0; + var need_pm = 0, need_mm = 0, need_pv = 0; + var old = source; + source = source.replace(/ftransform\(\)/g, '(u_projection * u_modelView * a_position)'); + if (old != source) need_pm = need_mm = need_pv = 1; + old = source; + source = source.replace(/gl_ProjectionMatrix/g, 'u_projection'); + if (old != source) need_pm = 1; + old = source; + source = source.replace(/gl_ModelViewMatrixTranspose\[2\]/g, 'vec4(u_modelView[0][2], u_modelView[1][2], u_modelView[2][2], u_modelView[3][2])'); // XXX extremely inefficient + if (old != source) need_mm = 1; + old = source; + source = source.replace(/gl_ModelViewMatrix/g, 'u_modelView'); + if (old != source) need_mm = 1; + old = source; + source = source.replace(/gl_Vertex/g, 'a_position'); + if (old != source) need_pv = 1; + old = source; + source = source.replace(/gl_ModelViewProjectionMatrix/g, '(u_projection * u_modelView)'); + if (old != source) need_pm = need_mm = 1; + if (need_pv && !has_pv) source = 'attribute vec4 a_position; \n' + source; + if (need_mm && !has_mm) source = 'uniform mat4 u_modelView; \n' + source; + if (need_pm && !has_pm) source = 'uniform mat4 u_projection; \n' + source; + GL.shaderInfos[shader].ftransform = need_pm || need_mm || need_pv; // we will need to provide the fixed function stuff as attributes and uniforms + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + // XXX To handle both regular texture mapping and cube mapping, we use vec4 for tex coordinates. + old = source; + var need_vtc = source.search(`v_texCoord${i}`) == -1; + source = source.replace(new RegExp(`gl_TexCoord\\[${i}\\]`, 'g'), `v_texCoord${i}`) + .replace(new RegExp(`gl_MultiTexCoord${i}`, 'g'), `a_texCoord${i}`); + if (source != old) { + source = `attribute vec4 a_texCoord${i}; \n${source}`; + if (need_vtc) { + source = `varying vec4 v_texCoord${i}; \n${source}`; + } + } + + old = source; + source = source.replace(new RegExp(`gl_TextureMatrix\\[${i}\\]`, 'g'), `u_textureMatrix${i}`); + if (source != old) { + source = `uniform mat4 u_textureMatrix${i}; \n${source}`; + } + } + if (source.includes('gl_FrontColor')) { + source = 'varying vec4 v_color; \n' + + source.replace(/gl_FrontColor/g, 'v_color'); + } + if (source.includes('gl_Color')) { + source = 'attribute vec4 a_color; \n' + + source.replace(/gl_Color/g, 'a_color'); + } + if (source.includes('gl_Normal')) { + source = 'attribute vec3 a_normal; \n' + + source.replace(/gl_Normal/g, 'a_normal'); + } + // fog + if (source.includes('gl_FogFragCoord')) { + source = 'varying float v_fogFragCoord; \n' + + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); + } + } else { // Fragment shader + for (i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + old = source; + source = source.replace(new RegExp(`gl_TexCoord\\[${i}\\]`, 'g'), `v_texCoord${i}`); + if (source != old) { + source = 'varying vec4 v_texCoord' + i + '; \n' + source; + } + } + if (source.includes('gl_Color')) { + source = 'varying vec4 v_color; \n' + source.replace(/gl_Color/g, 'v_color'); + } + if (source.includes('gl_Fog.color')) { + source = 'uniform vec4 u_fogColor; \n' + + source.replace(/gl_Fog.color/g, 'u_fogColor'); + } + if (source.includes('gl_Fog.end')) { + source = 'uniform float u_fogEnd; \n' + + source.replace(/gl_Fog.end/g, 'u_fogEnd'); + } + if (source.includes('gl_Fog.scale')) { + source = 'uniform float u_fogScale; \n' + + source.replace(/gl_Fog.scale/g, 'u_fogScale'); + } + if (source.includes('gl_Fog.density')) { + source = 'uniform float u_fogDensity; \n' + + source.replace(/gl_Fog.density/g, 'u_fogDensity'); + } + if (source.includes('gl_FogFragCoord')) { + source = 'varying float v_fogFragCoord; \n' + + source.replace(/gl_FogFragCoord/g, 'v_fogFragCoord'); + } + source = ensurePrecision(source); + } +#if GL_DEBUG + GL.shaderSources[shader] = source; + dbg("glShaderSource: Output: \n" + source); +#endif + GLctx.shaderSource(GL.shaders[shader], source); + }; + {{{ copySigs('glShaderSource') }}} + + var orig_glCompileShader = _glCompileShader; + _glCompileShader = _emscripten_glCompileShader = (shader) => { + GLctx.compileShader(GL.shaders[shader]); +#if GL_DEBUG + if (!GLctx.getShaderParameter(GL.shaders[shader], GLctx.COMPILE_STATUS)) { + dbg(`Failed to compile shader: ${GLctx.getShaderInfoLog(GL.shaders[shader])}`); + dbg(`Info: ${JSON.stringify(GL.shaderInfos[shader])}`); + dbg(`Original source: ${GL.shaderOriginalSources[shader]}`); + dbg(`Source: ${GL.shaderSources[shader]}`); + abort('Shader compilation halt'); + } +#endif + }; + {{{ copySigs('glCompileShader') }}} + + GL.programShaders = {}; + var orig_glAttachShader = _glAttachShader; + _glAttachShader = _emscripten_glAttachShader = (program, shader) => { + GL.programShaders[program] ||= []; + GL.programShaders[program].push(shader); + orig_glAttachShader(program, shader); + }; + {{{ copySigs('glAttachShader') }}} + + var orig_glDetachShader = _glDetachShader; + _glDetachShader = _emscripten_glDetachShader = (program, shader) => { + var programShader = GL.programShaders[program]; + if (!programShader) { + err(`WARNING: _glDetachShader received invalid program: ${program}`); + return; + } + var index = programShader.indexOf(shader); + programShader.splice(index, 1); + orig_glDetachShader(program, shader); + }; + {{{ copySigs('glDetachShader') }}} + + var orig_glUseProgram = _glUseProgram; + _glUseProgram = _emscripten_glUseProgram = (program) => { +#if GL_DEBUG + if (GL.debug) { + dbg('[using program with shaders]'); + if (program) { + for (var shader of GL.programShaders[program]) { + dbg(` shader ${shader}, original source: ${GL.shaderOriginalSources[shader]}`); + dbg(` Source: ${GL.shaderSources[shader]}`); + } + } + } +#endif + if (GL.currProgram != program) { + GLImmediate.currentRenderer = null; // This changes the FFP emulation shader program, need to recompute that. + GL.currProgram = program; + GLImmediate.fixedFunctionProgram = 0; + orig_glUseProgram(program); + } + } + {{{ copySigs('glUseProgram') }}} + + var orig_glDeleteProgram = _glDeleteProgram; + _glDeleteProgram = _emscripten_glDeleteProgram = (program) => { + orig_glDeleteProgram(program); + if (program == GL.currProgram) { + GLImmediate.currentRenderer = null; // This changes the FFP emulation shader program, need to recompute that. + GL.currProgram = 0; + } + }; + {{{ copySigs('glDeleteProgram') }}} + + // If attribute 0 was not bound, bind it to 0 for WebGL performance reasons. Track if 0 is free for that. + var zeroUsedPrograms = {}; + var orig_glBindAttribLocation = _glBindAttribLocation; + _glBindAttribLocation = _emscripten_glBindAttribLocation = (program, index, name) => { + if (index == 0) zeroUsedPrograms[program] = true; + orig_glBindAttribLocation(program, index, name); + }; + {{{ copySigs('glBindAttribLocation') }}} + + var orig_glLinkProgram = _glLinkProgram; + _glLinkProgram = _emscripten_glLinkProgram = (program) => { + if (!(program in zeroUsedPrograms)) { + GLctx.bindAttribLocation(GL.programs[program], 0, 'a_position'); + } + orig_glLinkProgram(program); + }; + {{{ copySigs('glLinkProgram') }}} + + var orig_glBindBuffer = _glBindBuffer; + _glBindBuffer = _emscripten_glBindBuffer = (target, buffer) => { + orig_glBindBuffer(target, buffer); + if (target == GLctx.ARRAY_BUFFER) { + if (GLEmulation.currentVao) { +#if ASSERTIONS + assert(GLEmulation.currentVao.arrayBuffer == buffer || GLEmulation.currentVao.arrayBuffer == 0 || buffer == 0, 'TODO: support for multiple array buffers in vao'); +#endif + GLEmulation.currentVao.arrayBuffer = buffer; + } + } else if (target == GLctx.ELEMENT_ARRAY_BUFFER) { + if (GLEmulation.currentVao) GLEmulation.currentVao.elementArrayBuffer = buffer; + } + }; + {{{ copySigs('glBindBuffer') }}} + + var orig_glGetFloatv = _glGetFloatv; + _glGetFloatv = _emscripten_glGetFloatv = (pname, params) => { + {{{ fromPtr('params') }}} + if (pname == 0xBA6) { // GL_MODELVIEW_MATRIX + HEAPF32.set(GLImmediate.matrix[0/*m*/], {{{ getHeapOffset('params', 'float') }}}); + } else if (pname == 0xBA7) { // GL_PROJECTION_MATRIX + HEAPF32.set(GLImmediate.matrix[1/*p*/], {{{ getHeapOffset('params', 'float') }}}); + } else if (pname == 0xBA8) { // GL_TEXTURE_MATRIX + HEAPF32.set(GLImmediate.matrix[2/*t*/ + GLImmediate.clientActiveTexture], {{{ getHeapOffset('params', 'float') }}}); + } else if (pname == 0xB66) { // GL_FOG_COLOR + HEAPF32.set(GLEmulation.fogColor, {{{ getHeapOffset('params', 'float') }}}); + } else if (pname == 0xB63) { // GL_FOG_START + {{{ makeSetValue('params', '0', 'GLEmulation.fogStart', 'float') }}}; + } else if (pname == 0xB64) { // GL_FOG_END + {{{ makeSetValue('params', '0', 'GLEmulation.fogEnd', 'float') }}}; + } else if (pname == 0xB62) { // GL_FOG_DENSITY + {{{ makeSetValue('params', '0', 'GLEmulation.fogDensity', 'float') }}}; + } else if (pname == 0xB65) { // GL_FOG_MODE + {{{ makeSetValue('params', '0', 'GLEmulation.fogMode', 'float') }}}; + } else if (pname == 0xB53) { // GL_LIGHT_MODEL_AMBIENT + {{{ makeSetValue('params', '0', 'GLEmulation.lightModelAmbient[0]', 'float') }}}; + {{{ makeSetValue('params', '4', 'GLEmulation.lightModelAmbient[1]', 'float') }}}; + {{{ makeSetValue('params', '8', 'GLEmulation.lightModelAmbient[2]', 'float') }}}; + {{{ makeSetValue('params', '12', 'GLEmulation.lightModelAmbient[3]', 'float') }}}; + } else if (pname == 0xBC2) { // GL_ALPHA_TEST_REF + {{{ makeSetValue('params', '0', 'GLEmulation.alphaTestRef', 'float') }}}; + } else { + orig_glGetFloatv(pname, params); + } + }; + {{{ copySigs('glGetFloatv') }}} + + var orig_glHint = _glHint; + _glHint = _emscripten_glHint = (target, mode) => { + if (target == 0x84EF) { // GL_TEXTURE_COMPRESSION_HINT + return; + } + orig_glHint(target, mode); + }; + {{{ copySigs('glHint') }}} + + var orig_glEnableVertexAttribArray = _glEnableVertexAttribArray; + _glEnableVertexAttribArray = _emscripten_glEnableVertexAttribArray = (index) => { + orig_glEnableVertexAttribArray(index); + GLEmulation.enabledVertexAttribArrays[index] = 1; + if (GLEmulation.currentVao) GLEmulation.currentVao.enabledVertexAttribArrays[index] = 1; + }; + {{{ copySigs('glEnableVertexAttribArray') }}} + + var orig_glDisableVertexAttribArray = _glDisableVertexAttribArray; + _glDisableVertexAttribArray = _emscripten_glDisableVertexAttribArray = (index) => { + orig_glDisableVertexAttribArray(index); + delete GLEmulation.enabledVertexAttribArrays[index]; + if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledVertexAttribArrays[index]; + }; + {{{ copySigs('glDisableVertexAttribArray') }}} + + var orig_glVertexAttribPointer = _glVertexAttribPointer; + _glVertexAttribPointer = _emscripten_glVertexAttribPointer = (index, size, type, normalized, stride, pointer) => { + orig_glVertexAttribPointer(index, size, type, normalized, stride, pointer); + if (GLEmulation.currentVao) { // TODO: avoid object creation here? likely not hot though + GLEmulation.currentVao.vertexAttribPointers[index] = [index, size, type, normalized, stride, pointer]; + } + }; + {{{ copySigs('glVertexAttribPointer') }}} + }, + + getAttributeFromCapability(cap) { + var attrib = null; + switch (cap) { + case 0xDE1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it +#if ASSERTIONS + abort("GL_TEXTURE_2D is not a spec-defined capability for gl{Enable,Disable}ClientState."); +#endif + // Fall through: + case 0x8078: // GL_TEXTURE_COORD_ARRAY + attrib = GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture; break; + case 0x8074: // GL_VERTEX_ARRAY + attrib = GLImmediate.VERTEX; break; + case 0x8075: // GL_NORMAL_ARRAY + attrib = GLImmediate.NORMAL; break; + case 0x8076: // GL_COLOR_ARRAY + attrib = GLImmediate.COLOR; break; + } + return attrib; + }, + }, + + glDeleteObject__deps: ['glDeleteProgram', 'glDeleteShader'], + glDeleteObject: (id) => { + if (GL.programs[id]) { + _glDeleteProgram(id); + } else if (GL.shaders[id]) { + _glDeleteShader(id); + } else { + err(`WARNING: deleteObject received invalid id: ${id}`); + } + }, + glDeleteObjectARB: 'glDeleteObject', + + glGetObjectParameteriv__deps: ['glGetProgramiv', 'glGetShaderiv'], + glGetObjectParameteriv: (id, type, result) => { + if (GL.programs[id]) { + if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB + var log = GLctx.getProgramInfoLog(GL.programs[id]); + if (log === null) log = '(unknown error)'; + {{{ makeSetValue('result', '0', 'log.length', 'i32') }}}; + return; + } + _glGetProgramiv(id, type, result); + } else if (GL.shaders[id]) { + if (type == 0x8B84) { // GL_OBJECT_INFO_LOG_LENGTH_ARB + var log = GLctx.getShaderInfoLog(GL.shaders[id]); + if (log === null) log = '(unknown error)'; + {{{ makeSetValue('result', '0', 'log.length', 'i32') }}}; + return; + } else if (type == 0x8B88) { // GL_OBJECT_SHADER_SOURCE_LENGTH_ARB + var source = GLctx.getShaderSource(GL.shaders[id]); + if (source === null) return; // If an error occurs, nothing will be written to result + {{{ makeSetValue('result', '0', 'source.length', 'i32') }}}; + return; + } + _glGetShaderiv(id, type, result); + } else { + err(`WARNING: getObjectParameteriv received invalid id: ${id}`); + } + }, + glGetObjectParameterivARB: 'glGetObjectParameteriv', + + glGetInfoLog__deps: ['glGetProgramInfoLog', 'glGetShaderInfoLog'], + glGetInfoLog: (id, maxLength, length, infoLog) => { + if (GL.programs[id]) { + _glGetProgramInfoLog(id, maxLength, length, infoLog); + } else if (GL.shaders[id]) { + _glGetShaderInfoLog(id, maxLength, length, infoLog); + } else { + err(`WARNING: glGetInfoLog received invalid id: ${id}`); + } + }, + glGetInfoLogARB: 'glGetInfoLog', + + glBindProgram: (type, id) => { +#if ASSERTIONS + assert(id == 0); +#endif + }, + glBindProgramARB: 'glBindProgram', + + glGetPointerv: (name, p) => { + var attribute; + switch (name) { + case 0x808E: // GL_VERTEX_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.VERTEX]; break; + case 0x8090: // GL_COLOR_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; + case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER + attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; break; + default: + GL.recordError(0x500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + err(`GL_INVALID_ENUM in glGetPointerv: Unsupported name ${name}!`); +#endif + return; + } + {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; + }, + + // GL Immediate mode + + // See comment in GLEmulation.init() +#if !FULL_ES2 + $GLImmediate__postset: 'GLImmediate.setupFuncs(); Browser.moduleContextCreatedCallbacks.push(() => GLImmediate.init());', +#endif + $GLImmediate__deps: ['$Browser', '$GL', '$GLEmulation'], + $GLImmediate: { + MapTreeLib: null, + spawnMapTreeLib: () => { + /** + * A naive implementation of a map backed by an array, and accessed by + * naive iteration along the array. (hashmap with only one bucket) + * @constructor + */ + function CNaiveListMap() { + var list = []; + + this.insert = function CNaiveListMap_insert(key, val) { + if (this.contains(key|0)) return false; + list.push([key, val]); + return true; + }; + + var __contains_i; + this.contains = function CNaiveListMap_contains(key) { + for (__contains_i = 0; __contains_i < list.length; ++__contains_i) { + if (list[__contains_i][0] === key) return true; + } + return false; + }; + + var __get_i; + this.get = function CNaiveListMap_get(key) { + for (__get_i = 0; __get_i < list.length; ++__get_i) { + if (list[__get_i][0] === key) return list[__get_i][1]; + } + return undefined; + }; + }; + + /** + * A tree of map nodes. + * Uses `KeyView`s to allow descending the tree without garbage. + * Example: { + * // Create our map object. + * var map = new ObjTreeMap(); + * + * // Grab the static keyView for the map. + * var keyView = map.GetStaticKeyView(); + * + * // Let's make a map for: + * // root: + * // 1: + * // 2: + * // 5: "Three, sir!" + * // 3: "Three!" + * + * // Note how we can chain together `Reset` and `Next` to + * // easily descend based on multiple key fragments. + * keyView.Reset().Next(1).Next(2).Next(5).Set("Three, sir!"); + * keyView.Reset().Next(1).Next(2).Next(3).Set("Three!"); + * } + * @constructor + */ + function CMapTree() { + /** @constructor */ + function CNLNode() { + var map = new CNaiveListMap(); + + this.child = function CNLNode_child(keyFrag) { + if (!map.contains(keyFrag|0)) { + map.insert(keyFrag|0, new CNLNode()); + } + return map.get(keyFrag|0); + }; + + this.value = undefined; + this.get = function CNLNode_get() { + return this.value; + }; + + this.set = function CNLNode_set(val) { + this.value = val; + }; + } + + /** @constructor */ + function CKeyView(root) { + var cur; + + this.reset = function CKeyView_reset() { + cur = root; + return this; + }; + this.reset(); + + this.next = function CKeyView_next(keyFrag) { + cur = cur.child(keyFrag); + return this; + }; + + this.get = function CKeyView_get() { + return cur.get(); + }; + + this.set = function CKeyView_set(val) { + cur.set(val); + }; + }; + + var root; + var staticKeyView; + + this.createKeyView = function CNLNode_createKeyView() { + return new CKeyView(root); + } + + this.clear = function CNLNode_clear() { + root = new CNLNode(); + staticKeyView = this.createKeyView(); + }; + this.clear(); + + this.getStaticKeyView = function CNLNode_getStaticKeyView() { + staticKeyView.reset(); + return staticKeyView; + }; + }; + + // Exports: + return { + create: () => new CMapTree(), + }; + }, + + TexEnvJIT: null, + spawnTexEnvJIT: () => { + // GL defs: + var GL_TEXTURE0 = 0x84C0; + var GL_TEXTURE_1D = 0xDE0; + var GL_TEXTURE_2D = 0xDE1; + var GL_TEXTURE_3D = 0x806f; + var GL_TEXTURE_CUBE_MAP = 0x8513; + var GL_TEXTURE_ENV = 0x2300; + var GL_TEXTURE_ENV_MODE = 0x2200; + var GL_TEXTURE_ENV_COLOR = 0x2201; + var GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; + var GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; + var GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + var GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; + var GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; + var GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; + + var GL_SRC0_RGB = 0x8580; + var GL_SRC1_RGB = 0x8581; + var GL_SRC2_RGB = 0x8582; + + var GL_SRC0_ALPHA = 0x8588; + var GL_SRC1_ALPHA = 0x8589; + var GL_SRC2_ALPHA = 0x858A; + + var GL_OPERAND0_RGB = 0x8590; + var GL_OPERAND1_RGB = 0x8591; + var GL_OPERAND2_RGB = 0x8592; + + var GL_OPERAND0_ALPHA = 0x8598; + var GL_OPERAND1_ALPHA = 0x8599; + var GL_OPERAND2_ALPHA = 0x859A; + + var GL_COMBINE_RGB = 0x8571; + var GL_COMBINE_ALPHA = 0x8572; + + var GL_RGB_SCALE = 0x8573; + var GL_ALPHA_SCALE = 0xD1C; + + // env.mode + var GL_ADD = 0x104; + var GL_BLEND = 0xBE2; + var GL_REPLACE = 0x1E01; + var GL_MODULATE = 0x2100; + var GL_DECAL = 0x2101; + var GL_COMBINE = 0x8570; + + // env.color/alphaCombiner + //var GL_ADD = 0x104; + //var GL_REPLACE = 0x1E01; + //var GL_MODULATE = 0x2100; + var GL_SUBTRACT = 0x84E7; + var GL_INTERPOLATE = 0x8575; + + // env.color/alphaSrc + var GL_TEXTURE = 0x1702; + var GL_CONSTANT = 0x8576; + var GL_PRIMARY_COLOR = 0x8577; + var GL_PREVIOUS = 0x8578; + + // env.color/alphaOp + var GL_SRC_COLOR = 0x300; + var GL_ONE_MINUS_SRC_COLOR = 0x301; + var GL_SRC_ALPHA = 0x302; + var GL_ONE_MINUS_SRC_ALPHA = 0x303; + + var GL_RGB = 0x1907; + var GL_RGBA = 0x1908; + + // Our defs: + var TEXENVJIT_NAMESPACE_PREFIX = "tej_"; + // Not actually constant, as they can be changed between JIT passes: + var TEX_UNIT_UNIFORM_PREFIX = "uTexUnit"; + var TEX_COORD_VARYING_PREFIX = "vTexCoord"; + var PRIM_COLOR_VARYING = "vPrimColor"; + var TEX_MATRIX_UNIFORM_PREFIX = "uTexMatrix"; + + // Static vars: + var s_texUnits = null; //[]; + var s_activeTexture = 0; + + var s_requiredTexUnitsForPass = []; + + // Static funcs: + function abort_noSupport(info) { + abort("[TexEnvJIT] ABORT: No support: " + info); + } + + function abort_sanity(info) { + abort("[TexEnvJIT] ABORT: Sanity failure: " + info); + } + + function genTexUnitSampleExpr(texUnitID) { + var texUnit = s_texUnits[texUnitID]; + var texType = texUnit.getTexType(); + + var func = null; + switch (texType) { + case GL_TEXTURE_1D: + func = "texture2D"; + break; + case GL_TEXTURE_2D: + func = "texture2D"; + break; + case GL_TEXTURE_3D: + return abort_noSupport("No support for 3D textures."); + case GL_TEXTURE_CUBE_MAP: + func = "textureCube"; + break; + default: + return abort_sanity(`Unknown texType: ${ptrToString(texType)}`); + } + + var texCoordExpr = TEX_COORD_VARYING_PREFIX + texUnitID; + if (TEX_MATRIX_UNIFORM_PREFIX != null) { + texCoordExpr = `(${TEX_MATRIX_UNIFORM_PREFIX}${texUnitID} * ${texCoordExpr})`; + } + return `${func}(${TEX_UNIT_UNIFORM_PREFIX}${texUnitID}, ${texCoordExpr}.xy)`; + } + + function getTypeFromCombineOp(op) { + switch (op) { + case GL_SRC_COLOR: + case GL_ONE_MINUS_SRC_COLOR: + return "vec3"; + case GL_SRC_ALPHA: + case GL_ONE_MINUS_SRC_ALPHA: + return "float"; + } + + return abort_noSupport("Unsupported combiner op: " + ptrToString(op)); + } + + function getCurTexUnit() { + return s_texUnits[s_activeTexture]; + } + + function genCombinerSourceExpr(texUnitID, constantExpr, previousVar, + src, op) + { + var srcExpr = null; + switch (src) { + case GL_TEXTURE: + srcExpr = genTexUnitSampleExpr(texUnitID); + break; + case GL_CONSTANT: + srcExpr = constantExpr; + break; + case GL_PRIMARY_COLOR: + srcExpr = PRIM_COLOR_VARYING; + break; + case GL_PREVIOUS: + srcExpr = previousVar; + break; + default: + return abort_noSupport("Unsupported combiner src: " + ptrToString(src)); + } + + var expr = null; + switch (op) { + case GL_SRC_COLOR: + expr = srcExpr + ".rgb"; + break; + case GL_ONE_MINUS_SRC_COLOR: + expr = "(vec3(1.0) - " + srcExpr + ".rgb)"; + break; + case GL_SRC_ALPHA: + expr = srcExpr + ".a"; + break; + case GL_ONE_MINUS_SRC_ALPHA: + expr = "(1.0 - " + srcExpr + ".a)"; + break; + default: + return abort_noSupport("Unsupported combiner op: " + ptrToString(op)); + } + + return expr; + } + + function valToFloatLiteral(val) { + if (val == Math.round(val)) return val + '.0'; + return val; + } + + + // Classes: + /** @constructor */ + function CTexEnv() { + this.mode = GL_MODULATE; + this.colorCombiner = GL_MODULATE; + this.alphaCombiner = GL_MODULATE; + this.colorScale = 1; + this.alphaScale = 1; + this.envColor = [0, 0, 0, 0]; + + this.colorSrc = [ + GL_TEXTURE, + GL_PREVIOUS, + GL_CONSTANT + ]; + this.alphaSrc = [ + GL_TEXTURE, + GL_PREVIOUS, + GL_CONSTANT + ]; + this.colorOp = [ + GL_SRC_COLOR, + GL_SRC_COLOR, + GL_SRC_ALPHA + ]; + this.alphaOp = [ + GL_SRC_ALPHA, + GL_SRC_ALPHA, + GL_SRC_ALPHA + ]; + + // Map GLenums to small values to efficiently pack the enums to bits for tighter access. + this.traverseKey = { + // mode + 0x1E01 /* GL_REPLACE */: 0, + 0x2100 /* GL_MODULATE */: 1, + 0x104 /* GL_ADD */: 2, + 0xBE2 /* GL_BLEND */: 3, + 0x2101 /* GL_DECAL */: 4, + 0x8570 /* GL_COMBINE */: 5, + + // additional color and alpha combiners + 0x84E7 /* GL_SUBTRACT */: 3, + 0x8575 /* GL_INTERPOLATE */: 4, + + // color and alpha src + 0x1702 /* GL_TEXTURE */: 0, + 0x8576 /* GL_CONSTANT */: 1, + 0x8577 /* GL_PRIMARY_COLOR */: 2, + 0x8578 /* GL_PREVIOUS */: 3, + + // color and alpha op + 0x300 /* GL_SRC_COLOR */: 0, + 0x301 /* GL_ONE_MINUS_SRC_COLOR */: 1, + 0x302 /* GL_SRC_ALPHA */: 2, + 0x303 /* GL_ONE_MINUS_SRC_ALPHA */: 3 + }; + + // The tuple (key0,key1,key2) uniquely identifies the state of the variables in CTexEnv. + // -1 on key0 denotes 'the whole cached key is dirty' + this.key0 = -1; + this.key1 = 0; + this.key2 = 0; + + this.computeKey0 = function() { + var k = this.traverseKey; + var key = k[this.mode] * 1638400; // 6 distinct values. + key += k[this.colorCombiner] * 327680; // 5 distinct values. + key += k[this.alphaCombiner] * 65536; // 5 distinct values. + // The above three fields have 6*5*5=150 distinct values -> 8 bits. + key += (this.colorScale-1) * 16384; // 10 bits used. + key += (this.alphaScale-1) * 4096; // 12 bits used. + key += k[this.colorSrc[0]] * 1024; // 14 + key += k[this.colorSrc[1]] * 256; // 16 + key += k[this.colorSrc[2]] * 64; // 18 + key += k[this.alphaSrc[0]] * 16; // 20 + key += k[this.alphaSrc[1]] * 4; // 22 + key += k[this.alphaSrc[2]]; // 24 bits used total. + return key; + } + this.computeKey1 = function() { + var k = this.traverseKey; + var key = k[this.colorOp[0]] * 4096; + key += k[this.colorOp[1]] * 1024; + key += k[this.colorOp[2]] * 256; + key += k[this.alphaOp[0]] * 16; + key += k[this.alphaOp[1]] * 4; + key += k[this.alphaOp[2]]; + return key; + } + // TODO: remove this. The color should not be part of the key! + this.computeKey2 = function() { + return this.envColor[0] * 16777216 + this.envColor[1] * 65536 + this.envColor[2] * 256 + 1 + this.envColor[3]; + } + this.recomputeKey = function() { + this.key0 = this.computeKey0(); + this.key1 = this.computeKey1(); + this.key2 = this.computeKey2(); + } + this.invalidateKey = function() { + this.key0 = -1; // The key of this texture unit must be recomputed when rendering the next time. + GLImmediate.currentRenderer = null; // The currently used renderer must be re-evaluated at next render. + } + } + + /** @constructor */ + function CTexUnit() { + this.env = new CTexEnv(); + this.enabled_tex1D = false; + this.enabled_tex2D = false; + this.enabled_tex3D = false; + this.enabled_texCube = false; + this.texTypesEnabled = 0; // A bitfield combination of the four flags above, used for fast access to operations. + + this.traverseState = function CTexUnit_traverseState(keyView) { + if (this.texTypesEnabled) { + if (this.env.key0 == -1) { + this.env.recomputeKey(); + } + keyView.next(this.texTypesEnabled | (this.env.key0 << 4)); + keyView.next(this.env.key1); + keyView.next(this.env.key2); + } else { + // For correctness, must traverse a zero value, theoretically a subsequent integer key could collide with this value otherwise. + keyView.next(0); + } + }; + }; + + // Class impls: + CTexUnit.prototype.enabled = function CTexUnit_enabled() { + return this.texTypesEnabled; + } + + CTexUnit.prototype.genPassLines = function CTexUnit_genPassLines(passOutputVar, passInputVar, texUnitID) { + if (!this.enabled()) { + return ["vec4 " + passOutputVar + " = " + passInputVar + ";"]; + } + var lines = this.env.genPassLines(passOutputVar, passInputVar, texUnitID).join('\n'); + + var texLoadLines = ''; + var texLoadRegex = /(texture.*?\(.*?\))/g; + var loadCounter = 0; + var load; + + // As an optimization, merge duplicate identical texture loads to one var. + while (load = texLoadRegex.exec(lines)) { + var texLoadExpr = load[1]; + var secondOccurrence = lines.slice(load.index+1).indexOf(texLoadExpr); + if (secondOccurrence != -1) { // And also has a second occurrence of same load expression.. + // Create new var to store the common load. + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; + var texLoadVar = prefix + 'texload' + loadCounter++; + var texLoadLine = 'vec4 ' + texLoadVar + ' = ' + texLoadExpr + ';\n'; + texLoadLines += texLoadLine + '\n'; // Store the generated texture load statements in a temp string to not confuse regex search in progress. + lines = lines.split(texLoadExpr).join(texLoadVar); + // Reset regex search, since we modified the string. + texLoadRegex = /(texture.*\(.*\))/g; + } + } + return [texLoadLines + lines]; + } + + CTexUnit.prototype.getTexType = function CTexUnit_getTexType() { + if (this.enabled_texCube) { + return GL_TEXTURE_CUBE_MAP; + } else if (this.enabled_tex3D) { + return GL_TEXTURE_3D; + } else if (this.enabled_tex2D) { + return GL_TEXTURE_2D; + } else if (this.enabled_tex1D) { + return GL_TEXTURE_1D; + } + return 0; + } + + CTexEnv.prototype.genPassLines = function CTexEnv_genPassLines(passOutputVar, passInputVar, texUnitID) { + switch (this.mode) { + case GL_REPLACE: { + /* RGB: + * Cv = Cs + * Av = Ap // Note how this is different, and that we'll + * need to track the bound texture internalFormat + * to get this right. + * + * RGBA: + * Cv = Cs + * Av = As + */ + return [ + "vec4 " + passOutputVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", + ]; + } + case GL_ADD: { + /* RGBA: + * Cv = Cp + Cs + * Av = ApAs + */ + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; + var texVar = prefix + "tex"; + var colorVar = prefix + "color"; + var alphaVar = prefix + "alpha"; + + return [ + "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", + "vec3 " + colorVar + " = " + passInputVar + ".rgb + " + texVar + ".rgb;", + "float " + alphaVar + " = " + passInputVar + ".a * " + texVar + ".a;", + "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", + ]; + } + case GL_MODULATE: { + /* RGBA: + * Cv = CpCs + * Av = ApAs + */ + var line = [ + "vec4 " + passOutputVar, + " = ", + passInputVar, + " * ", + genTexUnitSampleExpr(texUnitID), + ";", + ]; + return [line.join("")]; + } + case GL_DECAL: { + /* RGBA: + * Cv = Cp(1 - As) + CsAs + * Av = Ap + */ + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; + var texVar = prefix + "tex"; + var colorVar = prefix + "color"; + var alphaVar = prefix + "alpha"; + + return [ + "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", + [ + "vec3 " + colorVar + " = ", + passInputVar + ".rgb * (1.0 - " + texVar + ".a)", + " + ", + texVar + ".rgb * " + texVar + ".a", + ";" + ].join(""), + "float " + alphaVar + " = " + passInputVar + ".a;", + "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", + ]; + } + case GL_BLEND: { + /* RGBA: + * Cv = Cp(1 - Cs) + CcCs + * Av = As + */ + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; + var texVar = prefix + "tex"; + var colorVar = prefix + "color"; + var alphaVar = prefix + "alpha"; + + return [ + "vec4 " + texVar + " = " + genTexUnitSampleExpr(texUnitID) + ";", + [ + "vec3 " + colorVar + " = ", + passInputVar + ".rgb * (1.0 - " + texVar + ".rgb)", + " + ", + PRIM_COLOR_VARYING + ".rgb * " + texVar + ".rgb", + ";" + ].join(""), + "float " + alphaVar + " = " + texVar + ".a;", + "vec4 " + passOutputVar + " = vec4(" + colorVar + ", " + alphaVar + ");", + ]; + } + case GL_COMBINE: { + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + texUnitID + "_"; + var colorVar = prefix + "color"; + var alphaVar = prefix + "alpha"; + var colorLines = this.genCombinerLines(true, colorVar, + passInputVar, texUnitID, + this.colorCombiner, this.colorSrc, this.colorOp); + var alphaLines = this.genCombinerLines(false, alphaVar, + passInputVar, texUnitID, + this.alphaCombiner, this.alphaSrc, this.alphaOp); + + // Generate scale, but avoid generating an identity op that multiplies by one. + var scaledColor = (this.colorScale == 1) ? colorVar : (colorVar + " * " + valToFloatLiteral(this.colorScale)); + var scaledAlpha = (this.alphaScale == 1) ? alphaVar : (alphaVar + " * " + valToFloatLiteral(this.alphaScale)); + + var line = [ + "vec4 " + passOutputVar, + " = ", + "vec4(", + scaledColor, + ", ", + scaledAlpha, + ")", + ";", + ].join(""); + return [].concat(colorLines, alphaLines, [line]); + } + } + + return abort_noSupport("Unsupported TexEnv mode: " + ptrToString(this.mode)); + } + + CTexEnv.prototype.genCombinerLines = function CTexEnv_getCombinerLines(isColor, outputVar, + passInputVar, texUnitID, + combiner, srcArr, opArr) + { + var argsNeeded = null; + switch (combiner) { + case GL_REPLACE: + argsNeeded = 1; + break; + + case GL_MODULATE: + case GL_ADD: + case GL_SUBTRACT: + argsNeeded = 2; + break; + + case GL_INTERPOLATE: + argsNeeded = 3; + break; + + default: + return abort_noSupport("Unsupported combiner: " + ptrToString(combiner)); + } + + var constantExpr = [ + "vec4(", + valToFloatLiteral(this.envColor[0]), + ", ", + valToFloatLiteral(this.envColor[1]), + ", ", + valToFloatLiteral(this.envColor[2]), + ", ", + valToFloatLiteral(this.envColor[3]), + ")", + ].join(""); + var src0Expr = (argsNeeded >= 1) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[0], opArr[0]) + : null; + var src1Expr = (argsNeeded >= 2) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[1], opArr[1]) + : null; + var src2Expr = (argsNeeded >= 3) ? genCombinerSourceExpr(texUnitID, constantExpr, passInputVar, srcArr[2], opArr[2]) + : null; + + var outputType = isColor ? "vec3" : "float"; + var lines = null; + switch (combiner) { + case GL_REPLACE: { + lines = [`${outputType} ${outputVar} = ${src0Expr};`] + break; + } + case GL_MODULATE: { + lines = [`${outputType} ${outputVar} = ${src0Expr} * ${src1Expr};`]; + break; + } + case GL_ADD: { + lines = [`${outputType} ${outputVar} = ${src0Expr} + ${src1Expr};`] + break; + } + case GL_SUBTRACT: { + lines = [`${outputType} ${outputVar} = ${src0Expr} - ${src1Expr};`] + break; + } + case GL_INTERPOLATE: { + var prefix = `${TEXENVJIT_NAMESPACE_PREFIX}env${texUnitID}_`; + var arg2Var = `${prefix}colorSrc2`; + var arg2Type = getTypeFromCombineOp(this.colorOp[2]); + + lines = [ + `${arg2Type} ${arg2Var} = ${src2Expr};`, + `${outputType} ${outputVar} = ${src0Expr} * ${arg2Var} + ${src1Expr} * (1.0 - ${arg2Var});`, + ]; + break; + } + + default: + return abort_sanity("Unmatched TexEnv.colorCombiner?"); + } + + return lines; + } + + return { + // Exports: + init: (gl, specifiedMaxTextureImageUnits) => { + var maxTexUnits = 0; + if (specifiedMaxTextureImageUnits) { + maxTexUnits = specifiedMaxTextureImageUnits; + } else if (gl) { + maxTexUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + } +#if ASSERTIONS + assert(maxTexUnits > 0); +#endif + s_texUnits = []; + for (var i = 0; i < maxTexUnits; i++) { + s_texUnits.push(new CTexUnit()); + } + }, + + setGLSLVars: (uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix) => { + TEX_UNIT_UNIFORM_PREFIX = uTexUnitPrefix; + TEX_COORD_VARYING_PREFIX = vTexCoordPrefix; + PRIM_COLOR_VARYING = vPrimColor; + TEX_MATRIX_UNIFORM_PREFIX = uTexMatrixPrefix; + }, + + genAllPassLines: (resultDest, indentSize = 0) => { + s_requiredTexUnitsForPass.length = 0; // Clear the list. + var lines = []; + var lastPassVar = PRIM_COLOR_VARYING; + for (var i = 0; i < s_texUnits.length; i++) { + if (!s_texUnits[i].enabled()) continue; + + s_requiredTexUnitsForPass.push(i); + + var prefix = TEXENVJIT_NAMESPACE_PREFIX + 'env' + i + "_"; + var passOutputVar = prefix + "result"; + + var newLines = s_texUnits[i].genPassLines(passOutputVar, lastPassVar, i); + lines = lines.concat(newLines, [""]); + + lastPassVar = passOutputVar; + } + lines.push(resultDest + " = " + lastPassVar + ";"); + + var indent = ""; + for (var i = 0; i < indentSize; i++) indent += " "; + + var output = indent + lines.join("\n" + indent); + + return output; + }, + + getUsedTexUnitList: () => s_requiredTexUnitsForPass, + + getActiveTexture: () => s_activeTexture, + + traverseState: (keyView) => { + for (var texUnit of s_texUnits) { + texUnit.traverseState(keyView); + } + }, + + getTexUnitType: (texUnitID) => { +#if ASSERTIONS + assert(texUnitID >= 0 && + texUnitID < s_texUnits.length); +#endif + return s_texUnits[texUnitID].getTexType(); + }, + + // Hooks: + hook_activeTexture: (texture) => { + s_activeTexture = texture - GL_TEXTURE0; + // Check if the current matrix mode is GL_TEXTURE. + if (GLImmediate.currentMatrix >= 2) { + // Switch to the corresponding texture matrix stack. + GLImmediate.currentMatrix = 2 + s_activeTexture; + } + }, + + hook_enable: (cap) => { + var cur = getCurTexUnit(); + switch (cap) { + case GL_TEXTURE_1D: + if (!cur.enabled_tex1D) { + GLImmediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. + cur.enabled_tex1D = true; + cur.texTypesEnabled |= 1; + } + break; + case GL_TEXTURE_2D: + if (!cur.enabled_tex2D) { + GLImmediate.currentRenderer = null; + cur.enabled_tex2D = true; + cur.texTypesEnabled |= 2; + } + break; + case GL_TEXTURE_3D: + if (!cur.enabled_tex3D) { + GLImmediate.currentRenderer = null; + cur.enabled_tex3D = true; + cur.texTypesEnabled |= 4; + } + break; + case GL_TEXTURE_CUBE_MAP: + if (!cur.enabled_texCube) { + GLImmediate.currentRenderer = null; + cur.enabled_texCube = true; + cur.texTypesEnabled |= 8; + } + break; + } + }, + + hook_disable: (cap) => { + var cur = getCurTexUnit(); + switch (cap) { + case GL_TEXTURE_1D: + if (cur.enabled_tex1D) { + GLImmediate.currentRenderer = null; // Renderer state changed, and must be recreated or looked up again. + cur.enabled_tex1D = false; + cur.texTypesEnabled &= ~1; + } + break; + case GL_TEXTURE_2D: + if (cur.enabled_tex2D) { + GLImmediate.currentRenderer = null; + cur.enabled_tex2D = false; + cur.texTypesEnabled &= ~2; + } + break; + case GL_TEXTURE_3D: + if (cur.enabled_tex3D) { + GLImmediate.currentRenderer = null; + cur.enabled_tex3D = false; + cur.texTypesEnabled &= ~4; + } + break; + case GL_TEXTURE_CUBE_MAP: + if (cur.enabled_texCube) { + GLImmediate.currentRenderer = null; + cur.enabled_texCube = false; + cur.texTypesEnabled &= ~8; + } + break; + } + }, + + hook_texEnvf(target, pname, param) { + if (target != GL_TEXTURE_ENV) + return; + + var env = getCurTexUnit().env; + switch (pname) { + case GL_RGB_SCALE: + if (env.colorScale != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.colorScale = param; + } + break; + case GL_ALPHA_SCALE: + if (env.alphaScale != param) { + env.invalidateKey(); + env.alphaScale = param; + } + break; + + default: + err('WARNING: Unhandled `pname` in call to `glTexEnvf`.'); + } + }, + + hook_texEnvi(target, pname, param) { + if (target != GL_TEXTURE_ENV) + return; + + var env = getCurTexUnit().env; + switch (pname) { + case GL_TEXTURE_ENV_MODE: + if (env.mode != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.mode = param; + } + break; + + case GL_COMBINE_RGB: + if (env.colorCombiner != param) { + env.invalidateKey(); + env.colorCombiner = param; + } + break; + case GL_COMBINE_ALPHA: + if (env.alphaCombiner != param) { + env.invalidateKey(); + env.alphaCombiner = param; + } + break; + + case GL_SRC0_RGB: + if (env.colorSrc[0] != param) { + env.invalidateKey(); + env.colorSrc[0] = param; + } + break; + case GL_SRC1_RGB: + if (env.colorSrc[1] != param) { + env.invalidateKey(); + env.colorSrc[1] = param; + } + break; + case GL_SRC2_RGB: + if (env.colorSrc[2] != param) { + env.invalidateKey(); + env.colorSrc[2] = param; + } + break; + + case GL_SRC0_ALPHA: + if (env.alphaSrc[0] != param) { + env.invalidateKey(); + env.alphaSrc[0] = param; + } + break; + case GL_SRC1_ALPHA: + if (env.alphaSrc[1] != param) { + env.invalidateKey(); + env.alphaSrc[1] = param; + } + break; + case GL_SRC2_ALPHA: + if (env.alphaSrc[2] != param) { + env.invalidateKey(); + env.alphaSrc[2] = param; + } + break; + + case GL_OPERAND0_RGB: + if (env.colorOp[0] != param) { + env.invalidateKey(); + env.colorOp[0] = param; + } + break; + case GL_OPERAND1_RGB: + if (env.colorOp[1] != param) { + env.invalidateKey(); + env.colorOp[1] = param; + } + break; + case GL_OPERAND2_RGB: + if (env.colorOp[2] != param) { + env.invalidateKey(); + env.colorOp[2] = param; + } + break; + + case GL_OPERAND0_ALPHA: + if (env.alphaOp[0] != param) { + env.invalidateKey(); + env.alphaOp[0] = param; + } + break; + case GL_OPERAND1_ALPHA: + if (env.alphaOp[1] != param) { + env.invalidateKey(); + env.alphaOp[1] = param; + } + break; + case GL_OPERAND2_ALPHA: + if (env.alphaOp[2] != param) { + env.invalidateKey(); + env.alphaOp[2] = param; + } + break; + + case GL_RGB_SCALE: + if (env.colorScale != param) { + env.invalidateKey(); + env.colorScale = param; + } + break; + case GL_ALPHA_SCALE: + if (env.alphaScale != param) { + env.invalidateKey(); + env.alphaScale = param; + } + break; + + default: + err('WARNING: Unhandled `pname` in call to `glTexEnvi`.'); + } + }, + + hook_texEnvfv(target, pname, params) { + if (target != GL_TEXTURE_ENV) return; + + var env = getCurTexUnit().env; + switch (pname) { + case GL_TEXTURE_ENV_COLOR: { + for (var i = 0; i < 4; i++) { + var param = {{{ makeGetValue('params', 'i*4', 'float') }}}; + if (env.envColor[i] != param) { + env.invalidateKey(); // We changed FFP emulation renderer state. + env.envColor[i] = param; + } + } + break + } + default: + err('WARNING: Unhandled `pname` in call to `glTexEnvfv`.'); + } + }, + + hook_getTexEnviv(target, pname, param) { + if (target != GL_TEXTURE_ENV) + return; + + var env = getCurTexUnit().env; + switch (pname) { + case GL_TEXTURE_ENV_MODE: + {{{ makeSetValue('param', '0', 'env.mode', 'i32') }}}; + return; + + case GL_TEXTURE_ENV_COLOR: + {{{ makeSetValue('param', '0', 'Math.max(Math.min(env.envColor[0]*255, 255, -255))', 'i32') }}}; + {{{ makeSetValue('param', '1', 'Math.max(Math.min(env.envColor[1]*255, 255, -255))', 'i32') }}}; + {{{ makeSetValue('param', '2', 'Math.max(Math.min(env.envColor[2]*255, 255, -255))', 'i32') }}}; + {{{ makeSetValue('param', '3', 'Math.max(Math.min(env.envColor[3]*255, 255, -255))', 'i32') }}}; + return; + + case GL_COMBINE_RGB: + {{{ makeSetValue('param', '0', 'env.colorCombiner', 'i32') }}}; + return; + + case GL_COMBINE_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaCombiner', 'i32') }}}; + return; + + case GL_SRC0_RGB: + {{{ makeSetValue('param', '0', 'env.colorSrc[0]', 'i32') }}}; + return; + + case GL_SRC1_RGB: + {{{ makeSetValue('param', '0', 'env.colorSrc[1]', 'i32') }}}; + return; + + case GL_SRC2_RGB: + {{{ makeSetValue('param', '0', 'env.colorSrc[2]', 'i32') }}}; + return; + + case GL_SRC0_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaSrc[0]', 'i32') }}}; + return; + + case GL_SRC1_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaSrc[1]', 'i32') }}}; + return; + + case GL_SRC2_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaSrc[2]', 'i32') }}}; + return; + + case GL_OPERAND0_RGB: + {{{ makeSetValue('param', '0', 'env.colorOp[0]', 'i32') }}}; + return; + + case GL_OPERAND1_RGB: + {{{ makeSetValue('param', '0', 'env.colorOp[1]', 'i32') }}}; + return; + + case GL_OPERAND2_RGB: + {{{ makeSetValue('param', '0', 'env.colorOp[2]', 'i32') }}}; + return; + + case GL_OPERAND0_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaOp[0]', 'i32') }}}; + return; + + case GL_OPERAND1_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaOp[1]', 'i32') }}}; + return; + + case GL_OPERAND2_ALPHA: + {{{ makeSetValue('param', '0', 'env.alphaOp[2]', 'i32') }}}; + return; + + case GL_RGB_SCALE: + {{{ makeSetValue('param', '0', 'env.colorScale', 'i32') }}}; + return; + + case GL_ALPHA_SCALE: + {{{ makeSetValue('param', '0', 'env.alphaScale', 'i32') }}}; + return; + + default: + err('WARNING: Unhandled `pname` in call to `glGetTexEnvi`.'); + } + }, + + hook_getTexEnvfv: (target, pname, param) => { + if (target != GL_TEXTURE_ENV) + return; + + var env = getCurTexUnit().env; + switch (pname) { + case GL_TEXTURE_ENV_COLOR: + {{{ makeSetValue('param', '0', 'env.envColor[0]', 'float') }}}; + {{{ makeSetValue('param', '4', 'env.envColor[1]', 'float') }}}; + {{{ makeSetValue('param', '8', 'env.envColor[2]', 'float') }}}; + {{{ makeSetValue('param', '12', 'env.envColor[3]', 'float') }}}; + return; + } + } + }; + }, + + // Vertex and index data + vertexData: null, // current vertex data. either tempData (glBegin etc.) or a view into the heap (gl*Pointer). Default view is F32 + vertexDataU8: null, // U8 view + tempData: null, + indexData: null, + vertexCounter: 0, + mode: -1, + + rendererCache: null, + rendererComponents: [], // small cache for calls inside glBegin/end. counts how many times the element was seen + rendererComponentPointer: 0, // next place to start a glBegin/end component + lastRenderer: null, // used to avoid cleaning up and re-preparing the same renderer + lastArrayBuffer: null, // used in conjunction with lastRenderer + lastProgram: null, // "" + lastStride: -1, // "" + + // The following data structures are used for OpenGL Immediate Mode matrix routines. + matrix: [], + matrixStack: [], + currentMatrix: 0, // 0: modelview, 1: projection, 2+i, texture matrix i. + tempMatrix: null, + matricesModified: false, + useTextureMatrix: false, + + // Clientside attributes + VERTEX: 0, + NORMAL: 1, + COLOR: 2, + TEXTURE0: 3, + NUM_ATTRIBUTES: -1, // Initialized in GL emulation init(). + MAX_TEXTURES: -1, // Initialized in GL emulation init(). + + totalEnabledClientAttributes: 0, + enabledClientAttributes: [0, 0], + clientAttributes: [], // raw data, including possible unneeded ones + liveClientAttributes: [], // the ones actually alive in the current computation, sorted + currentRenderer: null, // Caches the currently active FFP emulation renderer, so that it does not have to be re-looked up unless relevant state changes. + modifiedClientAttributes: false, + clientActiveTexture: 0, + clientColor: null, + usedTexUnitList: [], + fixedFunctionProgram: null, + + setClientAttribute(name, size, type, stride, pointer) { + var attrib = GLImmediate.clientAttributes[name]; + if (!attrib) { + for (var i = 0; i <= name; i++) { // keep flat + GLImmediate.clientAttributes[i] ||= { + name, + size, + type, + stride, + pointer, + offset: 0 + }; + } + } else { + attrib.name = name; + attrib.size = size; + attrib.type = type; + attrib.stride = stride; + attrib.pointer = pointer; + attrib.offset = 0; + } + GLImmediate.modifiedClientAttributes = true; + }, + + // Renderers + addRendererComponent(name, size, type) { + if (!GLImmediate.rendererComponents[name]) { + GLImmediate.rendererComponents[name] = 1; +#if ASSERTIONS + if (GLImmediate.enabledClientAttributes[name]) { + warnOnce("Warning: glTexCoord used after EnableClientState for TEXTURE_COORD_ARRAY for TEXTURE0. Disabling TEXTURE_COORD_ARRAY..."); + } +#endif + GLImmediate.enabledClientAttributes[name] = true; + GLImmediate.setClientAttribute(name, size, type, 0, GLImmediate.rendererComponentPointer); + GLImmediate.rendererComponentPointer += size * GL.byteSizeByType[type - GL.byteSizeByTypeRoot]; +#if GL_FFP_ONLY + // We can enable the correct attribute stream index immediately here, since the same attribute in each shader + // will be bound to this same index. + GL.enableVertexAttribArray(name); +#endif + } else { + GLImmediate.rendererComponents[name]++; + } + }, + + disableBeginEndClientAttributes() { + for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { + if (GLImmediate.rendererComponents[i]) GLImmediate.enabledClientAttributes[i] = false; + } + }, + + getRenderer() { + // If no FFP state has changed that would have forced to re-evaluate which FFP emulation shader to use, + // we have the currently used renderer in cache, and can immediately return that. + if (GLImmediate.currentRenderer) { + return GLImmediate.currentRenderer; + } + // return a renderer object given the liveClientAttributes + // we maintain a cache of renderers, optimized to not generate garbage + var attributes = GLImmediate.liveClientAttributes; + var cacheMap = GLImmediate.rendererCache; + var keyView = cacheMap.getStaticKeyView().reset(); + + // By attrib state: + var enabledAttributesKey = 0; + for (var attr of attributes) { + enabledAttributesKey |= 1 << attr.name; + } + + // To prevent using more than 31 bits add another level to the maptree + // and reset the enabledAttributesKey for the next glemulation state bits + keyView.next(enabledAttributesKey); + enabledAttributesKey = 0; + + // By fog state: + var fogParam = 0; + if (GLEmulation.fogEnabled) { + switch (GLEmulation.fogMode) { + case 0x801: // GL_EXP2 + fogParam = 1; + break; + case 0x2601: // GL_LINEAR + fogParam = 2; + break; + default: // default to GL_EXP + fogParam = 3; + break; + } + } + enabledAttributesKey = (enabledAttributesKey << 2) | fogParam; + + // By clip plane mode + for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { + enabledAttributesKey = (enabledAttributesKey << 1) | GLEmulation.clipPlaneEnabled[clipPlaneId]; + } + + // By lighting mode and enabled lights + enabledAttributesKey = (enabledAttributesKey << 1) | GLEmulation.lightingEnabled; + for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { + enabledAttributesKey = (enabledAttributesKey << 1) | (GLEmulation.lightingEnabled ? GLEmulation.lightEnabled[lightId] : 0); + } + + // By alpha testing mode + enabledAttributesKey = (enabledAttributesKey << 3) | (GLEmulation.alphaTestEnabled ? (GLEmulation.alphaTestFunc - 0x200) : 0x7); + + // By drawing mode: + enabledAttributesKey = (enabledAttributesKey << 1) | (GLImmediate.mode == GLctx.POINTS ? 1 : 0); + + keyView.next(enabledAttributesKey); + +#if !GL_FFP_ONLY + // By cur program: + keyView.next(GL.currProgram); + if (!GL.currProgram) { +#endif + GLImmediate.TexEnvJIT.traverseState(keyView); +#if !GL_FFP_ONLY + } +#endif + + // If we don't already have it, create it. + var renderer = keyView.get(); + if (!renderer) { +#if GL_DEBUG + dbg(`generating renderer for ${JSON.stringify(attributes)}`); +#endif + renderer = GLImmediate.createRenderer(); + GLImmediate.currentRenderer = renderer; + keyView.set(renderer); + return renderer; + } + GLImmediate.currentRenderer = renderer; // Cache the currently used renderer, so later lookups without state changes can get this fast. + return renderer; + }, + + createRenderer(renderer) { + var useCurrProgram = !!GL.currProgram; + var hasTextures = false; + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + var texAttribName = GLImmediate.TEXTURE0 + i; + if (!GLImmediate.enabledClientAttributes[texAttribName]) + continue; + +#if ASSERTIONS + if (!useCurrProgram) { + if (GLImmediate.TexEnvJIT.getTexUnitType(i) == 0) { + warnOnce("GL_TEXTURE" + i + " coords are supplied, but that texture unit is disabled in the fixed-function pipeline."); + } + } +#endif + + hasTextures = true; + } + + /** @constructor */ + function Renderer() { + this.init = function() { + // For fixed-function shader generation. + var uTexUnitPrefix = 'u_texUnit'; + var aTexCoordPrefix = 'a_texCoord'; + var vTexCoordPrefix = 'v_texCoord'; + var vPrimColor = 'v_color'; + var uTexMatrixPrefix = GLImmediate.useTextureMatrix ? 'u_textureMatrix' : null; + + if (useCurrProgram) { + if (GL.shaderInfos[GL.programShaders[GL.currProgram][0]].type == GLctx.VERTEX_SHADER) { + this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][0]]; + this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][1]]; + } else { + this.vertexShader = GL.shaders[GL.programShaders[GL.currProgram][1]]; + this.fragmentShader = GL.shaders[GL.programShaders[GL.currProgram][0]]; + } + this.program = GL.programs[GL.currProgram]; + this.usedTexUnitList = []; + } else { + // IMPORTANT NOTE: If you parameterize the shader source based on any runtime values + // in order to create the least expensive shader possible based on the features being + // used, you should also update the code in the beginning of getRenderer to make sure + // that you cache the renderer based on the said parameters. + if (GLEmulation.fogEnabled) { + switch (GLEmulation.fogMode) { + case 0x801: // GL_EXP2 + // fog = exp(-(gl_Fog.density * gl_FogFragCoord)^2) + var fogFormula = ' float fog = exp(-u_fogDensity * u_fogDensity * ecDistance * ecDistance); \n'; + break; + case 0x2601: // GL_LINEAR + // fog = (gl_Fog.end - gl_FogFragCoord) * gl_fog.scale + var fogFormula = ' float fog = (u_fogEnd - ecDistance) * u_fogScale; \n'; + break; + default: // default to GL_EXP + // fog = exp(-gl_Fog.density * gl_FogFragCoord) + var fogFormula = ' float fog = exp(-u_fogDensity * ecDistance); \n'; + break; + } + } + + GLImmediate.TexEnvJIT.setGLSLVars(uTexUnitPrefix, vTexCoordPrefix, vPrimColor, uTexMatrixPrefix); + var fsTexEnvPass = GLImmediate.TexEnvJIT.genAllPassLines('gl_FragColor', 2); + + var texUnitAttribList = ''; + var texUnitVaryingList = ''; + var texUnitUniformList = ''; + var vsTexCoordInits = ''; + this.usedTexUnitList = GLImmediate.TexEnvJIT.getUsedTexUnitList(); + for (var texUnit of this.usedTexUnitList) { + texUnitAttribList += 'attribute vec4 ' + aTexCoordPrefix + texUnit + ';\n'; + texUnitVaryingList += 'varying vec4 ' + vTexCoordPrefix + texUnit + ';\n'; + texUnitUniformList += 'uniform sampler2D ' + uTexUnitPrefix + texUnit + ';\n'; + vsTexCoordInits += ' ' + vTexCoordPrefix + texUnit + ' = ' + aTexCoordPrefix + texUnit + ';\n'; + + if (GLImmediate.useTextureMatrix) { + texUnitUniformList += 'uniform mat4 ' + uTexMatrixPrefix + texUnit + ';\n'; + } + } + + var vsFogVaryingInit = null; + if (GLEmulation.fogEnabled) { + vsFogVaryingInit = ' v_fogFragCoord = abs(ecPosition.z);\n'; + } + + var vsPointSizeDefs = null; + var vsPointSizeInit = null; + if (GLImmediate.mode == GLctx.POINTS) { + vsPointSizeDefs = 'uniform float u_pointSize;\n'; + vsPointSizeInit = ' gl_PointSize = u_pointSize;\n'; + } + + var vsClipPlaneDefs = ''; + var vsClipPlaneInit = ''; + var fsClipPlaneDefs = ''; + var fsClipPlanePass = ''; + for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { + if (GLEmulation.clipPlaneEnabled[clipPlaneId]) { + vsClipPlaneDefs += 'uniform vec4 u_clipPlaneEquation' + clipPlaneId + ';'; + vsClipPlaneDefs += 'varying float v_clipDistance' + clipPlaneId + ';'; + vsClipPlaneInit += ' v_clipDistance' + clipPlaneId + ' = dot(ecPosition, u_clipPlaneEquation' + clipPlaneId + ');'; + fsClipPlaneDefs += 'varying float v_clipDistance' + clipPlaneId + ';'; + fsClipPlanePass += ' if (v_clipDistance' + clipPlaneId + ' < 0.0) discard;'; + } + } + + var vsLightingDefs = ''; + var vsLightingPass = ''; + if (GLEmulation.lightingEnabled) { + vsLightingDefs += 'attribute vec3 a_normal;'; + vsLightingDefs += 'uniform mat3 u_normalMatrix;'; + vsLightingDefs += 'uniform vec4 u_lightModelAmbient;'; + vsLightingDefs += 'uniform vec4 u_materialAmbient;'; + vsLightingDefs += 'uniform vec4 u_materialDiffuse;'; + vsLightingDefs += 'uniform vec4 u_materialSpecular;'; + vsLightingDefs += 'uniform float u_materialShininess;'; + vsLightingDefs += 'uniform vec4 u_materialEmission;'; + + vsLightingPass += ' vec3 ecNormal = normalize(u_normalMatrix * a_normal);'; + vsLightingPass += ' v_color.w = u_materialDiffuse.w;'; + vsLightingPass += ' v_color.xyz = u_materialEmission.xyz;'; + vsLightingPass += ' v_color.xyz += u_lightModelAmbient.xyz * u_materialAmbient.xyz;'; + + for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { + if (GLEmulation.lightEnabled[lightId]) { + vsLightingDefs += 'uniform vec4 u_lightAmbient' + lightId + ';'; + vsLightingDefs += 'uniform vec4 u_lightDiffuse' + lightId + ';'; + vsLightingDefs += 'uniform vec4 u_lightSpecular' + lightId + ';'; + vsLightingDefs += 'uniform vec4 u_lightPosition' + lightId + ';'; + + vsLightingPass += ' {'; + vsLightingPass += ' vec3 lightDirection = normalize(u_lightPosition' + lightId + ').xyz;'; + vsLightingPass += ' vec3 halfVector = normalize(lightDirection + vec3(0,0,1));'; + vsLightingPass += ' vec3 ambient = u_lightAmbient' + lightId + '.xyz * u_materialAmbient.xyz;'; + vsLightingPass += ' float diffuseI = max(dot(ecNormal, lightDirection), 0.0);'; + vsLightingPass += ' float specularI = max(dot(ecNormal, halfVector), 0.0);'; + vsLightingPass += ' vec3 diffuse = diffuseI * u_lightDiffuse' + lightId + '.xyz * u_materialDiffuse.xyz;'; + vsLightingPass += ' specularI = (diffuseI > 0.0 && specularI > 0.0) ? exp(u_materialShininess * log(specularI)) : 0.0;'; + vsLightingPass += ' vec3 specular = specularI * u_lightSpecular' + lightId + '.xyz * u_materialSpecular.xyz;'; + vsLightingPass += ' v_color.xyz += ambient + diffuse + specular;'; + vsLightingPass += ' }'; + } + } + vsLightingPass += ' v_color = clamp(v_color, 0.0, 1.0);'; + } + + var vsSource = [ + 'attribute vec4 a_position;', + 'attribute vec4 a_color;', + 'varying vec4 v_color;', + texUnitAttribList, + texUnitVaryingList, + (GLEmulation.fogEnabled ? 'varying float v_fogFragCoord;' : null), + 'uniform mat4 u_modelView;', + 'uniform mat4 u_projection;', + vsPointSizeDefs, + vsClipPlaneDefs, + vsLightingDefs, + 'void main()', + '{', + ' vec4 ecPosition = u_modelView * a_position;', // eye-coordinate position + ' gl_Position = u_projection * ecPosition;', + ' v_color = a_color;', + vsTexCoordInits, + vsFogVaryingInit, + vsPointSizeInit, + vsClipPlaneInit, + vsLightingPass, + '}', + '' + ].join('\n').replace(/\n\n+/g, '\n'); + + this.vertexShader = GLctx.createShader(GLctx.VERTEX_SHADER); + GLctx.shaderSource(this.vertexShader, vsSource); + GLctx.compileShader(this.vertexShader); + + var fogHeaderIfNeeded = null; + if (GLEmulation.fogEnabled) { + fogHeaderIfNeeded = [ + '', + 'varying float v_fogFragCoord; ', + 'uniform vec4 u_fogColor; ', + 'uniform float u_fogEnd; ', + 'uniform float u_fogScale; ', + 'uniform float u_fogDensity; ', + 'float ffog(in float ecDistance) { ', + fogFormula, + ' fog = clamp(fog, 0.0, 1.0); ', + ' return fog; ', + '}', + '', + ].join("\n"); + } + + var fogPass = null; + if (GLEmulation.fogEnabled) { + fogPass = 'gl_FragColor = vec4(mix(u_fogColor.rgb, gl_FragColor.rgb, ffog(v_fogFragCoord)), gl_FragColor.a);\n'; + } + + var fsAlphaTestDefs = ''; + var fsAlphaTestPass = ''; + if (GLEmulation.alphaTestEnabled) { + fsAlphaTestDefs = 'uniform float u_alphaTestRef;'; + switch (GLEmulation.alphaTestFunc) { + case 0x200: // GL_NEVER + fsAlphaTestPass = 'discard;'; + break; + case 0x201: // GL_LESS + fsAlphaTestPass = 'if (!(gl_FragColor.a < u_alphaTestRef)) { discard; }'; + break; + case 0x202: // GL_EQUAL + fsAlphaTestPass = 'if (!(gl_FragColor.a == u_alphaTestRef)) { discard; }'; + break; + case 0x203: // GL_LEQUAL + fsAlphaTestPass = 'if (!(gl_FragColor.a <= u_alphaTestRef)) { discard; }'; + break; + case 0x204: // GL_GREATER + fsAlphaTestPass = 'if (!(gl_FragColor.a > u_alphaTestRef)) { discard; }'; + break; + case 0x205: // GL_NOTEQUAL + fsAlphaTestPass = 'if (!(gl_FragColor.a != u_alphaTestRef)) { discard; }'; + break; + case 0x206: // GL_GEQUAL + fsAlphaTestPass = 'if (!(gl_FragColor.a >= u_alphaTestRef)) { discard; }'; + break; + case 0x207: // GL_ALWAYS + fsAlphaTestPass = ''; + break; + } + } + + var fsSource = [ + 'precision mediump float;', + texUnitVaryingList, + texUnitUniformList, + 'varying vec4 v_color;', + fogHeaderIfNeeded, + fsClipPlaneDefs, + fsAlphaTestDefs, + 'void main()', + '{', + fsClipPlanePass, + fsTexEnvPass, + fogPass, + fsAlphaTestPass, + '}', + '' + ].join("\n").replace(/\n\n+/g, '\n'); + + this.fragmentShader = GLctx.createShader(GLctx.FRAGMENT_SHADER); + GLctx.shaderSource(this.fragmentShader, fsSource); + GLctx.compileShader(this.fragmentShader); + + this.program = GLctx.createProgram(); + GLctx.attachShader(this.program, this.vertexShader); + GLctx.attachShader(this.program, this.fragmentShader); + + // As optimization, bind all attributes to prespecified locations, so that the FFP emulation + // code can submit attributes to any generated FFP shader without having to examine each shader in turn. + // These prespecified locations are only assumed if GL_FFP_ONLY is specified, since user could also create their + // own shaders that didn't have attributes in the same locations. + GLctx.bindAttribLocation(this.program, GLImmediate.VERTEX, 'a_position'); + GLctx.bindAttribLocation(this.program, GLImmediate.COLOR, 'a_color'); + GLctx.bindAttribLocation(this.program, GLImmediate.NORMAL, 'a_normal'); + var maxVertexAttribs = GLctx.getParameter(GLctx.MAX_VERTEX_ATTRIBS); + for (var i = 0; i < GLImmediate.MAX_TEXTURES && GLImmediate.TEXTURE0 + i < maxVertexAttribs; i++) { + GLctx.bindAttribLocation(this.program, GLImmediate.TEXTURE0 + i, 'a_texCoord'+i); + GLctx.bindAttribLocation(this.program, GLImmediate.TEXTURE0 + i, aTexCoordPrefix+i); + } + GLctx.linkProgram(this.program); + } + + // Stores an array that remembers which matrix uniforms are up-to-date in this FFP renderer, so they don't need to be resubmitted + // each time we render with this program. + this.textureMatrixVersion = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + + this.positionLocation = GLctx.getAttribLocation(this.program, 'a_position'); + + this.texCoordLocations = []; + + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + if (!GLImmediate.enabledClientAttributes[GLImmediate.TEXTURE0 + i]) { + this.texCoordLocations[i] = -1; + continue; + } + + if (useCurrProgram) { + this.texCoordLocations[i] = GLctx.getAttribLocation(this.program, `a_texCoord${i}`); + } else { + this.texCoordLocations[i] = GLctx.getAttribLocation(this.program, aTexCoordPrefix + i); + } + } + this.colorLocation = GLctx.getAttribLocation(this.program, 'a_color'); + if (!useCurrProgram) { + // Temporarily switch to the program so we can set our sampler uniforms early. + var prevBoundProg = GLctx.getParameter(GLctx.CURRENT_PROGRAM); + GLctx.useProgram(this.program); + { + for (var i = 0; i < this.usedTexUnitList.length; i++) { + var texUnitID = this.usedTexUnitList[i]; + var texSamplerLoc = GLctx.getUniformLocation(this.program, uTexUnitPrefix + texUnitID); + GLctx.uniform1i(texSamplerLoc, texUnitID); + } + } + // The default color attribute value is not the same as the default for all other attribute streams (0,0,0,1) but (1,1,1,1), + // so explicitly set it right at start. + GLctx.vertexAttrib4fv(this.colorLocation, [1,1,1,1]); + GLctx.useProgram(prevBoundProg); + } + + this.textureMatrixLocations = []; + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + this.textureMatrixLocations[i] = GLctx.getUniformLocation(this.program, `u_textureMatrix${i}`); + } + this.normalLocation = GLctx.getAttribLocation(this.program, 'a_normal'); + + this.modelViewLocation = GLctx.getUniformLocation(this.program, 'u_modelView'); + this.projectionLocation = GLctx.getUniformLocation(this.program, 'u_projection'); + this.normalMatrixLocation = GLctx.getUniformLocation(this.program, 'u_normalMatrix'); + + this.hasTextures = hasTextures; + this.hasNormal = GLImmediate.enabledClientAttributes[GLImmediate.NORMAL] && + GLImmediate.clientAttributes[GLImmediate.NORMAL].size > 0 && + this.normalLocation >= 0; + this.hasColor = (this.colorLocation === 0) || this.colorLocation > 0; + + this.floatType = GLctx.FLOAT; // minor optimization + + this.fogColorLocation = GLctx.getUniformLocation(this.program, 'u_fogColor'); + this.fogEndLocation = GLctx.getUniformLocation(this.program, 'u_fogEnd'); + this.fogScaleLocation = GLctx.getUniformLocation(this.program, 'u_fogScale'); + this.fogDensityLocation = GLctx.getUniformLocation(this.program, 'u_fogDensity'); + this.hasFog = !!(this.fogColorLocation || this.fogEndLocation || + this.fogScaleLocation || this.fogDensityLocation); + + this.pointSizeLocation = GLctx.getUniformLocation(this.program, 'u_pointSize'); + + this.hasClipPlane = false; + this.clipPlaneEquationLocation = []; + for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { + this.clipPlaneEquationLocation[clipPlaneId] = GLctx.getUniformLocation(this.program, `u_clipPlaneEquation${clipPlaneId}`); + this.hasClipPlane = (this.hasClipPlane || this.clipPlaneEquationLocation[clipPlaneId]); + } + + this.hasLighting = GLEmulation.lightingEnabled; + this.lightModelAmbientLocation = GLctx.getUniformLocation(this.program, 'u_lightModelAmbient'); + this.materialAmbientLocation = GLctx.getUniformLocation(this.program, 'u_materialAmbient'); + this.materialDiffuseLocation = GLctx.getUniformLocation(this.program, 'u_materialDiffuse'); + this.materialSpecularLocation = GLctx.getUniformLocation(this.program, 'u_materialSpecular'); + this.materialShininessLocation = GLctx.getUniformLocation(this.program, 'u_materialShininess'); + this.materialEmissionLocation = GLctx.getUniformLocation(this.program, 'u_materialEmission'); + this.lightAmbientLocation = [] + this.lightDiffuseLocation = [] + this.lightSpecularLocation = [] + this.lightPositionLocation = [] + for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { + this.lightAmbientLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightAmbient${lightId}`); + this.lightDiffuseLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightDiffuse${lightId}`); + this.lightSpecularLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightSpecular${lightId}`); + this.lightPositionLocation[lightId] = GLctx.getUniformLocation(this.program, `u_lightPosition${lightId}`); + } + + this.hasAlphaTest = GLEmulation.alphaTestEnabled; + this.alphaTestRefLocation = GLctx.getUniformLocation(this.program, 'u_alphaTestRef'); + + }; + + this.prepare = function() { + // Calculate the array buffer + var arrayBuffer; + if (!GLctx.currentArrayBufferBinding) { + var start = GLImmediate.firstVertex*GLImmediate.stride; + var end = GLImmediate.lastVertex*GLImmediate.stride; +#if ASSERTIONS + assert(end <= GL.MAX_TEMP_BUFFER_SIZE, 'too much vertex data'); +#endif + arrayBuffer = GL.getTempVertexBuffer(end); + // TODO: consider using the last buffer we bound, if it was larger. downside is larger buffer, but we might avoid rebinding and preparing + } else { + arrayBuffer = GLctx.currentArrayBufferBinding; + } + +#if GL_UNSAFE_OPTS + // If the array buffer is unchanged and the renderer as well, then we can avoid all the work here + // XXX We use some heuristics here, and this may not work in all cases. Try disabling GL_UNSAFE_OPTS if you + // have odd glitches + var lastRenderer = GLImmediate.lastRenderer; + var canSkip = this == lastRenderer && + arrayBuffer == GLImmediate.lastArrayBuffer && + (GL.currProgram || this.program) == GLImmediate.lastProgram && + GLImmediate.stride == GLImmediate.lastStride && + !GLImmediate.matricesModified; + if (!canSkip && lastRenderer) lastRenderer.cleanup(); +#endif + if (!GLctx.currentArrayBufferBinding) { + // Bind the array buffer and upload data after cleaning up the previous renderer + + if (arrayBuffer != GLImmediate.lastArrayBuffer) { + GLctx.bindBuffer(GLctx.ARRAY_BUFFER, arrayBuffer); + GLImmediate.lastArrayBuffer = arrayBuffer; + } + + GLctx.bufferSubData(GLctx.ARRAY_BUFFER, start, GLImmediate.vertexData.subarray(start >> 2, end >> 2)); + } +#if GL_UNSAFE_OPTS + if (canSkip) return; + GLImmediate.lastRenderer = this; + GLImmediate.lastProgram = GL.currProgram || this.program; + GLImmediate.lastStride = GLImmediate.stride; + GLImmediate.matricesModified = false; +#endif + + if (!GL.currProgram) { + if (GLImmediate.fixedFunctionProgram != this.program) { + GLctx.useProgram(this.program); + GLImmediate.fixedFunctionProgram = this.program; + } + } + + if (this.modelViewLocation && this.modelViewMatrixVersion != GLImmediate.matrixVersion[0/*m*/]) { + this.modelViewMatrixVersion = GLImmediate.matrixVersion[0/*m*/]; + GLctx.uniformMatrix4fv(this.modelViewLocation, false, GLImmediate.matrix[0/*m*/]); + + // set normal matrix to the upper 3x3 of the inverse transposed current modelview matrix + if (GLEmulation.lightEnabled) { + var tmpMVinv = GLImmediate.matrixLib.mat4.create(GLImmediate.matrix[0]); + GLImmediate.matrixLib.mat4.inverse(tmpMVinv); + GLImmediate.matrixLib.mat4.transpose(tmpMVinv); + GLctx.uniformMatrix3fv(this.normalMatrixLocation, false, GLImmediate.matrixLib.mat4.toMat3(tmpMVinv)); + } + } + if (this.projectionLocation && this.projectionMatrixVersion != GLImmediate.matrixVersion[1/*p*/]) { + this.projectionMatrixVersion = GLImmediate.matrixVersion[1/*p*/]; + GLctx.uniformMatrix4fv(this.projectionLocation, false, GLImmediate.matrix[1/*p*/]); + } + + var clientAttributes = GLImmediate.clientAttributes; + var posAttr = clientAttributes[GLImmediate.VERTEX]; + +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(posAttr.size, posAttr.type, GLImmediate.stride, clientAttributes[GLImmediate.VERTEX].offset); +#endif + +#if GL_FFP_ONLY + if (!GLctx.currentArrayBufferBinding) { + GLctx.vertexAttribPointer(GLImmediate.VERTEX, posAttr.size, posAttr.type, false, GLImmediate.stride, posAttr.offset); + if (this.hasNormal) { + var normalAttr = clientAttributes[GLImmediate.NORMAL]; + GLctx.vertexAttribPointer(GLImmediate.NORMAL, normalAttr.size, normalAttr.type, true, GLImmediate.stride, normalAttr.offset); + } + } +#else + GLctx.vertexAttribPointer(this.positionLocation, posAttr.size, posAttr.type, false, GLImmediate.stride, posAttr.offset); + GLctx.enableVertexAttribArray(this.positionLocation); + if (this.hasNormal) { + var normalAttr = clientAttributes[GLImmediate.NORMAL]; +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(normalAttr.size, normalAttr.type, GLImmediate.stride, normalAttr.offset); +#endif + GLctx.vertexAttribPointer(this.normalLocation, normalAttr.size, normalAttr.type, true, GLImmediate.stride, normalAttr.offset); + GLctx.enableVertexAttribArray(this.normalLocation); + } +#endif + if (this.hasTextures) { + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { +#if GL_FFP_ONLY + if (!GLctx.currentArrayBufferBinding) { + var attribLoc = GLImmediate.TEXTURE0+i; + var texAttr = clientAttributes[attribLoc]; + if (texAttr.size) { + GLctx.vertexAttribPointer(attribLoc, texAttr.size, texAttr.type, false, GLImmediate.stride, texAttr.offset); + } else { + // These two might be dangerous, but let's try them. + GLctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1); + } + } +#else + var attribLoc = this.texCoordLocations[i]; + if (attribLoc === undefined || attribLoc < 0) continue; + var texAttr = clientAttributes[GLImmediate.TEXTURE0+i]; + + if (texAttr.size) { +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(texAttr.size, texAttr.type, GLImmediate.stride, texAttr.offset); +#endif + GLctx.vertexAttribPointer(attribLoc, texAttr.size, texAttr.type, false, GLImmediate.stride, texAttr.offset); + GLctx.enableVertexAttribArray(attribLoc); + } else { + // These two might be dangerous, but let's try them. + GLctx.vertexAttrib4f(attribLoc, 0, 0, 0, 1); + GLctx.disableVertexAttribArray(attribLoc); + } +#endif + var t = 2/*t*/+i; + if (this.textureMatrixLocations[i] && this.textureMatrixVersion[t] != GLImmediate.matrixVersion[t]) { // XXX might we need this even without the condition we are currently in? + this.textureMatrixVersion[t] = GLImmediate.matrixVersion[t]; + GLctx.uniformMatrix4fv(this.textureMatrixLocations[i], false, GLImmediate.matrix[t]); + } + } + } + if (GLImmediate.enabledClientAttributes[GLImmediate.COLOR]) { + var colorAttr = clientAttributes[GLImmediate.COLOR]; +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(colorAttr.size, colorAttr.type, GLImmediate.stride, colorAttr.offset); +#endif +#if GL_FFP_ONLY + if (!GLctx.currentArrayBufferBinding) { + GLctx.vertexAttribPointer(GLImmediate.COLOR, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); + } +#else + GLctx.vertexAttribPointer(this.colorLocation, colorAttr.size, colorAttr.type, true, GLImmediate.stride, colorAttr.offset); + GLctx.enableVertexAttribArray(this.colorLocation); +#endif + } +#if !GL_FFP_ONLY + else if (this.hasColor) { + GLctx.disableVertexAttribArray(this.colorLocation); + GLctx.vertexAttrib4fv(this.colorLocation, GLImmediate.clientColor); + } +#endif + if (this.hasFog) { + if (this.fogColorLocation) GLctx.uniform4fv(this.fogColorLocation, GLEmulation.fogColor); + if (this.fogEndLocation) GLctx.uniform1f(this.fogEndLocation, GLEmulation.fogEnd); + if (this.fogScaleLocation) GLctx.uniform1f(this.fogScaleLocation, 1/(GLEmulation.fogEnd - GLEmulation.fogStart)); + if (this.fogDensityLocation) GLctx.uniform1f(this.fogDensityLocation, GLEmulation.fogDensity); + } + + if (this.hasClipPlane) { + for (var clipPlaneId = 0; clipPlaneId < GLEmulation.MAX_CLIP_PLANES; clipPlaneId++) { + if (this.clipPlaneEquationLocation[clipPlaneId]) GLctx.uniform4fv(this.clipPlaneEquationLocation[clipPlaneId], GLEmulation.clipPlaneEquation[clipPlaneId]); + } + } + + if (this.hasLighting) { + if (this.lightModelAmbientLocation) GLctx.uniform4fv(this.lightModelAmbientLocation, GLEmulation.lightModelAmbient); + if (this.materialAmbientLocation) GLctx.uniform4fv(this.materialAmbientLocation, GLEmulation.materialAmbient); + if (this.materialDiffuseLocation) GLctx.uniform4fv(this.materialDiffuseLocation, GLEmulation.materialDiffuse); + if (this.materialSpecularLocation) GLctx.uniform4fv(this.materialSpecularLocation, GLEmulation.materialSpecular); + if (this.materialShininessLocation) GLctx.uniform1f(this.materialShininessLocation, GLEmulation.materialShininess[0]); + if (this.materialEmissionLocation) GLctx.uniform4fv(this.materialEmissionLocation, GLEmulation.materialEmission); + for (var lightId = 0; lightId < GLEmulation.MAX_LIGHTS; lightId++) { + if (this.lightAmbientLocation[lightId]) GLctx.uniform4fv(this.lightAmbientLocation[lightId], GLEmulation.lightAmbient[lightId]); + if (this.lightDiffuseLocation[lightId]) GLctx.uniform4fv(this.lightDiffuseLocation[lightId], GLEmulation.lightDiffuse[lightId]); + if (this.lightSpecularLocation[lightId]) GLctx.uniform4fv(this.lightSpecularLocation[lightId], GLEmulation.lightSpecular[lightId]); + if (this.lightPositionLocation[lightId]) GLctx.uniform4fv(this.lightPositionLocation[lightId], GLEmulation.lightPosition[lightId]); + } + } + + if (this.hasAlphaTest) { + if (this.alphaTestRefLocation) GLctx.uniform1f(this.alphaTestRefLocation, GLEmulation.alphaTestRef); + } + + if (GLImmediate.mode == GLctx.POINTS) { + if (this.pointSizeLocation) { + GLctx.uniform1f(this.pointSizeLocation, GLEmulation.pointSize); + } + } + }; + + this.cleanup = function() { +#if !GL_FFP_ONLY + GLctx.disableVertexAttribArray(this.positionLocation); + if (this.hasTextures) { + for (var i = 0; i < GLImmediate.MAX_TEXTURES; i++) { + if (GLImmediate.enabledClientAttributes[GLImmediate.TEXTURE0+i] && this.texCoordLocations[i] >= 0) { + GLctx.disableVertexAttribArray(this.texCoordLocations[i]); + } + } + } + if (this.hasColor) { + GLctx.disableVertexAttribArray(this.colorLocation); + } + if (this.hasNormal) { + GLctx.disableVertexAttribArray(this.normalLocation); + } + if (!GL.currProgram) { + GLctx.useProgram(null); + GLImmediate.fixedFunctionProgram = 0; + } + if (!GLctx.currentArrayBufferBinding) { + GLctx.bindBuffer(GLctx.ARRAY_BUFFER, null); + GLImmediate.lastArrayBuffer = null; + } + +#if GL_UNSAFE_OPTS + GLImmediate.lastRenderer = null; + GLImmediate.lastProgram = null; +#endif + GLImmediate.matricesModified = true; +#endif + } + + this.init(); + } + return new Renderer(); + }, + + setupFuncs() { + // TexEnv stuff needs to be prepared early, so do it here. + // init() is too late for -O2, since it freezes the GL functions + // by that point. + GLImmediate.MapTreeLib = GLImmediate.spawnMapTreeLib(); + GLImmediate.spawnMapTreeLib = null; + + GLImmediate.TexEnvJIT = GLImmediate.spawnTexEnvJIT(); + GLImmediate.spawnTexEnvJIT = null; + + GLImmediate.setupHooks(); + }, + + setupHooks() { + if (!GLEmulation.hasRunInit) { + GLEmulation.init(); + } + + var glActiveTexture = _glActiveTexture; + _glActiveTexture = _emscripten_glActiveTexture = (texture) => { + GLImmediate.TexEnvJIT.hook_activeTexture(texture); + glActiveTexture(texture); + }; + + var glEnable = _glEnable; + _glEnable = _emscripten_glEnable = (cap) => { + GLImmediate.TexEnvJIT.hook_enable(cap); + glEnable(cap); + }; + + var glDisable = _glDisable; + _glDisable = _emscripten_glDisable = (cap) => { + GLImmediate.TexEnvJIT.hook_disable(cap); + glDisable(cap); + }; + + var glTexEnvf = (typeof _glTexEnvf != 'undefined') ? _glTexEnvf : () => {}; + /** @suppress {checkTypes} */ + _glTexEnvf = _emscripten_glTexEnvf = (target, pname, param) => { + GLImmediate.TexEnvJIT.hook_texEnvf(target, pname, param); + // Don't call old func, since we are the implementor. + //glTexEnvf(target, pname, param); + }; + + var glTexEnvi = (typeof _glTexEnvi != 'undefined') ? _glTexEnvi : () => {}; + /** @suppress {checkTypes} */ + _glTexEnvi = _emscripten_glTexEnvi = (target, pname, param) => { + {{{ fromPtr('param') }}} + GLImmediate.TexEnvJIT.hook_texEnvi(target, pname, param); + // Don't call old func, since we are the implementor. + //glTexEnvi(target, pname, param); + }; + + var glTexEnvfv = (typeof _glTexEnvfv != 'undefined') ? _glTexEnvfv : () => {}; + /** @suppress {checkTypes} */ + _glTexEnvfv = _emscripten_glTexEnvfv = (target, pname, param) => { + {{{ fromPtr('param') }}} + GLImmediate.TexEnvJIT.hook_texEnvfv(target, pname, param); + // Don't call old func, since we are the implementor. + //glTexEnvfv(target, pname, param); + }; + + _glGetTexEnviv = (target, pname, param) => { + {{{ fromPtr('param') }}} + GLImmediate.TexEnvJIT.hook_getTexEnviv(target, pname, param); + }; + + _glGetTexEnvfv = (target, pname, param) => { + {{{ fromPtr('param') }}} + GLImmediate.TexEnvJIT.hook_getTexEnvfv(target, pname, param); + }; + + var glGetIntegerv = _glGetIntegerv; + _glGetIntegerv = _emscripten_glGetIntegerv = (pname, params) => { + switch (pname) { + case 0x8B8D: { // GL_CURRENT_PROGRAM + // Just query directly so we're working with WebGL objects. + var cur = GLctx.getParameter(GLctx.CURRENT_PROGRAM); + if (cur == GLImmediate.fixedFunctionProgram) { + // Pretend we're not using a program. + {{{ makeSetValue('params', '0', '0', 'i32') }}}; + return; + } + break; + } + } + glGetIntegerv(pname, params); + }; + }, + + // Main functions + initted: false, + init() { + err('WARNING: using emscripten GL immediate mode emulation. This is very limited in what it supports'); + GLImmediate.initted = true; + + if (!Browser.useWebGL) return; // a 2D canvas may be currently used TODO: make sure we are actually called in that case + + // User can override the maximum number of texture units that we emulate. Using fewer texture units increases runtime performance + // slightly, so it is advantageous to choose as small value as needed. + // Limit to a maximum of 28 to not overflow the state bits used for renderer caching (31 bits = 3 attributes + 28 texture units). + GLImmediate.MAX_TEXTURES = Math.min(Module['GL_MAX_TEXTURE_IMAGE_UNITS'] || GLctx.getParameter(GLctx.MAX_TEXTURE_IMAGE_UNITS), 28); + + GLImmediate.TexEnvJIT.init(GLctx, GLImmediate.MAX_TEXTURES); + + GLImmediate.NUM_ATTRIBUTES = 3 /*pos+normal+color attributes*/ + GLImmediate.MAX_TEXTURES; + GLImmediate.clientAttributes = []; + GLEmulation.enabledClientAttribIndices = []; + for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { + GLImmediate.clientAttributes.push({}); + GLEmulation.enabledClientAttribIndices.push(false); + } + + // Initialize matrix library + // When user sets a matrix, increment a 'version number' on the new data, and when rendering, submit + // the matrices to the shader program only if they have an old version of the data. + GLImmediate.matrix = []; + GLImmediate.matrixStack = []; + GLImmediate.matrixVersion = []; + for (var i = 0; i < 2 + GLImmediate.MAX_TEXTURES; i++) { // Modelview, Projection, plus one matrix for each texture coordinate. + GLImmediate.matrixStack.push([]); + GLImmediate.matrixVersion.push(0); + GLImmediate.matrix.push(GLImmediate.matrixLib.mat4.create()); + GLImmediate.matrixLib.mat4.identity(GLImmediate.matrix[i]); + } + + // Renderer cache + GLImmediate.rendererCache = GLImmediate.MapTreeLib.create(); + + // Buffers for data + GLImmediate.tempData = new Float32Array(GL.MAX_TEMP_BUFFER_SIZE >> 2); + GLImmediate.indexData = new Uint16Array(GL.MAX_TEMP_BUFFER_SIZE >> 1); + + GLImmediate.vertexDataU8 = new Uint8Array(GLImmediate.tempData.buffer); + + GL.generateTempBuffers(true, GL.currentContext); + + GLImmediate.clientColor = new Float32Array([1, 1, 1, 1]); + }, + + // Prepares and analyzes client attributes. + // Modifies liveClientAttributes, stride, vertexPointer, vertexCounter + // count: number of elements we will draw + // beginEnd: whether we are drawing the results of a begin/end block + prepareClientAttributes(count, beginEnd) { + // If no client attributes were modified since we were last called, do + // nothing. Note that this does not work for glBegin/End, where we + // generate renderer components dynamically and then disable them + // ourselves, but it does help with glDrawElements/Arrays. + if (!GLImmediate.modifiedClientAttributes) { +#if GL_ASSERTIONS + if ((GLImmediate.stride & 3) != 0) { + warnOnce(`Warning: Rendering from client side vertex arrays where stride (${GLImmediate.stride}) is not a multiple of four! This is not currently supported!`); + } +#endif + GLImmediate.vertexCounter = (GLImmediate.stride * count) / 4; // XXX assuming float + return; + } + GLImmediate.modifiedClientAttributes = false; + + // The role of prepareClientAttributes is to examine the set of + // client-side vertex attribute buffers that user code has submitted, and + // to prepare them to be uploaded to a VBO in GPU memory (since WebGL does + // not support client-side rendering, i.e. rendering from vertex data in + // CPU memory). User can submit vertex data generally in three different + // configurations: + // 1. Fully planar: all attributes are in their own separate + // tightly-packed arrays in CPU memory. + // 2. Fully interleaved: all attributes share a single array where data is + // interleaved something like (pos,uv,normal), + // (pos,uv,normal), ... + // 3. Complex hybrid: Multiple separate arrays that either are sparsely + // strided, and/or partially interleaves vertex + // attributes. + + // For simplicity, we support the case (2) as the fast case. For (1) and + // (3), we do a memory copy of the vertex data here to prepare a + // relayouted buffer that is of the structure in case (2). The reason + // for this is that it allows the emulation code to get away with using + // just one VBO buffer for rendering, and not have to maintain multiple + // ones. Therefore cases (1) and (3) will be very slow, and case (2) is + // fast. + + // Detect which case we are in by using a quick heuristic by examining the + // strides of the buffers. If all the buffers have identical stride, we + // assume we have case (2), otherwise we have something more complex. + var clientStartPointer = {{{ POINTER_MAX }}}; + var bytes = 0; // Total number of bytes taken up by a single vertex. + var minStride = {{{ POINTER_MAX }}}; + var maxStride = 0; + var attributes = GLImmediate.liveClientAttributes; + attributes.length = 0; + for (var i = 0; i < 3+GLImmediate.MAX_TEXTURES; i++) { + if (GLImmediate.enabledClientAttributes[i]) { + var attr = GLImmediate.clientAttributes[i]; + attributes.push(attr); + clientStartPointer = Math.min(clientStartPointer, attr.pointer); + attr.sizeBytes = attr.size * GL.byteSizeByType[attr.type - GL.byteSizeByTypeRoot]; + bytes += attr.sizeBytes; + minStride = Math.min(minStride, attr.stride); + maxStride = Math.max(maxStride, attr.stride); + } + } + + if ((minStride != maxStride || maxStride < bytes) && !beginEnd) { + // We are in cases (1) or (3): slow path, shuffle the data around into a + // single interleaved vertex buffer. + // The immediate-mode glBegin()/glEnd() vertex submission gets + // automatically generated in appropriate layout, so never need to come + // down this path if that was used. +#if GL_ASSERTIONS + warnOnce('Rendering from planar client-side vertex arrays. This is a very slow emulation path! Use interleaved vertex arrays for best performance.'); +#endif + GLImmediate.restrideBuffer ||= _malloc(GL.MAX_TEMP_BUFFER_SIZE); + var start = GLImmediate.restrideBuffer; + bytes = 0; + // calculate restrided offsets and total size + for (var attr of attributes) { + var size = attr.sizeBytes; + if (size % 4 != 0) size += 4 - (size % 4); // align everything + attr.offset = bytes; + bytes += size; + } + // copy out the data (we need to know the stride for that, and define attr.pointer) + for (var attr of attributes) { + var srcStride = Math.max(attr.sizeBytes, attr.stride); + if ((srcStride & 3) == 0 && (attr.sizeBytes & 3) == 0) { + for (var j = 0; j < count; j++) { + for (var k = 0; k < attr.sizeBytes; k+=4) { // copy in chunks of 4 bytes, our alignment makes this possible + var val = {{{ makeGetValue('attr.pointer', 'j*srcStride + k', 'i32') }}}; + {{{ makeSetValue('start + attr.offset', 'bytes*j + k', 'val', 'i32') }}}; + } + } + } else { + for (var j = 0; j < count; j++) { + for (var k = 0; k < attr.sizeBytes; k++) { // source data was not aligned to multiples of 4, must copy byte by byte. + HEAP8[start + attr.offset + bytes*j + k] = HEAP8[attr.pointer + j*srcStride + k]; + } + } + } + attr.pointer = start + attr.offset; + } + GLImmediate.stride = bytes; + GLImmediate.vertexPointer = start; + } else { + // case (2): fast path, all data is interleaved to a single vertex array so we can get away with a single VBO upload. + if (GLctx.currentArrayBufferBinding) { + GLImmediate.vertexPointer = 0; + } else { + GLImmediate.vertexPointer = clientStartPointer; + } + for (var attr of attributes) { + attr.offset = attr.pointer - GLImmediate.vertexPointer; // Compute what will be the offset of this attribute in the VBO after we upload. + } + GLImmediate.stride = Math.max(maxStride, bytes); + } + if (!beginEnd) { +#if GL_ASSERTIONS + if ((GLImmediate.stride & 3) != 0) { + warnOnce(`Warning: Rendering from client side vertex arrays where stride (${GLImmediate.stride}) is not a multiple of four! This is not currently supported!`); + } +#endif + GLImmediate.vertexCounter = (GLImmediate.stride * count) / 4; // XXX assuming float + } + }, + + flush(numProvidedIndexes, startIndex = 0, ptr = 0) { +#if ASSERTIONS + assert(numProvidedIndexes >= 0 || !numProvidedIndexes); +#endif + var renderer = GLImmediate.getRenderer(); + + // Generate index data in a format suitable for GLES 2.0/WebGL + var numVertices = 4 * GLImmediate.vertexCounter / GLImmediate.stride; + if (!numVertices) return; +#if ASSERTIONS + assert(numVertices % 1 == 0, "`numVertices` must be an integer."); +#endif + var emulatedElementArrayBuffer = false; + var numIndexes = 0; + if (numProvidedIndexes) { + numIndexes = numProvidedIndexes; + if (!GLctx.currentArrayBufferBinding && GLImmediate.firstVertex > GLImmediate.lastVertex) { + // Figure out the first and last vertex from the index data +#if ASSERTIONS + // If we are going to upload array buffer data, we need to find which range to + // upload based on the indices. If they are in a buffer on the GPU, that is very + // inconvenient! So if you do not have an array buffer, you should also not have + // an element array buffer. But best is to use both buffers! + assert(!GLctx.currentElementArrayBufferBinding, 'must use array buffers when using element buffer'); +#endif + for (var i = 0; i < numProvidedIndexes; i++) { + var currIndex = {{{ makeGetValue('ptr', 'i*2', 'u16') }}}; + GLImmediate.firstVertex = Math.min(GLImmediate.firstVertex, currIndex); + GLImmediate.lastVertex = Math.max(GLImmediate.lastVertex, currIndex+1); + } + } + if (!GLctx.currentElementArrayBufferBinding) { + // If no element array buffer is bound, then indices is a literal pointer to clientside data +#if ASSERTIONS + assert(numProvidedIndexes << 1 <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (a)'); +#endif + var indexBuffer = GL.getTempIndexBuffer(numProvidedIndexes << 1); + GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, indexBuffer); + GLctx.bufferSubData(GLctx.ELEMENT_ARRAY_BUFFER, 0, {{{ makeHEAPView('U16', 'ptr', 'ptr + (numProvidedIndexes << 1)') }}}); + ptr = 0; + emulatedElementArrayBuffer = true; + } + } else if (GLImmediate.mode > 6) { // above GL_TRIANGLE_FAN are the non-GL ES modes + if (GLImmediate.mode != 7) abort('unsupported immediate mode ' + GLImmediate.mode); // GL_QUADS + // GLImmediate.firstVertex is the first vertex we want. Quad indexes are + // in the pattern 0 1 2, 0 2 3, 4 5 6, 4 6 7, so we need to look at + // index firstVertex * 1.5 to see it. Then since indexes are 2 bytes + // each, that means 3 +#if ASSERTIONS + assert(GLImmediate.firstVertex % 4 == 0); +#endif + ptr = GLImmediate.firstVertex * 3; + var numQuads = numVertices / 4; + numIndexes = numQuads * 6; // 0 1 2, 0 2 3 pattern +#if ASSERTIONS + assert(ptr + (numIndexes << 1) <= GL.MAX_TEMP_BUFFER_SIZE, 'too many immediate mode indexes (b)'); +#endif + GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.currentContext.tempQuadIndexBuffer); + emulatedElementArrayBuffer = true; + GLImmediate.mode = GLctx.TRIANGLES; + } + + renderer.prepare(); + + if (numIndexes) { + GLctx.drawElements(GLImmediate.mode, numIndexes, GLctx.UNSIGNED_SHORT, ptr); + } else { + GLctx.drawArrays(GLImmediate.mode, startIndex, numVertices); + } + + if (emulatedElementArrayBuffer) { + GLctx.bindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, GL.buffers[GLctx.currentElementArrayBufferBinding] || null); + } + +#if !GL_UNSAFE_OPTS +#if !GL_FFP_ONLY + renderer.cleanup(); +#endif +#endif + } + }, + + $GLImmediateSetup__deps: ['$GLImmediate', () => 'GLImmediate.matrixLib = ' + read('gl-matrix.js') + ';\n'], + $GLImmediateSetup: {}, + + glBegin__deps: ['$GLImmediateSetup'], + glBegin: (mode) => { + // Push the old state: + GLImmediate.enabledClientAttributes_preBegin = GLImmediate.enabledClientAttributes; + GLImmediate.enabledClientAttributes = []; + + GLImmediate.clientAttributes_preBegin = GLImmediate.clientAttributes; + GLImmediate.clientAttributes = [] + for (var i = 0; i < GLImmediate.clientAttributes_preBegin.length; i++) { + GLImmediate.clientAttributes.push({}); + } + + GLImmediate.mode = mode; + GLImmediate.vertexCounter = 0; + var components = GLImmediate.rendererComponents = []; + for (var i = 0; i < GLImmediate.NUM_ATTRIBUTES; i++) { + components[i] = 0; + } + GLImmediate.rendererComponentPointer = 0; + GLImmediate.vertexData = GLImmediate.tempData; + }, + + glEnd: () => { + GLImmediate.prepareClientAttributes(GLImmediate.rendererComponents[GLImmediate.VERTEX], true); + GLImmediate.firstVertex = 0; + GLImmediate.lastVertex = GLImmediate.vertexCounter / (GLImmediate.stride >> 2); + GLImmediate.flush(); + GLImmediate.disableBeginEndClientAttributes(); + GLImmediate.mode = -1; + + // Pop the old state: + GLImmediate.enabledClientAttributes = GLImmediate.enabledClientAttributes_preBegin; + GLImmediate.clientAttributes = GLImmediate.clientAttributes_preBegin; + GLImmediate.currentRenderer = null; // The set of active client attributes changed, we must re-lookup the renderer to use. + GLImmediate.modifiedClientAttributes = true; + }, + + glVertex2f: (x, y) => { +#if ASSERTIONS + assert(GLImmediate.mode >= 0); // must be in begin/end +#endif + GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = 0; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = 1; +#if ASSERTIONS + assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); +#endif + GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); + }, + + glVertex3f: (x, y, z) => { +#if ASSERTIONS + assert(GLImmediate.mode >= 0); // must be in begin/end +#endif + GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = 1; +#if ASSERTIONS + assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); +#endif + GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); + }, + + glVertex4f: (x, y, z, w) => { +#if ASSERTIONS + assert(GLImmediate.mode >= 0); // must be in begin/end +#endif + GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = w; +#if ASSERTIONS + assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); +#endif + GLImmediate.addRendererComponent(GLImmediate.VERTEX, 4, GLctx.FLOAT); + }, + + glVertex2fv__deps: ['glVertex2f'], + glVertex2fv: (p) => _glVertex2f({{{ makeGetValue('p', '0', 'float') }}}, + {{{ makeGetValue('p', '4', 'float') }}}), + + glVertex3fv__deps: ['glVertex3f'], + glVertex3fv: (p) => _glVertex3f({{{ makeGetValue('p', '0', 'float') }}}, + {{{ makeGetValue('p', '4', 'float') }}}, + {{{ makeGetValue('p', '8', 'float') }}}), + + glVertex4fv__deps: ['glVertex4f'], + glVertex4fv: (p) => _glVertex4f({{{ makeGetValue('p', '0', 'float') }}}, + {{{ makeGetValue('p', '4', 'float') }}}, + {{{ makeGetValue('p', '8', 'float') }}}, + {{{ makeGetValue('p', '12', 'float') }}}), + + glVertex2i: 'glVertex2f', + + glVertex3i: 'glVertex3f', + + glVertex4i: 'glVertex4f', + + glTexCoord2i: (u, v) => { +#if ASSERTIONS + assert(GLImmediate.mode >= 0); // must be in begin/end +#endif + GLImmediate.vertexData[GLImmediate.vertexCounter++] = u; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = v; + GLImmediate.addRendererComponent(GLImmediate.TEXTURE0, 2, GLctx.FLOAT); + }, + glTexCoord2f: 'glTexCoord2i', + + glTexCoord2fv__deps: ['glTexCoord2i'], + glTexCoord2fv: (v) => + _glTexCoord2i({{{ makeGetValue('v', '0', 'float') }}}, {{{ makeGetValue('v', '4', 'float') }}}), + + glTexCoord4f: () => { abort('glTexCoord4f: TODO') }, + + glColor4f: (r, g, b, a) => { + r = Math.max(Math.min(r, 1), 0); + g = Math.max(Math.min(g, 1), 0); + b = Math.max(Math.min(b, 1), 0); + a = Math.max(Math.min(a, 1), 0); + + // TODO: make ub the default, not f, save a few mathops + if (GLImmediate.mode >= 0) { + var start = GLImmediate.vertexCounter << 2; + GLImmediate.vertexDataU8[start + 0] = r * 255; + GLImmediate.vertexDataU8[start + 1] = g * 255; + GLImmediate.vertexDataU8[start + 2] = b * 255; + GLImmediate.vertexDataU8[start + 3] = a * 255; + GLImmediate.vertexCounter++; + GLImmediate.addRendererComponent(GLImmediate.COLOR, 4, GLctx.UNSIGNED_BYTE); + } else { + GLImmediate.clientColor[0] = r; + GLImmediate.clientColor[1] = g; + GLImmediate.clientColor[2] = b; + GLImmediate.clientColor[3] = a; +#if GL_FFP_ONLY + GLctx.vertexAttrib4fv(GLImmediate.COLOR, GLImmediate.clientColor); +#endif + } + }, + + glColor4d: 'glColor4f', + + glColor4ub__deps: ['glColor4f'], + glColor4ub: (r, g, b, a) => _glColor4f((r&255)/255, (g&255)/255, (b&255)/255, (a&255)/255), + + glColor4us__deps: ['glColor4f'], + glColor4us: (r, g, b, a) => _glColor4f((r&65535)/65535, (g&65535)/65535, (b&65535)/65535, (a&65535)/65535), + + glColor4ui__deps: ['glColor4f'], + glColor4ui: (r, g, b, a) => _glColor4f((r>>>0)/4294967295, (g>>>0)/4294967295, (b>>>0)/4294967295, (a>>>0)/4294967295), + + glColor3f__deps: ['glColor4f'], + glColor3f: (r, g, b) => _glColor4f(r, g, b, 1), + + glColor3d: 'glColor3f', + + glColor3ub__deps: ['glColor4ub'], + glColor3ub: (r, g, b) => _glColor4ub(r, g, b, 255), + + glColor3us__deps: ['glColor4us'], + glColor3us: (r, g, b) => _glColor4us(r, g, b, 65535), + + glColor3ui__deps: ['glColor4ui'], + glColor3ui: (r, g, b) => _glColor4ui(r, g, b, 4294967295), + + glColor3ubv__deps: ['glColor3ub'], + glColor3ubv: (p) => _glColor3ub({{{ makeGetValue('p', '0', 'i8') }}}, + {{{ makeGetValue('p', '1', 'i8') }}}, + {{{ makeGetValue('p', '2', 'i8') }}}), + + glColor3usv__deps: ['glColor3us'], + glColor3usv: (p) => _glColor3us({{{ makeGetValue('p', '0', 'i16') }}}, + {{{ makeGetValue('p', '2', 'i16') }}}, + {{{ makeGetValue('p', '4', 'i16') }}}), + + glColor3uiv__deps: ['glColor3ui'], + glColor3uiv: (p) => _glColor3ui({{{ makeGetValue('p', '0', 'i32') }}}, + {{{ makeGetValue('p', '4', 'i32') }}}, + {{{ makeGetValue('p', '8', 'i32') }}}), + + glColor3fv__deps: ['glColor3f'], + glColor3fv: (p) => _glColor3f({{{ makeGetValue('p', '0', 'float') }}}, + {{{ makeGetValue('p', '4', 'float') }}}, + {{{ makeGetValue('p', '8', 'float') }}}), + + glColor4fv__deps: ['glColor4f'], + glColor4fv: (p) => _glColor4f({{{ makeGetValue('p', '0', 'float') }}}, + {{{ makeGetValue('p', '4', 'float') }}}, + {{{ makeGetValue('p', '8', 'float') }}}, + {{{ makeGetValue('p', '12', 'float') }}}), + + glColor4ubv__deps: ['glColor4ub'], + glColor4ubv: (p) => _glColor4ub({{{ makeGetValue('p', '0', 'i8') }}}, + {{{ makeGetValue('p', '1', 'i8') }}}, + {{{ makeGetValue('p', '2', 'i8') }}}, + {{{ makeGetValue('p', '3', 'i8') }}}), + + glFogf: (pname, param) => { // partial support, TODO + switch (pname) { + case 0xB63: // GL_FOG_START + GLEmulation.fogStart = param; break; + case 0xB64: // GL_FOG_END + GLEmulation.fogEnd = param; break; + case 0xB62: // GL_FOG_DENSITY + GLEmulation.fogDensity = param; break; + case 0xB65: // GL_FOG_MODE + switch (param) { + case 0x801: // GL_EXP2 + case 0x2601: // GL_LINEAR + if (GLEmulation.fogMode != param) { + GLImmediate.currentRenderer = null; // Fog mode is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.fogMode = param; + } + break; + default: // default to GL_EXP + if (GLEmulation.fogMode != 0x800 /* GL_EXP */) { + GLImmediate.currentRenderer = null; // Fog mode is part of the FFP shader state, we must re-lookup the renderer to use. + GLEmulation.fogMode = 0x800 /* GL_EXP */; + } + break; + } + break; + } + }, + glFogi__deps: ['glFogf'], + glFogi: (pname, param) => { + return _glFogf(pname, param); + }, + glFogfv__deps: ['glFogf'], + glFogfv: (pname, param) => { // partial support, TODO + switch (pname) { + case 0xB66: // GL_FOG_COLOR + GLEmulation.fogColor[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.fogColor[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.fogColor[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.fogColor[3] = {{{ makeGetValue('param', '12', 'float') }}}; + break; + case 0xB63: // GL_FOG_START + case 0xB64: // GL_FOG_END + _glFogf(pname, {{{ makeGetValue('param', '0', 'float') }}}); break; + } + }, + glFogiv__deps: ['glFogf'], + glFogiv: (pname, param) => { + switch (pname) { + case 0xB66: // GL_FOG_COLOR + GLEmulation.fogColor[0] = ({{{ makeGetValue('param', '0', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[1] = ({{{ makeGetValue('param', '4', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[2] = ({{{ makeGetValue('param', '8', 'i32') }}}/2147483647)/2.0+0.5; + GLEmulation.fogColor[3] = ({{{ makeGetValue('param', '12', 'i32') }}}/2147483647)/2.0+0.5; + break; + default: + _glFogf(pname, {{{ makeGetValue('param', '0', 'i32') }}}); break; + } + }, + glFogx: 'glFogi', + glFogxv: 'glFogiv', + + glPointSize: (size) => { + GLEmulation.pointSize = size; + }, + + glPolygonMode: () => {}, // TODO + + glAlphaFunc: (func, ref) => { + switch(func) { + case 0x200: // GL_NEVER + case 0x201: // GL_LESS + case 0x202: // GL_EQUAL + case 0x203: // GL_LEQUAL + case 0x204: // GL_GREATER + case 0x205: // GL_NOTEQUAL + case 0x206: // GL_GEQUAL + case 0x207: // GL_ALWAYS + GLEmulation.alphaTestRef = ref; + if (GLEmulation.alphaTestFunc != func) { + GLEmulation.alphaTestFunc = func; + GLImmediate.currentRenderer = null; // alpha test mode is part of the FFP shader state, we must re-lookup the renderer to use. + } + break; + default: // invalid value provided +#if GL_ASSERTIONS + err(`glAlphaFunc: Invalid alpha comparison function ${ptrToString(func)}!`); +#endif + break; + } + }, + + glNormal3f: (x, y, z) => { +#if ASSERTIONS + assert(GLImmediate.mode >= 0); // must be in begin/end +#endif + GLImmediate.vertexData[GLImmediate.vertexCounter++] = x; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = y; + GLImmediate.vertexData[GLImmediate.vertexCounter++] = z; +#if ASSERTIONS + assert(GLImmediate.vertexCounter << 2 < GL.MAX_TEMP_BUFFER_SIZE); +#endif + GLImmediate.addRendererComponent(GLImmediate.NORMAL, 3, GLctx.FLOAT); + }, + + glNormal3fv__deps: ['glNormal3f'], + glNormal3fv: (p) => { + _glNormal3f({{{ makeGetValue('p', '0', 'float') }}}, {{{ makeGetValue('p', '4', 'float') }}}, {{{ makeGetValue('p', '8', 'float') }}}); + }, + + + // Additional non-GLES rendering calls + + glDrawRangeElements__deps: ['glDrawElements'], + glDrawRangeElements: (mode, start, end, count, type, indices) => { + _glDrawElements(mode, count, type, indices, start, end); + }, + + // ClientState/gl*Pointer + + glEnableClientState: (cap) => { + var attrib = GLEmulation.getAttributeFromCapability(cap); + if (attrib === null) { +#if ASSERTIONS + err(`WARNING: unhandled clientstate: ${cap}`); +#endif + return; + } + if (!GLImmediate.enabledClientAttributes[attrib]) { + GLImmediate.enabledClientAttributes[attrib] = true; + GLImmediate.totalEnabledClientAttributes++; + GLImmediate.currentRenderer = null; // Will need to change current renderer, since the set of active vertex pointers changed. +#if GL_FFP_ONLY + // In GL_FFP_ONLY mode, attributes are bound to the same index in each FFP emulation shader, so we can immediately apply the change here. + GL.enableVertexAttribArray(attrib); +#endif + if (GLEmulation.currentVao) GLEmulation.currentVao.enabledClientStates[cap] = 1; + GLImmediate.modifiedClientAttributes = true; + } + }, + glDisableClientState: (cap) => { + var attrib = GLEmulation.getAttributeFromCapability(cap); + if (attrib === null) { +#if ASSERTIONS + err(`WARNING: unhandled clientstate: ${cap}`); +#endif + return; + } + if (GLImmediate.enabledClientAttributes[attrib]) { + GLImmediate.enabledClientAttributes[attrib] = false; + GLImmediate.totalEnabledClientAttributes--; + GLImmediate.currentRenderer = null; // Will need to change current renderer, since the set of active vertex pointers changed. +#if GL_FFP_ONLY + // In GL_FFP_ONLY mode, attributes are bound to the same index in each FFP emulation shader, so we can immediately apply the change here. + GL.disableVertexAttribArray(attrib); +#endif + if (GLEmulation.currentVao) delete GLEmulation.currentVao.enabledClientStates[cap]; + GLImmediate.modifiedClientAttributes = true; + } + }, + + glVertexPointer: (size, type, stride, pointer) => { + GLImmediate.setClientAttribute(GLImmediate.VERTEX, size, type, stride, pointer); +#if GL_FFP_ONLY + if (GLctx.currentArrayBufferBinding) { + GLctx.vertexAttribPointer(GLImmediate.VERTEX, size, type, false, stride, pointer); + } +#endif + }, + glTexCoordPointer: (size, type, stride, pointer) => { + GLImmediate.setClientAttribute(GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture, size, type, stride, pointer); +#if GL_FFP_ONLY + if (GLctx.currentArrayBufferBinding) { + var loc = GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture; + GLctx.vertexAttribPointer(loc, size, type, false, stride, pointer); + } +#endif + }, + glNormalPointer: (type, stride, pointer) => { + GLImmediate.setClientAttribute(GLImmediate.NORMAL, 3, type, stride, pointer); +#if GL_FFP_ONLY + if (GLctx.currentArrayBufferBinding) { + GLctx.vertexAttribPointer(GLImmediate.NORMAL, 3, type, true, stride, pointer); + } +#endif + }, + glColorPointer: (size, type, stride, pointer) => { + GLImmediate.setClientAttribute(GLImmediate.COLOR, size, type, stride, pointer); +#if GL_FFP_ONLY + if (GLctx.currentArrayBufferBinding) { + GLctx.vertexAttribPointer(GLImmediate.COLOR, size, type, true, stride, pointer); + } +#endif + }, + + glClientActiveTexture: (texture) => { + GLImmediate.clientActiveTexture = texture - 0x84C0; // GL_TEXTURE0 + }, + + // Replace some functions with immediate-mode aware versions. If there are no + // client attributes enabled, and we use webgl-friendly modes (no GL_QUADS), + // then no need for emulation + glDrawArrays: (mode, first, count) => { + if (GLImmediate.totalEnabledClientAttributes == 0 && mode <= 6) { + GLctx.drawArrays(mode, first, count); + return; + } + GLImmediate.prepareClientAttributes(count, false); + GLImmediate.mode = mode; + if (!GLctx.currentArrayBufferBinding) { + GLImmediate.vertexData = {{{ makeHEAPView('F32', 'GLImmediate.vertexPointer', 'GLImmediate.vertexPointer + (first+count)*GLImmediate.stride') }}}; // XXX assuming float + GLImmediate.firstVertex = first; + GLImmediate.lastVertex = first + count; + } + GLImmediate.flush(null, first); + GLImmediate.mode = -1; + }, + + // start, end are given if we come from glDrawRangeElements + glDrawElements: (mode, count, type, indices, start, end) => { + if (GLImmediate.totalEnabledClientAttributes == 0 && mode <= 6 && GLctx.currentElementArrayBufferBinding) { + GLctx.drawElements(mode, count, type, indices); + return; + } +#if ASSERTIONS + if (!GLctx.currentElementArrayBufferBinding) { + assert(type == GLctx.UNSIGNED_SHORT); // We can only emulate buffers of this kind, for now + } + warnOnce("DrawElements doesn't actually prepareClientAttributes properly."); +#endif + GLImmediate.prepareClientAttributes(count, false); + GLImmediate.mode = mode; + if (!GLctx.currentArrayBufferBinding) { + GLImmediate.firstVertex = end ? start : HEAP8.length; // if we don't know the start, set an invalid value and we will calculate it later from the indices + GLImmediate.lastVertex = end ? end + 1 : 0; + start = GLImmediate.vertexPointer; + // TODO(sbc): Combine these two subarray calls back into a single one if + // we ever fix https://github.com/emscripten-core/emscripten/issues/21250. + if (end) { + end = GLImmediate.vertexPointer + (end +1 ) * GLImmediate.stride; + GLImmediate.vertexData = HEAPF32.subarray({{{ getHeapOffset('start', 'float') }}}, {{{ getHeapOffset('end', 'float') }}}); + } else { + GLImmediate.vertexData = HEAPF32.subarray({{{ getHeapOffset('start', 'float') }}}); + } + } + GLImmediate.flush(count, 0, indices); + GLImmediate.mode = -1; + }, + + // Vertex array object (VAO) support. TODO: when the WebGL extension is + // popular, use that and remove this code and GL.vaos + $emulGlGenVertexArrays__deps: ['$GLEmulation'], + $emulGlGenVertexArrays: (n, vaos) => { + for (var i = 0; i < n; i++) { + var id = GL.getNewId(GLEmulation.vaos); + GLEmulation.vaos[id] = { + id, + arrayBuffer: 0, + elementArrayBuffer: 0, + enabledVertexAttribArrays: {}, + vertexAttribPointers: {}, + enabledClientStates: {}, + }; + {{{ makeSetValue('vaos', 'i*4', 'id', 'i32') }}}; + } + }, + $emulGlDeleteVertexArrays: (n, vaos) => { + for (var i = 0; i < n; i++) { + var id = {{{ makeGetValue('vaos', 'i*4', 'i32') }}}; + GLEmulation.vaos[id] = null; + if (GLEmulation.currentVao?.id == id) GLEmulation.currentVao = null; + } + }, + $emulGlIsVertexArray: (array) => { + var vao = GLEmulation.vaos[array]; + if (!vao) return 0; + return 1; + }, + $emulGlBindVertexArray__deps: ['glBindBuffer', 'glEnableVertexAttribArray', 'glVertexAttribPointer', 'glEnableClientState'], + $emulGlBindVertexArray: (vao) => { + // undo vao-related things, wipe the slate clean, both for vao of 0 or an actual vao + GLEmulation.currentVao = null; // make sure the commands we run here are not recorded + GLImmediate.lastRenderer?.cleanup(); + _glBindBuffer(GLctx.ARRAY_BUFFER, 0); // XXX if one was there before we were bound? + _glBindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, 0); + for (var vaa in GLEmulation.enabledVertexAttribArrays) { + GLctx.disableVertexAttribArray(vaa); + } + GLEmulation.enabledVertexAttribArrays = {}; + GLImmediate.enabledClientAttributes = [0, 0]; + GLImmediate.totalEnabledClientAttributes = 0; + GLImmediate.modifiedClientAttributes = true; + if (vao) { + // replay vao + var info = GLEmulation.vaos[vao]; + _glBindBuffer(GLctx.ARRAY_BUFFER, info.arrayBuffer); // XXX overwrite current binding? + _glBindBuffer(GLctx.ELEMENT_ARRAY_BUFFER, info.elementArrayBuffer); + for (var vaa in info.enabledVertexAttribArrays) { + _glEnableVertexAttribArray(vaa); + } + for (var vaa in info.vertexAttribPointers) { + _glVertexAttribPointer(...info.vertexAttribPointers[vaa]); + } + for (var attrib in info.enabledClientStates) { + _glEnableClientState(attrib|0); + } + GLEmulation.currentVao = info; // set currentVao last, so the commands we ran here were not recorded + } + }, + + // OpenGL Immediate Mode matrix routines. + // Note that in the future we might make these available only in certain modes. + glMatrixMode__deps: ['$GL', '$GLImmediateSetup'], + glMatrixMode: (mode) => { + if (mode == 0x1700 /* GL_MODELVIEW */) { + GLImmediate.currentMatrix = 0/*m*/; + } else if (mode == 0x1701 /* GL_PROJECTION */) { + GLImmediate.currentMatrix = 1/*p*/; + } else if (mode == 0x1702) { // GL_TEXTURE + GLImmediate.useTextureMatrix = true; + GLImmediate.currentMatrix = 2/*t*/ + GLImmediate.TexEnvJIT.getActiveTexture(); + } else { + throw `Wrong mode ${mode} passed to glMatrixMode`; + } + }, + + glPushMatrix: () => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixStack[GLImmediate.currentMatrix].push( + Array.prototype.slice.call(GLImmediate.matrix[GLImmediate.currentMatrix])); + }, + + glPopMatrix: () => { + if (GLImmediate.matrixStack[GLImmediate.currentMatrix].length == 0) { + GL.recordError(0x504/*GL_STACK_UNDERFLOW*/); + return; + } + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrix[GLImmediate.currentMatrix] = GLImmediate.matrixStack[GLImmediate.currentMatrix].pop(); + }, + + glLoadIdentity__deps: ['$GL', '$GLImmediateSetup'], + glLoadIdentity: () => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.identity(GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + glLoadMatrixd: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + glLoadMatrixf: (matrix) => { +#if GL_DEBUG + if (GL.debug) dbg('glLoadMatrixf receiving: ' + Array.prototype.slice.call(HEAPF32.subarray(matrix >> 2, (matrix >> 2) + 16))); +#endif + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + glLoadTransposeMatrixd: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); + GLImmediate.matrixLib.mat4.transpose(GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + glLoadTransposeMatrixf: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, GLImmediate.matrix[GLImmediate.currentMatrix]); + GLImmediate.matrixLib.mat4.transpose(GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + glMultMatrixd: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], + {{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}); + }, + + glMultMatrixf: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], + {{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}); + }, + + glMultTransposeMatrixd: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + var colMajor = GLImmediate.matrixLib.mat4.create(); + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F64', 'matrix', 'matrix+' + (16*8)) }}}, colMajor); + GLImmediate.matrixLib.mat4.transpose(colMajor); + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], colMajor); + }, + + glMultTransposeMatrixf: (matrix) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + var colMajor = GLImmediate.matrixLib.mat4.create(); + GLImmediate.matrixLib.mat4.set({{{ makeHEAPView('F32', 'matrix', 'matrix+' + (16*4)) }}}, colMajor); + GLImmediate.matrixLib.mat4.transpose(colMajor); + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], colMajor); + }, + + glFrustum: (left, right, bottom, top_, nearVal, farVal) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], + GLImmediate.matrixLib.mat4.frustum(left, right, bottom, top_, nearVal, farVal)); + }, + glFrustumf: 'glFrustum', + + glOrtho: (left, right, bottom, top_, nearVal, farVal) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.multiply(GLImmediate.matrix[GLImmediate.currentMatrix], + GLImmediate.matrixLib.mat4.ortho(left, right, bottom, top_, nearVal, farVal)); + }, + glOrthof: 'glOrtho', + + glScaled: (x, y, z) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.scale(GLImmediate.matrix[GLImmediate.currentMatrix], [x, y, z]); + }, + glScalef: 'glScaled', + + glTranslated: (x, y, z) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.translate(GLImmediate.matrix[GLImmediate.currentMatrix], [x, y, z]); + }, + glTranslatef: 'glTranslated', + + glRotated: (angle, x, y, z) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.rotate(GLImmediate.matrix[GLImmediate.currentMatrix], angle*Math.PI/180, [x, y, z]); + }, + glRotatef: 'glRotated', + + glDrawBuffer: () => { abort('glDrawBuffer: TODO') }, +#if MAX_WEBGL_VERSION < 2 + glReadBuffer: () => { abort('glReadBuffer: TODO') }, +#endif + + glClipPlane: (pname, param) => { + if ((pname >= 0x3000) && (pname < 0x3006) /* GL_CLIP_PLANE0 to GL_CLIP_PLANE5 */) { + var clipPlaneId = pname - 0x3000; + + GLEmulation.clipPlaneEquation[clipPlaneId][0] = {{{ makeGetValue('param', '0', 'double') }}}; + GLEmulation.clipPlaneEquation[clipPlaneId][1] = {{{ makeGetValue('param', '8', 'double') }}}; + GLEmulation.clipPlaneEquation[clipPlaneId][2] = {{{ makeGetValue('param', '16', 'double') }}}; + GLEmulation.clipPlaneEquation[clipPlaneId][3] = {{{ makeGetValue('param', '24', 'double') }}}; + + // apply inverse transposed current modelview matrix when setting clip plane + var tmpMV = GLImmediate.matrixLib.mat4.create(GLImmediate.matrix[0]); + GLImmediate.matrixLib.mat4.inverse(tmpMV); + GLImmediate.matrixLib.mat4.transpose(tmpMV); + GLImmediate.matrixLib.mat4.multiplyVec4(tmpMV, GLEmulation.clipPlaneEquation[clipPlaneId]); + } + }, + + glLightfv: (light, pname, param) => { + if ((light >= 0x4000) && (light < 0x4008) /* GL_LIGHT0 to GL_LIGHT7 */) { + var lightId = light - 0x4000; + + if (pname == 0x1200) { // GL_AMBIENT + GLEmulation.lightAmbient[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.lightAmbient[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.lightAmbient[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.lightAmbient[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1201) { // GL_DIFFUSE + GLEmulation.lightDiffuse[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.lightDiffuse[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.lightDiffuse[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.lightDiffuse[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1202) { // GL_SPECULAR + GLEmulation.lightSpecular[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.lightSpecular[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.lightSpecular[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.lightSpecular[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1203) { // GL_POSITION + GLEmulation.lightPosition[lightId][0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.lightPosition[lightId][1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.lightPosition[lightId][2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.lightPosition[lightId][3] = {{{ makeGetValue('param', '12', 'float') }}}; + + // multiply position with current modelviewmatrix + GLImmediate.matrixLib.mat4.multiplyVec4(GLImmediate.matrix[0], GLEmulation.lightPosition[lightId]); + } else { + abort('glLightfv: TODO: ' + pname); + } + } + }, + + glLightModelf: (pname, param) => { + if (pname == 0x0B52) { // GL_LIGHT_MODEL_TWO_SIDE + GLEmulation.lightModelTwoSide = (param != 0) ? true : false; + } else { + abort('glLightModelf: TODO: ' + pname); + } + }, + + glLightModelfv: (pname, param) => { // TODO: GL_LIGHT_MODEL_LOCAL_VIEWER + if (pname == 0x0B53) { // GL_LIGHT_MODEL_AMBIENT + GLEmulation.lightModelAmbient[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.lightModelAmbient[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.lightModelAmbient[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.lightModelAmbient[3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else { + abort('glLightModelfv: TODO: ' + pname); + } + }, + + glMaterialfv: (face, pname, param) => { + if ((face != 0x0404) && (face != 0x0408)) { abort('glMaterialfv: TODO' + face); } // only GL_FRONT and GL_FRONT_AND_BACK supported + + if (pname == 0x1200) { // GL_AMBIENT + GLEmulation.materialAmbient[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.materialAmbient[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.materialAmbient[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.materialAmbient[3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1201) { // GL_DIFFUSE + GLEmulation.materialDiffuse[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.materialDiffuse[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.materialDiffuse[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.materialDiffuse[3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1202) { // GL_SPECULAR + GLEmulation.materialSpecular[0] = {{{ makeGetValue('param', '0', 'float') }}}; + GLEmulation.materialSpecular[1] = {{{ makeGetValue('param', '4', 'float') }}}; + GLEmulation.materialSpecular[2] = {{{ makeGetValue('param', '8', 'float') }}}; + GLEmulation.materialSpecular[3] = {{{ makeGetValue('param', '12', 'float') }}}; + } else if (pname == 0x1601) { // GL_SHININESS + GLEmulation.materialShininess[0] = {{{ makeGetValue('param', '0', 'float') }}}; + } else { + abort('glMaterialfv: TODO: ' + pname); + } + }, + + glTexGeni: (coord, pname, param) => abort('glTexGeni: TODO'), + glTexGenfv: (coord, pname, param) => abort('glTexGenfv: TODO'), + glTexEnvi: (target, pname, params) => warnOnce('glTexEnvi: TODO'), + glTexEnvf: (target, pname, params) => warnOnce('glTexEnvf: TODO'), + glTexEnvfv: (target, pname, params) => warnOnce('glTexEnvfv: TODO'), + + glGetTexEnviv: (target, pname, param) => abort('GL emulation not initialized!'), + glGetTexEnvfv: (target, pname, param) => abort('GL emulation not initialized!'), + + glTexImage1D: (target, level, internalformat, width, border, format, type, data) => abort('glTexImage1D: TODO'), + glTexCoord3f: (target, level, internalformat, width, border, format, type, data) => abort('glTexCoord3f: TODO'), + glGetTexLevelParameteriv: (target, level, pname, params) => abort('glGetTexLevelParameteriv: TODO'), + + glShadeModel: () => warnOnce('TODO: glShadeModel'), + + // Open GLES1.1 compatibility + + glGenFramebuffersOES: 'glGenFramebuffers', + glGenRenderbuffersOES: 'glGenRenderbuffers', + glBindFramebufferOES: 'glBindFramebuffer', + glBindRenderbufferOES: 'glBindRenderbuffer', + glGetRenderbufferParameterivOES: 'glGetRenderbufferParameteriv', + glFramebufferRenderbufferOES: 'glFramebufferRenderbuffer', + glRenderbufferStorageOES: 'glRenderbufferStorage', + glCheckFramebufferStatusOES: 'glCheckFramebufferStatus', + glDeleteFramebuffersOES: 'glDeleteFramebuffers', + glDeleteRenderbuffersOES: 'glDeleteRenderbuffers', + glFramebufferTexture2DOES: 'glFramebufferTexture2D', + + // GLU + + gluPerspective: (fov, aspect, near, far) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrix[GLImmediate.currentMatrix] = + GLImmediate.matrixLib.mat4.perspective(fov, aspect, near, far, + GLImmediate.matrix[GLImmediate.currentMatrix]); + }, + + gluLookAt: (ex, ey, ez, cx, cy, cz, ux, uy, uz) => { + GLImmediate.matricesModified = true; + GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; + GLImmediate.matrixLib.mat4.lookAt(GLImmediate.matrix[GLImmediate.currentMatrix], [ex, ey, ez], + [cx, cy, cz], [ux, uy, uz]); + }, + + gluProject: (objX, objY, objZ, model, proj, view, winX, winY, winZ) => { + // The algorithm for this function comes from Mesa + + var inVec = new Float32Array(4); + var outVec = new Float32Array(4); + GLImmediate.matrixLib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}}, + [objX, objY, objZ, 1.0], outVec); + GLImmediate.matrixLib.mat4.multiplyVec4({{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}}, + outVec, inVec); + if (inVec[3] == 0.0) { + return 0 /* GL_FALSE */; + } + inVec[0] /= inVec[3]; + inVec[1] /= inVec[3]; + inVec[2] /= inVec[3]; + // Map x, y and z to range 0-1 */ + inVec[0] = inVec[0] * 0.5 + 0.5; + inVec[1] = inVec[1] * 0.5 + 0.5; + inVec[2] = inVec[2] * 0.5 + 0.5; + // Map x, y to viewport + inVec[0] = inVec[0] * {{{ makeGetValue('view', 2*4, 'i32') }}} + {{{ makeGetValue('view', 0*4, 'i32') }}}; + inVec[1] = inVec[1] * {{{ makeGetValue('view', 3*4, 'i32') }}} + {{{ makeGetValue('view', 1*4, 'i32') }}}; + + {{{ makeSetValue('winX', '0', 'inVec[0]', 'double') }}}; + {{{ makeSetValue('winY', '0', 'inVec[1]', 'double') }}}; + {{{ makeSetValue('winZ', '0', 'inVec[2]', 'double') }}}; + + return 1 /* GL_TRUE */; + }, + + gluUnProject: (winX, winY, winZ, model, proj, view, objX, objY, objZ) => { + var result = GLImmediate.matrixLib.vec3.unproject([winX, winY, winZ], + {{{ makeHEAPView('F64', 'model', 'model+' + (16*8)) }}}, + {{{ makeHEAPView('F64', 'proj', 'proj+' + (16*8)) }}}, + {{{ makeHEAPView('32', 'view', 'view+' + (4*4)) }}}); + + if (result === null) { + return 0 /* GL_FALSE */; + } + + {{{ makeSetValue('objX', '0', 'result[0]', 'double') }}}; + {{{ makeSetValue('objY', '0', 'result[1]', 'double') }}}; + {{{ makeSetValue('objZ', '0', 'result[2]', 'double') }}}; + + return 1 /* GL_TRUE */; + }, + + gluOrtho2D__deps: ['glOrtho'], + gluOrtho2D: (left, right, bottom, top) => _glOrtho(left, right, bottom, top, -1, 1), +}; + +extraLibraryFuncs.push('$GLEmulation'); + +recordGLProcAddressGet(LibraryGLEmulation); + +addToLibrary(LibraryGLEmulation); diff --git a/src/lib/libglew.js b/src/lib/libglew.js new file mode 100644 index 0000000000000..dc8f9a3947a23 --- /dev/null +++ b/src/lib/libglew.js @@ -0,0 +1,132 @@ +/** + * @license + * Copyright 2014 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +/* + * EMSCRIPTEN GLEW 1.10.0 emulation + * + * What it does: + * - Stubs init function. + * - GL Extensions support. + * + * Optional: + * - isLinaroFork variable to enable glew-es specific error strings. + * This is enabled by default, but should be disabled when upstream glew conflicts. + * + * Authors: + * - Jari Vetoniemi + */ + +var LibraryGLEW = { + $GLEW__deps: ['glGetString', '$stringToNewUTF8', '$UTF8ToString', '$webglGetExtensions'], + $GLEW: { + isLinaroFork: 1, + extensions: null, + + error: { + 0:null, // GLEW_OK || GLEW_NO_ERROR + 1:null, // GLEW_ERROR_NO_GL_VERSION + 2:null, // GLEW_ERROR_GL_VERSION_10_ONLY + 3:null, // GLEW_ERROR_GLX_VERSION_11_ONLY + + 4:null, // GLEW_ERROR_NOT_GLES_VERSION + 5:null, // GLEW_ERROR_GLES_VERSION + 6:null, // GLEW_ERROR_NO_EGL_VERSION + 7:null, // GLEW_ERROR_EGL_VERSION_10_ONLY + + 8:null, // Unknown error + }, + + version: { + 1:null, // GLEW_VERSION + 2:null, // GLEW_VERSION_MAJOR + 3:null, // GLEW_VERSION_MINOR + 4:null, // GLEW_VERSION_MICRO + }, + + errorStringConstantFromCode(error) { + if (GLEW.isLinaroFork) { + switch (error) { + case 4:return "OpenGL ES lib expected, found OpenGL lib"; // GLEW_ERROR_NOT_GLES_VERSION + case 5:return "OpenGL lib expected, found OpenGL ES lib"; // GLEW_ERROR_GLES_VERSION + case 6:return "Missing EGL version"; // GLEW_ERROR_NO_EGL_VERSION + case 7:return "EGL 1.1 and up are supported"; // GLEW_ERROR_EGL_VERSION_10_ONLY + default:break; + } + } + + switch (error) { + case 0:return "No error"; // GLEW_OK || GLEW_NO_ERROR + case 1:return "Missing GL version"; // GLEW_ERROR_NO_GL_VERSION + case 2:return "GL 1.1 and up are supported"; // GLEW_ERROR_GL_VERSION_10_ONLY + case 3:return "GLX 1.2 and up are supported"; // GLEW_ERROR_GLX_VERSION_11_ONLY + default:return null; + } + }, + + errorString(error) { + if (!GLEW.error[error]) { + var string = GLEW.errorStringConstantFromCode(error); + if (!string) { + string = "Unknown error"; + error = 8; // prevent array from growing more than this + } + GLEW.error[error] = stringToNewUTF8(string); + } + return GLEW.error[error]; + }, + + versionStringConstantFromCode(name) { + switch (name) { + case 1:return "1.10.0"; // GLEW_VERSION + case 2:return "1"; // GLEW_VERSION_MAJOR + case 3:return "10"; // GLEW_VERSION_MINOR + case 4:return "0"; // GLEW_VERSION_MICRO + default:return null; + } + }, + + versionString(name) { + if (!GLEW.version[name]) { + var string = GLEW.versionStringConstantFromCode(name); + if (!string) + return 0; + GLEW.version[name] = stringToNewUTF8(string); + } + return GLEW.version[name]; + }, + + extensionIsSupported(name) { + GLEW.extensions ||= webglGetExtensions(); + + if (GLEW.extensions.includes(name)) + return 1; + + // extensions from GLEmulations do not come unprefixed + // so, try with prefix + return (GLEW.extensions.includes("GL_" + name)); + }, + }, + + glewInit: () => 0, + + glewIsSupported: (name) => { + var exts = UTF8ToString(name).split(' '); + for (var ext of exts) { + if (!GLEW.extensionIsSupported(ext)) return 0; + } + return 1; + }, + + glewGetExtension: (name) => GLEW.extensionIsSupported(UTF8ToString(name)), + + glewGetErrorString: (error) => GLEW.errorString(error), + + glewGetString: (name) => GLEW.versionString(name), + +}; + +autoAddDeps(LibraryGLEW, '$GLEW'); +addToLibrary(LibraryGLEW); diff --git a/src/lib/libglfw.js b/src/lib/libglfw.js new file mode 100644 index 0000000000000..efc5e96bf7371 --- /dev/null +++ b/src/lib/libglfw.js @@ -0,0 +1,2115 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +/* + * EMSCRIPTEN GLFW 2.x-3.x emulation. + * It tries to emulate the behavior described in + * http://www.glfw.org/docs/latest/ + * + * This also implements parts of GLFW 2.x on top of GLFW 3.x. + * + * What it does: + * - Creates a GL context. + * - Manage keyboard and mouse events. + * - GL Extensions support. + * + * What it does not but should probably do: + * - Transmit events when glfwPollEvents, glfwWaitEvents or glfwSwapBuffers is + * called. Events callbacks are called as soon as events are received. + * - Input modes. + * - Gamma ramps. + * - Video modes. + * - Monitors. + * - Clipboard (not possible from javascript?). + * - Multiple windows. + * - Error codes && messages through callback. + * - Thread emulation. (removed in GLFW3). + * - Image/Texture I/O support (removed in GLFW 3). + * + * Authors: + * - Jari Vetoniemi + * - Éloi Rivard + * - Thomas Borsos + */ + +var LibraryGLFW = { + $GLFW_Window__docs: '/** @constructor */', + $GLFW_Window: function(id, width, height, framebufferWidth, framebufferHeight, title, monitor, share) { + this.id = id; + this.x = 0; + this.y = 0; + this.fullscreen = false; // Used to determine if app is in fullscreen mode + this.storedX = 0; // Used to store X before fullscreen + this.storedY = 0; // Used to store Y before fullscreen + this.width = width; + this.height = height; + this.framebufferWidth = framebufferWidth; + this.framebufferHeight = framebufferHeight; + this.storedWidth = width; // Used to store width before fullscreen + this.storedHeight = height; // Used to store height before fullscreen + this.title = title; + this.monitor = monitor; + this.share = share; + this.attributes = {...GLFW.hints}; + this.inputModes = { + 0x00033001:0x00034001, // GLFW_CURSOR (GLFW_CURSOR_NORMAL) + 0x00033002:0, // GLFW_STICKY_KEYS + 0x00033003:0, // GLFW_STICKY_MOUSE_BUTTONS + }; + this.buttons = 0; + this.keys = new Array(); + this.domKeys = new Array(); + this.shouldClose = 0; + this.title = null; + this.windowPosFunc = 0; // GLFWwindowposfun + this.windowSizeFunc = 0; // GLFWwindowsizefun + this.windowCloseFunc = 0; // GLFWwindowclosefun + this.windowRefreshFunc = 0; // GLFWwindowrefreshfun + this.windowFocusFunc = 0; // GLFWwindowfocusfun + this.windowIconifyFunc = 0; // GLFWwindowiconifyfun + this.windowMaximizeFunc = 0; // GLFWwindowmaximizefun + this.framebufferSizeFunc = 0; // GLFWframebuffersizefun + this.windowContentScaleFunc = 0; // GLFWwindowcontentscalefun + this.mouseButtonFunc = 0; // GLFWmousebuttonfun + this.cursorPosFunc = 0; // GLFWcursorposfun + this.cursorEnterFunc = 0; // GLFWcursorenterfun + this.scrollFunc = 0; // GLFWscrollfun + this.dropFunc = 0; // GLFWdropfun + this.keyFunc = 0; // GLFWkeyfun + this.charFunc = 0; // GLFWcharfun + this.userptr = 0; + }, + + $GLFW__deps: ['emscripten_get_now', '$GL', '$Browser', '$GLFW_Window', + 'malloc', 'free', + '$MainLoop', + '$stringToNewUTF8', + '$getFullscreenElement', + 'emscripten_set_window_title', +#if FILESYSTEM + '$FS', +#endif + ], + $GLFW: { + WindowFromId: (id) => { + if (id <= 0 || !GLFW.windows) return null; + return GLFW.windows[id - 1]; + }, + + joystickFunc: 0, // GLFWjoystickfun + errorFunc: 0, // GLFWerrorfun + monitorFunc: 0, // GLFWmonitorfun + active: null, // active window + scale: null, + windows: null, + monitors: null, + monitorString: null, + versionString: null, + initialTime: null, + extensions: null, + devicePixelRatioMQL: null, // MediaQueryList from window.matchMedia + hints: null, + primaryTouchId: null, + defaultHints: { + 0x00020001:0, // GLFW_FOCUSED + 0x00020002:0, // GLFW_ICONIFIED + 0x00020003:1, // GLFW_RESIZABLE + 0x00020004:1, // GLFW_VISIBLE + 0x00020005:1, // GLFW_DECORATED + 0x0002000A:0, // GLFW_TRANSPARENT_FRAMEBUFFER + 0x0002200C:0, // GLFW_SCALE_TO_MONITOR + + 0x00021001:8, // GLFW_RED_BITS + 0x00021002:8, // GLFW_GREEN_BITS + 0x00021003:8, // GLFW_BLUE_BITS + 0x00021004:8, // GLFW_ALPHA_BITS + 0x00021005:24, // GLFW_DEPTH_BITS + 0x00021006:8, // GLFW_STENCIL_BITS + 0x00021007:0, // GLFW_ACCUM_RED_BITS + 0x00021008:0, // GLFW_ACCUM_GREEN_BITS + 0x00021009:0, // GLFW_ACCUM_BLUE_BITS + 0x0002100A:0, // GLFW_ACCUM_ALPHA_BITS + 0x0002100B:0, // GLFW_AUX_BUFFERS + 0x0002100C:0, // GLFW_STEREO + 0x0002100D:0, // GLFW_SAMPLES + 0x0002100E:0, // GLFW_SRGB_CAPABLE + 0x0002100F:0, // GLFW_REFRESH_RATE + + 0x00022001:0x00030001, // GLFW_CLIENT_API (GLFW_OPENGL_API) + 0x00022002:1, // GLFW_CONTEXT_VERSION_MAJOR + 0x00022003:0, // GLFW_CONTEXT_VERSION_MINOR + 0x00022004:0, // GLFW_CONTEXT_REVISION + 0x00022005:0, // GLFW_CONTEXT_ROBUSTNESS + 0x00022006:0, // GLFW_OPENGL_FORWARD_COMPAT + 0x00022007:0, // GLFW_OPENGL_DEBUG_CONTEXT + 0x00022008:0, // GLFW_OPENGL_PROFILE + }, + +/******************************************************************************* + * DOM EVENT CALLBACKS + ******************************************************************************/ + + /* https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/KeyboardEvent and GLFW/glfw3.h */ + DOMToGLFWKeyCode: (keycode) => { + switch (keycode) { + // these keycodes are only defined for GLFW3, assume they are the same for GLFW2 + case 0x20:return 32; // DOM_VK_SPACE -> GLFW_KEY_SPACE + case 0xDE:return 39; // DOM_VK_QUOTE -> GLFW_KEY_APOSTROPHE + case 0xBC:return 44; // DOM_VK_COMMA -> GLFW_KEY_COMMA + case 0xAD:return 45; // DOM_VK_HYPHEN_MINUS -> GLFW_KEY_MINUS + case 0xBD:return 45; // DOM_VK_MINUS -> GLFW_KEY_MINUS + case 0xBE:return 46; // DOM_VK_PERIOD -> GLFW_KEY_PERIOD + case 0xBF:return 47; // DOM_VK_SLASH -> GLFW_KEY_SLASH + case 0x30:return 48; // DOM_VK_0 -> GLFW_KEY_0 + case 0x31:return 49; // DOM_VK_1 -> GLFW_KEY_1 + case 0x32:return 50; // DOM_VK_2 -> GLFW_KEY_2 + case 0x33:return 51; // DOM_VK_3 -> GLFW_KEY_3 + case 0x34:return 52; // DOM_VK_4 -> GLFW_KEY_4 + case 0x35:return 53; // DOM_VK_5 -> GLFW_KEY_5 + case 0x36:return 54; // DOM_VK_6 -> GLFW_KEY_6 + case 0x37:return 55; // DOM_VK_7 -> GLFW_KEY_7 + case 0x38:return 56; // DOM_VK_8 -> GLFW_KEY_8 + case 0x39:return 57; // DOM_VK_9 -> GLFW_KEY_9 + case 0x3B:return 59; // DOM_VK_SEMICOLON -> GLFW_KEY_SEMICOLON + case 0x3D:return 61; // DOM_VK_EQUALS -> GLFW_KEY_EQUAL + case 0xBB:return 61; // DOM_VK_EQUALS -> GLFW_KEY_EQUAL + case 0x41:return 65; // DOM_VK_A -> GLFW_KEY_A + case 0x42:return 66; // DOM_VK_B -> GLFW_KEY_B + case 0x43:return 67; // DOM_VK_C -> GLFW_KEY_C + case 0x44:return 68; // DOM_VK_D -> GLFW_KEY_D + case 0x45:return 69; // DOM_VK_E -> GLFW_KEY_E + case 0x46:return 70; // DOM_VK_F -> GLFW_KEY_F + case 0x47:return 71; // DOM_VK_G -> GLFW_KEY_G + case 0x48:return 72; // DOM_VK_H -> GLFW_KEY_H + case 0x49:return 73; // DOM_VK_I -> GLFW_KEY_I + case 0x4A:return 74; // DOM_VK_J -> GLFW_KEY_J + case 0x4B:return 75; // DOM_VK_K -> GLFW_KEY_K + case 0x4C:return 76; // DOM_VK_L -> GLFW_KEY_L + case 0x4D:return 77; // DOM_VK_M -> GLFW_KEY_M + case 0x4E:return 78; // DOM_VK_N -> GLFW_KEY_N + case 0x4F:return 79; // DOM_VK_O -> GLFW_KEY_O + case 0x50:return 80; // DOM_VK_P -> GLFW_KEY_P + case 0x51:return 81; // DOM_VK_Q -> GLFW_KEY_Q + case 0x52:return 82; // DOM_VK_R -> GLFW_KEY_R + case 0x53:return 83; // DOM_VK_S -> GLFW_KEY_S + case 0x54:return 84; // DOM_VK_T -> GLFW_KEY_T + case 0x55:return 85; // DOM_VK_U -> GLFW_KEY_U + case 0x56:return 86; // DOM_VK_V -> GLFW_KEY_V + case 0x57:return 87; // DOM_VK_W -> GLFW_KEY_W + case 0x58:return 88; // DOM_VK_X -> GLFW_KEY_X + case 0x59:return 89; // DOM_VK_Y -> GLFW_KEY_Y + case 0x5a:return 90; // DOM_VK_Z -> GLFW_KEY_Z + case 0xDB:return 91; // DOM_VK_OPEN_BRACKET -> GLFW_KEY_LEFT_BRACKET + case 0xDC:return 92; // DOM_VK_BACKSLASH -> GLFW_KEY_BACKSLASH + case 0xDD:return 93; // DOM_VK_CLOSE_BRACKET -> GLFW_KEY_RIGHT_BRACKET + case 0xC0:return 96; // DOM_VK_BACK_QUOTE -> GLFW_KEY_GRAVE_ACCENT + +#if USE_GLFW == 2 + //#define GLFW_KEY_SPECIAL 256 + case 0x1B:return (256+1); // DOM_VK_ESCAPE -> GLFW_KEY_ESC + case 0x70:return (256+2); // DOM_VK_F1 -> GLFW_KEY_F1 + case 0x71:return (256+3); // DOM_VK_F2 -> GLFW_KEY_F2 + case 0x72:return (256+4); // DOM_VK_F3 -> GLFW_KEY_F3 + case 0x73:return (256+5); // DOM_VK_F4 -> GLFW_KEY_F4 + case 0x74:return (256+6); // DOM_VK_F5 -> GLFW_KEY_F5 + case 0x75:return (256+7); // DOM_VK_F6 -> GLFW_KEY_F6 + case 0x76:return (256+8); // DOM_VK_F7 -> GLFW_KEY_F7 + case 0x77:return (256+9); // DOM_VK_F8 -> GLFW_KEY_F8 + case 0x78:return (256+10); // DOM_VK_F9 -> GLFW_KEY_F9 + case 0x79:return (256+11); // DOM_VK_F10 -> GLFW_KEY_F10 + case 0x7A:return (256+12); // DOM_VK_F11 -> GLFW_KEY_F11 + case 0x7B:return (256+13); // DOM_VK_F12 -> GLFW_KEY_F12 + case 0x7C:return (256+14); // DOM_VK_F13 -> GLFW_KEY_F13 + case 0x7D:return (256+15); // DOM_VK_F14 -> GLFW_KEY_F14 + case 0x7E:return (256+16); // DOM_VK_F15 -> GLFW_KEY_F15 + case 0x7F:return (256+17); // DOM_VK_F16 -> GLFW_KEY_F16 + case 0x80:return (256+18); // DOM_VK_F17 -> GLFW_KEY_F17 + case 0x81:return (256+19); // DOM_VK_F18 -> GLFW_KEY_F18 + case 0x82:return (256+20); // DOM_VK_F19 -> GLFW_KEY_F19 + case 0x83:return (256+21); // DOM_VK_F20 -> GLFW_KEY_F20 + case 0x84:return (256+22); // DOM_VK_F21 -> GLFW_KEY_F21 + case 0x85:return (256+23); // DOM_VK_F22 -> GLFW_KEY_F22 + case 0x86:return (256+24); // DOM_VK_F23 -> GLFW_KEY_F23 + case 0x87:return (256+25); // DOM_VK_F24 -> GLFW_KEY_F24 + case 0x88:return (256+26); // 0x88 (not used?) -> GLFW_KEY_F25 + case 0x27:return (256+30); // DOM_VK_RIGHT -> GLFW_KEY_RIGHT + case 0x25:return (256+29); // DOM_VK_LEFT -> GLFW_KEY_LEFT + case 0x28:return (256+28); // DOM_VK_DOWN -> GLFW_KEY_DOWN + case 0x26:return (256+27); // DOM_VK_UP -> GLFW_KEY_UP + case 0x10:return (256+31); // DOM_VK_SHIFT -> GLFW_KEY_LSHIFT + // #define GLFW_KEY_RSHIFT (GLFW_KEY_SPECIAL+32) + case 0x11:return (256+33); // DOM_VK_CONTROL -> GLFW_KEY_LCTRL + // #define GLFW_KEY_RCTRL (GLFW_KEY_SPECIAL+34) + case 0x12:return (256+35); // DOM_VK_ALT -> GLFW_KEY_LALT + // #define GLFW_KEY_RALT (GLFW_KEY_SPECIAL+36) + case 0x09:return (256+37); // DOM_VK_TAB -> GLFW_KEY_TAB + case 0x0D:return (256+38); // DOM_VK_RETURN -> GLFW_KEY_ENTER + case 0x08:return (256+39); // DOM_VK_BACK -> GLFW_KEY_BACKSPACE + case 0x2D:return (256+40); // DOM_VK_INSERT -> GLFW_KEY_INSERT + case 0x2E:return (256+41); // DOM_VK_DELETE -> GLFW_KEY_DEL + case 0x21:return (256+42); // DOM_VK_PAGE_UP -> GLFW_KEY_PAGEUP + case 0x22:return (256+43); // DOM_VK_PAGE_DOWN -> GLFW_KEY_PAGEDOWN + case 0x24:return (256+44); // DOM_VK_HOME -> GLFW_KEY_HOME + case 0x23:return (256+45); // DOM_VK_END -> GLFW_KEY_END + case 0x60:return (256+46); // DOM_VK_NUMPAD0 -> GLFW_KEY_KP_0 + case 0x61:return (256+47); // DOM_VK_NUMPAD1 -> GLFW_KEY_KP_1 + case 0x62:return (256+48); // DOM_VK_NUMPAD2 -> GLFW_KEY_KP_2 + case 0x63:return (256+49); // DOM_VK_NUMPAD3 -> GLFW_KEY_KP_3 + case 0x64:return (256+50); // DOM_VK_NUMPAD4 -> GLFW_KEY_KP_4 + case 0x65:return (256+51); // DOM_VK_NUMPAD5 -> GLFW_KEY_KP_5 + case 0x66:return (256+52); // DOM_VK_NUMPAD6 -> GLFW_KEY_KP_6 + case 0x67:return (256+53); // DOM_VK_NUMPAD7 -> GLFW_KEY_KP_7 + case 0x68:return (256+54); // DOM_VK_NUMPAD8 -> GLFW_KEY_KP_8 + case 0x69:return (256+55); // DOM_VK_NUMPAD9 -> GLFW_KEY_KP_9 + case 0x6F:return (256+56); // DOM_VK_DIVIDE -> GLFW_KEY_KP_DIVIDE + case 0x6A:return (256+57); // DOM_VK_MULTIPLY -> GLFW_KEY_KP_MULTIPLY + case 0x6D:return (256+58); // DOM_VK_SUBTRACT -> GLFW_KEY_KP_SUBTRACT + case 0x6B:return (256+59); // DOM_VK_ADD -> GLFW_KEY_KP_ADD + case 0x6E:return (256+60); // DOM_VK_DECIMAL -> GLFW_KEY_KP_DECIMAL + // #define GLFW_KEY_KP_EQUAL (GLFW_KEY_SPECIAL+61) + // #define GLFW_KEY_KP_ENTER (GLFW_KEY_SPECIAL+62) + case 0x90:return (256+63); // DOM_VK_NUM_LOCK -> GLFW_KEY_KP_NUM_LOCK + case 0x14:return (256+64); // DOM_VK_CAPS_LOCK -> GLFW_KEY_CAPS_LOCK + case 0x91:return (256+65); // DOM_VK_SCROLL_LOCK -> GLFW_KEY_SCROLL_LOCK + case 0x13:return (256+66); // DOM_VK_PAUSE -> GLFW_KEY_PAUSE + case 0x5B:return (256+67); // DOM_VK_WIN -> GLFW_KEY_LSUPER + // #define GLFW_KEY_RSUPER (GLFW_KEY_SPECIAL+68) + case 0x5D:return (256+69); // DOM_VK_CONTEXT_MENU -> GLFW_KEY_MENU +#endif + +#if USE_GLFW == 3 + case 0x1B:return 256; // DOM_VK_ESCAPE -> GLFW_KEY_ESCAPE + case 0x0D:return 257; // DOM_VK_RETURN -> GLFW_KEY_ENTER + case 0x09:return 258; // DOM_VK_TAB -> GLFW_KEY_TAB + case 0x08:return 259; // DOM_VK_BACK -> GLFW_KEY_BACKSPACE + case 0x2D:return 260; // DOM_VK_INSERT -> GLFW_KEY_INSERT + case 0x2E:return 261; // DOM_VK_DELETE -> GLFW_KEY_DELETE + case 0x27:return 262; // DOM_VK_RIGHT -> GLFW_KEY_RIGHT + case 0x25:return 263; // DOM_VK_LEFT -> GLFW_KEY_LEFT + case 0x28:return 264; // DOM_VK_DOWN -> GLFW_KEY_DOWN + case 0x26:return 265; // DOM_VK_UP -> GLFW_KEY_UP + case 0x21:return 266; // DOM_VK_PAGE_UP -> GLFW_KEY_PAGE_UP + case 0x22:return 267; // DOM_VK_PAGE_DOWN -> GLFW_KEY_PAGE_DOWN + case 0x24:return 268; // DOM_VK_HOME -> GLFW_KEY_HOME + case 0x23:return 269; // DOM_VK_END -> GLFW_KEY_END + case 0x14:return 280; // DOM_VK_CAPS_LOCK -> GLFW_KEY_CAPS_LOCK + case 0x91:return 281; // DOM_VK_SCROLL_LOCK -> GLFW_KEY_SCROLL_LOCK + case 0x90:return 282; // DOM_VK_NUM_LOCK -> GLFW_KEY_NUM_LOCK + case 0x2C:return 283; // DOM_VK_SNAPSHOT -> GLFW_KEY_PRINT_SCREEN + case 0x13:return 284; // DOM_VK_PAUSE -> GLFW_KEY_PAUSE + case 0x70:return 290; // DOM_VK_F1 -> GLFW_KEY_F1 + case 0x71:return 291; // DOM_VK_F2 -> GLFW_KEY_F2 + case 0x72:return 292; // DOM_VK_F3 -> GLFW_KEY_F3 + case 0x73:return 293; // DOM_VK_F4 -> GLFW_KEY_F4 + case 0x74:return 294; // DOM_VK_F5 -> GLFW_KEY_F5 + case 0x75:return 295; // DOM_VK_F6 -> GLFW_KEY_F6 + case 0x76:return 296; // DOM_VK_F7 -> GLFW_KEY_F7 + case 0x77:return 297; // DOM_VK_F8 -> GLFW_KEY_F8 + case 0x78:return 298; // DOM_VK_F9 -> GLFW_KEY_F9 + case 0x79:return 299; // DOM_VK_F10 -> GLFW_KEY_F10 + case 0x7A:return 300; // DOM_VK_F11 -> GLFW_KEY_F11 + case 0x7B:return 301; // DOM_VK_F12 -> GLFW_KEY_F12 + case 0x7C:return 302; // DOM_VK_F13 -> GLFW_KEY_F13 + case 0x7D:return 303; // DOM_VK_F14 -> GLFW_KEY_F14 + case 0x7E:return 304; // DOM_VK_F15 -> GLFW_KEY_F15 + case 0x7F:return 305; // DOM_VK_F16 -> GLFW_KEY_F16 + case 0x80:return 306; // DOM_VK_F17 -> GLFW_KEY_F17 + case 0x81:return 307; // DOM_VK_F18 -> GLFW_KEY_F18 + case 0x82:return 308; // DOM_VK_F19 -> GLFW_KEY_F19 + case 0x83:return 309; // DOM_VK_F20 -> GLFW_KEY_F20 + case 0x84:return 310; // DOM_VK_F21 -> GLFW_KEY_F21 + case 0x85:return 311; // DOM_VK_F22 -> GLFW_KEY_F22 + case 0x86:return 312; // DOM_VK_F23 -> GLFW_KEY_F23 + case 0x87:return 313; // DOM_VK_F24 -> GLFW_KEY_F24 + case 0x88:return 314; // 0x88 (not used?) -> GLFW_KEY_F25 + case 0x60:return 320; // DOM_VK_NUMPAD0 -> GLFW_KEY_KP_0 + case 0x61:return 321; // DOM_VK_NUMPAD1 -> GLFW_KEY_KP_1 + case 0x62:return 322; // DOM_VK_NUMPAD2 -> GLFW_KEY_KP_2 + case 0x63:return 323; // DOM_VK_NUMPAD3 -> GLFW_KEY_KP_3 + case 0x64:return 324; // DOM_VK_NUMPAD4 -> GLFW_KEY_KP_4 + case 0x65:return 325; // DOM_VK_NUMPAD5 -> GLFW_KEY_KP_5 + case 0x66:return 326; // DOM_VK_NUMPAD6 -> GLFW_KEY_KP_6 + case 0x67:return 327; // DOM_VK_NUMPAD7 -> GLFW_KEY_KP_7 + case 0x68:return 328; // DOM_VK_NUMPAD8 -> GLFW_KEY_KP_8 + case 0x69:return 329; // DOM_VK_NUMPAD9 -> GLFW_KEY_KP_9 + case 0x6E:return 330; // DOM_VK_DECIMAL -> GLFW_KEY_KP_DECIMAL + case 0x6F:return 331; // DOM_VK_DIVIDE -> GLFW_KEY_KP_DIVIDE + case 0x6A:return 332; // DOM_VK_MULTIPLY -> GLFW_KEY_KP_MULTIPLY + case 0x6D:return 333; // DOM_VK_SUBTRACT -> GLFW_KEY_KP_SUBTRACT + case 0x6B:return 334; // DOM_VK_ADD -> GLFW_KEY_KP_ADD + // case 0x0D:return 335; // DOM_VK_RETURN -> GLFW_KEY_KP_ENTER (DOM_KEY_LOCATION_RIGHT) + // case 0x61:return 336; // DOM_VK_EQUALS -> GLFW_KEY_KP_EQUAL (DOM_KEY_LOCATION_RIGHT) + case 0x10:return 340; // DOM_VK_SHIFT -> GLFW_KEY_LEFT_SHIFT + case 0x11:return 341; // DOM_VK_CONTROL -> GLFW_KEY_LEFT_CONTROL + case 0x12:return 342; // DOM_VK_ALT -> GLFW_KEY_LEFT_ALT + case 0x5B:return 343; // DOM_VK_WIN -> GLFW_KEY_LEFT_SUPER + case 0xE0:return 343; // DOM_VK_META -> GLFW_KEY_LEFT_SUPER + // case 0x10:return 344; // DOM_VK_SHIFT -> GLFW_KEY_RIGHT_SHIFT (DOM_KEY_LOCATION_RIGHT) + // case 0x11:return 345; // DOM_VK_CONTROL -> GLFW_KEY_RIGHT_CONTROL (DOM_KEY_LOCATION_RIGHT) + // case 0x12:return 346; // DOM_VK_ALT -> GLFW_KEY_RIGHT_ALT (DOM_KEY_LOCATION_RIGHT) + // case 0x5B:return 347; // DOM_VK_WIN -> GLFW_KEY_RIGHT_SUPER (DOM_KEY_LOCATION_RIGHT) + case 0x5D:return 348; // DOM_VK_CONTEXT_MENU -> GLFW_KEY_MENU + // XXX: GLFW_KEY_WORLD_1, GLFW_KEY_WORLD_2 what are these? +#endif + default:return -1; // GLFW_KEY_UNKNOWN + }; + }, + + getModBits: (win) => { + var mod = 0; + if (win.keys[340]) mod |= 0x0001; // GLFW_MOD_SHIFT + if (win.keys[341]) mod |= 0x0002; // GLFW_MOD_CONTROL + if (win.keys[342]) mod |= 0x0004; // GLFW_MOD_ALT + if (win.keys[343] || win.keys[348]) mod |= 0x0008; // GLFW_MOD_SUPER + // add caps and num lock keys? only if lock_key_mod is set + return mod; + }, + + onKeyPress: (event) => { + if (!GLFW.active || !GLFW.active.charFunc) return; + if (event.ctrlKey || event.metaKey) return; + + // correct unicode charCode is only available with onKeyPress event + var charCode = event.charCode; + if (charCode == 0 || (charCode >= 0x00 && charCode <= 0x1F)) return; + +#if USE_GLFW == 2 + {{{ makeDynCall('vii', 'GLFW.active.charFunc') }}}(charCode, 1); +#endif +#if USE_GLFW == 3 + {{{ makeDynCall('vpi', 'GLFW.active.charFunc') }}}(GLFW.active.id, charCode); +#endif + }, + + onKeyChanged: (keyCode, status) => { + if (!GLFW.active) return; + + var key = GLFW.DOMToGLFWKeyCode(keyCode); + if (key == -1) return; + +#if USE_GLFW == 3 + var repeat = status && GLFW.active.keys[key]; +#endif + GLFW.active.keys[key] = status; + GLFW.active.domKeys[keyCode] = status; + + if (GLFW.active.keyFunc) { +#if USE_GLFW == 2 + {{{ makeDynCall('vii', 'GLFW.active.keyFunc') }}}(key, status); +#endif +#if USE_GLFW == 3 + if (repeat) status = 2; // GLFW_REPEAT + {{{ makeDynCall('vpiiii', 'GLFW.active.keyFunc') }}}(GLFW.active.id, key, keyCode, status, GLFW.getModBits(GLFW.active)); +#endif + } + }, + + onGamepadConnected: (event) => { + GLFW.refreshJoysticks(); + }, + + onGamepadDisconnected: (event) => { + GLFW.refreshJoysticks(); + }, + + onKeydown: (event) => { + GLFW.onKeyChanged(event.keyCode, 1); // GLFW_PRESS or GLFW_REPEAT + + // This logic comes directly from the sdl implementation. We cannot + // call preventDefault on all keydown events otherwise onKeyPress will + // not get called + if (event.key == 'Backspace' || event.key == 'Tab') { + event.preventDefault(); + } + }, + + onKeyup: (event) => { + GLFW.onKeyChanged(event.keyCode, 0); // GLFW_RELEASE + }, + + onBlur: (event) => { + if (!GLFW.active) return; + + for (var i = 0; i < GLFW.active.domKeys.length; ++i) { + if (GLFW.active.domKeys[i]) { + GLFW.onKeyChanged(i, 0); // GLFW_RELEASE + } + } + }, + + onMousemove: (event) => { + if (!GLFW.active) return; + + if (event.type === 'touchmove') { + // Handling for touch events that are being converted to mouse input. + + // Don't let the browser fire a duplicate mouse event. + event.preventDefault(); + + let primaryChanged = false; + for (let i of event.changedTouches) { + // If our chosen primary touch moved, update Browser mouse coords + if (GLFW.primaryTouchId === i.identifier) { + Browser.setMouseCoords(i.pageX, i.pageY); + primaryChanged = true; + break; + } + } + + if (!primaryChanged) { + // Do not send mouse events if some touch other than the primary triggered this. + return; + } + + } else { + // Handling for non-touch mouse input events. + Browser.calculateMouseEvent(event); + } + + if (event.target != Browser.getCanvas() || !GLFW.active.cursorPosFunc) return; + + if (GLFW.active.cursorPosFunc) { +#if USE_GLFW == 2 + {{{ makeDynCall('vii', 'GLFW.active.cursorPosFunc') }}}(Browser.mouseX, Browser.mouseY); +#endif +#if USE_GLFW == 3 + {{{ makeDynCall('vpdd', 'GLFW.active.cursorPosFunc') }}}(GLFW.active.id, Browser.mouseX, Browser.mouseY); +#endif + } + }, + + DOMToGLFWMouseButton: (event) => { + // DOM and glfw have different button codes. + // See http://www.w3schools.com/jsref/event_button.asp. + var eventButton = event['button']; + if (eventButton > 0) { + if (eventButton == 1) { + eventButton = 2; + } else { + eventButton = 1; + } + } + return eventButton; + }, + + onMouseenter: (event) => { + if (!GLFW.active) return; + + if (event.target != Browser.getCanvas()) return; + +#if USE_GLFW == 3 + if (GLFW.active.cursorEnterFunc) { + {{{ makeDynCall('vpi', 'GLFW.active.cursorEnterFunc') }}}(GLFW.active.id, 1); + } +#endif + }, + + onMouseleave: (event) => { + if (!GLFW.active) return; + + if (event.target != Browser.getCanvas()) return; + +#if USE_GLFW == 3 + if (GLFW.active.cursorEnterFunc) { + {{{ makeDynCall('vpi', 'GLFW.active.cursorEnterFunc') }}}(GLFW.active.id, 0); + } +#endif + }, + + onMouseButtonChanged: (event, status) => { + if (!GLFW.active) return; + + if (event.target != Browser.getCanvas()) return; + + // Is this from a touch event? + const isTouchType = event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchcancel'; + + // Only emulating mouse left-click behavior for touches. + let eventButton = 0; + if (isTouchType) { + // Handling for touch events that are being converted to mouse input. + + // Don't let the browser fire a duplicate mouse event. + event.preventDefault(); + + let primaryChanged = false; + + // Set a primary touch if we have none. + if (GLFW.primaryTouchId === null && event.type === 'touchstart' && event.targetTouches.length > 0) { + // Pick the first touch that started in the canvas and treat it as primary. + const chosenTouch = event.targetTouches[0]; + GLFW.primaryTouchId = chosenTouch.identifier; + + Browser.setMouseCoords(chosenTouch.pageX, chosenTouch.pageY); + primaryChanged = true; + } else if (event.type === 'touchend' || event.type === 'touchcancel') { + // Clear the primary touch if it ended. + for (let i of event.changedTouches) { + // If our chosen primary touch ended, remove it. + if (GLFW.primaryTouchId === i.identifier) { + GLFW.primaryTouchId = null; + primaryChanged = true; + break; + } + } + } + + if (!primaryChanged) { + // Do not send mouse events if some touch other than the primary triggered this. + return; + } + + } else { + // Handling for non-touch mouse input events. + Browser.calculateMouseEvent(event); + eventButton = GLFW.DOMToGLFWMouseButton(event); + } + + if (status == 1) { // GLFW_PRESS + GLFW.active.buttons |= (1 << eventButton); + try { + event.target.setCapture(); + } catch (e) {} + } else { // GLFW_RELEASE + GLFW.active.buttons &= ~(1 << eventButton); + } + + // Send mouse event to GLFW. + if (GLFW.active.mouseButtonFunc) { +#if USE_GLFW == 2 + {{{ makeDynCall('vii', 'GLFW.active.mouseButtonFunc') }}}(eventButton, status); +#endif +#if USE_GLFW == 3 + {{{ makeDynCall('vpiii', 'GLFW.active.mouseButtonFunc') }}}(GLFW.active.id, eventButton, status, GLFW.getModBits(GLFW.active)); +#endif + } + }, + + onMouseButtonDown: (event) => { + if (!GLFW.active) return; + GLFW.onMouseButtonChanged(event, 1); // GLFW_PRESS + }, + + onMouseButtonUp: (event) => { + if (!GLFW.active) return; + GLFW.onMouseButtonChanged(event, 0); // GLFW_RELEASE + }, + + onMouseWheel: (event) => { + // Note the minus sign that flips browser wheel direction (positive direction scrolls page down) to native wheel direction (positive direction is mouse wheel up) + var delta = -Browser.getMouseWheelDelta(event); + delta = (delta == 0) ? 0 : (delta > 0 ? Math.max(delta, 1) : Math.min(delta, -1)); // Quantize to integer so that minimum scroll is at least +/- 1. + GLFW.wheelPos += delta; + + if (!GLFW.active || !GLFW.active.scrollFunc || event.target != Browser.getCanvas()) return; +#if USE_GLFW == 2 + {{{ makeDynCall('vi', 'GLFW.active.scrollFunc') }}}(GLFW.wheelPos); +#endif +#if USE_GLFW == 3 + var sx = 0; + var sy = delta; + if (event.type == 'mousewheel') { + sx = event.wheelDeltaX; + } else { + sx = event.deltaX; + } + + {{{ makeDynCall('vpdd', 'GLFW.active.scrollFunc') }}}(GLFW.active.id, sx, sy); +#endif + + event.preventDefault(); + }, + + // width/height are the dimensions in screen coordinates the user interact with (ex: drawing, mouse coordinates...) + // framebufferWidth/framebufferHeight are the dimensions in pixel coordinates used for rendering + // in a HiDPI scenario framebufferWidth = devicePixelRatio * width + onCanvasResize: (width, height, framebufferWidth, framebufferHeight) => { + if (!GLFW.active) return; + + var resizeNeeded = false; + + // If the client is requesting fullscreen mode + if (getFullscreenElement()) { + if (!GLFW.active.fullscreen) { + resizeNeeded = width != screen.width || height != screen.height; + GLFW.active.storedX = GLFW.active.x; + GLFW.active.storedY = GLFW.active.y; + GLFW.active.storedWidth = GLFW.active.width; + GLFW.active.storedHeight = GLFW.active.height; + GLFW.active.x = GLFW.active.y = 0; + GLFW.active.width = screen.width; + GLFW.active.height = screen.height; + GLFW.active.fullscreen = true; + } + // If the client is reverting from fullscreen mode + } else if (GLFW.active.fullscreen == true) { + resizeNeeded = width != GLFW.active.storedWidth || height != GLFW.active.storedHeight; + GLFW.active.x = GLFW.active.storedX; + GLFW.active.y = GLFW.active.storedY; + GLFW.active.width = GLFW.active.storedWidth; + GLFW.active.height = GLFW.active.storedHeight; + GLFW.active.fullscreen = false; + } + + if (resizeNeeded) { + // width or height is changed (fullscreen / exit fullscreen) which will call this listener back + // with proper framebufferWidth/framebufferHeight + Browser.setCanvasSize(GLFW.active.width, GLFW.active.height); + } else if (GLFW.active.width != width || + GLFW.active.height != height || + GLFW.active.framebufferWidth != framebufferWidth || + GLFW.active.framebufferHeight != framebufferHeight) { + GLFW.active.width = width; + GLFW.active.height = height; + GLFW.active.framebufferWidth = framebufferWidth; + GLFW.active.framebufferHeight = framebufferHeight; + GLFW.onWindowSizeChanged(); + GLFW.onFramebufferSizeChanged(); + } + }, + + onWindowSizeChanged: () => { + if (!GLFW.active) return; + + if (GLFW.active.windowSizeFunc) { +#if USE_GLFW == 2 + {{{ makeDynCall('vii', 'GLFW.active.windowSizeFunc') }}}(GLFW.active.width, GLFW.active.height); +#endif +#if USE_GLFW == 3 + {{{ makeDynCall('vpii', 'GLFW.active.windowSizeFunc') }}}(GLFW.active.id, GLFW.active.width, GLFW.active.height); +#endif + } + }, + + onFramebufferSizeChanged: () => { + if (!GLFW.active) return; + +#if USE_GLFW == 3 + if (GLFW.active.framebufferSizeFunc) { + {{{ makeDynCall('vpii', 'GLFW.active.framebufferSizeFunc') }}}(GLFW.active.id, GLFW.active.framebufferWidth, GLFW.active.framebufferHeight); + } +#endif + }, + + onWindowContentScaleChanged: (scale) => { + GLFW.scale = scale; + if (!GLFW.active) return; + +#if USE_GLFW == 3 + if (GLFW.active.windowContentScaleFunc) { + {{{ makeDynCall('vpff', 'GLFW.active.windowContentScaleFunc') }}}(GLFW.active.id, GLFW.scale, GLFW.scale); + } +#endif + }, + + getTime: () => _emscripten_get_now() / 1000, + + /* GLFW2 wrapping */ + + setWindowTitle: (winid, title) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + + win.title = title; + if (GLFW.active.id == win.id) { + _emscripten_set_window_title(title); + } + }, + + setJoystickCallback: (cbfun) => { + var prevcbfun = GLFW.joystickFunc; + GLFW.joystickFunc = cbfun; + GLFW.refreshJoysticks(); + return prevcbfun; + }, + + joys: {}, // glfw joystick data + lastGamepadState: [], + lastGamepadStateFrame: null, // The integer value of MainLoop.currentFrameNumber of when the last gamepad state was produced. + + refreshJoysticks: () => { + // Produce a new Gamepad API sample if we are ticking a new game frame, or if not using emscripten_set_main_loop() at all to drive animation. + if (MainLoop.currentFrameNumber !== GLFW.lastGamepadStateFrame || !MainLoop.currentFrameNumber) { + GLFW.lastGamepadState = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads || []); + GLFW.lastGamepadStateFrame = MainLoop.currentFrameNumber; + + for (var joy = 0; joy < GLFW.lastGamepadState.length; ++joy) { + var gamepad = GLFW.lastGamepadState[joy]; + + if (gamepad) { + if (!GLFW.joys[joy]) { + out('glfw joystick connected:',joy); + GLFW.joys[joy] = { + id: stringToNewUTF8(gamepad.id), + buttonsCount: gamepad.buttons.length, + axesCount: gamepad.axes.length, + buttons: _malloc(gamepad.buttons.length), + axes: _malloc(gamepad.axes.length*4), + }; + + if (GLFW.joystickFunc) { + {{{ makeDynCall('vii', 'GLFW.joystickFunc') }}}(joy, 0x00040001); // GLFW_CONNECTED + } + } + + var data = GLFW.joys[joy]; + + for (var i = 0; i < gamepad.buttons.length; ++i) { + {{{ makeSetValue('data.buttons + i', '0', 'gamepad.buttons[i].pressed', 'i8') }}}; + } + + for (var i = 0; i < gamepad.axes.length; ++i) { + {{{ makeSetValue('data.axes + i*4', '0', 'gamepad.axes[i]', 'float') }}}; + } + } else { + if (GLFW.joys[joy]) { + out('glfw joystick disconnected',joy); + + if (GLFW.joystickFunc) { + {{{ makeDynCall('vii', 'GLFW.joystickFunc') }}}(joy, 0x00040002); // GLFW_DISCONNECTED + } + + _free(GLFW.joys[joy].id); + _free(GLFW.joys[joy].buttons); + _free(GLFW.joys[joy].axes); + + delete GLFW.joys[joy]; + } + } + } + } + }, + + setKeyCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.keyFunc; + win.keyFunc = cbfun; + return prevcbfun; + }, + + setCharCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.charFunc; + win.charFunc = cbfun; + return prevcbfun; + }, + + setMouseButtonCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.mouseButtonFunc; + win.mouseButtonFunc = cbfun; + return prevcbfun; + }, + + setCursorPosCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.cursorPosFunc; + win.cursorPosFunc = cbfun; + return prevcbfun; + }, + + setScrollCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.scrollFunc; + win.scrollFunc = cbfun; + return prevcbfun; + }, + + setDropCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.dropFunc; + win.dropFunc = cbfun; + return prevcbfun; + }, + + onDrop: (event) => { + if (!GLFW.active || !GLFW.active.dropFunc) return; + if (!event.dataTransfer || !event.dataTransfer.files || event.dataTransfer.files.length == 0) return; + + event.preventDefault(); + +#if FILESYSTEM + var drop_dir = '.glfw_dropped_files'; + var filenames = _malloc(event.dataTransfer.files.length * {{{ POINTER_SIZE }}}); + var filenamesArray = []; + for (var i = 0; i < event.dataTransfer.files.length; ++i) { + var path = `/${drop_dir}/${event.dataTransfer.files[i].name.replace(/\//g, "_")}`; + var filename = stringToNewUTF8(path); + filenamesArray.push(filename); + {{{ makeSetValue('filenames', `i*${POINTER_SIZE}` , 'filename', '*') }}}; + } + + // Read and save the files to emscripten's FS + var written = 0; + FS.createPath('/', drop_dir); + + function save(file, in_path, numfiles) { + var path = '/' + drop_dir + in_path + '/' + file.name.replace(/\//g, '_'); + var reader = new FileReader(); + reader.onloadend = (e) => { + if (reader.readyState != 2) { // not DONE + ++written; + err(`failed to read dropped file: ${in_path}/${file.name}: ${reader.error}`); + return; + } + + var data = e.target.result; + FS.writeFile(path, new Uint8Array(data)); + if (++written === numfiles) { + {{{ makeDynCall('vpip', 'GLFW.active.dropFunc') }}}(GLFW.active.id, filenamesArray.length, filenames); + + for (var i = 0; i < filenamesArray.length; ++i) { + _free(filenamesArray[i]); + } + _free(filenames); + } + }; + reader.readAsArrayBuffer(file); + } + + let filesQ = []; + function finalize() { + var count = filesQ.length; + for (var i = 0; i < count; ++i) { + save(filesQ[i].file, filesQ[i].path, count); + } + } + + if (DataTransferItem.prototype.webkitGetAsEntry) { + let entriesTree = {}; + function markDone(fullpath, recursive) { + if (entriesTree[fullpath].subpaths.length != 0) return; + delete entriesTree[fullpath]; + let parentpath = fullpath.substring(0, fullpath.lastIndexOf('/')); + if (!entriesTree.hasOwnProperty(parentpath)) { + if (Object.keys(entriesTree).length == 0) finalize(); + return; + } + const fpIndex = entriesTree[parentpath].subpaths.indexOf(fullpath); + if (fpIndex > -1) entriesTree[parentpath].subpaths.splice(fpIndex, 1); + if (recursive) markDone(parentpath, true); + if (Object.keys(entriesTree).length == 0) finalize(); + } + function processEntry(entry) { + let fp = entry.fullPath; + let pp = fp.substring(0, fp.lastIndexOf('/')); + entriesTree[fp] = { subpaths: [] }; + if (entry.isFile) { + entry.file((f) => { filesQ.push({ file: f, path: pp }); markDone(fp, false); }) + } else if (entry.isDirectory) { + if (entriesTree.hasOwnProperty(pp)) entriesTree[pp].subpaths.push(fp); + FS.createPath("/" + drop_dir + pp, entry.name); + var reader = entry.createReader(); + var rRead = function (dirEntries) { + if (dirEntries.length == 0) { + markDone(fp, true); + return; + } + for (const ent of dirEntries) processEntry(ent); + reader.readEntries(rRead); + }; + reader.readEntries(rRead); + } + } + for (const item of event.dataTransfer.items) { + processEntry(item.webkitGetAsEntry()); + } + } else { + // fallback for browsers that does not support webkitGetAsEntry + for (const file of event.dataTransfer.files) { + filesQ.push({ file: file, path: "" }); + } + finalize(); + } +#endif // FILESYSTEM + + return false; + }, + + onDragover: (event) => { + if (!GLFW.active || !GLFW.active.dropFunc) return; + + event.preventDefault(); + return false; + }, + + setWindowSizeCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowSizeFunc; + win.windowSizeFunc = cbfun; + +#if USE_GLFW == 2 + // As documented in GLFW2 API (http://www.glfw.org/GLFWReference27.pdf#page=22), when size + // callback function is set, it will be called with the current window size before this + // function returns. + // GLFW3 on the other hand doesn't have this behavior (https://github.com/glfw/glfw/issues/62). + if (!win.windowSizeFunc) return null; + {{{ makeDynCall('vii', 'win.windowSizeFunc') }}}(win.width, win.height); +#endif + + return prevcbfun; + }, + + setWindowCloseCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowCloseFunc; + win.windowCloseFunc = cbfun; + return prevcbfun; + }, + + setWindowRefreshCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowRefreshFunc; + win.windowRefreshFunc = cbfun; + return prevcbfun; + }, + + onClickRequestPointerLock: (e) => { + var canvas = Browser.getCanvas(); + if (!Browser.pointerLock && canvas.requestPointerLock) { + canvas.requestPointerLock(); + e.preventDefault(); + } + }, + + setInputMode: (winid, mode, value) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + + switch (mode) { + case 0x00033001: { // GLFW_CURSOR + var canvas = Browser.getCanvas(); + switch (value) { + case 0x00034001: { // GLFW_CURSOR_NORMAL + win.inputModes[mode] = value; + canvas.removeEventListener('click', GLFW.onClickRequestPointerLock, true); + document.exitPointerLock(); + break; + } + case 0x00034002: { // GLFW_CURSOR_HIDDEN + err('glfwSetInputMode called with GLFW_CURSOR_HIDDEN value not implemented'); + break; + } + case 0x00034003: { // GLFW_CURSOR_DISABLED + win.inputModes[mode] = value; + canvas.addEventListener('click', GLFW.onClickRequestPointerLock, true); + canvas.requestPointerLock(); + break; + } + default: { + err(`glfwSetInputMode called with unknown value parameter value: ${value}`); + break; + } + } + break; + } + case 0x00033002: { // GLFW_STICKY_KEYS + err('glfwSetInputMode called with GLFW_STICKY_KEYS mode not implemented'); + break; + } + case 0x00033003: { // GLFW_STICKY_MOUSE_BUTTONS + err('glfwSetInputMode called with GLFW_STICKY_MOUSE_BUTTONS mode not implemented'); + break; + } + case 0x00033004: { // GLFW_LOCK_KEY_MODS + err('glfwSetInputMode called with GLFW_LOCK_KEY_MODS mode not implemented'); + break; + } + case 0x00033005: { // GLFW_RAW_MOUSE_MOTION + err('glfwSetInputMode called with GLFW_RAW_MOUSE_MOTION mode not implemented'); + break; + } + default: { + err(`glfwSetInputMode called with unknown mode parameter value: ${mode}`); + break; + } + } + }, + + getKey: (winid, key) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return win.keys[key]; + }, + + getMouseButton: (winid, button) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return (win.buttons & (1 << button)) > 0; + }, + + getCursorPos: (winid, x, y) => { + {{{ makeSetValue('x', '0', 'Browser.mouseX', 'double') }}}; + {{{ makeSetValue('y', '0', 'Browser.mouseY', 'double') }}}; + }, + + getMousePos: (winid, x, y) => { + {{{ makeSetValue('x', '0', 'Browser.mouseX', 'i32') }}}; + {{{ makeSetValue('y', '0', 'Browser.mouseY', 'i32') }}}; + }, + + setCursorPos: (winid, x, y) => { + }, + + getWindowPos: (winid, x, y) => { + var wx = 0; + var wy = 0; + + var win = GLFW.WindowFromId(winid); + if (win) { + wx = win.x; + wy = win.y; + } + + if (x) { + {{{ makeSetValue('x', '0', 'wx', 'i32') }}}; + } + + if (y) { + {{{ makeSetValue('y', '0', 'wy', 'i32') }}}; + } + }, + + setWindowPos: (winid, x, y) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + win.x = x; + win.y = y; + }, + + getWindowSize: (winid, width, height) => { + var ww = 0; + var wh = 0; + + var win = GLFW.WindowFromId(winid); + if (win) { + ww = win.width; + wh = win.height; + } + + if (width) { + {{{ makeSetValue('width', '0', 'ww', 'i32') }}}; + } + + if (height) { + {{{ makeSetValue('height', '0', 'wh', 'i32') }}}; + } + }, + + setWindowSize: (winid, width, height) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + + if (GLFW.active.id == win.id) { + Browser.setCanvasSize(width, height); // triggers the listener (onCanvasResize) + windowSizeFunc + } + }, + + defaultWindowHints: () => { + GLFW.hints = {...GLFW.defaultHints}; + }, + + createWindow: (width, height, title, monitor, share) => { + var i, id; + for (i = 0; i < GLFW.windows.length && GLFW.windows[i] !== null; i++) { + // no-op + } + if (i > 0) abort("glfwCreateWindow only supports one window at time currently"); + + // id for window + id = i + 1; + + // not valid + if (width <= 0 || height <= 0) return 0; + + if (monitor) { + Browser.requestFullscreen(); + } else { + Browser.setCanvasSize(width, height); + } + + // Create context when there are no existing alive windows + for (i = 0; i < GLFW.windows.length && GLFW.windows[i] == null; i++) { + // no-op + } + + const canvas = Browser.getCanvas(); + + var useWebGL = GLFW.hints[0x00022001] > 0; // Use WebGL when we are told to based on GLFW_CLIENT_API + if (i == GLFW.windows.length) { + if (useWebGL) { + var contextAttributes = { + antialias: (GLFW.hints[0x0002100D] > 1), // GLFW_SAMPLES + depth: (GLFW.hints[0x00021005] > 0), // GLFW_DEPTH_BITS + stencil: (GLFW.hints[0x00021006] > 0), // GLFW_STENCIL_BITS + alpha: (GLFW.hints[0x00021004] > 0) // GLFW_ALPHA_BITS + } +#if OFFSCREEN_FRAMEBUFFER + // TODO: Make GLFW explicitly aware of whether it is being proxied or not, and set these to true only when proxying is being performed. + GL.enableOffscreenFramebufferAttributes(contextAttributes); +#endif + Browser.createContext(canvas, /*useWebGL=*/true, /*setInModule=*/true, contextAttributes); + } else { + Browser.init(); + } + } + + // If context creation failed, do not return a valid window + if (!Module['ctx'] && useWebGL) return 0; + + // Initializes the framebuffer size from the canvas + var win = new GLFW_Window(id, width, height, canvas.width, canvas.height, title, monitor, share); + + // Set window to array + if (id - 1 == GLFW.windows.length) { + GLFW.windows.push(win); + } else { + GLFW.windows[id - 1] = win; + } + + GLFW.active = win; + GLFW.adjustCanvasDimensions(); + return win.id; + }, + + destroyWindow: (winid) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + +#if USE_GLFW == 3 + if (win.windowCloseFunc) { + {{{ makeDynCall('vp', 'win.windowCloseFunc') }}}(win.id); + } +#endif + + GLFW.windows[win.id - 1] = null; + if (GLFW.active.id == win.id) { + GLFW.active = null; + } + + // Destroy context when no alive windows + for (win of GLFW.windows) { + if (win !== null) return; + } + + delete Module['ctx']; + }, + + swapBuffers: (winid) => { + }, + + // Overrides Browser.requestFullscreen to notify listeners even if Browser.resizeCanvas is false + requestFullscreen(lockPointer, resizeCanvas) { + Browser.lockPointer = lockPointer; + Browser.resizeCanvas = resizeCanvas; + if (typeof Browser.lockPointer == 'undefined') Browser.lockPointer = true; + if (typeof Browser.resizeCanvas == 'undefined') Browser.resizeCanvas = false; + + var canvas = Browser.getCanvas(); + function fullscreenChange() { + Browser.isFullscreen = false; + var canvasContainer = canvas.parentNode; + if (getFullscreenElement() === canvasContainer) { + canvas.exitFullscreen = Browser.exitFullscreen; + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullscreen = true; + if (Browser.resizeCanvas) { + Browser.setFullscreenCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + Browser.updateResizeListeners(); + } + } else { + // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen + canvasContainer.parentNode.insertBefore(canvas, canvasContainer); + canvasContainer.parentNode.removeChild(canvasContainer); + + if (Browser.resizeCanvas) { + Browser.setWindowedCanvasSize(); + } else { + Browser.updateCanvasDimensions(canvas); + Browser.updateResizeListeners(); + } + } + Module['onFullScreen']?.(Browser.isFullscreen); + Module['onFullscreen']?.(Browser.isFullscreen); + } + + if (!Browser.fullscreenHandlersInstalled) { + Browser.fullscreenHandlersInstalled = true; + document.addEventListener('fullscreenchange', fullscreenChange, false); + document.addEventListener('mozfullscreenchange', fullscreenChange, false); + document.addEventListener('webkitfullscreenchange', fullscreenChange, false); + document.addEventListener('MSFullscreenChange', fullscreenChange, false); + } + + // 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 + var canvasContainer = document.createElement("div"); + canvas.parentNode.insertBefore(canvasContainer, canvas); + canvasContainer.appendChild(canvas); + + // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) + canvasContainer.requestFullscreen = canvasContainer['requestFullscreen'] || + canvasContainer['mozRequestFullScreen'] || + canvasContainer['msRequestFullscreen'] || + (canvasContainer['webkitRequestFullscreen'] ? () => canvasContainer['webkitRequestFullscreen'](Element['ALLOW_KEYBOARD_INPUT']) : null) || + (canvasContainer['webkitRequestFullScreen'] ? () => canvasContainer['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) : null); + + canvasContainer.requestFullscreen(); + }, + + // Overrides Browser.updateCanvasDimensions to account for hi dpi scaling + updateCanvasDimensions(canvas, wNative, hNative) { + const scale = GLFW.getHiDPIScale(); + + if (wNative && hNative) { + canvas.widthNative = wNative; + canvas.heightNative = hNative; + } else { + wNative = canvas.widthNative; + hNative = canvas.heightNative; + } + var w = wNative; + var h = hNative; + if (Module['forcedAspectRatio'] && Module['forcedAspectRatio'] > 0) { + if (w/h < Module['forcedAspectRatio']) { + w = Math.round(h * Module['forcedAspectRatio']); + } else { + h = Math.round(w / Module['forcedAspectRatio']); + } + } + if ((getFullscreenElement() === canvas.parentNode) && (typeof screen != 'undefined')) { + var factor = Math.min(screen.width / w, screen.height / h); + w = Math.round(w * factor); + h = Math.round(h * factor); + } + if (Browser.resizeCanvas) { + wNative = w; + hNative = h; + } + const wNativeScaled = Math.floor(wNative * scale); + const hNativeScaled = Math.floor(hNative * scale); + if (canvas.width != wNativeScaled) canvas.width = wNativeScaled; + if (canvas.height != hNativeScaled) canvas.height = hNativeScaled; + if (typeof canvas.style != 'undefined') { + if (!GLFW.isCSSScalingEnabled()) { + canvas.style.setProperty( "width", wNative + "px", "important"); + canvas.style.setProperty("height", hNative + "px", "important"); + } else { + canvas.style.removeProperty( "width"); + canvas.style.removeProperty("height"); + } + } + }, + + // Overrides Browser.calculateMouseCoords to account for HiDPI scaling and CSS scaling + calculateMouseCoords(pageX, pageY) { + // Calculate the movement based on the changes + // in the coordinates. + const rect = Browser.getCanvas().getBoundingClientRect(); + + // Neither .scrollX or .pageXOffset are defined in a spec, but + // we prefer .scrollX because it is currently in a spec draft. + // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) + var scrollX = ((typeof window.scrollX != 'undefined') ? window.scrollX : window.pageXOffset); + var scrollY = ((typeof window.scrollY != 'undefined') ? window.scrollY : window.pageYOffset); +#if ASSERTIONS + // If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset + // and we have no viable fallback. + assert((typeof scrollX != 'undefined') && (typeof scrollY != 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.'); +#endif + var adjustedX = pageX - (scrollX + rect.left); + var adjustedY = pageY - (scrollY + rect.top); + + // getBoundingClientRect() returns dimension affected by CSS, so as a result: + // - when CSS scaling is enabled, this will fix the mouse coordinates to match the width/height of the window + // - otherwise the CSS width/height are forced to the width/height of the GLFW window (see updateCanvasDimensions), + // so there is no need to adjust the position + if (GLFW.isCSSScalingEnabled() && GLFW.active) { + adjustedX = adjustedX * (GLFW.active.width / rect.width); + adjustedY = adjustedY * (GLFW.active.height / rect.height); + } + + return { x: adjustedX, y: adjustedY }; + }, + + setWindowAttrib: (winid, attrib, value) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + const isHiDPIAware = GLFW.isHiDPIAware(); + win.attributes[attrib] = value; + if (isHiDPIAware !== GLFW.isHiDPIAware()) + GLFW.adjustCanvasDimensions(); + }, + + getDevicePixelRatio() { + return (typeof devicePixelRatio == 'number' && devicePixelRatio) || 1.0; + }, + + isHiDPIAware() { + if (GLFW.active) + return GLFW.active.attributes[0x0002200C] > 0; // GLFW_SCALE_TO_MONITOR + else + return false; + }, + + /** + * CSS Scaling is a feature that is NOT part of the GLFW API, but for historical reasons, it is available + * in Emscripten. + * It is automatically disabled when using Hi DPI (the library overrides CSS sizes). */ + isCSSScalingEnabled() { + return !GLFW.isHiDPIAware(); + }, + + adjustCanvasDimensions() { + if (GLFW.active) { + Browser.updateCanvasDimensions(Browser.getCanvas(), GLFW.active.width, GLFW.active.height); + Browser.updateResizeListeners(); + } + }, + + getHiDPIScale() { + return GLFW.isHiDPIAware() ? GLFW.scale : 1.0; + }, + + onDevicePixelRatioChange() { + GLFW.onWindowContentScaleChanged(GLFW.getDevicePixelRatio()); + GLFW.adjustCanvasDimensions(); + }, + + GLFW2ParamToGLFW3Param: (param) => { + var table = { + 0x00030001:0, // GLFW_MOUSE_CURSOR + 0x00030002:0, // GLFW_STICKY_KEYS + 0x00030003:0, // GLFW_STICKY_MOUSE_BUTTONS + 0x00030004:0, // GLFW_SYSTEM_KEYS + 0x00030005:0, // GLFW_KEY_REPEAT + 0x00030006:0, // GLFW_AUTO_POLL_EVENTS + 0x00020001:0, // GLFW_OPENED + 0x00020002:0, // GLFW_ACTIVE + 0x00020003:0, // GLFW_ICONIFIED + 0x00020004:0, // GLFW_ACCELERATED + 0x00020005:0x00021001, // GLFW_RED_BITS + 0x00020006:0x00021002, // GLFW_GREEN_BITS + 0x00020007:0x00021003, // GLFW_BLUE_BITS + 0x00020008:0x00021004, // GLFW_ALPHA_BITS + 0x00020009:0x00021005, // GLFW_DEPTH_BITS + 0x0002000A:0x00021006, // GLFW_STENCIL_BITS + 0x0002000B:0x0002100F, // GLFW_REFRESH_RATE + 0x0002000C:0x00021007, // GLFW_ACCUM_RED_BITS + 0x0002000D:0x00021008, // GLFW_ACCUM_GREEN_BITS + 0x0002000E:0x00021009, // GLFW_ACCUM_BLUE_BITS + 0x0002000F:0x0002100A, // GLFW_ACCUM_ALPHA_BITS + 0x00020010:0x0002100B, // GLFW_AUX_BUFFERS + 0x00020011:0x0002100C, // GLFW_STEREO + 0x00020012:0, // GLFW_WINDOW_NO_RESIZE + 0x00020013:0x0002100D, // GLFW_FSAA_SAMPLES + 0x00020014:0x00022002, // GLFW_OPENGL_VERSION_MAJOR + 0x00020015:0x00022003, // GLFW_OPENGL_VERSION_MINOR + 0x00020016:0x00022006, // GLFW_OPENGL_FORWARD_COMPAT + 0x00020017:0x00022007, // GLFW_OPENGL_DEBUG_CONTEXT + 0x00020018:0x00022008, // GLFW_OPENGL_PROFILE + }; + return table[param]; + } + }, + +/******************************************************************************* + * GLFW FUNCTIONS + ******************************************************************************/ + glfwInit: () => { + if (GLFW.windows) return 1; // GL_TRUE + + GLFW.initialTime = GLFW.getTime(); + GLFW.defaultWindowHints(); + GLFW.windows = new Array() + GLFW.active = null; + GLFW.scale = GLFW.getDevicePixelRatio(); + + + window.addEventListener('gamepadconnected', GLFW.onGamepadConnected, true); + window.addEventListener('gamepaddisconnected', GLFW.onGamepadDisconnected, true); + window.addEventListener('keydown', GLFW.onKeydown, true); + window.addEventListener('keypress', GLFW.onKeyPress, true); + window.addEventListener('keyup', GLFW.onKeyup, true); + window.addEventListener('blur', GLFW.onBlur, true); + + // watch for devicePixelRatio changes + GLFW.devicePixelRatioMQL = window.matchMedia('(resolution: ' + GLFW.getDevicePixelRatio() + 'dppx)'); + GLFW.devicePixelRatioMQL.addEventListener('change', GLFW.onDevicePixelRatioChange); + + var canvas = Browser.getCanvas(); + canvas.addEventListener('touchmove', GLFW.onMousemove, true); + canvas.addEventListener('touchstart', GLFW.onMouseButtonDown, true); + canvas.addEventListener('touchcancel', GLFW.onMouseButtonUp, true); + canvas.addEventListener('touchend', GLFW.onMouseButtonUp, true); + canvas.addEventListener('mousemove', GLFW.onMousemove, true); + canvas.addEventListener('mousedown', GLFW.onMouseButtonDown, true); + canvas.addEventListener("mouseup", GLFW.onMouseButtonUp, true); + canvas.addEventListener('wheel', GLFW.onMouseWheel, true); + canvas.addEventListener('mousewheel', GLFW.onMouseWheel, true); + canvas.addEventListener('mouseenter', GLFW.onMouseenter, true); + canvas.addEventListener('mouseleave', GLFW.onMouseleave, true); + canvas.addEventListener('drop', GLFW.onDrop, true); + canvas.addEventListener('dragover', GLFW.onDragover, true); + + // Overriding implementation to account for HiDPI + Browser.requestFullscreen = GLFW.requestFullscreen; + Browser.calculateMouseCoords = GLFW.calculateMouseCoords; + Browser.updateCanvasDimensions = GLFW.updateCanvasDimensions; + + Browser.resizeListeners.push((width, height) => { + if (GLFW.isHiDPIAware()) { + var canvas = Browser.getCanvas(); + GLFW.onCanvasResize(canvas.clientWidth, canvas.clientHeight, width, height); + } else { + GLFW.onCanvasResize(width, height, width, height); + } + }); + + return 1; // GL_TRUE + }, + + glfwTerminate: () => { + window.removeEventListener('gamepadconnected', GLFW.onGamepadConnected, true); + window.removeEventListener('gamepaddisconnected', GLFW.onGamepadDisconnected, true); + window.removeEventListener('keydown', GLFW.onKeydown, true); + window.removeEventListener('keypress', GLFW.onKeyPress, true); + window.removeEventListener('keyup', GLFW.onKeyup, true); + window.removeEventListener('blur', GLFW.onBlur, true); + var canvas = Browser.getCanvas(); + canvas.removeEventListener('touchmove', GLFW.onMousemove, true); + canvas.removeEventListener('touchstart', GLFW.onMouseButtonDown, true); + canvas.removeEventListener('touchcancel', GLFW.onMouseButtonUp, true); + canvas.removeEventListener('touchend', GLFW.onMouseButtonUp, true); + canvas.removeEventListener('mousemove', GLFW.onMousemove, true); + canvas.removeEventListener('mousedown', GLFW.onMouseButtonDown, true); + canvas.removeEventListener('mouseup', GLFW.onMouseButtonUp, true); + canvas.removeEventListener('wheel', GLFW.onMouseWheel, true); + canvas.removeEventListener('mousewheel', GLFW.onMouseWheel, true); + canvas.removeEventListener('mouseenter', GLFW.onMouseenter, true); + canvas.removeEventListener('mouseleave', GLFW.onMouseleave, true); + canvas.removeEventListener('drop', GLFW.onDrop, true); + canvas.removeEventListener('dragover', GLFW.onDragover, true); + + if (GLFW.devicePixelRatioMQL) + GLFW.devicePixelRatioMQL.removeEventListener('change', GLFW.onDevicePixelRatioChange); + + canvas.width = canvas.height = 1; + GLFW.windows = null; + GLFW.active = null; + }, + + glfwGetVersion: (major, minor, rev) => { +#if USE_GLFW == 2 + {{{ makeSetValue('major', '0', '2', 'i32') }}}; + {{{ makeSetValue('minor', '0', '7', 'i32') }}}; + {{{ makeSetValue('rev', '0', '7', 'i32') }}}; +#endif + +#if USE_GLFW == 3 + {{{ makeSetValue('major', '0', '3', 'i32') }}}; + {{{ makeSetValue('minor', '0', '2', 'i32') }}}; + {{{ makeSetValue('rev', '0', '1', 'i32') }}}; +#endif + }, + + glfwPollEvents: () => 0, + + glfwWaitEvents: () => 0, + + glfwGetTime: () => GLFW.getTime() - GLFW.initialTime, + + glfwSetTime: (time) => { + GLFW.initialTime = GLFW.getTime() - time; + }, + + glfwExtensionSupported__deps: ['glGetString', '$webglGetExtensions'], + glfwExtensionSupported: (extension) => { + GLFW.extensions ||= webglGetExtensions(); + + if (GLFW.extensions.includes(extension)) return 1; + + // extensions from GLEmulations do not come unprefixed + // so, try with prefix + return (GLFW.extensions.includes("GL_" + extension)); + }, + + glfwSwapInterval__deps: ['emscripten_set_main_loop_timing'], + glfwSwapInterval: (interval) => { + interval = Math.abs(interval); // GLFW uses negative values to enable GLX_EXT_swap_control_tear, which we don't have, so just treat negative and positive the same. + if (interval == 0) _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_SETTIMEOUT }}}, 0); + else _emscripten_set_main_loop_timing({{{ cDefs.EM_TIMING_RAF }}}, interval); + }, + +#if USE_GLFW == 3 + glfwGetVersionString: () => { + GLFW.versionString ||= stringToNewUTF8("3.2.1 JS WebGL Emscripten"); + return GLFW.versionString; + }, + + glfwSetErrorCallback: (cbfun) => { + var prevcbfun = GLFW.errorFunc; + GLFW.errorFunc = cbfun; + return prevcbfun; + }, + + glfwWaitEventsTimeout: (timeout) => 0, + + glfwPostEmptyEvent: () => 0, + + glfwGetMonitors__deps: ['malloc'], + glfwGetMonitors: (count) => { + {{{ makeSetValue('count', '0', '1', 'i32') }}}; + if (!GLFW.monitors) { + GLFW.monitors = _malloc({{{ POINTER_SIZE }}}); + {{{ makeSetValue('GLFW.monitors', '0', '1', 'i32') }}}; + } + return GLFW.monitors; + }, + + glfwGetPrimaryMonitor: () => 1, + + glfwGetMonitorPos: (monitor, x, y) => { + {{{ makeSetValue('x', '0', '0', 'i32') }}}; + {{{ makeSetValue('y', '0', '0', 'i32') }}}; + }, + + glfwGetMonitorWorkarea: (monitor, x, y, w, h) => { + {{{ makeSetValue('x', '0', '0', 'i32') }}}; + {{{ makeSetValue('y', '0', '0', 'i32') }}}; + + {{{ makeSetValue('w', '0', 'screen.availWidth', 'i32') }}}; + {{{ makeSetValue('h', '0', 'screen.availHeight', 'i32') }}}; + }, + + glfwGetMonitorPhysicalSize: (monitor, width, height) => { + // AFAIK there is no way to do this in javascript + // Maybe with platform specific ccalls? + // + // Let's report 0 now which is as wrong as it can get for end user. + {{{ makeSetValue('width', '0', '0', 'i32') }}}; + {{{ makeSetValue('height', '0', '0', 'i32') }}}; + }, + + glfwGetMonitorContentScale: (monitor, x, y) => { + {{{ makeSetValue('x', '0', 'GLFW.scale', 'float') }}}; + {{{ makeSetValue('y', '0', 'GLFW.scale', 'float') }}}; + }, + + glfwGetMonitorName: (mon) => { + GLFW.monitorString ||= stringToNewUTF8("HTML5 WebGL Canvas"); + return GLFW.monitorString; + }, + + glfwSetMonitorCallback: (cbfun) => { + var prevcbfun = GLFW.monitorFunc; + GLFW.monitorFunc = cbfun; + return prevcbfun; + }, + + // TODO: implement + glfwGetVideoModes: (monitor, count) => { + {{{ makeSetValue('count', '0', '0', 'i32') }}}; + return 0; + }, + + // TODO: implement + glfwGetVideoMode: (monitor) => 0, + + // TODO: implement + glfwSetGamma: (monitor, gamma) => 0, + + glfwGetGammaRamp: (monitor) => abort("glfwGetGammaRamp not implemented."), + + glfwSetGammaRamp: (monitor, ramp) => abort("glfwSetGammaRamp not implemented."), + + glfwDefaultWindowHints: () => GLFW.defaultWindowHints(), + + glfwWindowHint: (target, hint) => { + GLFW.hints[target] = hint; + }, + + glfwWindowHintString: (hint, value) => { + // from glfw docs -> we just ignore this. + // Some hints are platform specific. These may be set on any platform but they + // will only affect their specific platform. Other platforms will ignore them. + }, + + glfwCreateWindow: (width, height, title, monitor, share) => GLFW.createWindow(width, height, title, monitor, share), + + glfwDestroyWindow: (winid) => GLFW.destroyWindow(winid), + + glfwWindowShouldClose: (winid) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return win.shouldClose; + }, + + glfwSetWindowShouldClose: (winid, value) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + win.shouldClose = value; + }, + + glfwSetWindowTitle: (winid, title) => GLFW.setWindowTitle(winid, title), + + glfwGetWindowPos: (winid, x, y) => GLFW.getWindowPos(winid, x, y), + + glfwSetWindowPos: (winid, x, y) => GLFW.setWindowPos(winid, x, y), + + glfwGetWindowSize: (winid, width, height) => GLFW.getWindowSize(winid, width, height), + + glfwSetWindowSize: (winid, width, height) => GLFW.setWindowSize(winid, width, height), + + glfwGetFramebufferSize: (winid, width, height) => { + var ww = 0; + var wh = 0; + + var win = GLFW.WindowFromId(winid); + if (win) { + ww = win.framebufferWidth; + wh = win.framebufferHeight; + } + + if (width) { + {{{ makeSetValue('width', '0', 'ww', 'i32') }}}; + } + + if (height) { + {{{ makeSetValue('height', '0', 'wh', 'i32') }}}; + } + }, + + glfwGetWindowContentScale: (winid, x, y) => { + // winid doesn't matter. all windows will use same scale anyway. + // hope i used this makeSetValue correctly + {{{ makeSetValue('x', '0', 'GLFW.scale', 'float') }}}; + {{{ makeSetValue('y', '0', 'GLFW.scale', 'float') }}}; + }, + + glfwGetWindowOpacity: (winid) => 1.0, + + glfwSetWindowOpacity: (winid, opacity) => { /* error */ }, + + glfwIconifyWindow: (winid) => { +#if ASSERTIONS + warnOnce('glfwIconifyWindow is not implemented'); +#endif + }, + + glfwRestoreWindow: (winid) => { +#if ASSERTIONS + warnOnce('glfwRestoreWindow is not implemented'); +#endif + }, + + glfwShowWindow: (winid) => 0, + + glfwHideWindow: (winid) => 0, + + glfwGetWindowMonitor: (winid) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return win.monitor; + }, + + glfwGetWindowAttrib: (winid, attrib) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return win.attributes[attrib]; + }, + + glfwSetWindowAttrib: (winid, attrib, value) => GLFW.setWindowAttrib(winid, attrib, value), + + glfwSetWindowUserPointer: (winid, ptr) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + win.userptr = ptr; + }, + + glfwGetWindowUserPointer: (winid) => { + var win = GLFW.WindowFromId(winid); + if (!win) return 0; + return win.userptr; + }, + + glfwSetWindowPosCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowPosFunc; + win.windowPosFunc = cbfun; + return prevcbfun; + }, + + glfwSetWindowSizeCallback: (winid, cbfun) => GLFW.setWindowSizeCallback(winid, cbfun), + + glfwSetWindowCloseCallback: (winid, cbfun) => GLFW.setWindowCloseCallback(winid, cbfun), + + glfwSetWindowRefreshCallback: (winid, cbfun) => GLFW.setWindowRefreshCallback(winid, cbfun), + + glfwSetWindowFocusCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowFocusFunc; + win.windowFocusFunc = cbfun; + return prevcbfun; + }, + + glfwSetWindowIconifyCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowIconifyFunc; + win.windowIconifyFunc = cbfun; + return prevcbfun; + }, + + glfwSetWindowMaximizeCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowMaximizeFunc; + win.windowMaximizeFunc = cbfun; + return prevcbfun; + }, + + glfwSetWindowIcon: (winid, count, images) => 0, + + glfwSetWindowSizeLimits: (winid, minwidth, minheight, maxwidth, maxheight) => 0, + + glfwSetWindowAspectRatio: (winid, numer, denom) => 0, + + glfwGetWindowFrameSize: (winid, left, top, right, bottom) => abort("glfwGetWindowFrameSize not implemented."), + + glfwMaximizeWindow: (winid) => 0, + + glfwFocusWindow: (winid) => 0, + + glfwRequestWindowAttention: (winid) => 0, // maybe do window.focus()? + + glfwSetWindowMonitor: (winid, monitor, xpos, ypos, width, height, refreshRate) => abort("glfwSetWindowMonitor not implemented."), + + glfwCreateCursor: (image, xhot, yhot) => 0, + + glfwCreateStandardCursor: (shape) => 0, + + glfwDestroyCursor: (cursor) => 0, + + glfwSetCursor: (winid, cursor) => 0, + + glfwSetFramebufferSizeCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.framebufferSizeFunc; + win.framebufferSizeFunc = cbfun; + return prevcbfun; + }, + + glfwSetWindowContentScaleCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.windowContentScaleFunc; + win.windowContentScaleFunc = cbfun; + return prevcbfun; + }, + + glfwGetInputMode: (winid, mode) => { + var win = GLFW.WindowFromId(winid); + if (!win) return; + + switch (mode) { + case 0x00033001: { // GLFW_CURSOR + if (Browser.pointerLock) { + win.inputModes[mode] = 0x00034003; // GLFW_CURSOR_DISABLED + } else { + win.inputModes[mode] = 0x00034001; // GLFW_CURSOR_NORMAL + } + } + } + + return win.inputModes[mode]; + }, + + glfwSetInputMode: (winid, mode, value) => { + GLFW.setInputMode(winid, mode, value); + }, + + glfwRawMouseMotionSupported: () => 0, + + glfwGetKey: (winid, key) => GLFW.getKey(winid, key), + + glfwGetKeyName: (key, scancode) => abort("glfwGetKeyName not implemented."), + + glfwGetKeyScancode: (key) => abort("glfwGetKeyScancode not implemented."), + + glfwGetMouseButton: (winid, button) => GLFW.getMouseButton(winid, button), + + glfwGetCursorPos: (winid, x, y) => GLFW.getCursorPos(winid, x, y), + + // I believe it is not possible to move the mouse with JavaScript + glfwSetCursorPos: (winid, x, y) => GLFW.setCursorPos(winid, x, y), + + glfwSetKeyCallback: (winid, cbfun) => GLFW.setKeyCallback(winid, cbfun), + + glfwSetCharCallback: (winid, cbfun) => GLFW.setCharCallback(winid, cbfun), + + glfwSetCharModsCallback: (winid, cbfun) => abort("glfwSetCharModsCallback not implemented."), + + glfwSetMouseButtonCallback: (winid, cbfun) => GLFW.setMouseButtonCallback(winid, cbfun), + + glfwSetCursorPosCallback: (winid, cbfun) => GLFW.setCursorPosCallback(winid, cbfun), + + glfwSetCursorEnterCallback: (winid, cbfun) => { + var win = GLFW.WindowFromId(winid); + if (!win) return null; + var prevcbfun = win.cursorEnterFunc; + win.cursorEnterFunc = cbfun; + return prevcbfun; + }, + + glfwSetScrollCallback: (winid, cbfun) => GLFW.setScrollCallback(winid, cbfun), + + glfwVulkanSupported: () => 0, + + glfwSetDropCallback: (winid, cbfun) => GLFW.setDropCallback(winid, cbfun), + + glfwGetTimerValue: () => abort("glfwGetTimerValue is not implemented."), + + glfwGetTimerFrequency: () => abort("glfwGetTimerFrequency is not implemented."), + + glfwGetRequiredInstanceExtensions: (count) => abort("glfwGetRequiredInstanceExtensions is not implemented."), + + glfwJoystickPresent: (joy) => { + GLFW.refreshJoysticks(); + + return GLFW.joys[joy] !== undefined; + }, + + glfwGetJoystickAxes: (joy, count) => { + GLFW.refreshJoysticks(); + + var state = GLFW.joys[joy]; + if (!state || !state.axes) { + {{{ makeSetValue('count', '0', '0', 'i32') }}}; + return; + } + + {{{ makeSetValue('count', '0', 'state.axesCount', 'i32') }}}; + return state.axes; + }, + + glfwGetJoystickButtons: (joy, count) => { + GLFW.refreshJoysticks(); + + var state = GLFW.joys[joy]; + if (!state || !state.buttons) { + {{{ makeSetValue('count', '0', '0', 'i32') }}}; + return; + } + + {{{ makeSetValue('count', '0', 'state.buttonsCount', 'i32') }}}; + return state.buttons; + }, + + glfwGetJoystickHats: (joy, count) => abort("glfwGetJoystickHats is not implemented"), + + glfwGetJoystickName: (joy) => { + if (GLFW.joys[joy]) { + return GLFW.joys[joy].id; + } + return 0; + }, + + glfwGetJoystickGUID: (jid) => abort("glfwGetJoystickGUID not implemented"), + + glfwSetJoystickUserPointer: (jid, ptr) => abort("glfwSetJoystickUserPointer not implemented"), + + glfwGetJoystickUserPointer: (jid) => abort("glfwGetJoystickUserPointer not implemented"), + + glfwJoystickIsGamepad: (jid) => abort("glfwJoystickIsGamepad not implemented"), + + glfwSetJoystickCallback: (cbfun) => GLFW.setJoystickCallback(cbfun), + + glfwSetClipboardString: (win, string) => 0, + + glfwGetClipboardString: (win) => 0, + + glfwMakeContextCurrent: (winid) => 0, + + glfwGetCurrentContext: () => GLFW.active ? GLFW.active.id : 0, + + glfwSwapBuffers: (winid) => GLFW.swapBuffers(winid), + +#elif USE_GLFW == 2 + glfwOpenWindow: (width, height, redbits, greenbits, bluebits, alphabits, depthbits, stencilbits, mode) => { + GLFW.hints[0x00021001] = redbits; // GLFW_RED_BITS + GLFW.hints[0x00021002] = greenbits; // GLFW_GREEN_BITS + GLFW.hints[0x00021003] = bluebits; // GLFW_BLUE_BITS + GLFW.hints[0x00021004] = alphabits; // GLFW_ALPHA_BITS + GLFW.hints[0x00021005] = depthbits; // GLFW_DEPTH_BITS + GLFW.hints[0x00021006] = stencilbits; // GLFW_STENCIL_BITS + GLFW.createWindow(width, height, "GLFW2 Window", 0, 0); + return 1; // GL_TRUE + }, + + glfwCloseWindow: () => GLFW.destroyWindow(GLFW.active.id), + + glfwOpenWindowHint: (target, hint) => { + target = GLFW.GLFW2ParamToGLFW3Param(target); + GLFW.hints[target] = hint; + }, + + glfwGetWindowSize_v2: (width, height) => GLFW.getWindowSize(GLFW.active.id, width, height), + + glfwSetWindowSize_v2: (width, height) => GLFW.setWindowSize(GLFW.active.id, width, height), + + glfwSetWindowPos_v2: (x, y) => GLFW.setWindowPos(GLFW.active.id, x, y), + + glfwSetWindowTitle_v2: (title) => GLFW.setWindowTitle(GLFW.active.id, title), + + glfwIconifyWindow_v2: () => { +#if ASSERTIONS + warnOnce('glfwIconifyWindow is not implemented'); +#endif + }, + + glfwRestoreWindow_v2: () => { +#if ASSERTIONS + warnOnce('glfwRestoreWindow is not implemented'); +#endif + }, + + glfwSwapBuffers_v2: () => GLFW.swapBuffers(GLFW.active.id), + + glfwGetWindowParam: (param) => { + param = GLFW.GLFW2ParamToGLFW3Param(param); + return GLFW.hints[param]; + }, + + glfwSetWindowSizeCallback_v2: (cbfun) => { + GLFW.setWindowSizeCallback(GLFW.active.id, cbfun); + }, + + glfwSetWindowCloseCallback_v2: (cbfun) => { + GLFW.setWindowCloseCallback(GLFW.active.id, cbfun); + }, + + glfwSetWindowRefreshCallback_v2: (cbfun) => GLFW.setWindowRefreshCallback(GLFW.active.id, cbfun), + + glfwGetKey_v2: (key) => GLFW.getKey(GLFW.active.id, key), + + glfwGetMouseButton_v2: (button) => GLFW.getMouseButton(GLFW.active.id, button), + + glfwGetMousePos: (x, y) => { + GLFW.getMousePos(GLFW.active.id, x, y); + }, + + glfwSetMousePos: (x, y) => { + GLFW.setCursorPos(GLFW.active.id, x, y); + }, + + glfwGetMouseWheel: () => 0, + + glfwSetMouseWheel: (pos) => 0, + + glfwSetKeyCallback_v2: (cbfun) => { + GLFW.setKeyCallback(GLFW.active.id, cbfun); + }, + + glfwSetCharCallback_v2: (cbfun) => { + GLFW.setCharCallback(GLFW.active.id, cbfun); + }, + + glfwSetMouseButtonCallback_v2: (cbfun) => { + GLFW.setMouseButtonCallback(GLFW.active.id, cbfun); + }, + + glfwSetMousePosCallback: (cbfun) => { + GLFW.setCursorPosCallback(GLFW.active.id, cbfun); + }, + + glfwSetMouseWheelCallback: (cbfun) => { + GLFW.setScrollCallback(GLFW.active.id, cbfun); + }, + + glfwGetDesktopMode: (mode) => abort("glfwGetDesktopMode is not implemented."), + + glfwSleep__deps: ['sleep'], + glfwSleep: (time) => _sleep(time), + + glfwEnable: (target) => { + target = GLFW.GLFW2ParamToGLFW3Param(target); + GLFW.hints[target] = false; + }, + + glfwDisable: (target) => { + target = GLFW.GLFW2ParamToGLFW3Param(target); + GLFW.hints[target] = true; + }, + + glfwGetGLVersion: (major, minor, rev) => { + {{{ makeSetValue('major', '0', '0', 'i32') }}}; + {{{ makeSetValue('minor', '0', '0', 'i32') }}}; + {{{ makeSetValue('rev', '0', '1', 'i32') }}}; + }, + + glfwCreateThread: (fun, arg) => { + {{{ makeDynCall('vp', 'fun') }}}(arg); + // One single thread + return 0; + }, + + glfwDestroyThread: (ID) => 0, + + glfwWaitThread: (ID, waitmode) => 0, + + // One single thread + glfwGetThreadID: () => 0, + + glfwCreateMutex: () => abort("glfwCreateMutex is not implemented."), + + glfwDestroyMutex: (mutex) => abort("glfwDestroyMutex is not implemented."), + + glfwLockMutex: (mutex) => abort("glfwLockMutex is not implemented."), + + glfwUnlockMutex: (mutex) => abort("glfwUnlockMutex is not implemented."), + + glfwCreateCond: () => abort("glfwCreateCond is not implemented."), + + glfwDestroyCond: (cond) => abort("glfwDestroyCond is not implemented."), + + glfwWaitCond: (cond, mutex, timeout) => abort("glfwWaitCond is not implemented."), + + glfwSignalCond: (cond) => abort("glfwSignalCond is not implemented."), + + glfwBroadcastCond: (cond) => abort("glfwBroadcastCond is not implemented."), + + glfwGetNumberOfProcessors: () => 1, // Threads are disabled anyway… + + glfwReadImage: (name, img, flags) => abort("glfwReadImage is not implemented."), + + glfwReadMemoryImage: (data, size, img, flags) => abort("glfwReadMemoryImage is not implemented."), + + glfwFreeImage: (img) => abort("glfwFreeImage is not implemented."), + + glfwLoadTexture2D: (name, flags) => abort("glfwLoadTexture2D is not implemented."), + + glfwLoadMemoryTexture2D: (data, size, flags) => abort("glfwLoadMemoryTexture2D is not implemented."), + + glfwLoadTextureImage2D: (img, flags) => abort("glfwLoadTextureImage2D is not implemented."), +#endif // GLFW2 +}; + +autoAddDeps(LibraryGLFW, '$GLFW'); +addToLibrary(LibraryGLFW); diff --git a/src/lib/libglut.js b/src/lib/libglut.js new file mode 100644 index 0000000000000..1590c7ee25095 --- /dev/null +++ b/src/lib/libglut.js @@ -0,0 +1,659 @@ +/** + * @license + * Copyright 2012 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryGLUT = { + $GLUT__deps: ['$Browser', '$getFullscreenElement', 'glutPostRedisplay'], + $GLUT: { + initTime: null, + idleFunc: null, + displayFunc: null, + keyboardFunc: null, + keyboardUpFunc: null, + specialFunc: null, + specialUpFunc: null, + reshapeFunc: null, + motionFunc: null, + passiveMotionFunc: null, + mouseFunc: null, + buttons: 0, + modifiers: 0, + initWindowWidth: 256, + initWindowHeight: 256, + initDisplayMode: 0x0000 /*GLUT_RGBA*/ | 0x0002 /*GLUT_DOUBLE*/ | 0x0010 /*GLUT_DEPTH*/, + // Set when going fullscreen + windowX: 0, + windowY: 0, + windowWidth: 0, + windowHeight: 0, + requestedAnimationFrame: false, + + saveModifiers: (event) => { + GLUT.modifiers = 0; + if (event['shiftKey']) + GLUT.modifiers += 1; /* GLUT_ACTIVE_SHIFT */ + if (event['ctrlKey']) + GLUT.modifiers += 2; /* GLUT_ACTIVE_CTRL */ + if (event['altKey']) + GLUT.modifiers += 4; /* GLUT_ACTIVE_ALT */ + }, + + onMousemove: (event) => { + /* Send motion event only if the motion changed, prevents + * spamming our app with unnecessary callbacks. It does happen in + * Chrome on Windows. + */ + var lastX = Browser.mouseX; + var lastY = Browser.mouseY; + Browser.calculateMouseEvent(event); + var newX = Browser.mouseX; + var newY = Browser.mouseY; + if (newX == lastX && newY == lastY) return; + + if (GLUT.buttons == 0 && event.target == Browser.getCanvas() && GLUT.passiveMotionFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('vii', 'GLUT.passiveMotionFunc') }}}(lastX, lastY); + } else if (GLUT.buttons != 0 && GLUT.motionFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('vii', 'GLUT.motionFunc') }}}(lastX, lastY); + } + }, + + getSpecialKey: (keycode) => { + var key = null; + switch (keycode) { + case 8: key = 120 /* backspace */; break; + case 46: key = 111 /* delete */; break; + + case 0x70 /*DOM_VK_F1*/: key = 1 /* GLUT_KEY_F1 */; break; + case 0x71 /*DOM_VK_F2*/: key = 2 /* GLUT_KEY_F2 */; break; + case 0x72 /*DOM_VK_F3*/: key = 3 /* GLUT_KEY_F3 */; break; + case 0x73 /*DOM_VK_F4*/: key = 4 /* GLUT_KEY_F4 */; break; + case 0x74 /*DOM_VK_F5*/: key = 5 /* GLUT_KEY_F5 */; break; + case 0x75 /*DOM_VK_F6*/: key = 6 /* GLUT_KEY_F6 */; break; + case 0x76 /*DOM_VK_F7*/: key = 7 /* GLUT_KEY_F7 */; break; + case 0x77 /*DOM_VK_F8*/: key = 8 /* GLUT_KEY_F8 */; break; + case 0x78 /*DOM_VK_F9*/: key = 9 /* GLUT_KEY_F9 */; break; + case 0x79 /*DOM_VK_F10*/: key = 10 /* GLUT_KEY_F10 */; break; + case 0x7a /*DOM_VK_F11*/: key = 11 /* GLUT_KEY_F11 */; break; + case 0x7b /*DOM_VK_F12*/: key = 12 /* GLUT_KEY_F12 */; break; + case 0x25 /*DOM_VK_LEFT*/: key = 100 /* GLUT_KEY_LEFT */; break; + case 0x26 /*DOM_VK_UP*/: key = 101 /* GLUT_KEY_UP */; break; + case 0x27 /*DOM_VK_RIGHT*/: key = 102 /* GLUT_KEY_RIGHT */; break; + case 0x28 /*DOM_VK_DOWN*/: key = 103 /* GLUT_KEY_DOWN */; break; + case 0x21 /*DOM_VK_PAGE_UP*/: key = 104 /* GLUT_KEY_PAGE_UP */; break; + case 0x22 /*DOM_VK_PAGE_DOWN*/: key = 105 /* GLUT_KEY_PAGE_DOWN */; break; + case 0x24 /*DOM_VK_HOME*/: key = 106 /* GLUT_KEY_HOME */; break; + case 0x23 /*DOM_VK_END*/: key = 107 /* GLUT_KEY_END */; break; + case 0x2d /*DOM_VK_INSERT*/: key = 108 /* GLUT_KEY_INSERT */; break; + + case 16 /*DOM_VK_SHIFT*/: + case 0x05 /*DOM_VK_LEFT_SHIFT*/: + key = 112 /* GLUT_KEY_SHIFT_L */; + break; + case 0x06 /*DOM_VK_RIGHT_SHIFT*/: + key = 113 /* GLUT_KEY_SHIFT_R */; + break; + + case 17 /*DOM_VK_CONTROL*/: + case 0x03 /*DOM_VK_LEFT_CONTROL*/: + key = 114 /* GLUT_KEY_CONTROL_L */; + break; + case 0x04 /*DOM_VK_RIGHT_CONTROL*/: + key = 115 /* GLUT_KEY_CONTROL_R */; + break; + + case 18 /*DOM_VK_ALT*/: + case 0x02 /*DOM_VK_LEFT_ALT*/: + key = 116 /* GLUT_KEY_ALT_L */; + break; + case 0x01 /*DOM_VK_RIGHT_ALT*/: + key = 117 /* GLUT_KEY_ALT_R */; + break; + }; + return key; + }, + + getASCIIKey: (event) => { + if (event['ctrlKey'] || event['altKey'] || event['metaKey']) return null; + + var keycode = event['keyCode']; + + /* The exact list is soooo hard to find in a canonical place! */ + + if (48 <= keycode && keycode <= 57) + return keycode; // numeric TODO handle shift? + if (65 <= keycode && keycode <= 90) + return event['shiftKey'] ? keycode : keycode + 32; + if (96 <= keycode && keycode <= 105) + return keycode - 48; // numpad numbers + if (106 <= keycode && keycode <= 111) + return keycode - 106 + 42; // *,+-./ TODO handle shift? + + switch (keycode) { + case 9: // tab key + case 13: // return key + case 27: // escape + case 32: // space + case 61: // equal + return keycode; + } + + var s = event['shiftKey']; + switch (keycode) { + case 186: return s ? 58 : 59; // colon / semi-colon + case 187: return s ? 43 : 61; // add / equal (these two may be wrong) + case 188: return s ? 60 : 44; // less-than / comma + case 189: return s ? 95 : 45; // dash + case 190: return s ? 62 : 46; // greater-than / period + case 191: return s ? 63 : 47; // forward slash + case 219: return s ? 123 : 91; // open bracket + case 220: return s ? 124 : 47; // back slash + case 221: return s ? 125 : 93; // close bracket + case 222: return s ? 34 : 39; // single quote + } + + return null; + }, + + onKeydown: (event) => { + if (GLUT.specialFunc || GLUT.keyboardFunc) { + var key = GLUT.getSpecialKey(event['keyCode']); + if (key !== null) { + if (GLUT.specialFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viii', 'GLUT.specialFunc') }}}(key, Browser.mouseX, Browser.mouseY); + } + } else { + key = GLUT.getASCIIKey(event); + if (key !== null && GLUT.keyboardFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viii', 'GLUT.keyboardFunc') }}}(key, Browser.mouseX, Browser.mouseY); + } + } + } + }, + + onKeyup: (event) => { + if (GLUT.specialUpFunc || GLUT.keyboardUpFunc) { + var key = GLUT.getSpecialKey(event['keyCode']); + if (key !== null) { + if (GLUT.specialUpFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viii', 'GLUT.specialUpFunc') }}}(key, Browser.mouseX, Browser.mouseY); + } + } else { + key = GLUT.getASCIIKey(event); + if (key !== null && GLUT.keyboardUpFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viii', 'GLUT.keyboardUpFunc') }}}(key, Browser.mouseX, Browser.mouseY); + } + } + } + }, + + touchHandler: (event) => { + if (event.target != Browser.getCanvas()) { + return; + } + + var touches = event.changedTouches, + main = touches[0], + type = ""; + + switch (event.type) { + case "touchstart": type = "mousedown"; break; + case "touchmove": type = "mousemove"; break; + case "touchend": type = "mouseup"; break; + default: return; + } + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, + main.screenX, main.screenY, + main.clientX, main.clientY, false, + false, false, false, 0/*main*/, null); + + main.target.dispatchEvent(simulatedEvent); + event.preventDefault(); + }, + + onMouseButtonDown: (event) => { + Browser.calculateMouseEvent(event); + + GLUT.buttons |= (1 << event['button']); + + if (event.target == Browser.getCanvas() && GLUT.mouseFunc) { + try { + event.target.setCapture(); + } catch (e) {} + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viiii', 'GLUT.mouseFunc') }}}(event['button'], 0/*GLUT_DOWN*/, Browser.mouseX, Browser.mouseY); + } + }, + + onMouseButtonUp: (event) => { + Browser.calculateMouseEvent(event); + + GLUT.buttons &= ~(1 << event['button']); + + if (GLUT.mouseFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viiii', 'GLUT.mouseFunc') }}}(event['button'], 1/*GLUT_UP*/, Browser.mouseX, Browser.mouseY); + } + }, + + onMouseWheel: (event) => { + Browser.calculateMouseEvent(event); + + // cross-browser wheel delta + // Note the minus sign that flips browser wheel direction (positive direction scrolls page down) to native wheel direction (positive direction is mouse wheel up) + var delta = -Browser.getMouseWheelDelta(event); + delta = (delta == 0) ? 0 : (delta > 0 ? Math.max(delta, 1) : Math.min(delta, -1)); // Quantize to integer so that minimum scroll is at least +/- 1. + + var button = 3; // wheel up + if (delta < 0) { + button = 4; // wheel down + } + + if (GLUT.mouseFunc) { + event.preventDefault(); + GLUT.saveModifiers(event); + {{{ makeDynCall('viiii', 'GLUT.mouseFunc') }}}(button, 0/*GLUT_DOWN*/, Browser.mouseX, Browser.mouseY); + } + }, + + // TODO add fullscreen API ala: + // http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ + onFullscreenEventChange: (event) => { + var width; + var height; + if (getFullscreenElement()) { + width = screen["width"]; + height = screen["height"]; + } else { + width = GLUT.windowWidth; + height = GLUT.windowHeight; + // TODO set position + document.removeEventListener('fullscreenchange', GLUT.onFullscreenEventChange, true); + document.removeEventListener('mozfullscreenchange', GLUT.onFullscreenEventChange, true); + document.removeEventListener('webkitfullscreenchange', GLUT.onFullscreenEventChange, true); + } + Browser.setCanvasSize(width, height, true); // N.B. GLUT.reshapeFunc is also registered as a canvas resize callback. + // Just call it once here. + /* Can't call _glutReshapeWindow as that requests cancelling fullscreen. */ + if (GLUT.reshapeFunc) { + // out("GLUT.reshapeFunc (from FS): " + width + ", " + height); + {{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height); + } + _glutPostRedisplay(); + }, + + // Resize callback stage 1: update canvas by setCanvasSize, which notifies resizeListeners including GLUT.reshapeFunc + onResize: () => { + // Update canvas size to clientWidth and clientHeight, which include CSS scaling + var canvas = Browser.getCanvas(); + Browser.setCanvasSize(canvas.clientWidth, canvas.clientHeight, /*noUpdates*/false); + } + }, + + glutGetModifiers__proxy: 'sync', + glutGetModifiers: () => GLUT.modifiers, + + glutInit__deps: ['$Browser', '$addOnExit'], + glutInit__proxy: 'sync', + glutInit: (argcp, argv) => { + // Ignore arguments + GLUT.initTime = Date.now(); + + var isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + // onMouseButtonDown, onMouseButtonUp and onMousemove handlers + // depend on Browser.mouseX / Browser.mouseY fields. Those fields + // don't get updated by touch events. So register a touchHandler + // function that translates the touch events to mouse events. + + // GLUT doesn't support touch, mouse only, so from touch events we + // are only looking at single finger touches to emulate left click, + // so we can use workaround and convert all touch events in mouse + // events. See touchHandler. + window.addEventListener('touchmove', GLUT.touchHandler, true); + window.addEventListener('touchstart', GLUT.touchHandler, true); + window.addEventListener('touchend', GLUT.touchHandler, true); + } + + window.addEventListener('keydown', GLUT.onKeydown, true); + window.addEventListener('keyup', GLUT.onKeyup, true); + window.addEventListener('mousemove', GLUT.onMousemove, true); + window.addEventListener('mousedown', GLUT.onMouseButtonDown, true); + window.addEventListener('mouseup', GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.addEventListener('mousewheel', GLUT.onMouseWheel, true); + // Firefox + window.addEventListener('DOMMouseScroll', GLUT.onMouseWheel, true); + + // Resize callback stage 1: update canvas which notifies resizeListeners + window.addEventListener('resize', GLUT.onResize, true); + + // Resize callback stage 2: updateResizeListeners notifies reshapeFunc + Browser.resizeListeners.push((width, height) => { + if (GLUT.reshapeFunc) { + {{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height); + } + }); + + addOnExit(() => { + if (isTouchDevice) { + window.removeEventListener('touchmove', GLUT.touchHandler, true); + window.removeEventListener('touchstart', GLUT.touchHandler, true); + window.removeEventListener('touchend', GLUT.touchHandler, true); + } + + window.removeEventListener('keydown', GLUT.onKeydown, true); + window.removeEventListener('keyup', GLUT.onKeyup, true); + window.removeEventListener('mousemove', GLUT.onMousemove, true); + window.removeEventListener('mousedown', GLUT.onMouseButtonDown, true); + window.removeEventListener('mouseup', GLUT.onMouseButtonUp, true); + // IE9, Chrome, Safari, Opera + window.removeEventListener('mousewheel', GLUT.onMouseWheel, true); + // Firefox + window.removeEventListener('DOMMouseScroll', GLUT.onMouseWheel, true); + + window.removeEventListener('resize', GLUT.onResize, true); + + var canvas = Browser.getCanvas(); + canvas.width = canvas.height = 1; + }); + }, + + glutInitWindowSize__proxy: 'sync', + glutInitWindowSize: (width, height) => { + Browser.setCanvasSize( GLUT.initWindowWidth = width, + GLUT.initWindowHeight = height ); + }, + + glutInitWindowPosition__proxy: 'sync', + // Ignore for now + glutInitWindowPosition: (x, y) => {}, + + glutGet: (type) => { + switch (type) { + case 100: /* GLUT_WINDOW_X */ + return 0; /* TODO */ + case 101: /* GLUT_WINDOW_Y */ + return 0; /* TODO */ + case 102: /* GLUT_WINDOW_WIDTH */ + return Browser.getCanvas().width; + case 103: /* GLUT_WINDOW_HEIGHT */ + return Browser.getCanvas().height; + case 200: /* GLUT_SCREEN_WIDTH */ + return Browser.getCanvas().width; + case 201: /* GLUT_SCREEN_HEIGHT */ + return Browser.getCanvas().height; + case 500: /* GLUT_INIT_WINDOW_X */ + return 0; /* TODO */ + case 501: /* GLUT_INIT_WINDOW_Y */ + return 0; /* TODO */ + case 502: /* GLUT_INIT_WINDOW_WIDTH */ + return GLUT.initWindowWidth; + case 503: /* GLUT_INIT_WINDOW_HEIGHT */ + return GLUT.initWindowHeight; + case 700: /* GLUT_ELAPSED_TIME */ + var now = Date.now(); + return now - GLUT.initTime; + case 0x0069: /* GLUT_WINDOW_STENCIL_SIZE */ + return GLctx.getContextAttributes().stencil ? 8 : 0; + case 0x006A: /* GLUT_WINDOW_DEPTH_SIZE */ + return GLctx.getContextAttributes().depth ? 8 : 0; + case 0x006E: /* GLUT_WINDOW_ALPHA_SIZE */ + return GLctx.getContextAttributes().alpha ? 8 : 0; + case 0x0078: /* GLUT_WINDOW_NUM_SAMPLES */ + return GLctx.getContextAttributes().antialias ? 1 : 0; + + default: + abort("glutGet(" + type + ") not implemented yet"); + } + }, + + glutIdleFunc__proxy: 'sync', + glutIdleFunc__deps: ['$safeSetTimeout'], + glutIdleFunc: (func) => { + function callback() { + if (GLUT.idleFunc) { + {{{ makeDynCall('v', 'GLUT.idleFunc') }}}(); + safeSetTimeout(callback, 4); // HTML spec specifies a 4ms minimum delay on the main thread; workers might get more, but we standardize here + } + } + if (!GLUT.idleFunc) { + safeSetTimeout(callback, 0); + } + GLUT.idleFunc = func; + }, + + glutTimerFunc__proxy: 'sync', + glutTimerFunc__deps: ['$safeSetTimeout'], + glutTimerFunc: (msec, func, value) => + safeSetTimeout(() => {{{ makeDynCall('vi', 'func') }}}(value), msec), + + glutDisplayFunc__proxy: 'sync', + glutDisplayFunc: (func) => { + GLUT.displayFunc = func; + }, + + glutKeyboardFunc__proxy: 'sync', + glutKeyboardFunc: (func) => { + GLUT.keyboardFunc = func; + }, + + glutKeyboardUpFunc__proxy: 'sync', + glutKeyboardUpFunc: (func) => { + GLUT.keyboardUpFunc = func; + }, + + glutSpecialFunc__proxy: 'sync', + glutSpecialFunc: (func) => { + GLUT.specialFunc = func; + }, + + glutSpecialUpFunc__proxy: 'sync', + glutSpecialUpFunc: (func) => { + GLUT.specialUpFunc = func; + }, + + glutReshapeFunc__proxy: 'sync', + glutReshapeFunc: (func) => { + GLUT.reshapeFunc = func; + }, + + glutMotionFunc__proxy: 'sync', + glutMotionFunc: (func) => { + GLUT.motionFunc = func; + }, + + glutPassiveMotionFunc__proxy: 'sync', + glutPassiveMotionFunc: (func) => { + GLUT.passiveMotionFunc = func; + }, + + glutMouseFunc__proxy: 'sync', + glutMouseFunc: (func) => { + GLUT.mouseFunc = func; + }, + + glutSetCursor__proxy: 'sync', + glutSetCursor: (cursor) => { + var cursorStyle = 'auto'; + switch (cursor) { + case 0x0000: /* GLUT_CURSOR_RIGHT_ARROW */ + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0001: /* GLUT_CURSOR_LEFT_ARROW */ + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0002: /* GLUT_CURSOR_INFO */ + cursorStyle = 'pointer'; + break; + case 0x0003: /* GLUT_CURSOR_DESTROY */ + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0004: /* GLUT_CURSOR_HELP */ + cursorStyle = 'help'; + break; + case 0x0005: /* GLUT_CURSOR_CYCLE */ + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0006: /* GLUT_CURSOR_SPRAY */ + // No equivalent css cursor style, fallback to 'auto' + break; + case 0x0007: /* GLUT_CURSOR_WAIT */ + cursorStyle = 'wait'; + break; + case 0x0008: /* GLUT_CURSOR_TEXT */ + cursorStyle = 'text'; + break; + case 0x0009: /* GLUT_CURSOR_CROSSHAIR */ + case 0x0066: /* GLUT_CURSOR_FULL_CROSSHAIR */ + cursorStyle = 'crosshair'; + break; + case 0x000A: /* GLUT_CURSOR_UP_DOWN */ + cursorStyle = 'ns-resize'; + break; + case 0x000B: /* GLUT_CURSOR_LEFT_RIGHT */ + cursorStyle = 'ew-resize'; + break; + case 0x000C: /* GLUT_CURSOR_TOP_SIDE */ + cursorStyle = 'n-resize'; + break; + case 0x000D: /* GLUT_CURSOR_BOTTOM_SIDE */ + cursorStyle = 's-resize'; + break; + case 0x000E: /* GLUT_CURSOR_LEFT_SIDE */ + cursorStyle = 'w-resize'; + break; + case 0x000F: /* GLUT_CURSOR_RIGHT_SIDE */ + cursorStyle = 'e-resize'; + break; + case 0x0010: /* GLUT_CURSOR_TOP_LEFT_CORNER */ + cursorStyle = 'nw-resize'; + break; + case 0x0011: /* GLUT_CURSOR_TOP_RIGHT_CORNER */ + cursorStyle = 'ne-resize'; + break; + case 0x0012: /* GLUT_CURSOR_BOTTOM_RIGHT_CORNER */ + cursorStyle = 'se-resize'; + break; + case 0x0013: /* GLUT_CURSOR_BOTTOM_LEFT_CORNER */ + cursorStyle = 'sw-resize'; + break; + case 0x0064: /* GLUT_CURSOR_INHERIT */ + break; + case 0x0065: /* GLUT_CURSOR_NONE */ + cursorStyle = 'none'; + break; + default: + abort("glutSetCursor: Unknown cursor type: " + cursor); + } + Browser.getCanvas().style.cursor = cursorStyle; + }, + + glutCreateWindow__proxy: 'sync', + glutCreateWindow__deps: ['$Browser'], + glutCreateWindow: (name) => { + var contextAttributes = { + antialias: ((GLUT.initDisplayMode & 0x0080 /*GLUT_MULTISAMPLE*/) != 0), + depth: ((GLUT.initDisplayMode & 0x0010 /*GLUT_DEPTH*/) != 0), + stencil: ((GLUT.initDisplayMode & 0x0020 /*GLUT_STENCIL*/) != 0), + alpha: ((GLUT.initDisplayMode & 0x0008 /*GLUT_ALPHA*/) != 0) + }; +#if OFFSCREEN_FRAMEBUFFER + // TODO: Make glutCreateWindow explicitly aware of whether it is being proxied or not, and set these to true only when proxying is being performed. + GL.enableOffscreenFramebufferAttributes(contextAttributes); +#endif + if (!Browser.createContext(Browser.getCanvas(), /*useWebGL=*/true, /*setInModule=*/true, contextAttributes)) { + return 0; // failure + } + return 1; // a new GLUT window ID for the created context + }, + + glutDestroyWindow__proxy: 'sync', + glutDestroyWindow__deps: ['$Browser'], + glutDestroyWindow: (name) => { + delete Module['ctx']; + return 1; + }, + + glutReshapeWindow__proxy: 'sync', + glutReshapeWindow__deps: ['$GLUT', 'glutPostRedisplay'], + glutReshapeWindow: (width, height) => { + Browser.exitFullscreen(); + Browser.setCanvasSize(width, height, true); // N.B. GLUT.reshapeFunc is also registered as a canvas resize callback. + // Just call it once here. + if (GLUT.reshapeFunc) { + {{{ makeDynCall('vii', 'GLUT.reshapeFunc') }}}(width, height); + } + _glutPostRedisplay(); + }, + + glutPositionWindow__proxy: 'sync', + glutPositionWindow__deps: ['$GLUT', 'glutPostRedisplay'], + glutPositionWindow: (x, y) => { + Browser.exitFullscreen(); + /* TODO */ + _glutPostRedisplay(); + }, + + glutFullScreen__proxy: 'sync', + glutFullScreen__deps: ['$GLUT'], + glutFullScreen: () => { + GLUT.windowX = 0; // TODO + GLUT.windowY = 0; // TODO + var canvas = Browser.getCanvas(); + GLUT.windowWidth = canvas.width; + GLUT.windowHeight = canvas.height; + document.addEventListener('fullscreenchange', GLUT.onFullscreenEventChange, true); + document.addEventListener('mozfullscreenchange', GLUT.onFullscreenEventChange, true); + document.addEventListener('webkitfullscreenchange', GLUT.onFullscreenEventChange, true); + Browser.requestFullscreen(/*lockPointer=*/false, /*resizeCanvas=*/false); + }, + + glutInitDisplayMode__proxy: 'sync', + glutInitDisplayMode: (mode) => GLUT.initDisplayMode = mode, + + glutSwapBuffers__proxy: 'sync', + glutSwapBuffers: () => {}, + + glutPostRedisplay__proxy: 'sync', + glutPostRedisplay__deps: ['$MainLoop'], + glutPostRedisplay: () => { + if (GLUT.displayFunc && !GLUT.requestedAnimationFrame) { + GLUT.requestedAnimationFrame = true; + MainLoop.requestAnimationFrame(() => { + GLUT.requestedAnimationFrame = false; + MainLoop.runIter(() => {{{ makeDynCall('v', 'GLUT.displayFunc') }}}()); + }); + } + }, + + glutMainLoop__proxy: 'sync', + glutMainLoop__deps: ['$GLUT', 'glutPostRedisplay'], + glutMainLoop: () => { + // Do an initial resize, since there's no window resize event on startup + GLUT.onResize(); + _glutPostRedisplay(); + throw 'unwind'; + }, + +}; + +autoAddDeps(LibraryGLUT, '$GLUT'); +addToLibrary(LibraryGLUT); diff --git a/src/lib/libhtml5.js b/src/lib/libhtml5.js new file mode 100644 index 0000000000000..f8088f2c13b06 --- /dev/null +++ b/src/lib/libhtml5.js @@ -0,0 +1,2399 @@ +/** + * @license + * Copyright 2014 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryHTML5 = { + $JSEvents__deps: [ +#if PTHREADS + '_emscripten_run_callback_on_thread', +#endif + '$addOnExit', + ], + $JSEvents: { + +#if USE_CLOSURE_COMPILER + // pointers to structs malloc()ed to Emscripten HEAP for JS->C interop. + batteryEvent: 0, + gamepadEvent: 0, + keyEvent: 0, + mouseEvent: 0, + wheelEvent: 0, + uiEvent: 0, + focusEvent: 0, + deviceOrientationEvent: 0, + orientationChangeEvent: 0, + deviceMotionEvent: 0, + fullscreenChangeEvent: 0, + pointerlockChangeEvent: 0, + visibilityChangeEvent: 0, + touchEvent: 0, +#endif + +/* We do not depend on the exact initial values of falsey member fields - these + fields can be populated on-demand to save code size. + (but still documented here to keep track of what is supposed to be present) + + // When we transition from fullscreen to windowed mode, we remember here the + // element that was just in fullscreen mode so that we can report + // information about that element in the event message. + previousFullscreenElement: null, + + // When the C runtime exits via exit(), we unregister all event handlers + // added by this library to be nice and clean. + // Track in this field whether we have yet registered that onExit handler. + removeEventListenersRegistered: false, + +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // If we are in an event handler, specifies the event handler object from + // the eventHandlers array that is currently running. + currentEventHandler: null, +#endif +*/ + removeAllEventListeners() { + while (JSEvents.eventHandlers.length) { + JSEvents._removeHandler(JSEvents.eventHandlers.length - 1); + } +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + JSEvents.deferredCalls = []; +#endif + }, + +#if EXIT_RUNTIME + registerRemoveEventListeners() { + if (!JSEvents.removeEventListenersRegistered) { + addOnExit(JSEvents.removeAllEventListeners); + JSEvents.removeEventListenersRegistered = true; + } + }, +#endif + +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // If positive, we are currently executing in a JS event handler. + // (this particular property must be initialized to zero, as we ++/-- it) + inEventHandler: 0, + + deferredCalls: [], + + // Queues the given function call to occur the next time we enter an event handler. + // Existing implementations of pointerlock apis have required that + // the target element is active in fullscreen mode first. Therefore give + // fullscreen mode request a precedence of 1 and pointer lock a precedence of 2 + // and sort by that to always request fullscreen before pointer lock. + deferCall(targetFunction, precedence, argsList) { + function arraysHaveEqualContent(arrA, arrB) { + if (arrA.length != arrB.length) return false; + + for (var i in arrA) { + if (arrA[i] != arrB[i]) return false; + } + return true; + } + // Test if the given call was already queued, and if so, don't add it again. + for (var call of JSEvents.deferredCalls) { + if (call.targetFunction == targetFunction && arraysHaveEqualContent(call.argsList, argsList)) { + return; + } + } + JSEvents.deferredCalls.push({ + targetFunction, + precedence, + argsList + }); + + JSEvents.deferredCalls.sort((x,y) => x.precedence - y.precedence); + }, + + // Erases all deferred calls to the given target function from the queue list. + removeDeferredCalls(targetFunction) { + JSEvents.deferredCalls = JSEvents.deferredCalls.filter((call) => call.targetFunction != targetFunction); + }, + + canPerformEventHandlerRequests() { + if (navigator.userActivation) { + // Verify against transient activation status from UserActivation API + // whether it is possible to perform a request here without needing to defer. See + // https://developer.mozilla.org/en-US/docs/Web/Security/User_activation#transient_activation + // and https://caniuse.com/mdn-api_useractivation + // At the time of writing, Firefox does not support this API: https://bugzil.la/1791079 + return navigator.userActivation.isActive; + } + + return JSEvents.inEventHandler && JSEvents.currentEventHandler.allowsDeferredCalls; + }, + + runDeferredCalls() { + if (!JSEvents.canPerformEventHandlerRequests()) { + return; + } + var deferredCalls = JSEvents.deferredCalls; + JSEvents.deferredCalls = []; + for (var call of deferredCalls) { + call.targetFunction(...call.argsList); + } + }, +#endif + + // Stores objects representing each currently registered JS event handler. + eventHandlers: [], + + // Removes all event handlers on the given DOM element of the given type. + // Pass in eventTypeString == undefined/null to remove all event handlers + // regardless of the type. + removeAllHandlersOnTarget: (target, eventTypeString) => { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if (JSEvents.eventHandlers[i].target == target && + (!eventTypeString || eventTypeString == JSEvents.eventHandlers[i].eventTypeString)) { + JSEvents._removeHandler(i--); + } + } + }, + + _removeHandler(i) { + var h = JSEvents.eventHandlers[i]; + h.target.removeEventListener(h.eventTypeString, h.eventListenerFunc, h.useCapture); + JSEvents.eventHandlers.splice(i, 1); + }, + + registerOrRemoveHandler(eventHandler) { + if (!eventHandler.target) { +#if ASSERTIONS + err('registerOrRemoveHandler: the target element for event handler registration does not exist, when processing the following event handler registration:'); + console.dir(eventHandler); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + } + if (eventHandler.callbackfunc) { +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + eventHandler.eventListenerFunc = function(event) { + // Increment nesting count for the event handler. + ++JSEvents.inEventHandler; + JSEvents.currentEventHandler = eventHandler; + // Process any old deferred calls the user has placed. + JSEvents.runDeferredCalls(); + // Process the actual event, calls back to user C code handler. + eventHandler.handlerFunc(event); + // Process any new deferred calls that were placed right now from this event handler. + JSEvents.runDeferredCalls(); + // Out of event handler - restore nesting count. + --JSEvents.inEventHandler; + }; +#else + eventHandler.eventListenerFunc = eventHandler.handlerFunc; +#endif + + eventHandler.target.addEventListener(eventHandler.eventTypeString, + eventHandler.eventListenerFunc, + eventHandler.useCapture); + JSEvents.eventHandlers.push(eventHandler); +#if EXIT_RUNTIME + JSEvents.registerRemoveEventListeners(); +#endif + } else { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if (JSEvents.eventHandlers[i].target == eventHandler.target + && JSEvents.eventHandlers[i].eventTypeString == eventHandler.eventTypeString) { + JSEvents._removeHandler(i--); + } + } + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + removeSingleHandler(eventHandler) { + let success = false; + for (let i = 0; i < JSEvents.eventHandlers.length; ++i) { + const handler = JSEvents.eventHandlers[i]; + if (handler.target === eventHandler.target + && handler.eventTypeId === eventHandler.eventTypeId + && handler.callbackfunc === eventHandler.callbackfunc + && handler.userData === eventHandler.userData) { + // in some very rare cases (ex: Safari / fullscreen events), there is more than 1 handler (eventTypeString is different) + JSEvents._removeHandler(i--); + success = true; + } + } + return success ? {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}} : {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + }, + +#if PTHREADS + getTargetThreadForEventCallback(targetThread) { + switch (targetThread) { + case {{{ cDefs.EM_CALLBACK_THREAD_CONTEXT_MAIN_RUNTIME_THREAD }}}: + // The event callback for the current event should be called on the + // main browser thread. (0 == don't proxy) + return 0; + case {{{ cDefs.EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD }}}: + // The event callback for the current event should be backproxied to + // the thread that is registering the event. + // This can be 0 in the case that the caller uses + // EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD but on the main thread + // itself. + return PThread.currentProxiedOperationCallerThread; + default: + // The event callback for the current event should be proxied to the + // given specific thread. + return targetThread; + } + }, +#endif + + getNodeNameForTarget(target) { + if (!target) return ''; + if (target == window) return '#window'; + if (target == screen) return '#screen'; + return target?.nodeName || ''; + }, + + fullscreenEnabled() { + return document.fullscreenEnabled +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + // Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitFullscreenEnabled. + // TODO: If Safari at some point ships with unprefixed version, update the version check above. + || document.webkitFullscreenEnabled +#endif + ; + }, + }, + + $getFullscreenElement__internal: true, + $getFullscreenElement() { + return document.fullscreenElement || document.mozFullScreenElement || + document.webkitFullscreenElement || document.webkitCurrentFullScreenElement || + document.msFullscreenElement; + }, + + $registerKeyEventCallback__noleakcheck: true, + $registerKeyEventCallback__deps: ['$JSEvents', '$findEventTarget', '$stringToUTF8', 'malloc'], + $registerKeyEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenKeyboardEvent.__size__ }}}; + JSEvents.keyEvent ||= _malloc(eventSize); + + var keyEventHandlerFunc = (e) => { +#if ASSERTIONS + assert(e); +#endif + + var keyEventData = JSEvents.keyEvent; + {{{ makeSetValue('keyEventData', C_STRUCTS.EmscriptenKeyboardEvent.timestamp, 'e.timeStamp', 'double') }}}; + + var idx = {{{ getHeapOffset('keyEventData', 'i32') }}}; + + HEAP32[idx + {{{ C_STRUCTS.EmscriptenKeyboardEvent.location / 4 }}}] = e.location; + HEAP8[keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.ctrlKey }}}] = e.ctrlKey; + HEAP8[keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.shiftKey }}}] = e.shiftKey; + HEAP8[keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.altKey }}}] = e.altKey; + HEAP8[keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.metaKey }}}] = e.metaKey; + HEAP8[keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.repeat }}}] = e.repeat; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenKeyboardEvent.charCode / 4 }}}] = e.charCode; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenKeyboardEvent.keyCode / 4 }}}] = e.keyCode; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenKeyboardEvent.which / 4 }}}] = e.which; + stringToUTF8(e.key || '', keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.key }}}, {{{ cDefs.EM_HTML5_SHORT_STRING_LEN_BYTES }}}); + stringToUTF8(e.code || '', keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.code }}}, {{{ cDefs.EM_HTML5_SHORT_STRING_LEN_BYTES }}}); + stringToUTF8(e.char || '', keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.charValue }}}, {{{ cDefs.EM_HTML5_SHORT_STRING_LEN_BYTES }}}); + stringToUTF8(e.locale || '', keyEventData + {{{ C_STRUCTS.EmscriptenKeyboardEvent.locale }}}, {{{ cDefs.EM_HTML5_SHORT_STRING_LEN_BYTES }}}); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, keyEventData, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, keyEventData, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: keyEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + // In DOM capturing and bubbling sequence, there are two special elements at the top of the event chain that can be of interest + // to register many events to: document and window. These cannot be addressed by using document.querySelector(), so + // a special mechanism to address them is needed. (For any other special object, such as screen.orientation, no general access + // scheme should be needed, but the object-specific event callback registration functions should handle them individually). + // + // Users can also add more special event targets, basically by just doing something like + // specialHTMLTargets["!canvas"] = Module.canvas; + // (that will let !canvas map to the canvas held in Module.canvas). + $specialHTMLTargets__docs: '/** @type {Object} */', +#if ENVIRONMENT_MAY_BE_WORKER || ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL || PTHREADS + $specialHTMLTargets: "[0, globalThis.document ?? 0, globalThis.window ?? 0]", +#else + $specialHTMLTargets: "[0, document, window]", +#endif + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + $maybeCStringToJsString: (cString) => { + // "cString > 2" checks if the input is a number, and isn't of the special + // values we accept here, EMSCRIPTEN_EVENT_TARGET_* (which map to 0, 1, 2). + // In other words, if cString > 2 then it's a pointer to a valid place in + // memory, and points to a C string. + return cString > 2 ? UTF8ToString(cString) : cString; + }, + + // Find a DOM element with the given ID, or null if none is found. + $findEventTarget__deps: ['$maybeCStringToJsString', '$specialHTMLTargets'], + $findEventTarget: (target) => { + target = maybeCStringToJsString(target); +#if ENVIRONMENT_MAY_BE_WORKER || ENVIRONMENT_MAY_BE_NODE + var domElement = specialHTMLTargets[target] || globalThis.document?.querySelector(target); +#else + var domElement = specialHTMLTargets[target] || document.querySelector(target); +#endif + return domElement; + }, + +#if OFFSCREENCANVAS_SUPPORT + $findCanvasEventTarget__deps: ['$GL', '$maybeCStringToJsString', '$specialHTMLTargets'], + $findCanvasEventTarget: (target) => { + target = maybeCStringToJsString(target); + + // When compiling with OffscreenCanvas support and looking up a canvas to target, + // we first look up if the target Canvas has been transferred to OffscreenCanvas use. + // These transfers are represented/tracked by GL.offscreenCanvases object, which contain + // the OffscreenCanvas element for each regular Canvas element that has been transferred. + + // Note that each pthread/worker have their own set of GL.offscreenCanvases. That is, + // when an OffscreenCanvas is transferred from a pthread/main thread to another pthread, + // it will move in the GL.offscreenCanvases array between threads. Hence GL.offscreenCanvases + // represents the set of OffscreenCanvases owned by the current calling thread. + + // First check out the list of OffscreenCanvases by CSS selector ID ('#myCanvasID') + return GL.offscreenCanvases[target.slice(1)] // Remove '#' prefix + // If not found, if one is querying by using DOM tag name selector 'canvas', grab the first + // OffscreenCanvas that we can find. + || (target == 'canvas' && Object.values(GL.offscreenCanvases)[0]) + // If not found, check specialHTMLTargets + || specialHTMLTargets[target] + // If that is not found either, query via the regular DOM selector. +#if PTHREADS + || globalThis.document?.querySelector(target); +#else + || document.querySelector(target); +#endif + }, +#else + $findCanvasEventTarget: '$findEventTarget', +#endif + +#else + // Find a DOM element with the given ID, or null if none is found. + $findEventTarget__deps: ['$specialHTMLTargets'], + $findEventTarget: (target) => { +#if ASSERTIONS + warnOnce('Rules for selecting event targets in HTML5 API are changing: instead of using document.getElementById() that only can refer to elements by their DOM ID, new event target selection mechanism uses the more flexible function document.querySelector() that can look up element names, classes, and complex CSS selectors. Build with -sDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR to change to the new lookup rules. See https://github.com/emscripten-core/emscripten/pull/7977 for more details.'); +#endif + // The sensible "default" target varies between events, but use window as the default + // since DOM events mostly can default to that. Specific callback registrations + // override their own defaults. + if (!target) return window; + if (typeof target == "number") target = specialHTMLTargets[target] || UTF8ToString(target); + if (target === '#window') return window; + else if (target === '#document') return document; + else if (target === '#screen') return screen; + else if (target === '#canvas') return Module['canvas']; + else if (typeof target == 'string') +#if ENVIRONMENT_MAY_BE_WORKER || ENVIRONMENT_MAY_BE_NODE + return globalThis.document?.getElementById(target); +#else + return document.getElementById(target); +#endif + return target; + }, + + // Like findEventTarget, but looks for OffscreenCanvas elements first + $findCanvasEventTarget__deps: ['$findEventTarget'], + $findCanvasEventTarget: (target) => { + if (typeof target == 'number') target = UTF8ToString(target); + if (!target || target === '#canvas') { + if (typeof GL != 'undefined' && GL.offscreenCanvases['canvas']) return GL.offscreenCanvases['canvas']; // TODO: Remove this line, target '#canvas' should refer only to Module['canvas'], not to GL.offscreenCanvases['canvas'] - but need stricter tests to be able to remove this line. + return Module['canvas']; + } + if (typeof GL != 'undefined' && GL.offscreenCanvases[target]) return GL.offscreenCanvases[target]; + return findEventTarget(target); + }, +#endif + + emscripten_html5_remove_event_listener__proxy: 'sync', + emscripten_html5_remove_event_listener__deps: ['$JSEvents', '$findEventTarget'], + emscripten_html5_remove_event_listener: (target, userData, eventTypeId, callback) => { + var eventHandler = { + target: findEventTarget(target), + userData, + eventTypeId, + callbackfunc: callback, + }; + return JSEvents.removeSingleHandler(eventHandler); + }, + + emscripten_set_keypress_callback_on_thread__proxy: 'sync', + emscripten_set_keypress_callback_on_thread__deps: ['$registerKeyEventCallback'], + emscripten_set_keypress_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerKeyEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_KEYPRESS }}}, "keypress", targetThread), + + emscripten_set_keydown_callback_on_thread__proxy: 'sync', + emscripten_set_keydown_callback_on_thread__deps: ['$registerKeyEventCallback'], + emscripten_set_keydown_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerKeyEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_KEYDOWN }}}, "keydown", targetThread), + + emscripten_set_keyup_callback_on_thread__proxy: 'sync', + emscripten_set_keyup_callback_on_thread__deps: ['$registerKeyEventCallback'], + emscripten_set_keyup_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerKeyEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_KEYUP }}}, "keyup", targetThread), + + // Outline access to function .getBoundingClientRect() since it is a long string. Closure compiler does not outline access to it by itself, but it can inline access if + // there is only one caller to this function. + $getBoundingClientRect__deps: ['$specialHTMLTargets'], + $getBoundingClientRect: (e) => specialHTMLTargets.indexOf(e) < 0 ? e.getBoundingClientRect() : {'left':0,'top':0}, + + // Copies mouse event data from the given JS mouse event 'e' to the specified Emscripten mouse event structure in the HEAP. + // eventStruct: the structure to populate. + // e: The JS mouse event to read data from. + // target: Specifies a target DOM element that will be used as the reference to populate targetX and targetY parameters. + $fillMouseEventData__deps: ['$getBoundingClientRect'], + $fillMouseEventData: (eventStruct, e, target) => { +#if ASSERTIONS + assert(eventStruct % 4 == 0); +#endif + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.timestamp, 'e.timeStamp', 'double') }}}; + var idx = {{{ getHeapOffset('eventStruct', 'i32') }}}; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.screenX / 4 }}}] = e.screenX; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.screenY / 4 }}}] = e.screenY; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.clientX / 4 }}}] = e.clientX; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.clientY / 4 }}}] = e.clientY; + HEAP8[eventStruct + {{{ C_STRUCTS.EmscriptenMouseEvent.ctrlKey }}}] = e.ctrlKey; + HEAP8[eventStruct + {{{ C_STRUCTS.EmscriptenMouseEvent.shiftKey }}}] = e.shiftKey; + HEAP8[eventStruct + {{{ C_STRUCTS.EmscriptenMouseEvent.altKey }}}] = e.altKey; + HEAP8[eventStruct + {{{ C_STRUCTS.EmscriptenMouseEvent.metaKey }}}] = e.metaKey; + HEAP16[idx*2 + {{{ C_STRUCTS.EmscriptenMouseEvent.button / 2 }}}] = e.button; + HEAP16[idx*2 + {{{ C_STRUCTS.EmscriptenMouseEvent.buttons / 2 }}}] = e.buttons; + + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.movementX / 4 }}}] = e["movementX"]; + + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.movementY / 4 }}}] = e["movementY"]; + +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + if (Module['canvas']) { + var rect = getBoundingClientRect(Module['canvas']); + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.canvasX / 4 }}}] = e.clientX - (rect.left | 0); + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.canvasY / 4 }}}] = e.clientY - (rect.top | 0); + } else { // Canvas is not initialized, return 0. + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.canvasX / 4 }}}] = 0; + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.canvasY / 4 }}}] = 0; + } +#endif + // Note: rect contains doubles (truncated to placate SAFE_HEAP, which is the same behaviour when writing to HEAP32 anyway) + var rect = getBoundingClientRect(target); + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.targetX / 4 }}}] = e.clientX - (rect.left | 0); + HEAP32[idx + {{{ C_STRUCTS.EmscriptenMouseEvent.targetY / 4 }}}] = e.clientY - (rect.top | 0); + }, + + $registerMouseEventCallback__noleakcheck: true, + $registerMouseEventCallback__deps: ['$JSEvents', '$fillMouseEventData', '$findEventTarget', 'malloc'], + $registerMouseEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenMouseEvent.__size__ }}}; + JSEvents.mouseEvent ||= _malloc(eventSize); + target = findEventTarget(target); + + var mouseEventHandlerFunc = (e) => { + // TODO: Make this access thread safe, or this could update live while app is reading it. + fillMouseEventData(JSEvents.mouseEvent, e, target); + +#if PTHREADS + if (targetThread) { + __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, JSEvents.mouseEvent, eventSize, userData); + } else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, JSEvents.mouseEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! +#endif + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: mouseEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_click_callback_on_thread__proxy: 'sync', + emscripten_set_click_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_click_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_CLICK }}}, "click", targetThread), + + emscripten_set_mousedown_callback_on_thread__proxy: 'sync', + emscripten_set_mousedown_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mousedown_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEDOWN }}}, "mousedown", targetThread), + + emscripten_set_mouseup_callback_on_thread__proxy: 'sync', + emscripten_set_mouseup_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mouseup_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEUP }}}, "mouseup", targetThread), + + emscripten_set_dblclick_callback_on_thread__proxy: 'sync', + emscripten_set_dblclick_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_dblclick_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_DBLCLICK }}}, "dblclick", targetThread), + + emscripten_set_mousemove_callback_on_thread__proxy: 'sync', + emscripten_set_mousemove_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mousemove_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEMOVE }}}, "mousemove", targetThread), + + emscripten_set_mouseenter_callback_on_thread__proxy: 'sync', + emscripten_set_mouseenter_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mouseenter_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEENTER }}}, "mouseenter", targetThread), + + emscripten_set_mouseleave_callback_on_thread__proxy: 'sync', + emscripten_set_mouseleave_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mouseleave_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSELEAVE }}}, "mouseleave", targetThread), + + emscripten_set_mouseover_callback_on_thread__proxy: 'sync', + emscripten_set_mouseover_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mouseover_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEOVER }}}, "mouseover", targetThread), + + emscripten_set_mouseout_callback_on_thread__proxy: 'sync', + emscripten_set_mouseout_callback_on_thread__deps: ['$registerMouseEventCallback'], + emscripten_set_mouseout_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_MOUSEOUT }}}, "mouseout", targetThread), + + // HTML5 does not really have a polling API for mouse events, so implement one + // manually by returning the data from the most recently received event. This + // requires that user has registered at least some no-op function as an event + // handler to any of the mouse function. + _emscripten_get_last_mouse_event__proxy: 'sync', + _emscripten_get_last_mouse_event__internal: true, + _emscripten_get_last_mouse_event__deps: ['$JSEvents'], + _emscripten_get_last_mouse_event: () => JSEvents.mouseEvent, + + $registerWheelEventCallback__noleakcheck: true, + $registerWheelEventCallback__deps: ['$JSEvents', '$fillMouseEventData', 'malloc'], + $registerWheelEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenWheelEvent.__size__ }}}; + JSEvents.wheelEvent ||= _malloc(eventSize) + + // The DOM Level 3 events spec event 'wheel' + var wheelHandlerFunc = (e) => { + var wheelEvent = JSEvents.wheelEvent; + fillMouseEventData(wheelEvent, e, target); + {{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}}; + {{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e["deltaY"]', 'double') }}}; + {{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e["deltaZ"]', 'double') }}}; + {{{ makeSetValue('wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e["deltaMode"]', 'i32') }}}; +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, wheelEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, wheelEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + allowsDeferredCalls: true, +#endif + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: wheelHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_wheel_callback_on_thread__proxy: 'sync', + emscripten_set_wheel_callback_on_thread__deps: ['$registerWheelEventCallback', '$findEventTarget'], + emscripten_set_wheel_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + target = findEventTarget(target); + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + if (typeof target.onwheel != 'undefined') { + return registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_WHEEL }}}, "wheel", targetThread); + } else { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + }, + + $registerUiEventCallback__noleakcheck: true, + $registerUiEventCallback__deps: ['$JSEvents', '$findEventTarget', 'malloc'], + $registerUiEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenUiEvent.__size__ }}}; + JSEvents.uiEvent ||= _malloc(eventSize); + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + if (eventTypeId == {{{ cDefs.EMSCRIPTEN_EVENT_SCROLL }}} && !target) { + target = document; // By default read scroll events on document rather than window. + } else { + target = findEventTarget(target); + } +#else +#endif + + var uiEventHandlerFunc = (e) => { + if (e.target != target) { + // Never take ui events such as scroll via a 'bubbled' route, but always from the direct element that + // was targeted. Otherwise e.g. if app logs a message in response to a page scroll, the Emscripten log + // message box could cause to scroll, generating a new (bubbled) scroll message, causing a new log print, + // causing a new scroll, etc.. + return; + } + var b = document.body; // Take document.body to a variable, Closure compiler does not outline access to it on its own. + if (!b) { + // During a page unload 'body' can be null, with "Cannot read property 'clientWidth' of null" being thrown + return; + } + var uiEvent = JSEvents.uiEvent; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.detail, '0', 'i32') }}}; // always zero for resize and scroll + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.documentBodyClientWidth, 'b.clientWidth', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.documentBodyClientHeight, 'b.clientHeight', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.windowInnerWidth, 'innerWidth', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.windowInnerHeight, 'innerHeight', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.windowOuterWidth, 'outerWidth', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.windowOuterHeight, 'outerHeight', 'i32') }}}; + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.scrollTop, 'pageXOffset | 0', 'i32') }}}; // scroll offsets are float + {{{ makeSetValue('uiEvent', C_STRUCTS.EmscriptenUiEvent.scrollLeft, 'pageYOffset | 0', 'i32') }}}; +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, uiEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, uiEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: uiEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_resize_callback_on_thread__proxy: 'sync', + emscripten_set_resize_callback_on_thread__deps: ['$registerUiEventCallback'], + emscripten_set_resize_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerUiEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_RESIZE }}}, "resize", targetThread), + + emscripten_set_scroll_callback_on_thread__proxy: 'sync', + emscripten_set_scroll_callback_on_thread__deps: ['$registerUiEventCallback'], + emscripten_set_scroll_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerUiEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_SCROLL }}}, "scroll", targetThread), + + $registerFocusEventCallback__noleakcheck: true, + $registerFocusEventCallback__deps: ['$JSEvents', '$findEventTarget', 'malloc', '$stringToUTF8'], + $registerFocusEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenFocusEvent.__size__ }}}; + JSEvents.focusEvent ||= _malloc(eventSize); + + var focusEventHandlerFunc = (e) => { + var nodeName = JSEvents.getNodeNameForTarget(e.target); + var id = e.target.id ? e.target.id : ''; + + var focusEvent = JSEvents.focusEvent; + stringToUTF8(nodeName, focusEvent + {{{ C_STRUCTS.EmscriptenFocusEvent.nodeName }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + stringToUTF8(id, focusEvent + {{{ C_STRUCTS.EmscriptenFocusEvent.id }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, focusEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, focusEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: focusEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_blur_callback_on_thread__proxy: 'sync', + emscripten_set_blur_callback_on_thread__deps: ['$registerFocusEventCallback'], + emscripten_set_blur_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerFocusEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_BLUR }}}, "blur", targetThread), + + emscripten_set_focus_callback_on_thread__proxy: 'sync', + emscripten_set_focus_callback_on_thread__deps: ['$registerFocusEventCallback'], + emscripten_set_focus_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerFocusEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FOCUS }}}, "focus", targetThread), + + emscripten_set_focusin_callback_on_thread__proxy: 'sync', + emscripten_set_focusin_callback_on_thread__deps: ['$registerFocusEventCallback'], + emscripten_set_focusin_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerFocusEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FOCUSIN }}}, "focusin", targetThread), + + emscripten_set_focusout_callback_on_thread__proxy: 'sync', + emscripten_set_focusout_callback_on_thread__deps: ['$registerFocusEventCallback'], + emscripten_set_focusout_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerFocusEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FOCUSOUT }}}, "focusout", targetThread), + + $fillDeviceOrientationEventData: (eventStruct, e, target) => { + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.alpha, 'e.alpha', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.beta, 'e.beta', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.gamma, 'e.gamma', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceOrientationEvent.absolute, 'e.absolute', 'i8') }}}; + }, + + $registerDeviceOrientationEventCallback__noleakcheck: true, + $registerDeviceOrientationEventCallback__deps: ['$JSEvents', '$fillDeviceOrientationEventData', '$findEventTarget'], + $registerDeviceOrientationEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenDeviceOrientationEvent.__size__ }}}; + JSEvents.deviceOrientationEvent ||= _malloc(eventSize); + + var deviceOrientationEventHandlerFunc = (e) => { + fillDeviceOrientationEventData(JSEvents.deviceOrientationEvent, e, target); // TODO: Thread-safety with respect to emscripten_get_deviceorientation_status() + +#if PTHREADS + if (targetThread) { + __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, JSEvents.deviceOrientationEvent, eventSize, userData); + } else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, JSEvents.deviceOrientationEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: deviceOrientationEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_deviceorientation_callback_on_thread__proxy: 'sync', + emscripten_set_deviceorientation_callback_on_thread__deps: ['$registerDeviceOrientationEventCallback'], + emscripten_set_deviceorientation_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => { + return registerDeviceOrientationEventCallback({{{ cDefs.EMSCRIPTEN_EVENT_TARGET_WINDOW }}}, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_DEVICEORIENTATION }}}, "deviceorientation", targetThread); + }, + + // HTML5 does not really have a polling API for device orientation events, so + // implement one manually by returning the data from the most recently + // received event. This requires that user has registered at least some + // no-op function as an event handler. + _emscripten_get_last_deviceorientation_event__proxy: 'sync', + _emscripten_get_last_deviceorientation_event__internal: true, + _emscripten_get_last_deviceorientation_event__deps: ['$JSEvents'], + _emscripten_get_last_deviceorientation_event: () => JSEvents.deviceOrientationEvent, + + $fillDeviceMotionEventData: (eventStruct, e, target) => { + var supportedFields = 0; + var a = e['acceleration']; + supportedFields |= a && {{{ cDefs.EMSCRIPTEN_DEVICE_MOTION_EVENT_SUPPORTS_ACCELERATION }}}; + var ag = e['accelerationIncludingGravity']; + supportedFields |= ag && {{{ cDefs.EMSCRIPTEN_DEVICE_MOTION_EVENT_SUPPORTS_ACCELERATION_INCLUDING_GRAVITY }}}; + var rr = e['rotationRate']; + supportedFields |= rr && {{{ cDefs.EMSCRIPTEN_DEVICE_MOTION_EVENT_SUPPORTS_ROTATION_RATE }}}; + a = a || {}; + ag = ag || {}; + rr = rr || {}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.supportedFields, 'supportedFields', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationX, 'a["x"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationY, 'a["y"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationZ, 'a["z"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationIncludingGravityX, 'ag["x"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationIncludingGravityY, 'ag["y"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.accelerationIncludingGravityZ, 'ag["z"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.rotationRateAlpha, 'rr["alpha"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.rotationRateBeta, 'rr["beta"]', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenDeviceMotionEvent.rotationRateGamma, 'rr["gamma"]', 'double') }}}; + }, + + $registerDeviceMotionEventCallback__noleakcheck: true, + $registerDeviceMotionEventCallback__deps: ['$JSEvents', '$fillDeviceMotionEventData', '$findEventTarget', 'malloc'], + $registerDeviceMotionEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenDeviceMotionEvent.__size__ }}}; + JSEvents.deviceMotionEvent ||= _malloc(eventSize); + + var deviceMotionEventHandlerFunc = (e) => { + fillDeviceMotionEventData(JSEvents.deviceMotionEvent, e, target); // TODO: Thread-safety with respect to emscripten_get_devicemotion_status() + +#if PTHREADS + if (targetThread) { + __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, JSEvents.deviceMotionEvent, eventSize, userData); + } else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, JSEvents.deviceMotionEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: deviceMotionEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_devicemotion_callback_on_thread__proxy: 'sync', + emscripten_set_devicemotion_callback_on_thread__deps: ['$registerDeviceMotionEventCallback'], + emscripten_set_devicemotion_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => + registerDeviceMotionEventCallback({{{ cDefs.EMSCRIPTEN_EVENT_TARGET_WINDOW }}}, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_DEVICEMOTION }}}, "devicemotion", targetThread), + + // HTML5 does not really have a polling API for device motion events, so + // implement one manually by returning the data from the most recently + // received event. This requires that user has registered at least some + // no-op function as an event handler. + _emscripten_get_last_devicemotion_event__proxy: 'sync', + _emscripten_get_last_devicemotion_event__internal: true, + _emscripten_get_last_devicemotion_event__deps: ['$JSEvents'], + _emscripten_get_last_devicemotion_event: () => JSEvents.deviceMotionEvent, + + $screenOrientation: () => { + if (!window.screen) return undefined; + return screen.orientation || screen['mozOrientation'] || screen['webkitOrientation']; + }, + + $fillOrientationChangeEventData__deps: ['$screenOrientation'], + $fillOrientationChangeEventData: (eventStruct) => { + // OrientationType enum + var orientationsType1 = ['portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary']; + // alternative selection from OrientationLockType enum + var orientationsType2 = ['portrait', 'portrait', 'landscape', 'landscape']; + + var orientationIndex = {{{ cDefs.EMSCRIPTEN_ORIENTATION_UNSUPPORTED }}}; + var orientationAngle = 0; + var screenOrientObj = screenOrientation(); + if (typeof screenOrientObj === 'object') { + orientationIndex = orientationsType1.indexOf(screenOrientObj.type); + if (orientationIndex < 0) { + orientationIndex = orientationsType2.indexOf(screenOrientObj.type); + } + if (orientationIndex >= 0) { + orientationIndex = 1 << orientationIndex; + } + orientationAngle = screenOrientObj.angle; + } +#if MIN_SAFARI_VERSION < 160400 + else { + // fallback for Safari earlier than 16.4 (March 2023) + orientationAngle = window.orientation; + } +#endif + + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenOrientationChangeEvent.orientationIndex, 'orientationIndex', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenOrientationChangeEvent.orientationAngle, 'orientationAngle', 'i32') }}}; + }, + + $registerOrientationChangeEventCallback__noleakcheck: true, + $registerOrientationChangeEventCallback__deps: ['$JSEvents', '$fillOrientationChangeEventData', 'malloc'], + $registerOrientationChangeEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenOrientationChangeEvent.__size__ }}}; + JSEvents.orientationChangeEvent ||= _malloc(eventSize); + + var orientationChangeEventHandlerFunc = (e) => { + var orientationChangeEvent = JSEvents.orientationChangeEvent; + fillOrientationChangeEventData(orientationChangeEvent); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, orientationChangeEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, orientationChangeEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: orientationChangeEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_orientationchange_callback_on_thread__proxy: 'sync', + emscripten_set_orientationchange_callback_on_thread__deps: ['$registerOrientationChangeEventCallback'], + emscripten_set_orientationchange_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => { + if (!window.screen || !screen.orientation) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + return registerOrientationChangeEventCallback(screen.orientation, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_ORIENTATIONCHANGE }}}, 'change', targetThread); + }, + + emscripten_get_orientation_status__proxy: 'sync', + emscripten_get_orientation_status__deps: ['$fillOrientationChangeEventData', '$screenOrientation'], + emscripten_get_orientation_status: (orientationChangeEvent) => { + // screenOrientation() resolving standard, window.orientation being the deprecated mobile-only + if (!screenOrientation() && typeof orientation == 'undefined') return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + fillOrientationChangeEventData(orientationChangeEvent); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_lock_orientation__proxy: 'sync', + emscripten_lock_orientation: (allowedOrientations) => { + var orientations = []; + if (allowedOrientations & 1) orientations.push("portrait-primary"); + if (allowedOrientations & 2) orientations.push("portrait-secondary"); + if (allowedOrientations & 4) orientations.push("landscape-primary"); + if (allowedOrientations & 8) orientations.push("landscape-secondary"); + var succeeded; + if (screen.lockOrientation) { + succeeded = screen.lockOrientation(orientations); + } else if (screen.mozLockOrientation) { + succeeded = screen.mozLockOrientation(orientations); + } else if (screen.webkitLockOrientation) { + succeeded = screen.webkitLockOrientation(orientations); + } else { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + if (succeeded) { + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_FAILED }}}; + }, + + emscripten_unlock_orientation__proxy: 'sync', + emscripten_unlock_orientation: () => { + if (screen.unlockOrientation) { + screen.unlockOrientation(); + } else if (screen.mozUnlockOrientation) { + screen.mozUnlockOrientation(); + } else if (screen.webkitUnlockOrientation) { + screen.webkitUnlockOrientation(); + } else { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $fillFullscreenChangeEventData__deps: ['$JSEvents', '$stringToUTF8', '$getFullscreenElement'], + $fillFullscreenChangeEventData: (eventStruct) => { + var fullscreenElement = getFullscreenElement(); + var isFullscreen = !!fullscreenElement; + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.isFullscreen, 'isFullscreen', 'i8') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.fullscreenEnabled, 'JSEvents.fullscreenEnabled()', 'i8') }}}; + // If transitioning to fullscreen, report info about the element that is now fullscreen. + // If transitioning to windowed mode, report info about the element that just was fullscreen. + var reportedElement = isFullscreen ? fullscreenElement : JSEvents.previousFullscreenElement; + var nodeName = JSEvents.getNodeNameForTarget(reportedElement); + var id = reportedElement?.id || ''; + stringToUTF8(nodeName, eventStruct + {{{ C_STRUCTS.EmscriptenFullscreenChangeEvent.nodeName }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + stringToUTF8(id, eventStruct + {{{ C_STRUCTS.EmscriptenFullscreenChangeEvent.id }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.elementWidth, 'reportedElement ? reportedElement.clientWidth : 0', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.elementHeight, 'reportedElement ? reportedElement.clientHeight : 0', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.screenWidth, 'screen.width', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.screenHeight, 'screen.height', 'i32') }}}; + if (isFullscreen) { + JSEvents.previousFullscreenElement = fullscreenElement; + } + }, + + $registerFullscreenChangeEventCallback__noleakcheck: true, + $registerFullscreenChangeEventCallback__deps: ['$JSEvents', '$fillFullscreenChangeEventData', 'malloc'], + $registerFullscreenChangeEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenFullscreenChangeEvent.__size__ }}}; + JSEvents.fullscreenChangeEvent ||= _malloc(eventSize); + + var fullscreenChangeEventHandlerFunc = (e) => { + var fullscreenChangeEvent = JSEvents.fullscreenChangeEvent; + fillFullscreenChangeEventData(fullscreenChangeEvent); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, fullscreenChangeEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, fullscreenChangeEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: fullscreenChangeEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_fullscreenchange_callback_on_thread__proxy: 'sync', + emscripten_set_fullscreenchange_callback_on_thread__deps: ['$JSEvents', '$registerFullscreenChangeEventCallback', '$findEventTarget', +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + '$specialHTMLTargets' +#endif + ], + emscripten_set_fullscreenchange_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + if (!JSEvents.fullscreenEnabled()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + target = target ? findEventTarget(target) : specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}]; +#endif + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + // TODO: When this block is removed, also change test/test_html5_remove_event_listener.c test expectation on emscripten_set_fullscreenchange_callback(). + registerFullscreenChangeEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FULLSCREENCHANGE }}}, "webkitfullscreenchange", targetThread); +#endif + + return registerFullscreenChangeEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_FULLSCREENCHANGE }}}, "fullscreenchange", targetThread); + }, + + emscripten_get_fullscreen_status__proxy: 'sync', + emscripten_get_fullscreen_status__deps: ['$JSEvents', '$fillFullscreenChangeEventData'], + emscripten_get_fullscreen_status: (fullscreenStatus) => { + if (!JSEvents.fullscreenEnabled()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + fillFullscreenChangeEventData(fullscreenStatus); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $JSEvents_requestFullscreen__deps: ['$JSEvents', '$JSEvents_resizeCanvasForFullscreen'], + $JSEvents_requestFullscreen: (target, strategy) => { + // EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT + EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE is a mode where no extra logic is performed to the DOM elements. + if (strategy.scaleMode != {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT }}} || strategy.canvasResolutionScaleMode != {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE }}}) { + JSEvents_resizeCanvasForFullscreen(target, strategy); + } + + if (target.requestFullscreen) { + target.requestFullscreen(); +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + } else if (target.webkitRequestFullscreen) { + target.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); +#endif + } else { + return JSEvents.fullscreenEnabled() ? {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}} : {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + + currentFullscreenStrategy = strategy; + + if (strategy.canvasResizedCallback) { +#if PTHREADS + if (strategy.canvasResizedCallbackTargetThread) __emscripten_run_callback_on_thread(strategy.canvasResizedCallbackTargetThread, strategy.canvasResizedCallback, {{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + else +#endif + {{{ makeDynCall('iipp', 'strategy.canvasResizedCallback') }}}({{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + } + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $JSEvents_resizeCanvasForFullscreen__deps: ['$registerRestoreOldStyle', '$getCanvasElementSize', '$setLetterbox', '$setCanvasElementSize', '$getBoundingClientRect'], + $JSEvents_resizeCanvasForFullscreen: (target, strategy) => { + var restoreOldStyle = registerRestoreOldStyle(target); + var cssWidth = strategy.softFullscreen ? innerWidth : screen.width; + var cssHeight = strategy.softFullscreen ? innerHeight : screen.height; + var rect = getBoundingClientRect(target); + var windowedCssWidth = rect.width; + var windowedCssHeight = rect.height; + var canvasSize = getCanvasElementSize(target); + var windowedRttWidth = canvasSize[0]; + var windowedRttHeight = canvasSize[1]; + + if (strategy.scaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_CENTER }}}) { + setLetterbox(target, (cssHeight - windowedCssHeight) / 2, (cssWidth - windowedCssWidth) / 2); + cssWidth = windowedCssWidth; + cssHeight = windowedCssHeight; + } else if (strategy.scaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT }}}) { + if (cssWidth*windowedRttHeight < windowedRttWidth*cssHeight) { + var desiredCssHeight = windowedRttHeight * cssWidth / windowedRttWidth; + setLetterbox(target, (cssHeight - desiredCssHeight) / 2, 0); + cssHeight = desiredCssHeight; + } else { + var desiredCssWidth = windowedRttWidth * cssHeight / windowedRttHeight; + setLetterbox(target, 0, (cssWidth - desiredCssWidth) / 2); + cssWidth = desiredCssWidth; + } + } + + // If we are adding padding, must choose a background color or otherwise Chrome will give the + // padding a default white color. Do it only if user has not customized their own background color. + target.style.backgroundColor ||= 'black'; + // IE11 does the same, but requires the color to be set in the document body. + document.body.style.backgroundColor ||= 'black'; // IE11 + // Firefox always shows black letterboxes independent of style color. + + target.style.width = cssWidth + 'px'; + target.style.height = cssHeight + 'px'; + + if (strategy.filteringMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_FILTERING_NEAREST }}}) { + target.style.imageRendering = 'optimizeSpeed'; + target.style.imageRendering = '-moz-crisp-edges'; + target.style.imageRendering = '-o-crisp-edges'; + target.style.imageRendering = '-webkit-optimize-contrast'; + target.style.imageRendering = 'optimize-contrast'; + target.style.imageRendering = 'crisp-edges'; + target.style.imageRendering = 'pixelated'; + } + + var dpiScale = (strategy.canvasResolutionScaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF }}}) ? devicePixelRatio : 1; + if (strategy.canvasResolutionScaleMode != {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE }}}) { + var newWidth = (cssWidth * dpiScale)|0; + var newHeight = (cssHeight * dpiScale)|0; + setCanvasElementSize(target, newWidth, newHeight); + if (target.GLctxObject) target.GLctxObject.GLctx.viewport(0, 0, newWidth, newHeight); + } + return restoreOldStyle; + }, + + $registerRestoreOldStyle__deps: ['$getCanvasElementSize', '$setCanvasElementSize', '$currentFullscreenStrategy'], + $registerRestoreOldStyle: (canvas) => { + var canvasSize = getCanvasElementSize(canvas); + var oldWidth = canvasSize[0]; + var oldHeight = canvasSize[1]; + var oldCssWidth = canvas.style.width; + var oldCssHeight = canvas.style.height; + var oldBackgroundColor = canvas.style.backgroundColor; // Chrome reads color from here. + var oldDocumentBackgroundColor = document.body.style.backgroundColor; // IE11 reads color from here. + // Firefox always has black background color. + var oldPaddingLeft = canvas.style.paddingLeft; // Chrome, FF, Safari + var oldPaddingRight = canvas.style.paddingRight; + var oldPaddingTop = canvas.style.paddingTop; + var oldPaddingBottom = canvas.style.paddingBottom; + var oldMarginLeft = canvas.style.marginLeft; // IE11 + var oldMarginRight = canvas.style.marginRight; + var oldMarginTop = canvas.style.marginTop; + var oldMarginBottom = canvas.style.marginBottom; + var oldDocumentBodyMargin = document.body.style.margin; + var oldDocumentOverflow = document.documentElement.style.overflow; // Chrome, Firefox + var oldDocumentScroll = document.body.scroll; // IE + var oldImageRendering = canvas.style.imageRendering; + + function restoreOldStyle() { + if (!getFullscreenElement()) { + document.removeEventListener('fullscreenchange', restoreOldStyle); + +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + document.removeEventListener('webkitfullscreenchange', restoreOldStyle); +#endif + + setCanvasElementSize(canvas, oldWidth, oldHeight); + + canvas.style.width = oldCssWidth; + canvas.style.height = oldCssHeight; + canvas.style.backgroundColor = oldBackgroundColor; // Chrome + // IE11 hack: assigning 'undefined' or an empty string to document.body.style.backgroundColor has no effect, so first assign back the default color + // before setting the undefined value. Setting undefined value is also important, or otherwise we would later treat that as something that the user + // had explicitly set so subsequent fullscreen transitions would not set background color properly. + if (!oldDocumentBackgroundColor) document.body.style.backgroundColor = 'white'; + document.body.style.backgroundColor = oldDocumentBackgroundColor; // IE11 + canvas.style.paddingLeft = oldPaddingLeft; // Chrome, FF, Safari + canvas.style.paddingRight = oldPaddingRight; + canvas.style.paddingTop = oldPaddingTop; + canvas.style.paddingBottom = oldPaddingBottom; + canvas.style.marginLeft = oldMarginLeft; // IE11 + canvas.style.marginRight = oldMarginRight; + canvas.style.marginTop = oldMarginTop; + canvas.style.marginBottom = oldMarginBottom; + document.body.style.margin = oldDocumentBodyMargin; + document.documentElement.style.overflow = oldDocumentOverflow; // Chrome, Firefox + document.body.scroll = oldDocumentScroll; // IE + canvas.style.imageRendering = oldImageRendering; + if (canvas.GLctxObject) canvas.GLctxObject.GLctx.viewport(0, 0, oldWidth, oldHeight); + + if (currentFullscreenStrategy.canvasResizedCallback) { +#if PTHREADS + if (currentFullscreenStrategy.canvasResizedCallbackTargetThread) __emscripten_run_callback_on_thread(currentFullscreenStrategy.canvasResizedCallbackTargetThread, currentFullscreenStrategy.canvasResizedCallback, {{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, currentFullscreenStrategy.canvasResizedCallbackUserData); + else +#endif + {{{ makeDynCall('iipp', 'currentFullscreenStrategy.canvasResizedCallback') }}}({{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, currentFullscreenStrategy.canvasResizedCallbackUserData); + } + } + } + document.addEventListener('fullscreenchange', restoreOldStyle); +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + // As of Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitfullscreenchange. TODO: revisit this check once Safari ships unprefixed version. + document.addEventListener('webkitfullscreenchange', restoreOldStyle); +#endif + return restoreOldStyle; + }, + + // Walks the DOM tree and hides every element by setting "display: none;" except the given element. + // Returns a list of [{node: element, displayState: oldDisplayStyle}] entries to allow restoring previous + // visibility states after done. + $hideEverythingExceptGivenElement: (onlyVisibleElement) => { + var child = onlyVisibleElement; + var parent = child.parentNode; + var hiddenElements = []; + while (child != document.body) { + var children = parent.children; + for (var currChild of children) { + if (currChild != child) { + hiddenElements.push({ node: currChild, displayState: currChild.style.display }); + currChild.style.display = 'none'; + } + } + child = parent; + parent = parent.parentNode; + } + return hiddenElements; + }, + + // Applies old visibility states, given a list of changes returned by hideEverythingExceptGivenElement(). + $restoreHiddenElements: (hiddenElements) => { + for (var elem of hiddenElements) { + elem.node.style.display = elem.displayState; + } + }, + + // Add letterboxes to a fullscreen element in a cross-browser way. + $setLetterbox: (element, topBottom, leftRight) => { + // Cannot use margin to specify letterboxes in FF or Chrome, since those ignore margins in fullscreen mode. + element.style.paddingLeft = element.style.paddingRight = leftRight + 'px'; + element.style.paddingTop = element.style.paddingBottom = topBottom + 'px'; + }, + + $currentFullscreenStrategy: {}, + $restoreOldWindowedStyle: null, + + $softFullscreenResizeWebGLRenderTarget__deps: ['$setLetterbox', '$currentFullscreenStrategy', '$getCanvasElementSize', '$setCanvasElementSize', '$jstoi_q'], + $softFullscreenResizeWebGLRenderTarget: () => { + var dpr = devicePixelRatio; + var inHiDPIFullscreenMode = currentFullscreenStrategy.canvasResolutionScaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF }}}; + var inAspectRatioFixedFullscreenMode = currentFullscreenStrategy.scaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT }}}; + var inPixelPerfectFullscreenMode = currentFullscreenStrategy.canvasResolutionScaleMode != {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE }}}; + var inCenteredWithoutScalingFullscreenMode = currentFullscreenStrategy.scaleMode == {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_CENTER }}}; + var screenWidth = inHiDPIFullscreenMode ? Math.round(innerWidth*dpr) : innerWidth; + var screenHeight = inHiDPIFullscreenMode ? Math.round(innerHeight*dpr) : innerHeight; + var w = screenWidth; + var h = screenHeight; + var canvas = currentFullscreenStrategy.target; + var canvasSize = getCanvasElementSize(canvas); + var x = canvasSize[0]; + var y = canvasSize[1]; + var topMargin; + + if (inAspectRatioFixedFullscreenMode) { + if (w*y < x*h) h = (w * y / x) | 0; + else if (w*y > x*h) w = (h * x / y) | 0; + topMargin = ((screenHeight - h) / 2) | 0; + } + + if (inPixelPerfectFullscreenMode) { + setCanvasElementSize(canvas, w, h); + if (canvas.GLctxObject) canvas.GLctxObject.GLctx.viewport(0, 0, w, h); + } + + // Back to CSS pixels. + if (inHiDPIFullscreenMode) { + topMargin /= dpr; + w /= dpr; + h /= dpr; + // Round to nearest 4 digits of precision. + w = Math.round(w*1e4)/1e4; + h = Math.round(h*1e4)/1e4; + topMargin = Math.round(topMargin*1e4)/1e4; + } + + if (inCenteredWithoutScalingFullscreenMode) { + var t = (innerHeight - jstoi_q(canvas.style.height)) / 2; + var b = (innerWidth - jstoi_q(canvas.style.width)) / 2; + setLetterbox(canvas, t, b); + } else { + canvas.style.width = w + 'px'; + canvas.style.height = h + 'px'; + var b = (innerWidth - w) / 2; + setLetterbox(canvas, topMargin, b); + } + + if (!inCenteredWithoutScalingFullscreenMode && currentFullscreenStrategy.canvasResizedCallback) { +#if PTHREADS + if (currentFullscreenStrategy.canvasResizedCallbackTargetThread) __emscripten_run_callback_on_thread(currentFullscreenStrategy.canvasResizedCallbackTargetThread, currentFullscreenStrategy.canvasResizedCallback, {{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, currentFullscreenStrategy.canvasResizedCallbackUserData); + else +#endif + {{{ makeDynCall('iipp', 'currentFullscreenStrategy.canvasResizedCallback') }}}({{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, currentFullscreenStrategy.canvasResizedCallbackUserData); + } + }, + + // https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode + $doRequestFullscreen__deps: ['$JSEvents', '$JSEvents_requestFullscreen', '$findEventTarget'], + $doRequestFullscreen: (target, strategy) => { + if (!JSEvents.fullscreenEnabled()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target ||= '#canvas'; +#endif + target = findEventTarget(target); + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + + if (!target.requestFullscreen +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED + && !target.webkitRequestFullscreen +#endif + ) { + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + } + +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // Queue this function call if we're not currently in an event handler and + // the user saw it appropriate to do so. + if (!JSEvents.canPerformEventHandlerRequests()) { + if (strategy.deferUntilInEventHandler) { + JSEvents.deferCall(JSEvents_requestFullscreen, 1 /* priority over pointer lock */, [target, strategy]); + return {{{ cDefs.EMSCRIPTEN_RESULT_DEFERRED }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED }}}; + } +#endif + + return JSEvents_requestFullscreen(target, strategy); + }, + + emscripten_request_fullscreen__deps: ['$doRequestFullscreen'], + emscripten_request_fullscreen__proxy: 'sync', + emscripten_request_fullscreen: (target, deferUntilInEventHandler) => { + var strategy = { + // These options perform no added logic, but just bare request fullscreen. + scaleMode: {{{ cDefs.EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT }}}, + canvasResolutionScaleMode: {{{ cDefs.EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE }}}, + filteringMode: {{{ cDefs.EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT }}}, +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + deferUntilInEventHandler, +#endif + canvasResizedCallbackTargetThread: {{{ cDefs.EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD }}} + }; + return doRequestFullscreen(target, strategy); + }, + + emscripten_request_fullscreen_strategy__deps: ['$doRequestFullscreen'], + emscripten_request_fullscreen_strategy__proxy: 'sync', + emscripten_request_fullscreen_strategy: (target, deferUntilInEventHandler, fullscreenStrategy) => { + var strategy = { + scaleMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.scaleMode, 'i32') }}}, + canvasResolutionScaleMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResolutionScaleMode, 'i32') }}}, + filteringMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.filteringMode, 'i32') }}}, +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + deferUntilInEventHandler, +#endif +#if PTHREADS + canvasResizedCallbackTargetThread: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResizedCallbackTargetThread, 'i32') }}}, +#endif + canvasResizedCallback: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResizedCallback, 'i32') }}}, + canvasResizedCallbackUserData: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResizedCallbackUserData, 'i32') }}} + }; + + return doRequestFullscreen(target, strategy); + }, + + emscripten_enter_soft_fullscreen__deps: ['$JSEvents', '$hideEverythingExceptGivenElement', '$restoreOldWindowedStyle', '$restoreHiddenElements', '$currentFullscreenStrategy', '$softFullscreenResizeWebGLRenderTarget', '$JSEvents_resizeCanvasForFullscreen', '$findEventTarget'], + emscripten_enter_soft_fullscreen__proxy: 'sync', + emscripten_enter_soft_fullscreen: (target, fullscreenStrategy) => { +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target ||= '#canvas'; +#endif + target = findEventTarget(target); + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + + var strategy = { + scaleMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.scaleMode, 'i32') }}}, + canvasResolutionScaleMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResolutionScaleMode, 'i32') }}}, + filteringMode: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.filteringMode, 'i32') }}}, + canvasResizedCallback: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResizedCallback, 'i32') }}}, + canvasResizedCallbackUserData: {{{ makeGetValue('fullscreenStrategy', C_STRUCTS.EmscriptenFullscreenStrategy.canvasResizedCallbackUserData, 'i32') }}}, +#if PTHREADS + canvasResizedCallbackTargetThread: JSEvents.getTargetThreadForEventCallback(), +#endif + target, + softFullscreen: true + }; + + var restoreOldStyle = JSEvents_resizeCanvasForFullscreen(target, strategy); + + document.documentElement.style.overflow = 'hidden'; // Firefox, Chrome + document.body.scroll = "no"; // IE11 + document.body.style.margin = '0px'; // Override default document margin area on all browsers. + + var hiddenElements = hideEverythingExceptGivenElement(target); + + function restoreWindowedState() { + restoreOldStyle(); + restoreHiddenElements(hiddenElements); + removeEventListener('resize', softFullscreenResizeWebGLRenderTarget); + if (strategy.canvasResizedCallback) { +#if PTHREADS + if (strategy.canvasResizedCallbackTargetThread) __emscripten_run_callback_on_thread(strategy.canvasResizedCallbackTargetThread, strategy.canvasResizedCallback, {{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + else +#endif + {{{ makeDynCall('iipp', 'strategy.canvasResizedCallback') }}}({{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + } + currentFullscreenStrategy = 0; + } + restoreOldWindowedStyle = restoreWindowedState; + currentFullscreenStrategy = strategy; + addEventListener('resize', softFullscreenResizeWebGLRenderTarget); + + // Inform the caller that the canvas size has changed. + if (strategy.canvasResizedCallback) { +#if PTHREADS + if (strategy.canvasResizedCallbackTargetThread) __emscripten_run_callback_on_thread(strategy.canvasResizedCallbackTargetThread, strategy.canvasResizedCallback, {{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + else +#endif + {{{ makeDynCall('iipp', 'strategy.canvasResizedCallback') }}}({{{ cDefs.EMSCRIPTEN_EVENT_CANVASRESIZED }}}, 0, strategy.canvasResizedCallbackUserData); + } + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_exit_soft_fullscreen__deps: ['$restoreOldWindowedStyle'], + emscripten_exit_soft_fullscreen__proxy: 'sync', + emscripten_exit_soft_fullscreen: () => { + restoreOldWindowedStyle?.(); + restoreOldWindowedStyle = null; + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_exit_fullscreen__deps: [ + '$JSEvents', + '$specialHTMLTargets', +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + '$JSEvents_requestFullscreen', +#endif + ], + emscripten_exit_fullscreen__proxy: 'sync', + emscripten_exit_fullscreen: () => { + if (!JSEvents.fullscreenEnabled()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // Make sure no queued up calls will fire after this. + JSEvents.removeDeferredCalls(JSEvents_requestFullscreen); +#endif + + var d = specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}]; + if (d.exitFullscreen) { + d.fullscreenElement && d.exitFullscreen(); +#if MIN_SAFARI_VERSION != TARGET_NOT_SUPPORTED // https://caniuse.com/#feat=mdn-api_document_exitfullscreen + } else if (d.webkitExitFullscreen) { + d.webkitFullscreenElement && d.webkitExitFullscreen(); +#endif + } else { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $fillPointerlockChangeEventData__deps: ['$JSEvents', '$stringToUTF8'], + $fillPointerlockChangeEventData: (eventStruct) => { + var pointerLockElement = document.pointerLockElement; + var isPointerlocked = !!pointerLockElement; + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenPointerlockChangeEvent.isActive, 'isPointerlocked', 'i8') }}}; + var nodeName = JSEvents.getNodeNameForTarget(pointerLockElement); + var id = pointerLockElement?.id || ''; + stringToUTF8(nodeName, eventStruct + {{{ C_STRUCTS.EmscriptenPointerlockChangeEvent.nodeName }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + stringToUTF8(id, eventStruct + {{{ C_STRUCTS.EmscriptenPointerlockChangeEvent.id }}}, {{{ cDefs.EM_HTML5_LONG_STRING_LEN_BYTES }}}); + }, + + $registerPointerlockChangeEventCallback__noleakcheck: true, + $registerPointerlockChangeEventCallback__deps: ['$JSEvents', '$fillPointerlockChangeEventData', 'malloc'], + $registerPointerlockChangeEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenPointerlockChangeEvent.__size__ }}}; + JSEvents.pointerlockChangeEvent ||= _malloc(eventSize); + + var pointerlockChangeEventHandlerFunc = (e) => { + var pointerlockChangeEvent = JSEvents.pointerlockChangeEvent; + fillPointerlockChangeEventData(pointerlockChangeEvent); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, pointerlockChangeEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, pointerlockChangeEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: pointerlockChangeEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_pointerlockchange_callback_on_thread__proxy: 'sync', + emscripten_set_pointerlockchange_callback_on_thread__deps: ['$registerPointerlockChangeEventCallback', '$findEventTarget', +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + '$specialHTMLTargets' +#endif + ], + emscripten_set_pointerlockchange_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + if (!document.body?.requestPointerLock) { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + target = target ? findEventTarget(target) : specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}]; // Pointer lock change events need to be captured from 'document' by default instead of 'window' +#endif + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + return registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_POINTERLOCKCHANGE }}}, "pointerlockchange", targetThread); + }, + + $registerPointerlockErrorEventCallback__deps: ['$JSEvents'], + $registerPointerlockErrorEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + + var pointerlockErrorEventHandlerFunc = (e) => { +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, 0, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, 0, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: pointerlockErrorEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_pointerlockerror_callback_on_thread__proxy: 'sync', + emscripten_set_pointerlockerror_callback_on_thread__deps: ['$registerPointerlockErrorEventCallback', '$findEventTarget', +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + '$specialHTMLTargets' +#endif + ], + emscripten_set_pointerlockerror_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + if (!document.body?.requestPointerLock) { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + target = target ? findEventTarget(target) : specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}]; // Pointer lock change events need to be captured from 'document' by default instead of 'window' +#endif + + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + return registerPointerlockErrorEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_POINTERLOCKERROR }}}, "pointerlockerror", targetThread); + }, + + emscripten_get_pointerlock_status__proxy: 'sync', + emscripten_get_pointerlock_status__deps: ['$fillPointerlockChangeEventData'], + emscripten_get_pointerlock_status: (pointerlockStatus) => { + if (pointerlockStatus) fillPointerlockChangeEventData(pointerlockStatus); + if (!document.body?.requestPointerLock) { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $requestPointerLock: (target) => { + if (target.requestPointerLock) { + target.requestPointerLock(); + } else { + // document.body is known to accept pointer lock, so use that to differentiate if the user passed a bad element, + // or if the whole browser just doesn't support the feature. + if (document.body.requestPointerLock) { + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_request_pointerlock__proxy: 'sync', + emscripten_request_pointerlock__deps: ['$requestPointerLock', '$findEventTarget', +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + '$JSEvents', +#endif + ], + emscripten_request_pointerlock: (target, deferUntilInEventHandler) => { +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target ||= '#canvas'; +#endif + target = findEventTarget(target); + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + if (!target.requestPointerLock) { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // Queue this function call if we're not currently in an event handler and + // the user saw it appropriate to do so. + if (!JSEvents.canPerformEventHandlerRequests()) { + if (deferUntilInEventHandler) { + JSEvents.deferCall(requestPointerLock, 2 /* priority below fullscreen */, [target]); + return {{{ cDefs.EMSCRIPTEN_RESULT_DEFERRED }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED }}}; + } +#endif + + return requestPointerLock(target); + }, + +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + emscripten_exit_pointerlock__deps: ['$JSEvents', '$requestPointerLock'], +#endif + emscripten_exit_pointerlock__proxy: 'sync', + emscripten_exit_pointerlock: () => { +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + // Make sure no queued up calls will fire after this. + JSEvents.removeDeferredCalls(requestPointerLock); +#endif + if (!document.exitPointerLock) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + document.exitPointerLock(); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_vibrate__proxy: 'sync', + emscripten_vibrate: (msecs) => { + if (!navigator.vibrate) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + navigator.vibrate(msecs); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_vibrate_pattern__proxy: 'sync', + emscripten_vibrate_pattern: (msecsArray, numEntries) => { + if (!navigator.vibrate) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + + var vibrateList = []; + for (var i = 0; i < numEntries; ++i) { + var msecs = {{{ makeGetValue('msecsArray', 'i*4', 'i32') }}}; + vibrateList.push(msecs); + } + navigator.vibrate(vibrateList); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $fillVisibilityChangeEventData: (eventStruct) => { + var visibilityStates = [ "hidden", "visible", "prerender", "unloaded" ]; + var visibilityState = visibilityStates.indexOf(document.visibilityState); + + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenVisibilityChangeEvent.hidden, 'document.hidden', 'i8') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenVisibilityChangeEvent.visibilityState, 'visibilityState', 'i32') }}}; + }, + + $registerVisibilityChangeEventCallback__noleakcheck: true, + $registerVisibilityChangeEventCallback__deps: ['$JSEvents', '$fillVisibilityChangeEventData', 'malloc'], + $registerVisibilityChangeEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenVisibilityChangeEvent.__size__ }}}; + JSEvents.visibilityChangeEvent ||= _malloc(eventSize); + + var visibilityChangeEventHandlerFunc = (e) => { + var visibilityChangeEvent = JSEvents.visibilityChangeEvent; + fillVisibilityChangeEventData(visibilityChangeEvent); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, visibilityChangeEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, visibilityChangeEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: visibilityChangeEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_visibilitychange_callback_on_thread__proxy: 'sync', + emscripten_set_visibilitychange_callback_on_thread__deps: ['$registerVisibilityChangeEventCallback', '$specialHTMLTargets'], + emscripten_set_visibilitychange_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => { +#if ENVIRONMENT_MAY_BE_WORKER || ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL + if (!specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}]) { + return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + } +#endif + return registerVisibilityChangeEventCallback(specialHTMLTargets[{{{ cDefs.EMSCRIPTEN_EVENT_TARGET_DOCUMENT }}}], userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_VISIBILITYCHANGE }}}, "visibilitychange", targetThread); + }, + + emscripten_get_visibility_status__proxy: 'sync', + emscripten_get_visibility_status__deps: ['$fillVisibilityChangeEventData'], + emscripten_get_visibility_status: (visibilityStatus) => { + if (typeof document.visibilityState == 'undefined' && typeof document.hidden == 'undefined') { + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } + fillVisibilityChangeEventData(visibilityStatus); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $registerTouchEventCallback__noleakcheck: true, + $registerTouchEventCallback__deps: ['$JSEvents', '$findEventTarget', '$getBoundingClientRect', 'malloc'], + $registerTouchEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenTouchEvent.__size__ }}}; + JSEvents.touchEvent ||= _malloc(eventSize); + + target = findEventTarget(target); + + var touchEventHandlerFunc = (e) => { +#if ASSERTIONS + assert(e); +#endif + var t, touches = {}, et = e.touches; + // To ease marshalling different kinds of touches that browser reports (all touches are listed in e.touches, + // only changed touches in e.changedTouches, and touches on target at a.targetTouches), mark a boolean in + // each Touch object so that we can later loop only once over all touches we see to marshall over to Wasm. + + for (let t of et) { + // Browser might recycle the generated Touch objects between each frame (Firefox on Android), so reset any + // changed/target states we may have set from previous frame. + t.isChanged = t.onTarget = 0; + touches[t.identifier] = t; + } + // Mark which touches are part of the changedTouches list. + for (let t of e.changedTouches) { + t.isChanged = 1; + touches[t.identifier] = t; + } + // Mark which touches are part of the targetTouches list. + for (let t of e.targetTouches) { + touches[t.identifier].onTarget = 1; + } + + var touchEvent = JSEvents.touchEvent; + {{{ makeSetValue('touchEvent', C_STRUCTS.EmscriptenTouchEvent.timestamp, 'e.timeStamp', 'double') }}}; + HEAP8[touchEvent + {{{ C_STRUCTS.EmscriptenTouchEvent.ctrlKey }}}] = e.ctrlKey; + HEAP8[touchEvent + {{{ C_STRUCTS.EmscriptenTouchEvent.shiftKey }}}] = e.shiftKey; + HEAP8[touchEvent + {{{ C_STRUCTS.EmscriptenTouchEvent.altKey }}}] = e.altKey; + HEAP8[touchEvent + {{{ C_STRUCTS.EmscriptenTouchEvent.metaKey }}}] = e.metaKey; + var idx = touchEvent + {{{ C_STRUCTS.EmscriptenTouchEvent.touches }}}; +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + var canvasRect = Module['canvas'] ? getBoundingClientRect(Module['canvas']) : undefined; +#endif + var targetRect = getBoundingClientRect(target); + var numTouches = 0; + for (let t of Object.values(touches)) { + var idx32 = {{{ getHeapOffset('idx', 'i32') }}}; // Pre-shift the ptr to index to HEAP32 to save code size + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.identifier / 4 }}}] = t.identifier; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.screenX / 4 }}}] = t.screenX; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.screenY / 4 }}}] = t.screenY; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.clientX / 4 }}}] = t.clientX; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.clientY / 4 }}}] = t.clientY; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.pageX / 4 }}}] = t.pageX; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.pageY / 4 }}}] = t.pageY; + HEAP8[idx + {{{ C_STRUCTS.EmscriptenTouchPoint.isChanged }}}] = t.isChanged; + HEAP8[idx + {{{ C_STRUCTS.EmscriptenTouchPoint.onTarget }}}] = t.onTarget; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.targetX / 4 }}}] = t.clientX - (targetRect.left | 0); + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.targetY / 4 }}}] = t.clientY - (targetRect.top | 0); +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.canvasX / 4 }}}] = canvasRect ? t.clientX - (canvasRect.left | 0) : 0; + HEAP32[idx32 + {{{ C_STRUCTS.EmscriptenTouchPoint.canvasY / 4 }}}] = canvasRect ? t.clientY - (canvasRect.top | 0) : 0; +#endif + + idx += {{{ C_STRUCTS.EmscriptenTouchPoint.__size__ }}}; + + if (++numTouches > 31) { + break; + } + } + {{{ makeSetValue('touchEvent', C_STRUCTS.EmscriptenTouchEvent.numTouches, 'numTouches', 'i32') }}}; + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, touchEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, touchEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend', +#endif + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: touchEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_touchstart_callback_on_thread__proxy: 'sync', + emscripten_set_touchstart_callback_on_thread__deps: ['$registerTouchEventCallback'], + emscripten_set_touchstart_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerTouchEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_TOUCHSTART }}}, "touchstart", targetThread), + + emscripten_set_touchend_callback_on_thread__proxy: 'sync', + emscripten_set_touchend_callback_on_thread__deps: ['$registerTouchEventCallback'], + emscripten_set_touchend_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerTouchEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_TOUCHEND }}}, "touchend", targetThread), + + emscripten_set_touchmove_callback_on_thread__proxy: 'sync', + emscripten_set_touchmove_callback_on_thread__deps: ['$registerTouchEventCallback'], + emscripten_set_touchmove_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerTouchEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_TOUCHMOVE }}}, "touchmove", targetThread), + + emscripten_set_touchcancel_callback_on_thread__proxy: 'sync', + emscripten_set_touchcancel_callback_on_thread__deps: ['$registerTouchEventCallback'], + emscripten_set_touchcancel_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => + registerTouchEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_TOUCHCANCEL }}}, "touchcancel", targetThread), + + $fillGamepadEventData__deps: ['$stringToUTF8'], + $fillGamepadEventData: (eventStruct, e) => { + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenGamepadEvent.timestamp, 'e.timestamp', 'double') }}}; + for (var i = 0; i < e.axes.length; ++i) { + {{{ makeSetValue('eventStruct+i*8', C_STRUCTS.EmscriptenGamepadEvent.axis, 'e.axes[i]', 'double') }}}; + } + for (var i = 0; i < e.buttons.length; ++i) { + if (typeof e.buttons[i] == 'object') { + {{{ makeSetValue('eventStruct+i*8', C_STRUCTS.EmscriptenGamepadEvent.analogButton, 'e.buttons[i].value', 'double') }}}; + } else { + {{{ makeSetValue('eventStruct+i*8', C_STRUCTS.EmscriptenGamepadEvent.analogButton, 'e.buttons[i]', 'double') }}}; + } + } + for (var i = 0; i < e.buttons.length; ++i) { + if (typeof e.buttons[i] == 'object') { + {{{ makeSetValue('eventStruct+i', C_STRUCTS.EmscriptenGamepadEvent.digitalButton, 'e.buttons[i].pressed', 'i8') }}}; + } else { + // Assigning a boolean to HEAP32, that's ok, but Closure would like to warn about it: + /** @suppress {checkTypes} */ + {{{ makeSetValue('eventStruct+i', C_STRUCTS.EmscriptenGamepadEvent.digitalButton, 'e.buttons[i] == 1', 'i8') }}}; + } + } + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenGamepadEvent.connected, 'e.connected', 'i8') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenGamepadEvent.index, 'e.index', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenGamepadEvent.numAxes, 'e.axes.length', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenGamepadEvent.numButtons, 'e.buttons.length', 'i32') }}}; + stringToUTF8(e.id, eventStruct + {{{ C_STRUCTS.EmscriptenGamepadEvent.id }}}, {{{ cDefs.EM_HTML5_MEDIUM_STRING_LEN_BYTES }}}); + stringToUTF8(e.mapping, eventStruct + {{{ C_STRUCTS.EmscriptenGamepadEvent.mapping }}}, {{{ cDefs.EM_HTML5_MEDIUM_STRING_LEN_BYTES }}}); + }, + + $registerGamepadEventCallback__noleakcheck: true, + $registerGamepadEventCallback__deps: ['$JSEvents', '$fillGamepadEventData', '$findEventTarget', 'malloc'], + $registerGamepadEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenGamepadEvent.__size__ }}}; + JSEvents.gamepadEvent ||= _malloc(eventSize); + + var gamepadEventHandlerFunc = (e) => { + var gamepadEvent = JSEvents.gamepadEvent; + fillGamepadEventData(gamepadEvent, e["gamepad"]); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, gamepadEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, gamepadEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), +#if HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS + allowsDeferredCalls: true, +#endif + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: gamepadEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_gamepadconnected_callback_on_thread__proxy: 'sync', + emscripten_set_gamepadconnected_callback_on_thread__deps: ['$registerGamepadEventCallback', 'emscripten_sample_gamepad_data'], + emscripten_set_gamepadconnected_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => { + if (_emscripten_sample_gamepad_data()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + return registerGamepadEventCallback({{{ cDefs.EMSCRIPTEN_EVENT_TARGET_WINDOW }}}, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_GAMEPADCONNECTED }}}, "gamepadconnected", targetThread); + }, + + emscripten_set_gamepaddisconnected_callback_on_thread__proxy: 'sync', + emscripten_set_gamepaddisconnected_callback_on_thread__deps: ['$registerGamepadEventCallback', 'emscripten_sample_gamepad_data'], + emscripten_set_gamepaddisconnected_callback_on_thread: (userData, useCapture, callbackfunc, targetThread) => { + if (_emscripten_sample_gamepad_data()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + return registerGamepadEventCallback({{{ cDefs.EMSCRIPTEN_EVENT_TARGET_WINDOW }}}, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED }}}, "gamepaddisconnected", targetThread); + }, + + emscripten_sample_gamepad_data__docs: '/** @suppress {checkTypes} */', // We assign null to navigator.getGamepads, which Closure would like to complain about. + emscripten_sample_gamepad_data__proxy: 'sync', + emscripten_sample_gamepad_data__deps: ['$JSEvents'], + emscripten_sample_gamepad_data: () => { + try { + if (navigator.getGamepads) return (JSEvents.lastGamepadState = navigator.getGamepads()) + ? {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}} : {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + } catch(e) { +#if ASSERTIONS + err(`navigator.getGamepads() exists, but failed to execute with exception ${e}. Disabling Gamepad access.`); +#endif + navigator.getGamepads = null; // Disable getGamepads() so that it won't be attempted to be used again. + } + return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + }, + + emscripten_get_num_gamepads__proxy: 'sync', + emscripten_get_num_gamepads__deps: ['$JSEvents'], + emscripten_get_num_gamepads: () => { +#if ASSERTIONS + assert(JSEvents.lastGamepadState, 'emscripten_get_num_gamepads() can only be called after having first called emscripten_sample_gamepad_data() and that function has returned EMSCRIPTEN_RESULT_SUCCESS!'); +#endif + // N.B. Do not call emscripten_get_num_gamepads() unless having first called emscripten_sample_gamepad_data(), and that has returned EMSCRIPTEN_RESULT_SUCCESS. + // Otherwise the following line will throw an exception. + return JSEvents.lastGamepadState.length; + }, + + emscripten_get_gamepad_status__proxy: 'sync', + emscripten_get_gamepad_status__deps: ['$JSEvents', '$fillGamepadEventData'], + emscripten_get_gamepad_status: (index, gamepadState) => { +#if ASSERTIONS + assert(JSEvents.lastGamepadState, 'emscripten_get_gamepad_status() can only be called after having first called emscripten_sample_gamepad_data() and that function has returned EMSCRIPTEN_RESULT_SUCCESS!'); +#endif + // INVALID_PARAM is returned on a Gamepad index that never was there. + if (index < 0 || index >= JSEvents.lastGamepadState.length) return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + + // NO_DATA is returned on a Gamepad index that was removed. + // For previously disconnected gamepads there should be an empty slot (null/undefined/false) at the index. + // This is because gamepads must keep their original position in the array. + // For example, removing the first of two gamepads produces [null/undefined/false, gamepad]. + if (!JSEvents.lastGamepadState[index]) return {{{ cDefs.EMSCRIPTEN_RESULT_NO_DATA }}}; + + fillGamepadEventData(gamepadState, JSEvents.lastGamepadState[index]); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $registerBeforeUnloadEventCallback__deps: ['$JSEvents', '$findEventTarget'], + $registerBeforeUnloadEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString) => { + var beforeUnloadEventHandlerFunc = (e) => { + // Note: This is always called on the main browser thread, since it needs synchronously return a value! + var confirmationMessage = {{{ makeDynCall('pipp', 'callbackfunc') }}}(eventTypeId, 0, userData); + + if (confirmationMessage) { + confirmationMessage = UTF8ToString(confirmationMessage); + } + if (confirmationMessage) { + e.preventDefault(); + e.returnValue = confirmationMessage; + return confirmationMessage; + } + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: beforeUnloadEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_beforeunload_callback_on_thread__proxy: 'sync', + emscripten_set_beforeunload_callback_on_thread__deps: ['$registerBeforeUnloadEventCallback'], + emscripten_set_beforeunload_callback_on_thread: (userData, callbackfunc, targetThread) => { + if (typeof onbeforeunload == 'undefined') return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + // beforeunload callback can only be registered on the main browser thread, because the page will go away immediately after returning from the handler, + // and there is no time to start proxying it anywhere. + if (targetThread !== {{{ cDefs.EM_CALLBACK_THREAD_CONTEXT_MAIN_RUNTIME_THREAD }}}) return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + return registerBeforeUnloadEventCallback({{{ cDefs.EMSCRIPTEN_EVENT_TARGET_WINDOW }}}, userData, true, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_BEFOREUNLOAD }}}, "beforeunload"); + }, + + $fillBatteryEventData: (eventStruct, battery) => { + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenBatteryEvent.chargingTime, 'battery.chargingTime', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenBatteryEvent.dischargingTime, 'battery.dischargingTime', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenBatteryEvent.level, 'battery.level', 'double') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenBatteryEvent.charging, 'battery.charging', 'i8') }}}; + }, + + $hasBatteryAPI__internal: true, + $hasBatteryAPI: () => globalThis.navigator?.getBattery, + + $registerBatteryEventCallback__noleakcheck: true, + $registerBatteryEventCallback__deps: ['$JSEvents', '$fillBatteryEventData', 'malloc'], + $registerBatteryEventCallback: (battery, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + var eventSize = {{{ C_STRUCTS.EmscriptenBatteryEvent.__size__ }}}; + JSEvents.batteryEvent ||= _malloc(eventSize) + + var batteryEventHandlerFunc = (e) => { + var batteryEvent = JSEvents.batteryEvent; + fillBatteryEventData(batteryEvent, battery); + +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, batteryEvent, eventSize, userData); + else +#endif + if ({{{ makeDynCall('iipp', 'callbackfunc') }}}(eventTypeId, batteryEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: battery, + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: batteryEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_batterychargingchange_callback_on_thread__proxy: 'sync', + emscripten_set_batterychargingchange_callback_on_thread__deps: ['$registerBatteryEventCallback', '$hasBatteryAPI'], + emscripten_set_batterychargingchange_callback_on_thread: (userData, callbackfunc, targetThread) => { + if (!hasBatteryAPI()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + navigator.getBattery().then((b) => { + registerBatteryEventCallback(b, userData, true, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE }}}, "chargingchange", targetThread); + }); + }, + + emscripten_set_batterylevelchange_callback_on_thread__proxy: 'sync', + emscripten_set_batterylevelchange_callback_on_thread__deps: ['$registerBatteryEventCallback', '$hasBatteryAPI'], + emscripten_set_batterylevelchange_callback_on_thread: (userData, callbackfunc, targetThread) => { + if (!hasBatteryAPI()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + navigator.getBattery().then((b) => { + registerBatteryEventCallback(b, userData, true, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE }}}, "levelchange", targetThread); + }); + }, + + $batteryManager: undefined, + $batteryManager__internal: true, + + emscripten_get_battery_status__proxy: 'sync', + emscripten_get_battery_status__deps: ['$fillBatteryEventData', '$hasBatteryAPI', '$batteryManager'], + emscripten_get_battery_status: (batteryState) => { + if (!hasBatteryAPI()) return {{{ cDefs.EMSCRIPTEN_RESULT_NOT_SUPPORTED }}}; + if (!batteryManager) { + navigator.getBattery().then((b) => { + batteryManager = b; + }); + return {{{ cDefs.EMSCRIPTEN_RESULT_NO_DATA }}}; + } + fillBatteryEventData(batteryState, batteryManager); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + +#if PTHREADS + $setCanvasElementSizeCallingThread__deps: [ +#if OFFSCREENCANVAS_SUPPORT + '$setOffscreenCanvasSizeOnTargetThread', +#endif + '$findCanvasEventTarget'], + $setCanvasElementSizeCallingThread: (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + +#if OFFSCREENCANVAS_SUPPORT + if (canvas.canvasSharedPtr) { + // N.B. We hold the canvasSharedPtr info structure as the authoritative source for specifying the size of a canvas + // since the actual canvas size changes are asynchronous if the canvas is owned by an OffscreenCanvas on another thread. + // Therefore when setting the size, eagerly set the size of the canvas on the calling thread here, though this thread + // might not be the one that actually ends up specifying the size, but the actual size change may be dispatched + // as an asynchronous event below. + {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'width', 'i32') }}}; + {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'height', 'i32') }}}; + } + + if (canvas.offscreenCanvas || !canvas.controlTransferredOffscreen) { + if (canvas.offscreenCanvas) canvas = canvas.offscreenCanvas; +#else + if (!canvas.controlTransferredOffscreen) { +#endif + var autoResizeViewport = false; + if (canvas.GLctxObject?.GLctx) { + var prevViewport = canvas.GLctxObject.GLctx.getParameter(0xBA2 /* GL_VIEWPORT */); + // TODO: Perhaps autoResizeViewport should only be true if FBO 0 is currently active? + autoResizeViewport = (prevViewport[0] === 0 && prevViewport[1] === 0 && prevViewport[2] === canvas.width && prevViewport[3] === canvas.height); +#if GL_DEBUG + dbg(`Resizing canvas from ${canvas.width}x${canvas.height} to ${width}x${height}. Previous GL viewport size was ${prevViewport}, so autoResizeViewport=${autoResizeViewport}`); +#endif + } + canvas.width = width; + canvas.height = height; + if (autoResizeViewport) { +#if GL_DEBUG + dbg(`Automatically resizing GL viewport to cover whole render target ${width}x${height}`); +#endif + // TODO: Add -sCANVAS_RESIZE_SETS_GL_VIEWPORT=0/1 option (default=1). This is commonly done and several graphics engines depend on this, + // but this can be quite disruptive. + canvas.GLctxObject.GLctx.viewport(0, 0, width, height); + } +#if OFFSCREENCANVAS_SUPPORT + } else if (canvas.canvasSharedPtr) { + var targetThread = {{{ makeGetValue('canvas.canvasSharedPtr', 8, '*') }}}; + setOffscreenCanvasSizeOnTargetThread(targetThread, target, width, height); + return {{{ cDefs.EMSCRIPTEN_RESULT_DEFERRED }}}; // This will have to be done asynchronously +#endif + } else { +#if GL_DEBUG + dbg('canvas.controlTransferredOffscreen but we do not own the canvas, and do not know who has (no canvas.canvasSharedPtr present, an internal bug?)!\n'); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + } +#if OFFSCREEN_FRAMEBUFFER + if (canvas.GLctxObject) GL.resizeOffscreenFramebuffer(canvas.GLctxObject); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + +#if OFFSCREENCANVAS_SUPPORT + $setOffscreenCanvasSizeOnTargetThread__deps: ['$stringToNewUTF8', '_emscripten_set_offscreencanvas_size_on_thread'], + $setOffscreenCanvasSizeOnTargetThread: (targetThread, targetCanvas, width, height) => { + targetCanvas = targetCanvas ? UTF8ToString(targetCanvas) : ''; + var targetCanvasPtr = 0; + if (targetCanvas) { + targetCanvasPtr = stringToNewUTF8(targetCanvas); + } + __emscripten_set_offscreencanvas_size_on_thread(targetThread, targetCanvasPtr, width, height); + }, +#endif + + $setCanvasElementSizeMainThread__proxy: 'sync', + $setCanvasElementSizeMainThread__deps: ['$setCanvasElementSizeCallingThread'], + $setCanvasElementSizeMainThread: (target, width, height) => setCanvasElementSizeCallingThread(target, width, height), + + emscripten_set_canvas_element_size__deps: ['$setCanvasElementSizeCallingThread', '$setCanvasElementSizeMainThread', '$findCanvasEventTarget'], + emscripten_set_canvas_element_size: (target, width, height) => { +#if GL_DEBUG + dbg(`emscripten_set_canvas_element_size(target=${target},width=${width},height=${height}`); +#endif + var canvas = findCanvasEventTarget(target); + if (canvas) { + return setCanvasElementSizeCallingThread(target, width, height); + } + return setCanvasElementSizeMainThread(target, width, height); + }, +#else + emscripten_set_canvas_element_size__deps: ['$findCanvasEventTarget'], + emscripten_set_canvas_element_size: (target, width, height) => { +#if GL_DEBUG + dbg(`emscripten_set_canvas_element_size(target=${target},width=${width},height=${height}`); +#endif + var canvas = findCanvasEventTarget(target); + if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + canvas.width = width; + canvas.height = height; +#if OFFSCREEN_FRAMEBUFFER + if (canvas.GLctxObject) GL.resizeOffscreenFramebuffer(canvas.GLctxObject); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, +#endif + + $setCanvasElementSize__deps: ['emscripten_set_canvas_element_size', '$stackSave', '$stackRestore', '$stringToUTF8OnStack'], + $setCanvasElementSize: (target, width, height) => { +#if GL_DEBUG + dbg(`setCanvasElementSize(target=${target},width=${width},height=${height}`); +#endif + if (!target.controlTransferredOffscreen) { + target.width = width; + target.height = height; + } else { + // This function is being called from high-level JavaScript code instead of asm.js/Wasm, + // and it needs to synchronously proxy over to another thread, so marshal the string onto the heap to do the call. + var sp = stackSave(); + var targetInt = stringToUTF8OnStack(target.id); + _emscripten_set_canvas_element_size(targetInt, width, height); + stackRestore(sp); + } + }, + +#if PTHREADS + $getCanvasSizeCallingThread__deps: ['$findCanvasEventTarget'], + $getCanvasSizeCallingThread: (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + +#if OFFSCREENCANVAS_SUPPORT + if (canvas.canvasSharedPtr) { + // N.B. Reading the size of the Canvas takes priority from our shared state structure, which is not the actual size. + // However if is possible that there is a canvas size set event pending on an OffscreenCanvas owned by another thread, + // so that the real sizes of the canvas have not updated yet. Therefore reading the real values would be racy. + var w = {{{ makeGetValue('canvas.canvasSharedPtr', 0, 'i32') }}}; + var h = {{{ makeGetValue('canvas.canvasSharedPtr', 4, 'i32') }}}; + {{{ makeSetValue('width', 0, 'w', 'i32') }}}; + {{{ makeSetValue('height', 0, 'h', 'i32') }}}; + } else if (canvas.offscreenCanvas) { + {{{ makeSetValue('width', 0, 'canvas.offscreenCanvas.width', 'i32') }}}; + {{{ makeSetValue('height', 0, 'canvas.offscreenCanvas.height', 'i32') }}}; + } else +#endif + if (!canvas.controlTransferredOffscreen) { + {{{ makeSetValue('width', 0, 'canvas.width', 'i32') }}}; + {{{ makeSetValue('height', 0, 'canvas.height', 'i32') }}}; + } else { +#if GL_DEBUG + dbg('canvas.controlTransferredOffscreen but we do not own the canvas, and do not know who has (no canvas.canvasSharedPtr present, an internal bug?)!\n'); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + $getCanvasSizeMainThread__proxy: 'sync', + $getCanvasSizeMainThread__deps: ['$getCanvasSizeCallingThread'], + $getCanvasSizeMainThread: (target, width, height) => getCanvasSizeCallingThread(target, width, height), + + emscripten_get_canvas_element_size__deps: ['$getCanvasSizeCallingThread', '$getCanvasSizeMainThread', '$findCanvasEventTarget'], + emscripten_get_canvas_element_size: (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (canvas) { + return getCanvasSizeCallingThread(target, width, height); + } + return getCanvasSizeMainThread(target, width, height); + }, +#else + emscripten_get_canvas_element_size__deps: ['$findCanvasEventTarget'], + emscripten_get_canvas_element_size: (target, width, height) => { + var canvas = findCanvasEventTarget(target); + if (!canvas) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + {{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}}; + }, +#endif + + // JavaScript-friendly API, returns pair [width, height] + $getCanvasElementSize__deps: ['emscripten_get_canvas_element_size', '$stackSave', '$stackRestore', '$stringToUTF8OnStack'], + $getCanvasElementSize: (target) => { + var sp = stackSave(); + var w = stackAlloc(8); + var h = w + 4; + + var targetInt = stringToUTF8OnStack(target.id); + var ret = _emscripten_get_canvas_element_size(targetInt, w, h); + var size = [{{{ makeGetValue('w', 0, 'i32')}}}, {{{ makeGetValue('h', 0, 'i32')}}}]; + stackRestore(sp); + return size; + }, + + emscripten_set_element_css_size__proxy: 'sync', + emscripten_set_element_css_size__deps: ['$findEventTarget'], + emscripten_set_element_css_size: (target, width, height) => { +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + target = target ? findEventTarget(target) : Module['canvas']; +#endif + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + + target.style.width = width + "px"; + target.style.height = height + "px"; + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_get_element_css_size__proxy: 'sync', + emscripten_get_element_css_size__deps: ['$findEventTarget', '$getBoundingClientRect'], + emscripten_get_element_css_size: (target, width, height) => { +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target = findEventTarget(target); +#else + target = target ? findEventTarget(target) : Module['canvas']; +#endif + if (!target) return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + + var rect = getBoundingClientRect(target); + {{{ makeSetValue('width', '0', 'rect.width', 'double') }}}; + {{{ makeSetValue('height', '0', 'rect.height', 'double') }}}; + + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_html5_remove_all_event_listeners__deps: ['$JSEvents'], + emscripten_html5_remove_all_event_listeners: () => JSEvents.removeAllEventListeners(), + + emscripten_request_animation_frame: (cb, userData) => + requestAnimationFrame((timeStamp) => {{{ makeDynCall('idp', 'cb') }}}(timeStamp, userData)), + + emscripten_cancel_animation_frame: (id) => cancelAnimationFrame(id), + + emscripten_request_animation_frame_loop: (cb, userData) => { + function tick(timeStamp) { + if ({{{ makeDynCall('idp', 'cb') }}}(timeStamp, userData)) { + requestAnimationFrame(tick); + } + } + return requestAnimationFrame(tick); + }, + + emscripten_get_device_pixel_ratio__proxy: 'sync', + emscripten_get_device_pixel_ratio: () => { +#if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL + return globalThis.devicePixelRatio ?? 1.0; +#else // otherwise, on the web and in workers, things are simpler + return devicePixelRatio; +#endif + } +}; + +addToLibrary(LibraryHTML5); diff --git a/src/lib/libhtml5_webgl.js b/src/lib/libhtml5_webgl.js new file mode 100644 index 0000000000000..8db08e6023078 --- /dev/null +++ b/src/lib/libhtml5_webgl.js @@ -0,0 +1,635 @@ +/** + * @license + * Copyright 2014 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var LibraryHtml5WebGL = { + // Writes a JS typed array containing 32-bit floats or ints to memory + $writeGLArray: (arr, dst, dstLength, heapType) => { +#if ASSERTIONS + assert(arr); + assert(typeof arr.length != 'undefined'); +#endif + var len = arr.length; + var writeLength = dstLength < len ? dstLength : len; + var heap = heapType ? HEAPF32 : HEAP32; + // Works because HEAPF32 and HEAP32 have the same bytes-per-element + dst = {{{ getHeapOffset('dst', 'float') }}}; + for (var i = 0; i < writeLength; ++i) { + heap[dst + i] = arr[i]; + } + return len; + }, + + $webglPowerPreferences__internal: true, + $webglPowerPreferences: ['default', 'low-power', 'high-performance'], + +#if PTHREADS && OFFSCREEN_FRAMEBUFFER + // In offscreen framebuffer mode, we implement a proxied version of the + // emscripten_webgl_create_context() function in JS. + emscripten_webgl_create_context_proxied__proxy: 'sync', + emscripten_webgl_create_context_proxied__deps: ['emscripten_webgl_do_create_context'], + emscripten_webgl_create_context_proxied: (target, attributes) => + _emscripten_webgl_do_create_context(target, attributes), + + // The other proxied GL commands are defined in C (guarded by the + // __EMSCRIPTEN_OFFSCREEN_FRAMEBUFFER__ definition). +#else + // When not in offscreen framebuffer mode, these functions are implemented + // in JS and forwarded without any proxying. + emscripten_webgl_create_context: 'emscripten_webgl_do_create_context', + + emscripten_webgl_get_current_context: 'emscripten_webgl_do_get_current_context', + + emscripten_webgl_commit_frame: 'emscripten_webgl_do_commit_frame', +#endif + +#if OFFSCREENCANVAS_SUPPORT + emscripten_webgl_do_create_context__postset: ` + registerPreMainLoop(() => { + // If the current GL context is an OffscreenCanvas, but it was initialized + // with implicit swap mode, perform the swap on behalf of the user. + if (GL.currentContext && !GL.currentContextIsProxied && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) { + GL.currentContext.GLctx.commit(); + } + });`, +#endif + + emscripten_webgl_do_create_context__deps: [ +#if OFFSCREENCANVAS_SUPPORT + '$registerPreMainLoop', + 'malloc', + 'emscripten_supports_offscreencanvas', +#endif +#if PTHREADS && OFFSCREEN_FRAMEBUFFER + 'emscripten_webgl_create_context_proxied', +#endif + '$webglPowerPreferences', '$findCanvasEventTarget'], + // This function performs proxying manually, depending on the style of context that is to be created. + emscripten_webgl_do_create_context: (target, attributes) => { +#if ASSERTIONS + assert(attributes); +#endif + var attr32 = {{{ getHeapOffset('attributes', 'i32') }}}; + var powerPreference = HEAP32[attr32 + ({{{ C_STRUCTS.EmscriptenWebGLContextAttributes.powerPreference }}}>>2)]; + var contextAttributes = { + 'alpha': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.alpha }}}], + 'depth': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.depth }}}], + 'stencil': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.stencil }}}], + 'antialias': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.antialias }}}], + 'premultipliedAlpha': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.premultipliedAlpha }}}], + 'preserveDrawingBuffer': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.preserveDrawingBuffer }}}], + 'powerPreference': webglPowerPreferences[powerPreference], + 'failIfMajorPerformanceCaveat': !!HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.failIfMajorPerformanceCaveat }}}], + // The following are not predefined WebGL context attributes in the WebGL specification, so the property names can be minified by Closure. + majorVersion: HEAP32[attr32 + ({{{ C_STRUCTS.EmscriptenWebGLContextAttributes.majorVersion }}}>>2)], + minorVersion: HEAP32[attr32 + ({{{ C_STRUCTS.EmscriptenWebGLContextAttributes.minorVersion }}}>>2)], + enableExtensionsByDefault: HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.enableExtensionsByDefault }}}], + explicitSwapControl: HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.explicitSwapControl }}}], + proxyContextToMainThread: HEAP32[attr32 + ({{{ C_STRUCTS.EmscriptenWebGLContextAttributes.proxyContextToMainThread }}}>>2)], + renderViaOffscreenBackBuffer: HEAP8[attributes + {{{ C_STRUCTS.EmscriptenWebGLContextAttributes.renderViaOffscreenBackBuffer }}}] + }; + + +#if ASSERTIONS + // TODO: Make these into hard errors at some point in the future + if (contextAttributes.majorVersion !== 1 && contextAttributes.majorVersion !== 2) { + err(`Invalid WebGL version requested: ${contextAttributes.majorVersion}`); + } +#if MIN_WEBGL_VERSION >= 2 + if (contextAttributes.majorVersion !== 2) { + err('WebGL 1 requested but only WebGL 2 is supported (MIN_WEBGL_VERSION is 2)'); + } +#elif MAX_WEBGL_VERSION == 1 + if (contextAttributes.majorVersion !== 1) { + err('WebGL 2 requested but only WebGL 1 is supported (set -sMAX_WEBGL_VERSION=2 to fix the problem)'); + } +#endif +#endif + + var canvas = findCanvasEventTarget(target); +#if OFFSCREENCANVAS_SUPPORT + // If our canvas from findCanvasEventTarget is actually an offscreen canvas record, we should extract the inner canvas. + if (canvas?.canvas) { canvas = canvas.canvas; } +#endif +#if GL_DEBUG + var targetStr = UTF8ToString(target); +#endif + +#if PTHREADS && OFFSCREEN_FRAMEBUFFER + // Create a WebGL context that is proxied to main thread if canvas was not found on worker, or if explicitly requested to do so. + if (ENVIRONMENT_IS_PTHREAD) { + if (contextAttributes.proxyContextToMainThread === {{{ cDefs.EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS }}} || + (!canvas && contextAttributes.proxyContextToMainThread === {{{ cDefs.EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK }}})) { + // When WebGL context is being proxied via the main thread, we must render using an offscreen FBO render target to avoid WebGL's + // "implicit swap when callback exits" behavior. TODO: If OffscreenCanvas is supported, explicitSwapControl=true and still proxying, + // then this can be avoided, since OffscreenCanvas enables explicit swap control. +#if GL_DEBUG + if (contextAttributes.proxyContextToMainThread === {{{ cDefs.EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS }}}) dbg('EMSCRIPTEN_WEBGL_CONTEXT_PROXY_ALWAYS enabled, proxying WebGL rendering from pthread to main thread.'); + if (!canvas && contextAttributes.proxyContextToMainThread === {{{ cDefs.EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK }}}) dbg(`Specified canvas target "${targetStr}" is not an OffscreenCanvas in the current pthread, but EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK is set. Proxying WebGL rendering from pthread to main thread.`); + dbg('Performance warning: forcing renderViaOffscreenBackBuffer=true and preserveDrawingBuffer=true since proxying WebGL rendering.'); +#endif + // We will be proxying - if OffscreenCanvas is supported, we can proxy a bit more efficiently by avoiding having to create an Offscreen FBO. + if (!_emscripten_supports_offscreencanvas()) { + {{{ makeSetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.renderViaOffscreenBackBuffer, '1', 'i8') }}}; + {{{ makeSetValue('attributes', C_STRUCTS.EmscriptenWebGLContextAttributes.preserveDrawingBuffer, '1', 'i8') }}}; + } + return _emscripten_webgl_create_context_proxied(target, attributes); + } + } +#endif + + if (!canvas) { +#if GL_DEBUG + dbg(`emscripten_webgl_create_context failed: Unknown canvas target "${targetStr}"!`); +#endif + return 0; + } + +#if OFFSCREENCANVAS_SUPPORT + if (canvas.offscreenCanvas) canvas = canvas.offscreenCanvas; + +#if GL_DEBUG + if (_emscripten_supports_offscreencanvas() && canvas instanceof OffscreenCanvas) dbg(`emscripten_webgl_create_context: Creating an OffscreenCanvas-based WebGL context on target "${targetStr}"`); + else if (typeof HTMLCanvasElement != 'undefined' && canvas instanceof HTMLCanvasElement) dbg(`emscripten_webgl_create_context: Creating an HTMLCanvasElement-based WebGL context on target "${targetStr}"`); +#endif + + if (contextAttributes.explicitSwapControl) { + var supportsOffscreenCanvas = canvas.transferControlToOffscreen || (_emscripten_supports_offscreencanvas() && canvas instanceof OffscreenCanvas); + + if (!supportsOffscreenCanvas) { +#if OFFSCREEN_FRAMEBUFFER + if (!contextAttributes.renderViaOffscreenBackBuffer) { + contextAttributes.renderViaOffscreenBackBuffer = true; +#if GL_DEBUG + dbg('emscripten_webgl_create_context: Performance warning, OffscreenCanvas is not supported but explicitSwapControl was requested, so force-enabling renderViaOffscreenBackBuffer=true to allow explicit swapping!'); +#endif + } +#else +#if GL_DEBUG + dbg('emscripten_webgl_create_context failed: OffscreenCanvas is not supported but explicitSwapControl was requested!'); +#endif + return 0; +#endif + } + + if (canvas.transferControlToOffscreen) { +#if GL_DEBUG + dbg(`explicitSwapControl requested: canvas.transferControlToOffscreen() on canvas "${targetStr}" to get .commit() function and not rely on implicit WebGL swap`); +#endif + if (!canvas.controlTransferredOffscreen) { + GL.offscreenCanvases[canvas.id] = { + canvas: canvas.transferControlToOffscreen(), + canvasSharedPtr: _malloc(12), + id: canvas.id + }; + canvas.controlTransferredOffscreen = true; + } else if (!GL.offscreenCanvases[canvas.id]) { +#if GL_DEBUG + dbg(`OffscreenCanvas is supported, and canvas "${canvas.id}" has already before been transferred offscreen, but there is no known OffscreenCanvas with that name!`); +#endif + return 0; + } + canvas = GL.offscreenCanvases[canvas.id].canvas; + } + } +#else // !OFFSCREENCANVAS_SUPPORT +#if OFFSCREEN_FRAMEBUFFER + if (contextAttributes.explicitSwapControl && !contextAttributes.renderViaOffscreenBackBuffer) { + contextAttributes.renderViaOffscreenBackBuffer = true; +#if GL_DEBUG + dbg('emscripten_webgl_create_context: Performance warning, not building with OffscreenCanvas support enabled but explicitSwapControl was requested, so force-enabling renderViaOffscreenBackBuffer=true to allow explicit swapping!'); +#endif + } +#else + if (contextAttributes.explicitSwapControl) { +#if GL_DEBUG + dbg('emscripten_webgl_create_context failed: explicitSwapControl is not supported, please rebuild with -sOFFSCREENCANVAS_SUPPORT to enable targeting the experimental OffscreenCanvas specification, or rebuild with -sOFFSCREEN_FRAMEBUFFER to emulate explicitSwapControl in the absence of OffscreenCanvas support!'); +#endif + return 0; + } +#endif // ~!OFFSCREEN_FRAMEBUFFER + +#endif // ~!OFFSCREENCANVAS_SUPPORT + + var contextHandle = GL.createContext(canvas, contextAttributes); + return contextHandle; + }, + +#if PTHREADS && OFFSCREEN_FRAMEBUFFER + // Runs on the calling thread, proxies if needed. + emscripten_webgl_make_context_current_calling_thread__sig: 'ip', + emscripten_webgl_make_context_current_calling_thread: (contextHandle) => { + var success = GL.makeContextCurrent(contextHandle); + if (success) GL.currentContextIsProxied = false; // If succeeded above, we will have a local GL context from this thread (worker or main). + return success ? {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}} : {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + }, + // This function gets called in a pthread, after it has successfully activated (with make_current()) a proxied GL context to itself from the main thread. + // In this scenario, the pthread does not hold a high-level JS object to the GL context, because it lives on the main thread, in which case we record + // an integer pointer as a token value to represent the GL context activation from another thread. (when this function is called, the main browser thread + // has already accepted the GL context activation for our pthread, so that side is good) +#if GL_SUPPORT_EXPLICIT_SWAP_CONTROL + _emscripten_proxied_gl_context_activated_from_main_browser_thread__deps: ['$registerPreMainLoop'], + _emscripten_proxied_gl_context_activated_from_main_browser_thread__postjs: ` + // If the current GL context is a proxied regular WebGL context, and was + // initialized with implicit swap mode on the main thread, and we are on the + // parent thread, perform the swap on behalf of the user. + registerPreMainLoop(() => { + if (GL.currentContext && GL.currentContextIsProxied) { + var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}}; + if (!explicitSwapControl) _emscripten_webgl_commit_frame(); + } + });`, +#endif + _emscripten_proxied_gl_context_activated_from_main_browser_thread: (contextHandle) => { + GLctx = Module['ctx'] = GL.currentContext = contextHandle; + GL.currentContextIsProxied = true; + }, +#else + emscripten_webgl_make_context_current: (contextHandle) => { + var success = GL.makeContextCurrent(contextHandle); + return success ? {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}} : {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + }, +#endif + + emscripten_webgl_do_get_current_context: () => GL.currentContext ? GL.currentContext.handle : 0, + + emscripten_webgl_get_drawing_buffer_size__proxy: 'sync_on_webgl_context_handle_thread', + emscripten_webgl_get_drawing_buffer_size: (contextHandle, width, height) => { + var GLContext = GL.getContext(contextHandle); + + if (!GLContext || !GLContext.GLctx || !width || !height) { + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + } + {{{ makeSetValue('width', '0', 'GLContext.GLctx.drawingBufferWidth', 'i32') }}}; + {{{ makeSetValue('height', '0', 'GLContext.GLctx.drawingBufferHeight', 'i32') }}}; + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_webgl_do_commit_frame: () => { +#if TRACE_WEBGL_CALLS + var threadId = (typeof _pthread_self != 'undefined') ? _pthread_self : () => 1; + err(`[Thread ${threadId()}, GL ctx: ${GL.currentContext.handle}]: emscripten_webgl_do_commit_frame()`); +#endif + if (!GL.currentContext || !GL.currentContext.GLctx) { +#if GL_DEBUG + dbg('emscripten_webgl_commit_frame() failed: no GL context set current via emscripten_webgl_make_context_current()!'); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + } + +#if OFFSCREEN_FRAMEBUFFER + if (GL.currentContext.defaultFbo) { + GL.blitOffscreenFramebuffer(GL.currentContext); +#if GL_DEBUG && OFFSCREENCANVAS_SUPPORT + if (GL.currentContext.GLctx.commit) dbg('emscripten_webgl_commit_frame(): Offscreen framebuffer should never have gotten created when canvas is in OffscreenCanvas mode, since it is redundant and not necessary'); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + } +#endif + if (!GL.currentContext.attributes.explicitSwapControl) { +#if GL_DEBUG + dbg('emscripten_webgl_commit_frame() cannot be called for canvases with implicit swap control mode!'); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + } + // We would do GL.currentContext.GLctx.commit(); here, but the current implementation + // in browsers has removed it - swap is implicit, so this function is a no-op for now + // (until/unless the spec changes). + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_webgl_get_context_attributes__proxy: 'sync_on_webgl_context_handle_thread', + emscripten_webgl_get_context_attributes__deps: ['$webglPowerPreferences'], + emscripten_webgl_get_context_attributes: (c, a) => { + if (!a) return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_PARAM }}}; + c = GL.contexts[c]; + if (!c) return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + var t = c.GLctx?.getContextAttributes(); + if (!t) return {{{ cDefs.EMSCRIPTEN_RESULT_INVALID_TARGET }}}; + + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.alpha, 't.alpha', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.depth, 't.depth', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.stencil, 't.stencil', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.antialias, 't.antialias', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.premultipliedAlpha, 't.premultipliedAlpha', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.preserveDrawingBuffer, 't.preserveDrawingBuffer', 'i8') }}}; + var power = t['powerPreference'] && webglPowerPreferences.indexOf(t['powerPreference']); + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.powerPreference, 'power', 'i32') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.failIfMajorPerformanceCaveat, 't.failIfMajorPerformanceCaveat', 'i8') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.majorVersion, 'c.version', 'i32') }}}; + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.minorVersion, 0, 'i32') }}}; +#if GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.enableExtensionsByDefault, 'c.attributes.enableExtensionsByDefault', 'i8') }}}; +#endif +#if GL_SUPPORT_EXPLICIT_SWAP_CONTROL + {{{ makeSetValue('a', C_STRUCTS.EmscriptenWebGLContextAttributes.explicitSwapControl, 'c.attributes.explicitSwapControl', 'i8') }}}; +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_webgl_destroy_context__proxy: 'sync_on_webgl_context_handle_thread', + emscripten_webgl_destroy_context: (contextHandle) => { + if (GL.currentContext == contextHandle) GL.currentContext = 0; + GL.deleteContext(contextHandle); + }, + +#if PTHREADS + // Special function that will be invoked on the thread calling emscripten_webgl_destroy_context(), before routing + // the call over to the target thread. + $emscripten_webgl_destroy_context_before_on_calling_thread__deps: ['emscripten_webgl_get_current_context', 'emscripten_webgl_make_context_current'], + $emscripten_webgl_destroy_context_before_on_calling_thread: (contextHandle) => { + if (_emscripten_webgl_get_current_context() == contextHandle) _emscripten_webgl_make_context_current(0); + }, +#endif + + emscripten_webgl_enable_extension__deps: [ +#if GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS +#if MIN_WEBGL_VERSION == 1 + '$webgl_enable_ANGLE_instanced_arrays', + '$webgl_enable_OES_vertex_array_object', + '$webgl_enable_WEBGL_draw_buffers', +#endif +#if MAX_WEBGL_VERSION >= 2 + '$webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance', + '$webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance', +#endif + '$webgl_enable_EXT_polygon_offset_clamp', + '$webgl_enable_EXT_clip_control', + '$webgl_enable_WEBGL_polygon_mode', + '$webgl_enable_WEBGL_multi_draw', +#endif + ], + emscripten_webgl_enable_extension__proxy: 'sync_on_webgl_context_handle_thread', + emscripten_webgl_enable_extension: (contextHandle, extension) => { + var context = GL.getContext(contextHandle); + var extString = UTF8ToString(extension); +#if GL_EXTENSIONS_IN_PREFIXED_FORMAT + if (extString.startsWith('GL_')) extString = extString.slice(3); // Allow enabling extensions both with "GL_" prefix and without. +#endif + +#if GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS + // Switch-board that pulls in code for all GL extensions, even if those are not used :/ + // Build with -sGL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=0 to avoid this. + +#if MIN_WEBGL_VERSION == 1 + // Obtain function entry points to WebGL 1 extension related functions. + if (extString == 'ANGLE_instanced_arrays') webgl_enable_ANGLE_instanced_arrays(GLctx); + if (extString == 'OES_vertex_array_object') webgl_enable_OES_vertex_array_object(GLctx); + if (extString == 'WEBGL_draw_buffers') webgl_enable_WEBGL_draw_buffers(GLctx); +#endif + +#if MAX_WEBGL_VERSION >= 2 + if (extString == 'WEBGL_draw_instanced_base_vertex_base_instance') webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx); + if (extString == 'WEBGL_multi_draw_instanced_base_vertex_base_instance') webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx); +#endif + + if (extString == 'WEBGL_multi_draw') webgl_enable_WEBGL_multi_draw(GLctx); + if (extString == 'EXT_polygon_offset_clamp') webgl_enable_EXT_polygon_offset_clamp(GLctx); + if (extString == 'EXT_clip_control') webgl_enable_EXT_clip_control(GLctx); + if (extString == 'WEBGL_polygon_mode') webgl_enable_WEBGL_polygon_mode(GLctx); + +#elif ASSERTIONS || GL_ASSERTIONS + if (['ANGLE_instanced_arrays', + 'OES_vertex_array_object', + 'WEBGL_draw_buffers', + 'WEBGL_multi_draw', + 'EXT_polygon_offset_clamp', + 'EXT_clip_control', + 'WEBGL_polygon_mode', + 'WEBGL_draw_instanced_base_vertex_base_instance', + 'WEBGL_multi_draw_instanced_base_vertex_base_instance'].includes(extString)) { + err('When building with -sGL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=0, function emscripten_webgl_enable_extension() cannot be used to enable extension ' + + extString + '! Use one of the functions emscripten_webgl_enable_*() to enable it!'); + } +#endif + + var ext = context.GLctx.getExtension(extString); + return !!ext; + }, + + emscripten_supports_offscreencanvas: () => + // TODO: Add a new build mode, e.g. OFFSCREENCANVAS_SUPPORT=2, which + // necessitates OffscreenCanvas support at build time, and "return 1;" here in that build mode. +#if OFFSCREENCANVAS_SUPPORT + typeof OffscreenCanvas != 'undefined' +#else + 0 +#endif + , + + $registerWebGlEventCallback__deps: ['$JSEvents', '$findEventTarget'], + $registerWebGlEventCallback: (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { +#if PTHREADS + targetThread = JSEvents.getTargetThreadForEventCallback(targetThread); +#endif + +#if !DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR + target ||= Module['canvas']; +#endif + + var webGlEventHandlerFunc = (e) => { +#if PTHREADS + if (targetThread) __emscripten_run_callback_on_thread(targetThread, callbackfunc, eventTypeId, 0, userData); + else +#endif + if ({{{ makeDynCall('iiii', 'callbackfunc') }}}(eventTypeId, 0, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: webGlEventHandlerFunc, + useCapture + }; + JSEvents.registerOrRemoveHandler(eventHandler); + }, + + emscripten_set_webglcontextlost_callback_on_thread__proxy: 'sync', + emscripten_set_webglcontextlost_callback_on_thread__deps: ['$registerWebGlEventCallback'], + emscripten_set_webglcontextlost_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST }}}, "webglcontextlost", targetThread); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_set_webglcontextrestored_callback_on_thread__proxy: 'sync', + emscripten_set_webglcontextrestored_callback_on_thread__deps: ['$registerWebGlEventCallback'], + emscripten_set_webglcontextrestored_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => { + registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefs.EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED }}}, "webglcontextrestored", targetThread); + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + + emscripten_is_webgl_context_lost__proxy: 'sync_on_webgl_context_handle_thread', + emscripten_is_webgl_context_lost: (contextHandle) => + !GL.contexts[contextHandle] || GL.contexts[contextHandle].GLctx.isContextLost(), // No context ~> lost context. + + emscripten_webgl_get_supported_extensions__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_supported_extensions__deps: ['$stringToNewUTF8'], + // Here we report the full list of extensions supported by WebGL rather than + // using getEmscriptenSupportedExtensions which filters the list based on + // what is has explicit support in. + emscripten_webgl_get_supported_extensions: () => + stringToNewUTF8(GLctx.getSupportedExtensions().join(' ')), + + emscripten_webgl_get_program_parameter_d__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_program_parameter_d: (program, param) => + GLctx.getProgramParameter(GL.programs[program], param), + + emscripten_webgl_get_program_info_log_utf8__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_program_info_log_utf8__deps: ['$stringToNewUTF8'], + emscripten_webgl_get_program_info_log_utf8: (program) => + stringToNewUTF8(GLctx.getProgramInfoLog(GL.programs[program])), + + emscripten_webgl_get_shader_parameter_d__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_shader_parameter_d: (shader, param) => + GLctx.getShaderParameter(GL.shaders[shader], param), + + emscripten_webgl_get_shader_info_log_utf8__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_shader_info_log_utf8__deps: ['$stringToNewUTF8'], + emscripten_webgl_get_shader_info_log_utf8: (shader) => + stringToNewUTF8(GLctx.getShaderInfoLog(GL.shaders[shader])), + + emscripten_webgl_get_shader_source_utf8__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_shader_source_utf8__deps: ['$stringToNewUTF8'], + emscripten_webgl_get_shader_source_utf8: (shader) => + stringToNewUTF8(GLctx.getShaderSource(GL.shaders[shader])), + + emscripten_webgl_get_vertex_attrib_d__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_vertex_attrib_d: (index, param) => + GLctx.getVertexAttrib(index, param), + + emscripten_webgl_get_vertex_attrib_o__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_vertex_attrib_o: (index, param) => { + var obj = GLctx.getVertexAttrib(index, param); + return obj?.name; + }, + + emscripten_webgl_get_vertex_attrib_v__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_vertex_attrib_v__deps: ['$writeGLArray'], + emscripten_webgl_get_vertex_attrib_v: (index, param, dst, dstLength, dstType) => + writeGLArray(GLctx.getVertexAttrib(index, param), dst, dstLength, dstType), + + emscripten_webgl_get_uniform_d__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_uniform_d__deps: ['$webglGetUniformLocation'], + emscripten_webgl_get_uniform_d: (program, location) => + GLctx.getUniform(GL.programs[program], webglGetUniformLocation(location)), + + emscripten_webgl_get_uniform_v__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_uniform_v__deps: ['$writeGLArray', '$webglGetUniformLocation'], + emscripten_webgl_get_uniform_v: (program, location, dst, dstLength, dstType) => + writeGLArray(GLctx.getUniform(GL.programs[program], webglGetUniformLocation(location)), dst, dstLength, dstType), + + emscripten_webgl_get_parameter_v__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_parameter_v__deps: ['$writeGLArray'], + emscripten_webgl_get_parameter_v: (param, dst, dstLength, dstType) => + writeGLArray(GLctx.getParameter(param), dst, dstLength, dstType), + + emscripten_webgl_get_parameter_d__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_parameter_d: (param) => GLctx.getParameter(param), + + emscripten_webgl_get_parameter_o__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_parameter_o: (param) => { + var obj = GLctx.getParameter(param); + return obj?.name; + }, + + emscripten_webgl_get_parameter_utf8__deps: ['$stringToNewUTF8'], + emscripten_webgl_get_parameter_utf8__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_parameter_utf8: (param) => stringToNewUTF8(GLctx.getParameter(param)), + + emscripten_webgl_get_parameter_i64v__proxy: 'sync_on_current_webgl_context_thread', + emscripten_webgl_get_parameter_i64v__deps: ['$writeI53ToI64'], + emscripten_webgl_get_parameter_i64v: (param, dst) => writeI53ToI64(dst, GLctx.getParameter(param)), +}; + + +function handleWebGLProxying(funcs) { +#if PTHREADS + // Process 'sync_on_webgl_context_handle_thread' and + // 'sync_on_current_webgl_context_thread' pseudo-proxying modes to appropriate + // proxying mechanism, either proxying on-demand, unconditionally, or never, + // depending on build modes. + // 'sync_on_webgl_context_handle_thread' is used for function signatures that + // take a HTML5 WebGL context handle object as the first argument. + // 'sync_on_current_webgl_context_thread' is used for functions that operate on + // the implicit "current WebGL context" as activated via + // emscripten_webgl_make_current() function. + function listOfNFunctionArgs(func) { + const args = []; + for (var i = 0; i < func.length; ++i) { + args.push('p' + i); + } + return args; + } + + const targetingOffscreenCanvas = {{{ OFFSCREENCANVAS_SUPPORT }}}; + const targetingOffscreenFramebuffer = {{{ OFFSCREEN_FRAMEBUFFER }}}; + + for (const i in funcs) { + // Is this a function that takes GL context handle as first argument? + const proxyContextHandle = funcs[i + '__proxy'] == 'sync_on_webgl_context_handle_thread'; + + // Is this a function that operates on the implicit current GL context object? + const proxyCurrentContext = funcs[i + '__proxy'] == 'sync_on_current_webgl_context_thread'; + + if (!proxyContextHandle && !proxyCurrentContext) { + continue; // no resolving of pseudo-proxying needed for this function. + } + + if (targetingOffscreenCanvas && (targetingOffscreenFramebuffer || proxyContextHandle)) { + // Dynamically check at runtime whether the current thread owns the GL context + // handle/current GL context object. If not, proxy the call to main thread. + // TODO: this handles the calling pthread and main thread cases, but not yet + // the case from pthread->pthread. + const sig = funcs[i + '__sig'] || LibraryManager.library[i + '__sig'] + assert(sig); + funcs[i + '_calling_thread'] = funcs[i]; + funcs[i + '_main_thread'] = i + '_calling_thread'; + funcs[i + '_main_thread__proxy'] = 'sync'; + funcs[i + '_main_thread__sig'] = sig; + funcs[i + '__deps'] ??= []; + funcs[i + '__deps'].push(i + '_calling_thread'); + funcs[i + '__deps'].push(i + '_main_thread'); + delete funcs[i + '__proxy']; + const funcArgs = listOfNFunctionArgs(funcs[i]); + const funcArgsString = funcArgs.join(','); + const retStatement = sig[0] != 'v' ? 'return' : ''; + const contextCheck = proxyContextHandle ? 'GL.contexts[p0]' : 'GLctx'; + var funcBody = `${retStatement} ${contextCheck} ? _${i}_calling_thread(${funcArgsString}) : _${i}_main_thread(${funcArgsString});`; + if (funcs[i + '_before_on_calling_thread']) { + funcs[i + '__deps'].push('$' + i + '_before_on_calling_thread'); + funcBody = `${i}_before_on_calling_thread(${funcArgsString}); ` + funcBody; + } + funcs[i] = new Function(funcArgs, funcBody); + } else if (targetingOffscreenFramebuffer) { + // When targeting only OFFSCREEN_FRAMEBUFFER, unconditionally proxy all GL + // calls to main thread. + funcs[i + '__proxy'] = 'sync'; + } else { + // Building without OFFSCREENCANVAS_SUPPORT or OFFSCREEN_FRAMEBUFFER; or building + // with OFFSCREENCANVAS_SUPPORT and no OFFSCREEN_FRAMEBUFFER: the application + // will only utilize WebGL in the main browser thread, and in the calling thread. + // Remove the WebGL proxying directives. + delete funcs[i + '__proxy']; + } + } +#else + // In single threaded mode just delete our custom __proxy attributes, otherwise + // they will causes errors in the JS compiler. + for (const i in funcs) { + delete funcs[i + '__proxy']; + } +#endif // PTHREADS +} + +handleWebGLProxying(LibraryHtml5WebGL); + +#if LibraryManager.has('libwebgl.js') +autoAddDeps(LibraryHtml5WebGL, '$GL'); +#endif + +addToLibrary(LibraryHtml5WebGL); diff --git a/src/library_icasefs.js b/src/lib/libicasefs.js similarity index 100% rename from src/library_icasefs.js rename to src/lib/libicasefs.js diff --git a/src/lib/libidbfs.js b/src/lib/libidbfs.js new file mode 100644 index 0000000000000..036e52fd9126c --- /dev/null +++ b/src/lib/libidbfs.js @@ -0,0 +1,396 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $IDBFS__deps: ['$FS', '$MEMFS', '$PATH'], + $IDBFS__postset: () => { + addAtExit('IDBFS.quit();'); + return ''; + }, + $IDBFS: { + dbs: {}, + indexedDB: () => { +#if ASSERTIONS + assert(typeof indexedDB != 'undefined', 'IDBFS used, but indexedDB not supported'); +#endif + return indexedDB; + }, + DB_VERSION: 21, + DB_STORE_NAME: 'FILE_DATA', + + // Queues a new VFS -> IDBFS synchronization operation + queuePersist: (mount) => { + function onPersistComplete() { + if (mount.idbPersistState === 'again') startPersist(); // If a new sync request has appeared in between, kick off a new sync + else mount.idbPersistState = 0; // Otherwise reset sync state back to idle to wait for a new sync later + } + function startPersist() { + mount.idbPersistState = 'idb'; // Mark that we are currently running a sync operation + IDBFS.syncfs(mount, /*populate:*/false, onPersistComplete); + } + + if (!mount.idbPersistState) { + // Programs typically write/copy/move multiple files in the in-memory + // filesystem within a single app frame, so when a filesystem sync + // command is triggered, do not start it immediately, but only after + // the current frame is finished. This way all the modified files + // inside the main loop tick will be batched up to the same sync. + mount.idbPersistState = setTimeout(startPersist, 0); + } else if (mount.idbPersistState === 'idb') { + // There is an active IndexedDB sync operation in-flight, but we now + // have accumulated more files to sync. We should therefore queue up + // a new sync after the current one finishes so that all writes + // will be properly persisted. + mount.idbPersistState = 'again'; + } + }, + + mount: (mount) => { + // reuse core MEMFS functionality + var mnt = MEMFS.mount(mount); + // If the automatic IDBFS persistence option has been selected, then automatically persist + // all modifications to the filesystem as they occur. + if (mount?.opts?.autoPersist) { + mount.idbPersistState = 0; // IndexedDB sync starts in idle state + var memfs_node_ops = mnt.node_ops; + mnt.node_ops = {...mnt.node_ops}; // Clone node_ops to inject write tracking + mnt.node_ops.mknod = (parent, name, mode, dev) => { + var node = memfs_node_ops.mknod(parent, name, mode, dev); + // Propagate injected node_ops to the newly created child node + node.node_ops = mnt.node_ops; + // Remember for each IDBFS node which IDBFS mount point they came from so we know which mount to persist on modification. + node.idbfs_mount = mnt.mount; + // Remember original MEMFS stream_ops for this node + node.memfs_stream_ops = node.stream_ops; + // Clone stream_ops to inject write tracking + node.stream_ops = {...node.stream_ops}; + + // Track all file writes + node.stream_ops.write = (stream, buffer, offset, length, position, canOwn) => { + // This file has been modified, we must persist IndexedDB when this file closes + stream.node.isModified = true; + return node.memfs_stream_ops.write(stream, buffer, offset, length, position, canOwn); + }; + + // Persist IndexedDB on file close + node.stream_ops.close = (stream) => { + var n = stream.node; + if (n.isModified) { + IDBFS.queuePersist(n.idbfs_mount); + n.isModified = false; + } + if (n.memfs_stream_ops.close) return n.memfs_stream_ops.close(stream); + }; + + // Persist the node we just created to IndexedDB + IDBFS.queuePersist(mnt.mount); + + return node; + }; + // Also kick off persisting the filesystem on other operations that modify the filesystem. + mnt.node_ops.rmdir = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.rmdir(...args)); + mnt.node_ops.symlink = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.symlink(...args)); + mnt.node_ops.unlink = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.unlink(...args)); + mnt.node_ops.rename = (...args) => (IDBFS.queuePersist(mnt.mount), memfs_node_ops.rename(...args)); + } + return mnt; + }, + + syncfs: (mount, populate, callback) => { + IDBFS.getLocalSet(mount, (err, local) => { + if (err) return callback(err); + + IDBFS.getRemoteSet(mount, (err, remote) => { + if (err) return callback(err); + + var src = populate ? remote : local; + var dst = populate ? local : remote; + + IDBFS.reconcile(src, dst, callback); + }); + }); + }, + quit: () => { + for (var value of Object.values(IDBFS.dbs)) { + value.close() + } + IDBFS.dbs = {}; + }, + getDB: (name, callback) => { + // check the cache first + var db = IDBFS.dbs[name]; + if (db) { + return callback(null, db); + } + + var req; + try { + req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION); + } catch (e) { + return callback(e); + } + if (!req) { + return callback("Unable to connect to IndexedDB"); + } + req.onupgradeneeded = (e) => { + var db = /** @type {IDBDatabase} */ (e.target.result); + var transaction = e.target.transaction; + + var fileStore; + + if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { + fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME); + } else { + fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME); + } + + if (!fileStore.indexNames.contains('timestamp')) { + fileStore.createIndex('timestamp', 'timestamp', { unique: false }); + } + }; + req.onsuccess = () => { + db = /** @type {IDBDatabase} */ (req.result); + + // add to the cache + IDBFS.dbs[name] = db; + callback(null, db); + }; + req.onerror = (e) => { + callback(e.target.error); + e.preventDefault(); + }; + }, + getLocalSet: (mount, callback) => { + var entries = {}; + + function isRealDir(p) { + return p !== '.' && p !== '..'; + }; + function toAbsolute(root) { + return (p) => PATH.join2(root, p); + }; + + var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); + + while (check.length) { + var path = check.pop(); + var stat; + + try { + stat = FS.lstat(path); + } catch (e) { + return callback(e); + } + + if (FS.isDir(stat.mode)) { + check.push(...FS.readdir(path).filter(isRealDir).map(toAbsolute(path))); + } + + entries[path] = { 'timestamp': stat.mtime }; + } + + return callback(null, { type: 'local', entries: entries }); + }, + getRemoteSet: (mount, callback) => { + var entries = {}; + + IDBFS.getDB(mount.mountpoint, (err, db) => { + if (err) return callback(err); + + try { + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly'); + transaction.onerror = (e) => { + callback(e.target.error); + e.preventDefault(); + }; + + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + var index = store.index('timestamp'); + + index.openKeyCursor().onsuccess = (event) => { + var cursor = event.target.result; + + if (!cursor) { + return callback(null, { type: 'remote', db, entries }); + } + + entries[cursor.primaryKey] = { 'timestamp': cursor.key }; + + cursor.continue(); + }; + } catch (e) { + return callback(e); + } + }); + }, + loadLocalEntry: (path, callback) => { + var stat, node; + + try { + var lookup = FS.lookupPath(path); + node = lookup.node; + stat = FS.lstat(path); + } catch (e) { + return callback(e); + } + + if (FS.isDir(stat.mode)) { + return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode }); + } else if (FS.isLink(stat.mode)) { + return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode, 'link': node.link, }); + } else if (FS.isFile(stat.mode)) { + // Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array. + // Therefore always convert the file contents to a typed array first before writing the data to IndexedDB. + node.contents = MEMFS.getFileDataAsTypedArray(node); + return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode, 'contents': node.contents }); + } else { + return callback(new Error('node type not supported')); + } + }, + storeLocalEntry: (path, entry, callback) => { + try { + if (FS.isDir(entry['mode'])) { + FS.mkdirTree(path, entry['mode']); + } else if (FS.isLink(entry['mode'])) { + FS.symlink(entry['link'], path); + } else if (FS.isFile(entry['mode'])) { + FS.writeFile(path, entry['contents'], { canOwn: true }); + } else { + return callback(new Error('node type not supported')); + } + + FS.chmod(path, entry['mode']); + FS.utime(path, entry['timestamp'], entry['timestamp']); + } catch (e) { + return callback(e); + } + + callback(null); + }, + removeLocalEntry: (path, callback) => { + try { + var stat = FS.lstat(path); + + if (FS.isDir(stat.mode)) { + FS.rmdir(path); + } else { + FS.unlink(path); + } + } catch (e) { + return callback(e); + } + + callback(null); + }, + loadRemoteEntry: (store, path, callback) => { + var req = store.get(path); + req.onsuccess = (event) => callback(null, event.target.result); + req.onerror = (e) => { + callback(e.target.error); + e.preventDefault(); + }; + }, + storeRemoteEntry: (store, path, entry, callback) => { + try { + var req = store.put(entry, path); + } catch (e) { + callback(e); + return; + } + req.onsuccess = (event) => callback(); + req.onerror = (e) => { + callback(e.target.error); + e.preventDefault(); + }; + }, + removeRemoteEntry: (store, path, callback) => { + var req = store.delete(path); + req.onsuccess = (event) => callback(); + req.onerror = (e) => { + callback(e.target.error); + e.preventDefault(); + }; + }, + reconcile: (src, dst, callback) => { + var total = 0; + + var create = []; + for (var [key, e] of Object.entries(src.entries)) { + var e2 = dst.entries[key]; + if (!e2 || e['timestamp'].getTime() != e2['timestamp'].getTime()) { + create.push(key); + total++; + } + } + + var remove = []; + for (var key of Object.keys(dst.entries)) { + if (!src.entries[key]) { + remove.push(key); + total++; + } + } + + if (!total) { + return callback(null); + } + + var errored = false; + var db = src.type === 'remote' ? src.db : dst.db; + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + + function done(err) { + if (err && !errored) { + errored = true; + return callback(err); + } + }; + + // transaction may abort if (for example) there is a QuotaExceededError + transaction.onerror = transaction.onabort = (e) => { + done(e.target.error); + e.preventDefault(); + }; + + transaction.oncomplete = (e) => { + if (!errored) { + callback(null); + } + }; + + // sort paths in ascending order so directory entries are created + // before the files inside them + for (const path of create.sort()) { + if (dst.type === 'local') { + IDBFS.loadRemoteEntry(store, path, (err, entry) => { + if (err) return done(err); + IDBFS.storeLocalEntry(path, entry, done); + }); + } else { + IDBFS.loadLocalEntry(path, (err, entry) => { + if (err) return done(err); + IDBFS.storeRemoteEntry(store, path, entry, done); + }); + } + } + + // sort paths in descending order so files are deleted before their + // parent directories + for (var path of remove.sort().reverse()) { + if (dst.type === 'local') { + IDBFS.removeLocalEntry(path, done); + } else { + IDBFS.removeRemoteEntry(store, path, done); + } + } + } + } +}); + +if (WASMFS) { + error("using -lidbfs is not currently supported in WasmFS."); +} diff --git a/src/lib/libidbstore.js b/src/lib/libidbstore.js new file mode 100644 index 0000000000000..b2a9f058e5b13 --- /dev/null +++ b/src/lib/libidbstore.js @@ -0,0 +1,167 @@ +/** + * @license + * Copyright 2015 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +#include "IDBStore.js" + +var LibraryIDBStore = { + // A simple IDB-backed storage mechanism. Suitable for saving and loading + // large files asynchronously. This does *NOT* use the emscripten filesystem, + // intentionally, to avoid overhead. It lets your application define whatever + // filesystem-like layer you want, with the overhead 100% controlled by you. + // At the extremes, you could either just store large files, with almost no + // extra code; or you could implement a file b-tree using posix-compliant + // filesystem on top. + $IDBStore: IDBStore, + emscripten_idb_async_load__deps: ['$UTF8ToString', '$callUserCallback', 'malloc', 'free'], + emscripten_idb_async_load: (db, id, arg, onload, onerror) => { + {{{ runtimeKeepalivePush() }}}; + IDBStore.getFile(UTF8ToString(db), UTF8ToString(id), (error, byteArray) => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if (error) { + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + return; + } + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + {{{ makeDynCall('vppi', 'onload') }}}(arg, buffer, byteArray.length); + _free(buffer); + }); + }); + }, + emscripten_idb_async_store__deps: ['$UTF8ToString', '$callUserCallback'], + emscripten_idb_async_store: (db, id, ptr, num, arg, onstore, onerror) => { + // note that we copy the data here, as these are async operations - changes + // to HEAPU8 meanwhile should not affect us! + {{{ runtimeKeepalivePush() }}}; + IDBStore.setFile(UTF8ToString(db), UTF8ToString(id), new Uint8Array(HEAPU8.subarray(ptr, ptr+num)), (error) => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if (error) { + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + return; + } + if (onstore) {{{ makeDynCall('vp', 'onstore') }}}(arg); + }); + }); + }, + emscripten_idb_async_delete__deps: ['$UTF8ToString', '$callUserCallback'], + emscripten_idb_async_delete: (db, id, arg, ondelete, onerror) => { + {{{ runtimeKeepalivePush() }}}; + IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if (error) { + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + return; + } + if (ondelete) {{{ makeDynCall('vp', 'ondelete') }}}(arg); + }); + }); + }, + emscripten_idb_async_exists__deps: ['$UTF8ToString', '$callUserCallback'], + emscripten_idb_async_exists: (db, id, arg, oncheck, onerror) => { + {{{ runtimeKeepalivePush() }}}; + IDBStore.existsFile(UTF8ToString(db), UTF8ToString(id), (error, exists) => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if (error) { + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + return; + } + if (oncheck) {{{ makeDynCall('vpi', 'oncheck') }}}(arg, exists); + }); + }); + }, + emscripten_idb_async_clear__deps: ['$UTF8ToString', '$callUserCallback'], + emscripten_idb_async_clear: (db, arg, onclear, onerror) => { + {{{ runtimeKeepalivePush() }}}; + IDBStore.clearStore(UTF8ToString(db), (error) => { + {{{ runtimeKeepalivePop() }}} + callUserCallback(() => { + if (error) { + if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg); + return; + } + if (onclear) {{{ makeDynCall('vp', 'onclear') }}}(arg); + }); + }); + }, + +#if ASYNCIFY + emscripten_idb_load__async: 'auto', + emscripten_idb_load__deps: ['malloc'], + emscripten_idb_load: (db, id, pbuffer, pnum, perror) => new Promise((resolve) => { + IDBStore.getFile(UTF8ToString(db), UTF8ToString(id), (error, byteArray) => { + if (error) { + {{{ makeSetValue('perror', 0, '1', 'i32') }}}; + resolve(); + return; + } + var buffer = _malloc(byteArray.length); // must be freed by the caller! + HEAPU8.set(byteArray, buffer); + {{{ makeSetValue('pbuffer', 0, 'buffer', '*') }}}; + {{{ makeSetValue('pnum', 0, 'byteArray.length', 'i32') }}}; + {{{ makeSetValue('perror', 0, '0', 'i32') }}}; + resolve(); + }); + }), + emscripten_idb_store__async: 'auto', + emscripten_idb_store: (db, id, ptr, num, perror) => new Promise((resolve) => { + IDBStore.setFile(UTF8ToString(db), UTF8ToString(id), new Uint8Array(HEAPU8.subarray(ptr, ptr+num)), (error) => { + // Closure warns about storing booleans in TypedArrays. + /** @suppress{checkTypes} */ + {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; + resolve(); + }); + }), + emscripten_idb_delete__async: 'auto', + emscripten_idb_delete: (db, id, perror) => new Promise((resolve) => { + IDBStore.deleteFile(UTF8ToString(db), UTF8ToString(id), (error) => { + /** @suppress{checkTypes} */ + {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; + resolve(); + }); + }), + emscripten_idb_exists__async: 'auto', + emscripten_idb_exists: (db, id, pexists, perror) => new Promise((resolve) => { + IDBStore.existsFile(UTF8ToString(db), UTF8ToString(id), (error, exists) => { + /** @suppress{checkTypes} */ + {{{ makeSetValue('pexists', 0, '!!exists', 'i32') }}}; + /** @suppress{checkTypes} */ + {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; + resolve(); + }); + }), + emscripten_idb_clear__async: 'auto', + emscripten_idb_clear: (db, perror) => new Promise((resolve) => { + IDBStore.clearStore(UTF8ToString(db), (error) => { + /** @suppress{checkTypes} */ + {{{ makeSetValue('perror', 0, '!!error', 'i32') }}}; + resolve(); + }); + }), +#else + emscripten_idb_load: (db, id, pbuffer, pnum, perror) => { + abort('Please compile your program with async support in order to use synchronous operations like emscripten_idb_load, etc.'); + }, + emscripten_idb_store: (db, id, ptr, num, perror) => { + abort('Please compile your program with async support in order to use synchronous operations like emscripten_idb_store, etc.'); + }, + emscripten_idb_delete: (db, id, perror) => { + abort('Please compile your program with async support in order to use synchronous operations like emscripten_idb_delete, etc.'); + }, + emscripten_idb_exists: (db, id, pexists, perror) => { + abort('Please compile your program with async support in order to use synchronous operations like emscripten_idb_exists, etc.'); + }, + emscripten_idb_clear: (db, perror) => { + abort('Please compile your program with async support in order to use synchronous operations like emscripten_idb_clear, etc.'); + }, +#endif // ASYNCIFY +}; + +autoAddDeps(LibraryIDBStore, '$IDBStore'); +addToLibrary(LibraryIDBStore); diff --git a/src/lib/libint53.js b/src/lib/libint53.js new file mode 100644 index 0000000000000..40aca59384daa --- /dev/null +++ b/src/lib/libint53.js @@ -0,0 +1,148 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ +#if ASSERTIONS + $writeI53ToI64__deps: ['$readI53FromI64', '$readI53FromU64' +#if MINIMAL_RUNTIME + , '$warnOnce' +#endif + ], +#endif + // Writes the given JavaScript Number to the WebAssembly heap as a 64-bit integer variable. + // If the given number is not in the range [-2^53, 2^53] (inclusive), then an unexpectedly + // rounded or incorrect number can be written to the heap. ("garbage in, garbage out") + // Note that unlike the most other function variants in this library, there is no separate + // function $writeI53ToU64(): the implementation would be identical, and it is up to the + // C/C++ side code to interpret the resulting number as signed or unsigned as is desirable. + $writeI53ToI64: (ptr, num) => { + {{{ makeSetValue('ptr', 0, 'num', 'u32') }}}; + var lower = {{{ makeGetValue('ptr', 0, 'u32') }}}; + {{{ makeSetValue('ptr', 4, '(num - lower)/4294967296', 'u32') }}}; +#if ASSERTIONS + var deserialized = (num >= 0) ? readI53FromU64(ptr) : readI53FromI64(ptr); + var offset = {{{ getHeapOffset('ptr', 'u32') }}}; + if (deserialized != num) warnOnce(`writeI53ToI64() out of range: serialized JS Number ${num} to Wasm heap as bytes lo=${ptrToString(HEAPU32[offset])}, hi=${ptrToString(HEAPU32[offset+1])}, which deserializes back to ${deserialized} instead!`); +#endif + }, + + // Same as writeI53ToI64, but if the double precision number does not fit within the + // 64-bit number, the number is clamped to range [-2^63, 2^63-1]. + $writeI53ToI64Clamped__deps: ['$writeI53ToI64'], + $writeI53ToI64Clamped: (ptr, num) => { + if (num > 0x7FFFFFFFFFFFFFFF) { + {{{ makeSetValue('ptr', 0, 0xFFFFFFFF, 'u32') }}}; + {{{ makeSetValue('ptr', 4, 0x7FFFFFFF, 'u32') }}}; + } else if (num < -0x8000000000000000) { + {{{ makeSetValue('ptr', 0, 0, 'u32') }}}; + {{{ makeSetValue('ptr', 4, 0x80000000, 'u32') }}}; + } else { + writeI53ToI64(ptr, num); + } + }, + + // Like writeI53ToI64, but throws if the passed number is out of range of int64. + $writeI53ToI64Signaling__deps: ['$writeI53ToI64'], + $writeI53ToI64Signaling: (ptr, num) => { + if (num > 0x7FFFFFFFFFFFFFFF || num < -0x8000000000000000) { +#if ASSERTIONS + throw `RangeError in writeI53ToI64Signaling(): input value ${num} is out of range of int64`; +#else + throw `RangeError: ${num}`; +#endif + } + writeI53ToI64(ptr, num); + }, + + // Uint64 variant of writeI53ToI64Clamped. Writes the Number to a Uint64 variable on + // the heap, clamping out of range values to range [0, 2^64-1]. + $writeI53ToU64Clamped__deps: ['$writeI53ToI64'], + $writeI53ToU64Clamped: (ptr, num) => { + if (num > 0xFFFFFFFFFFFFFFFF) { + {{{ makeSetValue('ptr', 0, 0xFFFFFFFF, 'u32') }}}; + {{{ makeSetValue('ptr', 4, 0xFFFFFFFF, 'u32') }}}; + } else if (num < 0) { + {{{ makeSetValue('ptr', 0, 0, 'u32') }}}; + {{{ makeSetValue('ptr', 4, 0, 'u32') }}}; + } else { + writeI53ToI64(ptr, num); + } + }, + + // Like writeI53ToI64, but throws if the passed number is out of range of uint64. + $writeI53ToU64Signaling__deps: ['$writeI53ToI64'], + $writeI53ToU64Signaling: (ptr, num) => { + if (num < 0 || num > 0xFFFFFFFFFFFFFFFF) { +#if ASSERTIONS + throw `RangeError in writeI53ToU64Signaling(): input value ${num} is out of range of uint64`; +#else + throw `RangeError: ${num}`; +#endif + } + writeI53ToI64(ptr, num); + }, + + // Reads a 64-bit signed integer from the WebAssembly heap and + // converts it to a JavaScript Number, which can represent 53 integer bits precisely. + // TODO: Add $readI53FromI64Signaling() variant. + $readI53FromI64: (ptr) => { + return {{{ makeGetValue('ptr', 0, 'u32') }}} + {{{ makeGetValue('ptr', 4, 'i32') }}} * 4294967296; + }, + + // Reads a 64-bit unsigned integer from the WebAssembly heap and + // converts it to a JavaScript Number, which can represent 53 integer bits precisely. + // TODO: Add $readI53FromU64Signaling() variant. + $readI53FromU64: (ptr) => { + return {{{ makeGetValue('ptr', 0, 'u32') }}} + {{{ makeGetValue('ptr', 4, 'u32') }}} * 4294967296; + }, + + // Converts the given signed 32-bit low-high pair to a JavaScript Number that + // can represent 53 bits of precision. + $convertI32PairToI53: (lo, hi) => { +#if ASSERTIONS + // This function should not be getting called with too large unsigned numbers + // in high part (if hi >= 0x7FFFFFFFF, one should have been calling + // convertU32PairToI53()) + assert(hi === (hi|0)); +#endif + return (lo >>> 0) + hi * 4294967296; + }, + + // Converts the given signed 32-bit low-high pair to a JavaScript Number that can + // represent 53 bits of precision. Returns a NaN if the number exceeds the safe + // integer range representable by a Number (x > 9007199254740992 || x < -9007199254740992) + $convertI32PairToI53Checked: (lo, hi) => { +#if ASSERTIONS + assert(lo == (lo >>> 0) || lo == (lo|0)); // lo should either be a i32 or a u32 + assert(hi === (hi|0)); // hi should be a i32 +#endif + return ((hi + 0x200000) >>> 0 < 0x400001 - !!lo) ? (lo >>> 0) + hi * 4294967296 : NaN; + }, + + // Converts the given unsigned 32-bit low-high pair to a JavaScript Number that can + // represent 53 bits of precision. + // TODO: Add $convertU32PairToI53Checked() variant. + $convertU32PairToI53: (lo, hi) => { + return (lo >>> 0) + (hi >>> 0) * 4294967296; + }, + +#if WASM_BIGINT + $INT53_MAX: '{{{ Math.pow(2, 53) }}}', + $INT53_MIN: '-{{{ Math.pow(2, 53) }}}', + // Convert a bigint value (usually coming from Wasm->JS call) into an int53 + // JS Number. This is used when we have an incoming i64 that we know is a + // pointer or size_t and is expected to be within the int53 range. + // Returns NaN if the incoming bigint is outside the range. + $bigintToI53Checked__deps: ['$INT53_MAX', '$INT53_MIN'], + $bigintToI53Checked: (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num), +#endif +}); + +#if WASM_BIGINT +globalThis.i53ConversionDeps = ['$bigintToI53Checked']; +#else +globalThis.i53ConversionDeps = ['$convertI32PairToI53Checked']; +#endif diff --git a/src/library_jsfilefs.js b/src/lib/libjsfilefs.js similarity index 100% rename from src/library_jsfilefs.js rename to src/lib/libjsfilefs.js diff --git a/src/lib/liblegacy.js b/src/lib/liblegacy.js new file mode 100644 index 0000000000000..7077bf8a8d0af --- /dev/null +++ b/src/lib/liblegacy.js @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + * + * Legacy library symbols that are no longer used by emscripten itself but + * could have external users. + * + * Symbols in this file are not available in `-sSTRICT` mode. + * + * Any usage of symbols in this file will result in a `-Wdeprecated` warning. + * + * Symbol in this file should be removed after "enough time" has passed such + * that all external users have been able to transition away. + */ + +legacyFuncs = { + $ALLOC_NORMAL: 0, // Tries to use _malloc() + $ALLOC_STACK: 1, // Lives for the duration of the current function call + + /** + * allocate(): This function is no longer used by emscripten but is kept around to avoid + * breaking external users. + * You should normally not use allocate(), and instead allocate + * memory using _malloc()/stackAlloc(), initialize it with + * setValue(), and so forth. + * @param {(Uint8Array|Array)} slab: An array of data. + * @param {number=} allocator : How to allocate memory, see ALLOC_* + */ + $allocate__deps: ['$ALLOC_STACK', 'malloc', '$stackAlloc'], + $allocate: (slab, allocator) => { + var ret; + #if ASSERTIONS + assert(typeof allocator == 'number', 'allocate no longer takes a type argument') + assert(typeof slab != 'number', 'allocate no longer takes a number as arg0') + #endif + + if (allocator == ALLOC_STACK) { + ret = stackAlloc(slab.length); + } else { + ret = _malloc(slab.length); + } + + if (!slab.subarray && !slab.slice) { + slab = new Uint8Array(slab); + } + HEAPU8.set(slab, ret); + return ret; + }, + + // Deprecated: This function should not be called because it is unsafe and + // does not provide a maximum length limit of how many bytes it is allowed to + // write. Prefer calling the function stringToUTF8Array() instead, which takes + // in a maximum length that can be used to be secure from out of bounds + // writes. + $writeStringToMemory__docs: '/** @deprecated @param {boolean=} dontAddNull */', + $writeStringToMemory__deps: ['$lengthBytesUTF8', '$stringToUTF8'], + $writeStringToMemory: (string, buffer, dontAddNull) => { + warnOnce('writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!'); + + var /** @type {number} */ lastChar, /** @type {number} */ end; + if (dontAddNull) { + // stringToUTF8 always appends null. If we don't want to do that, remember the + // character that existed at the location where the null will be placed, and restore + // that after the write (below). + end = buffer + lengthBytesUTF8(string); + lastChar = HEAP8[end]; + } + stringToUTF8(string, buffer, Infinity); + if (dontAddNull) HEAP8[end] = lastChar; // Restore the value under the null character. + }, + + // Deprecated: Use stringToAscii + $writeAsciiToMemory__docs: '/** @param {boolean=} dontAddNull */', + $writeAsciiToMemory: (str, buffer, dontAddNull) => { + for (var i = 0; i < str.length; ++i) { +#if ASSERTIONS + assert(str.charCodeAt(i) === (str.charCodeAt(i) & 0xff)); +#endif + {{{ makeSetValue('buffer++', 0, 'str.charCodeAt(i)', 'i8') }}}; + } + // Null-terminate the string + if (!dontAddNull) {{{ makeSetValue('buffer', 0, 0, 'i8') }}}; + }, + + $allocateUTF8__deps: ['$stringToNewUTF8'], + $allocateUTF8: (...args) => stringToNewUTF8(...args), + $allocateUTF8OnStack__deps: ['$stringToUTF8OnStack'], + $allocateUTF8OnStack: (...args) => stringToUTF8OnStack(...args), + +#if LINK_AS_CXX + $demangle__deps: ['$withStackSave', '__cxa_demangle', 'free', '$stringToUTF8OnStack'], + $demangle: (func) => { + // If demangle has failed before, stop demangling any further function names + // This avoids an infinite recursion with malloc()->abort()->stackTrace()->demangle()->malloc()->... + demangle.recursionGuard = (demangle.recursionGuard|0)+1; + if (demangle.recursionGuard > 1) return func; + return withStackSave(() => { + try { + var s = func; + if (s.startsWith('__Z')) + s = s.slice(1); + var buf = stringToUTF8OnStack(s); + var status = stackAlloc(4); + var ret = ___cxa_demangle(buf, 0, 0, status); + if ({{{ makeGetValue('status', '0', 'i32') }}} === 0 && ret) { + return UTF8ToString(ret); + } + // otherwise, libcxxabi failed + } catch(e) { + } finally { + _free(ret); + if (demangle.recursionGuard < 2) --demangle.recursionGuard; + } + // failure when using libcxxabi, don't demangle + return func; + }); + }, +#endif + + $stackTrace__deps: ['$jsStackTrace'], + $stackTrace: () => { + var js = jsStackTrace(); + if (Module['extraStackTrace']) js += '\n' + Module['extraStackTrace'](); + return js; + }, + + // Legacy names for runtime `out`/`err` symbols. + $print: '=out', + $printErr: '=err', + + // Converts a JS string to an integer base-10. Despite _s, which + // suggests signaling error handling, this returns NaN on error. + // (This was a mistake in the original implementation, and kept + // to avoid breakage.) + $jstoi_s: 'Number', + + $getNativeTypeSize__deps: ['$POINTER_SIZE'], + $getNativeTypeSize: {{{ getNativeTypeSize }}}, +}; + +if (WARN_DEPRECATED && !INCLUDE_FULL_LIBRARY) { + for (const name of Object.keys(legacyFuncs)) { + if (!isDecorator(name)) { + const depsKey = `${name}__deps`; + legacyFuncs[depsKey] ??= [] + legacyFuncs[depsKey].push(() => { + warn(`JS library symbol '${name}' is deprecated. Please open a bug if you have a continuing need for this symbol [-Wdeprecated]`); + }); + } + } +} + +addToLibrary(legacyFuncs); diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js new file mode 100644 index 0000000000000..8a2ba67c8af2a --- /dev/null +++ b/src/lib/liblittle_endian_heap.js @@ -0,0 +1,127 @@ +var LibraryLittleEndianHeap = { + $LE_HEAP_STORE_U16: (byteOffset, value) => + HEAP_DATA_VIEW.setUint16(byteOffset, value, true), + + $LE_HEAP_STORE_I16: (byteOffset, value) => + HEAP_DATA_VIEW.setInt16(byteOffset, value, true), + + $LE_HEAP_STORE_U32: (byteOffset, value) => + HEAP_DATA_VIEW.setUint32(byteOffset, value, true), + + $LE_HEAP_STORE_I32: (byteOffset, value) => + HEAP_DATA_VIEW.setInt32(byteOffset, value, true), + + $LE_HEAP_STORE_U64: (byteOffset, value) => + HEAP_DATA_VIEW.setBigUint64(byteOffset, value, true), + + $LE_HEAP_STORE_I64: (byteOffset, value) => + HEAP_DATA_VIEW.setBigInt64(byteOffset, value, true), + + $LE_HEAP_STORE_F32: (byteOffset, value) => + HEAP_DATA_VIEW.setFloat32(byteOffset, value, true), + + $LE_HEAP_STORE_F64: (byteOffset, value) => + HEAP_DATA_VIEW.setFloat64(byteOffset, value, true), + + $LE_HEAP_LOAD_U16: (byteOffset) => + HEAP_DATA_VIEW.getUint16(byteOffset, true), + + $LE_HEAP_LOAD_I16: (byteOffset) => + HEAP_DATA_VIEW.getInt16(byteOffset, true), + + $LE_HEAP_LOAD_U32: (byteOffset) => + HEAP_DATA_VIEW.getUint32(byteOffset, true), + + $LE_HEAP_LOAD_I32: (byteOffset) => + HEAP_DATA_VIEW.getInt32(byteOffset, true), + + $LE_HEAP_LOAD_U64: (byteOffset) => + HEAP_DATA_VIEW.getBigUint64(byteOffset, true), + + $LE_HEAP_LOAD_I64: (byteOffset) => + HEAP_DATA_VIEW.getBigInt64(byteOffset, true), + + $LE_HEAP_LOAD_F32: (byteOffset) => + HEAP_DATA_VIEW.getFloat32(byteOffset, true), + + $LE_HEAP_LOAD_F64: (byteOffset) => + HEAP_DATA_VIEW.getFloat64(byteOffset, true), + + $LE_ATOMICS_NATIVE_BYTE_ORDER__postset: ` +LE_ATOMICS_NATIVE_BYTE_ORDER = (new Int8Array(new Int16Array([1]).buffer)[0] === 1) + ? [ /* little endian */ + (x => x), + (x => x), + undefined, + (x => x), + ] + : [ /* big endian */ + (x => x), + (x => (((x & 0xff00) << 8) | ((x & 0xff) << 24)) >> 16), + undefined, + (x => ((x >> 24) & 0xff) | ((x >> 8) & 0xff00) | ((x & 0xff00) << 8) | ((x & 0xff) << 24)), + ]; +function LE_HEAP_UPDATE() { + HEAPU16.unsigned = (x => x & 0xffff); + HEAPU32.unsigned = (x => x >>> 0); +} + `, + $LE_ATOMICS_NATIVE_BYTE_ORDER: [], + + $LE_ATOMICS_ADD: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.add(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_AND: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.and(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_COMPAREEXCHANGE: (heap, offset, expected, replacement) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.compareExchange(heap, offset, order(expected), order(replacement))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_EXCHANGE: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.exchange(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_ISLOCKFREE: (size) => Atomics.isLockFree(size), + $LE_ATOMICS_LOAD: (heap, offset) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.load(heap, offset)); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_NOTIFY: (heap, offset, count) => Atomics.notify(heap, offset, count), + $LE_ATOMICS_OR: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.or(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_STORE: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + Atomics.store(heap, offset, order(value)); + }, + $LE_ATOMICS_SUB: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.sub(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, + $LE_ATOMICS_WAIT: (heap, offset, value, timeout = Infinity) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + return Atomics.wait(heap, offset, order(value), timeout); + }, + $LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout = Infinity) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + return Atomics.waitAsync(heap, offset, order(value), timeout); + }, + $LE_ATOMICS_XOR: (heap, offset, value) => { + const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + const res = order(Atomics.xor(heap, offset, order(value))); + return heap.unsigned ? heap.unsigned(res) : res; + }, +} + +addToLibrary(LibraryLittleEndianHeap); diff --git a/src/lib/liblz4.js b/src/lib/liblz4.js new file mode 100644 index 0000000000000..8e4a85dc6748b --- /dev/null +++ b/src/lib/liblz4.js @@ -0,0 +1,209 @@ +/** + * @license + * Copyright 2015 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +#if LZ4 +addToLibrary({ + $LZ4__deps: ['$FS', '$preloadPlugins', '$getUniqueRunDependency', '$addRunDependency', '$removeRunDependency'], + $LZ4: { + DIR_MODE: {{{ cDefs.S_IFDIR | 0o777 }}}, + FILE_MODE: {{{ cDefs.S_IFREG | 0o777 }}}, + CHUNK_SIZE: -1, + codec: null, + init() { + if (LZ4.codec) return; + LZ4.codec = (() => { + {{{ read('../third_party/mini-lz4.js') }}}; + return MiniLZ4; + })(); + LZ4.CHUNK_SIZE = LZ4.codec.CHUNK_SIZE; + }, + loadPackage(pack, preloadPlugin) { + LZ4.init(); + var compressedData = pack['compressedData'] || LZ4.codec.compressPackage(pack['data']); + assert(compressedData['cachedIndexes'].length === compressedData['cachedChunks'].length); + for (var i = 0; i < compressedData['cachedIndexes'].length; i++) { + compressedData['cachedIndexes'][i] = -1; + compressedData['cachedChunks'][i] = compressedData['data'].subarray(compressedData['cachedOffset'] + i*LZ4.CHUNK_SIZE, + compressedData['cachedOffset'] + (i+1)*LZ4.CHUNK_SIZE); + assert(compressedData['cachedChunks'][i].length === LZ4.CHUNK_SIZE); + } + for (var file of pack['metadata'].files) { + var dir = PATH.dirname(file.filename); + var name = PATH.basename(file.filename); + FS.createPath('', dir, true, true); + var parent = FS.analyzePath(dir).object; + LZ4.createNode(parent, name, LZ4.FILE_MODE, 0, { + compressedData, + start: file.start, + end: file.end, + }); + } + // Preload files if necessary. This code is largely similar to + // createPreloadedFile in library_fs.js. However, a main difference here + // is that we only decompress the file if it can be preloaded. + // Abstracting out the common parts seems to be more effort than it is + // worth. + if (preloadPlugin) { + Browser.init(); + for (var file of pack['metadata'].files) { + var fullname = file.filename; + for (var plugin of preloadPlugins) { + if (plugin['canHandle'](fullname)) { + var dep = getUniqueRunDependency('fp ' + fullname); + addRunDependency(dep); + var finish = () => removeRunDependency(dep); + var byteArray = FS.readFile(fullname); +#if ASSERTIONS + assert(plugin['handle'].constructor.name === 'AsyncFunction', 'Filesystem plugin handlers must be async functions (See #24914)') +#endif + plugin['handle'](byteArray, fullname).then(finish).catch(finish); + break; + } + } + } + } + }, + createNode(parent, name, mode, dev, contents, mtime) { + var node = FS.createNode(parent, name, mode); + node.mode = mode; + node.node_ops = LZ4.node_ops; + node.stream_ops = LZ4.stream_ops; + this.atime = this.mtime = this.ctime = (mtime || new Date).getTime(); + assert(LZ4.FILE_MODE !== LZ4.DIR_MODE); + if (mode === LZ4.FILE_MODE) { + node.size = contents.end - contents.start; + node.contents = contents; + } else { + node.size = 4096; + node.contents = {}; + } + if (parent) { + parent.contents[name] = node; + } + return node; + }, + node_ops: { + getattr(node) { + return { + dev: 1, + ino: node.id, + mode: node.mode, + nlink: 1, + uid: 0, + gid: 0, + rdev: 0, + size: node.size, + atime: new Date(node.atime), + mtime: new Date(node.mtime), + ctime: new Date(node.ctime), + blksize: 4096, + blocks: Math.ceil(node.size / 4096), + }; + }, + setattr(node, attr) { + for (const key of ['mode', 'atime', 'mtime', 'ctime']) { + if (attr[key]) { + node[key] = attr[key]; + } + } + }, + lookup(parent, name) { + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); + }, + mknod(parent, name, mode, dev) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + rename(oldNode, newDir, newName) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + unlink(parent, name) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + rmdir(parent, name) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + readdir(node) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + symlink(parent, newName, oldPath) { + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + }, + }, + stream_ops: { + read(stream, buffer, offset, length, position) { + //out('LZ4 read ' + [offset, length, position]); + length = Math.min(length, stream.node.size - position); + if (length <= 0) return 0; + var contents = stream.node.contents; + var compressedData = contents.compressedData; + var written = 0; + while (written < length) { + var start = contents.start + position + written; // start index in uncompressed data + var desired = length - written; + //out('current read: ' + ['start', start, 'desired', desired]); + var chunkIndex = Math.floor(start / LZ4.CHUNK_SIZE); + var compressedStart = compressedData['offsets'][chunkIndex]; + var compressedSize = compressedData['sizes'][chunkIndex]; + var currChunk; + if (compressedData['successes'][chunkIndex]) { + var found = compressedData['cachedIndexes'].indexOf(chunkIndex); + if (found >= 0) { + currChunk = compressedData['cachedChunks'][found]; + } else { + // decompress the chunk + compressedData['cachedIndexes'].pop(); + compressedData['cachedIndexes'].unshift(chunkIndex); + currChunk = compressedData['cachedChunks'].pop(); + compressedData['cachedChunks'].unshift(currChunk); + if (compressedData['debug']) { + out('decompressing chunk ' + chunkIndex); + Module['decompressedChunks'] = (Module['decompressedChunks'] || 0) + 1; + } + var compressed = compressedData['data'].subarray(compressedStart, compressedStart + compressedSize); + //var t = Date.now(); + var originalSize = LZ4.codec.uncompress(compressed, currChunk); + //out('decompress time: ' + (Date.now() - t)); + if (chunkIndex < compressedData['successes'].length-1) assert(originalSize === LZ4.CHUNK_SIZE); // all but the last chunk must be full-size + } + } else { + // uncompressed + currChunk = compressedData['data'].subarray(compressedStart, compressedStart + LZ4.CHUNK_SIZE); + } + var startInChunk = start % LZ4.CHUNK_SIZE; + var endInChunk = Math.min(startInChunk + desired, LZ4.CHUNK_SIZE); + buffer.set(currChunk.subarray(startInChunk, endInChunk), offset + written); + var currWritten = endInChunk - startInChunk; + written += currWritten; + } + return written; + }, + write(stream, buffer, offset, length, position) { + throw new FS.ErrnoError({{{ cDefs.EIO }}}); + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefs.SEEK_CUR }}}) { + position += stream.position; + } else if (whence === {{{ cDefs.SEEK_END }}}) { + if (FS.isFile(stream.node.mode)) { + position += stream.node.size; + } + } + if (position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return position; + }, + }, + }, +}); +if (LibraryManager.library['$FS__deps']) { + LibraryManager.library['$FS__deps'].push('$LZ4'); // LZ4=1, so auto-include us +} else { + warn('FS does not seem to be in use (no preloaded files etc.), LZ4 will not do anything'); +} +#endif + diff --git a/src/lib/libmath.js b/src/lib/libmath.js new file mode 100644 index 0000000000000..371ef37d30fd7 --- /dev/null +++ b/src/lib/libmath.js @@ -0,0 +1,35 @@ +addToLibrary({ + emscripten_math_cbrt: 'Math.cbrt', + emscripten_math_pow: 'Math.pow', + emscripten_math_random: 'Math.random', + emscripten_math_sign: 'Math.sign', + emscripten_math_sqrt: 'Math.sqrt', + emscripten_math_exp: 'Math.exp', + emscripten_math_expm1: 'Math.expm1', + emscripten_math_fmod: (x, y) => x % y, + emscripten_math_log: 'Math.log', + emscripten_math_log1p: 'Math.log1p', + emscripten_math_log10: 'Math.log10', + emscripten_math_log2: 'Math.log2', + emscripten_math_round: 'Math.round', + emscripten_math_acos: 'Math.acos', + emscripten_math_acosh: 'Math.acosh', + emscripten_math_asin: 'Math.asin', + emscripten_math_asinh: 'Math.asinh', + emscripten_math_atan: 'Math.atan', + emscripten_math_atanh: 'Math.atanh', + emscripten_math_atan2: 'Math.atan2', + emscripten_math_cos: 'Math.cos', + emscripten_math_cosh: 'Math.cosh', + emscripten_math_hypot: (count, varargs) => { + var args = []; + for (var i = 0; i < count; ++i) { + args.push({{{ makeGetValue('varargs', `i * ${getNativeTypeSize('double')}`, 'double') }}}); + } + return Math.hypot(...args); + }, + emscripten_math_sin: 'Math.sin', + emscripten_math_sinh: 'Math.sinh', + emscripten_math_tan: 'Math.tan', + emscripten_math_tanh: 'Math.tanh', +}); diff --git a/src/lib/libmemfs.js b/src/lib/libmemfs.js new file mode 100644 index 0000000000000..cfa5b56be7e00 --- /dev/null +++ b/src/lib/libmemfs.js @@ -0,0 +1,356 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $MEMFS__deps: ['$FS', '$mmapAlloc'], + $MEMFS: { + ops_table: null, + mount(mount) { + return MEMFS.createNode(null, '/', {{{ cDefs.S_IFDIR | 0o777 }}}, 0); + }, + createNode(parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + // not supported + throw new FS.ErrnoError({{{ cDefs.EPERM }}}); + } + MEMFS.ops_table ||= { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink + }, + stream: { + llseek: MEMFS.stream_ops.llseek + } + }, + file: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + read: MEMFS.stream_ops.read, + write: MEMFS.stream_ops.write, + mmap: MEMFS.stream_ops.mmap, + msync: MEMFS.stream_ops.msync + } + }, + link: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + readlink: MEMFS.node_ops.readlink + }, + stream: {} + }, + chrdev: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr + }, + stream: FS.chrdev_stream_ops + } + }; + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + // The actual number of bytes used in the typed array, as opposed to + // contents.length which gives the whole capacity. + node.usedBytes = 0; + // The byte data of the file is stored in a typed array. + // Note: typed arrays are not resizable like normal JS arrays are, so + // there is a small penalty involved for appending file writes that + // continuously grow a file similar to std::vector capacity vs used. + node.contents = MEMFS.emptyFileContents ??= new Uint8Array(0); + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.atime = node.mtime = node.ctime = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + parent.atime = parent.mtime = parent.ctime = node.atime; + } + return node; + }, + + // Given a file node, returns its file data converted to a typed array. + getFileDataAsTypedArray(node) { +#if ASSERTIONS + assert(FS.isFile(node.mode), 'getFileDataAsTypedArray called on non-file'); +#endif + return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + }, + + // Allocates a new backing store for the given node so that it can fit at + // least newSize amount of bytes. + // May allocate more, to provide automatic geometric increase and amortized + // linear performance appending writes. + // Never shrinks the storage. + expandFileStorage(node, newCapacity) { + var prevCapacity = node.contents.length; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very + // small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for + // large sizes, do a much more conservative size*1.125 increase to avoid + // overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0); + if (prevCapacity) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = MEMFS.getFileDataAsTypedArray(node); + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + node.contents.set(oldContents); + }, + + // Performs an exact resize of the backing file storage to the given size, + // if the size is not exactly this, the storage is fully reallocated. + resizeFileStorage(node, newSize) { + if (node.usedBytes == newSize) return; + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); // Allocate new storage. + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. + node.usedBytes = newSize; + }, + + node_ops: { + getattr(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.atime); + attr.mtime = new Date(node.mtime); + attr.ctime = new Date(node.ctime); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr(node, attr) { + for (const key of ["mode", "atime", "mtime", "ctime"]) { + if (attr[key] != null) { + node[key] = attr[key]; + } + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup(parent, name) { +#if ASSERTIONS + throw new FS.ErrnoError({{{ cDefs.ENOENT }}}); +#else + // This error may happen quite a bit. To avoid overhead we reuse it (and + // suffer a lack of stack info). + if (!MEMFS.doesNotExistError) { + MEMFS.doesNotExistError = new FS.ErrnoError({{{ cDefs.ENOENT }}}); + /** @suppress {checkTypes} */ + MEMFS.doesNotExistError.stack = ''; + } + throw MEMFS.doesNotExistError; +#endif + }, + mknod(parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename(old_node, new_dir, new_name) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + if (FS.isDir(old_node.mode)) { + // if we're overwriting a directory at new_name, make sure it's empty. + for (var i in new_node.contents) { + throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}}); + } + } + FS.hashRemoveNode(new_node); + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + new_dir.contents[new_name] = old_node; + old_node.name = new_name; + new_dir.ctime = new_dir.mtime = old_node.parent.ctime = old_node.parent.mtime = Date.now(); + }, + unlink(parent, name) { + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + rmdir(parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}}); + } + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + readdir(node) { + return ['.', '..', ...Object.keys(node.contents)]; + }, + symlink(parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 0o777 | {{{ cDefs.S_IFLNK }}}, 0); + node.link = oldpath; + return node; + }, + readlink(node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return node.link; + }, + }, + stream_ops: { + read(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); +#if ASSERTIONS + assert(size >= 0); +#endif + buffer.set(contents.subarray(position, position + size), offset); + return size; + }, + + /** + * Writes the byte range (buffer[offset], buffer[offset+length]) to offset + * 'position' into the file pointed by 'stream'. + * @param {TypedArray} buffer + * @param {boolean=} canOwn - A boolean that tells if this function can + * take ownership of the passed in buffer from the subbuffer portion + * that the typed array view 'buffer' points to. The underlying + * ArrayBuffer can be larger than that, but canOwn=true will not take + * ownership of the portion outside the bytes addressed by the view. + * This means that with canOwn=true, creating a copy of the bytes is + * avoided, but the caller shouldn't touch the passed in range of + * bytes anymore since their contents now represent file data inside + * the filesystem. + */ + write(stream, buffer, offset, length, position, canOwn) { +#if ASSERTIONS + assert(buffer.subarray, 'FS.write expects a TypedArray'); +#endif +#if ALLOW_MEMORY_GROWTH + // If the buffer is located in main memory (HEAP), and if + // memory can grow, we can't hold on to references of the + // memory buffer, as they may get invalidated. That means we + // need to copy its contents. + if (buffer.buffer === HEAP8.buffer) { + canOwn = false; + } +#endif // ALLOW_MEMORY_GROWTH + + if (!length) return 0; + var node = stream.node; + node.mtime = node.ctime = Date.now(); + + if (canOwn) { +#if ASSERTIONS + assert(position === 0, 'canOwn must imply no weird position inside the file'); +#endif + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = buffer.slice(offset, offset + length); + node.usedBytes = length; + } else { + MEMFS.expandFileStorage(node, position+length); + // Use typed array write which is available. + node.contents.set(buffer.subarray(offset, offset + length), position); + node.usedBytes = Math.max(node.usedBytes, position + length); + } + return length; + }, + + llseek(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefs.SEEK_CUR }}}) { + position += stream.position; + } else if (whence === {{{ cDefs.SEEK_END }}}) { + if (FS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return position; + }, + mmap(stream, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if (!(flags & {{{ cDefs.MAP_PRIVATE }}}) && contents.buffer === HEAP8.buffer) { + // We can't emulate MAP_SHARED when the file is not backed by the + // buffer we're mapping to (e.g. the HEAP buffer). + allocated = false; + ptr = contents.byteOffset; + } else { + allocated = true; + ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError({{{ cDefs.ENOMEM }}}); + } + if (contents) { + // Try to avoid unnecessary slices. + if (position > 0 || position + length < contents.length) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call(contents, position, position + length); + } + } + HEAP8.set(contents, ptr); + } + } + return { ptr, allocated }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); + // should we check if bytesWritten and length are the same? + return 0; + } + } + } +}); + diff --git a/src/lib/libnodefs.js b/src/lib/libnodefs.js new file mode 100644 index 0000000000000..d3ba827f5a436 --- /dev/null +++ b/src/lib/libnodefs.js @@ -0,0 +1,320 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ +#if WASMFS + $NODEFS__deps: ['$stringToUTF8OnStack', 'wasmfs_create_node_backend'], + $NODEFS: { + createBackend(opts) { + return _wasmfs_create_node_backend(stringToUTF8OnStack(opts.root)); + } + } +#else + $NODEFS__deps: ['$FS', '$PATH', '$ERRNO_CODES', '$mmapAlloc'], + $NODEFS__postset: 'if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); }', + $NODEFS: { + isWindows: false, + staticInit() { + NODEFS.isWindows = !!process.platform.match(/^win/); + var flags = process.binding("constants")["fs"]; + NODEFS.flagsForNodeMap = { + "{{{ cDefs.O_APPEND }}}": flags["O_APPEND"], + "{{{ cDefs.O_CREAT }}}": flags["O_CREAT"], + "{{{ cDefs.O_EXCL }}}": flags["O_EXCL"], + "{{{ cDefs.O_NOCTTY }}}": flags["O_NOCTTY"], + "{{{ cDefs.O_RDONLY }}}": flags["O_RDONLY"], + "{{{ cDefs.O_RDWR }}}": flags["O_RDWR"], + "{{{ cDefs.O_DSYNC }}}": flags["O_SYNC"], + "{{{ cDefs.O_TRUNC }}}": flags["O_TRUNC"], + "{{{ cDefs.O_WRONLY }}}": flags["O_WRONLY"], + "{{{ cDefs.O_NOFOLLOW }}}": flags["O_NOFOLLOW"], + }; +#if ASSERTIONS + // The 0 define must match on both sides, as otherwise we would not + // know to add it. + assert(NODEFS.flagsForNodeMap["0"] === 0); +#endif + }, + convertNodeCode(e) { + var code = e.code; +#if ASSERTIONS + assert(code in ERRNO_CODES, `unexpected node error code: ${code} (${e})`); +#endif + return ERRNO_CODES[code]; + }, + tryFSOperation(f) { + try { + return f(); + } catch (e) { + if (!e.code) throw e; + // node under windows can return code 'UNKNOWN' here: + // https://github.com/emscripten-core/emscripten/issues/15468 + if (e.code === 'UNKNOWN') throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + throw new FS.ErrnoError(NODEFS.convertNodeCode(e)); + } + }, + mount(mount) { +#if ASSERTIONS + assert(ENVIRONMENT_IS_NODE); +#endif + return NODEFS.createNode(null, '/', NODEFS.getMode(mount.opts.root), 0); + }, + createNode(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = NODEFS.node_ops; + node.stream_ops = NODEFS.stream_ops; + return node; + }, + getMode(path) { + return NODEFS.tryFSOperation(() => { + var mode = fs.lstatSync(path).mode; + if (NODEFS.isWindows) { + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. + mode |= (mode & {{{ cDefs.S_IRUGO }}}) >> 2; + } + return mode; + }); + }, + realPath(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join(...parts); + }, + // This maps the integer permission modes from http://linux.die.net/man/3/open + // to node.js-specific file open permission strings at http://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback + flagsForNode(flags) { + flags &= ~{{{ cDefs.O_PATH }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~{{{ cDefs.O_NONBLOCK }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~{{{ cDefs.O_LARGEFILE }}}; // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~{{{ cDefs.O_CLOEXEC }}}; // Some applications may pass it; it makes no sense for a single process. + flags &= ~{{{ cDefs.O_DIRECTORY }}}; // Node.js doesn't need this passed in, it errors. + var newFlags = 0; + for (var k in NODEFS.flagsForNodeMap) { + if (flags & k) { + newFlags |= NODEFS.flagsForNodeMap[k]; + flags ^= k; + } + } + if (flags) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return newFlags; + }, + getattr(func, node) { + var stat = NODEFS.tryFSOperation(func); + if (NODEFS.isWindows) { + // node.js v0.10.20 doesn't report blksize and blocks on Windows. Fake + // them with default blksize of 4096. + // See http://support.microsoft.com/kb/140365 + if (!stat.blksize) { + stat.blksize = 4096; + } + if (!stat.blocks) { + stat.blocks = (stat.size+stat.blksize-1)/stat.blksize|0; + } + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. + stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2; + } + return { + dev: stat.dev, + ino: node.id, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks + }; + }, + // Common code for both node and stream setattr + // For node getattr: + // - arg is a native path + // - chmod, utimes, truncate are fs.chmodSync, fs.utimesSync, fs.truncateSync + // For stream getattr: + // - arg is a native file descriptor + // - chmod, utimes, truncate are fs.fchmodSync, fs.futimesSync, fs.ftruncateSync + setattr(arg, node, attr, chmod, utimes, truncate, stat) { + NODEFS.tryFSOperation(() => { + if (attr.mode !== undefined) { + var mode = attr.mode; + if (NODEFS.isWindows) { + // Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR) + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod + mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}}; + } + chmod(arg, mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (typeof (attr.atime ?? attr.mtime) === "number") { + // Unfortunately, we have to stat the current value if we don't want + // to change it. On top of that, since the times don't round trip + // this will only keep the value nearly unchanged not exactly + // unchanged. See: + // https://github.com/nodejs/node/issues/56492 + var atime = new Date(attr.atime ?? stat(arg).atime); + var mtime = new Date(attr.mtime ?? stat(arg).mtime); + utimes(arg, atime, mtime); + } + if (attr.size !== undefined) { + truncate(arg, attr.size); + } + }); + }, + node_ops: { + getattr(node) { + var path = NODEFS.realPath(node); + return NODEFS.getattr(() => fs.lstatSync(path), node); + }, + setattr(node, attr) { + var path = NODEFS.realPath(node); + if (attr.mode != null && attr.dontFollow) { + throw new FS.ErrnoError({{{ cDefs.ENOSYS }}}); + } + NODEFS.setattr(path, node, attr, fs.chmodSync, fs.utimesSync, fs.truncateSync, fs.lstatSync); + }, + lookup(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + var mode = NODEFS.getMode(path); + return NODEFS.createNode(parent, name, mode); + }, + mknod(parent, name, mode, dev) { + var node = NODEFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = NODEFS.realPath(node); + NODEFS.tryFSOperation(() => { + if (FS.isDir(node.mode)) { + fs.mkdirSync(path, node.mode); + } else { + fs.writeFileSync(path, '', { mode: node.mode }); + } + }); + return node; + }, + rename(oldNode, newDir, newName) { + var oldPath = NODEFS.realPath(oldNode); + var newPath = PATH.join2(NODEFS.realPath(newDir), newName); + try { + FS.unlink(newPath); + } catch(e) {} + NODEFS.tryFSOperation(() => fs.renameSync(oldPath, newPath)); + oldNode.name = newName; + }, + unlink(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + NODEFS.tryFSOperation(() => fs.unlinkSync(path)); + }, + rmdir(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + NODEFS.tryFSOperation(() => fs.rmdirSync(path)); + }, + readdir(node) { + var path = NODEFS.realPath(node); + return NODEFS.tryFSOperation(() => fs.readdirSync(path)); + }, + symlink(parent, newName, oldPath) { + var newPath = PATH.join2(NODEFS.realPath(parent), newName); + NODEFS.tryFSOperation(() => fs.symlinkSync(oldPath, newPath)); + }, + readlink(node) { + var path = NODEFS.realPath(node); + return NODEFS.tryFSOperation(() => fs.readlinkSync(path)); + }, + statfs(path) { + var stats = NODEFS.tryFSOperation(() => fs.statfsSync(path)); + // Node.js doesn't provide frsize (fragment size). Set it to bsize (block size) + // as they're often the same in many file systems. May not be accurate for all. + stats.frsize = stats.bsize; + return stats; + } + }, + stream_ops: { + getattr(stream) { + return NODEFS.getattr(() => fs.fstatSync(stream.nfd), stream.node); + }, + setattr(stream, attr) { + NODEFS.setattr(stream.nfd, stream.node, attr, fs.fchmodSync, fs.futimesSync, fs.ftruncateSync, fs.fstatSync); + }, + open(stream) { + var path = NODEFS.realPath(stream.node); + NODEFS.tryFSOperation(() => { + stream.shared.refcount = 1; + stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags)); + }); + }, + close(stream) { + NODEFS.tryFSOperation(() => { + if (stream.nfd && --stream.shared.refcount === 0) { + fs.closeSync(stream.nfd); + } + }); + }, + dup(stream) { + stream.shared.refcount++; + }, + read(stream, buffer, offset, length, position) { + return NODEFS.tryFSOperation(() => + fs.readSync(stream.nfd, buffer, offset, length, position) + ); + }, + write(stream, buffer, offset, length, position) { + return NODEFS.tryFSOperation(() => + fs.writeSync(stream.nfd, buffer, offset, length, position) + ); + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefs.SEEK_CUR }}}) { + position += stream.position; + } else if (whence === {{{ cDefs.SEEK_END }}}) { + if (FS.isFile(stream.node.mode)) { + NODEFS.tryFSOperation(() => { + var stat = fs.fstatSync(stream.nfd); + position += stat.size; + }); + } + } + + if (position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + + return position; + }, + mmap(stream, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); + } + + var ptr = mmapAlloc(length); + + NODEFS.stream_ops.read(stream, HEAP8, ptr, length, position); + return { ptr, allocated: true }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + NODEFS.stream_ops.write(stream, buffer, 0, length, offset, false); + // should we check if bytesWritten and length are the same? + return 0; + } + } + } +#endif +}); diff --git a/src/lib/libnodepath.js b/src/lib/libnodepath.js new file mode 100644 index 0000000000000..d891bf7339662 --- /dev/null +++ b/src/lib/libnodepath.js @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2022 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +// This implementation ensures that Windows-style paths are being +// used when running on a Windows operating system - see: +// https://nodejs.org/api/path.html#path_windows_vs_posix +// It's only used/needed when linking with `-sNODERAWFS`, as that +// will replace all normal filesystem access with direct Node.js +// operations. Hence, using `nodePath` should be safe here. + +addToLibrary({ + $nodePath: "require('node:path')", + $PATH__deps: ['$nodePath'], + $PATH: `{ + isAbs: nodePath.isAbsolute, + normalize: nodePath.normalize, + dirname: nodePath.dirname, + basename: nodePath.basename, + join: nodePath.join, + join2: nodePath.join, + }`, + // The FS-using parts are split out into a separate object, so simple path + // usage does not require the FS. + $PATH_FS__deps: ['$FS', '$nodePath'], + $PATH_FS__docs: '/** @type{{resolve: function(...*)}} */', + $PATH_FS: { + resolve: (...paths) => { + paths.unshift(FS.cwd()); + return nodePath.posix.resolve(...paths); + }, + relative: (from, to) => nodePath.posix.relative(from || FS.cwd(), to || FS.cwd()), + } +}); diff --git a/src/lib/libnoderawfs.js b/src/lib/libnoderawfs.js new file mode 100644 index 0000000000000..57387ad233674 --- /dev/null +++ b/src/lib/libnoderawfs.js @@ -0,0 +1,260 @@ +/** + * @license + * Copyright 2018 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $NODERAWFS__deps: ['$ERRNO_CODES', '$FS', '$NODEFS', '$mmapAlloc', '$FS_modeStringToFlags', '$NODERAWFS_stream_funcs'], + $NODERAWFS__postset: ` + if (!ENVIRONMENT_IS_NODE) { + throw new Error("NODERAWFS is currently only supported on Node.js environment.") + } + var nodeTTY = require('node:tty'); + function _wrapNodeError(func) { + return (...args) => { + try { + return func(...args) + } catch (e) { + // Hack for Deno which throws BadResource instead of EBADF: + // https://github.com/emscripten-core/emscripten/issues/26239 + if (e.name == 'BadResource') { + e.code = 'EBADF'; + } + if (e.code) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + throw e; + } + } + } + function _wrapNodeStreamFunc(func, vfs_func) { + return _wrapNodeError((stream, ...args) => { + if (stream.stream_ops) { + // this stream was created by some other FS. e.g: PIPEFS. + return vfs_func(stream, ...args); + } + return func(stream, ...args); + }); + } + // Use this to reference our in-memory filesystem + /** @suppress {partialAlias} */ + var VFS = {...FS}; + // Wrap the whole in-memory filesystem API with + // our Node.js based functions + for (const [key, value] of Object.entries(NODERAWFS)) { + FS[key] = _wrapNodeError(value); + } + for (const [key, value] of Object.entries(NODERAWFS_stream_funcs)) { + FS[key] = _wrapNodeStreamFunc(value, FS[key]); + }`, + $NODERAWFS: { + lookup(parent, name) { +#if ASSERTIONS + assert(parent) + assert(parent.path) +#endif + return FS.lookupPath(`${parent.path}/${name}`).node; + }, + lookupPath(path, opts = {}) { + if (opts.parent) { + path = PATH.dirname(path); + } + var st = fs.lstatSync(path); + var mode = NODEFS.getMode(path); + return { path, node: { id: st.ino, mode, node_ops: NODERAWFS, path }}; + }, + createStandardStreams() { + FS.createStream({ nfd: 0, position: 0, path: '/dev/stdin', flags: 0, seekable: false }, 0); + var paths = [,'/dev/stdout', '/dev/stderr']; + for (var i = 1; i < 3; i++) { + FS.createStream({ nfd: i, position: 0, path: paths[i], flags: {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}, seekable: false }, i); + } + }, + // generic function for all node creation + cwd() { return process.cwd(); }, + chdir(...args) { process.chdir(...args); }, + mknod(path, mode) { + if (FS.isDir(path)) { + fs.mkdirSync(path, mode); + } else { + fs.writeFileSync(path, '', { mode: mode }); + } + }, + mkdir(...args) { fs.mkdirSync(...args); }, + symlink(...args) { fs.symlinkSync(...args); }, + rename(...args) { fs.renameSync(...args); }, + rmdir(...args) { fs.rmdirSync(...args); }, + readdir(...args) { return ['.', '..'].concat(fs.readdirSync(...args)); }, + unlink(...args) { fs.unlinkSync(...args); }, + readlink(...args) { return fs.readlinkSync(...args); }, + stat(path, dontFollow) { + var stat = dontFollow ? fs.lstatSync(path) : fs.statSync(path); + if (NODEFS.isWindows) { + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. + stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2; + } + return stat; + }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + return fs.fstatSync(stream.nfd); + }, + statfs(path) { + // Node's fs.statfsSync API doesn't provide these attributes so include + // some defaults. + var defaults = { + fsid: 42, + flags: 2, + namelen: 255, + } + return Object.assign(defaults, fs.statfsSync(path)); + }, + statfsStream(stream) { + return FS.statfs(stream.path); + }, + chmod(path, mode, dontFollow) { + mode &= {{{ cDefs.S_IALLUGO }}}; + if (NODEFS.isWindows) { + // Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR) + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod + mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}}; + } + if (dontFollow && fs.lstatSync(path).isSymbolicLink()) { + // Node (and indeed linux) does not support chmod on symlinks + // https://nodejs.org/api/fs.html#fslchmodsyncpath-mode + throw new FS.ErrnoError({{{ cDefs.EOPNOTSUPP }}}); + } + fs.chmodSync(path, mode); + }, + fchmod(fd, mode) { + var stream = FS.getStreamChecked(fd); + fs.fchmodSync(stream.nfd, mode); + }, + chown(...args) { fs.chownSync(...args); }, + fchown(fd, owner, group) { + var stream = FS.getStreamChecked(fd); + fs.fchownSync(stream.nfd, owner, group); + }, + truncate(path, len) { + // See https://github.com/nodejs/node/issues/35632 + if (len < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + return fs.truncateSync(path, len); + }, + ftruncate(fd, len) { + // See https://github.com/nodejs/node/issues/35632 + if (len < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var stream = FS.getStreamChecked(fd); + fs.ftruncateSync(stream.nfd, len); + }, + utime(path, atime, mtime) { + // null here for atime or mtime means UTIME_OMIT was passed. Since node + // doesn't support this concept we need to first find the existing + // timestamps in order to preserve them. + if ((atime === null) || (mtime === null)) { + var st = fs.statSync(path); + atime ||= st.atimeMs; + mtime ||= st.mtimeMs; + } + fs.utimesSync(path, atime/1000, mtime/1000); + }, + open(path, flags, mode) { + flags = FS_modeStringToFlags(flags); + var pathTruncated = path.split('/').map((s) => s.slice(0, 255)).join('/'); + var nfd = fs.openSync(pathTruncated, NODEFS.flagsForNode(flags), mode); + var st = fs.fstatSync(nfd); + if (flags & {{{ cDefs.O_DIRECTORY }}} && !st.isDirectory()) { + fs.closeSync(nfd); + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + var newMode = NODEFS.getMode(pathTruncated); + var node = { id: st.ino, mode: newMode, node_ops: NODERAWFS, path } + return FS.createStream({ nfd, position: 0, path, flags, node, seekable: true }); + }, + createStream(stream, fd) { + // Call the original FS.createStream + var rtn = VFS.createStream(stream, fd); + // Detect PIPEFS streams and skip the refcnt/tty initialization in that case. + if (!stream.stream_ops) { + rtn.shared.refcnt ??= 0; + rtn.shared.refcnt++; + rtn.tty = nodeTTY.isatty(rtn.nfd); + } + return rtn; + }, + }, + + /** + * These functions all take a stream as the first argument which + * could either be a stream created by NODERAWFS itself, or, for example + * one created by PIPEFS. We wrap all these function in an extra check. + */ + $NODERAWFS_stream_funcs: { + close(stream) { + VFS.closeStream(stream.fd); + // Don't close stdin/stdout/stderr since they are used by node itself. + if (--stream.shared.refcnt <= 0 && stream.nfd > 2) { + // This stream is created by our Node.js filesystem, close the + // native file descriptor when its reference count drops to 0. + fs.closeSync(stream.nfd); + } + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefs.SEEK_CUR }}}) { + position += stream.position; + } else if (whence === {{{ cDefs.SEEK_END }}}) { + position += fs.fstatSync(stream.nfd).size; + } else if (whence !== {{{ cDefs.SEEK_SET }}}) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + + if (position < 0) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + stream.position = position; + return position; + }, + read(stream, buffer, offset, length, position) { + var seeking = typeof position != 'undefined'; + if (!seeking && stream.seekable) position = stream.position; + var bytesRead = fs.readSync(stream.nfd, buffer, offset, length, position); + // update position marker when non-seeking + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write(stream, buffer, offset, length, position) { + if (stream.flags & {{{ cDefs.O_APPEND }}}) { + // seek to the end before writing in append mode + FS.llseek(stream, 0, {{{ cDefs.SEEK_END }}}); + } + var seeking = typeof position != 'undefined'; + if (!seeking && stream.seekable) position = stream.position; + var bytesWritten = fs.writeSync(stream.nfd, buffer, offset, length, position); + // update position marker when non-seeking + if (!seeking) stream.position += bytesWritten; + return bytesWritten; + }, + mmap(stream, length, position, prot, flags) { + if (!length) { + throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); + } + var ptr = mmapAlloc(length); + FS.read(stream, HEAP8, ptr, length, position); + return { ptr, allocated: true }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + FS.write(stream, buffer, 0, length, offset); + // should we check if bytesWritten and length are the same? + return 0; + }, + ioctl(stream, cmd, arg) { + throw new FS.ErrnoError({{{ cDefs.ENOTTY }}}); + }, + }, +}); diff --git a/src/lib/libopenal.js b/src/lib/libopenal.js new file mode 100644 index 0000000000000..6142a67b41a62 --- /dev/null +++ b/src/lib/libopenal.js @@ -0,0 +1,4782 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +//'use strict'; + +var LibraryOpenAL = { + // ************************************************************************ + // ** INTERNALS + // ************************************************************************ + + $AL__deps: ['$MainLoop'], + $AL: { + // ------------------------------------------------------ + // -- Constants + // ------------------------------------------------------ + + QUEUE_INTERVAL: 25, + QUEUE_LOOKAHEAD: 100.0 / 1000.0, + + DEVICE_NAME: 'Emscripten OpenAL', + CAPTURE_DEVICE_NAME: 'Emscripten OpenAL capture', + + ALC_EXTENSIONS: { + // TODO: 'ALC_EXT_EFX': true, + 'ALC_EXT_capture': true, + 'ALC_SOFT_pause_device': true, + 'ALC_SOFT_HRTF': true + }, + AL_EXTENSIONS: { + 'AL_EXT_float32': true, + 'AL_SOFT_loop_points': true, + 'AL_SOFT_source_length': true, + 'AL_EXT_source_distance_model': true, + 'AL_SOFT_source_spatialize': true + }, + + // ------------------------------------------------------ + // -- ALC Fields + // ------------------------------------------------------ + + _alcErr: 0, + get alcErr() { + return this._alcErr; + }, + set alcErr(val) { + // Errors should not be overwritten by later errors until they are cleared by a query. + if (this._alcErr === {{{ cDefs.ALC_NO_ERROR }}} || val === {{{ cDefs.ALC_NO_ERROR }}}) { + this._alcErr = val; + } + }, + + deviceRefCounts: {}, + alcStringCache: {}, + paused: false, + + // ------------------------------------------------------ + // -- AL Fields + // ------------------------------------------------------ + + stringCache: {}, + contexts: {}, + currentCtx: null, + buffers: { + // The zero buffer is legal to use, so create a placeholder for it + '0': { + id: 0, + refCount: 0, + audioBuf: null, + frequency: 0, + bytesPerSample: 2, + channels: 1, + length: 0 + } + }, + paramArray: [], // Used to prevent allocating a new array for each param call + + _nextId: 1, + newId: () => AL.freeIds.length > 0 ? AL.freeIds.pop() : AL._nextId++, + freeIds: [], + + // ------------------------------------------------------ + // -- Mixing Logic + // ------------------------------------------------------ + + scheduleContextAudio: (ctx) => { + // If we are animating using the requestAnimationFrame method, then the main loop does not run when in the background. + // To give a perfect glitch-free audio stop when switching from foreground to background, we need to avoid updating + // audio altogether when in the background, so detect that case and kill audio buffer streaming if so. + if (MainLoop.timingMode === {{{ cDefs.EM_TIMING_RAF }}} && document['visibilityState'] != 'visible') { + return; + } + + for (var i in ctx.sources) { + AL.scheduleSourceAudio(ctx.sources[i]); + } + }, + + // This function is the core scheduler that queues web-audio buffers for output. + // src.bufQueue represents the abstract OpenAL buffer queue, which is traversed to schedule + // corresponding web-audio buffers. These buffers are stored in src.audioQueue, which + // represents the queue of buffers scheduled for physical playback. These two queues are + // distinct because of the differing semantics of OpenAL and web audio. Some changes + // to OpenAL parameters, such as pitch, may require the web audio queue to be flushed and rescheduled. + scheduleSourceAudio: (src, lookahead) => { + // See comment on scheduleContextAudio above. + if (MainLoop.timingMode === {{{ cDefs.EM_TIMING_RAF }}} && document['visibilityState'] != 'visible') { + return; + } + if (src.state !== {{{ cDefs.AL_PLAYING }}}) { + return; + } + + var currentTime = AL.updateSourceTime(src); + + var startTime = src.bufStartTime; + var startOffset = src.bufOffset; + var bufCursor = src.bufsProcessed; + + // Advance past any audio that is already scheduled + for (var i = 0; i < src.audioQueue.length; i++) { + var audioSrc = src.audioQueue[i]; + startTime = audioSrc._startTime + audioSrc._duration; + startOffset = 0.0; + bufCursor += audioSrc._skipCount + 1; + } + + if (!lookahead) { + lookahead = AL.QUEUE_LOOKAHEAD; + } + var lookaheadTime = currentTime + lookahead; + var skipCount = 0; + while (startTime < lookaheadTime) { + if (bufCursor >= src.bufQueue.length) { + if (src.looping) { + bufCursor %= src.bufQueue.length; + } else { + break; + } + } + + var buf = src.bufQueue[bufCursor % src.bufQueue.length]; + // If the buffer contains no data, skip it + if (buf.length === 0) { + skipCount++; + // If we've gone through the whole queue and everything is 0 length, just give up + if (skipCount === src.bufQueue.length) { + break; + } + } else { + var audioSrc = src.context.audioCtx.createBufferSource(); + audioSrc.buffer = buf.audioBuf; + audioSrc.playbackRate.value = src.playbackRate; + if (buf.audioBuf._loopStart || buf.audioBuf._loopEnd) { + audioSrc.loopStart = buf.audioBuf._loopStart; + audioSrc.loopEnd = buf.audioBuf._loopEnd; + } + + var duration = 0.0; + // If the source is a looping static buffer, use native looping for gapless playback + if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { + duration = Number.POSITIVE_INFINITY; + audioSrc.loop = true; + if (buf.audioBuf._loopStart) { + audioSrc.loopStart = buf.audioBuf._loopStart; + } + if (buf.audioBuf._loopEnd) { + audioSrc.loopEnd = buf.audioBuf._loopEnd; + } + } else { + duration = (buf.audioBuf.duration - startOffset) / src.playbackRate; + } + + audioSrc._startOffset = startOffset; + audioSrc._duration = duration; + audioSrc._skipCount = skipCount; + skipCount = 0; + + audioSrc.connect(src.gain); + + if (typeof audioSrc.start != 'undefined') { + // Sample the current time as late as possible to mitigate drift + startTime = Math.max(startTime, src.context.audioCtx.currentTime); + audioSrc.start(startTime, startOffset); + } else if (typeof audioSrc.noteOn != 'undefined') { + startTime = Math.max(startTime, src.context.audioCtx.currentTime); + audioSrc.noteOn(startTime); +#if OPENAL_DEBUG + if (offset > 0.0) { + warnOnce('The current browser does not support AudioBufferSourceNode.start(when, offset); method, so cannot play back audio with an offset '+startOffset+' secs! Audio glitches will occur!'); + } +#endif + } +#if OPENAL_DEBUG + else { + warnOnce('Unable to start AudioBufferSourceNode playback! Not supported by the browser?'); + } + + dbg(`scheduleSourceAudio() queuing buffer ${buf.id} for source ${src.id} at ${startTime} (offset by ${startOffset})`); +#endif + audioSrc._startTime = startTime; + src.audioQueue.push(audioSrc); + + startTime += duration; + } + + startOffset = 0.0; + bufCursor++; + } + }, + + // Advance the state of a source forward to the current time + updateSourceTime: (src) => { + var currentTime = src.context.audioCtx.currentTime; + if (src.state !== {{{ cDefs.AL_PLAYING }}}) { + return currentTime; + } + + // if the start time is unset, determine it based on the current offset. + // This will be the case when a source is resumed after being paused, and + // allows us to pretend that the source actually started playing some time + // in the past such that it would just now have reached the stored offset. + if (!isFinite(src.bufStartTime)) { + src.bufStartTime = currentTime - src.bufOffset / src.playbackRate; + src.bufOffset = 0.0; + } + + var nextStartTime = 0.0; + while (src.audioQueue.length) { + var audioSrc = src.audioQueue[0]; + src.bufsProcessed += audioSrc._skipCount; + nextStartTime = audioSrc._startTime + audioSrc._duration; // n.b. audioSrc._duration already factors in playbackRate, so no divide by src.playbackRate on it. + + if (currentTime < nextStartTime) { + break; + } + + src.audioQueue.shift(); + src.bufStartTime = nextStartTime; + src.bufOffset = 0.0; + src.bufsProcessed++; + } + + if (src.bufsProcessed >= src.bufQueue.length && !src.looping) { + // The source has played its entire queue and is non-looping, so just mark it as stopped. + AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); + } else if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { + // If the source is a looping static buffer, determine the buffer offset based on the loop points + var buf = src.bufQueue[0]; + if (buf.length === 0) { + src.bufOffset = 0.0; + } else { + var delta = (currentTime - src.bufStartTime) * src.playbackRate; + var loopStart = buf.audioBuf._loopStart || 0.0; + var loopEnd = buf.audioBuf._loopEnd || buf.audioBuf.duration; + if (loopEnd <= loopStart) { + loopEnd = buf.audioBuf.duration; + } + + if (delta < loopEnd) { + src.bufOffset = delta; + } else { + src.bufOffset = loopStart + (delta - loopStart) % (loopEnd - loopStart); + } + } + } else if (src.audioQueue[0]) { + // The source is still actively playing, so we just need to calculate where we are in the current buffer + // so it can be remembered if the source gets paused. + src.bufOffset = (currentTime - src.audioQueue[0]._startTime) * src.playbackRate; + } else { + // The source hasn't finished yet, but there is no scheduled audio left for it. This can be because + // the source has just been started/resumed, or due to an underrun caused by a long blocking operation. + // We need to determine what state we would be in by this point in time so that when we next schedule + // audio playback, it will be just as if no underrun occurred. + + if (src.type !== {{{ cDefs.AL_STATIC }}} && src.looping) { + // if the source is a looping buffer queue, let's first calculate the queue duration, so we can + // quickly fast forward past any full loops of the queue and only worry about the remainder. + var srcDuration = AL.sourceDuration(src) / src.playbackRate; + if (srcDuration > 0.0) { + src.bufStartTime += Math.floor((currentTime - src.bufStartTime) / srcDuration) * srcDuration; + } + } + + // Since we've already skipped any full-queue loops if there were any, we just need to find + // out where in the queue the remaining time puts us, which won't require stepping through the + // entire queue more than once. + for (var i = 0; i < src.bufQueue.length; i++) { + if (src.bufsProcessed >= src.bufQueue.length) { + if (src.looping) { + src.bufsProcessed %= src.bufQueue.length; + } else { + AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); + break; + } + } + + var buf = src.bufQueue[src.bufsProcessed]; + if (buf.length > 0) { + nextStartTime = src.bufStartTime + buf.audioBuf.duration / src.playbackRate; + + if (currentTime < nextStartTime) { + src.bufOffset = (currentTime - src.bufStartTime) * src.playbackRate; + break; + } + + src.bufStartTime = nextStartTime; + } + + src.bufOffset = 0.0; + src.bufsProcessed++; + } + } + + return currentTime; + }, + + cancelPendingSourceAudio: (src) => { + AL.updateSourceTime(src); + + for (var i = 1; i < src.audioQueue.length; i++) { + var audioSrc = src.audioQueue[i]; + audioSrc.stop(); + } + + if (src.audioQueue.length > 1) { + src.audioQueue.length = 1; + } + }, + + stopSourceAudio: (src) => { + for (var i = 0; i < src.audioQueue.length; i++) { + src.audioQueue[i].stop(); + } + src.audioQueue.length = 0; + }, + + setSourceState: (src, state) => { + if (state === {{{ cDefs.AL_PLAYING }}}) { + if (src.state === {{{ cDefs.AL_PLAYING }}} || src.state == {{{ cDefs.AL_STOPPED }}}) { + src.bufsProcessed = 0; + src.bufOffset = 0.0; +#if OPENAL_DEBUG + dbg(`setSourceState() resetting and playing source ${src.id}`); +#endif + } else { +#if OPENAL_DEBUG + dbg(`setSourceState() playing source ${src.id} at ${src.bufOffset}`); +#endif + } + + AL.stopSourceAudio(src); + + src.state = {{{ cDefs.AL_PLAYING }}}; + src.bufStartTime = Number.NEGATIVE_INFINITY; + AL.scheduleSourceAudio(src); + } else if (state === {{{ cDefs.AL_PAUSED }}}) { + if (src.state === {{{ cDefs.AL_PLAYING }}}) { + // Store off the current offset to restore with on resume. + AL.updateSourceTime(src); + AL.stopSourceAudio(src); + + src.state = {{{ cDefs.AL_PAUSED }}}; +#if OPENAL_DEBUG + dbg(`setSourceState() pausing source ${src.id} at ${src.bufOffset}`); +#endif + } + } else if (state === {{{ cDefs.AL_STOPPED }}}) { + if (src.state !== {{{ cDefs.AL_INITIAL }}}) { + src.state = {{{ cDefs.AL_STOPPED }}}; + src.bufsProcessed = src.bufQueue.length; + src.bufStartTime = Number.NEGATIVE_INFINITY; + src.bufOffset = 0.0; + AL.stopSourceAudio(src); +#if OPENAL_DEBUG + dbg(`setSourceState() stopping source ${src.id}`); +#endif + } + } else if (state === {{{ cDefs.AL_INITIAL }}}) { + if (src.state !== {{{ cDefs.AL_INITIAL }}}) { + src.state = {{{ cDefs.AL_INITIAL }}}; + src.bufsProcessed = 0; + src.bufStartTime = Number.NEGATIVE_INFINITY; + src.bufOffset = 0.0; + AL.stopSourceAudio(src); +#if OPENAL_DEBUG + dbg(`setSourceState() initializing source ${src.id}`); +#endif + } + } + }, + + initSourcePanner: (src) => { + if (src.type === 0x1030 /* AL_UNDETERMINED */) { + return; + } + + // Find the first non-zero buffer in the queue to determine the proper format + var templateBuf = AL.buffers[0]; + for (var i = 0; i < src.bufQueue.length; i++) { + if (src.bufQueue[i].id !== 0) { + templateBuf = src.bufQueue[i]; + break; + } + } + // Create a panner if AL_SOURCE_SPATIALIZE_SOFT is set to true, or alternatively if it's set to auto and the source is mono + if (src.spatialize === {{{ cDefs.AL_TRUE }}} || (src.spatialize === 2 /* AL_AUTO_SOFT */ && templateBuf.channels === 1)) { + if (src.panner) { + return; + } + src.panner = src.context.audioCtx.createPanner(); + + AL.updateSourceGlobal(src); + AL.updateSourceSpace(src); + + src.panner.connect(src.context.gain); + src.gain.disconnect(); + src.gain.connect(src.panner); + } else { + if (!src.panner) { + return; + } + + src.panner.disconnect(); + src.gain.disconnect(); + src.gain.connect(src.context.gain); + src.panner = null; + } + }, + + updateContextGlobal: (ctx) => { + for (var i in ctx.sources) { + AL.updateSourceGlobal(ctx.sources[i]); + } + }, + + updateSourceGlobal: (src) => { + var panner = src.panner; + if (!panner) { + return; + } + + panner.refDistance = src.refDistance; + panner.maxDistance = src.maxDistance; + panner.rolloffFactor = src.rolloffFactor; + + panner.panningModel = src.context.hrtf ? 'HRTF' : 'equalpower'; + + // Use the source's distance model if AL_SOURCE_DISTANCE_MODEL is enabled + var distanceModel = src.context.sourceDistanceModel ? src.distanceModel : src.context.distanceModel; + switch (distanceModel) { + case {{{ cDefs.AL_NONE }}}: + panner.distanceModel = 'inverse'; + panner.refDistance = 3.40282e38 /* FLT_MAX */; + break; + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + panner.distanceModel = 'inverse'; + break; + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + panner.distanceModel = 'linear'; + break; + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + panner.distanceModel = 'exponential'; + break; + } + }, + + updateListenerSpace: (ctx) => { + var listener = ctx.audioCtx.listener; + if (listener.positionX) { + listener.positionX.value = ctx.listener.position[0]; + listener.positionY.value = ctx.listener.position[1]; + listener.positionZ.value = ctx.listener.position[2]; + } else { +#if OPENAL_DEBUG + warnOnce('Listener position attributes are not present, falling back to setPosition()'); +#endif + listener.setPosition(ctx.listener.position[0], ctx.listener.position[1], ctx.listener.position[2]); + } + if (listener.forwardX) { + listener.forwardX.value = ctx.listener.direction[0]; + listener.forwardY.value = ctx.listener.direction[1]; + listener.forwardZ.value = ctx.listener.direction[2]; + listener.upX.value = ctx.listener.up[0]; + listener.upY.value = ctx.listener.up[1]; + listener.upZ.value = ctx.listener.up[2]; + } else { +#if OPENAL_DEBUG + warnOnce('Listener orientation attributes are not present, falling back to setOrientation()'); +#endif + listener.setOrientation( + ctx.listener.direction[0], ctx.listener.direction[1], ctx.listener.direction[2], + ctx.listener.up[0], ctx.listener.up[1], ctx.listener.up[2]); + } + + // Update sources that are relative to the listener + for (var i in ctx.sources) { + AL.updateSourceSpace(ctx.sources[i]); + } + }, + + updateSourceSpace: (src) => { + if (!src.panner) { + return; + } + var panner = src.panner; + + var posX = src.position[0]; + var posY = src.position[1]; + var posZ = src.position[2]; + var dirX = src.direction[0]; + var dirY = src.direction[1]; + var dirZ = src.direction[2]; + + var listener = src.context.listener; + var lPosX = listener.position[0]; + var lPosY = listener.position[1]; + var lPosZ = listener.position[2]; + + // WebAudio does spatialization in world-space coordinates, meaning both the buffer sources and + // the listener position are in the same absolute coordinate system relative to a fixed origin. + // By default, OpenAL works this way as well, but it also provides a "listener relative" mode, where + // a buffer source's coordinates are interpreted not in absolute world space, but as being relative + // to the listener object itself, so as the listener moves the source appears to move with it + // with no update required. Since web audio does not support this mode, we must transform the source + // coordinates from listener-relative space to absolute world space. + // + // We do this via affine transformation matrices applied to the source position and source direction. + // A change-of-basis converts from listener-space displacements to world-space displacements, + // which must be done for both the source position and direction. Lastly, the source position must be + // added to the listener position to get the final source position, since the source position represents + // a displacement from the listener. + if (src.relative) { + // Negate the listener direction since forward is -Z. + var lBackX = -listener.direction[0]; + var lBackY = -listener.direction[1]; + var lBackZ = -listener.direction[2]; + var lUpX = listener.up[0]; + var lUpY = listener.up[1]; + var lUpZ = listener.up[2]; + + var inverseMagnitude = (x, y, z) => { + var length = Math.sqrt(x * x + y * y + z * z); + + if (length < Number.EPSILON) { + return 0.0; + } + + return 1.0 / length; + }; + + // Normalize the Back vector + var invMag = inverseMagnitude(lBackX, lBackY, lBackZ); + lBackX *= invMag; + lBackY *= invMag; + lBackZ *= invMag; + + // ...and the Up vector + invMag = inverseMagnitude(lUpX, lUpY, lUpZ); + lUpX *= invMag; + lUpY *= invMag; + lUpZ *= invMag; + + // Calculate the Right vector as the cross product of the Up and Back vectors + var lRightX = (lUpY * lBackZ - lUpZ * lBackY); + var lRightY = (lUpZ * lBackX - lUpX * lBackZ); + var lRightZ = (lUpX * lBackY - lUpY * lBackX); + + // Back and Up might not be exactly perpendicular, so the cross product also needs normalization + invMag = inverseMagnitude(lRightX, lRightY, lRightZ); + lRightX *= invMag; + lRightY *= invMag; + lRightZ *= invMag; + + // Recompute Up from the now orthonormal Right and Back vectors so we have a fully orthonormal basis + lUpX = (lBackY * lRightZ - lBackZ * lRightY); + lUpY = (lBackZ * lRightX - lBackX * lRightZ); + lUpZ = (lBackX * lRightY - lBackY * lRightX); + + var oldX = dirX; + var oldY = dirY; + var oldZ = dirZ; + + // Use our 3 vectors to apply a change-of-basis matrix to the source direction + dirX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; + dirY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; + dirZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; + + oldX = posX; + oldY = posY; + oldZ = posZ; + + // ...and to the source position + posX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; + posY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; + posZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; + + // The change-of-basis corrects the orientation, but the origin is still the listener. + // Translate the source position by the listener position to finish. + posX += lPosX; + posY += lPosY; + posZ += lPosZ; + } + + if (panner.positionX) { + // Assigning to panner.positionX/Y/Z unnecessarily seems to cause performance issues + // See https://github.com/emscripten-core/emscripten/issues/15847 + + if (posX != panner.positionX.value) panner.positionX.value = posX; + if (posY != panner.positionY.value) panner.positionY.value = posY; + if (posZ != panner.positionZ.value) panner.positionZ.value = posZ; + } else { +#if OPENAL_DEBUG + warnOnce('Panner position attributes are not present, falling back to setPosition()'); +#endif + panner.setPosition(posX, posY, posZ); + } + if (panner.orientationX) { + // Assigning to panner.orientation/Y/Z unnecessarily seems to cause performance issues + // See https://github.com/emscripten-core/emscripten/issues/15847 + + if (dirX != panner.orientationX.value) panner.orientationX.value = dirX; + if (dirY != panner.orientationY.value) panner.orientationY.value = dirY; + if (dirZ != panner.orientationZ.value) panner.orientationZ.value = dirZ; + } else { +#if OPENAL_DEBUG + warnOnce('Panner orientation attributes are not present, falling back to setOrientation()'); +#endif + panner.setOrientation(dirX, dirY, dirZ); + } + + var oldShift = src.dopplerShift; + var velX = src.velocity[0]; + var velY = src.velocity[1]; + var velZ = src.velocity[2]; + var lVelX = listener.velocity[0]; + var lVelY = listener.velocity[1]; + var lVelZ = listener.velocity[2]; + if (posX === lPosX && posY === lPosY && posZ === lPosZ + || velX === lVelX && velY === lVelY && velZ === lVelZ) + { + src.dopplerShift = 1.0; + } else { + // Doppler algorithm from 1.1 spec + var speedOfSound = src.context.speedOfSound; + var dopplerFactor = src.context.dopplerFactor; + + var slX = lPosX - posX; + var slY = lPosY - posY; + var slZ = lPosZ - posZ; + + var magSl = Math.sqrt(slX * slX + slY * slY + slZ * slZ); + var vls = (slX * lVelX + slY * lVelY + slZ * lVelZ) / magSl; + var vss = (slX * velX + slY * velY + slZ * velZ) / magSl; + + vls = Math.min(vls, speedOfSound / dopplerFactor); + vss = Math.min(vss, speedOfSound / dopplerFactor); + + src.dopplerShift = (speedOfSound - dopplerFactor * vls) / (speedOfSound - dopplerFactor * vss); + } + if (src.dopplerShift !== oldShift) { + AL.updateSourceRate(src); + } + }, + + updateSourceRate: (src) => { + if (src.state === {{{ cDefs.AL_PLAYING }}}) { + // clear scheduled buffers + AL.cancelPendingSourceAudio(src); + + var audioSrc = src.audioQueue[0]; + if (!audioSrc) { + return; // It is possible that AL.scheduleContextAudio() has not yet fed the next buffer, if so, skip. + } + + var duration; + if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { + duration = Number.POSITIVE_INFINITY; + } else { + // audioSrc._duration is expressed after factoring in playbackRate, so when changing playback rate, need + // to recompute/rescale the rate to the new playback speed. + duration = (audioSrc.buffer.duration - audioSrc._startOffset) / src.playbackRate; + } + + audioSrc._duration = duration; + audioSrc.playbackRate.value = src.playbackRate; + + // reschedule buffers with the new playbackRate + AL.scheduleSourceAudio(src); + } + }, + + sourceDuration: (src) => { + var length = 0.0; + for (var i = 0; i < src.bufQueue.length; i++) { + var audioBuf = src.bufQueue[i].audioBuf; + length += audioBuf ? audioBuf.duration : 0.0; + } + return length; + }, + + sourceTell: (src) => { + AL.updateSourceTime(src); + + var offset = 0.0; + for (var i = 0; i < src.bufsProcessed; i++) { + if (src.bufQueue[i].audioBuf) { + offset += src.bufQueue[i].audioBuf.duration; + } + } + offset += src.bufOffset; + + return offset; + }, + + sourceSeek: (src, offset) => { + var playing = src.state == {{{ cDefs.AL_PLAYING }}}; + if (playing) { + AL.setSourceState(src, {{{ cDefs.AL_INITIAL }}}); + } + + if (src.bufQueue[src.bufsProcessed].audioBuf !== null) { + src.bufsProcessed = 0; + while (offset > src.bufQueue[src.bufsProcessed].audioBuf.duration) { + offset -= src.bufQueue[src.bufsProcessed].audioBuf.duration; + src.bufsProcessed++; + } + + src.bufOffset = offset; + } + + if (playing) { + AL.setSourceState(src, {{{ cDefs.AL_PLAYING }}}); + } + }, + + // ------------------------------------------------------ + // -- Accessor Helpers + // ------------------------------------------------------ + + getGlobalParam: (funcname, param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return null; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + return AL.currentCtx.dopplerFactor; + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + return AL.currentCtx.speedOfSound; + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return AL.currentCtx.distanceModel; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return null; + } + }, + + setGlobalParam: (funcname, param, value) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + if (!Number.isFinite(value) || value < 0.0) { // Strictly negative values are disallowed +#if OPENAL_DEBUG + dbg(`${funcname}() value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.currentCtx.dopplerFactor = value; + AL.updateListenerSpace(AL.currentCtx); + break; + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + if (!Number.isFinite(value) || value <= 0.0) { // Negative or zero values are disallowed +#if OPENAL_DEBUG + dbg(`${funcname}() value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.currentCtx.speedOfSound = value; + AL.updateListenerSpace(AL.currentCtx); + break; + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + switch (value) { + case {{{ cDefs.AL_NONE }}}: + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + AL.currentCtx.distanceModel = value; + AL.updateContextGlobal(AL.currentCtx); + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + getListenerParam: (funcname, param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return null; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + return AL.currentCtx.listener.position; + case {{{ cDefs.AL_VELOCITY }}}: + return AL.currentCtx.listener.velocity; + case {{{ cDefs.AL_ORIENTATION }}}: + return AL.currentCtx.listener.direction.concat(AL.currentCtx.listener.up); + case {{{ cDefs.AL_GAIN }}}: + return AL.currentCtx.gain.gain.value; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return null; + } + }, + + setListenerParam: (funcname, param, value) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return; + } + if (value === null) { +#if OPENAL_DEBUG + dbg(`${funcname}(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + + var listener = AL.currentCtx.listener; + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_POSITION value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + listener.position[0] = value[0]; + listener.position[1] = value[1]; + listener.position[2] = value[2]; + AL.updateListenerSpace(AL.currentCtx); + break; + case {{{ cDefs.AL_VELOCITY }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_VELOCITY value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + listener.velocity[0] = value[0]; + listener.velocity[1] = value[1]; + listener.velocity[2] = value[2]; + AL.updateListenerSpace(AL.currentCtx); + break; + case {{{ cDefs.AL_GAIN }}}: + if (!Number.isFinite(value) || value < 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_GAIN value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.currentCtx.gain.gain.value = value; + break; + case {{{ cDefs.AL_ORIENTATION }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2]) + || !Number.isFinite(value[3]) || !Number.isFinite(value[4]) || !Number.isFinite(value[5]) + ) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_ORIENTATION value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + listener.direction[0] = value[0]; + listener.direction[1] = value[1]; + listener.direction[2] = value[2]; + listener.up[0] = value[3]; + listener.up[1] = value[4]; + listener.up[2] = value[5]; + AL.updateListenerSpace(AL.currentCtx); + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + getBufferParam: (funcname, bufferId, param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return; + } + var buf = AL.buffers[bufferId]; + if (!buf || bufferId === 0) { +#if OPENAL_DEBUG + dbg(`${funcname}() called with an invalid buffer`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + return buf.frequency; + case 0x2002 /* AL_BITS */: + return buf.bytesPerSample * 8; + case 0x2003 /* AL_CHANNELS */: + return buf.channels; + case 0x2004 /* AL_SIZE */: + return buf.length * buf.bytesPerSample * buf.channels; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + if (buf.length === 0) { + return [0, 0]; + } + return [ + (buf.audioBuf._loopStart || 0.0) * buf.frequency, + (buf.audioBuf._loopEnd || buf.length) * buf.frequency + ]; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return null; + } + }, + + setBufferParam: (funcname, bufferId, param, value) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return; + } + var buf = AL.buffers[bufferId]; + if (!buf || bufferId === 0) { +#if OPENAL_DEBUG + dbg(`${funcname}() called with an invalid buffer`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + if (value === null) { +#if OPENAL_DEBUG + dbg(`${funcname}(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + + switch (param) { + case 0x2004 /* AL_SIZE */: + if (value !== 0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_SIZE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + // Per the spec, setting AL_SIZE to 0 is a legal NOP. + break; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + if (value[0] < 0 || value[0] > buf.length || value[1] < 0 || value[1] > buf.Length || value[0] >= value[1]) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_LOOP_POINTS_SOFT value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + if (buf.refCount > 0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_LOOP_POINTS_SOFT set on bound buffer`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + return; + } + + if (buf.audioBuf) { + buf.audioBuf._loopStart = value[0] / buf.frequency; + buf.audioBuf._loopEnd = value[1] / buf.frequency; + } + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)}' is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + getSourceParam: (funcname, sourceId, param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return null; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg(`${funcname}() called with an invalid source`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return null; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + return src.relative; + case 0x1001 /* AL_CONE_INNER_ANGLE */: + return src.coneInnerAngle; + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + return src.coneOuterAngle; + case 0x1003 /* AL_PITCH */: + return src.pitch; + case {{{ cDefs.AL_POSITION }}}: + return src.position; + case {{{ cDefs.AL_DIRECTION }}}: + return src.direction; + case {{{ cDefs.AL_VELOCITY }}}: + return src.velocity; + case 0x1007 /* AL_LOOPING */: + return src.looping; + case 0x1009 /* AL_BUFFER */: + if (src.type === {{{ cDefs.AL_STATIC }}}) { + return src.bufQueue[0].id; + } + return 0; + case {{{ cDefs.AL_GAIN }}}: + return src.gain.gain.value; + case 0x100D /* AL_MIN_GAIN */: + return src.minGain; + case 0x100E /* AL_MAX_GAIN */: + return src.maxGain; + case 0x1010 /* AL_SOURCE_STATE */: + return src.state; + case 0x1015 /* AL_BUFFERS_QUEUED */: + if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { + return 0; + } + return src.bufQueue.length; + case 0x1016 /* AL_BUFFERS_PROCESSED */: + if ((src.bufQueue.length === 1 && src.bufQueue[0].id === 0) || src.looping) { + return 0; + } + return src.bufsProcessed; + case 0x1020 /* AL_REFERENCE_DISTANCE */: + return src.refDistance; + case 0x1021 /* AL_ROLLOFF_FACTOR */: + return src.rolloffFactor; + case 0x1022 /* AL_CONE_OUTER_GAIN */: + return src.coneOuterGain; + case 0x1023 /* AL_MAX_DISTANCE */: + return src.maxDistance; + case 0x1024 /* AL_SEC_OFFSET */: + return AL.sourceTell(src); + case 0x1025 /* AL_SAMPLE_OFFSET */: + var offset = AL.sourceTell(src); + if (offset > 0.0) { + offset *= src.bufQueue[0].frequency; + } + return offset; + case 0x1026 /* AL_BYTE_OFFSET */: + var offset = AL.sourceTell(src); + if (offset > 0.0) { + offset *= src.bufQueue[0].frequency * src.bufQueue[0].bytesPerSample; + } + return offset; + case 0x1027 /* AL_SOURCE_TYPE */: + return src.type; + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + return src.spatialize; + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + var length = 0; + var bytesPerFrame = 0; + for (var i = 0; i < src.bufQueue.length; i++) { + length += src.bufQueue[i].length; + if (src.bufQueue[i].id !== 0) { + bytesPerFrame = src.bufQueue[i].bytesPerSample * src.bufQueue[i].channels; + } + } + return length * bytesPerFrame; + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + var length = 0; + for (var i = 0; i < src.bufQueue.length; i++) { + length += src.bufQueue[i].length; + } + return length; + case 0x200B /* AL_SEC_LENGTH_SOFT */: + return AL.sourceDuration(src); + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return src.distanceModel; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)}' is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return null; + } + }, + + setSourceParam: (funcname, sourceId, param, value) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg(`${funcname}() called without a valid context`); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourcef() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + if (value === null) { +#if OPENAL_DEBUG + dbg(`${funcname}(): param ${ptrToString(param)}' has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + if (value === {{{ cDefs.AL_TRUE }}}) { + src.relative = true; + AL.updateSourceSpace(src); + } else if (value === {{{ cDefs.AL_FALSE }}}) { + src.relative = false; + AL.updateSourceSpace(src); + } else { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_SOURCE_RELATIVE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + break; + case 0x1001 /* AL_CONE_INNER_ANGLE */: + if (!Number.isFinite(value)) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_CONE_INNER_ANGLE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.coneInnerAngle = value; + if (src.panner) { + src.panner.coneInnerAngle = value % 360.0; + } + break; + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + if (!Number.isFinite(value)) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_CONE_OUTER_ANGLE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.coneOuterAngle = value; + if (src.panner) { + src.panner.coneOuterAngle = value % 360.0; + } + break; + case 0x1003 /* AL_PITCH */: + if (!Number.isFinite(value) || value <= 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_PITCH value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + if (src.pitch === value) { + break; + } + + src.pitch = value; + AL.updateSourceRate(src); + break; + case {{{ cDefs.AL_POSITION }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_POSITION value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.position[0] = value[0]; + src.position[1] = value[1]; + src.position[2] = value[2]; + AL.updateSourceSpace(src); + break; + case {{{ cDefs.AL_DIRECTION }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_DIRECTION value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.direction[0] = value[0]; + src.direction[1] = value[1]; + src.direction[2] = value[2]; + AL.updateSourceSpace(src); + break; + case {{{ cDefs.AL_VELOCITY }}}: + if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_VELOCITY value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.velocity[0] = value[0]; + src.velocity[1] = value[1]; + src.velocity[2] = value[2]; + AL.updateSourceSpace(src); + break; + case 0x1007 /* AL_LOOPING */: + if (value === {{{ cDefs.AL_TRUE }}}) { + src.looping = true; + AL.updateSourceTime(src); + if (src.type === {{{ cDefs.AL_STATIC }}} && src.audioQueue.length > 0) { + var audioSrc = src.audioQueue[0]; + audioSrc.loop = true; + audioSrc._duration = Number.POSITIVE_INFINITY; + } + } else if (value === {{{ cDefs.AL_FALSE }}}) { + src.looping = false; + var currentTime = AL.updateSourceTime(src); + if (src.type === {{{ cDefs.AL_STATIC }}} && src.audioQueue.length > 0) { + var audioSrc = src.audioQueue[0]; + audioSrc.loop = false; + audioSrc._duration = src.bufQueue[0].audioBuf.duration / src.playbackRate; + audioSrc._startTime = currentTime - src.bufOffset / src.playbackRate; + } + } else { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_LOOPING value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + break; + case 0x1009 /* AL_BUFFER */: + if (src.state === {{{ cDefs.AL_PLAYING }}} || src.state === {{{ cDefs.AL_PAUSED }}}) { +#if OPENAL_DEBUG + dbg(`${funcname}(AL_BUFFER) called while source is playing or paused`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + return; + } + + if (value === 0) { + for (var i in src.bufQueue) { + src.bufQueue[i].refCount--; + } + src.bufQueue.length = 1; + src.bufQueue[0] = AL.buffers[0]; + + src.bufsProcessed = 0; + src.type = 0x1030 /* AL_UNDETERMINED */; + } else { + var buf = AL.buffers[value]; + if (!buf) { +#if OPENAL_DEBUG + dbg('alSourcei(AL_BUFFER) called with an invalid buffer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + for (var i in src.bufQueue) { + src.bufQueue[i].refCount--; + } + src.bufQueue.length = 0; + + buf.refCount++; + src.bufQueue = [buf]; + src.bufsProcessed = 0; + src.type = {{{ cDefs.AL_STATIC }}}; + } + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + break; + case {{{ cDefs.AL_GAIN }}}: + if (!Number.isFinite(value) || value < 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_GAIN value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + src.gain.gain.value = value; + break; + case 0x100D /* AL_MIN_GAIN */: + if (!Number.isFinite(value) || value < 0.0 || value > Math.min(src.maxGain, 1.0)) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_MIN_GAIN value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } +#if OPENAL_DEBUG + warnOnce('AL_MIN_GAIN is not currently supported'); +#endif + src.minGain = value; + break; + case 0x100E /* AL_MAX_GAIN */: + if (!Number.isFinite(value) || value < Math.max(0.0, src.minGain) || value > 1.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_MAX_GAIN value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } +#if OPENAL_DEBUG + warnOnce('AL_MAX_GAIN is not currently supported'); +#endif + src.maxGain = value; + break; + case 0x1020 /* AL_REFERENCE_DISTANCE */: + if (!Number.isFinite(value) || value < 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_REFERENCE_DISTANCE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + src.refDistance = value; + if (src.panner) { + src.panner.refDistance = value; + } + break; + case 0x1021 /* AL_ROLLOFF_FACTOR */: + if (!Number.isFinite(value) || value < 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_ROLLOFF_FACTOR value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + src.rolloffFactor = value; + if (src.panner) { + src.panner.rolloffFactor = value; + } + break; + case 0x1022 /* AL_CONE_OUTER_GAIN */: + if (!Number.isFinite(value) || value < 0.0 || value > 1.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_CORE_OUTER_GAIN value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + src.coneOuterGain = value; + if (src.panner) { + src.panner.coneOuterGain = value; + } + break; + case 0x1023 /* AL_MAX_DISTANCE */: + if (!Number.isFinite(value) || value < 0.0) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_MAX_DISTANCE value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + src.maxDistance = value; + if (src.panner) { + src.panner.maxDistance = value; + } + break; + case 0x1024 /* AL_SEC_OFFSET */: + if (value < 0.0 || value > AL.sourceDuration(src)) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_SEC_OFFSET value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1025 /* AL_SAMPLE_OFFSET */: + var srcLen = AL.sourceDuration(src); + if (srcLen > 0.0) { + var frequency; + for (var bufId in src.bufQueue) { + if (bufId) { + frequency = src.bufQueue[bufId].frequency; + break; + } + } + value /= frequency; + } + if (value < 0.0 || value > srcLen) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_SAMPLE_OFFSET value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1026 /* AL_BYTE_OFFSET */: + var srcLen = AL.sourceDuration(src); + if (srcLen > 0.0) { + var bytesPerSec; + for (var bufId in src.bufQueue) { + if (bufId) { + var buf = src.bufQueue[bufId]; + bytesPerSec = buf.frequency * buf.bytesPerSample * buf.channels; + break; + } + } + value /= bytesPerSec; + } + if (value < 0.0 || value > srcLen) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_BYTE_OFFSET value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.sourceSeek(src, value); + break; + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + if (value !== {{{ cDefs.AL_FALSE }}} && value !== {{{ cDefs.AL_TRUE }}} && value !== 2 /* AL_AUTO_SOFT */) { +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_SOURCE_SPATIALIZE_SOFT value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + src.spatialize = value; + AL.initSourcePanner(src); + break; + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + case 0x200B /* AL_SEC_LENGTH_SOFT */: +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_*_LENGTH_SOFT is read only`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + break; + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + switch (value) { + case {{{ cDefs.AL_NONE }}}: + case 0xd001 /* AL_INVERSE_DISTANCE */: + case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: + case 0xd003 /* AL_LINEAR_DISTANCE */: + case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: + case 0xd005 /* AL_EXPONENT_DISTANCE */: + case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: + src.distanceModel = value; + if (AL.currentCtx.sourceDistanceModel) { + AL.updateContextGlobal(AL.currentCtx); + } + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param AL_DISTANCE_MODEL value ${value} is out of range`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + break; + default: +#if OPENAL_DEBUG + dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + // ------------------------------------------------------- + // -- Capture + // ------------------------------------------------------- + + // A map of 'capture device contexts'. + captures: {}, + + sharedCaptureAudioCtx: null, + + // Helper which: + // - Asserts that deviceId is both non-NULL AND a known device ID; + // - Returns a reference to it, or null if not found. + // - Sets alcErr accordingly. + // Treat NULL and separately because careless + // people might assume that most alcCapture functions + // accept NULL as a 'use the default' device. + requireValidCaptureDevice: (deviceId, funcname) => { + if (deviceId === 0) { +#if OPENAL_DEBUG + dbg(`${funcname}() on a NULL device is an error`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return null; + } + var c = AL.captures[deviceId]; + if (!c) { +#if OPENAL_DEBUG + dbg(`${funcname}() on an invalid device`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return null; + } + var err = c.mediaStreamError; + if (err) { +#if OPENAL_DEBUG + switch (err.name) { + case 'PermissionDeniedError': + dbg(`${funcname}() but the user denied access to the device`); + break; + case 'NotFoundError': + dbg(`${funcname}() but no capture device was found`); + break; + default: + dbg(`${funcname}() but a MediaStreamError was encountered: ${err}`); + break; + } +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return null; + } + return c; + } + + }, + + // *************************************************************************** + // ** ALC API + // *************************************************************************** + + // ------------------------------------------------------- + // -- ALC Capture + // ------------------------------------------------------- + + // bufferSize is actually 'number of sample frames', so was renamed + // bufferFrameCapacity here for clarity. + alcCaptureOpenDevice__deps: ['$autoResumeAudioContext'], + alcCaptureOpenDevice__proxy: 'sync', + alcCaptureOpenDevice: (pDeviceName, requestedSampleRate, format, bufferFrameCapacity) => { + + var resolvedDeviceName = AL.CAPTURE_DEVICE_NAME; + + // NULL is a valid device name here (resolves to default); + if (pDeviceName !== 0) { + resolvedDeviceName = UTF8ToString(pDeviceName); + if (resolvedDeviceName !== AL.CAPTURE_DEVICE_NAME) { +#if OPENAL_DEBUG + dbg(`alcCaptureOpenDevice() with invalid device name '${resolvedDeviceName}'`); +#endif + // ALC_OUT_OF_MEMORY + // From the programmer's guide, ALC_OUT_OF_MEMORY's meaning is + // overloaded here, to mean: + // 'The specified device is invalid, or can not capture audio.' + // This may be misleading to API users, but well... + AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + } + + // Otherwise it's probably okay (though useless) for bufferFrameCapacity to be zero. + if (bufferFrameCapacity < 0) { // ALCsizei is signed int +#if OPENAL_DEBUG + dbg('alcCaptureOpenDevice() with negative bufferSize'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + + navigator.getUserMedia = navigator.getUserMedia + || navigator.webkitGetUserMedia + || navigator.mozGetUserMedia + || navigator.msGetUserMedia; + var has_getUserMedia = navigator.getUserMedia + || (navigator.mediaDevices + && navigator.mediaDevices.getUserMedia); + + if (!has_getUserMedia) { +#if OPENAL_DEBUG + dbg('alcCaptureOpenDevice() cannot capture audio, because your browser lacks a `getUserMedia()` implementation'); +#endif + // See previously mentioned rationale for ALC_OUT_OF_MEMORY + AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + + var AudioContext = window.AudioContext || window.webkitAudioContext; + + if (!AL.sharedCaptureAudioCtx) { + try { + AL.sharedCaptureAudioCtx = new AudioContext(); + } catch(e) { +#if OPENAL_DEBUG + dbg(`alcCaptureOpenDevice() could not create the shared capture AudioContext: ${e}`); +#endif + // See previously mentioned rationale for ALC_OUT_OF_MEMORY + AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + } + + autoResumeAudioContext(AL.sharedCaptureAudioCtx); + + var outputChannelCount; + + switch (format) { + case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ + case 0x1101: /* AL_FORMAT_MONO16 */ + case 0x1100: /* AL_FORMAT_MONO8 */ + outputChannelCount = 1; + break; + case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ + case 0x1103: /* AL_FORMAT_STEREO16 */ + case 0x1102: /* AL_FORMAT_STEREO8 */ + outputChannelCount = 2; + break; + default: +#if OPENAL_DEBUG + dbg(`alcCaptureOpenDevice() with unsupported format ${format}`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + + function newF32Array(cap) { return new Float32Array(cap);} + function newI16Array(cap) { return new Int16Array(cap); } + function newU8Array(cap) { return new Uint8Array(cap); } + + var requestedSampleType; + var newSampleArray; + + switch (format) { + case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ + case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ + requestedSampleType = 'f32'; + newSampleArray = newF32Array; + break; + case 0x1101: /* AL_FORMAT_MONO16 */ + case 0x1103: /* AL_FORMAT_STEREO16 */ + requestedSampleType = 'i16'; + newSampleArray = newI16Array; + break; + case 0x1100: /* AL_FORMAT_MONO8 */ + case 0x1102: /* AL_FORMAT_STEREO8 */ + requestedSampleType = 'u8'; + newSampleArray = newU8Array; + break; + } + + var buffers = []; + try { + for (var chan=0; chan < outputChannelCount; ++chan) { + buffers[chan] = newSampleArray(bufferFrameCapacity); + } + } catch(e) { +#if OPENAL_DEBUG + dbg(`alcCaptureOpenDevice() failed to allocate internal buffers (is bufferSize low enough?): ${e}`); +#endif + AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; + return 0; + } + + + // What we'll place into the `AL.captures` array in the end, + // declared here for closures to access it + var newCapture = { + audioCtx: AL.sharedCaptureAudioCtx, + deviceName: resolvedDeviceName, + requestedSampleRate, + requestedSampleType, + outputChannelCount, + inputChannelCount: null, // Not known until the getUserMedia() promise resolves + mediaStreamError: null, // Used by other functions to return early and report an error. + mediaStreamSourceNode: null, + mediaStream: null, + // Either one, or none of the below two, is active. + mergerNode: null, + splitterNode: null, + scriptProcessorNode: null, + isCapturing: false, + buffers, + get bufferFrameCapacity() { + return buffers[0].length; + }, + capturePlayhead: 0, // current write position, in sample frames + captureReadhead: 0, + capturedFrameCount: 0 + }; + + // Preparing for getUserMedia() + + var onError = (mediaStreamError) => { + newCapture.mediaStreamError = mediaStreamError; +#if OPENAL_DEBUG + dbg(`navigator.getUserMedia() errored with: ${mediaStreamError}`); +#endif + }; + var onSuccess = (mediaStream) => { + newCapture.mediaStreamSourceNode = newCapture.audioCtx.createMediaStreamSource(mediaStream); + newCapture.mediaStream = mediaStream; + + var inputChannelCount = 1; + switch (newCapture.mediaStreamSourceNode.channelCountMode) { + case 'max': + inputChannelCount = outputChannelCount; + break; + case 'clamped-max': + inputChannelCount = Math.min(outputChannelCount, newCapture.mediaStreamSourceNode.channelCount); + break; + case 'explicit': + inputChannelCount = newCapture.mediaStreamSourceNode.channelCount; + break; + } + + newCapture.inputChannelCount = inputChannelCount; + +#if OPENAL_DEBUG + if (inputChannelCount > 2 || outputChannelCount > 2) { + dbg('The number of input or output channels is too high, capture might not work as expected!'); + } +#endif + + // Have to pick a size from 256, 512, 1024, 2048, 4096, 8192, 16384. + // One can also set it to zero, which leaves the decision up to the impl. + // An extension could allow specifying this value. + var processorFrameCount = 512; + + newCapture.scriptProcessorNode = newCapture.audioCtx.createScriptProcessor( + processorFrameCount, inputChannelCount, outputChannelCount + ); + + if (inputChannelCount > outputChannelCount) { + newCapture.mergerNode = newCapture.audioCtx.createChannelMerger(inputChannelCount); + newCapture.mediaStreamSourceNode.connect(newCapture.mergerNode); + newCapture.mergerNode.connect(newCapture.scriptProcessorNode); + } else if (inputChannelCount < outputChannelCount) { + newCapture.splitterNode = newCapture.audioCtx.createChannelSplitter(outputChannelCount); + newCapture.mediaStreamSourceNode.connect(newCapture.splitterNode); + newCapture.splitterNode.connect(newCapture.scriptProcessorNode); + } else { + newCapture.mediaStreamSourceNode.connect(newCapture.scriptProcessorNode); + } + + newCapture.scriptProcessorNode.connect(newCapture.audioCtx.destination); + + newCapture.scriptProcessorNode.onaudioprocess = (audioProcessingEvent) => { + if (!newCapture.isCapturing) { + return; + } + + var c = newCapture; + var srcBuf = audioProcessingEvent.inputBuffer; + + // Actually just copy srcBuf's channel data into + // c.buffers, optimizing for each case. + switch (format) { + case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ + var channel0 = srcBuf.getChannelData(0); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i]; + } + break; + case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i]; + c.buffers[1][wi] = channel1[i]; + } + break; + case 0x1101: /* AL_FORMAT_MONO16 */ + var channel0 = srcBuf.getChannelData(0); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i] * 32767; + } + break; + case 0x1103: /* AL_FORMAT_STEREO16 */ + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = channel0[i] * 32767; + c.buffers[1][wi] = channel1[i] * 32767; + } + break; + case 0x1100: /* AL_FORMAT_MONO8 */ + var channel0 = srcBuf.getChannelData(0); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = (channel0[i] + 1.0) * 127; + } + break; + case 0x1102: /* AL_FORMAT_STEREO8 */ + var channel0 = srcBuf.getChannelData(0); + var channel1 = srcBuf.getChannelData(1); + for (var i = 0 ; i < srcBuf.length; ++i) { + var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; + c.buffers[0][wi] = (channel0[i] + 1.0) * 127; + c.buffers[1][wi] = (channel1[i] + 1.0) * 127; + } + break; + } + + c.capturePlayhead += srcBuf.length; + c.capturePlayhead %= c.bufferFrameCapacity; + c.capturedFrameCount += srcBuf.length; + c.capturedFrameCount = Math.min(c.capturedFrameCount, c.bufferFrameCapacity); + }; + }; + + // The latest way to call getUserMedia() + if (navigator.mediaDevices?.getUserMedia) { + navigator.mediaDevices + .getUserMedia({audio: true}) + .then(onSuccess) + .catch(onError); + } else { // The usual (now deprecated) way + navigator.getUserMedia({audio: true}, onSuccess, onError); + } + + var id = AL.newId(); + AL.captures[id] = newCapture; + return id; + }, + + alcCaptureCloseDevice__proxy: 'sync', + alcCaptureCloseDevice: (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureCloseDevice'); + if (!c) return false; + + delete AL.captures[deviceId]; + AL.freeIds.push(deviceId); + + // This clean-up might be unnecessary (paranoid) ? + + // May happen if user hasn't decided to grant or deny input + c.mediaStreamSourceNode?.disconnect(); + c.mergerNode?.disconnect(); + c.splitterNode?.disconnect(); + // May happen if user hasn't decided to grant or deny input + c.scriptProcessorNode?.disconnect(); + if (c.mediaStream) { + // Disabling the microphone of the browser. + // Without this operation, the red dot on the browser tab page will remain. + c.mediaStream.getTracks().forEach((track) => track.stop()); + } + + delete c.buffers; + + c.capturedFrameCount = 0; + c.isCapturing = false; + + return true; + }, + + alcCaptureStart__proxy: 'sync', + alcCaptureStart: (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStart'); + if (!c) return; + + if (c.isCapturing) { +#if OPENAL_DEBUG + dbg('Redundant call to alcCaptureStart()'); +#endif + // NOTE: Spec says (emphasis mine): + // The amount of audio samples available after **restarting** a + // stopped capture device is reset to zero. + // So redundant calls to alcCaptureStart() must have no effect. + return; + } + c.isCapturing = true; + c.capturedFrameCount = 0; + c.capturePlayhead = 0; + }, + + alcCaptureStop__proxy: 'sync', + alcCaptureStop: (deviceId) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStop'); + if (!c) return; + +#if OPENAL_DEBUG + if (!c.isCapturing) { + dbg('Redundant call to alcCaptureStop()'); + } +#endif + c.isCapturing = false; + }, + + // The OpenAL spec hints that implementations are allowed to + // 'defer resampling and other conversions' up until this point. + // + // The last parameter is actually 'number of sample frames', so was + // renamed accordingly here + alcCaptureSamples__proxy: 'sync', + alcCaptureSamples: (deviceId, pFrames, requestedFrameCount) => { + var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureSamples'); + if (!c) return; + + // ALCsizei is actually 32-bit signed int, so could be negative + // Also, spec says : + // Requesting more sample frames than are currently available is + // an error. + + var dstfreq = c.requestedSampleRate; + var srcfreq = c.audioCtx.sampleRate; + + var fratio = srcfreq / dstfreq; + + if (requestedFrameCount < 0 + || requestedFrameCount > (c.capturedFrameCount / fratio)) + { +#if OPENAL_DEBUG + dbg('alcCaptureSamples() with invalid bufferSize'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return; + } + + function setF32Sample(i, sample) { + {{{ makeSetValue('pFrames', '4*i', 'sample', 'float') }}}; + } + function setI16Sample(i, sample) { + {{{ makeSetValue('pFrames', '2*i', 'sample', 'i16') }}}; + } + function setU8Sample(i, sample) { + {{{ makeSetValue('pFrames', 'i', 'sample', 'i8') }}}; + } + + var setSample; + + switch (c.requestedSampleType) { + case 'f32': setSample = setF32Sample; break; + case 'i16': setSample = setI16Sample; break; + case 'u8' : setSample = setU8Sample ; break; + default: +#if OPENAL_DEBUG + dbg(`Internal error: Unknown sample type '${c.requestedSampleType}'`); +#endif + return; + } + + // If fratio is an integer we don't need linear resampling, just skip samples + if (Math.floor(fratio) == fratio) { + for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { + for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { + setSample(i, c.buffers[chan][c.captureReadhead]); + } + c.captureReadhead = (fratio + c.captureReadhead) % c.bufferFrameCapacity; + } + } else { + // Perform linear resampling. + + // There is room for improvement - right now we're fine with linear resampling. + // We don't use OfflineAudioContexts for this: See the discussion at + // https://github.com/jpernst/emscripten/issues/2#issuecomment-312729735 + // if you're curious about why. + for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { + var lefti = Math.floor(c.captureReadhead); + var righti = Math.ceil(c.captureReadhead); + var d = c.captureReadhead - lefti; + for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { + var lefts = c.buffers[chan][lefti]; + var rights = c.buffers[chan][righti]; + setSample(i, (1 - d) * lefts + d * rights); + } + c.captureReadhead = (c.captureReadhead + fratio) % c.bufferFrameCapacity; + } + } + + // Spec doesn't say if alcCaptureSamples() must zero the number + // of available captured sample-frames, but not only would it + // be insane not to do, OpenAL-Soft happens to do that as well. + c.capturedFrameCount = 0; + }, + + + // ------------------------------------------------------- + // -- ALC Resources + // ------------------------------------------------------- + + alcOpenDevice__proxy: 'sync', + alcOpenDevice: (pDeviceName) => { + if (pDeviceName) { + var name = UTF8ToString(pDeviceName); + if (name !== AL.DEVICE_NAME) { + return 0; + } + } + + if (globalThis.AudioContext || globalThis.webkitAudioContext) { + var deviceId = AL.newId(); + AL.deviceRefCounts[deviceId] = 0; + return deviceId; + } + return 0; + }, + + alcCloseDevice__proxy: 'sync', + alcCloseDevice: (deviceId) => { + if (!(deviceId in AL.deviceRefCounts) || AL.deviceRefCounts[deviceId] > 0) { + return {{{ cDefs.ALC_FALSE }}}; + } + + delete AL.deviceRefCounts[deviceId]; + AL.freeIds.push(deviceId); + return {{{ cDefs.ALC_TRUE }}}; + }, + + alcCreateContext__deps: ['$autoResumeAudioContext'], + alcCreateContext__proxy: 'sync', + alcCreateContext: (deviceId, pAttrList) => { + if (!(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcCreateContext() called with an invalid device'); +#endif + AL.alcErr = 0xA001; /* ALC_INVALID_DEVICE */ + return 0; + } + + var options = null; + var attrs = []; + var hrtf = null; + pAttrList >>= 2; + if (pAttrList) { + var attr = 0; + var val = 0; + while (true) { + attr = HEAP32[pAttrList++]; + attrs.push(attr); + if (attr === 0) { + break; + } + val = HEAP32[pAttrList++]; + attrs.push(val); + + switch (attr) { + case 0x1007 /* ALC_FREQUENCY */: + if (!options) { + options = {}; + } + + options.sampleRate = val; + break; + case 0x1010 /* ALC_MONO_SOURCES */: // fallthrough + case 0x1011 /* ALC_STEREO_SOURCES */: + // Do nothing; these hints are satisfied by default + break + case 0x1992 /* ALC_HRTF_SOFT */: + switch (val) { + case {{{ cDefs.ALC_FALSE }}}: + hrtf = false; + break; + case {{{ cDefs.ALC_TRUE }}}: + hrtf = true; + break; + case 2 /* ALC_DONT_CARE_SOFT */: + break; + default: +#if OPENAL_DEBUG + dbg(`Unsupported ALC_HRTF_SOFT mode ${val}`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + break; + case 0x1996 /* ALC_HRTF_ID_SOFT */: + if (val !== 0) { +#if OPENAL_DEBUG + dbg(`Invalid ALC_HRTF_ID_SOFT index ${val}`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + break; + default: +#if OPENAL_DEBUG + dbg(`Unsupported context attribute ${ptrToString(attr)}`); +#endif + AL.alcErr = 0xA004; /* ALC_INVALID_VALUE */ + return 0; + } + } + } + + var AudioContext = window.AudioContext || window.webkitAudioContext; + var ac = null; + try { + // Only try to pass options if there are any, for compat with browsers that don't support this + if (options) { + ac = new AudioContext(options); + } else { + ac = new AudioContext(); + } + } catch (e) { + if (e.name === 'NotSupportedError') { +#if OPENAL_DEBUG + dbg('Invalid or unsupported options'); +#endif + AL.alcErr = 0xA004; /* ALC_INVALID_VALUE */ + } else { + AL.alcErr = 0xA001; /* ALC_INVALID_DEVICE */ + } + + return 0; + } + + autoResumeAudioContext(ac); + + // Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function. + if (typeof ac.createGain == 'undefined') { + ac.createGain = ac.createGainNode; + } + + var gain = ac.createGain(); + gain.connect(ac.destination); + var ctx = { + deviceId, + id: AL.newId(), + attrs, + audioCtx: ac, + listener: { + position: [0.0, 0.0, 0.0], + velocity: [0.0, 0.0, 0.0], + direction: [0.0, 0.0, 0.0], + up: [0.0, 0.0, 0.0] + }, + sources: [], + interval: setInterval(() => AL.scheduleContextAudio(ctx), AL.QUEUE_INTERVAL), + gain, + distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, + speedOfSound: 343.3, + dopplerFactor: 1.0, + sourceDistanceModel: false, + hrtf: hrtf || false, + + _err: 0, + get err() { + return this._err; + }, + set err(val) { + // Errors should not be overwritten by later errors until they are cleared by a query. + if (this._err === {{{ cDefs.AL_NO_ERROR }}} || val === {{{ cDefs.AL_NO_ERROR }}}) { + this._err = val; + } + } + }; + AL.deviceRefCounts[deviceId]++; + AL.contexts[ctx.id] = ctx; + + if (hrtf !== null) { + // Apply hrtf attrib to all contexts for this device + for (var ctxId in AL.contexts) { + var c = AL.contexts[ctxId]; + if (c.deviceId === deviceId) { + c.hrtf = hrtf; + AL.updateContextGlobal(c); + } + } + } + + return ctx.id; + }, + + alcDestroyContext__proxy: 'sync', + alcDestroyContext: (contextId) => { + var ctx = AL.contexts[contextId]; + if (AL.currentCtx === ctx) { +#if OPENAL_DEBUG + dbg('alcDestroyContext() called with an invalid context'); +#endif + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + // Stop playback, etc + if (AL.contexts[contextId].interval) { + clearInterval(AL.contexts[contextId].interval); + } + AL.deviceRefCounts[ctx.deviceId]--; + delete AL.contexts[contextId]; + AL.freeIds.push(contextId); + }, + + // ------------------------------------------------------- + // -- ALC State + // ------------------------------------------------------- + + alcGetError__proxy: 'sync', + alcGetError: (deviceId) => { + var err = AL.alcErr; + AL.alcErr = {{{ cDefs.ALC_NO_ERROR }}}; + return err; + }, + + alcGetCurrentContext__proxy: 'sync', + alcGetCurrentContext: () => { + if (AL.currentCtx !== null) { + return AL.currentCtx.id; + } + return 0; + }, + + alcMakeContextCurrent__proxy: 'sync', + alcMakeContextCurrent: (contextId) => { + if (contextId === 0) { + AL.currentCtx = null; + } else { + AL.currentCtx = AL.contexts[contextId]; + } + return {{{ cDefs.ALC_TRUE }}}; + }, + + alcGetContextsDevice__proxy: 'sync', + alcGetContextsDevice: (contextId) => { + if (contextId in AL.contexts) { + return AL.contexts[contextId].deviceId; + } + return 0; + }, + + // The spec is vague about what these are actually supposed to do, and NOP is a reasonable implementation + alcProcessContext: (contextId) => {}, + alcSuspendContext: (contextId) => {}, + + alcIsExtensionPresent__proxy: 'sync', + alcIsExtensionPresent: (deviceId, pExtName) => { + var name = UTF8ToString(pExtName); + + return AL.ALC_EXTENSIONS[name] ? 1 : 0; + }, + + alcGetEnumValue__proxy: 'sync', + alcGetEnumValue: (deviceId, pEnumName) => { + // Spec says : + // Using a NULL handle is legal, but only the + // tokens defined by the AL core are guaranteed. + if (deviceId !== 0 && !(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcGetEnumValue() called with an invalid device'); +#endif + // ALC_INVALID_DEVICE is not listed as a possible error state for + // this function, sadly. + return 0; + } else if (!pEnumName) { + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + var name = UTF8ToString(pEnumName); + // See alGetEnumValue(), but basically behave the same as OpenAL-Soft + switch (name) { + case 'ALC_NO_ERROR': return 0; + case 'ALC_INVALID_DEVICE': return 0xA001; + case 'ALC_INVALID_CONTEXT': return 0xA002; + case 'ALC_INVALID_ENUM': return 0xA003; + case 'ALC_INVALID_VALUE': return 0xA004; + case 'ALC_OUT_OF_MEMORY': return 0xA005; + case 'ALC_MAJOR_VERSION': return 0x1000; + case 'ALC_MINOR_VERSION': return 0x1001; + case 'ALC_ATTRIBUTES_SIZE': return 0x1002; + case 'ALC_ALL_ATTRIBUTES': return 0x1003; + case 'ALC_DEFAULT_DEVICE_SPECIFIER': return 0x1004; + case 'ALC_DEVICE_SPECIFIER': return 0x1005; + case 'ALC_EXTENSIONS': return 0x1006; + case 'ALC_FREQUENCY': return 0x1007; + case 'ALC_REFRESH': return 0x1008; + case 'ALC_SYNC': return 0x1009; + case 'ALC_MONO_SOURCES': return 0x1010; + case 'ALC_STEREO_SOURCES': return 0x1011; + case 'ALC_CAPTURE_DEVICE_SPECIFIER': return 0x310; + case 'ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER': return 0x311; + case 'ALC_CAPTURE_SAMPLES': return 0x312; + + /* Extensions */ + case 'ALC_HRTF_SOFT': return 0x1992; + case 'ALC_HRTF_ID_SOFT': return 0x1996; + case 'ALC_DONT_CARE_SOFT': return 0x0002; + case 'ALC_HRTF_STATUS_SOFT': return 0x1993; + case 'ALC_NUM_HRTF_SPECIFIERS_SOFT': return 0x1994; + case 'ALC_HRTF_SPECIFIER_SOFT': return 0x1995; + case 'ALC_HRTF_DISABLED_SOFT': return 0x0000; + case 'ALC_HRTF_ENABLED_SOFT': return 0x0001; + case 'ALC_HRTF_DENIED_SOFT': return 0x0002; + case 'ALC_HRTF_REQUIRED_SOFT': return 0x0003; + case 'ALC_HRTF_HEADPHONES_DETECTED_SOFT': return 0x0004; + case 'ALC_HRTF_UNSUPPORTED_FORMAT_SOFT': return 0x0005; + + default: +#if OPENAL_DEBUG + dbg(`No value for '${pEnumName}' is known by alcGetEnumValue()`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return {{{ cDefs.AL_NONE }}}; + } + }, + + alcGetString__proxy: 'sync', + alcGetString__deps: ['$stringToNewUTF8'], + alcGetString: (deviceId, param) => { + if (AL.alcStringCache[param]) { + return AL.alcStringCache[param]; + } + + var ret; + switch (param) { + case {{{ cDefs.ALC_NO_ERROR }}}: + ret = 'No Error'; + break; + case {{{ cDefs.ALC_INVALID_DEVICE }}}: + ret = 'Invalid Device'; + break; + case 0xA002 /* ALC_INVALID_CONTEXT */: + ret = 'Invalid Context'; + break; + case {{{ cDefs.ALC_INVALID_ENUM }}}: + ret = 'Invalid Enum'; + break; + case {{{ cDefs.ALC_INVALID_VALUE }}}: + ret = 'Invalid Value'; + break; + case 0xA005 /* ALC_OUT_OF_MEMORY */: + ret = 'Out of Memory'; + break; + case 0x1004 /* ALC_DEFAULT_DEVICE_SPECIFIER */: + if (globalThis.AudioContext || globalThis.webkitAudioContext) { + ret = AL.DEVICE_NAME; + } else { + return 0; + } + break; + case 0x1005 /* ALC_DEVICE_SPECIFIER */: + if (globalThis.AudioContext || globalThis.webkitAudioContext) { + ret = AL.DEVICE_NAME + '\0'; + } else { + ret = '\0'; + } + break; + case 0x311 /* ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER */: + ret = AL.CAPTURE_DEVICE_NAME; + break; + case 0x310 /* ALC_CAPTURE_DEVICE_SPECIFIER */: + if (deviceId === 0) { + ret = AL.CAPTURE_DEVICE_NAME + '\0'; + } else { + var c = AL.requireValidCaptureDevice(deviceId, 'alcGetString'); + if (!c) { + return 0; + } + ret = c.deviceName; + } + break; + case 0x1006 /* ALC_EXTENSIONS */: + if (!deviceId) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return 0; + } + + ret = Object.keys(AL.ALC_EXTENSIONS).join(' ') + break; + default: + AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; + return 0; + } + + ret = stringToNewUTF8(ret); + AL.alcStringCache[param] = ret; + return ret; + }, + + alcGetIntegerv__proxy: 'sync', + alcGetIntegerv: (deviceId, param, size, pValues) => { + if (size === 0 || !pValues) { + // Ignore the query, per the spec + return; + } + + switch (param) { + case 0x1000 /* ALC_MAJOR_VERSION */: + {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; + break; + case 0x1001 /* ALC_MINOR_VERSION */: + {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; + break; + case 0x1002 /* ALC_ATTRIBUTES_SIZE */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + {{{ makeSetValue('pValues', '0', 'AL.currentCtx.attrs.length', 'i32') }}}; + break; + case 0x1003 /* ALC_ALL_ATTRIBUTES */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + for (var i = 0; i < AL.currentCtx.attrs.length; i++) { + {{{ makeSetValue('pValues', 'i*4', 'AL.currentCtx.attrs[i]', 'i32') }}}; + } + break; + case 0x1007 /* ALC_FREQUENCY */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + {{{ makeSetValue('pValues', '0', 'AL.currentCtx.audioCtx.sampleRate', 'i32') }}}; + break; + case 0x1010 /* ALC_MONO_SOURCES */: + case 0x1011 /* ALC_STEREO_SOURCES */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + {{{ makeSetValue('pValues', '0', '0x7FFFFFFF', 'i32') }}}; + break; + case 0x1992 /* ALC_HRTF_SOFT */: + case 0x1993 /* ALC_HRTF_STATUS_SOFT */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + + var hrtfStatus = 0 /* ALC_HRTF_DISABLED_SOFT */; + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId === deviceId) { + hrtfStatus = ctx.hrtf ? 1 /* ALC_HRTF_ENABLED_SOFT */ : 0 /* ALC_HRTF_DISABLED_SOFT */; + } + } + {{{ makeSetValue('pValues', '0', 'hrtfStatus', 'i32') }}}; + break; + case 0x1994 /* ALC_NUM_HRTF_SPECIFIERS_SOFT */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; + break; + case 0x20003 /* ALC_MAX_AUXILIARY_SENDS */: + if (!(deviceId in AL.deviceRefCounts)) { + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + if (!AL.currentCtx) { + AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; + return; + } + + {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; + case 0x312 /* ALC_CAPTURE_SAMPLES */: + var c = AL.requireValidCaptureDevice(deviceId, 'alcGetIntegerv'); + if (!c) { + return; + } + var n = c.capturedFrameCount; + var dstfreq = c.requestedSampleRate; + var srcfreq = c.audioCtx.sampleRate; + var nsamples = Math.floor(n * (dstfreq/srcfreq)); + {{{ makeSetValue('pValues', '0', 'nsamples', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alcGetIntegerv() with param ${ptrToString(param)} not implemented yet`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; + return; + } + }, + + emscripten_alcDevicePauseSOFT__proxy: 'sync', + emscripten_alcDevicePauseSOFT__sig: 'vi', + emscripten_alcDevicePauseSOFT: (deviceId) => { + if (!(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcDevicePauseSOFT() called with an invalid device'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + + if (AL.paused) { + return; + } + AL.paused = true; + + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId !== deviceId) { + continue; + } + + ctx.audioCtx.suspend(); + clearInterval(ctx.interval); + ctx.interval = null; + } + }, + + emscripten_alcDeviceResumeSOFT__proxy: 'sync', + emscripten_alcDeviceResumeSOFT__sig: 'vi', + emscripten_alcDeviceResumeSOFT: (deviceId) => { + if (!(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcDeviceResumeSOFT() called with an invalid device'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return; + } + + if (!AL.paused) { + return; + } + AL.paused = false; + + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId !== deviceId) { + continue; + } + + ctx.interval = setInterval(() => AL.scheduleContextAudio(ctx), AL.QUEUE_INTERVAL); + ctx.audioCtx.resume(); + } + }, + + emscripten_alcGetStringiSOFT__proxy: 'sync', + emscripten_alcGetStringiSOFT__sig: 'iiii', + emscripten_alcGetStringiSOFT__deps: ['alcGetString', '$stringToNewUTF8'], + emscripten_alcGetStringiSOFT: (deviceId, param, index) => { + if (!(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcGetStringiSOFT() called with an invalid device'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return 0; + } + + if (AL.alcStringCache[param]) { + return AL.alcStringCache[param]; + } + + var ret; + switch (param) { + case 0x1995 /* ALC_HRTF_SPECIFIER_SOFT */: + if (index === 0) { + ret = 'Web Audio HRTF'; + } else { +#if OPENAL_DEBUG + dbg(`alcGetStringiSOFT() with param ALC_HRTF_SPECIFIER_SOFT index ${index} is out of range`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; + return 0; + } + break; + default: + if (index !== 0) { +#if OPENAL_DEBUG + dbg(`alcGetStringiSOFT() with param ${ptrToString(param)} not implemented yet`); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; + return 0; + } + return _alcGetString(deviceId, param); + } + + ret = stringToNewUTF8(ret); + AL.alcStringCache[param] = ret; + return ret; + }, + + emscripten_alcResetDeviceSOFT__proxy: 'sync', + emscripten_alcResetDeviceSOFT__sig: 'iii', + emscripten_alcResetDeviceSOFT: (deviceId, pAttrList) => { + if (!(deviceId in AL.deviceRefCounts)) { +#if OPENAL_DEBUG + dbg('alcResetDeviceSOFT() called with an invalid device'); +#endif + AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; + return {{{ cDefs.ALC_FALSE }}}; + } + + var hrtf = null; + pAttrList >>= 2; + if (pAttrList) { + var attr = 0; + var val = 0; + while (true) { + attr = HEAP32[pAttrList++]; + if (attr === 0) { + break; + } + val = HEAP32[pAttrList++]; + + switch (attr) { + case 0x1992 /* ALC_HRTF_SOFT */: + if (val === {{{ cDefs.ALC_TRUE }}}) { + hrtf = true; + } else if (val === {{{ cDefs.ALC_FALSE }}}) { + hrtf = false; + } + break; + } + } + } + + if (hrtf !== null) { + // Apply hrtf attrib to all contexts for this device + for (var ctxId in AL.contexts) { + var ctx = AL.contexts[ctxId]; + if (ctx.deviceId === deviceId) { + ctx.hrtf = hrtf; + AL.updateContextGlobal(ctx); + } + } + } + + return {{{ cDefs.ALC_TRUE }}}; + }, + + // *************************************************************************** + // ** AL API + // *************************************************************************** + + // ------------------------------------------------------- + // -- AL Resources + // ------------------------------------------------------- + + alGenBuffers__proxy: 'sync', + alGenBuffers: (count, pBufferIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alGenBuffers() called without a valid context'); +#endif + return; + } + + for (var i = 0; i < count; ++i) { + var buf = { + deviceId: AL.currentCtx.deviceId, + id: AL.newId(), + refCount: 0, + audioBuf: null, + frequency: 0, + bytesPerSample: 2, + channels: 1, + length: 0, + }; + AL.deviceRefCounts[buf.deviceId]++; + AL.buffers[buf.id] = buf; + {{{ makeSetValue('pBufferIds', 'i*4', 'buf.id', 'i32') }}}; + } + }, + + alDeleteBuffers__proxy: 'sync', + alDeleteBuffers: (count, pBufferIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alDeleteBuffers() called without a valid context'); +#endif + return; + } + + for (var i = 0; i < count; ++i) { + var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; + /// Deleting the zero buffer is a legal NOP, so ignore it + if (bufId === 0) { + continue; + } + + // Make sure the buffer index is valid. + if (!AL.buffers[bufId]) { +#if OPENAL_DEBUG + dbg('alDeleteBuffers() called with an invalid buffer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + + // Make sure the buffer is no longer in use. + if (AL.buffers[bufId].refCount) { +#if OPENAL_DEBUG + dbg('alDeleteBuffers() called with a used buffer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; + if (bufId === 0) { + continue; + } + + AL.deviceRefCounts[AL.buffers[bufId].deviceId]--; + delete AL.buffers[bufId]; + AL.freeIds.push(bufId); + } + }, + + alGenSources__proxy: 'sync', + alGenSources: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alGenSources() called without a valid context'); +#endif + return; + } + for (var i = 0; i < count; ++i) { + var gain = AL.currentCtx.audioCtx.createGain(); + gain.connect(AL.currentCtx.gain); + var src = { + context: AL.currentCtx, + id: AL.newId(), + type: 0x1030 /* AL_UNDETERMINED */, + state: {{{ cDefs.AL_INITIAL }}}, + bufQueue: [AL.buffers[0]], + audioQueue: [], + looping: false, + pitch: 1.0, + dopplerShift: 1.0, + gain, + minGain: 0.0, + maxGain: 1.0, + panner: null, + bufsProcessed: 0, + bufStartTime: Number.NEGATIVE_INFINITY, + bufOffset: 0.0, + relative: false, + refDistance: 1.0, + maxDistance: 3.40282e38 /* FLT_MAX */, + rolloffFactor: 1.0, + position: [0.0, 0.0, 0.0], + velocity: [0.0, 0.0, 0.0], + direction: [0.0, 0.0, 0.0], + coneOuterGain: 0.0, + coneInnerAngle: 360.0, + coneOuterAngle: 360.0, + distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, + spatialize: 2 /* AL_AUTO_SOFT */, + + get playbackRate() { + return this.pitch * this.dopplerShift; + } + }; + AL.currentCtx.sources[src.id] = src; + {{{ makeSetValue('pSourceIds', 'i*4', 'src.id', 'i32') }}}; + } + }, + + alDeleteSources__deps: ['alSourcei'], + alDeleteSources__proxy: 'sync', + alDeleteSources: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alDeleteSources() called without a valid context'); +#endif + return; + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + if (!AL.currentCtx.sources[srcId]) { +#if OPENAL_DEBUG + dbg('alDeleteSources() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_STOPPED }}}); + _alSourcei(srcId, 0x1009 /* AL_BUFFER */, 0); + delete AL.currentCtx.sources[srcId]; + AL.freeIds.push(srcId); + } + }, + + // ------------------------------------------------------- + // --- AL Context State + // ------------------------------------------------------- + + alGetError__proxy: 'sync', + alGetError: () => { + if (!AL.currentCtx) { + return {{{ cDefs.AL_INVALID_OPERATION }}}; + } + // Reset error on get. + var err = AL.currentCtx.err; + AL.currentCtx.err = {{{ cDefs.AL_NO_ERROR }}}; + return err; + }, + + alIsExtensionPresent__proxy: 'sync', + alIsExtensionPresent: (pExtName) => { + var name = UTF8ToString(pExtName); + + return AL.AL_EXTENSIONS[name] ? 1 : 0; + }, + + alGetEnumValue__proxy: 'sync', + alGetEnumValue: (pEnumName) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alGetEnumValue() called without a valid context'); +#endif + return 0; + } + + if (!pEnumName) { +#if OPENAL_DEBUG + dbg('alGetEnumValue() called with null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return {{{ cDefs.AL_NONE }}}; + } + var name = UTF8ToString(pEnumName); + + switch (name) { + // Spec doesn't clearly state that alGetEnumValue() is required to + // support _only_ extension tokens. + // We should probably follow OpenAL-Soft's example and support all + // of the names we know. + // See http://repo.or.cz/openal-soft.git/blob/HEAD:/Alc/ALc.c + case 'AL_BITS': return 0x2002; + case 'AL_BUFFER': return 0x1009; + case 'AL_BUFFERS_PROCESSED': return 0x1016; + case 'AL_BUFFERS_QUEUED': return 0x1015; + case 'AL_BYTE_OFFSET': return 0x1026; + case 'AL_CHANNELS': return 0x2003; + case 'AL_CONE_INNER_ANGLE': return 0x1001; + case 'AL_CONE_OUTER_ANGLE': return 0x1002; + case 'AL_CONE_OUTER_GAIN': return 0x1022; + case 'AL_DIRECTION': return 0x1005; + case 'AL_DISTANCE_MODEL': return 0xD000; + case 'AL_DOPPLER_FACTOR': return 0xC000; + case 'AL_DOPPLER_VELOCITY': return 0xC001; + case 'AL_EXPONENT_DISTANCE': return 0xD005; + case 'AL_EXPONENT_DISTANCE_CLAMPED': return 0xD006; + case 'AL_EXTENSIONS': return 0xB004; + case 'AL_FORMAT_MONO16': return 0x1101; + case 'AL_FORMAT_MONO8': return 0x1100; + case 'AL_FORMAT_STEREO16': return 0x1103; + case 'AL_FORMAT_STEREO8': return 0x1102; + case 'AL_FREQUENCY': return 0x2001; + case 'AL_GAIN': return 0x100A; + case 'AL_INITIAL': return 0x1011; + case 'AL_INVALID': return -1; + case 'AL_ILLEGAL_ENUM': // fallthrough + case 'AL_INVALID_ENUM': return 0xA002; + case 'AL_INVALID_NAME': return 0xA001; + case 'AL_ILLEGAL_COMMAND': // fallthrough + case 'AL_INVALID_OPERATION': return 0xA004; + case 'AL_INVALID_VALUE': return 0xA003; + case 'AL_INVERSE_DISTANCE': return 0xD001; + case 'AL_INVERSE_DISTANCE_CLAMPED': return 0xD002; + case 'AL_LINEAR_DISTANCE': return 0xD003; + case 'AL_LINEAR_DISTANCE_CLAMPED': return 0xD004; + case 'AL_LOOPING': return 0x1007; + case 'AL_MAX_DISTANCE': return 0x1023; + case 'AL_MAX_GAIN': return 0x100E; + case 'AL_MIN_GAIN': return 0x100D; + case 'AL_NONE': return 0; + case 'AL_NO_ERROR': return 0; + case 'AL_ORIENTATION': return 0x100F; + case 'AL_OUT_OF_MEMORY': return 0xA005; + case 'AL_PAUSED': return 0x1013; + case 'AL_PENDING': return 0x2011; + case 'AL_PITCH': return 0x1003; + case 'AL_PLAYING': return 0x1012; + case 'AL_POSITION': return 0x1004; + case 'AL_PROCESSED': return 0x2012; + case 'AL_REFERENCE_DISTANCE': return 0x1020; + case 'AL_RENDERER': return 0xB003; + case 'AL_ROLLOFF_FACTOR': return 0x1021; + case 'AL_SAMPLE_OFFSET': return 0x1025; + case 'AL_SEC_OFFSET': return 0x1024; + case 'AL_SIZE': return 0x2004; + case 'AL_SOURCE_RELATIVE': return 0x202; + case 'AL_SOURCE_STATE': return 0x1010; + case 'AL_SOURCE_TYPE': return 0x1027; + case 'AL_SPEED_OF_SOUND': return 0xC003; + case 'AL_STATIC': return 0x1028; + case 'AL_STOPPED': return 0x1014; + case 'AL_STREAMING': return 0x1029; + case 'AL_UNDETERMINED': return 0x1030; + case 'AL_UNUSED': return 0x2010; + case 'AL_VELOCITY': return 0x1006; + case 'AL_VENDOR': return 0xB001; + case 'AL_VERSION': return 0xB002; + + /* Extensions */ + case 'AL_AUTO_SOFT': return 0x0002; + case 'AL_SOURCE_DISTANCE_MODEL': return 0x200; + case 'AL_SOURCE_SPATIALIZE_SOFT': return 0x1214; + case 'AL_LOOP_POINTS_SOFT': return 0x2015; + case 'AL_BYTE_LENGTH_SOFT': return 0x2009; + case 'AL_SAMPLE_LENGTH_SOFT': return 0x200A; + case 'AL_SEC_LENGTH_SOFT': return 0x200B; + case 'AL_FORMAT_MONO_FLOAT32': return 0x10010; + case 'AL_FORMAT_STEREO_FLOAT32': return 0x10011; + + default: +#if OPENAL_DEBUG + dbg(`No value for '${name}' is known by alGetEnumValue()`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return 0; + } + }, + + alGetString__proxy: 'sync', + alGetString__deps: ['$stringToNewUTF8'], + alGetString: (param) => { + if (AL.stringCache[param]) { + return AL.stringCache[param]; + } + + var ret; + switch (param) { + case {{{ cDefs.AL_NO_ERROR }}}: + ret = 'No Error'; + break; + case {{{ cDefs.AL_INVALID_NAME }}}: + ret = 'Invalid Name'; + break; + case {{{ cDefs.AL_INVALID_ENUM }}}: + ret = 'Invalid Enum'; + break; + case {{{ cDefs.AL_INVALID_VALUE }}}: + ret = 'Invalid Value'; + break; + case {{{ cDefs.AL_INVALID_OPERATION }}}: + ret = 'Invalid Operation'; + break; + case 0xA005 /* AL_OUT_OF_MEMORY */: + ret = 'Out of Memory'; + break; + case 0xB001 /* AL_VENDOR */: + ret = 'Emscripten'; + break; + case 0xB002 /* AL_VERSION */: + ret = '1.1'; + break; + case 0xB003 /* AL_RENDERER */: + ret = 'WebAudio'; + break; + case 0xB004 /* AL_EXTENSIONS */: + ret = Object.keys(AL.AL_EXTENSIONS).join(' '); + break; + default: + if (AL.currentCtx) { + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + } else { + #if OPENAL_DEBUG + dbg('alGetString() called without a valid context'); + #endif + } + return 0; + } + + ret = stringToNewUTF8(ret); + AL.stringCache[param] = ret; + return ret; + }, + + alEnable__proxy: 'sync', + alEnable: (param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alEnable() called without a valid context'); +#endif + return; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + AL.currentCtx.sourceDistanceModel = true; + AL.updateContextGlobal(AL.currentCtx); + break; + default: +#if OPENAL_DEBUG + dbg(`alEnable() with param ${ptrToString(param)} not implemented yet`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alDisable__proxy: 'sync', + alDisable: (param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alDisable() called without a valid context'); +#endif + return; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + AL.currentCtx.sourceDistanceModel = false; + AL.updateContextGlobal(AL.currentCtx); + break; + default: +#if OPENAL_DEBUG + dbg(`alDisable() with param ${ptrToString(param)} not implemented yet`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alIsEnabled__proxy: 'sync', + alIsEnabled: (param) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alIsEnabled() called without a valid context'); +#endif + return 0; + } + switch (param) { + case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: + return AL.currentCtx.sourceDistanceModel ? {{{ cDefs.AL_FALSE }}} : {{{ cDefs.AL_TRUE }}}; + default: +#if OPENAL_DEBUG + dbg(`alIsEnabled() with param ${ptrToString(param)} not implemented yet`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return 0; + } + }, + + alGetDouble__proxy: 'sync', + alGetDouble: (param) => { + var val = AL.getGlobalParam('alGetDouble', param); + if (val === null) { + return 0.0; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return val; + default: +#if OPENAL_DEBUG + dbg(`alGetDouble(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return 0.0; + } + }, + + alGetDoublev__proxy: 'sync', + alGetDoublev: (param, pValues) => { + var val = AL.getGlobalParam('alGetDoublev', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValues', '0', 'val', 'double') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetDoublev(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetFloat__proxy: 'sync', + alGetFloat: (param) => { + var val = AL.getGlobalParam('alGetFloat', param); + if (val === null) { + return 0.0; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return val; + default: +#if OPENAL_DEBUG + dbg(`alGetFloat(): param ${ptrToString(param)} has wrong signature`); +#endif + return 0.0; + } + }, + + alGetFloatv__proxy: 'sync', + alGetFloatv: (param, pValues) => { + var val = AL.getGlobalParam('alGetFloatv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValues', '0', 'val', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetFloatv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetInteger__proxy: 'sync', + alGetInteger: (param) => { + var val = AL.getGlobalParam('alGetInteger', param); + if (val === null) { + return 0; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return val; + default: +#if OPENAL_DEBUG + dbg(`alGetInteger(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return 0; + } + }, + + alGetIntegerv__proxy: 'sync', + alGetIntegerv: (param, pValues) => { + var val = AL.getGlobalParam('alGetIntegerv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetIntegerv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetBoolean__proxy: 'sync', + alGetBoolean: (param) => { + var val = AL.getGlobalParam('alGetBoolean', param); + if (val === null) { + return {{{ cDefs.AL_FALSE }}}; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + return val !== 0 ? {{{ cDefs.AL_TRUE }}} : {{{ cDefs.AL_FALSE }}}; + default: +#if OPENAL_DEBUG + dbg(`alGetBoolean(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return {{{ cDefs.AL_FALSE }}}; + } + }, + + alGetBooleanv__proxy: 'sync', + alGetBooleanv: (param, pValues) => { + var val = AL.getGlobalParam('alGetBooleanv', param); + // Silently ignore null destinations, as per the spec for global state functions + if (val === null || !pValues) { + return; + } + + switch (param) { + case {{{ cDefs.AL_DOPPLER_FACTOR }}}: + case {{{ cDefs.AL_SPEED_OF_SOUND }}}: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValues', '0', 'val', 'i8') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetBooleanv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alDistanceModel__proxy: 'sync', + alDistanceModel: (model) => { + AL.setGlobalParam('alDistanceModel', {{{ cDefs.AL_DISTANCE_MODEL }}}, model); + }, + + alSpeedOfSound__proxy: 'sync', + alSpeedOfSound: (value) => { + AL.setGlobalParam('alSpeedOfSound', {{{ cDefs.AL_SPEED_OF_SOUND }}}, value); + }, + + alDopplerFactor__proxy: 'sync', + alDopplerFactor: (value) => { + AL.setGlobalParam('alDopplerFactor', {{{ cDefs.AL_DOPPLER_FACTOR }}}, value); + }, + + // http://openal.996291.n3.nabble.com/alSpeedOfSound-or-alDopperVelocity-tp1960.html + // alDopplerVelocity() sets a multiplier for the speed of sound. + // It's deprecated since it's equivalent to directly calling + // alSpeedOfSound() with an appropriately premultiplied value. + alDopplerVelocity__proxy: 'sync', + alDopplerVelocity: (value) => { + warnOnce('alDopplerVelocity() is deprecated, and only kept for compatibility with OpenAL 1.0. Use alSpeedOfSound() instead.'); + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alDopplerVelocity() called without a valid context'); +#endif + return; + } + if (value <= 0) { // Negative or zero values are disallowed + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + }, + + // ------------------------------------------------------- + // -- AL Listener State + // ------------------------------------------------------- + + alGetListenerf__proxy: 'sync', + alGetListenerf: (param, pValue) => { + var val = AL.getListenerParam('alGetListenerf', param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetListenerf() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_GAIN }}}: + {{{ makeSetValue('pValue', '0', 'val', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetListenerf(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetListener3f__proxy: 'sync', + alGetListener3f: (param, pValue0, pValue1, pValue2) => { + var val = AL.getListenerParam('alGetListener3f', param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetListener3f() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValue0', '0', 'val[0]', 'float') }}}; + {{{ makeSetValue('pValue1', '0', 'val[1]', 'float') }}}; + {{{ makeSetValue('pValue2', '0', 'val[2]', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetListener3f(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetListenerfv__proxy: 'sync', + alGetListenerfv: (param, pValues) => { + var val = AL.getListenerParam('alGetListenerfv', param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetListenerfv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; + break; + case {{{ cDefs.AL_ORIENTATION }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; + {{{ makeSetValue('pValues', '12', 'val[3]', 'float') }}}; + {{{ makeSetValue('pValues', '16', 'val[4]', 'float') }}}; + {{{ makeSetValue('pValues', '20', 'val[5]', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetListenerfv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetListeneri__proxy: 'sync', + alGetListeneri: (param, pValue) => { + var val = AL.getListenerParam('alGetListeneri', param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetListeneri() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + +#if OPENAL_DEBUG + dbg(`alGetListeneri(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + }, + + alGetListener3i__proxy: 'sync', + alGetListener3i: (param, pValue0, pValue1, pValue2) => { + var val = AL.getListenerParam('alGetListener3i', param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetListener3i() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValue0', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValue1', '0', 'val[1]', 'i32') }}}; + {{{ makeSetValue('pValue2', '0', 'val[2]', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetListener3i(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetListeneriv__proxy: 'sync', + alGetListeneriv: (param, pValues) => { + var val = AL.getListenerParam('alGetListeneriv', param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetListeneriv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; + break; + case {{{ cDefs.AL_ORIENTATION }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; + {{{ makeSetValue('pValues', '12', 'val[3]', 'i32') }}}; + {{{ makeSetValue('pValues', '16', 'val[4]', 'i32') }}}; + {{{ makeSetValue('pValues', '20', 'val[5]', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetListeneriv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alListenerf__proxy: 'sync', + alListenerf: (param, value) => { + switch (param) { + case {{{ cDefs.AL_GAIN }}}: + AL.setListenerParam('alListenerf', param, value); + break; + default: + AL.setListenerParam('alListenerf', param, null); + break; + } + }, + + alListener3f__proxy: 'sync', + alListener3f: (param, value0, value1, value2) => { + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setListenerParam('alListener3f', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListener3f', param, null); + break; + } + }, + + alListenerfv__proxy: 'sync', + alListenerfv: (param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alListenerfv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alListenerfv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; + AL.setListenerParam('alListenerfv', param, AL.paramArray); + break; + case {{{ cDefs.AL_ORIENTATION }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; + AL.paramArray[3] = {{{ makeGetValue('pValues', '12', 'float') }}}; + AL.paramArray[4] = {{{ makeGetValue('pValues', '16', 'float') }}}; + AL.paramArray[5] = {{{ makeGetValue('pValues', '20', 'float') }}}; + AL.setListenerParam('alListenerfv', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListenerfv', param, null); + break; + } + }, + + alListeneri__proxy: 'sync', + alListeneri: (param, value) => { + AL.setListenerParam('alListeneri', param, null); + }, + + alListener3i__proxy: 'sync', + alListener3i: (param, value0, value1, value2) => { + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setListenerParam('alListener3i', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListener3i', param, null); + break; + } + }, + + alListeneriv__proxy: 'sync', + alListeneriv: (param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alListeneriv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alListeneriv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; + AL.setListenerParam('alListeneriv', param, AL.paramArray); + break; + case {{{ cDefs.AL_ORIENTATION }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; + AL.paramArray[3] = {{{ makeGetValue('pValues', '12', 'i32') }}}; + AL.paramArray[4] = {{{ makeGetValue('pValues', '16', 'i32') }}}; + AL.paramArray[5] = {{{ makeGetValue('pValues', '20', 'i32') }}}; + AL.setListenerParam('alListeneriv', param, AL.paramArray); + break; + default: + AL.setListenerParam('alListeneriv', param, null); + break; + } + }, + + // ------------------------------------------------------- + // -- AL Buffer State + // ------------------------------------------------------- + + alIsBuffer__proxy: 'sync', + alIsBuffer: (bufferId) => { + if (!AL.currentCtx) { + return false; + } + if (bufferId > AL.buffers.length) { + return false; + } + + if (!AL.buffers[bufferId]) { + return false; + } + return true; + }, + + alBufferData__proxy: 'sync', + alBufferData: (bufferId, format, pData, size, freq) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alBufferData() called without a valid context'); +#endif + return; + } + var buf = AL.buffers[bufferId]; + if (!buf) { +#if OPENAL_DEBUG + dbg('alBufferData() called with an invalid buffer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + if (freq <= 0) { +#if OPENAL_DEBUG + dbg('alBufferData() called with an invalid frequency'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + var audioBuf = null; + try { + switch (format) { + case 0x1100 /* AL_FORMAT_MONO8 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size, freq); + var channel0 = audioBuf.getChannelData(0); + for (var i = 0; i < size; ++i) { + channel0[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + } + } + buf.bytesPerSample = 1; + buf.channels = 1; + buf.length = size; + break; + case 0x1101 /* AL_FORMAT_MONO16 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size >> 1, freq); + var channel0 = audioBuf.getChannelData(0); + pData >>= 1; + for (var i = 0; i < size >> 1; ++i) { + channel0[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; + } + } + buf.bytesPerSample = 2; + buf.channels = 1; + buf.length = size >> 1; + break; + case 0x1102 /* AL_FORMAT_STEREO8 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 1, freq); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + for (var i = 0; i < size >> 1; ++i) { + channel0[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + channel1[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; + } + } + buf.bytesPerSample = 1; + buf.channels = 2; + buf.length = size >> 1; + break; + case 0x1103 /* AL_FORMAT_STEREO16 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 2, freq); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + pData >>= 1; + for (var i = 0; i < size >> 2; ++i) { + channel0[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; + channel1[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; + } + } + buf.bytesPerSample = 2; + buf.channels = 2; + buf.length = size >> 2; + break; + case 0x10010 /* AL_FORMAT_MONO_FLOAT32 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size >> 2, freq); + var channel0 = audioBuf.getChannelData(0); + pData >>= 2; + for (var i = 0; i < size >> 2; ++i) { + channel0[i] = HEAPF32[pData++]; + } + } + buf.bytesPerSample = 4; + buf.channels = 1; + buf.length = size >> 2; + break; + case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */: + if (size > 0) { + audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 3, freq); + var channel0 = audioBuf.getChannelData(0); + var channel1 = audioBuf.getChannelData(1); + pData >>= 2; + for (var i = 0; i < size >> 3; ++i) { + channel0[i] = HEAPF32[pData++]; + channel1[i] = HEAPF32[pData++]; + } + } + buf.bytesPerSample = 4; + buf.channels = 2; + buf.length = size >> 3; + break; + default: +#if OPENAL_DEBUG + dbg(`alBufferData() called with invalid format ${format}`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + buf.frequency = freq; + buf.audioBuf = audioBuf; + } catch (e) { +#if OPENAL_DEBUG + dbg(`alBufferData() upload failed with an exception ${e}`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + }, + + alGetBufferf__proxy: 'sync', + alGetBufferf: (bufferId, param, pValue) => { + var val = AL.getBufferParam('alGetBufferf', bufferId, param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetBufferf() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + +#if OPENAL_DEBUG + dbg(`alGetBufferf(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + }, + + alGetBuffer3f__proxy: 'sync', + alGetBuffer3f: (bufferId, param, pValue0, pValue1, pValue2) => { + var val = AL.getBufferParam('alGetBuffer3f', bufferId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetBuffer3f() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + +#if OPENAL_DEBUG + dbg(`alGetBuffer3f(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + }, + + alGetBufferfv__proxy: 'sync', + alGetBufferfv: (bufferId, param, pValues) => { + var val = AL.getBufferParam('alGetBufferfv', bufferId, param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetBufferfv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + +#if OPENAL_DEBUG + dbg(`alGetBufferfv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + }, + + alGetBufferi__proxy: 'sync', + alGetBufferi: (bufferId, param, pValue) => { + var val = AL.getBufferParam('alGetBufferi', bufferId, param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetBufferi() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + case 0x2002 /* AL_BITS */: + case 0x2003 /* AL_CHANNELS */: + case 0x2004 /* AL_SIZE */: + {{{ makeSetValue('pValue', '0', 'val', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetBufferi(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetBuffer3i__proxy: 'sync', + alGetBuffer3i: (bufferId, param, pValue0, pValue1, pValue2) => { + var val = AL.getBufferParam('alGetBuffer3i', bufferId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetBuffer3i() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + +#if OPENAL_DEBUG + dbg(`alGetBuffer3i(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + }, + + alGetBufferiv__proxy: 'sync', + alGetBufferiv: (bufferId, param, pValues) => { + var val = AL.getBufferParam('alGetBufferiv', bufferId, param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetBufferiv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x2001 /* AL_FREQUENCY */: + case 0x2002 /* AL_BITS */: + case 0x2003 /* AL_CHANNELS */: + case 0x2004 /* AL_SIZE */: + {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; + break; + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetBufferiv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + // All of the remaining alBuffer* setters and getters are only of interest + // to extensions which need them. Core OpenAL alone defines no valid + // property for these. + + alBufferf__proxy: 'sync', + alBufferf: (bufferId, param, value) => { + AL.setBufferParam('alBufferf', bufferId, param, null); + }, + + alBuffer3f__proxy: 'sync', + alBuffer3f: (bufferId, param, value0, value1, value2) => { + AL.setBufferParam('alBuffer3f', bufferId, param, null); + }, + + alBufferfv__proxy: 'sync', + alBufferfv: (bufferId, param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alBufferfv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alBufferfv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + AL.setBufferParam('alBufferfv', bufferId, param, null); + }, + + alBufferi__proxy: 'sync', + alBufferi: (bufferId, param, value) => { + AL.setBufferParam('alBufferi', bufferId, param, null); + }, + + alBuffer3i__proxy: 'sync', + alBuffer3i: (bufferId, param, value0, value1, value2) => { + AL.setBufferParam('alBuffer3i', bufferId, param, null); + }, + + alBufferiv__proxy: 'sync', + alBufferiv: (bufferId, param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alBufferiv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alBufferiv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x2015 /* AL_LOOP_POINTS_SOFT */: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; + AL.setBufferParam('alBufferiv', bufferId, param, AL.paramArray); + break; + default: + AL.setBufferParam('alBufferiv', bufferId, param, null); + break; + } + }, + + // ------------------------------------------------------- + // -- AL Source State + // ------------------------------------------------------- + + alIsSource__proxy: 'sync', + alIsSource: (sourceId) => { + if (!AL.currentCtx) { + return false; + } + + if (!AL.currentCtx.sources[sourceId]) { + return false; + } + return true; + }, + + alSourceQueueBuffers__proxy: 'sync', + alSourceQueueBuffers: (sourceId, count, pBufferIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceQueueBuffers() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourceQueueBuffers() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + if (src.type === {{{ cDefs.AL_STATIC }}}) { +#if OPENAL_DEBUG + dbg('alSourceQueueBuffers() called while a static buffer is bound'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + return; + } + + if (count === 0) { + return; + } + + // Find the first non-zero buffer in the queue to determine the proper format + var templateBuf = AL.buffers[0]; + for (var buf of src.bufQueue) { + if (buf.id !== 0) { + templateBuf = buf; + break; + } + } + + for (var i = 0; i < count; ++i) { + var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; + var buf = AL.buffers[bufId]; + if (!buf) { +#if OPENAL_DEBUG + dbg('alSourceQueueBuffers() called with an invalid buffer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + + // Check that the added buffer has the correct format. If the template is the zero buffer, any format is valid. + if (templateBuf.id !== 0 && ( + buf.frequency !== templateBuf.frequency + || buf.bytesPerSample !== templateBuf.bytesPerSample + || buf.channels !== templateBuf.channels) + ) { +#if OPENAL_DEBUG + dbg('alSourceQueueBuffers() called with a buffer of different format'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; + } + } + + // If the only buffer in the queue is the zero buffer, clear the queue before we add anything. + if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { + src.bufQueue.length = 0; + } + + src.type = 0x1029 /* AL_STREAMING */; + for (var i = 0; i < count; ++i) { + var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; + var buf = AL.buffers[bufId]; + buf.refCount++; + src.bufQueue.push(buf); + } + + // if the source is looping, cancel the schedule so we can reschedule the loop order + if (src.looping) { + AL.cancelPendingSourceAudio(src); + } + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + }, + + alSourceUnqueueBuffers__proxy: 'sync', + alSourceUnqueueBuffers: (sourceId, count, pBufferIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceUnqueueBuffers() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourceUnqueueBuffers() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + if (count > (src.bufQueue.length === 1 && src.bufQueue[0].id === 0 ? 0 : src.bufsProcessed)) { + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + if (count === 0) { + return; + } + + for (var i = 0; i < count; i++) { + var buf = src.bufQueue.shift(); + buf.refCount--; + // Write the buffers index out to the return list. + {{{ makeSetValue('pBufferIds', 'i*4', 'buf.id', 'i32') }}}; + src.bufsProcessed--; + } + + /// If the queue is empty, put the zero buffer back in + if (src.bufQueue.length === 0) { + src.bufQueue.push(AL.buffers[0]); + } + + AL.initSourcePanner(src); + AL.scheduleSourceAudio(src); + }, + + alSourcePlay__proxy: 'sync', + alSourcePlay: (sourceId) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourcePlay() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourcePlay() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + AL.setSourceState(src, {{{ cDefs.AL_PLAYING }}}); + }, + + alSourcePlayv__proxy: 'sync', + alSourcePlayv: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourcePlayv() called without a valid context'); +#endif + return; + } + if (!pSourceIds) { +#if OPENAL_DEBUG + dbg('alSourcePlayv() called with null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { +#if OPENAL_DEBUG + dbg('alSourcePlayv() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_PLAYING }}}); + } + }, + + alSourceStop__proxy: 'sync', + alSourceStop: (sourceId) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceStop() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourceStop() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); + }, + + alSourceStopv__proxy: 'sync', + alSourceStopv: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceStopv() called without a valid context'); +#endif + return; + } + if (!pSourceIds) { +#if OPENAL_DEBUG + dbg('alSourceStopv() called with null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { +#if OPENAL_DEBUG + dbg('alSourceStopv() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_STOPPED }}}); + } + }, + + alSourceRewind__proxy: 'sync', + alSourceRewind: (sourceId) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceRewind() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourceRewind() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + // Stop the source first to clear the source queue + AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); + // Now set the state of AL_INITIAL according to the specification + AL.setSourceState(src, {{{ cDefs.AL_INITIAL }}}); + }, + + alSourceRewindv__proxy: 'sync', + alSourceRewindv: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceRewindv() called without a valid context'); +#endif + return; + } + if (!pSourceIds) { +#if OPENAL_DEBUG + dbg('alSourceRewindv() called with null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { +#if OPENAL_DEBUG + dbg('alSourceRewindv() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_INITIAL }}}); + } + }, + + alSourcePause__proxy: 'sync', + alSourcePause: (sourceId) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourcePause() called without a valid context'); +#endif + return; + } + var src = AL.currentCtx.sources[sourceId]; + if (!src) { +#if OPENAL_DEBUG + dbg('alSourcePause() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + AL.setSourceState(src, {{{ cDefs.AL_PAUSED }}}); + }, + + alSourcePausev__proxy: 'sync', + alSourcePausev: (count, pSourceIds) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourcePausev() called without a valid context'); +#endif + return; + } + if (!pSourceIds) { +#if OPENAL_DEBUG + dbg('alSourcePausev() called with null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + } + for (var i = 0; i < count; ++i) { + if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { +#if OPENAL_DEBUG + dbg('alSourcePausev() called with an invalid source'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; + return; + } + } + + for (var i = 0; i < count; ++i) { + var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; + AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_PAUSED }}}); + } + }, + + alGetSourcef__proxy: 'sync', + alGetSourcef: (sourceId, param, pValue) => { + var val = AL.getSourceParam('alGetSourcef', sourceId, param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetSourcef() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case {{{ cDefs.AL_GAIN }}}: + case 0x100D /* AL_MIN_GAIN */: + case 0x100E /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200B /* AL_SEC_LENGTH_SOFT */: + {{{ makeSetValue('pValue', '0', 'val', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSourcef(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetSource3f__proxy: 'sync', + alGetSource3f: (sourceId, param, pValue0, pValue1, pValue2) => { + var val = AL.getSourceParam('alGetSource3f', sourceId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetSource3f() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValue0', '0', 'val[0]', 'float') }}}; + {{{ makeSetValue('pValue1', '0', 'val[1]', 'float') }}}; + {{{ makeSetValue('pValue2', '0', 'val[2]', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSource3f(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetSourcefv__proxy: 'sync', + alGetSourcefv: (sourceId, param, pValues) => { + var val = AL.getSourceParam('alGetSourcefv', sourceId, param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetSourcefv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case {{{ cDefs.AL_GAIN }}}: + case 0x100D /* AL_MIN_GAIN */: + case 0x100E /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200B /* AL_SEC_LENGTH_SOFT */: + {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; + break; + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSourcefv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetSourcei__proxy: 'sync', + alGetSourcei: (sourceId, param, pValue) => { + var val = AL.getSourceParam('alGetSourcei', sourceId, param); + if (val === null) { + return; + } + if (!pValue) { +#if OPENAL_DEBUG + dbg('alGetSourcei() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1010 /* AL_SOURCE_STATE */: + case 0x1015 /* AL_BUFFERS_QUEUED */: + case 0x1016 /* AL_BUFFERS_PROCESSED */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1027 /* AL_SOURCE_TYPE */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValue', '0', 'val', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSourcei(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetSource3i__proxy: 'sync', + alGetSource3i: (sourceId, param, pValue0, pValue1, pValue2) => { + var val = AL.getSourceParam('alGetSource3i', sourceId, param); + if (val === null) { + return; + } + if (!pValue0 || !pValue1 || !pValue2) { +#if OPENAL_DEBUG + dbg('alGetSource3i() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValue0', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValue1', '0', 'val[1]', 'i32') }}}; + {{{ makeSetValue('pValue2', '0', 'val[2]', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSource3i(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alGetSourceiv__proxy: 'sync', + alGetSourceiv: (sourceId, param, pValues) => { + var val = AL.getSourceParam('alGetSourceiv', sourceId, param); + if (val === null) { + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alGetSourceiv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1010 /* AL_SOURCE_STATE */: + case 0x1015 /* AL_BUFFERS_QUEUED */: + case 0x1016 /* AL_BUFFERS_PROCESSED */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1027 /* AL_SOURCE_TYPE */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; + break; + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; + {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; + {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; + break; + default: +#if OPENAL_DEBUG + dbg(`alGetSourceiv(): param ${ptrToString(param)} has wrong signature`); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; + return; + } + }, + + alSourcef__proxy: 'sync', + alSourcef: (sourceId, param, value) => { + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case {{{ cDefs.AL_GAIN }}}: + case 0x100D /* AL_MIN_GAIN */: + case 0x100E /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200B /* AL_SEC_LENGTH_SOFT */: + AL.setSourceParam('alSourcef', sourceId, param, value); + break; + default: + AL.setSourceParam('alSourcef', sourceId, param, null); + break; + } + }, + + alSource3f__proxy: 'sync', + alSource3f: (sourceId, param, value0, value1, value2) => { + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setSourceParam('alSource3f', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSource3f', sourceId, param, null); + break; + } + }, + + alSourcefv__proxy: 'sync', + alSourcefv: (sourceId, param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourcefv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alSourcefv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1003 /* AL_PITCH */: + case {{{ cDefs.AL_GAIN }}}: + case 0x100D /* AL_MIN_GAIN */: + case 0x100E /* AL_MAX_GAIN */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1022 /* AL_CONE_OUTER_GAIN */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x200B /* AL_SEC_LENGTH_SOFT */: + var val = {{{ makeGetValue('pValues', '0', 'float') }}}; + AL.setSourceParam('alSourcefv', sourceId, param, val); + break; + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; + AL.setSourceParam('alSourcefv', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSourcefv', sourceId, param, null); + break; + } + }, + + alSourcei__proxy: 'sync', + alSourcei: (sourceId, param, value) => { + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + AL.setSourceParam('alSourcei', sourceId, param, value); + break; + default: + AL.setSourceParam('alSourcei', sourceId, param, null); + break; + } + }, + + alSource3i__proxy: 'sync', + alSource3i: (sourceId, param, value0, value1, value2) => { + switch (param) { + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = value0; + AL.paramArray[1] = value1; + AL.paramArray[2] = value2; + AL.setSourceParam('alSource3i', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSource3i', sourceId, param, null); + break; + } + }, + + alSourceiv__proxy: 'sync', + alSourceiv: (sourceId, param, pValues) => { + if (!AL.currentCtx) { +#if OPENAL_DEBUG + dbg('alSourceiv() called without a valid context'); +#endif + return; + } + if (!pValues) { +#if OPENAL_DEBUG + dbg('alSourceiv() called with a null pointer'); +#endif + AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; + return; + } + + switch (param) { + case 0x202 /* AL_SOURCE_RELATIVE */: + case 0x1001 /* AL_CONE_INNER_ANGLE */: + case 0x1002 /* AL_CONE_OUTER_ANGLE */: + case 0x1007 /* AL_LOOPING */: + case 0x1009 /* AL_BUFFER */: + case 0x1020 /* AL_REFERENCE_DISTANCE */: + case 0x1021 /* AL_ROLLOFF_FACTOR */: + case 0x1023 /* AL_MAX_DISTANCE */: + case 0x1024 /* AL_SEC_OFFSET */: + case 0x1025 /* AL_SAMPLE_OFFSET */: + case 0x1026 /* AL_BYTE_OFFSET */: + case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: + case 0x2009 /* AL_BYTE_LENGTH_SOFT */: + case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: + case {{{ cDefs.AL_DISTANCE_MODEL }}}: + var val = {{{ makeGetValue('pValues', '0', 'i32') }}}; + AL.setSourceParam('alSourceiv', sourceId, param, val); + break; + case {{{ cDefs.AL_POSITION }}}: + case {{{ cDefs.AL_DIRECTION }}}: + case {{{ cDefs.AL_VELOCITY }}}: + AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; + AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; + AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; + AL.setSourceParam('alSourceiv', sourceId, param, AL.paramArray); + break; + default: + AL.setSourceParam('alSourceiv', sourceId, param, null); + break; + } + } +}; + +autoAddDeps(LibraryOpenAL, '$AL'); +addToLibrary(LibraryOpenAL); diff --git a/src/library_opfs.js b/src/lib/libopfs.js similarity index 100% rename from src/library_opfs.js rename to src/lib/libopfs.js diff --git a/src/lib/libpath.js b/src/lib/libpath.js new file mode 100644 index 0000000000000..1905aae0bf1af --- /dev/null +++ b/src/lib/libpath.js @@ -0,0 +1,136 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $PATH: { + isAbs: (path) => path.charAt(0) === '/', + // split a filename into [root, dir, basename, ext], unix version + // 'root' is just a slash, or nothing. + splitPath: (filename) => { + var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: (parts, allowAboveRoot) => { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up; up--) { + parts.unshift('..'); + } + } + return parts; + }, + normalize: (path) => { + var isAbsolute = PATH.isAbs(path), + trailingSlash = path.slice(-1) === '/'; + // Normalize the path + path = PATH.normalizeArray(path.split('/').filter((p) => !!p), !isAbsolute).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; + }, + dirname: (path) => { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.slice(0, -1); + } + return root + dir; + }, + // This differs from node's path.basename in that it returns '/' for '/' + // rather than the empty string. + basename: (path) => path && path.match(/([^\/]+|\/)\/*$/)[1], + join: (...paths) => PATH.normalize(paths.join('/')), + join2: (l, r) => PATH.normalize(l + '/' + r), + }, + // The FS-using parts are split out into a separate object, so simple path + // usage does not require the FS. + $PATH_FS__deps: [ + '$PATH', + '$FS', +#if WASMFS + // In WasmFS, FS.cwd() is implemented via a call into wasm, so we need to + // add a dependency on that. + '_wasmfs_get_cwd', +#endif + ], + $PATH_FS: { + resolve: (...args) => { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? args[i] : FS.cwd(); + // Skip empty and invalid entries + if (typeof path != 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + return ''; // an invalid portion invalidates the whole thing + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = PATH.isAbs(path); + } + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter((p) => !!p), !resolvedAbsolute).join('/'); + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; + }, + relative: (from, to) => { + from = PATH_FS.resolve(from).slice(1); + to = PATH_FS.resolve(to).slice(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + } + } +}); diff --git a/src/lib/libpipefs.js b/src/lib/libpipefs.js new file mode 100644 index 0000000000000..7de0c8817f5f2 --- /dev/null +++ b/src/lib/libpipefs.js @@ -0,0 +1,281 @@ +/** + * @license + * Copyright 2017 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $PIPEFS__postset: () => addAtInit('PIPEFS.root = FS.mount(PIPEFS, {}, null);'), + $PIPEFS__deps: ['$FS'], + $PIPEFS: { + BUCKET_BUFFER_SIZE: 1024 * 8, // 8KiB Buffer + mount(mount) { + // Do not pollute the real root directory or its child nodes with pipes + // Looks like it is OK to create another pseudo-root node not linked to the FS.root hierarchy this way + return FS.createNode(null, '/', {{{ cDefs.S_IFDIR }}} | 0o777, 0); + }, + createPipe() { + var pipe = { + buckets: [], + // refcnt 2 because pipe has a read end and a write end. We need to be + // able to read from the read end after write end is closed. + refcnt : 2, + timestamp: new Date(), +#if PTHREADS || ASYNCIFY + readableHandlers: [], + registerReadableHandler: (callback) => { + callback.registerCleanupFunc(() => { + const i = pipe.readableHandlers.indexOf(callback); + if (i !== -1) pipe.readableHandlers.splice(i, 1); + }); + pipe.readableHandlers.push(callback); + }, + notifyReadableHandlers: () => { + while (pipe.readableHandlers.length > 0) { + const cb = pipe.readableHandlers.shift(); + if (cb) cb({{{ cDefs.POLLRDNORM }}} | {{{ cDefs.POLLIN }}}); + } + pipe.readableHandlers = []; + } +#endif + }; + + pipe.buckets.push({ + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0 + }); + + var rName = PIPEFS.nextname(); + var wName = PIPEFS.nextname(); + var rNode = FS.createNode(PIPEFS.root, rName, {{{ cDefs.S_IFIFO }}}, 0); + var wNode = FS.createNode(PIPEFS.root, wName, {{{ cDefs.S_IFIFO }}}, 0); + + rNode.pipe = pipe; + wNode.pipe = pipe; + + var readableStream = FS.createStream({ + path: rName, + node: rNode, + flags: {{{ cDefs.O_RDONLY }}}, + seekable: false, + stream_ops: PIPEFS.stream_ops + }); + rNode.stream = readableStream; + + var writableStream = FS.createStream({ + path: wName, + node: wNode, + flags: {{{ cDefs.O_WRONLY }}}, + seekable: false, + stream_ops: PIPEFS.stream_ops + }); + wNode.stream = writableStream; + + return { + readable_fd: readableStream.fd, + writable_fd: writableStream.fd + }; + }, + stream_ops: { + getattr(stream) { + var node = stream.node; + var timestamp = node.pipe.timestamp; + return { + dev: 14, + ino: node.id, + mode: 0o10600, + nlink: 1, + uid: 0, + gid: 0, + rdev: 0, + size: 0, + atime: timestamp, + mtime: timestamp, + ctime: timestamp, + blksize: 4096, + blocks: 0, + }; + }, + poll(stream, timeout, notifyCallback) { + var pipe = stream.node.pipe; + + if ((stream.flags & {{{ cDefs.O_ACCMODE }}}) === {{{ cDefs.O_WRONLY }}}) { + return ({{{ cDefs.POLLWRNORM }}} | {{{ cDefs.POLLOUT }}}); + } + for (var bucket of pipe.buckets) { + if (bucket.offset - bucket.roffset > 0) { + return ({{{ cDefs.POLLRDNORM }}} | {{{ cDefs.POLLIN }}}); + } + } + +#if PTHREADS || ASYNCIFY + if (notifyCallback) pipe.registerReadableHandler(notifyCallback); +#endif + return 0; + }, + dup(stream) { + stream.node.pipe.refcnt++; + }, + ioctl(stream, request, varargs) { + return {{{ cDefs.EINVAL }}}; + }, + fsync(stream) { + return {{{ cDefs.EINVAL }}}; + }, + read(stream, buffer, offset, length, position /* ignored */) { + var pipe = stream.node.pipe; + var currentLength = 0; + + for (var bucket of pipe.buckets) { + currentLength += bucket.offset - bucket.roffset; + } + +#if ASSERTIONS && !(MEMORY64 && MAXIMUM_MEMORY > FOUR_GB) +#if PTHREADS + assert(buffer instanceof ArrayBuffer || buffer instanceof SharedArrayBuffer || ArrayBuffer.isView(buffer)); +#else + assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer)); +#endif +#endif + var data = buffer.subarray(offset, offset + length); + + if (length <= 0) { + return 0; + } + if (currentLength == 0) { + // Behave as if the read end is always non-blocking + throw new FS.ErrnoError({{{ cDefs.EAGAIN }}}); + } + var toRead = Math.min(currentLength, length); + + var totalRead = toRead; + var toRemove = 0; + + for (var bucket of pipe.buckets) { + var bucketSize = bucket.offset - bucket.roffset; + + if (toRead <= bucketSize) { + var tmpSlice = bucket.buffer.subarray(bucket.roffset, bucket.offset); + if (toRead < bucketSize) { + tmpSlice = tmpSlice.subarray(0, toRead); + bucket.roffset += toRead; + } else { + toRemove++; + } + data.set(tmpSlice); + break; + } else { + var tmpSlice = bucket.buffer.subarray(bucket.roffset, bucket.offset); + data.set(tmpSlice); + data = data.subarray(tmpSlice.byteLength); + toRead -= tmpSlice.byteLength; + toRemove++; + } + } + + if (toRemove && toRemove == pipe.buckets.length) { + // Do not generate excessive garbage in use cases such as + // write several bytes, read everything, write several bytes, read everything... + toRemove--; + pipe.buckets[toRemove].offset = 0; + pipe.buckets[toRemove].roffset = 0; + } + + pipe.buckets.splice(0, toRemove); + + return totalRead; + }, + write(stream, buffer, offset, length, position /* ignored */) { + var pipe = stream.node.pipe; + +#if ASSERTIONS && !(MEMORY64 && MAXIMUM_MEMORY > FOUR_GB) +#if PTHREADS + assert(buffer instanceof ArrayBuffer || buffer instanceof SharedArrayBuffer || ArrayBuffer.isView(buffer)); +#else + assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer)); +#endif +#endif + var data = buffer.subarray(offset, offset + length); + + var dataLen = data.byteLength; + if (dataLen <= 0) { + return 0; + } + + var currBucket = null; + + if (pipe.buckets.length == 0) { + currBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0 + }; + pipe.buckets.push(currBucket); + } else { + currBucket = pipe.buckets[pipe.buckets.length - 1]; + } + +#if ASSERTIONS + assert(currBucket.offset <= PIPEFS.BUCKET_BUFFER_SIZE); +#endif + + var freeBytesInCurrBuffer = PIPEFS.BUCKET_BUFFER_SIZE - currBucket.offset; + if (freeBytesInCurrBuffer >= dataLen) { + currBucket.buffer.set(data, currBucket.offset); + currBucket.offset += dataLen; +#if PTHREADS || ASYNCIFY + pipe.notifyReadableHandlers(); +#endif + return dataLen; + } else if (freeBytesInCurrBuffer > 0) { + currBucket.buffer.set(data.subarray(0, freeBytesInCurrBuffer), currBucket.offset); + currBucket.offset += freeBytesInCurrBuffer; + data = data.subarray(freeBytesInCurrBuffer, data.byteLength); + } + + var numBuckets = (data.byteLength / PIPEFS.BUCKET_BUFFER_SIZE) | 0; + var remElements = data.byteLength % PIPEFS.BUCKET_BUFFER_SIZE; + + for (var i = 0; i < numBuckets; i++) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: PIPEFS.BUCKET_BUFFER_SIZE, + roffset: 0 + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set(data.subarray(0, PIPEFS.BUCKET_BUFFER_SIZE)); + data = data.subarray(PIPEFS.BUCKET_BUFFER_SIZE, data.byteLength); + } + + if (remElements > 0) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: data.byteLength, + roffset: 0 + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set(data); + } + +#if PTHREADS || ASYNCIFY + pipe.notifyReadableHandlers(); +#endif + return dataLen; + }, + close(stream) { + var pipe = stream.node.pipe; + pipe.refcnt--; + if (pipe.refcnt === 0) { + pipe.buckets = null; + } + } + }, + nextname() { + if (!PIPEFS.nextname.current) { + PIPEFS.nextname.current = 0; + } + return 'pipe[' + (PIPEFS.nextname.current++) + ']'; + }, + }, +}); diff --git a/src/lib/libpromise.js b/src/lib/libpromise.js new file mode 100644 index 0000000000000..0ea127ce84c48 --- /dev/null +++ b/src/lib/libpromise.js @@ -0,0 +1,277 @@ +/** + * @license + * Copyright 2023 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $promiseMap__deps: ['$HandleAllocator'], + $promiseMap: "new HandleAllocator();", + + $getPromise__deps: ['$promiseMap'], + $getPromise: (id) => promiseMap.get(id).promise, + + $makePromise__deps: ['$promiseMap'], + $makePromise: () => { + var promiseInfo = {}; + promiseInfo.promise = new Promise((resolve, reject) => { + promiseInfo.reject = reject; + promiseInfo.resolve = resolve; + }); + promiseInfo.id = promiseMap.allocate(promiseInfo); +#if RUNTIME_DEBUG + dbg(`makePromise: ${promiseInfo.id}`); +#endif + return promiseInfo; + }, + + $idsToPromises__deps: ['$getPromise'], + $idsToPromises: (idBuf, size) => { + var promises = []; + for (var i = 0; i < size; i++) { + var id = {{{ makeGetValue('idBuf', `i*${POINTER_SIZE}`, 'i32') }}}; + promises[i] = getPromise(id); + } + return promises; + }, + + emscripten_promise_create__deps: ['$makePromise'], + emscripten_promise_create: () => makePromise().id, + + emscripten_promise_destroy__deps: ['$promiseMap'], + emscripten_promise_destroy: (id) => { +#if RUNTIME_DEBUG + dbg(`emscripten_promise_destroy: ${id}`); +#endif + promiseMap.free(id); + }, + + emscripten_promise_resolve__deps: ['$promiseMap', + '$getPromise', + 'emscripten_promise_destroy'], + emscripten_promise_resolve: (id, result, value) => { +#if RUNTIME_DEBUG + dbg(`emscripten_promise_resolve: ${id}`); +#endif + var info = promiseMap.get(id); + switch (result) { + case {{{ cDefs.EM_PROMISE_FULFILL }}}: + info.resolve(value); + return; + case {{{ cDefs.EM_PROMISE_MATCH }}}: + info.resolve(getPromise(value)); + return; + case {{{ cDefs.EM_PROMISE_MATCH_RELEASE }}}: + info.resolve(getPromise(value)); + _emscripten_promise_destroy(value); + return; + case {{{ cDefs.EM_PROMISE_REJECT }}}: + info.reject(value); + return; + } +#if ASSERTIONS + abort("unexpected promise callback result " + result); +#endif + }, + + $makePromiseCallback__deps: ['$getPromise', + '$POINTER_SIZE', + 'emscripten_promise_destroy', + '$stackAlloc', + '$stackRestore', + '$stackSave'], + $makePromiseCallback: (callback, userData) => { + return (value) => { +#if RUNTIME_DEBUG + dbg(`emscripten promise callback: ${value}`); +#endif + {{{ runtimeKeepalivePop() }}}; + var stack = stackSave(); + // Allocate space for the result value and initialize it to NULL. + var resultPtr = stackAlloc(POINTER_SIZE); + {{{ makeSetValue('resultPtr', 0, '0', '*') }}}; + try { + var result = + {{{ makeDynCall('ippp', 'callback') }}}(resultPtr, userData, value); + var resultVal = {{{ makeGetValue('resultPtr', 0, '*') }}}; + } catch (e) { + // If the thrown value is potentially a valid pointer, use it as the + // rejection reason. Otherwise use a null pointer as the reason. If we + // allow arbitrary objects to be thrown here, we will get a TypeError in + // MEMORY64 mode when they are later converted to void* rejection + // values. +#if MEMORY64 + if (typeof e != 'bigint') { + throw 0n; + } +#else + if (typeof e != 'number') { + throw 0; + } +#endif + throw e; + } finally { + // Thrown errors will reject the promise, but at least we will restore + // the stack first. + stackRestore(stack); + } + switch (result) { + case {{{ cDefs.EM_PROMISE_FULFILL }}}: + return resultVal; + case {{{ cDefs.EM_PROMISE_MATCH }}}: + return getPromise(resultVal); + case {{{ cDefs.EM_PROMISE_MATCH_RELEASE }}}: + var ret = getPromise(resultVal); + _emscripten_promise_destroy(resultVal); + return ret; + case {{{ cDefs.EM_PROMISE_REJECT }}}: + throw resultVal; + } +#if ASSERTIONS + abort("unexpected promise callback result " + result); +#endif + }; + }, + + emscripten_promise_then__deps: ['$promiseMap', + '$getPromise', + '$makePromiseCallback'], + emscripten_promise_then: (id, onFulfilled, onRejected, userData) => { +#if RUNTIME_DEBUG + dbg(`emscripten_promise_then: ${id}`); +#endif + {{{ runtimeKeepalivePush() }}}; + var promise = getPromise(id); + var newId = promiseMap.allocate({ + promise: promise.then(makePromiseCallback(onFulfilled, userData), + makePromiseCallback(onRejected, userData)) + }); +#if RUNTIME_DEBUG + dbg(`emscripten_promise_then: -> ${newId}`); +#endif + return newId; + }, + + emscripten_promise_all__deps: ['$promiseMap', '$idsToPromises'], + emscripten_promise_all: (idBuf, resultBuf, size) => { + var promises = idsToPromises(idBuf, size); +#if RUNTIME_DEBUG + dbg(`emscripten_promise_all: ${promises}`); +#endif + var id = promiseMap.allocate({ + promise: Promise.all(promises).then((results) => { + if (resultBuf) { + for (var i = 0; i < size; i++) { + var result = results[i]; + {{{ makeSetValue('resultBuf', `i*${POINTER_SIZE}`, 'result', '*') }}}; + } + } + return resultBuf; + }) + }); +#if RUNTIME_DEBUG + dbg(`create: ${id}`); +#endif + return id; + }, + + $setPromiseResult__internal: true, + $setPromiseResult: (ptr, fulfill, value) => { +#if ASSERTIONS + assert(typeof value == 'undefined' || typeof value === 'number', `native promises can only handle numeric results (${value} ${typeof value})`); +#endif + var result = fulfill ? {{{ cDefs.EM_PROMISE_FULFILL }}} : {{{ cDefs.EM_PROMISE_REJECT }}} + {{{ makeSetValue('ptr', C_STRUCTS.em_settled_result_t.result, 'result', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.em_settled_result_t.value, 'value', '*') }}}; + }, + + emscripten_promise_all_settled__deps: ['$promiseMap', '$idsToPromises', '$setPromiseResult'], + emscripten_promise_all_settled: (idBuf, resultBuf, size) => { + var promises = idsToPromises(idBuf, size); +#if RUNTIME_DEBUG + dbg(`emscripten_promise_all_settled: ${promises}`); +#endif + var id = promiseMap.allocate({ + promise: Promise.allSettled(promises).then((results) => { + if (resultBuf) { + var offset = resultBuf; + for (var i = 0; i < size; i++, offset += {{{ C_STRUCTS.em_settled_result_t.__size__ }}}) { + if (results[i].status === 'fulfilled') { + setPromiseResult(offset, true, results[i].value); + } else { + setPromiseResult(offset, false, results[i].reason); + } + } + } + return resultBuf; + }) + }); +#if RUNTIME_DEBUG + dbg(`create: ${id}`); +#endif + return id; + }, + + emscripten_promise_any__deps: [ + '$promiseMap', '$idsToPromises', +#if !SUPPORTS_PROMISE_ANY && !INCLUDE_FULL_LIBRARY + () => error("emscripten_promise_any used, but Promise.any is not supported by the current runtime configuration (run with EMCC_DEBUG=1 in the env for more details)"), +#endif + ], + emscripten_promise_any: (idBuf, errorBuf, size) => { + var promises = idsToPromises(idBuf, size); +#if RUNTIME_DEBUG + dbg(`emscripten_promise_any: ${promises}`); +#endif +#if ASSERTIONS + assert(typeof Promise.any != 'undefined', "Promise.any does not exist"); +#endif + var id = promiseMap.allocate({ + promise: Promise.any(promises).catch((err) => { + if (errorBuf) { + for (var i = 0; i < size; i++) { + {{{ makeSetValue('errorBuf', `i*${POINTER_SIZE}`, 'err.errors[i]', '*') }}}; + } + } + throw errorBuf; + }) + }); +#if RUNTIME_DEBUG + dbg(`create: ${id}`); +#endif + return id; + }, + + emscripten_promise_race__deps: ['$promiseMap', '$idsToPromises'], + emscripten_promise_race: (idBuf, size) => { + var promises = idsToPromises(idBuf, size); +#if RUNTIME_DEBUG + dbg(`emscripten_promise_race: ${promises}`); +#endif + var id = promiseMap.allocate({ + promise: Promise.race(promises) + }); +#if RUNTIME_DEBUG + dbg(`create: ${id}`); +#endif + return id; + }, + + emscripten_promise_await__async: 'auto', +#if ASYNCIFY + emscripten_promise_await__deps: ['$getPromise', '$setPromiseResult'], +#endif + emscripten_promise_await: (returnValuePtr, id) => { +#if ASYNCIFY +#if RUNTIME_DEBUG + dbg(`emscripten_promise_await: ${id}`); +#endif + return getPromise(id).then( + value => setPromiseResult(returnValuePtr, true, value), + error => setPromiseResult(returnValuePtr, false, error) + ); +#else + abort('emscripten_promise_await is only available with ASYNCIFY'); +#endif + }, +}); diff --git a/src/lib/libproxyfs.js b/src/lib/libproxyfs.js new file mode 100644 index 0000000000000..6a2a9d35efe49 --- /dev/null +++ b/src/lib/libproxyfs.js @@ -0,0 +1,224 @@ +/** + * @license + * Copyright 2016 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +addToLibrary({ + $PROXYFS__deps: ['$FS', '$PATH', '$ERRNO_CODES'], + $PROXYFS: { + mount(mount) { + return PROXYFS.createNode(null, '/', mount.opts.fs.lstat(mount.opts.root).mode, 0); + }, + createNode(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = PROXYFS.node_ops; + node.stream_ops = PROXYFS.stream_ops; + return node; + }, + realPath(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join(...parts); + }, + node_ops: { + getattr(node) { + var path = PROXYFS.realPath(node); + var stat; + try { + stat = node.mount.opts.fs.lstat(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks + }; + }, + setattr(node, attr) { + var path = PROXYFS.realPath(node); + try { + if (attr.mode !== undefined) { + node.mount.opts.fs.chmod(path, attr.mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (attr.atime || attr.mtime) { + var atime = new Date(attr.atime || attr.mtime); + var mtime = new Date(attr.mtime || attr.atime); + node.mount.opts.fs.utime(path, atime, mtime); + } + if (attr.size !== undefined) { + node.mount.opts.fs.truncate(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup(parent, name) { + try { + var path = PATH.join2(PROXYFS.realPath(parent), name); + var mode = parent.mount.opts.fs.lstat(path).mode; + var node = PROXYFS.createNode(parent, name, mode); + return node; + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + mknod(parent, name, mode, dev) { + var node = PROXYFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = PROXYFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + node.mount.opts.fs.mkdir(path, node.mode); + } else { + node.mount.opts.fs.writeFile(path, '', { mode: node.mode }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename(oldNode, newDir, newName) { + var oldPath = PROXYFS.realPath(oldNode); + var newPath = PATH.join2(PROXYFS.realPath(newDir), newName); + try { + oldNode.mount.opts.fs.rename(oldPath, newPath); + oldNode.name = newName; + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.unlink(path); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.rmdir(path); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readdir(path); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink(parent, newName, oldPath) { + var newPath = PATH.join2(PROXYFS.realPath(parent), newName); + try { + parent.mount.opts.fs.symlink(oldPath, newPath); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readlink(path); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open(stream) { + var path = PROXYFS.realPath(stream.node); + try { + stream.nfd = stream.node.mount.opts.fs.open(path,stream.flags); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close(stream) { + try { + stream.node.mount.opts.fs.close(stream.nfd); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.read(stream.nfd, buffer, offset, length, position); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + write(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.write(stream.nfd, buffer, offset, length, position); + } catch(e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefs.SEEK_CUR }}}) { + position += stream.position; + } else if (whence === {{{ cDefs.SEEK_END }}}) { + if (FS.isFile(stream.node.mode)) { + try { + var stat = stream.node.node_ops.getattr(stream.node); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + return position; + } + } + } +}); + +if (WASMFS) { + error("using -lproxyfs is not currently supported in WasmFS."); +} diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js new file mode 100644 index 0000000000000..95eb163ca4f36 --- /dev/null +++ b/src/lib/libpthread.js @@ -0,0 +1,1318 @@ +/** + * @license + * Copyright 2015 The Emscripten Authors + * SPDX-License-Identifier: MIT + * + * Because only modern JS engines support SAB we can use modern JS language + * features within this file (ES2020). + */ + +#if !PTHREADS +#error "Internal error! PTHREADS should be enabled when including library_pthread.js." +#endif +#if !SHARED_MEMORY +#error "Internal error! SHARED_MEMORY should be enabled when including library_pthread.js." +#endif +#if PTHREADS == 2 +#error "PTHREADS=2 is no longer supported" +#endif +#if BUILD_AS_WORKER +#error "pthreads + BUILD_AS_WORKER require separate modes that don't work together, see https://github.com/emscripten-core/emscripten/issues/8854" +#endif +#if EVAL_CTORS +#error "EVAL_CTORS is not compatible with pthreads yet (passive segments)" +#endif +#if EXPORT_ES6 && (MIN_FIREFOX_VERSION < 114 || MIN_CHROME_VERSION < 80 || MIN_SAFARI_VERSION < 150000) +#error "internal error, feature_matrix should not allow this" +#endif + +{{{ +#if MEMORY64 +const MAX_PTR = Number((2n ** 64n) - 1n); +#else +const MAX_PTR = (2 ** 32) - 1 +#endif + +#if WASM_ESM_INTEGRATION +const pthreadWorkerScript = TARGET_BASENAME + '.pthread.mjs'; +#else +const pthreadWorkerScript = TARGET_JS_NAME; +#endif + +// Use a macro to avoid duplicating pthread worker options. +// We cannot use a normal JS variable since the vite bundler requires that worker +// options be inline. +// See https://github.com/emscripten-core/emscripten/issues/22394 +const pthreadWorkerOptions = `{ +#if EXPORT_ES6 + 'type': 'module', +#endif +#if ENVIRONMENT_MAY_BE_NODE + // This is the way that we signal to the node worker that it is hosting + // a pthread. + 'workerData': 'em-pthread', +#if WASMFS + // In WasmFS, close() is not proxied to the main thread. Suppress + // warnings when a thread closes a file descriptor it didn't open. + // See: https://github.com/emscripten-core/emscripten/issues/24731 + 'trackUnmanagedFds': false, +#endif +#endif +#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER + // This is the way that we signal to the Web Worker that it is hosting + // a pthread. +#if ASSERTIONS + 'name': 'em-pthread-' + PThread.nextWorkerID, +#else + 'name': 'em-pthread', +#endif +#endif +}`; +}}} + +var LibraryPThread = { + $PThread__postset: 'PThread.init();', + $PThread__deps: ['_emscripten_thread_init', + '$terminateWorker', + '$cleanupThread', + '$addOnPreRun', +#if MAIN_MODULE + '$markAsFinished', +#endif +#if !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE && !PTHREAD_POOL_DELAY_LOAD + '$addRunDependency', + '$removeRunDependency', +#endif + '$spawnThread', + '_emscripten_thread_free_data', + 'exit', +#if PTHREADS_DEBUG || ASSERTIONS + '$ptrToString', +#endif + ], + $PThread: { + // Contains all Workers that are idle/unused and not currently hosting an + // executing pthread. Unused Workers can either be pooled up before page + // startup, but also when a pthread quits, its hosting Worker is not + // terminated, but is returned to this pool as an optimization so that + // starting the next thread is faster. + unusedWorkers: [], + // Contains all Workers that are currently hosting an active pthread. + runningWorkers: [], + tlsInitFunctions: [], + // Maps pthread_t pointers to the workers on which they are running. For + // the reverse mapping, each worker has a `pthread_ptr` when its running a + // pthread. + pthreads: {}, +#if ASSERTIONS + nextWorkerID: 1, +#endif + init() { + if ({{{ ENVIRONMENT_IS_MAIN_THREAD() }}}) { + PThread.initMainThread(); + } + }, + initMainThread() { +#if PTHREAD_POOL_SIZE + var pthreadPoolSize = {{{ PTHREAD_POOL_SIZE }}}; + // Start loading up the Worker pool, if requested. + while (pthreadPoolSize--) { + PThread.allocateUnusedWorker(); + } +#endif +#if !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE + // MINIMAL_RUNTIME takes care of calling loadWasmModuleToAllWorkers + // in postamble_minimal.js + addOnPreRun(async () => { + var pthreadPoolReady = PThread.loadWasmModuleToAllWorkers(); +#if !PTHREAD_POOL_DELAY_LOAD + addRunDependency('loading-workers'); + await pthreadPoolReady; + removeRunDependency('loading-workers'); +#endif // PTHREAD_POOL_DELAY_LOAD + }); +#endif // !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE +#if MAIN_MODULE + PThread.outstandingPromises = {}; + // Finished threads are threads that have finished running but we are not yet + // joined. + PThread.finishedThreads = new Set(); +#endif + }, + +#if PTHREADS_PROFILING + getThreadName(pthreadPtr) { + var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}}; + if (!profilerBlock) return ""; + return UTF8ToString(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}}); + }, + + threadStatusToString(threadStatus) { + switch (threadStatus) { + case 0: return "not yet started"; + case 1: return "running"; + case 2: return "sleeping"; + case 3: return "waiting for a futex"; + case 4: return "waiting for a mutex"; + case 5: return "waiting for a proxied operation"; + case 6: return "finished execution"; + default: return "unknown (corrupt?!)"; + } + }, + + threadStatusAsString(pthreadPtr) { + var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}}; + var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, {{{ getHeapOffset('profilerBlock + ' + C_STRUCTS.thread_profiler_block.threadStatus, 'i32') }}}); + return PThread.threadStatusToString(status); + }, +#endif + + terminateAllThreads: () => { +#if ASSERTIONS + assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! terminateAllThreads() can only ever be called from main application thread!'); +#endif +#if PTHREADS_DEBUG + dbg('terminateAllThreads'); +#endif + // Attempt to kill all workers. Sadly (at least on the web) there is no + // way to terminate a worker synchronously, or to be notified when a + // worker is actually terminated. This means there is some risk that + // pthreads will continue to be executing after `worker.terminate` has + // returned. For this reason, we don't call `returnWorkerToPool` here or + // free the underlying pthread data structures. + for (var worker of PThread.runningWorkers) { + terminateWorker(worker); + } + for (var worker of PThread.unusedWorkers) { + terminateWorker(worker); + } + PThread.unusedWorkers = []; + PThread.runningWorkers = []; + PThread.pthreads = {}; + }, + returnWorkerToPool: (worker) => { + // We don't want to run main thread queued calls here, since we are doing + // some operations that leave the worker queue in an invalid state until + // we are completely done (it would be bad if free() ends up calling a + // queued pthread_create which looks at the global data structures we are + // modifying). To achieve that, defer the free() until the very end, when + // we are all done. + var pthread_ptr = worker.pthread_ptr; + delete PThread.pthreads[pthread_ptr]; + // Note: worker is intentionally not terminated so the pool can + // dynamically grow. + PThread.unusedWorkers.push(worker); + PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1); + // Not a running Worker anymore + // Detach the worker from the pthread object, and return it to the + // worker pool as an unused worker. + worker.pthread_ptr = 0; + +#if ENVIRONMENT_MAY_BE_NODE && PROXY_TO_PTHREAD + if (ENVIRONMENT_IS_NODE) { + // Once the proxied main thread has finished, mark it as weakly + // referenced so that its existence does not prevent Node.js from + // exiting. This has no effect if the worker is already weakly + // referenced. + worker.unref(); + } +#endif + + // Finally, free the underlying (and now-unused) pthread structure in + // linear memory. + __emscripten_thread_free_data(pthread_ptr); + }, +#if OFFSCREENCANVAS_SUPPORT + receiveOffscreenCanvases(data) { + if (typeof GL != 'undefined') { + Object.assign(GL.offscreenCanvases, data.offscreenCanvases); + if (!Module['canvas'] && data.moduleCanvasId && GL.offscreenCanvases[data.moduleCanvasId]) { + Module['canvas'] = GL.offscreenCanvases[data.moduleCanvasId].offscreenCanvas; + Module['canvas'].id = data.moduleCanvasId; + } + } + }, +#endif + // Called by worker.js each time a thread is started. + threadInitTLS() { +#if PTHREADS_DEBUG + dbg('threadInitTLS'); +#endif + // Call thread init functions (these are the _emscripten_tls_init for each + // module loaded. + PThread.tlsInitFunctions.forEach((f) => f()); + }, + // Loads the WebAssembly module into the given Worker. + // onFinishedLoading: A callback function that will be called once all of + // the workers have been initialized and are + // ready to host pthreads. + loadWasmModuleToWorker: (worker) => new Promise((onFinishedLoading) => { + worker.onmessage = (e) => { + var d = e['data']; + var cmd = d.cmd; +#if PTHREADS_DEBUG + dbg(`main thread: received message '${cmd}' from worker. ${d}`); +#endif + + // If this message is intended to a recipient that is not the main + // thread, forward it to the target thread. + if (d.targetThread && d.targetThread != _pthread_self()) { + var targetWorker = PThread.pthreads[d.targetThread]; + if (targetWorker) { + targetWorker.postMessage(d, d.transferList); + } else { + err(`Internal error! Worker sent a message "${cmd}" to target pthread ${d.targetThread}, but that thread no longer exists!`); + } + return; + } + + if (cmd === 'checkMailbox') { + checkMailbox(); + } else if (cmd === 'spawnThread') { + spawnThread(d); + } else if (cmd === 'cleanupThread') { + // cleanupThread needs to be run via callUserCallback since it calls + // back into user code to free thread data. Without this it's possible + // the unwind or ExitStatus exception could escape here. + callUserCallback(() => cleanupThread(d.thread)); +#if MAIN_MODULE + } else if (cmd === 'markAsFinished') { + markAsFinished(d.thread); +#endif + } else if (cmd === 'loaded') { + worker.loaded = true; +#if ENVIRONMENT_MAY_BE_NODE && PTHREAD_POOL_SIZE + // Check that this worker doesn't have an associated pthread. + if (ENVIRONMENT_IS_NODE && !worker.pthread_ptr) { + // Once worker is loaded & idle, mark it as weakly referenced, + // so that mere existence of a Worker in the pool does not prevent + // Node.js from exiting the app. + worker.unref(); + } +#endif + onFinishedLoading(worker); + } else if (d.target === 'setimmediate') { + // Worker wants to postMessage() to itself to implement setImmediate() + // emulation. + worker.postMessage(d); +#if ENVIRONMENT_MAY_BE_NODE + } else if (cmd === 'uncaughtException') { + // Message handler for Node.js specific out-of-order behavior: + // https://github.com/nodejs/node/issues/59617 + // A pthread sent an uncaught exception event. Re-raise it on the main thread. + worker.onerror(d.error); +#endif + } else if (cmd === 'callHandler') { + Module[d.handler](...d.args); + } else if (cmd) { + // The received message looks like something that should be handled by this message + // handler, (since there is a e.data.cmd field present), but is not one of the + // recognized commands: + err(`worker sent an unknown command ${cmd}`); + } + }; + + worker.onerror = (e) => { + var message = 'worker sent an error!'; +#if ASSERTIONS + if (worker.pthread_ptr) { + message = `Pthread ${ptrToString(worker.pthread_ptr)} sent an error!`; + } +#endif + err(`${message} ${e.filename}:${e.lineno}: ${e.message}`); + throw e; + }; + +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + worker.on('message', (data) => worker.onmessage({ data: data })); + worker.on('error', (e) => worker.onerror(e)); + +#if PTHREADS_DEBUG + worker.on('exit', (code) => { + if (worker.pthread_ptr) dbg(`Worker hosting pthread ${ptrToString(worker.pthread_ptr)} has terminated with code ${code}.`); + else dbg(`Worker has terminated with code ${code}.`); + }); +#endif + } +#endif + +#if ASSERTIONS + assert(wasmMemory instanceof WebAssembly.Memory, 'WebAssembly memory should have been loaded by now!'); +#if !WASM_ESM_INTEGRATION + assert(wasmModule instanceof WebAssembly.Module, 'WebAssembly Module should have been loaded by now!'); +#endif +#endif + + // When running on a pthread, none of the incoming parameters on the module + // object are present. Proxy known handlers back to the main thread if specified. + var handlers = []; + var knownHandlers = [ +#if expectToReceiveOnModule('onExit') + 'onExit', +#endif +#if expectToReceiveOnModule('onAbort') + 'onAbort', +#endif +#if expectToReceiveOnModule('print') + 'print', +#endif +#if expectToReceiveOnModule('printErr') + 'printErr', +#endif +#if expectToReceiveOnModule('onMalloc') + 'onMalloc', +#endif +#if expectToReceiveOnModule('onRealloc') + 'onRealloc', +#endif +#if expectToReceiveOnModule('onFree') + 'onFree', +#endif +#if expectToReceiveOnModule('onSbrkGrow') + 'onSbrkGrow', +#endif + ]; + for (var handler of knownHandlers) { + if (Module.propertyIsEnumerable(handler)) { + handlers.push(handler); + } + } + + // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. + worker.postMessage({ + cmd: 'load', + handlers: handlers, +#if WASM2JS + // the polyfill WebAssembly.Memory instance has function properties, + // which will fail in postMessage, so just send a custom object with the + // property we need, the buffer + wasmMemory: { 'buffer': wasmMemory.buffer }, +#else // WASM2JS + wasmMemory, +#endif // WASM2JS +#if !WASM_ESM_INTEGRATION + wasmModule, +#endif +#if LOAD_SOURCE_MAP + wasmSourceMap, +#endif +#if MAIN_MODULE + dynamicLibraries, + // Share all modules that have been loaded so far. New workers + // won't start running threads until these are all loaded. + sharedModules, +#endif +#if ASSERTIONS + 'workerID': worker.workerID, +#endif + }); + }), + +#if PTHREAD_POOL_SIZE + async loadWasmModuleToAllWorkers() { + // Instantiation is synchronous in pthreads. + if ( + ENVIRONMENT_IS_PTHREAD +#if WASM_WORKERS + || ENVIRONMENT_IS_WASM_WORKER +#endif + ) { + return; + } + + let pthreadPoolReady = Promise.all(PThread.unusedWorkers.map(PThread.loadWasmModuleToWorker)); +#if PTHREAD_POOL_DELAY_LOAD + // PTHREAD_POOL_DELAY_LOAD means we want to proceed synchronously without + // waiting for the pthread pool during the startup phase. + // If the user wants to wait on it elsewhere, they can do so via the + // Module['pthreadPoolReady'] promise. + Module['pthreadPoolReady'] = pthreadPoolReady; + return; +#else + return pthreadPoolReady; +#endif + }, +#endif // PTHREAD_POOL_SIZE + + // Creates a new web Worker and places it in the unused worker pool to wait for its use. + allocateUnusedWorker() { + var worker; +#if EXPORT_ES6 + // If we're using module output, use bundler-friendly pattern. +#if PTHREADS_DEBUG + dbg(`Allocating a new web worker from ${import.meta.url}`); +#endif +#if TRUSTED_TYPES + // Use Trusted Types compatible wrappers. + if (globalThis.trustedTypes?.createPolicy) { + var p = trustedTypes.createPolicy('emscripten#workerPolicy1', { createScriptURL: (ignored) => new URL('{{{ pthreadWorkerScript }}}', import.meta.url) }); + worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}}); + } else +#endif +#if expectToReceiveOnModule('mainScriptUrlOrBlob') + if (Module['mainScriptUrlOrBlob']) { + var pthreadMainJs = Module['mainScriptUrlOrBlob']; + if (typeof pthreadMainJs != 'string') { + pthreadMainJs = URL.createObjectURL(pthreadMainJs); + } + worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}}); + } else +#endif +#if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB + // Support cross-origin loading by creating a new Blob URL to actually + // perform the `import`. Without this the `new Worker` would fail + // due to CORS restrictions. + // https://github.com/emscripten-core/emscripten/issues/21937 + if (ENVIRONMENT_IS_WEB) { + var url = URL.createObjectURL(new Blob([`import '${import.meta.url}'`], { type: 'application/javascript' })); + worker = new Worker(url, {{{ pthreadWorkerOptions }}}); + } else +#endif + // We need to generate the URL with import.meta.url as the base URL of the JS file + // instead of just using new URL(import.meta.url) because bundlers only recognize + // the first case in their bundling step. The latter ends up producing an invalid + // URL to import from the server (e.g., for webpack the file:// path). + // See https://github.com/webpack/webpack/issues/12638 + worker = new Worker(new URL('{{{ pthreadWorkerScript }}}', import.meta.url), {{{ pthreadWorkerOptions }}}); +#else // EXPORT_ES6 + var pthreadMainJs = _scriptName; +#if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB + // In order to support cross origin loading of worker threads load the + // worker via a tiny inline `importScripts` call. For some reason it's + // fine to `importScripts` across origins, in cases where new Worker + // itself does not allow this. + // https://github.com/emscripten-core/emscripten/issues/21937 + if (ENVIRONMENT_IS_WEB) { + pthreadMainJs = URL.createObjectURL(new Blob([`importScripts('${_scriptName}')`], { type: 'application/javascript' })); + } +#endif +#if expectToReceiveOnModule('mainScriptUrlOrBlob') + // We can't use makeModuleReceiveWithVar here since we want to also + // call URL.createObjectURL on the mainScriptUrlOrBlob. + if (Module['mainScriptUrlOrBlob']) { + pthreadMainJs = Module['mainScriptUrlOrBlob']; + if (typeof pthreadMainJs != 'string') { + pthreadMainJs = URL.createObjectURL(pthreadMainJs); + } + } +#endif +#if PTHREADS_DEBUG + dbg(`Allocating a new web worker from ${pthreadMainJs}`); +#endif +#if TRUSTED_TYPES + // Use Trusted Types compatible wrappers. + if (globalThis.trustedTypes?.createPolicy) { + var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: (ignored) => pthreadMainJs }); + worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}}); + } else +#endif + worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}}); +#endif // EXPORT_ES6 +#if ASSERTIONS + worker.workerID = PThread.nextWorkerID++; +#endif + PThread.unusedWorkers.push(worker); + }, + + getNewWorker() { + if (PThread.unusedWorkers.length == 0) { +// PTHREAD_POOL_SIZE_STRICT should show a warning and, if set to level `2`, return from the function. +#if (PTHREAD_POOL_SIZE_STRICT && ASSERTIONS) || PTHREAD_POOL_SIZE_STRICT == 2 +// However, if we're in Node.js, then we can create new workers on the fly and PTHREAD_POOL_SIZE_STRICT +// should be ignored altogether. +#if ENVIRONMENT_MAY_BE_NODE + if (!ENVIRONMENT_IS_NODE) { +#endif +#if ASSERTIONS + err('Tried to spawn a new thread, but the thread pool is exhausted.\n' + + 'This might result in a deadlock unless some threads eventually exit or the code explicitly breaks out to the event loop.\n' + + 'If you want to increase the pool size, use setting `-sPTHREAD_POOL_SIZE=...`.' +#if PTHREAD_POOL_SIZE_STRICT == 1 + + '\nIf you want to throw an explicit error instead of the risk of deadlocking in those cases, use setting `-sPTHREAD_POOL_SIZE_STRICT=2`.' +#endif + ); +#endif // ASSERTIONS +#if PTHREAD_POOL_SIZE_STRICT == 2 + return; +#endif +#if ENVIRONMENT_MAY_BE_NODE + } +#endif +#endif // PTHREAD_POOL_SIZE_STRICT +#if PTHREAD_POOL_SIZE_STRICT < 2 || ENVIRONMENT_MAY_BE_NODE + PThread.allocateUnusedWorker(); + PThread.loadWasmModuleToWorker(PThread.unusedWorkers[0]); +#endif + } + return PThread.unusedWorkers.pop(); + } + }, + + $terminateWorker: (worker) => { +#if PTHREADS_DEBUG + dbg(`terminateWorker: ${worker.workerID}`); +#endif + worker.terminate(); + // terminate() can be asynchronous, so in theory the worker can continue + // to run for some amount of time after termination. However from our POV + // the worker is now dead and we don't want to hear from it again, so we stub + // out its message handler here. This avoids having to check in each of + // the onmessage handlers if the message was coming from a valid worker. + worker.onmessage = (e) => { +#if ASSERTIONS + var cmd = e['data'].cmd; + err(`received "${cmd}" command from terminated worker: ${worker.workerID}`); +#endif + }; + }, + + _emscripten_thread_cleanup: (thread) => { + // Called when a thread needs to be cleaned up so it can be reused. + // A thread is considered reusable when it either returns from its + // entry point, calls pthread_exit, or acts upon a cancellation. + // Detached threads are responsible for calling this themselves, + // otherwise pthread_join is responsible for calling this. +#if PTHREADS_DEBUG + dbg(`_emscripten_thread_cleanup: ${ptrToString(thread)}`) +#endif + if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread); + else postMessage({ cmd: 'cleanupThread', thread }); + }, + + _emscripten_thread_set_strongref: (thread) => { + // Called when a thread needs to be strongly referenced. + // Currently only used for: + // - keeping the "main" thread alive in PROXY_TO_PTHREAD mode; + // - crashed threads that need to propagate the uncaught exception + // back to the main thread. +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + PThread.pthreads[thread].ref(); + } +#endif + }, + + $cleanupThread: (pthread_ptr) => { +#if PTHREADS_DEBUG + dbg(`cleanupThread: ${ptrToString(pthread_ptr)}`) +#endif +#if ASSERTIONS + assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!'); + assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!'); +#endif + var worker = PThread.pthreads[pthread_ptr]; +#if MAIN_MODULE + PThread.finishedThreads.delete(pthread_ptr); + if (pthread_ptr in PThread.outstandingPromises) { + PThread.outstandingPromises[pthread_ptr].resolve(); + } +#endif +#if ASSERTIONS + assert(worker); +#endif + PThread.returnWorkerToPool(worker); + }, + +#if MAIN_MODULE + $registerTLSInit: (tlsInitFunc, moduleExports, metadata) => { +#if DYLINK_DEBUG + dbg('registerTLSInit:', tlsInitFunc, metadata?.tlsExports); +#endif + // In relocatable builds, we use the result of calling tlsInitFunc + // (`_emscripten_tls_init`) to relocate the TLS exports of the module + // according to this new __tls_base. + function tlsInitWrapper() { + var __tls_base = tlsInitFunc(); +#if DYLINK_DEBUG + dbg(`tlsInit -> ${__tls_base}`); +#endif + if (!__tls_base) { +#if ASSERTIONS + // __tls_base should never be zero if there are tls exports + assert(__tls_base || metadata.tlsExports.size == 0); +#endif + return; + } + var tlsExports = {}; + metadata.tlsExports.forEach((s) => tlsExports[s] = moduleExports[s]); + updateGOT(relocateExports(tlsExports, __tls_base), /*replace=*/true); + } + + // Register this function so that its gets called for each thread on + // startup. + PThread.tlsInitFunctions.push(tlsInitWrapper); + + // If the main thread is already running we also need to call this function + // now. If the main thread is not yet running this will happen when it + // is initialized and processes `PThread.tlsInitFunctions`. + if (runtimeInitialized) { + tlsInitWrapper(); + } + }, +#else + $registerTLSInit: (tlsInitFunc) => PThread.tlsInitFunctions.push(tlsInitFunc), +#endif + + $spawnThread: (threadParams) => { +#if ASSERTIONS + assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! spawnThread() can only ever be called from main application thread!'); + assert(threadParams.pthread_ptr, 'Internal error, no pthread ptr!'); +#endif + + var worker = PThread.getNewWorker(); + if (!worker) { + // No available workers in the PThread pool. + return {{{ cDefs.EAGAIN }}}; + } +#if ASSERTIONS + assert(!worker.pthread_ptr, 'Internal error!'); +#endif + + PThread.runningWorkers.push(worker); + + // Add to pthreads map + PThread.pthreads[threadParams.pthread_ptr] = worker; + + worker.pthread_ptr = threadParams.pthread_ptr; + var msg = { + cmd: 'run', + start_routine: threadParams.startRoutine, + arg: threadParams.arg, + pthread_ptr: threadParams.pthread_ptr, + }; +#if OFFSCREENCANVAS_SUPPORT + // Note that we do not need to quote these names because they are only used + // in this file, and not from the external worker.js. + msg.moduleCanvasId = threadParams.moduleCanvasId; + msg.offscreenCanvases = threadParams.offscreenCanvases; +#endif +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) { + // Mark worker as weakly referenced once we start executing a pthread, + // so that its existence does not prevent Node.js from exiting. This + // has no effect if the worker is already weakly referenced (e.g. if + // this worker was previously idle/unused). + worker.unref(); + } +#endif + // Ask the worker to start executing its pthread entry point function. + worker.postMessage(msg, threadParams.transferList); + return 0; + }, + + _emscripten_init_main_thread_js: (tb) => { + // Pass the thread address to the native code where they are stored in wasm + // globals which act as a form of TLS. Global constructors trying + // to access this value will read the wrong value, but that is UB anyway. + __emscripten_thread_init( + tb, + /*is_main=*/!ENVIRONMENT_IS_WORKER, + /*is_runtime=*/1, + /*can_block=*/!ENVIRONMENT_IS_WEB, + /*default_stacksize=*/{{{ DEFAULT_PTHREAD_STACK_SIZE }}}, +#if PTHREADS_PROFILING + /*start_profiling=*/true, +#else + /*start_profiling=*/false, +#endif + ); + PThread.threadInitTLS(); + }, + + $pthreadCreateProxied__internal: true, + $pthreadCreateProxied__proxy: 'sync', + $pthreadCreateProxied__deps: ['__pthread_create_js'], + $pthreadCreateProxied: (pthread_ptr, attr, startRoutine, arg) => ___pthread_create_js(pthread_ptr, attr, startRoutine, arg), + +#if OFFSCREENCANVAS_SUPPORT + // ASan wraps the emscripten_builtin_pthread_create call in + // __lsan::ScopedInterceptorDisabler. Unfortunately, that only disables it on + // the thread that made the call. __pthread_create_js gets proxied to the + // main thread, where LSan is not disabled. This makes it necessary for us to + // disable LSan here (using __noleakcheck), so that it does not detect + // pthread's internal allocations as leaks. If/when we remove all the + // allocations from __pthread_create_js we could also remove this. + __pthread_create_js__noleakcheck: true, +#endif + __pthread_create_js__deps: ['$spawnThread', '$pthreadCreateProxied', + 'emscripten_has_threading_support', +#if OFFSCREENCANVAS_SUPPORT + 'malloc', +#endif + ], + __pthread_create_js: (pthread_ptr, attr, startRoutine, arg) => { + if (!_emscripten_has_threading_support()) { +#if ASSERTIONS + dbg('pthread_create: environment does not support SharedArrayBuffer, pthreads are not available'); +#endif + return {{{ cDefs.EAGAIN }}}; + } +#if PTHREADS_DEBUG + dbg("createThread: " + ptrToString(pthread_ptr)); +#endif + + // List of JS objects that will transfer ownership to the Worker hosting the thread + var transferList = []; + var error = 0; + +#if OFFSCREENCANVAS_SUPPORT + // Deduce which WebGL canvases (HTMLCanvasElements or OffscreenCanvases) should be passed over to the + // Worker that hosts the spawned pthread. + // Comma-delimited list of CSS selectors that must identify canvases by IDs: "#canvas1, #canvas2, ..." + var transferredCanvasNames = attr ? {{{ makeGetValue('attr', C_STRUCTS.pthread_attr_t._a_transferredcanvases, '*') }}} : 0; +#if OFFSCREENCANVASES_TO_PTHREAD + // Proxied canvases string pointer -1/MAX_PTR is used as a special token to + // fetch whatever canvases were passed to build in + // -sOFFSCREENCANVASES_TO_PTHREAD= command line. + if (transferredCanvasNames == {{{ MAX_PTR }}}) { + transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}'; + } else +#endif + { + transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim(); + } + transferredCanvasNames = transferredCanvasNames ? transferredCanvasNames.split(',') : []; +#if GL_DEBUG + dbg(`pthread_create: transferredCanvasNames="${transferredCanvasNames}"`); +#endif + + var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own + var moduleCanvasId = Module['canvas']?.id || ''; + // Note that transferredCanvasNames might be null (so we cannot do a for-of loop). + for (var name of transferredCanvasNames) { + name = name.trim(); + var offscreenCanvasInfo; + try { + if (name == '#canvas') { + if (!Module['canvas']) { + err(`pthread_create: could not find canvas with ID "${name}" to transfer to thread!`); + error = {{{ cDefs.EINVAL }}}; + break; + } + name = Module['canvas'].id; + } +#if ASSERTIONS + assert(typeof GL == 'object', 'OFFSCREENCANVAS_SUPPORT assumes GL is in use (you can force-include it with \'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$GL\')'); +#endif + if (GL.offscreenCanvases[name]) { + offscreenCanvasInfo = GL.offscreenCanvases[name]; + GL.offscreenCanvases[name] = null; // This thread no longer owns this canvas. + if (Module['canvas'] instanceof OffscreenCanvas && name === Module['canvas'].id) Module['canvas'] = null; + } else if (!ENVIRONMENT_IS_PTHREAD) { + var canvas = (Module['canvas'] && Module['canvas'].id === name) ? Module['canvas'] : document.querySelector(name); + if (!canvas) { + err(`pthread_create: could not find canvas with ID "${name}" to transfer to thread!`); + error = {{{ cDefs.EINVAL }}}; + break; + } + if (canvas.controlTransferredOffscreen) { + err(`pthread_create: cannot transfer canvas with ID "${name}" to thread, since the current thread does not have control over it!`); + error = {{{ cDefs.EPERM }}}; // Operation not permitted, some other thread is accessing the canvas. + break; + } + if (canvas.transferControlToOffscreen) { +#if GL_DEBUG + dbg(`pthread_create: canvas.transferControlToOffscreen(), transferring canvas by name "${name}" (DOM id="${canvas.id}") from main thread to pthread`); +#endif + // Create a shared information block in heap so that we can control + // the canvas size from any thread. + if (!canvas.canvasSharedPtr) { + canvas.canvasSharedPtr = _malloc({{{ 8 + POINTER_SIZE }}}); + {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'canvas.width', 'i32') }}}; + {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'canvas.height', 'i32') }}}; + {{{ makeSetValue('canvas.canvasSharedPtr', 8, 0, '*') }}}; // pthread ptr to the thread that owns this canvas, filled in below. + } + offscreenCanvasInfo = { + offscreenCanvas: canvas.transferControlToOffscreen(), + canvasSharedPtr: canvas.canvasSharedPtr, + id: canvas.id + } + // After calling canvas.transferControlToOffscreen(), it is no + // longer possible to access certain operations on the canvas, such + // as resizing it or obtaining GL contexts via it. + // Use this field to remember that we have permanently converted + // this Canvas to be controlled via an OffscreenCanvas (there is no + // way to undo this in the spec) + canvas.controlTransferredOffscreen = true; + } else { + err(`pthread_create: cannot transfer control of canvas "${name}" to pthread, because current browser does not support OffscreenCanvas!`); + // If building with OFFSCREEN_FRAMEBUFFER=1 mode, we don't need to + // be able to transfer control to offscreen, but WebGL can be + // proxied from worker to main thread. +#if !OFFSCREEN_FRAMEBUFFER + err('pthread_create: Build with -sOFFSCREEN_FRAMEBUFFER to enable fallback proxying of GL commands from pthread to main thread.'); + return {{{ cDefs.ENOSYS }}}; // Function not implemented, browser doesn't have support for this. +#endif + } + } + if (offscreenCanvasInfo) { + transferList.push(offscreenCanvasInfo.offscreenCanvas); + offscreenCanvases[offscreenCanvasInfo.id] = offscreenCanvasInfo; + } + } catch(e) { + err(`pthread_create: failed to transfer control of canvas "${name}" to OffscreenCanvas! Error: ${e}`); + return {{{ cDefs.EINVAL }}}; // Hitting this might indicate an implementation bug or some other internal error + } + } +#endif // OFFSCREENCANVAS_SUPPORT + + // Synchronously proxy the thread creation to main thread if possible. If we + // need to transfer ownership of objects, then proxy asynchronously via + // postMessage. + if (ENVIRONMENT_IS_PTHREAD && (transferList.length === 0 || error)) { + return pthreadCreateProxied(pthread_ptr, attr, startRoutine, arg); + } + + // If on the main thread, and accessing Canvas/OffscreenCanvas failed, abort + // with the detected error. + if (error) return error; + +#if OFFSCREENCANVAS_SUPPORT + // Register for each of the transferred canvases that the new thread now + // owns the OffscreenCanvas. + for (var canvas of Object.values(offscreenCanvases)) { + // pthread ptr to the thread that owns this canvas. + {{{ makeSetValue('canvas.canvasSharedPtr', 8, 'pthread_ptr', '*') }}}; + } +#endif + + var threadParams = { + startRoutine, + pthread_ptr, + arg, +#if OFFSCREENCANVAS_SUPPORT + moduleCanvasId, + offscreenCanvases, +#endif + transferList, + }; + + if (ENVIRONMENT_IS_PTHREAD) { + // The prepopulated pool of web workers that can host pthreads is stored + // in the main JS thread. Therefore if a pthread is attempting to spawn a + // new thread, the thread creation must be deferred to the main JS thread. + threadParams.cmd = 'spawnThread'; + postMessage(threadParams, transferList); + // When we defer thread creation this way, we have no way to detect thread + // creation synchronously today, so we have to assume success and return 0. + return 0; + } + + // We are the main thread, so we have the pthread warmup pool in this + // thread and can fire off JS thread creation directly ourselves. + return spawnThread(threadParams); + }, + +#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME + emscripten_check_blocking_allowed__deps: ['$warnOnce'], +#endif + emscripten_check_blocking_allowed: () => { +#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) return; +#endif + + if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine. + + warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#if !ALLOW_BLOCKING_ON_MAIN_THREAD + abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#endif + +#endif + }, + + // This function is called by a pthread to signal that exit() was called and + // that the entire process should exit. + // This function is always called from a pthread, but is executed on the + // main thread due to the __proxy attribute. + $exitOnMainThread__deps: ['exit'], + $exitOnMainThread__proxy: 'async', + $exitOnMainThread: (returnCode) => { +#if PTHREADS_DEBUG + dbg('exitOnMainThread'); +#endif +#if PROXY_TO_PTHREAD + {{{ runtimeKeepalivePop() }}}; +#endif + _exit(returnCode); + }, + +#if MEMORY64 + // Calls proxyToMainThread but returns a bigint rather than a number + $proxyToMainThreadPtr__deps: ['$proxyToMainThread'], + $proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)), +#endif + + $proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_js_on_main_thread'], + $proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */', + $proxyToMainThread: (funcIndex, emAsmAddr, proxyMode, ...callArgs) => { + // EM_ASM proxying is done by passing a pointer to the address of the EM_ASM + // content as `emAsmAddr`. JS library proxying is done by passing an index + // into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then + // `funcIndex` will be ignored. + // Additional arguments are passed after the first three are the actual + // function arguments. + // The serialization buffer contains the number of call params, and then + // all the args here. + // + // We also pass 'proxyMode' to C separately, since C needs to look at it. + // + // Allocate a buffer (on the stack), which will be copied if necessary by + // the C code. + // + // First passed parameter specifies the number of arguments to the function. + // When BigInt support is enabled, we must handle types in a more complex + // way, detecting at runtime if a value is a BigInt or not (as we have no + // type info here). To do that, add a "prefix" before each value that + // indicates if it is a BigInt, which effectively doubles the number of + // values we serialize for proxying. TODO: pack this? + var bufSize = 8 * callArgs.length {{{ WASM_BIGINT ? "* 2" : "" }}}; + var sp = stackSave(); + var args = stackAlloc(bufSize); + var b = {{{ getHeapOffset('args', 'i64') }}}; + for (var arg of callArgs) { +#if WASM_BIGINT + if (typeof arg == 'bigint') { + // The prefix is non-zero to indicate a bigint. + HEAP64[b++] = 1n; + HEAP64[b++] = arg; + } else { + // The prefix is zero to indicate a JS Number. + HEAP64[b++] = 0n; + HEAPF64[b++] = arg; + } +#else + HEAPF64[b++] = arg; +#endif + } + var rtn = __emscripten_run_js_on_main_thread(funcIndex, emAsmAddr, bufSize, args, proxyMode); + stackRestore(sp); + return rtn; + }, + + // Reuse global JS array to avoid creating JS garbage for each proxied call + $proxiedJSCallArgs: [], + + _emscripten_receive_on_main_thread_js__deps: [ + '$proxyToMainThread', + '_emscripten_run_js_on_main_thread_done', + '$proxiedJSCallArgs'], + _emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args, ctx, ctxArgs) => { + // Sometimes we need to backproxy events to the calling thread (e.g. + // HTML5 DOM events handlers such as + // emscripten_set_mousemove_callback()), so keep track in a globally + // accessible variable about the thread that initiated the proxying. + proxiedJSCallArgs.length = 0; + var b = {{{ getHeapOffset('args', 'i64') }}}; + var end = {{{ getHeapOffset('args + bufSize', 'i64') }}}; + while (b < end) { +#if WASM_BIGINT + var arg; + if (HEAP64[b++]) { + // It's a BigInt. + arg = HEAP64[b++]; + } else { + // It's a Number. + arg = HEAPF64[b++]; + } +#else + var arg = HEAPF64[b++]; +#endif + proxiedJSCallArgs.push(arg); + } + // Proxied JS library funcs use funcIndex and EM_ASM functions use emAsmAddr +#if HAVE_EM_ASM + var func = emAsmAddr ? ASM_CONSTS[emAsmAddr] : proxiedFunctionTable[funcIndex]; +#else +#if ASSERTIONS + assert(!emAsmAddr); +#endif + var func = proxiedFunctionTable[funcIndex]; +#endif +#if ASSERTIONS + assert(!(funcIndex && emAsmAddr)); + assert(func.length == proxiedJSCallArgs.length, 'Call args mismatch in _emscripten_receive_on_main_thread_js'); +#endif + PThread.currentProxiedOperationCallerThread = callingThread; + var rtn = func(...proxiedJSCallArgs); + PThread.currentProxiedOperationCallerThread = 0; + if (ctx) { + rtn.then((rtn) => __emscripten_run_js_on_main_thread_done(ctx, ctxArgs, rtn)); + return; + } + +#if MEMORY64 + // In memory64 mode some proxied functions return bigint/pointer but + // our return type is i53/double. + if (typeof rtn == "bigint") { + rtn = bigintToI53Checked(rtn); + } +#endif +#if ASSERTIONS + // Proxied functions can return any type except bigint. All other types + // coerce to f64/double (the return type of this function in C) but not + // bigint. + assert(typeof rtn != "bigint"); +#endif + return rtn; + }, + + $establishStackSpace__internal: true, + $establishStackSpace__deps: ['$stackRestore', 'emscripten_stack_set_limits'], + $establishStackSpace: function (pthread_ptr) { + var stackHigh = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack, '*') }}}; + var stackSize = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack_size, '*') }}}; + var stackLow = stackHigh - stackSize; +#if PTHREADS_DEBUG + dbg(`establishStackSpace: ${ptrToString(stackHigh)} -> ${ptrToString(stackLow)}`); +#endif +#if ASSERTIONS + assert(stackHigh != 0); + assert(stackLow != 0); + assert(stackHigh > stackLow, 'stackHigh must be higher then stackLow'); +#endif + // Set stack limits used by `emscripten/stack.h` function. These limits are + // cached in wasm-side globals to make checks as fast as possible. + _emscripten_stack_set_limits(stackHigh, stackLow); + +#if STACK_OVERFLOW_CHECK >= 2 + setStackLimits(); +#endif STACK_OVERFLOW_CHECK + + // Call inside wasm module to set up the stack frame for this pthread in wasm module scope + stackRestore(stackHigh); + +#if STACK_OVERFLOW_CHECK + // Write the stack cookie last, after we have set up the proper bounds and + // current position of the stack. + writeStackCookie(); +#endif + }, + + $invokeEntryPoint__deps: [ + '_emscripten_thread_exit', +#if !MINIMAL_RUNTIME + '$keepRuntimeAlive', + '$runtimeKeepaliveCounter', +#endif + ], + $invokeEntryPoint: {{{ asyncIf(ASYNCIFY == 2) }}}(ptr, arg) => { +#if PTHREADS_DEBUG + dbg(`invokeEntryPoint: ${ptrToString(ptr)}`); +#endif +#if !MINIMAL_RUNTIME + // An old thread on this worker may have been canceled without returning the + // `runtimeKeepaliveCounter` to zero. Reset it now so the new thread won't + // be affected. + runtimeKeepaliveCounter = 0; + +#if isSymbolNeeded('$noExitRuntime') + // Same for noExitRuntime. The default for pthreads should always be false + // otherwise pthreads would never complete and attempts to pthread_join to + // them would block forever. + // pthreads can still choose to set `noExitRuntime` explicitly, or + // call emscripten_unwind_to_js_event_loop to extend their lifetime beyond + // their main function. See comment in src/runtime_pthread.js for more. + noExitRuntime = 0; +#endif +#endif + +#if MAIN_MODULE + // Before we call the thread entry point, make sure any shared libraries + // have been loaded on this thread. Otherwise our table might be not be + // in sync and might not contain the function pointer `ptr` at all. + __emscripten_dlsync_self(); +#endif + // pthread entry points are always of signature 'void *ThreadMain(void *arg)' + // Native codebases sometimes spawn threads with other thread entry point + // signatures, such as void ThreadMain(void *arg), void *ThreadMain(), or + // void ThreadMain(). That is not acceptable per C/C++ specification, but + // x86 compiler ABI extensions enable that to work. If you find the + // following line to crash, either change the signature to "proper" void + // *ThreadMain(void *arg) form, or try linking with the Emscripten linker + // flag -sEMULATE_FUNCTION_POINTER_CASTS to add in emulation for this x86 + // ABI extension. + + var result = {{{ makeDynCall('pp', 'ptr', ASYNCIFY == 2) }}}(arg); + +#if STACK_OVERFLOW_CHECK + checkStackCookie(); +#endif + function finish(result) { +#if !MINIMAL_RUNTIME + // In MINIMAL_RUNTIME the noExitRuntime concept does not apply to + // pthreads. To exit a pthread with live runtime, use the function + // emscripten_unwind_to_js_event_loop() in the pthread body. + if (keepRuntimeAlive()) { + EXITSTATUS = result; + return; + } +#endif + __emscripten_thread_exit(result); + } +#if ASYNCIFY == 2 + result = await result; +#endif + finish(result); + }, + +#if MAIN_MODULE + _emscripten_thread_exit_joinable: (thread) => { + // Called when a thread exits and is joinable. We mark these threads + // as finished, which means they are in state where are no longer actually + // running, but remain around waiting to be joined. In this state they + // cannot run any more proxied work. + if (!ENVIRONMENT_IS_PTHREAD) markAsFinished(thread); + else postMessage({ cmd: 'markAsFinished', thread }); + }, + + $markAsFinished: (pthread_ptr) => { +#if PTHREADS_DEBUG + dbg(`markAsFinished: ${ptrToString(pthread_ptr)}`); +#endif + PThread.finishedThreads.add(pthread_ptr); + if (pthread_ptr in PThread.outstandingPromises) { + PThread.outstandingPromises[pthread_ptr].resolve(); + } + }, + + // Asynchronous version _emscripten_dlsync_threads. + // This is always called on the main thread. This work happens asynchronously. + $dlsyncThreadsAsync__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'], + $dlsyncThreadsAsync: async () => { + const caller = PThread.currentProxiedOperationCallerThread; +#if PTHREADS_DEBUG + dbg("dlsyncThreadsAsync caller=" + ptrToString(caller)); +#endif +#if ASSERTIONS + assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! dlsyncThreadsAsync() can only ever be called from main thread'); + assert(Object.keys(PThread.outstandingPromises).length === 0); +#endif + + const promises = []; + + // This first promise resolves once the main thread has loaded all modules. + var info = makePromise(); + promises.push(info.promise); + __emscripten_dlsync_self_async(info.id); + + + // We then create a sequence of promises, one per thread, that resolve once + // each thread has performed its sync using _emscripten_proxy_dlsync. + // Any new threads that are created after this call will automatically be + // in sync because we call `__emscripten_dlsync_self` in + // invokeEntryPoint before the threads entry point is called. + for (const ptr of Object.keys(PThread.pthreads)) { + const pthread_ptr = Number(ptr); + if (pthread_ptr !== caller && !PThread.finishedThreads.has(pthread_ptr)) { + info = makePromise(); + __emscripten_proxy_dlsync_async(pthread_ptr, info.id); + PThread.outstandingPromises[pthread_ptr] = info; + promises.push(info.promise); + } + } + +#if PTHREADS_DEBUG + dbg(`dlsyncThreadsAsync: waiting on ${promises.length} promises`); +#endif + await Promise.all(promises); + + PThread.outstandingPromises = {}; +#if PTHREADS_DEBUG + dbg('dlsyncThreadsAsync done'); +#endif + }, + + // Synchronous version of dlsync_threads. This is only needed for the case when + // the main thread call dlopen and in that case we have no choice but to + // synchronously block the main thread until all other threads are in sync. + // When `dlopen` is called from a worker, the worker itself is blocked but + // the operation its waiting on (on the main thread) can be async. + _emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync', '$dlsyncThreadsAsync'], + _emscripten_dlsync_threads__async: 'auto', + _emscripten_dlsync_threads__proxy: 'sync', + _emscripten_dlsync_threads: () => { + const callingThread = PThread.currentProxiedOperationCallerThread; + if (callingThread) { + return dlsyncThreadsAsync(); + } + for (const ptr of Object.keys(PThread.pthreads)) { + const pthread_ptr = Number(ptr); + if (!PThread.finishedThreads.has(pthread_ptr)) { + __emscripten_proxy_dlsync(pthread_ptr); + } + } + }, +#endif // MAIN_MODULE + + $checkMailbox__deps: ['$callUserCallback', + 'pthread_self', + '_emscripten_check_mailbox', + '_emscripten_thread_mailbox_await'], + $checkMailbox: () => callUserCallback(() => { + // Only check the mailbox if we have a live pthread runtime. We implement + // pthread_self to return 0 if there is no live runtime. + // + // TODO(https://github.com/emscripten-core/emscripten/issues/25076): + // Is this check still needed? `callUserCallback` is supposed to + // ensure the runtime is alive, and if `_pthread_self` is NULL then the + // runtime certainly is *not* alive, so this should be a redundant check. + var pthread_ptr = _pthread_self(); + if (pthread_ptr) { + // If we are using Atomics.waitAsync as our notification mechanism, wait + // for a notification before processing the mailbox to avoid missing any + // work that could otherwise arrive after we've finished processing the + // mailbox and before we're ready for the next notification. + __emscripten_thread_mailbox_await(pthread_ptr); + __emscripten_check_mailbox(); + } + }), + + _emscripten_thread_mailbox_await__deps: ['$checkMailbox', '$waitAsyncPolyfilled'], + _emscripten_thread_mailbox_await: (pthread_ptr) => { + if (!waitAsyncPolyfilled) { + // Wait on the pthread's initial self-pointer field because it is easy and + // safe to access from sending threads that need to notify the waiting + // thread. + // Note: Under wasm64 only the low 32-bit of the pthread_ptr are + // read/compared here, but we don't actually care about the exact values + // here as long as they match. + var wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('pthread_ptr', 'i32') }}}, pthread_ptr); +#if ASSERTIONS + assert(wait.async); +#endif + wait.value.then(checkMailbox); + var waitingAsync = pthread_ptr + {{{ C_STRUCTS.pthread.waiting_async }}}; + Atomics.store(HEAP32, {{{ getHeapOffset('waitingAsync', 'i32') }}}, 1); + } + // If `Atomics.waitAsync` is not implemented, then we will always fall back + // to postMessage and there is no need to do anything here. + }, + + // PostMessage is used to notify threads instead of Atomics.notify whenever + // the environment does not implement Atomics.waitAsync or when messaging a + // new thread that has not had a chance to initialize itself and execute + // Atomics.waitAsync to prepare for the notification. + _emscripten_notify_mailbox_postmessage__deps: ['$checkMailbox'], + _emscripten_notify_mailbox_postmessage: (targetThread, currThreadId) => { + if (targetThread == currThreadId) { + setTimeout(checkMailbox); + } else if (ENVIRONMENT_IS_PTHREAD) { + postMessage({targetThread, cmd: 'checkMailbox'}); + } else { + var worker = PThread.pthreads[targetThread]; + if (!worker) { +#if ASSERTIONS + err(`Cannot send message to thread with ID ${targetThread}, unknown thread ID!`); +#endif + return; + } + worker.postMessage({cmd: 'checkMailbox'}); + } + } +}; + +autoAddDeps(LibraryPThread, '$PThread'); +addToLibrary(LibraryPThread); diff --git a/src/lib/libpthread_stub.js b/src/lib/libpthread_stub.js new file mode 100644 index 0000000000000..8c6e93d3d1598 --- /dev/null +++ b/src/lib/libpthread_stub.js @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2015 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +#if PTHREADS +#error "Internal error! PTHREADS should not be enabled when including library_pthread_stub.js." +#endif + +var LibraryPThreadStub = { + // =================================================================================== + // Stub implementation for pthread.h when not compiling with pthreads support enabled. + // =================================================================================== + + emscripten_is_main_browser_thread: () => +#if MINIMAL_RUNTIME + typeof WorkerGlobalScope == 'undefined' +#else + !ENVIRONMENT_IS_WORKER +#endif + , +}; + +addToLibrary(LibraryPThreadStub); diff --git a/src/lib/libsdl.js b/src/lib/libsdl.js new file mode 100644 index 0000000000000..b17b1da60e805 --- /dev/null +++ b/src/lib/libsdl.js @@ -0,0 +1,3646 @@ +/** + * @license + * Copyright 2010 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +//"use strict"; + +// See browser tests for examples (test/runner.py, search for sdl_). Run with +// test/runner browser + +// Notes: +// SDL_VIDEORESIZE: This is sent when the canvas is resized. Note that the user +// cannot manually do so, so this is only sent when the +// program manually resizes it (emscripten_set_canvas_element_size +// or otherwise). + +var LibrarySDL = { + $SDL__deps: [ + '$PATH', '$Browser', 'SDL_GetTicks', 'SDL_LockSurface', + '$MainLoop', + // For makeCEvent(). + '$stringToUTF8', + // Many SDL functions depend on malloc/free + 'malloc', 'free', + 'memcpy', + ], + $SDL: { + defaults: { + width: 320, + height: 200, + // If true, SDL_LockSurface will copy the contents of each surface back to + // the Emscripten HEAP so that C code can access it. If false, the surface + // contents are captured only back to JS code. + copyOnLock: true, + // If true, SDL_LockSurface will discard the contents of each surface when + // SDL_LockSurface() is called. This greatly improves performance of + // SDL_LockSurface(). If discardOnLock is true, copyOnLock is ignored. + discardOnLock: false, + // If true, emulate compatibility with desktop SDL by ignoring alpha on + // the screen frontbuffer canvas. Setting this to false will improve + // performance considerably and enables alpha-blending on the frontbuffer, + // so be sure to properly write 0xFF alpha for opaque pixels if you set + // this to false! + opaqueFrontBuffer: true + }, + + version: null, + + surfaces: {}, + // A pool of freed canvas elements. Reusing them avoids GC pauses. + canvasPool: [], + events: [], + fonts: [null], + + // The currently preloaded audio elements ready to be played + audios: [null], + rwops: [null], + // The currently playing audio element. There's only one music track. + music: { + audio: null, + volume: 1.0 + }, + mixerFrequency: 22050, + mixerFormat: {{{ cDefs.AUDIO_S16LSB }}}, + mixerNumChannels: 2, + mixerChunkSize: 1024, + channelMinimumNumber: 0, + + // Set to true if we call SDL_SetVideoMode with SDL_OPENGL, and if so, we do + // not create 2D canvases&contexts for blitting + // Note that images loaded before SDL_SetVideoMode will not get this + // optimization + GL: false, + + // all possible GL attributes, with their default value + glAttributes: { + 0: 3, /* SDL_GL_RED_SIZE */ + 1: 3, /* SDL_GL_GREEN_SIZE */ + 2: 2, /* SDL_GL_BLUE_SIZE */ + 3: 0, /* SDL_GL_ALPHA_SIZE */ + 4: 0, /* SDL_GL_BUFFER_SIZE */ + 5: 1, /* SDL_GL_DOUBLEBUFFER */ + 6: 16, /* SDL_GL_DEPTH_SIZE */ + 7: 0, /* SDL_GL_STENCIL_SIZE */ + 8: 0, /* SDL_GL_ACCUM_RED_SIZE */ + 9: 0, /* SDL_GL_ACCUM_GREEN_SIZE */ + 10: 0, /* SDL_GL_ACCUM_BLUE_SIZE */ + 11: 0, /* SDL_GL_ACCUM_ALPHA_SIZE */ + 12: 0, /* SDL_GL_STEREO */ + 13: 0, /* SDL_GL_MULTISAMPLEBUFFERS */ + 14: 0, /* SDL_GL_MULTISAMPLESAMPLES */ + 15: 1, /* SDL_GL_ACCELERATED_VISUAL */ + 16: 0, /* SDL_GL_RETAINED_BACKING */ + 17: 0, /* SDL_GL_CONTEXT_MAJOR_VERSION */ + 18: 0 /* SDL_GL_CONTEXT_MINOR_VERSION */ + }, + + keyboardState: null, + keyboardMap: {}, + + canRequestFullscreen: false, + isRequestingFullscreen: false, + + textInput: false, + unicode: false, + ttfContext: null, + audio: null, + + startTime: null, + initFlags: 0, // The flags passed to SDL_Init + buttonState: 0, + modState: 0, + DOMButtons: [0, 0, 0], + + DOMEventToSDLEvent: {}, + + TOUCH_DEFAULT_ID: 0, // Our default deviceID for touch events (we get nothing from the browser) + + eventHandler: null, + eventHandlerContext: null, + eventHandlerTemp: 0, + + // DOM code ==> SDL code. See + // https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/KeyboardEvent + // and SDL_keycode.h + // For keys that don't have unicode value, we map DOM codes with the + // corresponding scan codes + 1024 (using "| 1 << 10") + keyCodes: { + 16: 225 | 1<<10, // shift + 17: 224 | 1<<10, // control (right, or left) + 18: 226 | 1<<10, // alt + 20: 57 | 1<<10, // caps lock + + 33: 75 | 1<<10, // pagedup + 34: 78 | 1<<10, // pagedown + 35: 77 | 1<<10, // end + 36: 74 | 1<<10, // home + 37: 80 | 1<<10, // left arrow + 38: 82 | 1<<10, // up arrow + 39: 79 | 1<<10, // right arrow + 40: 81 | 1<<10, // down arrow + 44: 316, // print screen + 45: 73 | 1<<10, // insert + 46: 127, // SDLK_DEL == '\177' + + 91: 227 | 1<<10, // windows key or super key on linux (doesn't work on Mac) + 93: 101 | 1<<10, // application + + 96: 98 | 1<<10, // keypad 0 + 97: 89 | 1<<10, // keypad 1 + 98: 90 | 1<<10, // keypad 2 + 99: 91 | 1<<10, // keypad 3 + 100: 92 | 1<<10, // keypad 4 + 101: 93 | 1<<10, // keypad 5 + 102: 94 | 1<<10, // keypad 6 + 103: 95 | 1<<10, // keypad 7 + 104: 96 | 1<<10, // keypad 8 + 105: 97 | 1<<10, // keypad 9 + 106: 85 | 1<<10, // keypad multiply + 107: 87 | 1<<10, // keypad plus + 109: 86 | 1<<10, // keypad minus + 110: 99 | 1<<10, // keypad decimal point + 111: 84 | 1<<10, // keypad divide + 112: 58 | 1<<10, // F1 + 113: 59 | 1<<10, // F2 + 114: 60 | 1<<10, // F3 + 115: 61 | 1<<10, // F4 + 116: 62 | 1<<10, // F5 + 117: 63 | 1<<10, // F6 + 118: 64 | 1<<10, // F7 + 119: 65 | 1<<10, // F8 + 120: 66 | 1<<10, // F9 + 121: 67 | 1<<10, // F10 + 122: 68 | 1<<10, // F11 + 123: 69 | 1<<10, // F12 + 124: 104 | 1<<10, // F13 + 125: 105 | 1<<10, // F14 + 126: 106 | 1<<10, // F15 + 127: 107 | 1<<10, // F16 + 128: 108 | 1<<10, // F17 + 129: 109 | 1<<10, // F18 + 130: 110 | 1<<10, // F19 + 131: 111 | 1<<10, // F20 + 132: 112 | 1<<10, // F21 + 133: 113 | 1<<10, // F22 + 134: 114 | 1<<10, // F23 + 135: 115 | 1<<10, // F24 + + 144: 83 | 1<<10, // keypad num lock + + 160: 94, // caret + 161: 33, // exclaim + 162: 34, // double quote + 163: 35, // hash + 164: 36, // dollar + 165: 37, // percent + 166: 38, // ampersand + 167: 95, // underscore + 168: 40, // open parenthesis + 169: 41, // close parenthesis + 170: 42, // asterix + 171: 43, // plus + 172: 124, // pipe + 173: 45, // minus + 174: 123, // open curly bracket + 175: 125, // close curly bracket + 176: 126, // tilde + + 181: 127, // audio mute + 182: 129, // audio volume down + 183: 128, // audio volume up + + 188: 44, // comma + 190: 46, // period + 191: 47, // slash (/) + 192: 96, // backtick/backquote (`) + 219: 91, // open square bracket + 220: 92, // back slash + 221: 93, // close square bracket + 222: 39, // quote + 224: 227 | 1<<10, // meta (command/windows) + }, + + scanCodes: { // SDL keycode ==> SDL scancode. See SDL_scancode.h + 8: 42, // backspace + 9: 43, // tab + 13: 40, // return + 27: 41, // escape + 32: 44, // space + 35: 204, // hash + + 39: 53, // grave + + 44: 54, // comma + 46: 55, // period + 47: 56, // slash + 48: 39, // 0 + 49: 30, // 1 + 50: 31, // 2 + 51: 32, // 3 + 52: 33, // 4 + 53: 34, // 5 + 54: 35, // 6 + 55: 36, // 7 + 56: 37, // 8 + 57: 38, // 9 + 58: 203, // colon + 59: 51, // semicolon + + 61: 46, // equals + + 91: 47, // left bracket + 92: 49, // backslash + 93: 48, // right bracket + + 96: 52, // apostrophe + 97: 4, // A + 98: 5, // B + 99: 6, // C + 100: 7, // D + 101: 8, // E + 102: 9, // F + 103: 10, // G + 104: 11, // H + 105: 12, // I + 106: 13, // J + 107: 14, // K + 108: 15, // L + 109: 16, // M + 110: 17, // N + 111: 18, // O + 112: 19, // P + 113: 20, // Q + 114: 21, // R + 115: 22, // S + 116: 23, // T + 117: 24, // U + 118: 25, // V + 119: 26, // W + 120: 27, // X + 121: 28, // Y + 122: 29, // Z + + 127: 76, // delete + + 305: 224, // ctrl + + 308: 226, // alt + + 316: 70, // print screen + }, + + loadRect(rect) { + return { + x: {{{ makeGetValue('rect', C_STRUCTS.SDL_Rect.x, 'i32') }}}, + y: {{{ makeGetValue('rect', C_STRUCTS.SDL_Rect.y, 'i32') }}}, + w: {{{ makeGetValue('rect', C_STRUCTS.SDL_Rect.w, 'i32') }}}, + h: {{{ makeGetValue('rect', C_STRUCTS.SDL_Rect.h, 'i32') }}} + }; + }, + + updateRect(rect, r) { + {{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.x, 'r.x', 'i32') }}}; + {{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.y, 'r.y', 'i32') }}}; + {{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.w, 'r.w', 'i32') }}}; + {{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.h, 'r.h', 'i32') }}}; + }, + + intersectionOfRects(first, second) { + var leftX = Math.max(first.x, second.x); + var leftY = Math.max(first.y, second.y); + var rightX = Math.min(first.x + first.w, second.x + second.w); + var rightY = Math.min(first.y + first.h, second.y + second.h); + + return { + x: leftX, + y: leftY, + w: Math.max(leftX, rightX) - leftX, + h: Math.max(leftY, rightY) - leftY + } + }, + + checkPixelFormat(fmt) { +#if ASSERTIONS + // Canvas screens are always RGBA. + var format = {{{ makeGetValue('fmt', C_STRUCTS.SDL_PixelFormat.format, 'i32') }}}; + if (format != {{{ cDefs.SDL_PIXELFORMAT_RGBA8888 }}}) { + warnOnce('Unsupported pixel format!'); + } +#endif + }, + + // Load SDL color into a CSS-style color specification + loadColorToCSSRGB(color) { + var rgba = {{{ makeGetValue('color', 0, 'i32') }}}; + return 'rgb(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ')'; + }, + loadColorToCSSRGBA(color) { + var rgba = {{{ makeGetValue('color', 0, 'i32') }}}; + return 'rgba(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ',' + (((rgba >> 24)&255)/255) + ')'; + }, + + translateColorToCSSRGBA: (rgba) => + 'rgba(' + (rgba&0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>>24)/0xff + ')', + + translateRGBAToCSSRGBA: (r, g, b, a) => + 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')', + + translateRGBAToColor: (r, g, b, a) => r | g << 8 | b << 16 | a << 24, + + makeSurface(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) { + var is_SDL_HWSURFACE = flags & {{{ cDefs.SDL_HWSURFACE }}}; + var is_SDL_HWPALETTE = flags & {{{ cDefs.SDL_HWPALETTE }}}; + var is_SDL_OPENGL = flags & {{{ cDefs.SDL_OPENGL }}}; + + var surf = _malloc({{{ C_STRUCTS.SDL_Surface.__size__ }}}); + var pixelFormat = _malloc({{{ C_STRUCTS.SDL_PixelFormat.__size__ }}}); + // surface with SDL_HWPALETTE flag is 8bpp surface (1 byte) + var bpp = is_SDL_HWPALETTE ? 1 : 4; + var buffer = 0; + + // preemptively initialize this for software surfaces, + // otherwise it will be lazily initialized inside of SDL_LockSurface + if (!is_SDL_HWSURFACE && !is_SDL_OPENGL) { + buffer = _malloc(width * height * 4); + } + + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.flags, 'flags', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.format, 'pixelFormat', '*') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.w, 'width', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.h, 'height', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pitch, 'width * bpp', 'i32') }}}; // assuming RGBA or indexed for now, + // since that is what ImageData gives us in browsers + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'buffer', '*') }}}; + + var canvas = Browser.getCanvas(); + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.x, '0', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.y, '0', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.w, 'canvas.width', 'i32') }}}; + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.h, 'canvas.height', 'i32') }}}; + + {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.refcount, '1', 'i32') }}}; + + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.format, cDefs.SDL_PIXELFORMAT_RGBA8888, 'i32') }}}; + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.palette, '0', 'i32') }}};// TODO + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BitsPerPixel, 'bpp * 8', 'i8') }}}; + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BytesPerPixel, 'bpp', 'i8') }}}; + + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Rmask, 'rmask || 0x000000ff', 'i32') }}}; + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Gmask, 'gmask || 0x0000ff00', 'i32') }}}; + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Bmask, 'bmask || 0x00ff0000', 'i32') }}}; + {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Amask, 'amask || 0xff000000', 'i32') }}}; + + // Decide if we want to use WebGL or not + SDL.GL = SDL.GL || is_SDL_OPENGL; + if (!usePageCanvas) { + if (SDL.canvasPool.length > 0) { + canvas = SDL.canvasPool.pop(); + } else { + canvas = document.createElement('canvas'); + } + canvas.width = width; + canvas.height = height; + } + + var webGLContextAttributes = { + antialias: ((SDL.glAttributes[{{{ cDefs.SDL_GL_MULTISAMPLEBUFFERS }}}] != 0) && (SDL.glAttributes[{{{ cDefs.SDL_GL_MULTISAMPLESAMPLES }}}] > 1)), + depth: (SDL.glAttributes[{{{ cDefs.SDL_GL_DEPTH_SIZE }}}] > 0), + stencil: (SDL.glAttributes[{{{ cDefs.SDL_GL_STENCIL_SIZE }}}] > 0), + alpha: (SDL.glAttributes[{{{ cDefs.SDL_GL_ALPHA_SIZE }}}] > 0) + }; + +#if OFFSCREEN_FRAMEBUFFER + // TODO: Make SDL explicitly aware of whether it is being proxied or not, + // and set these to true only when proxying is being performed. + GL.enableOffscreenFramebufferAttributes(webGLContextAttributes); +#endif + var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes); + + SDL.surfaces[surf] = { + width, + height, + canvas, + ctx, + surf, + buffer, + pixelFormat, + alpha: 255, + flags, + locked: 0, + usePageCanvas, + source, + + isFlagSet: (flag) => flags & flag + }; + + return surf; + }, + + // Copy data from the C++-accessible storage to the canvas backing + // for surface with HWPALETTE flag(8bpp depth) + copyIndexedColorData(surfData, rX, rY, rW, rH) { + // HWPALETTE works with palette + // set by SDL_SetColors + if (!surfData.colors) { + return; + } + + var canvas = Browser.getCanvas(); + var fullWidth = canvas.width; + var fullHeight = canvas.height; + + var startX = rX || 0; + var startY = rY || 0; + var endX = (rW || (fullWidth - startX)) + startX; + var endY = (rH || (fullHeight - startY)) + startY; + + var buffer = surfData.buffer; + + if (!surfData.image.data32) { + surfData.image.data32 = new Uint32Array(surfData.image.data.buffer); + } + var data32 = surfData.image.data32; + + var colors32 = surfData.colors32; + + for (var y = startY; y < endY; ++y) { + var base = y * fullWidth; + for (var x = startX; x < endX; ++x) { + data32[base + x] = colors32[{{{ makeGetValue('buffer', 'base + x', 'u8') }}}]; + } + } + }, + + freeSurface(surf) { + var refcountPointer = surf + {{{ C_STRUCTS.SDL_Surface.refcount }}}; + var refcount = {{{ makeGetValue('refcountPointer', 0, 'i32') }}}; + if (refcount > 1) { + {{{ makeSetValue('refcountPointer', 0, 'refcount - 1', 'i32') }}}; + return; + } + + var info = SDL.surfaces[surf]; + if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas); + _free(info.buffer); + _free(info.pixelFormat); + _free(surf); + SDL.surfaces[surf] = null; + + if (surf === SDL.screen) { + SDL.screen = null; + } + }, + + blitSurface(src, srcrect, dst, dstrect, scale) { + var srcData = SDL.surfaces[src]; + var dstData = SDL.surfaces[dst]; + var sr, dr; + if (srcrect) { + sr = SDL.loadRect(srcrect); + } else { + sr = { x: 0, y: 0, w: srcData.width, h: srcData.height }; + } + if (dstrect) { + dr = SDL.loadRect(dstrect); + } else { + dr = { x: 0, y: 0, w: srcData.width, h: srcData.height }; + } + if (dstData.clipRect) { + var widthScale = (!scale || sr.w === 0) ? 1 : sr.w / dr.w; + var heightScale = (!scale || sr.h === 0) ? 1 : sr.h / dr.h; + + dr = SDL.intersectionOfRects(dstData.clipRect, dr); + + sr.w = dr.w * widthScale; + sr.h = dr.h * heightScale; + + if (dstrect) { + SDL.updateRect(dstrect, dr); + } + } + var blitw, blith; + if (scale) { + blitw = dr.w; blith = dr.h; + } else { + blitw = sr.w; blith = sr.h; + } + if (sr.w === 0 || sr.h === 0 || blitw === 0 || blith === 0) { + return 0; + } + var oldAlpha = dstData.ctx.globalAlpha; + dstData.ctx.globalAlpha = srcData.alpha/255; + dstData.ctx.drawImage(srcData.canvas, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, blitw, blith); + dstData.ctx.globalAlpha = oldAlpha; + if (dst != SDL.screen) { + // XXX As in IMG_Load, for compatibility we write out |pixels| + warnOnce('WARNING: copying canvas data to memory for compatibility'); + _SDL_LockSurface(dst); + dstData.locked--; // The surface is not actually locked in this hack + } + return 0; + }, + + // the browser sends out touchstart events with the whole group of touches + // even if we received a previous touchstart for a specific touch identifier. + // You can test this by pressing one finger to the screen, then another. You'll + // receive two touchstart events, the first with a touches count of 1 the second + // with a touches count of two. + // SDL sends out a new touchstart event for only each newly started touch so to + // emulate this, we keep track of previously started touches. + downFingers: {}, + savedKeydown: null, + + receiveEvent(event) { + function unpressAllPressedKeys() { + // Un-press all pressed keys: TODO + for (var keyCode of Object.values(SDL.keyboardMap)) { + SDL.events.push({ + type: 'keyup', + keyCode, + }); + } + }; + switch (event.type) { + case 'touchstart': + case 'touchmove': { + event.preventDefault(); + + var touches = []; + + // Clear out any touchstart events that we've already processed + if (event.type === 'touchstart') { + for (var touch of event.touches) { + if (SDL.downFingers[touch.identifier] != true) { + SDL.downFingers[touch.identifier] = true; + touches.push(touch); + } + } + } else { + touches = event.touches; + } + + var firstTouch = touches[0]; + if (firstTouch) { + if (event.type == 'touchstart') { + SDL.DOMButtons[0] = 1; + } + var mouseEventType; + switch (event.type) { + case 'touchstart': mouseEventType = 'mousedown'; break; + case 'touchmove': mouseEventType = 'mousemove'; break; + } + var mouseEvent = { + type: mouseEventType, + button: 0, + pageX: firstTouch.clientX, + pageY: firstTouch.clientY + }; + SDL.events.push(mouseEvent); + } + + for (var touch of touches) { + SDL.events.push({ + type: event.type, + touch + }); + }; + break; + } + case 'touchend': { + event.preventDefault(); + + // Remove the entry in the SDL.downFingers hash + // because the finger is no longer down. + for (var touch of event.changedTouches) { + if (SDL.downFingers[touch.identifier] === true) { + delete SDL.downFingers[touch.identifier]; + } + } + + var mouseEvent = { + type: 'mouseup', + button: 0, + pageX: event.changedTouches[0].clientX, + pageY: event.changedTouches[0].clientY + }; + SDL.DOMButtons[0] = 0; + SDL.events.push(mouseEvent); + + for (var touch of event.changedTouches) { + SDL.events.push({ + type: 'touchend', + touch + }); + }; + break; + } + case 'DOMMouseScroll': + case 'mousewheel': + case 'wheel': + // Flip the wheel direction to translate from browser wheel direction + // (+:down) to SDL direction (+:up) + var delta = -Browser.getMouseWheelDelta(event); + // Quantize to integer so that minimum scroll is at least +/- 1. + delta = (delta == 0) ? 0 : (delta > 0 ? Math.max(delta, 1) : Math.min(delta, -1)); + + // Simulate old-style SDL events representing mouse wheel input as buttons + // Subtract one since JS->C marshalling is defined to add one back. + var button = (delta > 0 ? {{{ cDefs.SDL_BUTTON_WHEELUP }}} : {{{ cDefs.SDL_BUTTON_WHEELDOWN }}}) - 1; + SDL.events.push({ type: 'mousedown', button, pageX: event.pageX, pageY: event.pageY }); + SDL.events.push({ type: 'mouseup', button, pageX: event.pageX, pageY: event.pageY }); + + // Pass a delta motion event. + SDL.events.push({ type: 'wheel', deltaX: 0, deltaY: delta }); + // If we don't prevent this, then 'wheel' event will be sent again by + // the browser as 'DOMMouseScroll' and we will receive this same event + // the second time. + event.preventDefault(); + break; + case 'mousemove': + if (SDL.DOMButtons[0] === 1) { + SDL.events.push({ + type: 'touchmove', + touch: { + identifier: 0, + deviceID: {{{ cDefs.SDL_TOUCH_MOUSEID }}}, + pageX: event.pageX, + pageY: event.pageY + } + }); + } + if (Browser.pointerLock) { + // workaround for firefox bug 750111 + if ('mozMovementX' in event) { + event['movementX'] = event['mozMovementX']; + event['movementY'] = event['mozMovementY']; + } + // workaround for Firefox bug 782777 + if (event['movementX'] == 0 && event['movementY'] == 0) { + // ignore a mousemove event if it doesn't contain any movement info + // (without pointer lock, we infer movement from pageX/pageY, so this check is unnecessary) + event.preventDefault(); + return; + } + } + // fall through + case 'keydown': + case 'keyup': + case 'keypress': + case 'mousedown': + case 'mouseup': + // If we preventDefault on keydown events, the subsequent keypress events + // won't fire. However, it's fine (and in some cases necessary) to + // preventDefault for keys that don't generate a character. Otherwise, + // preventDefault is the right thing to do in general. + if (event.type !== 'keydown' || (!SDL.unicode && !SDL.textInput) || (event.key == 'Backspace' || event.key == 'Tab')) { + event.preventDefault(); + } + + if (event.type == 'mousedown') { + SDL.DOMButtons[event.button] = 1; + SDL.events.push({ + type: 'touchstart', + touch: { + identifier: 0, + deviceID: {{{ cDefs.SDL_TOUCH_MOUSEID }}}, + pageX: event.pageX, + pageY: event.pageY + } + }); + } else if (event.type == 'mouseup') { + // ignore extra ups, can happen if we leave the canvas while pressing down, then return, + // since we add a mouseup in that case + if (!SDL.DOMButtons[event.button]) { + return; + } + + SDL.events.push({ + type: 'touchend', + touch: { + identifier: 0, + deviceID: {{{ cDefs.SDL_TOUCH_MOUSEID }}}, + pageX: event.pageX, + pageY: event.pageY + } + }); + SDL.DOMButtons[event.button] = 0; + } + + // We can only request fullscreen as the result of user input. + // Due to this limitation, we toggle a boolean on keydown which + // SDL_WM_ToggleFullScreen will check and subsequently set another + // flag indicating for us to request fullscreen on the following + // keyup. This isn't perfect, but it enables SDL_WM_ToggleFullScreen + // to work as the result of a keypress (which is an extremely + // common use case). + if (event.type === 'keydown' || event.type === 'mousedown') { + SDL.canRequestFullscreen = true; + } else if (event.type === 'keyup' || event.type === 'mouseup') { + if (SDL.isRequestingFullscreen) { + Module['requestFullscreen'](/*lockPointer=*/true, /*resizeCanvas=*/true); + SDL.isRequestingFullscreen = false; + } + SDL.canRequestFullscreen = false; + } + + // SDL expects a unicode character to be passed to its keydown events. + // Unfortunately, the browser APIs only provide a charCode property on + // keypress events, so we must backfill in keydown events with their + // subsequent keypress event's charCode. + if (event.type === 'keypress' && SDL.savedKeydown) { + // charCode is read-only + SDL.savedKeydown.keypressCharCode = event.charCode; + SDL.savedKeydown = null; + } else if (event.type === 'keydown') { + SDL.savedKeydown = event; + } + + // Don't push keypress events unless SDL_StartTextInput has been called. + if (event.type !== 'keypress' || SDL.textInput) { + SDL.events.push(event); + } + break; + case 'mouseout': + // Un-press all pressed mouse buttons, because we might miss the release outside of the canvas + for (var i = 0; i < 3; i++) { + if (SDL.DOMButtons[i]) { + SDL.events.push({ + type: 'mouseup', + button: i, + pageX: event.pageX, + pageY: event.pageY + }); + SDL.DOMButtons[i] = 0; + } + } + event.preventDefault(); + break; + case 'focus': + SDL.events.push(event); + event.preventDefault(); + break; + case 'blur': + SDL.events.push(event); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'visibilitychange': + SDL.events.push({ + type: 'visibilitychange', + visible: !document.hidden + }); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'unload': + if (MainLoop.runner) { + SDL.events.push(event); + // Force-run a main event loop, since otherwise this event will never be caught! + MainLoop.runner(); + } + return; + case 'resize': + SDL.events.push(event); + // manually triggered resize event doesn't have a preventDefault member + if (event.preventDefault) { + event.preventDefault(); + } + break; + } + if (SDL.events.length >= 10000) { + err('SDL event queue full, dropping events'); + SDL.events = SDL.events.slice(0, 10000); + } + // If we have a handler installed, this will push the events to the app + // instead of the app polling for them. + SDL.flushEventsToHandler(); + return; + }, + + lookupKeyCodeForEvent(event) { + var code = event.keyCode; + if (code >= 65 && code <= 90) { // ASCII A-Z + code += 32; // make lowercase for SDL + } else { + // Look up DOM code in the keyCodes table with fallback for ASCII codes + // which can match between DOM codes and SDL keycodes (allows keyCodes + // to be smaller). + code = SDL.keyCodes[code] || (code < 128 ? code : 0); +#if RUNTIME_DEBUG + if (!code) dbg('unmapped keyCode: ', event.keyCode); +#endif + // If this is one of the modifier keys (224 | 1<<10 - 227 | 1<<10), and the event specifies that it is + // a right key, add 4 to get the right key SDL key code. + if (event.location === 2 /*KeyboardEvent.DOM_KEY_LOCATION_RIGHT*/ && code >= (224 | 1<<10) && code <= (227 | 1<<10)) { + code += 4; + } + } + return code; + }, + + handleEvent(event) { + if (event.handled) return; + event.handled = true; + + switch (event.type) { + case 'touchstart': + case 'touchend': + case 'touchmove': { + Browser.calculateMouseEvent(event); + break; + } + case 'keydown': + case 'keyup': { + var down = event.type === 'keydown'; + var code = SDL.lookupKeyCodeForEvent(event); + // Ignore key events that we don't (yet) map to SDL keys + if (!code) return; + // Assigning a boolean to HEAP8, that's alright but Closure would like to warn about it. + // TODO(https://github.com/emscripten-core/emscripten/issues/16311): + // This is kind of ugly hack. Perhaps we can find a better way? + /** @suppress{checkTypes} */ + {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}}; + // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED + SDL.modState = + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_LCTRL, 'i8') }}} ? {{{ cDefs.KMOD_LCTRL }}} : 0) | + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_LSHIFT, 'i8') }}} ? {{{ cDefs.KMOD_LSHIFT }}} : 0) | + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_LALT, 'i8') }}} ? {{{ cDefs.KMOD_LALT }}} : 0) | + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_RCTRL, 'i8') }}} ? {{{ cDefs.KMOD_RCTRL }}} : 0) | + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_RSHIFT, 'i8') }}} ? {{{ cDefs.KMOD_RSHIFT }}} : 0) | + ({{{ makeGetValue('SDL.keyboardState', cDefs.SDLK_RALT, 'i8') }}} ? {{{ cDefs.KMOD_RALT }}} : 0); + if (down) { + SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur + } else { + delete SDL.keyboardMap[code]; + } + + break; + } + case 'mousedown': + case 'mouseup': + if (event.type == 'mousedown') { + // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3, + // and DOM buttons are 0-2, so this means that the below formula is + // correct. + SDL.buttonState |= 1 << event.button; + } else if (event.type == 'mouseup') { + SDL.buttonState &= ~(1 << event.button); + } + // fall through + case 'mousemove': { + Browser.calculateMouseEvent(event); + break; + } + } + }, + + flushEventsToHandler() { + if (!SDL.eventHandler) return; + + while (SDL.pollEvent(SDL.eventHandlerTemp)) { + {{{ makeDynCall('ipp', 'SDL.eventHandler') }}}(SDL.eventHandlerContext, SDL.eventHandlerTemp); + } + }, + + pollEvent(ptr) { + if (SDL.initFlags & {{{ cDefs.SDL_INIT_JOYSTICK }}} && SDL.joystickEventState) { + // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured + // to automatically query for events, query for joystick events. + SDL.queryJoysticks(); + } + if (ptr) { + while (SDL.events.length > 0) { + if (SDL.makeCEvent(SDL.events.shift(), ptr) !== false) return 1; + } + return 0; + } + // XXX: somewhat risky in that we do not check if the event is real or not + // (makeCEvent returns false) if no pointer supplied + return SDL.events.length > 0; + }, + + // returns false if the event was determined to be irrelevant + makeCEvent(event, ptr) { + if (typeof event == 'number') { + // This is a pointer to a copy of a native C event that was SDL_PushEvent'ed + _memcpy(ptr, event, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}}); + _free(event); // the copy is no longer needed + return; + } + + SDL.handleEvent(event); + + switch (event.type) { + case 'keydown': case 'keyup': { + var down = event.type === 'keydown'; +#if RUNTIME_DEBUG + dbg(`received ${event.type} event: keyCode=${event.keyCode}, key=${event.key}, code=${event.code}`); +#endif + var key = SDL.lookupKeyCodeForEvent(event); + // Ignore key events that we don't (yet) map to SDL keys + if (!key) return false; + var scan; + if (key >= 1024) { + scan = key - 1024; + } else { + scan = SDL.scanCodes[key] || key; + } + + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.state, 'down ? 1 : 0', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.repeat, '0', 'i8') }}}; // TODO + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.scancode, 'scan', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.sym, 'key', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.mod, 'SDL.modState', 'i16') }}}; + // some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode. + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.unicode, 'event.keypressCharCode || key', 'i32') }}}; + + break; + } + case 'keypress': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + // Not filling in windowID for now + stringToUTF8(String.fromCharCode(event.charCode), ptr + {{{ C_STRUCTS.SDL_TextInputEvent.text }}}, 4); + break; + } + case 'mousedown': case 'mouseup': case 'mousemove': { + if (event.type != 'mousemove') { + var down = event.type === 'mousedown'; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.timestamp, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.which, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3 + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}}; + } else { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.timestamp, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.which, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}}; + } + break; + } + case 'wheel': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}}; + break; + } + case 'touchstart': case 'touchend': case 'touchmove': { + var touch = event.touch; + if (!Browser.touches[touch.identifier]) break; + var canvas = Browser.getCanvas(); + var x = Browser.touches[touch.identifier].x / canvas.width; + var y = Browser.touches[touch.identifier].y / canvas.height; + var lx = Browser.lastTouches[touch.identifier].x / canvas.width; + var ly = Browser.lastTouches[touch.identifier].y / canvas.height; + var dx = x - lx; + var dy = y - ly; + if (touch['deviceID'] === undefined) touch.deviceID = SDL.TOUCH_DEFAULT_ID; + if (dx === 0 && dy === 0 && event.type === 'touchmove') return false; // don't send these if nothing happened + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.timestamp, '_SDL_GetTicks()', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.touchId, 'touch.deviceID', 'i64') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.fingerId, 'touch.identifier', 'i64') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.x, 'x', 'float') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.y, 'y', 'float') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dx, 'dx', 'float') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dy, 'dy', 'float') }}}; + if (touch.force !== undefined) { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'touch.force', 'float') }}}; + } else { // No pressure data, send a digital 0/1 pressure. + {{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'event.type == "touchend" ? 0 : 1', 'float') }}}; + } + break; + } + case 'unload': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + break; + } + case 'resize': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.w, 'event.w', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}}; + break; + } + case 'joystick_button_up': case 'joystick_button_down': { + var state = event.type === 'joystick_button_up' ? 0 : 1; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}}; + break; + } + case 'joystick_axis_motion': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; + break; + } + case 'focus': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, cDefs.SDL_WINDOWEVENT_FOCUS_GAINED, 'i8') }}}; + break; + } + case 'blur': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, cDefs.SDL_WINDOWEVENT_FOCUS_LOST, 'i8') }}}; + break; + } + case 'visibilitychange': { + var visibilityEventID = event.visible ? {{{ cDefs.SDL_WINDOWEVENT_SHOWN }}} : {{{ cDefs.SDL_WINDOWEVENT_HIDDEN }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}}; + break; + } + default: abort(`Unhandled SDL event: ${event.type}`); + } + }, + + makeFontString(height, fontName) { + if (fontName[0] != "'" && fontName[0] != '"') { + // https://developer.mozilla.org/ru/docs/Web/CSS/font-family + // Font family names containing whitespace should be quoted. + // BTW, quote all font names is easier than searching spaces + fontName = `"${fontName}"`; + } + return height + 'px ' + fontName + ', serif'; + }, + + estimateTextWidth(fontData, text) { + var h = fontData.size; + var fontString = SDL.makeFontString(h, fontData.name); + var tempCtx = SDL.ttfContext; +#if ASSERTIONS + assert(tempCtx, 'TTF_Init must have been called'); +#endif + tempCtx.font = fontString; + var ret = tempCtx.measureText(text).width | 0; + return ret; + }, + + // Sound + + // Channels are a SDL abstraction for allowing multiple sound tracks to be + // played at the same time. We don't need to actually implement the mixing + // since the browser engine handles that for us. Therefore, in JS we just + // maintain a list of channels and return IDs for them to the SDL consumer. + allocateChannels(num) { // called from Mix_AllocateChannels and init + if (SDL.numChannels >= num && num != 0) return; + SDL.numChannels = num; + SDL.channels = []; + for (var i = 0; i < num; i++) { + SDL.channels[i] = { + audio: null, + volume: 1.0 + }; + } + }, + + setGetVolume(info, volume) { + if (!info) return 0; + var ret = info.volume * 128; // MIX_MAX_VOLUME + if (volume != -1) { + info.volume = Math.min(Math.max(volume, 0), 128) / 128; + if (info.audio) { + try { + info.audio.volume = info.volume; // For