diff --git a/.github/workflows/auto-start-ci.yml b/.github/workflows/auto-start-ci.yml index 34488eeed6d7..0a69ff636f25 100644 --- a/.github/workflows/auto-start-ci.yml +++ b/.github/workflows/auto-start-ci.yml @@ -1,3 +1,6 @@ +# This action uses the following secrets: +# JENKINS_USER: GitHub user whose Jenkins token is defined below +# JENKINS_TOKEN: Jenkins token, to be used to start CI name: Auto Start CI on: @@ -36,11 +39,13 @@ jobs: -t '{{ range . }}{{ .number }} {{ end }}' \ --limit 5)" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} start-ci: permissions: + checks: read contents: read pull-requests: write + statuses: read needs: get-prs-for-ci if: needs.get-prs-for-ci.outputs.numbers != '' runs-on: ubuntu-slim @@ -59,10 +64,10 @@ jobs: ncu-config set token "$GH_TOKEN" ncu-config set jenkins_token "$JENKINS_TOKEN" ncu-config set owner "$GITHUB_REPOSITORY_OWNER" - ncu-config set repo "$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)" + ncu-config set repo "${GITHUB_REPOSITORY#*/}" env: USERNAME: ${{ secrets.JENKINS_USER }} - GH_TOKEN: ${{ secrets.GH_USER_TOKEN }} + GH_TOKEN: ${{ github.token }} JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }} - name: Start the CI @@ -70,5 +75,4 @@ jobs: curl -fsSL "https://github.com/${GITHUB_REPOSITORY}/raw/${GITHUB_SHA}/tools/actions/start-ci.sh" \ | sh -s -- ${{ needs.get-prs-for-ci.outputs.numbers }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 276cf738f8a6..561f8d56813f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -141,7 +141,7 @@ jobs: --arg devTools '[]' \ --arg benchmarkTools '[]' \ --run ' - make -j4 V=1 + make build-ci -j4 V=1 ' - name: Run benchmark diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml index 1e1febe22c7f..248e64bab776 100644 --- a/.github/workflows/build-tarball.yml +++ b/.github/workflows/build-tarball.yml @@ -23,6 +23,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - vcbuild.bat - .** @@ -52,6 +53,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - vcbuild.bat - .** @@ -120,7 +122,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Download tarball uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.github/workflows/commit-queue.yml b/.github/workflows/commit-queue.yml index 7af712268711..a1d691e9e19f 100644 --- a/.github/workflows/commit-queue.yml +++ b/.github/workflows/commit-queue.yml @@ -22,78 +22,152 @@ permissions: contents: read jobs: - get_mergeable_prs: + get_candidate_prs: permissions: pull-requests: read if: github.repository == 'nodejs/node' runs-on: ubuntu-slim outputs: - numbers: ${{ steps.get_mergeable_prs.outputs.numbers }} + candidates: ${{ steps.get_candidate_prs.outputs.candidates }} steps: - - name: Get Pull Requests - id: get_mergeable_prs + - name: Get Pull Request Candidates + id: get_candidate_prs run: | - prs=$(gh pr list \ + list_prs() { + gh pr list \ --repo "$GITHUB_REPOSITORY" \ --base "$GITHUB_REF_NAME" \ --label 'commit-queue' \ + "$@" \ --json 'number' \ - --search "created:<=$(date --date="2 days ago" +"%Y-%m-%dT%H:%M:%S%z") -label:blocked" \ -t '{{ range . }}{{ .number }} {{ end }}' \ - --limit 100) - fast_track_prs=$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --base "$GITHUB_REF_NAME" \ - --label 'commit-queue' \ + --limit 100 + } + aged_prs=$(list_prs \ + --search "created:<=$(date --date="2 days ago" +"%Y-%m-%dT%H:%M:%S%z") -label:blocked") + fast_track_prs=$(list_prs \ --label 'fast-track' \ - --search "-label:blocked" \ - --json 'number' \ - -t '{{ range . }}{{ .number }} {{ end }}' \ - --limit 100) - numbers=$(echo $prs' '$fast_track_prs | jq -r -s 'unique | join(" ")') - echo "numbers=$numbers" >> "$GITHUB_OUTPUT" + --search "-label:blocked") + candidates=$(printf '%s %s\n' "$fast_track_prs" "$aged_prs" | + jq -r -s 'reduce .[] as $pr ([]; if index($pr) then . else . + [$pr] end) | join(" ")') + echo "candidates=$candidates" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} commitQueue: - needs: get_mergeable_prs - if: needs.get_mergeable_prs.outputs.numbers != '' + needs: get_candidate_prs + if: needs.get_candidate_prs.outputs.candidates != '' + permissions: + checks: read + contents: read + pull-requests: read + statuses: read runs-on: ubuntu-slim steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - # A personal token is required because pushing with GITHUB_TOKEN will - # prevent commits from running CI after they land. It needs - # to be set here because `checkout` configures GitHub authentication - # for push as well. - token: ${{ secrets.GH_USER_TOKEN }} - - # Install dependencies - name: Install Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ env.NODE_VERSION }} + - name: Install @node-core/utils run: npm install -g @node-core/utils - - name: Set variables - run: | - echo "REPOSITORY=$(echo "$GITHUB_REPOSITORY" | cut -d/ -f2)" >> "$GITHUB_ENV" - - name: Configure @node-core/utils run: | - ncu-config set branch "${GITHUB_REF_NAME}" - ncu-config set upstream origin - ncu-config set username "$USERNAME" - ncu-config set token "$GITHUB_TOKEN" - ncu-config set jenkins_token "$JENKINS_TOKEN" - ncu-config set repo "${REPOSITORY}" - ncu-config set owner "${GITHUB_REPOSITORY_OWNER}" + # Keep the config outside the workspace so checkout does not remove it. + ncu-config --global set branch "${GITHUB_REF_NAME}" + ncu-config --global set upstream origin + ncu-config --global set username "$USERNAME" + ncu-config --global set token "$GH_TOKEN" + ncu-config --global set jenkins_token "$JENKINS_TOKEN" + ncu-config --global set repo "${GITHUB_REPOSITORY#*/}" + ncu-config --global set owner "${GITHUB_REPOSITORY_OWNER}" env: USERNAME: ${{ secrets.JENKINS_USER }} - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} + GH_TOKEN: ${{ github.token }} JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }} + - name: Filter Pull Requests + id: get_mergeable_prs + run: | + readme="${RUNNER_TEMP}/README.md" + curl -fsSLo "$readme" "https://github.com/${GITHUB_REPOSITORY}/raw/${GITHUB_SHA}/README.md" + + numbers= + # shellcheck disable=SC2086 + for pr in $CANDIDATES; do + metadata="${RUNNER_TEMP}/metadata-${pr}.json" + output="${RUNNER_TEMP}/metadata-${pr}.txt" + if git node metadata "$pr" \ + --readme "$readme" \ + --json > "$metadata" 2> "$output"; then + metadata_status=0 + else + metadata_status=$? + fi + + if [ -s "$output" ]; then + cat "$output" + fi + + case "$metadata_status" in + 0|2[0-9]|4[0-9]) ;; + *) + echo "git node metadata failed for pr ${pr} with exit code ${metadata_status}" + exit 1 + ;; + esac + + metadata_exit_code=$(jq -r '.exitCode' "$metadata") || { + echo "failed to parse metadata JSON for pr ${pr}" + exit 1 + } + if [ "$metadata_exit_code" != "$metadata_status" ]; then + echo "metadata JSON exitCode mismatch for pr ${pr}" + exit 1 + fi + metadata_reason_codes=$(jq -r '.reasonCodes | join(", ")' "$metadata") || { + echo "failed to parse metadata reason codes for pr ${pr}" + exit 1 + } + + if [ "$metadata_status" -eq 0 ]; then + echo "pr ${pr} is ready for the commit queue" + numbers="$numbers $pr" + continue + fi + + if [ "$metadata_status" -ge 20 ] && [ "$metadata_status" -le 29 ]; then + echo "pr ${pr} skipped, not ready to land" + echo "reason codes: ${metadata_reason_codes}" + continue + fi + + echo "pr ${pr} will be handled by the commit queue" + echo "reason codes: ${metadata_reason_codes}" + numbers="$numbers $pr" + done + + numbers=$(echo "$numbers" | xargs) + echo "numbers=$numbers" >> "$GITHUB_OUTPUT" + env: + CANDIDATES: ${{ needs.get_candidate_prs.outputs.candidates }} + GH_TOKEN: ${{ github.token }} + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + if: steps.get_mergeable_prs.outputs.numbers != '' + with: + # A personal token is required because pushing with GITHUB_TOKEN will + # prevent commits from running CI after they land. It needs + # to be set here because `checkout` configures GitHub authentication + # for push as well. + token: ${{ secrets.GH_USER_TOKEN }} + - name: Start the Commit Queue - run: ./tools/actions/commit-queue.sh "${GITHUB_REPOSITORY_OWNER}" "${REPOSITORY}" ${{ needs.get_mergeable_prs.outputs.numbers }} + if: steps.get_mergeable_prs.outputs.numbers != '' + run: | + git config --local user.email "github-bot@iojs.org" + git config --local user.name "Node.js GitHub Bot" + ncu-config set token "$GH_TOKEN" + ./tools/actions/commit-queue.sh ${{ steps.get_mergeable_prs.outputs.numbers }} env: - GITHUB_TOKEN: ${{ secrets.GH_USER_TOKEN }} + GH_TOKEN: ${{ secrets.GH_USER_TOKEN }} diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml index 1519ef6592e8..92c9f3b88217 100644 --- a/.github/workflows/coverage-linux-without-intl.yml +++ b/.github/workflows/coverage-linux-without-intl.yml @@ -66,7 +66,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Install gcovr run: pip install gcovr==7.2 - name: Configure diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index e4a3c334c8cd..e97b759bc6a4 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -66,7 +66,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Install gcovr run: pip install gcovr==7.2 - name: Configure diff --git a/.github/workflows/coverage-windows.yml b/.github/workflows/coverage-windows.yml index 59329670e6c2..d0236cd7d609 100644 --- a/.github/workflows/coverage-windows.yml +++ b/.github/workflows/coverage-windows.yml @@ -23,6 +23,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - .** - '!.github/workflows/coverage-windows.yml' @@ -49,6 +50,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - .** - '!.github/workflows/coverage-windows.yml' diff --git a/.github/workflows/major-release.yml b/.github/workflows/major-release.yml index b65917f89e74..1b15a00c8df1 100644 --- a/.github/workflows/major-release.yml +++ b/.github/workflows/major-release.yml @@ -2,7 +2,7 @@ name: Major Release on: schedule: - - cron: 0 0 15 2,8 * # runs at midnight UTC every 15 February and 15 August + - cron: 0 0 15 2 * # runs at midnight UTC every 15 February permissions: contents: read diff --git a/.github/workflows/nix-changes.yml b/.github/workflows/nix-changes.yml index ef05b0bfc8ed..ed25798152e3 100644 --- a/.github/workflows/nix-changes.yml +++ b/.github/workflows/nix-changes.yml @@ -11,10 +11,12 @@ on: - v[0-9]+.x paths: - '**.nix' + - tools/nix/** - .github/workflows/nix-changes.yml pull_request: paths: - '**.nix' + - tools/nix/** - .github/workflows/nix-changes.yml types: [opened, synchronize, reopened, ready_for_review] @@ -47,7 +49,9 @@ jobs: with: fetch-depth: 2 persist-credentials: false - sparse-checkout: '*.nix' + sparse-checkout: | + shell.nix + tools/nix/ sparse-checkout-cone-mode: false - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 @@ -60,39 +64,14 @@ jobs: name: nodejs - name: Compute requisites after change - shell: bash # See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference, we want the pipefail option. - run: | - nix-store --query --references "$( - nix-instantiate -I "nixpkgs=./tools/nix/pkgs.nix" shell.nix \ - --arg devTools " - (import ./tools/nix/devTools.nix {}) - ++ builtins.attrValues ( - { inherit (import {}) nixfmt-tree sccache; } - // import ./tools/nix/openssl-matrix.nix {} - // import ./tools/nix/pkcs11.nix {} - )")" \ - | xargs nix-store --realise \ - | xargs nix-store --query --requisites \ - | sort -k1.45 \ - > requisites-${{ matrix.system }}-after.list + run: ./tools/nix/list-requisites.sh > requisites-${{ matrix.system }}-after.list - name: Compute requisites before change - shell: bash # See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference, we want the pipefail option. - # TODO(panva): add `// import ./tools/nix/pkcs11.nix {}` once landed run: | git reset HEAD^ --hard - nix-store --query --references "$( - nix-instantiate -I "nixpkgs=./tools/nix/pkgs.nix" shell.nix \ - --arg devTools " - (import ./tools/nix/devTools.nix {}) - ++ builtins.attrValues ( - { inherit (import {}) nixfmt-tree sccache; } - // import ./tools/nix/openssl-matrix.nix {} - )")" \ - | xargs nix-store --realise \ - | xargs nix-store --query --requisites \ - | sort -k1.45 \ - > requisites-${{ matrix.system }}-before.list + # TODO(aduh95): remove this once list-requisites.sh has reached `main` + [ -f tools/nix/list-requisites.sh ] || git checkout FETCH_HEAD -- tools/nix/list-requisites.sh + ./tools/nix/list-requisites.sh > requisites-${{ matrix.system }}-before.list - name: Output diff run: | diff --git a/.github/workflows/stress-test.yml b/.github/workflows/stress-test.yml index b6fa42137d5e..6f1e75813915 100644 --- a/.github/workflows/stress-test.yml +++ b/.github/workflows/stress-test.yml @@ -78,7 +78,7 @@ jobs: - name: Set up sccache uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 # This is needed due to https://github.com/nodejs/build/issues/3878 - name: Cleanup if: runner.os == 'macOS' diff --git a/.github/workflows/test-internet.yml b/.github/workflows/test-internet.yml index bcb9ff76372f..7052f014b200 100644 --- a/.github/workflows/test-internet.yml +++ b/.github/workflows/test-internet.yml @@ -63,7 +63,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Build run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn" - name: Test Internet diff --git a/.github/workflows/test-linux-quic.yml b/.github/workflows/test-linux-quic.yml index 38d2ef9b8407..e1a05cf91859 100644 --- a/.github/workflows/test-linux-quic.yml +++ b/.github/workflows/test-linux-quic.yml @@ -68,7 +68,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Build working-directory: node run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --v8-enable-temporal-support --experimental-quic" diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index c38bae0693fd..7681a176139d 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -9,6 +9,7 @@ on: - tools/actions/** - tools/clang-format/** - tools/dep_updaters/** + - tools/nix/** - test/internet/** - '**.nix' - .github/** @@ -27,6 +28,7 @@ on: - tools/actions/** - tools/clang-format/** - tools/dep_updaters/** + - tools/nix/** - test/internet/** - '**.nix' - .github/** @@ -79,7 +81,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 - name: Build working-directory: node run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --v8-enable-temporal-support" diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 173f6758ad71..c53bcde2851c 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -23,6 +23,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - vcbuild.bat - .** @@ -53,6 +54,7 @@ on: - tools/eslint-rules/** - tools/eslint/** - tools/lint-md/** + - tools/nix/** - typings/** - vcbuild.bat - .** @@ -102,7 +104,7 @@ jobs: if: github.base_ref == 'main' || github.ref_name == 'main' uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: - version: v0.16.0 + version: v0.17.0 # The `npm ci` for this step fails a lot as part of the Test step. Run it # now so that we don't have to wait 2 hours for the Build step to pass # first before that failure happens. (And if there's something about diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml index a8f2ddc27831..8a04fe4d74fc 100644 --- a/.github/workflows/test-shared.yml +++ b/.github/workflows/test-shared.yml @@ -44,6 +44,7 @@ on: - '!tools/nix/**' - '!tools/v8/**' - '!tools/v8_gypfiles/**' + - tools/nix/list-requisites.sh - typings/** - vcbuild.bat - .** @@ -96,6 +97,7 @@ on: - '!tools/nix/**' - '!tools/v8/**' - '!tools/v8_gypfiles/**' + - tools/nix/list-requisites.sh - typings/** - vcbuild.bat - .** @@ -246,7 +248,7 @@ jobs: with: runner: ubuntu-24.04-arm v8-nar: ${{ needs.build-aarch64-linux-v8.outputs.local-cache && 'libv8-aarch64-linux.nar' }} - pkcs11-store-test: ${{ matrix.openssl.attr == 'openssl_3_5' }} + pkcs11-store-test: ${{ matrix.openssl.attr == 'openssl' }} # Override just the `openssl` attr of the default shared-lib set with # the matrix-selected nixpkgs attribute (e.g. `openssl_3_6`). All # other shared libs (brotli, cares, libuv, …) keep their defaults. diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 0008b0478f55..e80b65315a4a 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -35,6 +35,7 @@ on: - nghttp2 - nghttp3 - ngtcp2 + - perfetto - postject - root-certificates - simdjson @@ -237,6 +238,14 @@ jobs: cat temp-output tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true rm temp-output + - id: perfetto + subsystem: deps + label: dependencies + run: | + ./tools/dep_updaters/update-perfetto.sh > temp-output + cat temp-output + tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true + rm temp-output - id: postject subsystem: deps,test label: test @@ -336,7 +345,7 @@ jobs: # no-op if the base branch is already up-to-date. with: token: ${{ secrets.GH_USER_TOKEN }} - branch: actions/${{ github.ref_name == 'main' || format('{0}/', github.ref_name) }}tools-update-${{ matrix.id }} # Custom branch *just* for this Action. + branch: actions/${{ github.ref_name != 'main' && format('{0}/', github.ref_name) || '' }}tools-update-${{ matrix.id }} # Custom branch *just* for this Action. delete-branch: true commit-message: ${{ env.COMMIT_MSG }} labels: ${{ matrix.label }} diff --git a/.gitignore b/.gitignore index 69c1dd205316..2a7ce3337021 100644 --- a/.gitignore +++ b/.gitignore @@ -163,7 +163,6 @@ install_manifest.txt # === Rules for AI assistants === CLAUDE.md -AGENTS.md # === Global Rules === # Keep last to avoid being excluded diff --git a/.mailmap b/.mailmap index 0860e8e01478..6cdb3bc4f739 100644 --- a/.mailmap +++ b/.mailmap @@ -55,6 +55,7 @@ Ashok Suthar Ashutosh Kumar Singh Atsuo Fukaya Austin Kelleher +Aviv Keller Azard <330815461@qq.com> Ben Lugavere Ben Noordhuis diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..79e1d1b688b8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,64 @@ +# Agents and Automated Tools + +This document outlines rules and requirements for AI automation agents and automated +tooling that interact with the Node.js project. + +Existing Node.js collaborators (as listed in the README.md) may use AI agents to +contribute but must do so responsibly. + +New Node.js contributors should avoid using AI agents to interact with the project. + +## Code Contributions + +* **No unreviewed automation**: Automated pull requests must not be created without + ongoing human oversight. The pull request must be actively maintained by a human + contributor who responds to feedback. The AI agent is not permitted to create pull + requests, open issues, post comments, respond to reviews, or push commits without + ongoing human oversight. + +* **Commit responsibility**: Commits created with agent assistance must follow all + Node.js [commit message guidelines](./doc/contributing/pull-requests.md#commit-message-guidelines). + The human contributor opening the PR takes full responsibility for the changes. + +* **Testing and verification**: All changes must pass the Node.js continuous integration. + Human judgment must verify that existing tests are not removed or modified inappropriately, + and that new tests correctly validate the intended behavior. + +### Requirements + +* All commits must be signed off by the human user using `Signed-off-by: ()` + as an attestation to the [Developer Certificate of Origin](https://developercertificate.org/). +* AI-assistance must be acknowledged using the `Assisted-by: ` annotation. +* AI-authored code contributions must be compatible with the project's licensing and + contribution guidelines. + +## Prohibited Activities + +AI agents **must not**: + +* Push to any branch or tag in nodejs/node. +* Create unsupervised pull requests or issues without active human engagement. +* Make claims about code without human verification against actual source code. +* Remove or modify existing tests without human judgment. +* Interact with the repository through means other than those explicitly authorized. +* Use commit messages to promote for-profit AI tools or commercial brands. A single + `Assisted-by: ` annotation is required disclosure, not promotion, and is + permitted. +* Post AI-generated messages directly into pull requests, issues, or project communication + channels without direct human review and editing to ensure clarity, accuracy, and respect + for collaborator time. +* Sign off commits using `Signed-off-by: ` or `Co-authored-by: `. + +## Violations + +Automated interactions that violate these rules may result in: + +* Immediate closure of pull requests without review. +* Blocking of the automation tool from further interaction with the project. +* Blocking of the tool's account or its owner from contributing. +* Reports to relevant platforms or organizations operating the automation. + +*** + +For more information on AI use in general contributions (not specific to agents), +see [AI use policy and guidelines](./doc/contributing/ai-guidelines.md). diff --git a/BUILDING.md b/BUILDING.md index 02ea54c2f8ac..e477d46863f4 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1032,11 +1032,11 @@ as `deps/icu` (You'll have: `deps/icu/source/...`) ### Configure OpenSSL appname Node.js can use an OpenSSL configuration file by specifying the environment -variable `OPENSSL_CONF`, or using the command line option `--openssl-conf`, and -if none of those are specified will default to reading the default OpenSSL -configuration file `openssl.cnf`. Node.js will only read a section that is by -default named `nodejs_conf`, but this name can be overridden using the following -configure option: +variable `OPENSSL_CONF`, or using the command line option `--openssl-config`, +which takes precedence. If neither is specified, Node.js defaults to reading the +default OpenSSL configuration file `openssl.cnf`. Node.js will only read a +section that is by default named `nodejs_conf`, but this name can be overridden +using the following configure option: ```bash ./configure --openssl-conf-name= @@ -1048,6 +1048,8 @@ Node.js supports FIPS when statically or dynamically linked with OpenSSL 3 via [OpenSSL's provider model](https://docs.openssl.org/3.0/man7/crypto/#OPENSSL-PROVIDERS). It is not necessary to rebuild Node.js to enable support for FIPS. +When using OpenSSL 1.1.1, Node.js must be built against a FIPS-capable OpenSSL. + See [FIPS mode](doc/api/crypto.md#fips-mode) for more information on how to enable FIPS support in Node.js. diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f8ca5bcc02..3574f2231b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,8 @@ release. -26.7.0
+26.8.0
+26.7.0
26.6.0
26.5.1
26.5.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b47b9868461b..cfc1bf72b47f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ works. * [Issues](#issues) * [Pull Requests](#pull-requests) * [Automation and bots](#automation-and-bots) +* [AI Use Policy and Guidelines](#ai-use-policy-and-guidelines) * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11) ## [Code of Conduct](./doc/contributing/code-of-conduct.md) @@ -46,6 +47,8 @@ See [details on our policy on Code of Conduct](./doc/contributing/code-of-conduc Pull Requests are the way concrete changes are made to the code, documentation, dependencies, and tools contained in the `nodejs/node` repository. +Contributors who are not Collaborators may have no more than 10 pull requests +open at a time. * [Dependencies](./doc/contributing/pull-requests.md#dependencies) * [Setting up your local environment](./doc/contributing/pull-requests.md#setting-up-your-local-environment) @@ -66,6 +69,15 @@ by an automation that was not authorized by Node.js collaborators are subject to immediate moderation enforcement on the automation and owner without notice. +## [AI Use Policy and Guidelines](./doc/contributing/ai-guidelines.md) + +Node.js requires contributors to understand and take full responsibility for +every change they propose. Pull requests containing AI-generated code the +contributor has not personally understood, tested, and verified will likely be closed +without review. + +See [details on our AI use policy and guidelines](./doc/contributing/ai-guidelines.md). + ## Developer's Certificate of Origin 1.1 ```text diff --git a/Makefile b/Makefile index bc9dd7a144db..4da0900c6fca 100644 --- a/Makefile +++ b/Makefile @@ -1288,7 +1288,7 @@ ifneq ($(SKIP_SHARED_DEPS), 1) cp doc/node.1 $(TARNAME)/doc/node.1 cp -r out/doc/api/* $(TARNAME)/doc/api/ endif - sed 's/fileset = fileset.intersection (fileset.gitTracked root)/fileset =/' tools/nix/v8.nix > $(TARNAME)/tools/nix/v8.nix + sed 's/fileset = fileset.intersection (fileset.gitTracked root)/fileset =/' tools/nix/v8.nix > $(TARNAME)/tools/nix/v8.nix $(RM) -r $(TARNAME)/.editorconfig $(RM) -r $(TARNAME)/.git* $(RM) -r $(TARNAME)/.mailmap @@ -1449,15 +1449,15 @@ binary: $(BINARYTAR) ## Build release binary tarballs. # Note: this is strictly for release builds on release machines only. binary-upload: binary ssh $(STAGINGSERVER) "mkdir -p nodejs/$(DISTTYPEDIR)/$(FULLVERSION)" - chmod 664 $(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz - scp -p $(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz $(STAGINGSERVER):nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz - ssh $(STAGINGSERVER) "rclone copyto nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz $(CLOUDFLARE_BUCKET)/nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz" - ssh $(STAGINGSERVER) "touch nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.gz.done" + chmod 664 $(BINARYNAME).tar.gz + scp -p $(BINARYNAME).tar.gz $(STAGINGSERVER):nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.gz + ssh $(STAGINGSERVER) "rclone copyto nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.gz $(CLOUDFLARE_BUCKET)/nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.gz" + ssh $(STAGINGSERVER) "touch nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.gz.done" ifeq ($(XZ), 1) - chmod 664 $(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz - scp -p $(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz $(STAGINGSERVER):nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz - ssh $(STAGINGSERVER) "rclone copyto nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz $(CLOUDFLARE_BUCKET)/nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz" - ssh $(STAGINGSERVER) "touch nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(TARNAME)-$(OSTYPE)-$(ARCH).tar.xz.done" + chmod 664 $(BINARYNAME).tar.xz + scp -p $(BINARYNAME).tar.xz $(STAGINGSERVER):nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.xz + ssh $(STAGINGSERVER) "rclone copyto nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.xz $(CLOUDFLARE_BUCKET)/nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.xz" + ssh $(STAGINGSERVER) "touch nodejs/$(DISTTYPEDIR)/$(FULLVERSION)/$(BINARYNAME).tar.xz.done" endif .PHONY: bench-all @@ -1480,7 +1480,7 @@ else LINT_MD_NEWER = -newer tools/.mdlintstamp endif -LINT_MD_TARGETS = doc src lib benchmark test tools/doc tools/icu $(filter-out CLAUDE.md AGENTS.md,$(wildcard *.md)) +LINT_MD_TARGETS = doc src lib benchmark test tools/doc tools/icu $(filter-out CLAUDE.md,$(wildcard *.md)) LINT_MD_FILES = $(shell $(FIND) $(LINT_MD_TARGETS) -type f \ ! -path '*node_modules*' ! -path 'test/fixtures/*' -name '*.md' \ $(LINT_MD_NEWER)) diff --git a/README.md b/README.md index a6cf7d6683fa..3827556a44fb 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,6 @@ For information about the governance of the Node.js project, see **Ruy Adorno** <> (he/him) * [ShogunPanda](https://github.com/ShogunPanda) - **Paolo Insogna** <> (he/him) -* [targos](https://github.com/targos) - - **Michaël Zasso** <> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <> (he/him) @@ -260,6 +258,8 @@ For information about the governance of the Node.js project, see **Sam Roberts** <> * [shigeki](https://github.com/shigeki) - **Shigeki Ohtsu** <> (he/him) +* [targos](https://github.com/targos) - + **Michaël Zasso** <> (he/him) * [thefourtheye](https://github.com/thefourtheye) - **Sakthipriyan Vairamani** <> (he/him) * [TimothyGu](https://github.com/TimothyGu) - @@ -323,8 +323,6 @@ For information about the governance of the Node.js project, see **Erick Wendel** <> (he/him) * [Ethan-Arrowood](https://github.com/Ethan-Arrowood) - **Ethan Arrowood** <> (he/him) -* [fhinkel](https://github.com/fhinkel) - - **Franziska Hinkelmann** <> (she/her) * [Flarna](https://github.com/Flarna) - **Gerhard Stöbich** <> (he/they) * [gabrielschulhof](https://github.com/gabrielschulhof) - @@ -431,8 +429,6 @@ For information about the governance of the Node.js project, see **Stefan Stojanovic** <> (he/him) * [sxa](https://github.com/sxa) - **Stewart X Addison** <> (he/him) -* [targos](https://github.com/targos) - - **Michaël Zasso** <> (he/him) * [theanarkh](https://github.com/theanarkh) - **theanarkh** <> (he/him) * [tniessen](https://github.com/tniessen) - @@ -525,6 +521,8 @@ For information about the governance of the Node.js project, see **Evan Lucas** <> (he/him) * [F3n67u](https://github.com/F3n67u) - **Feng Yu** <> (he/him) +* [fhinkel](https://github.com/fhinkel) - + **Franziska Hinkelmann** <> (she/her) * [firedfox](https://github.com/firedfox) - **Daniel Wang** <> * [Fishrock123](https://github.com/Fishrock123) - @@ -699,6 +697,8 @@ For information about the governance of the Node.js project, see **Weijia Wang** <> * [stefanmb](https://github.com/stefanmb) - **Stefan Budeanu** <> +* [targos](https://github.com/targos) - + **Michaël Zasso** <> (he/him) * [tellnes](https://github.com/tellnes) - **Christian Tellnes** <> * [thefourtheye](https://github.com/thefourtheye) - @@ -779,8 +779,6 @@ Primary GPG keys for Node.js Releasers (some Releasers sign with subkeys): `DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7` * **Marco Ippolito** <> `CC68F5A3106FF448322E48ED27F5E38D5B0A215F` -* **Michaël Zasso** <> - `8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600` * **Rafael Gonzaga** <> `890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4` * **Richard Lau** <> @@ -846,6 +844,8 @@ verify a downloaded file. `61FC681DFB92A079F1685E77973F295594EC4689` * **Julien Gilli** <> `114F43EE0176B71C7BC219DD50A3051F888C628D` +* **Michaël Zasso** <> + `8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600` * **Myles Borins** <> `C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8` * **Rod Vagg** <> diff --git a/benchmark/_benchmark_progress.js b/benchmark/_benchmark_progress.js index 6c925f34e682..117e86609028 100644 --- a/benchmark/_benchmark_progress.js +++ b/benchmark/_benchmark_progress.js @@ -25,9 +25,10 @@ function getTime(diff) { // A run is an item in the job queue: { binary, filename, iter } // A config is an item in the subqueue: { binary, filename, iter, configs } class BenchmarkProgress { - constructor(queue, benchmarks) { + constructor(queue, benchmarks, options = {}) { this.queue = queue; // Scheduled runs. this.benchmarks = benchmarks; // Filenames of scheduled benchmarks. + this.analyze = !!options.analyze; // stdout is not piped, but unused. this.completedRuns = 0; // Number of completed runs. this.scheduledRuns = queue.length; // Number of scheduled runs. // Time when starting to run benchmarks. @@ -107,7 +108,10 @@ class BenchmarkProgress { } updateProgress() { - if (!process.stderr.isTTY || process.stdout.isTTY) { + // Progress renders on stderr when stdout is piped (not a TTY). + // In --analyze mode, stdout is the terminal but is unused during + // the run, so treat it the same as piped. + if (!process.stderr.isTTY || (process.stdout.isTTY && !this.analyze)) { return; } readline.clearLine(process.stderr); diff --git a/benchmark/buffers/buffer-write-string-utf8.js b/benchmark/buffers/buffer-write-string-utf8.js new file mode 100644 index 000000000000..930b83dcd55b --- /dev/null +++ b/benchmark/buffers/buffer-write-string-utf8.js @@ -0,0 +1,36 @@ +'use strict'; + +// buf.write(string, 'utf8') for strings whose in-memory representation is +// one-byte (Latin-1) or two-byte (UTF-16), which take different encoder paths. +const common = require('../common.js'); +const bench = common.createBenchmark(main, { + chars: ['one-byte', 'two-byte', 'two-byte-astral', 'two-byte-lone-surrogate'], + len: [16, 256, 2048, 65536], + n: [5e5], +}); + +function makeString(chars, len) { + switch (chars) { + case 'one-byte': + return 'aé'.repeat(len / 2); + case 'two-byte': + return 'aé€日'.repeat(len / 4); + case 'two-byte-astral': + return 'aé€日\u{1F600}'.repeat(len / 6).padEnd(len, 'a'); + case 'two-byte-lone-surrogate': + return 'aé€日'.repeat(len / 4 - 1) + 'ab\ud800c'; + default: + throw new Error(chars); + } +} + +function main({ chars, len, n }) { + const string = makeString(chars, len); + const buf = Buffer.allocUnsafe(Buffer.byteLength(string)); + if (len >= 65536) n = Math.floor(n / 32); + bench.start(); + for (let i = 0; i < n; ++i) { + buf.write(string, 0, 'utf8'); + } + bench.end(n); +} diff --git a/benchmark/compare.js b/benchmark/compare.js index ad3084db3904..6aaaee7a9190 100644 --- a/benchmark/compare.js +++ b/benchmark/compare.js @@ -13,7 +13,8 @@ const cli = new CLI(`usage: ./node compare.js [options] [--] ... Run each benchmark in the directory many times using two different node versions. More than one directory can be specified. The output is formatted as csv, which can be processed using for - example 'compare.R'. + example 'compare.R'. Use --analyze to perform statistical analysis + directly without R. --new ./new-node-binary new node binary (required) --old ./old-node-binary old node binary (required) @@ -24,13 +25,21 @@ const cli = new CLI(`usage: ./node compare.js [options] [--] ... repeated) --set variable=value set benchmark variable (can be repeated) --no-progress don't show benchmark progress indicator + --analyze perform statistical analysis after benchmarks + complete (Welch's t-test, effect size) instead + of printing csv output + --scale 1000 rate-to-integer multiplier for histogram + precision when using --analyze (default: 1000) + --max-regression N exit with code 1 if any statistically + significant regression exceeds N% (implies + --analyze) Examples: --set CPUSET=0 Runs benchmarks on CPU core 0. --set CPUSET=0-2 Specifies that benchmarks should run on CPU cores 0 to 2. Note: The CPUSET format should match the specifications of the 'taskset' command -`, { arrayArgs: ['set', 'filter', 'exclude'], boolArgs: ['no-progress'] }); +`, { arrayArgs: ['set', 'filter', 'exclude'], boolArgs: ['no-progress', 'analyze'] }); if (!cli.optional.new || !cli.optional.old) { cli.abort(cli.usage); @@ -38,6 +47,11 @@ if (!cli.optional.new || !cli.optional.old) { const binaries = ['old', 'new']; const runs = cli.optional.runs ? parseInt(cli.optional.runs, 10) : 30; +const maxRegression = cli.optional['max-regression'] ? + parseFloat(cli.optional['max-regression']) : + 0; +const analyze = !!cli.optional.analyze || maxRegression > 0; +const scale = cli.optional.scale ? parseInt(cli.optional.scale, 10) : 1000; const benchmarks = cli.benchmarks(); if (benchmarks.length === 0) { @@ -46,6 +60,9 @@ if (benchmarks.length === 0) { return; } +// When --analyze is set, collect results for statistical analysis. +const results = analyze ? new Map() : null; + // Create queue from the benchmarks list such both node versions are tested // `runs` amount of times each. // Note: BenchmarkProgress relies on this order to estimate @@ -61,15 +78,17 @@ for (const filename of benchmarks) { } // queue.length = binary.length * runs * benchmarks.length -// Print csv header -console.log('"binary","filename","configuration","rate","time"'); +// Print csv header (unless analyzing inline). +if (!analyze) { + console.log('"binary","filename","configuration","rate","time"'); +} const kStartOfQueue = 0; const showProgress = !cli.optional['no-progress']; let progress; if (showProgress) { - progress = new BenchmarkProgress(queue, benchmarks); + progress = new BenchmarkProgress(queue, benchmarks, { analyze }); progress.startQueue(kStartOfQueue); } @@ -99,11 +118,20 @@ if (showProgress) { conf += ` ${key}=${inspect(data.conf[key])}`; } conf = conf.slice(1); - // Escape quotes (") for correct csv formatting - conf = conf.replace(/"/g, '""'); - console.log(`"${job.binary}","${job.filename}","${conf}",` + - `${data.rate},${data.time}`); + if (analyze) { + // Collect results for post-run analysis. + const name = `${job.filename} ${conf}`; + if (!results.has(name)) { + results.set(name, { old: [], new: [] }); + } + results.get(name)[job.binary].push(data.rate); + } else { + // Escape quotes (") for correct csv formatting + conf = conf.replace(/"/g, '""'); + console.log(`"${job.binary}","${job.filename}","${conf}",` + + `${data.rate},${data.time}`); + } if (showProgress) { // One item in the subqueue has been completed. progress.completeConfig(data); @@ -125,6 +153,199 @@ if (showProgress) { // If there are more benchmarks execute the next if (i + 1 < queue.length) { recursive(i + 1); + } else if (analyze) { + printAnalysis(results, scale, maxRegression); } }); })(kStartOfQueue); + +function printAnalysis(results, scale, maxRegression) { + const { createHistogram } = require('node:perf_hooks'); + + // Build per-benchmark histograms and run statistical tests. + const rows = []; + let maxNameLen = 0; + + let skipped = 0; + + for (const [name, { old: oldRates, new: newRates }] of results) { + if (oldRates.length < 2 || newRates.length < 2) { + skipped++; + continue; + } + + const hOld = createHistogram({ figures: 3 }); + const hNew = createHistogram({ figures: 3 }); + + for (const r of oldRates) hOld.record(Math.max(1, Math.round(r * scale))); + for (const r of newRates) hNew.record(Math.max(1, Math.round(r * scale))); + + const oldMean = oldRates.reduce((a, b) => a + b, 0) / oldRates.length; + const newMean = newRates.reduce((a, b) => a + b, 0) / newRates.length; + const improvement = ((newMean - oldMean) / oldMean) * 100; + + // Query the three confidence levels. The p-value and t-statistic + // are the same regardless of the confidence level, so we extract + // them from the first result. + const w95 = hOld.welchTest(hNew, { confidence: 0.95 }); + const w99 = hOld.welchTest(hNew, { confidence: 0.99 }); + const w999 = hOld.welchTest(hNew, { confidence: 0.999 }); + + // Significance stars matching compare.R convention. + let stars = ''; + if (w95.pValue < 0.001) stars = '***'; + else if (w95.pValue < 0.01) stars = ' **'; + else if (w95.pValue < 0.05) stars = ' *'; + + // Confidence intervals expressed as percentage of the old mean. + const ciPct = (w) => { + const half = + (w.confidenceInterval.upper - w.confidenceInterval.lower) / 2; + return (half / (oldMean * scale)) * 100; + }; + + rows.push({ + name, + stars, + improvement, + ci95: ciPct(w95), + ci99: ciPct(w99), + ci999: ciPct(w999), + pValue: w95.pValue, + }); + + if (name.length > maxNameLen) maxNameLen = name.length; + } + + // Print header. + const pad = (s, n) => s + ' '.repeat(Math.max(0, n - s.length)); + const rpad = (s, n) => ' '.repeat(Math.max(0, n - s.length)) + s; + + console.log(`${pad('', maxNameLen)} confidence` + + ` improvement accuracy (*) (**) (***)`); + + for (const row of rows) { + const imp = `${row.improvement >= 0 ? '+' : ''}${row.improvement.toFixed(2)} %`; + console.log( + `${pad(row.name, maxNameLen)} ${pad(row.stars, 10)}` + + ` ${rpad(imp, 11)}` + + ` ±${row.ci95.toFixed(2)}%` + + ` ±${row.ci99.toFixed(2)}%` + + ` ±${row.ci999.toFixed(2)}%`, + ); + } + + if (skipped > 0) { + console.log(''); + console.log( + `Note: ${skipped} configuration${skipped === 1 ? ' was' : 's were'}` + + ` skipped because Welch's t-test requires at least 2 samples per` + + ` binary. Use --runs 2 or higher.`, + ); + } + + // --- Bar chart visualization --- + printChart(rows, maxNameLen); + + console.log(''); + console.log( + `Rates were scaled by ${scale}x into HdrHistogram (3 significant figures).\n` + + `Use --scale to adjust precision if needed.\n`, + ); + console.log( + `Be aware that when doing many comparisons the risk of a false-positive\n` + + `result increases. In this case, there are ${rows.length} comparisons, ` + + `you can thus\nexpect the following amount of false-positive results:\n` + + ` ${(rows.length * 0.05).toFixed(2)} false positives, when considering ` + + `a 5% risk acceptance (*, **, ***),\n` + + ` ${(rows.length * 0.01).toFixed(2)} false positives, when considering ` + + `a 1% risk acceptance (**, ***),\n` + + ` ${(rows.length * 0.001).toFixed(2)} false positives, when considering ` + + `a 0.1% risk acceptance (***)`, + ); + + // Gate: exit with error if any significant regression exceeds the limit. + if (maxRegression > 0) { + const failures = rows.filter( + (r) => r.stars.trim() !== '' && r.improvement < -maxRegression, + ); + if (failures.length > 0) { + console.log(''); + console.log( + `FAIL: ${failures.length} benchmark${failures.length === 1 ? '' : 's'}` + + ` showed a statistically significant regression exceeding` + + ` ${maxRegression}%:`, + ); + for (const f of failures) { + console.log(` ${f.name} ${f.improvement.toFixed(2)}%`); + } + process.exitCode = 1; + } + } +} + +function printChart(rows, maxNameLen) { + if (rows.length === 0) return; + + // Determine the chart scale from the data. The bar region covers + // the range [-maxAbs, +maxAbs] so the zero line sits in the center. + const barWidth = 40; + const halfWidth = barWidth / 2; + let maxAbs = 0; + for (const row of rows) { + const extent = Math.abs(row.improvement) + row.ci95; + if (extent > maxAbs) maxAbs = extent; + } + if (maxAbs === 0) maxAbs = 1; + + const pad = (s, n) => s + ' '.repeat(Math.max(0, n - s.length)); + + // Scale axis labels. + const axisLeft = `-${maxAbs.toFixed(1)}%`; + const axisRight = `+${maxAbs.toFixed(1)}%`; + const axisCenter = '0%'; + + // Print axis header. + const labelPad = maxNameLen + 5; + const leftLabel = ' '.repeat(labelPad) + + axisLeft + + ' '.repeat(Math.max(0, halfWidth - axisLeft.length - Math.floor(axisCenter.length / 2))) + + axisCenter + + ' '.repeat(Math.max(0, halfWidth - Math.ceil(axisCenter.length / 2) - axisRight.length)) + + axisRight; + console.log(''); + console.log(leftLabel); + + for (const row of rows) { + const imp = row.improvement; + const ci = row.ci95; + + // Position of the improvement value in the bar region [0, barWidth]. + const center = halfWidth; + const impPos = center + (imp / maxAbs) * halfWidth; + + // CI extent in bar positions. + const ciLeft = center + ((imp - ci) / maxAbs) * halfWidth; + const ciRight = center + ((imp + ci) / maxAbs) * halfWidth; + + // Build the bar character by character. + const chars = []; + for (let x = 0; x < barWidth; x++) { + const pos = x + 0.5; // Center of this character cell. + if (x === Math.floor(center)) { + chars.push('|'); + } else if ((imp >= 0 && pos > center && pos <= impPos) || + (imp < 0 && pos < center && pos >= impPos)) { + chars.push(row.stars ? '\u2588' : '\u2593'); // solid or dark shade + } else if (pos >= ciLeft && pos <= ciRight) { + chars.push('\u2591'); // Light shade for CI region + } else { + chars.push(' '); + } + } + + const label = `${row.improvement >= 0 ? '+' : ''}${row.improvement.toFixed(2)}%`; + const sig = row.stars.trim(); + console.log(`${pad(row.name, maxNameLen)} ${chars.join('')} ${label} ${sig}`); + } +} diff --git a/benchmark/esm/get-data-protocol-format.js b/benchmark/esm/get-data-protocol-format.js new file mode 100644 index 000000000000..35e54a770035 --- /dev/null +++ b/benchmark/esm/get-data-protocol-format.js @@ -0,0 +1,30 @@ +// Benchmarks defaultGetFormat() on `data:` URLs. The MIME-matching regex used +// to be susceptible to catastrophic backtracking on malformed input lacking a +// `,` separator (https://github.com/nodejs/node/issues/61904); `pathLength` +// scales the malformed path so a regression shows up as a sharp drop in ops/sec +// rather than a hang. +'use strict'; + +const common = require('../common.js'); + +const configs = { + n: [1e4], + pathLength: [1e2, 1e3, 1e4], +}; + +const options = { + flags: ['--expose-internals'], +}; + +const bench = common.createBenchmark(main, configs, options); + +function main({ n, pathLength }) { + const { defaultGetFormat } = require('internal/modules/esm/get_format'); + const url = new URL(`data:a/${'a'.repeat(pathLength)}B`); + + bench.start(); + for (let i = 0; i < n; i++) { + defaultGetFormat(url, { parentURL: undefined }); + } + bench.end(n); +} diff --git a/benchmark/fetch/headers.js b/benchmark/fetch/headers.js new file mode 100644 index 000000000000..4ff5091ebfbf --- /dev/null +++ b/benchmark/fetch/headers.js @@ -0,0 +1,94 @@ +'use strict'; +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [1e5], + method: [ + 'construct-empty', + 'construct-object', + 'construct-headers', + 'get', + 'get-common', + 'set', + 'append', + 'has', + 'delete', + 'iterate', + ], +}); + +const objectInit = { + 'Accept': 'application/json', + 'Content-Type': 'text/plain', + 'User-Agent': 'benchmark', + 'Authorization': 'Bearer token', + 'Cookie': 'a=1', + 'X-Request-Id': 'abc', + 'Cache-Control': 'no-cache', + 'Host': 'example.com', +}; + +function main({ n, method }) { + const headers = new Headers(objectInit); + const copySource = new Headers(objectInit); + let result; + + bench.start(); + switch (method) { + case 'construct-empty': + for (let i = 0; i < n; i++) + new Headers(); + break; + case 'construct-object': + for (let i = 0; i < n; i++) + new Headers(objectInit); + break; + case 'construct-headers': + for (let i = 0; i < n; i++) + new Headers(copySource); + break; + case 'get': + for (let i = 0; i < n; i++) + result = headers.get('x-request-id'); + break; + case 'get-common': + for (let i = 0; i < n; i++) + result = headers.get('content-type'); + break; + case 'set': + for (let i = 0; i < n; i++) + headers.set('x-count', i); + break; + case 'append': + for (let i = 0; i < n; i++) { + const current = new Headers(); + current.append('Accept', 'text/html'); + current.append('X-Custom', i); + } + break; + case 'has': + for (let i = 0; i < n; i++) + result = headers.has('authorization'); + break; + case 'delete': { + for (let i = 0; i < n; i++) { + const current = new Headers(objectInit); + current.delete('content-type'); + } + break; + } + case 'iterate': + for (let i = 0; i < n; i++) { + for (const entry of headers) + result = entry; + } + break; + default: + throw new Error(`Unexpected method "${method}"`); + } + bench.end(n); + + // Keep a live use so V8 cannot DCE the loop. + if (result === Symbol.for('benchmark-never')) + throw new Error('unreachable'); +} diff --git a/benchmark/ffi/get-function.js b/benchmark/ffi/get-function.js new file mode 100644 index 000000000000..3c1e2e974ce6 --- /dev/null +++ b/benchmark/ffi/get-function.js @@ -0,0 +1,48 @@ +'use strict'; + +// Measures symbol resolution rather than call throughput. Creating a callable +// for a fast-eligible signature emits a native trampoline, so this benchmark +// covers the trampoline allocation path that the call benchmarks never reach. +// +// The `fast` variant is eligible for a generated trampoline; `slow` exceeds the +// x86_64 register budget and falls back, so it resolves without allocating one. +// Comparing the two isolates trampoline creation cost from the rest of symbol +// resolution. + +const common = require('../common.js'); +const { DynamicLibrary } = require('node:ffi'); +const { libraryPath, ensureFixtureLibrary } = require('./common.js'); + +const bench = common.createBenchmark(main, { + signature: ['fast', 'slow'], + n: [1e3], +}, { + flags: ['--experimental-ffi'], +}); + +ensureFixtureLibrary(); + +const signatures = { + fast: { name: 'add_i32', return: 'i32', arguments: ['i32', 'i32'] }, + slow: { + name: 'sum_8_i32', + return: 'i32', + arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'], + }, +}; + +function main({ n, signature }) { + const { name, ...definition } = signatures[signature]; + const lib = new DynamicLibrary(libraryPath); + + // Warm up one-time initialization (libffi setup, executable memory probe) so + // it is not attributed to the measured resolutions. + lib.getFunction(name, definition); + + bench.start(); + for (let i = 0; i < n; ++i) + lib.getFunction(name, definition); + bench.end(n); + + lib.close(); +} diff --git a/benchmark/ffi/invoke-function.js b/benchmark/ffi/invoke-function.js new file mode 100644 index 000000000000..ae8d5b2ef795 --- /dev/null +++ b/benchmark/ffi/invoke-function.js @@ -0,0 +1,79 @@ +'use strict'; + +const assert = require('node:assert'); +const common = require('../common.js'); +const { libraryPath, ensureFixtureLibrary } = require('./common.js'); + +// Measure the invocation (call) path for signatures that bypass V8 Fast API +// and use libffi through FFIFunction::Invoke(). On x86-64 System V with +// libffi >= 3.7, Invoke() reuses a precomputed call plan that avoids repeating +// argument-placement work on every call. This benchmark quantifies the +// per-call benefit. +// +// Signatures chosen to bypass both V8 Fast API and keep native work minimal: +// - call_int_callback (null): 'function' type forces the generic path; null +// pointer triggers the early return in C so native computation is negligible. +// From libffi's perspective this is a register-only plan (2 pointer-sized +// args both fit in GP registers on x86-64 System V). +// - sum_8_i32: 8 GP args exceed the x86-64 Fast API register cap (6), forcing +// the generic path. From libffi's perspective 6 args go in registers and 2 +// spill to the stack, exercising a stack-spilled plan. + +const bench = common.createBenchmark(main, { + n: [1e7], + symbol: ['call_int_callback', 'sum_8_i32'], +}, { + flags: ['--experimental-ffi', '--no-warnings'], +}); + +ensureFixtureLibrary(); + +function main({ n, symbol }) { + const ffi = require('node:ffi'); + + if (symbol === 'call_int_callback') { + // 'function' type bypasses Fast API (IsFastCallEligible rejects it). + // Pass 0n (null function pointer) so the native function returns -1 + // immediately without invoking any callback, keeping per-call overhead + // dominated by the FFI call machinery itself. + const { lib, functions } = ffi.dlopen(libraryPath, { + call_int_callback: { return: 'i32', arguments: ['function', 'i32'] }, + }); + + try { + // Verify the null-pointer early return. + assert.strictEqual(functions.call_int_callback(0n, 7), -1); + + bench.start(); + for (let i = 0; i < n; ++i) + functions.call_int_callback(0n, 21); + bench.end(n); + } finally { + lib.close(); + } + } else { + // 8 integer args exceed the x86-64 SysV GP register cap (6), which makes + // CreateFastFFIMetadata reject the signature. Calls go through the + // SharedBuffer or generic invoker into FFIFunction::Invoke(). + const { lib, functions } = ffi.dlopen(libraryPath, { + sum_8_i32: { + return: 'i32', + arguments: [ + 'i32', 'i32', 'i32', 'i32', + 'i32', 'i32', 'i32', 'i32', + ], + }, + }); + + const fn = functions.sum_8_i32; + + assert.strictEqual(fn(1, 2, 3, 4, 5, 6, 7, 8), 36); + + bench.start(); + for (let i = 0; i < n; ++i) + fn(1, 2, 3, 4, 5, 6, 7, 14); + bench.end(n); + + lib.close(); + } +} diff --git a/benchmark/fs/bench-readdir.js b/benchmark/fs/bench-readdir.js index 8fa0e7a3cbdc..e276653f4584 100644 --- a/benchmark/fs/bench-readdir.js +++ b/benchmark/fs/bench-readdir.js @@ -8,16 +8,18 @@ const bench = common.createBenchmark(main, { n: [10], dir: [ 'lib', 'test/parallel'], withFileTypes: ['true', 'false'], + recursive: ['true', 'false'], }); -function main({ n, dir, withFileTypes }) { +function main({ n, dir, withFileTypes, recursive }) { withFileTypes = withFileTypes === 'true'; + recursive = recursive === 'true'; const fullPath = path.resolve(__dirname, '../../', dir); bench.start(); (function r(cntr) { if (cntr-- <= 0) return bench.end(n); - fs.readdir(fullPath, { withFileTypes }, () => { + fs.readdir(fullPath, { withFileTypes, recursive }, () => { r(cntr); }); }(n)); diff --git a/benchmark/fs/bench-readdirSync.js b/benchmark/fs/bench-readdirSync.js index 8ae1d061d1f1..ce34e083cb53 100644 --- a/benchmark/fs/bench-readdirSync.js +++ b/benchmark/fs/bench-readdirSync.js @@ -8,15 +8,17 @@ const bench = common.createBenchmark(main, { n: [10], dir: [ 'lib', 'test/parallel'], withFileTypes: ['true', 'false'], + recursive: ['true', 'false'], }); -function main({ n, dir, withFileTypes }) { +function main({ n, dir, withFileTypes, recursive }) { withFileTypes = withFileTypes === 'true'; + recursive = recursive === 'true'; const fullPath = path.resolve(__dirname, '../../', dir); bench.start(); for (let i = 0; i < n; i++) { - fs.readdirSync(fullPath, { withFileTypes }); + fs.readdirSync(fullPath, { withFileTypes, recursive }); } bench.end(n); } diff --git a/benchmark/http/bench-parser.js b/benchmark/http/bench-parser.js index 0a1e8f7b5e8a..72cb2b6feb18 100644 --- a/benchmark/http/bench-parser.js +++ b/benchmark/http/bench-parser.js @@ -31,6 +31,8 @@ function main({ len, n }) { function newParser(type) { const parser = new HTTPParser(); parser.initialize(type, {}); + // Direct parsers bypass cleanParser(); use its production default. + parser.maxHeaderPairs = 2000; parser.headers = []; diff --git a/benchmark/http/end-string.js b/benchmark/http/end-string.js new file mode 100644 index 000000000000..9c5c6afc5869 --- /dev/null +++ b/benchmark/http/end-string.js @@ -0,0 +1,35 @@ +// Responses sent as a single res.end(string) with a known Content-Length - +// the shape a JSON or HTML endpoint produces. +'use strict'; + +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + len: [4, 64, 1024, 16384, 102400], + c: [50], + duration: 5, +}); + +function main({ len, c, duration }) { + const http = require('http'); + const body = 'a'.repeat(len); + const headers = { + 'Content-Type': 'text/plain', + 'Content-Length': `${len}`, + }; + + const server = http.createServer((req, res) => { + res.writeHead(200, headers); + res.end(body); + }); + + server.listen(0, () => { + bench.http({ + connections: c, + duration, + port: server.address().port, + }, () => { + server.close(); + }); + }); +} diff --git a/benchmark/net/net-blocklist.js b/benchmark/net/net-blocklist.js new file mode 100644 index 000000000000..9c293682ff61 --- /dev/null +++ b/benchmark/net/net-blocklist.js @@ -0,0 +1,146 @@ +'use strict'; + +const common = require('../common.js'); +const { BlockList, SocketAddress } = require('net'); + +const hasAddAddresses = typeof BlockList.prototype.addAddresses === 'function'; + +const operations = ['check', 'checkWithSocketAddress', 'addAddress']; +if (hasAddAddresses) { + operations.push('addAddresses'); +} + +const bench = common.createBenchmark(main, { + n: [1e6], + ruleCount: [10, 100, 1000, 10000], + ruleType: ['address', 'subnet', 'mixed'], + checkResult: ['hit', 'miss'], + operation: operations, +}, { + combinationFilter({ operation, ruleCount, ruleType }) { + // addAddress and addAddresses only need address rules, not subnets. + if ((operation === 'addAddress' || operation === 'addAddresses') && + ruleType !== 'address') { + return false; + } + return true; + }, +}); + +function generateIPv4(index) { + return `${(index >>> 24) & 0xff}.${(index >>> 16) & 0xff}.` + + `${(index >>> 8) & 0xff}.${index & 0xff}`; +} + +function buildBlockList(ruleCount, ruleType) { + const blockList = new BlockList(); + + if (ruleType === 'address' || ruleType === 'mixed') { + const addressCount = ruleType === 'mixed' ? + Math.floor(ruleCount / 2) : ruleCount; + const addresses = []; + for (let i = 0; i < addressCount; i++) { + // Start from 10.0.0.1 to avoid 0.0.0.0 + addresses.push(generateIPv4(0x0a000001 + i)); + } + if (hasAddAddresses) { + blockList.addAddresses(addresses); + } else { + for (const addr of addresses) { + blockList.addAddress(addr); + } + } + } + + if (ruleType === 'subnet' || ruleType === 'mixed') { + const subnetCount = ruleType === 'mixed' ? + Math.floor(ruleCount / 2) : ruleCount; + for (let i = 0; i < subnetCount; i++) { + // Use distinct /24 subnets: 172.i.j.0/24 + const second = (i >>> 8) & 0xff; + const third = i & 0xff; + blockList.addSubnet(`172.${second}.${third}.0`, 24); + } + } + + return blockList; +} + +function main({ n, ruleCount, ruleType, checkResult, operation }) { + if (operation === 'check') { + benchCheck(n, ruleCount, ruleType, checkResult); + } else if (operation === 'checkWithSocketAddress') { + benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult); + } else if (operation === 'addAddress') { + benchAddAddress(n, ruleCount); + } else if (operation === 'addAddresses') { + benchAddAddresses(n, ruleCount); + } +} + +// Benchmark check() with string addresses (the common JS API path). +function benchCheck(n, ruleCount, ruleType, checkResult) { + const blockList = buildBlockList(ruleCount, ruleType); + + // For 'hit', use an address that's in the list. + // For 'miss', use an address that's not in the list. + const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255'; + + bench.start(); + for (let i = 0; i < n; i++) { + blockList.check(address); + } + bench.end(n); +} + +// Benchmark check() with pre-created SocketAddress objects +// (avoids measuring SocketAddress construction overhead). +function benchCheckWithSocketAddress(n, ruleCount, ruleType, checkResult) { + const blockList = buildBlockList(ruleCount, ruleType); + + const address = checkResult === 'hit' ? '10.0.0.1' : '192.168.255.255'; + const sa = new SocketAddress({ address }); + + bench.start(); + for (let i = 0; i < n; i++) { + blockList.check(sa); + } + bench.end(n); +} + +// Benchmark single addAddress() calls (one lock acquire per call). +function benchAddAddress(n, ruleCount) { + // Scale n down for large rule counts to keep runtime reasonable. + const iterations = Math.min(n, ruleCount * 100); + + const addresses = []; + for (let i = 0; i < ruleCount; i++) { + addresses.push(generateIPv4(0x0a000001 + i)); + } + + bench.start(); + for (let i = 0; i < iterations; i++) { + const blockList = new BlockList(); + for (let j = 0; j < addresses.length; j++) { + blockList.addAddress(addresses[j]); + } + } + bench.end(iterations); +} + +// Benchmark batch addAddresses() (one lock acquire per batch). +function benchAddAddresses(n, ruleCount) { + const iterations = Math.min(n, ruleCount * 100); + + const addresses = []; + for (let i = 0; i < ruleCount; i++) { + addresses.push(generateIPv4(0x0a000001 + i)); + } + + bench.start(); + for (let i = 0; i < iterations; i++) { + const blockList = new BlockList(); + blockList.addAddresses(addresses); + } + bench.end(iterations); +} diff --git a/benchmark/repl/completion.js b/benchmark/repl/completion.js new file mode 100644 index 000000000000..b9f55d8416b0 --- /dev/null +++ b/benchmark/repl/completion.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common.js'); +const repl = require('node:repl'); +const { PassThrough, Writable } = require('node:stream'); + +const bench = common.createBenchmark(main, { + n: [5e3], + query: [ + 'cons', + 'console.lo', + 'Buffer.prototype.wri', + "require('f", + ], + useGlobal: [0, 1], +}); + +function main({ n, query, useGlobal }) { + const input = new PassThrough(); + const output = new Writable({ write(c, e, cb) { cb(); } }); + const server = new repl.REPLServer({ + input, + output, + terminal: false, + useGlobal: !!useGlobal, + }); + + // Inspector callbacks do not keep the event loop alive on their own. + const keepAlive = setInterval(() => {}, 0x7fffffff); + let remaining = n; + + function complete() { + server.complete(query, onComplete); + } + + function onComplete(err) { + if (err) { + throw err; + } + + if (--remaining === 0) { + bench.end(n); + clearInterval(keepAlive); + server.close(); + return; + } + + setImmediate(complete); + } + + bench.start(); + setImmediate(complete); +} diff --git a/benchmark/repl/creation.js b/benchmark/repl/creation.js new file mode 100644 index 000000000000..60e795063d19 --- /dev/null +++ b/benchmark/repl/creation.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common.js'); +const repl = require('node:repl'); +const { PassThrough, Writable } = require('node:stream'); + +const bench = common.createBenchmark(main, { + n: [500], + preview: [0, 1], + terminal: [0, 1], + useGlobal: [0, 1], +}, { + combinationFilter: ({ preview, terminal }) => !!terminal || !preview, +}); + +function main({ n, preview, terminal, useGlobal }) { + const inputs = Array.from({ length: n }, () => new PassThrough()); + const outputs = Array.from( + { length: n }, + () => new Writable({ write(c, e, cb) { cb(); } }), + ); + const servers = new Array(n); + + bench.start(); + for (let i = 0; i < n; i++) { + servers[i] = new repl.REPLServer({ + input: inputs[i], + output: outputs[i], + preview: !!preview, + terminal: !!terminal, + useGlobal: !!useGlobal, + }); + } + bench.end(n); + + for (const server of servers) { + server.close(); + } +} diff --git a/benchmark/repl/evaluate.js b/benchmark/repl/evaluate.js new file mode 100644 index 000000000000..48376a2b48fe --- /dev/null +++ b/benchmark/repl/evaluate.js @@ -0,0 +1,57 @@ +'use strict'; + +const common = require('../common.js'); +const repl = require('node:repl'); +const { PassThrough, Writable } = require('node:stream'); + +const bench = common.createBenchmark(main, { + n: [2e4], + code: [ + '1 + 1', + '({ answer: 42 })', + 'Promise.resolve(42)', + 'await Promise.resolve(42)', + ], + mode: ['sloppy', 'strict'], + useGlobal: [0, 1], +}); + +function main({ n, code, mode, useGlobal }) { + const input = new PassThrough(); + const output = new Writable({ write(c, e, cb) { cb(); } }); + const server = new repl.REPLServer({ + input, + output, + replMode: mode === 'strict' ? + repl.REPL_MODE_STRICT : + repl.REPL_MODE_SLOPPY, + terminal: false, + useGlobal: !!useGlobal, + }); + + // Inspector callbacks do not keep the event loop alive on their own. + const keepAlive = setInterval(() => {}, 0x7fffffff); + let remaining = n; + + function evaluate() { + server.eval(`${code}\n`, server.context, 'repl', onEvaluate); + } + + function onEvaluate(err) { + if (err) { + throw err; + } + + if (--remaining === 0) { + bench.end(n); + clearInterval(keepAlive); + server.close(); + return; + } + + setImmediate(evaluate); + } + + bench.start(); + setImmediate(evaluate); +} diff --git a/benchmark/repl/process-lines.js b/benchmark/repl/process-lines.js new file mode 100644 index 000000000000..fd019512f9fc --- /dev/null +++ b/benchmark/repl/process-lines.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common.js'); +const repl = require('node:repl'); +const { PassThrough, Writable } = require('node:stream'); + +const bench = common.createBenchmark(main, { + n: [1e4], + code: [ + '1 + 1\n', + 'Promise.resolve(42)\n', + ], + mode: ['sloppy', 'strict'], + terminal: [0, 1], + useGlobal: [0, 1], +}); + +function main({ n, code: inputCode, mode, terminal, useGlobal }) { + const input = new PassThrough(); + const output = new Writable({ write(c, e, cb) { cb(); } }); + const server = new repl.REPLServer({ + input, + output, + replMode: mode === 'strict' ? + repl.REPL_MODE_STRICT : + repl.REPL_MODE_SLOPPY, + terminal: !!terminal, + useGlobal: !!useGlobal, + }); + const originalEval = server.eval; + // TTY input dispatch can briefly have no other active event loop handles. + const keepAlive = setInterval(() => {}, 0x7fffffff); + let remaining = n; + + // eslint-disable-next-line node-core/func-name-matching + server.eval = function REPLEval(code, context, file, callback) { + originalEval(code, context, file, function onEvaluate() { + const result = Reflect.apply(callback, this, arguments); + if (--remaining === 0) { + bench.end(n); + clearInterval(keepAlive); + server.close(); + } else { + setImmediate(() => input.write(inputCode)); + } + return result; + }); + }; + + bench.start(); + input.write(inputCode); +} diff --git a/benchmark/repl/reset-context.js b/benchmark/repl/reset-context.js new file mode 100644 index 000000000000..ab96f92564f2 --- /dev/null +++ b/benchmark/repl/reset-context.js @@ -0,0 +1,26 @@ +'use strict'; + +const common = require('../common.js'); +const repl = require('node:repl'); +const { PassThrough, Writable } = require('node:stream'); + +const bench = common.createBenchmark(main, { + n: [1e3], +}); + +function main({ n }) { + const input = new PassThrough(); + const output = new Writable({ write(c, e, cb) { cb(); } }); + const server = new repl.REPLServer({ + input, + output, + terminal: false, + }); + + bench.start(); + for (let i = 0; i < n; i++) { + server.resetContext(); + } + bench.end(n); + server.close(); +} diff --git a/benchmark/sqlite/sqlite-diagnostic-channel.js b/benchmark/sqlite/sqlite-diagnostic-channel.js new file mode 100644 index 000000000000..0610839653df --- /dev/null +++ b/benchmark/sqlite/sqlite-diagnostic-channel.js @@ -0,0 +1,42 @@ +'use strict'; +const common = require('../common.js'); +const sqlite = require('node:sqlite'); +const dc = require('node:diagnostics_channel'); +const assert = require('node:assert'); + +const bench = common.createBenchmark(main, { + n: [1e5], + mode: ['none', 'subscribed', 'unsubscribed'], +}); + +function main(conf) { + const { n, mode } = conf; + + const db = new sqlite.DatabaseSync(':memory:'); + db.exec('CREATE TABLE t (x INTEGER)'); + const insert = db.prepare('INSERT INTO t VALUES (?)'); + + let subscriber; + if (mode === 'subscribed') { + subscriber = () => {}; + dc.subscribe('sqlite.db.query', subscriber); + } else if (mode === 'unsubscribed') { + subscriber = () => {}; + dc.subscribe('sqlite.db.query', subscriber); + dc.unsubscribe('sqlite.db.query', subscriber); + } + // mode === 'none': no subscription ever made + + let result; + bench.start(); + for (let i = 0; i < n; i++) { + result = insert.run(i); + } + bench.end(n); + + if (mode === 'subscribed') { + dc.unsubscribe('sqlite.db.query', subscriber); + } + + assert.ok(result !== undefined); +} diff --git a/benchmark/sqlite/sqlite-is-transaction.js b/benchmark/sqlite/sqlite-is-transaction.js index 3bfc896cf91c..dca31a18d986 100644 --- a/benchmark/sqlite/sqlite-is-transaction.js +++ b/benchmark/sqlite/sqlite-is-transaction.js @@ -16,14 +16,14 @@ function main(conf) { } let i; - let deadCodeElimination = true; + let deadCodeElimination; bench.start(); for (i = 0; i < conf.n; i += 1) - deadCodeElimination &&= db.isTransaction; + deadCodeElimination = db.isTransaction; bench.end(conf.n); - assert.ok(deadCodeElimination === (conf.transaction === 'true')); + assert.strictEqual(deadCodeElimination, conf.transaction === 'true'); if (conf.transaction === 'true') { db.exec('ROLLBACK'); diff --git a/benchmark/test_runner/hooks.js b/benchmark/test_runner/hooks.js new file mode 100644 index 000000000000..dc73ff4fb1e1 --- /dev/null +++ b/benchmark/test_runner/hooks.js @@ -0,0 +1,51 @@ +'use strict'; + +const common = require('../common'); +const { finished } = require('node:stream/promises'); +const reporter = require('../fixtures/empty-test-reporter'); +const { + after, + afterEach, + before, + beforeEach, + describe, + it, +} = require('node:test'); + +const bench = common.createBenchmark(main, { + n: [1000], + hook: ['before', 'after', 'beforeEach', 'afterEach'], +}, { + // We don't want to test the reporter here. + flags: ['--test-reporter=./benchmark/fixtures/empty-test-reporter.js'], +}); + +const hookList = { + before: before, + after: after, + beforeEach: beforeEach, + afterEach: afterEach, +}; + +const noop = () => {}; + +function run(loopAmount, hookFn) { + for (let i = 0; i < loopAmount; i++) { + describe(`${i}`, () => { + hookFn(noop); + it(`${i}`, noop); + }); + } + + return finished(reporter); +} + +function main(params) { + const hookFn = hookList[params.hook]; + + bench.start(); + + run(params.n, hookFn).then(() => { + bench.end(params.n); + }); +} diff --git a/benchmark/test_runner/mock-timers.js b/benchmark/test_runner/mock-timers.js new file mode 100644 index 000000000000..4815c20ecd73 --- /dev/null +++ b/benchmark/test_runner/mock-timers.js @@ -0,0 +1,262 @@ +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const { test } = require('node:test'); +const nodeTimersPromises = require('node:timers/promises'); + +const bench = common.createBenchmark(main, { + n: [1000], + mode: [ + 'enable-empty-apis', + 'enable-setTimeout', + 'enable-setInterval', + 'enable-setImmediate', + 'enable-Date', + 'enable-scheduler.wait', + 'enable-AbortSignal.timeout', + 'enable-all', + 'enable-default', + 'setTimeout', + 'setInterval', + 'setImmediate', + 'scheduler.wait', + 'AbortSignal.timeout', + 'Date', + 'setTime', + 'runAll', + ], +}, { + // We don't want to test the reporter here. + flags: ['--test-reporter=./benchmark/fixtures/empty-test-reporter.js'], +}); + +function benchmarkEnable(n, mode) { + const enableMode = mode.replace('enable-', ''); + let enableOptions = { apis: [enableMode] }; + + if (enableMode === 'all') { + enableOptions.apis = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait', 'AbortSignal.timeout']; + } + + if (enableMode === 'empty-apis') { + enableOptions.apis = []; + } + + if (enableMode === 'default') { + enableOptions = undefined; + } + + test((t) => { + bench.start(); + + for (let i = 0; i < n; i++) { + t.mock.timers.enable(enableOptions); + t.mock.timers.reset(); + } + + bench.end(n); + }); +} + +function benchmarkSetTimeout(n) { + test((t) => { + let noDead; + + t.mock.timers.enable({ apis: ['setTimeout'] }); + bench.start(); + + for (let i = 0; i < n; i++) { + setTimeout(() => { + noDead = i; + }, i + 1); + } + + t.mock.timers.tick(n + 1); + bench.end(n); + + assert.strictEqual(noDead, n - 1); + }); +} + +function benchmarkSetInterval(n) { + test((t) => { + let noDead = 0; + + t.mock.timers.enable({ apis: ['setInterval'] }); + + setInterval(() => { + noDead++; + }, 1); + + bench.start(); + + t.mock.timers.tick(n); + + bench.end(n); + + assert.strictEqual(noDead, n); + }); +} + +function benchmarkSetImmediate(n) { + test((t) => { + let noDead; + + t.mock.timers.enable({ apis: ['setImmediate'] }); + + bench.start(); + + for (let i = 0; i < n; i++) { + setImmediate(() => { + noDead = i; + }); + } + + t.mock.timers.tick(0); + bench.end(n); + + assert.strictEqual(noDead, n - 1); + }); +} + +function benchmarkSchedulerWait(n) { + test(async (t) => { + const promises = []; + let noDead; + + t.mock.timers.enable({ apis: ['scheduler.wait'] }); + + bench.start(); + + for (let i = 0; i < n; i++) { + promises.push(nodeTimersPromises.scheduler.wait(i + 1).then(() => { + noDead = i; + })); + } + + t.mock.timers.tick(n + 1); + await Promise.all(promises); + bench.end(n); + + assert.strictEqual(noDead, n - 1); + }); +} + +function benchmarkAbortSignalTimeout(n) { + test((t) => { + let noDead; + + t.mock.timers.enable({ apis: ['AbortSignal.timeout'] }); + + bench.start(); + + for (let i = 0; i < n; i++) { + noDead = AbortSignal.timeout(i + 1); + } + + t.mock.timers.tick(n + 1); + bench.end(n); + + assert.strictEqual(noDead.aborted, true); + }); +} + +function benchmarkDate(n) { + test((t) => { + let noDead; + + t.mock.timers.enable({ apis: ['Date'] }); + + bench.start(); + + for (let i = 0; i < n; i++) { + noDead = Date.now(); + } + + bench.end(n); + + assert.strictEqual(noDead, 0); + }); +} + +function benchmarkSetTime(n) { + test((t) => { + let noDead; + + t.mock.timers.enable({ apis: ['Date'] }); + + bench.start(); + + for (let i = 0; i < n; i++) { + t.mock.timers.setTime(i); + noDead = Date.now(); + } + + bench.end(n); + + assert.strictEqual(noDead, n - 1); + }); +} + +function benchmarkRunAll(n) { + test((t) => { + let noDead = 0; + + t.mock.timers.enable({ apis: ['setTimeout'] }); + + for (let i = 0; i < n; i++) { + setTimeout(() => { + noDead++; + }, i + 1); + } + + bench.start(); + + t.mock.timers.runAll(); + + bench.end(n); + + assert.strictEqual(noDead, n); + }); +} + +function main({ n, mode }) { + switch (mode) { + case 'enable-empty-apis': + case 'enable-setTimeout': + case 'enable-setInterval': + case 'enable-setImmediate': + case 'enable-Date': + case 'enable-scheduler.wait': + case 'enable-AbortSignal.timeout': + case 'enable-all': + case 'enable-default': + benchmarkEnable(n, mode); + break; + case 'setTimeout': + benchmarkSetTimeout(n); + break; + case 'setInterval': + benchmarkSetInterval(n); + break; + case 'setImmediate': + benchmarkSetImmediate(n); + break; + case 'scheduler.wait': + benchmarkSchedulerWait(n); + break; + case 'AbortSignal.timeout': + benchmarkAbortSignalTimeout(n); + break; + case 'Date': + benchmarkDate(n); + break; + case 'setTime': + benchmarkSetTime(n); + break; + case 'runAll': + benchmarkRunAll(n); + break; + } +} diff --git a/benchmark/test_runner/test-only.js b/benchmark/test_runner/test-only.js new file mode 100644 index 000000000000..fe79f10dfdd8 --- /dev/null +++ b/benchmark/test_runner/test-only.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +const { finished } = require('node:stream/promises'); +const reporter = require('../fixtures/empty-test-reporter'); +const { test } = require('node:test'); + +const bench = common.createBenchmark(main, { + n: [10000], + selected: [1], +}, { + // We don't want to test the reporter here. + flags: [ + '--test-reporter=./benchmark/fixtures/empty-test-reporter.js', + '--test-only', + ], +}); + +async function run({ n, selected }) { + for (let i = 0; i < selected; i++) { + test(`selected-${i}`, { only: true }, () => {}); + } + + for (let i = 0; i < n; i++) { + test(`not-selected-${i}`, () => { + throw new Error(`This test ${i} should not run.`); + }); + } + + return finished(reporter); +} + +function main(params) { + bench.start(); + + run(params).then(() => { + bench.end(params.n); + }); +} diff --git a/benchmark/test_runner/test-options.js b/benchmark/test_runner/test-options.js new file mode 100644 index 000000000000..1d608c1f9ccb --- /dev/null +++ b/benchmark/test_runner/test-options.js @@ -0,0 +1,114 @@ +'use strict'; + +const common = require('../common'); +const { finished } = require('node:stream/promises'); +const reporter = require('../fixtures/empty-test-reporter'); +const { it } = require('node:test'); + +const bench = common.createBenchmark(main, { + n: [10000], + option: [ + 'none', + 'skip', + 'skip-with-message', + 'skip-method', + 'skip-method-with-message', + 'todo', + 'todo-with-message', + 'todo-method', + 'todo-method-with-message', + ], +}, { + // We don't want to test the reporter here. + flags: ['--test-reporter=./benchmark/fixtures/empty-test-reporter.js'], +}); + +const noop = () => {}; + +const allTests = { + 'none': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, noop); + } + + return finished(reporter); + }, + 'skip': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, { skip: true }, () => { + throw new Error('This test should not run.'); + }); + } + + return finished(reporter); + }, + 'skip-with-message': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, { skip: 'skip reason' }, () => { + throw new Error('This test should not run.'); + }); + } + + return finished(reporter); + }, + 'skip-method': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, (t) => { + t.skip(); + }); + } + + return finished(reporter); + }, + 'skip-method-with-message': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, (t) => { + t.skip('skip reason'); + }); + } + + return finished(reporter); + }, + 'todo': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, { todo: true }, noop); + } + + return finished(reporter); + }, + 'todo-with-message': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, { todo: 'todo reason' }, noop); + } + + return finished(reporter); + }, + 'todo-method': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, (t) => { + t.todo(); + }); + } + + return finished(reporter); + }, + 'todo-method-with-message': (loopAmount) => { + for (let i = 0; i < loopAmount; i++) { + it(`${i}`, (t) => { + t.todo('todo reason'); + }); + } + + return finished(reporter); + }, +}; + +function main({ n, option }) { + const runOption = allTests[option]; + + bench.start(); + + runOption(n).then(() => { + bench.end(n); + }); +} diff --git a/benchmark/util/utf8-value.js b/benchmark/util/utf8-value.js new file mode 100644 index 000000000000..bc6101e74e51 --- /dev/null +++ b/benchmark/util/utf8-value.js @@ -0,0 +1,25 @@ +'use strict'; + +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + type: ['ascii', 'two_bytes', 'three_bytes', 'mixed'], + n: [5e6], +}); + +const urls = { + ascii: 'https://example.com/path/to/resource?query=value&foo=bar', + two_bytes: 'https://example.com/yol/türkçe/içerik?sağlık=değer', + three_bytes: 'https://example.com/路径/资源?查询=值&名称=数据', + mixed: 'https://example.com/hello/世界/path?name=değer&key=数据', +}; + +function main({ n, type }) { + const str = urls[type]; + + bench.start(); + for (let i = 0; i < n; i++) { + URL.canParse(str); + } + bench.end(n); +} diff --git a/benchmark/webstreams/encoding-streams.js b/benchmark/webstreams/encoding-streams.js new file mode 100644 index 000000000000..00759bc09eb7 --- /dev/null +++ b/benchmark/webstreams/encoding-streams.js @@ -0,0 +1,39 @@ +'use strict'; +const common = require('../common.js'); +const { + ReadableStream, + TextEncoderStream, + TextDecoderStream, +} = require('node:stream/web'); + +const bench = common.createBenchmark(main, { + n: [1e5], + kind: ['encode', 'decode'], + len: [16, 1024], +}); + +async function main({ n, kind, len }) { + const encoded = new TextEncoder().encode('a'.repeat(len)); + const decoded = 'a'.repeat(len); + let i = 0; + const rs = new ReadableStream({ + pull(controller) { + if (i++ < n) { + controller.enqueue(kind === 'encode' ? decoded : encoded); + } else { + controller.close(); + } + }, + }); + const ts = kind === 'encode' ? + new TextEncoderStream() : + new TextDecoderStream(); + + const reader = rs.pipeThrough(ts).getReader(); + bench.start(); + for (;;) { + const { done } = await reader.read(); + if (done) break; + } + bench.end(n); +} diff --git a/benchmark/webstreams/from.js b/benchmark/webstreams/from.js new file mode 100644 index 000000000000..05eca4079f1d --- /dev/null +++ b/benchmark/webstreams/from.js @@ -0,0 +1,29 @@ +'use strict'; +const common = require('../common.js'); +const { + ReadableStream, +} = require('node:stream/web'); + +const bench = common.createBenchmark(main, { + n: [1e6], + kind: ['sync', 'async'], +}); + +async function main({ n, kind }) { + function* syncGen() { + for (let i = 0; i < n; i++) yield i; + } + + async function* asyncGen() { + for (let i = 0; i < n; i++) yield i; + } + + const reader = ReadableStream.from( + kind === 'sync' ? syncGen() : asyncGen()).getReader(); + bench.start(); + for (;;) { + const { done } = await reader.read(); + if (done) break; + } + bench.end(n); +} diff --git a/benchmark/webstreams/pipe-through.js b/benchmark/webstreams/pipe-through.js new file mode 100644 index 000000000000..8af088f4eed1 --- /dev/null +++ b/benchmark/webstreams/pipe-through.js @@ -0,0 +1,38 @@ +'use strict'; +const common = require('../common.js'); +const { + ReadableStream, + TransformStream, +} = require('node:stream/web'); + +const bench = common.createBenchmark(main, { + n: [5e5], + kind: ['default', 'transform'], +}); + +async function main({ n, kind }) { + const b = Buffer.alloc(64); + let i = 0; + const rs = new ReadableStream({ + pull(controller) { + if (i++ < n) { + controller.enqueue(b); + } else { + controller.close(); + } + }, + }); + const ts = kind === 'default' ? + new TransformStream() : + new TransformStream({ + transform(chunk, controller) { controller.enqueue(chunk); }, + }); + + const reader = rs.pipeThrough(ts).getReader(); + bench.start(); + for (;;) { + const { done } = await reader.read(); + if (done) break; + } + bench.end(n); +} diff --git a/benchmark/webstreams/pipe-to.js b/benchmark/webstreams/pipe-to.js index 38324cd20822..e902f67a9887 100644 --- a/benchmark/webstreams/pipe-to.js +++ b/benchmark/webstreams/pipe-to.js @@ -7,8 +7,8 @@ const { const bench = common.createBenchmark(main, { n: [5e5], - highWaterMarkR: [512, 1024, 2048, 4096], - highWaterMarkW: [512, 1024, 2048, 4096], + highWaterMarkR: [1, 1024, 4096], + highWaterMarkW: [1, 1024, 4096], }); @@ -16,7 +16,6 @@ async function main({ n, highWaterMarkR, highWaterMarkW }) { const b = Buffer.alloc(1024); let i = 0; const rs = new ReadableStream({ - highWaterMark: highWaterMarkR, pull: function(controller) { if (i++ < n) { controller.enqueue(b); @@ -24,12 +23,11 @@ async function main({ n, highWaterMarkR, highWaterMarkW }) { controller.close(); } }, - }); + }, { highWaterMark: highWaterMarkR }); const ws = new WritableStream({ - highWaterMark: highWaterMarkW, write(chunk, controller) {}, close() { bench.end(n); }, - }); + }, { highWaterMark: highWaterMarkW }); bench.start(); rs.pipeTo(ws); diff --git a/common.gypi b/common.gypi index 0b01ec8c49fe..a83523286a91 100644 --- a/common.gypi +++ b/common.gypi @@ -604,12 +604,12 @@ 'cflags': [ '-mminimal-toc' ], }], ], - 'cflags': [ '-m64' ], - 'ldflags': [ '-m64' ], + 'cflags': [ '-m64', '-mcpu=power9' ], + 'ldflags': [ '-m64', '-mcpu=power9' ], }], [ 'host_arch=="s390x" and OS=="linux"', { - 'cflags': [ '-m64', '-march=z196' ], - 'ldflags': [ '-m64', '-march=z196' ], + 'cflags': [ '-m64', '-march=z14' ], + 'ldflags': [ '-m64', '-march=z14' ], }], ], }], @@ -629,12 +629,12 @@ 'cflags': [ '-mminimal-toc' ], }], ], - 'cflags': [ '-m64' ], - 'ldflags': [ '-m64' ], + 'cflags': [ '-m64', '-mcpu=power9' ], + 'ldflags': [ '-m64', '-mcpu=power9' ], }], [ 'target_arch=="s390x" and OS=="linux"', { - 'cflags': [ '-m64', '-march=z196' ], - 'ldflags': [ '-m64', '-march=z196' ], + 'cflags': [ '-m64', '-march=z14' ], + 'ldflags': [ '-m64', '-march=z14' ], }], ], }], diff --git a/configure.py b/configure.py index 20bf5b7d101f..b663ba85f58e 100755 --- a/configure.py +++ b/configure.py @@ -1101,7 +1101,7 @@ action='store_true', dest='enable_static', default=None, - help='build as static library') + help=argparse.SUPPRESS) # Deprecated parser.add_argument('--no-browser-globals', action='store_true', @@ -1517,7 +1517,7 @@ def get_openssl_version(o): return version_number - except (OSError, ValueError, subprocess.SubprocessError) as e: + except (OSError, TypeError, ValueError, subprocess.SubprocessError) as e: warn(f'Failed to determine OpenSSL version from header: {e}') return 0 @@ -2075,9 +2075,6 @@ def configure_node(o): if options.v8_options: o['variables']['node_v8_options'] = options.v8_options.replace('"', '\\"') - if options.enable_static: - o['variables']['node_target_type'] = 'static_library' - o['variables']['node_debug_lib'] = b(options.node_debug_lib) if options.debug_nghttp2: @@ -2120,10 +2117,13 @@ def configure_node(o): else: o['variables']['coverage'] = 'false' + if options.enable_static and options.shared: + error('--enable-static must not be set with --shared') + if options.enable_static: + warn('--enable-static is deprecated and libnode.a is always produced') + if options.shared: o['variables']['node_target_type'] = 'shared_library' - elif options.enable_static: - o['variables']['node_target_type'] = 'static_library' else: o['variables']['node_target_type'] = 'executable' diff --git a/deps/googletest/include/gtest/gtest-matchers.h b/deps/googletest/include/gtest/gtest-matchers.h index d7bdd047f1a6..b5950425cc69 100644 --- a/deps/googletest/include/gtest/gtest-matchers.h +++ b/deps/googletest/include/gtest/gtest-matchers.h @@ -45,6 +45,7 @@ #include #include #include +#include #include #include "gtest/gtest-printers.h" @@ -543,9 +544,8 @@ Matcher : public internal::MatcherBase { Matcher(const char* s); // NOLINT }; -#if GTEST_INTERNAL_HAS_STRING_VIEW // The following two specializations allow the user to write str -// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view +// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string_view // matcher is expected. template <> class GTEST_API_ [[nodiscard]] Matcher @@ -569,7 +569,7 @@ class GTEST_API_ [[nodiscard]] Matcher // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT - // Allows the user to pass absl::string_views or std::string_views directly. + // Allows the user to pass std::string_views directly. Matcher(internal::StringView s); // NOLINT }; @@ -596,10 +596,9 @@ class GTEST_API_ [[nodiscard]] Matcher // Allows the user to write "foo" instead of Eq("foo") sometimes. Matcher(const char* s); // NOLINT - // Allows the user to pass absl::string_views or std::string_views directly. + // Allows the user to pass std::string_views directly. Matcher(internal::StringView s); // NOLINT }; -#endif // GTEST_INTERNAL_HAS_STRING_VIEW // Prints a matcher in a human-readable format. template @@ -812,9 +811,26 @@ class [[nodiscard]] ImplicitCastEqMatcher { StoredRhs stored_rhs_; }; -template >> -using StringLike = T; +// Dummy function (never defined) whose return type evaluates to std::string if +// the given type is a string-like type that can be converted to std::string, +// either directly or through an intermediate std::string_view. +template +extern std::enable_if_t, std::string> +ResolveAsString(const void* /* preferred */); + +#if GTEST_HAS_STD_WSTRING +// Same as above, but for std::wstring. In cases where both conversions are +// possible, this overload takes lower priority. +template +extern std::enable_if_t, std::wstring> +ResolveAsString(... /* fallback */); +#endif + +// Evaluates to the std::basic_string type that the given string-like type can +// be converted to. Prefers std::string over std::wstring if both are possible. +// Fails in a SFINAE-friendly way if no conversion was viable. +template +using StringType = decltype(ResolveAsString(nullptr)); // Implements polymorphic matchers MatchesRegex(regex) and // ContainsRegex(regex), which can be used as a Matcher as long as @@ -824,12 +840,10 @@ class [[nodiscard]] MatchesRegexMatcher { MatchesRegexMatcher(const RE* regex, bool full_match) : regex_(regex), full_match_(full_match) {} -#if GTEST_INTERNAL_HAS_STRING_VIEW bool MatchAndExplain(const internal::StringView& s, MatchResultListener* listener) const { return MatchAndExplain(std::string(s), listener); } -#endif // GTEST_INTERNAL_HAS_STRING_VIEW // Accepts pointer types, particularly: // const char* @@ -844,7 +858,7 @@ class [[nodiscard]] MatchesRegexMatcher { // Matches anything that can convert to std::string. // // This is a template, not just a plain function with const std::string&, - // because absl::string_view has some interfering non-explicit constructors. + // because std::string_view has some interfering non-explicit constructors. template bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { @@ -877,9 +891,10 @@ inline PolymorphicMatcher MatchesRegex( return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true)); } template -PolymorphicMatcher MatchesRegex( - const internal::StringLike& regex) { - return MatchesRegex(new internal::RE(std::string(regex))); +std::enable_if_t>, + PolymorphicMatcher> +MatchesRegex(const T& regex) { + return MatchesRegex(new internal::RE(internal::StringType(regex))); } // Matches a string that contains regular expression 'regex'. @@ -889,9 +904,10 @@ inline PolymorphicMatcher ContainsRegex( return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false)); } template -PolymorphicMatcher ContainsRegex( - const internal::StringLike& regex) { - return ContainsRegex(new internal::RE(std::string(regex))); +std::enable_if_t>, + PolymorphicMatcher> +ContainsRegex(const T& regex) { + return ContainsRegex(new internal::RE(internal::StringType(regex))); } // Creates a polymorphic matcher that matches anything equal to x. diff --git a/deps/googletest/include/gtest/gtest-printers.h b/deps/googletest/include/gtest/gtest-printers.h index fc0913ff0094..69c9fec3ca95 100644 --- a/deps/googletest/include/gtest/gtest-printers.h +++ b/deps/googletest/include/gtest/gtest-printers.h @@ -291,11 +291,9 @@ struct ConvertibleToIntegerPrinter { }; struct ConvertibleToStringViewPrinter { -#if GTEST_INTERNAL_HAS_STRING_VIEW static void PrintValue(internal::StringView value, ::std::ostream* os) { internal::UniversalPrint(value, os); } -#endif }; #ifdef GTEST_HAS_ABSL @@ -703,12 +701,12 @@ void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { } } -// Overloads for ::std::string and ::std::string_view -GTEST_API_ void PrintStringTo(::std::string_view s, ::std::ostream* os); +// Overloads for ::std::string and std::string_view +GTEST_API_ void PrintStringTo(std::string_view s, ::std::ostream* os); inline void PrintTo(const ::std::string& s, ::std::ostream* os) { PrintStringTo(s, os); } -inline void PrintTo(::std::string_view s, ::std::ostream* os) { +inline void PrintTo(std::string_view s, ::std::ostream* os) { PrintStringTo(s, os); } @@ -752,16 +750,14 @@ inline void PrintTo(::std::wstring_view s, ::std::ostream* os) { } #endif // GTEST_HAS_STD_WSTRING -#if GTEST_INTERNAL_HAS_STRING_VIEW // Overload for internal::StringView. Needed for build configurations where // internal::StringView is an alias for absl::string_view, but absl::string_view // is a distinct type from std::string_view. template , int> = 0> + std::enable_if_t, int> = 0> inline void PrintTo(internal::StringView sp, ::std::ostream* os) { PrintStringTo(sp, os); } -#endif // GTEST_INTERNAL_HAS_STRING_VIEW inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; } @@ -1177,15 +1173,12 @@ class [[nodiscard]] UniversalTersePrinter { } } }; -#endif template <> -class [[nodiscard]] UniversalTersePrinter { - public: - static void Print(wchar_t* str, ::std::ostream* os) { - UniversalTersePrinter::Print(str, os); - } -}; +class [[nodiscard]] UniversalTersePrinter + : public UniversalTersePrinter {}; + +#endif // GTEST_HAS_STD_WSTRING template void UniversalTersePrint(const T& value, ::std::ostream* os) { diff --git a/deps/googletest/include/gtest/internal/gtest-death-test-internal.h b/deps/googletest/include/gtest/internal/gtest-death-test-internal.h index f88e2049c249..f0f93e520b7b 100644 --- a/deps/googletest/include/gtest/internal/gtest-death-test-internal.h +++ b/deps/googletest/include/gtest/internal/gtest-death-test-internal.h @@ -43,6 +43,7 @@ #include #include +#include #include "gtest/gtest-matchers.h" #include "gtest/internal/gtest-internal.h" @@ -63,6 +64,10 @@ inline Matcher MakeDeathTestMatcher( ::testing::internal::RE regex) { return ContainsRegex(regex.pattern()); } +inline Matcher MakeDeathTestMatcher( + std::string_view regex) { + return ContainsRegex(regex); +} inline Matcher MakeDeathTestMatcher(const char* regex) { return ContainsRegex(regex); } diff --git a/deps/googletest/include/gtest/internal/gtest-internal.h b/deps/googletest/include/gtest/internal/gtest-internal.h index 2b048c5dc098..55e9966720bf 100644 --- a/deps/googletest/include/gtest/internal/gtest-internal.h +++ b/deps/googletest/include/gtest/internal/gtest-internal.h @@ -1451,13 +1451,13 @@ class [[nodiscard]] NeverThrown { // Implements Boolean test assertions such as EXPECT_TRUE. expression can be // either a boolean expression or an AssertionResult. text is a textual // representation of expression as it was passed into the EXPECT_TRUE. -#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AssertionResultExpectation gtest_are_ = { \ - ::testing::AssertionResult(expression), expected}) \ - ; \ - else \ - fail(::testing::internal::GetBoolAssertionFailureMessage( \ +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::internal::AssertionResultExpectation gtest_are_ = { \ + ::testing::AssertionResult(expression), expected}) \ + ; \ + else /* NOLINT */ \ + fail(::testing::internal::GetBoolAssertionFailureMessage( \ gtest_are_.assertion_result, text, #actual, #expected)) #define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ diff --git a/deps/googletest/include/gtest/internal/gtest-port.h b/deps/googletest/include/gtest/internal/gtest-port.h index 31654b09c1dc..051228553449 100644 --- a/deps/googletest/include/gtest/internal/gtest-port.h +++ b/deps/googletest/include/gtest/internal/gtest-port.h @@ -293,9 +293,10 @@ #include #include #include +// #include // Guarded by GTEST_IS_THREADSAFE below #include #include -// #include // Guarded by GTEST_IS_THREADSAFE below +#include #include #include #include @@ -499,22 +500,71 @@ typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; #endif // defined(_MSC_VER) || defined(__BORLANDC__) #endif // GTEST_HAS_EXCEPTIONS -#ifndef GTEST_HAS_STD_WSTRING -// The user didn't tell us whether ::std::wstring is available, so we need -// to figure it out. +// 1. Calculate default GTEST_HAS_STD_WSTRING values based on STL capabilities. +#if defined(_MSVC_STL_VERSION) +// Microsoft's STL implementation always supports ::std::wstring. +#define GTEST_HAS_STD_WSTRING_DEFAULT 1 + +#elif defined(_LIBCPP_VERSION) +// Modern libc++ always defines _LIBCPP_HAS_WIDE_CHARACTERS; its value +// determines whether wide characters are supported. +// Older libc++ omits a definition for _LIBCPP_HAS_NO_WIDE_CHARACTERS when wide +// characters are supported. +#if (defined(_LIBCPP_HAS_WIDE_CHARACTERS) && !_LIBCPP_HAS_WIDE_CHARACTERS) || \ + defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) +#define GTEST_HAS_STD_WSTRING_DEFAULT 0 +#else +#define GTEST_HAS_STD_WSTRING_DEFAULT 1 +#endif + +#elif defined(__GLIBCXX__) +#if defined(_GLIBCXX_USE_WCHAR_T) && _GLIBCXX_USE_WCHAR_T +#define GTEST_HAS_STD_WSTRING_DEFAULT 1 +#else +#define GTEST_HAS_STD_WSTRING_DEFAULT 0 +#endif + +#else +// Unknown standard library implementation; fall back looking at the OS. +// +// Always let the user override the defaults in this case; they might have more +// information about what's supported than we do. +#if defined(GTEST_OS_LINUX_ANDROID) +// Android started supporting std::wstring with API Level 21 (Lollipop). +#define GTEST_HAS_STD_WSTRING_DEFAULT (__ANDROID_API__ >= 21) +// The following platforms are known not to support ::std::wstring; assume it's +// supported on all others. +// // Cygwin 1.7 and below doesn't support ::std::wstring. -// Solaris' libc++ doesn't support it either. Android has -// no support for it at least as recent as Froyo (2.2). -#if (!(defined(GTEST_OS_LINUX_ANDROID) || defined(GTEST_OS_CYGWIN) || \ - defined(GTEST_OS_SOLARIS) || defined(GTEST_OS_HAIKU) || \ - defined(GTEST_OS_ESP32) || defined(GTEST_OS_ESP8266) || \ - defined(GTEST_OS_XTENSA) || defined(GTEST_OS_QURT) || \ - defined(GTEST_OS_NXP_QN9090) || defined(GTEST_OS_NRF52))) -#define GTEST_HAS_STD_WSTRING 1 +// Solaris' libc++ doesn't support it either. +#elif defined(GTEST_OS_CYGWIN) || defined(GTEST_OS_SOLARIS) || \ + defined(GTEST_OS_HAIKU) || defined(GTEST_OS_ESP32) || \ + defined(GTEST_OS_ESP8266) || defined(GTEST_OS_XTENSA) || \ + defined(GTEST_OS_QURT) || defined(GTEST_OS_NXP_QN9090) || \ + defined(GTEST_OS_NRF52) +#define GTEST_HAS_STD_WSTRING_DEFAULT 0 #else -#define GTEST_HAS_STD_WSTRING 0 +#define GTEST_HAS_STD_WSTRING_DEFAULT 1 +#endif +#endif + +// 2. Validate explicit user overrides (if user passed -DGTEST_HAS_*=1) against +// what the standard library implementation tells us it supports. +#if defined(GTEST_HAS_STD_WSTRING) && GTEST_HAS_STD_WSTRING +#if defined(_LIBCPP_VERSION) && \ + ((defined(_LIBCPP_HAS_WIDE_CHARACTERS) && !_LIBCPP_HAS_WIDE_CHARACTERS) || \ + defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)) +#error Cannot explicitly enable GTEST_HAS_STD_WSTRING without libc++ wide character support. +#elif defined(__GLIBCXX__) && \ + !(defined(_GLIBCXX_USE_WCHAR_T) && _GLIBCXX_USE_WCHAR_T) +#error Cannot explicitly enable GTEST_HAS_STD_WSTRING without libstdc++ wide character support. +#endif +#endif + +// 3. Set final values if not explicitly overridden by user +#if !defined(GTEST_HAS_STD_WSTRING) +#define GTEST_HAS_STD_WSTRING GTEST_HAS_STD_WSTRING_DEFAULT #endif -#endif // GTEST_HAS_STD_WSTRING #ifndef GTEST_HAS_FILE_SYSTEM // Most platforms support a file system. @@ -949,21 +999,21 @@ GTEST_API_ bool IsTrue(bool condition); #ifdef GTEST_USES_RE2 // This is almost `using RE = ::RE2`, except it is copy-constructible, and it -// needs to disambiguate the `std::string`, `absl::string_view`, and `const +// needs to disambiguate the `std::string`, `std::string_view`, and `const // char*` constructors. class GTEST_API_ [[nodiscard]] RE { public: - RE(absl::string_view regex) : regex_(regex) {} // NOLINT - RE(const char* regex) : RE(absl::string_view(regex)) {} // NOLINT - RE(const std::string& regex) : RE(absl::string_view(regex)) {} // NOLINT + RE(std::string_view regex) : regex_(regex) {} // NOLINT + RE(const char* regex) : RE(std::string_view(regex)) {} // NOLINT + RE(const std::string& regex) : RE(std::string_view(regex)) {} // NOLINT RE(const RE& other) : RE(other.pattern()) {} const std::string& pattern() const { return regex_.pattern(); } - static bool FullMatch(absl::string_view str, const RE& re) { + static bool FullMatch(std::string_view str, const RE& re) { return RE2::FullMatch(str, re.regex_); } - static bool PartialMatch(absl::string_view str, const RE& re) { + static bool PartialMatch(std::string_view str, const RE& re) { return RE2::PartialMatch(str, re.regex_); } @@ -2396,7 +2446,6 @@ const char* StringFromGTestEnv(const char* flag, const char* default_val); #ifdef GTEST_HAS_ABSL // Always use absl::string_view for Matcher<> specializations if googletest // is built with absl support. -#define GTEST_INTERNAL_HAS_STRING_VIEW 1 #include "absl/strings/string_view.h" namespace testing { namespace internal { @@ -2404,26 +2453,15 @@ using StringView = ::absl::string_view; } // namespace internal } // namespace testing #else -#if defined(__cpp_lib_string_view) || \ - (GTEST_INTERNAL_HAS_INCLUDE() && \ - GTEST_INTERNAL_CPLUSPLUS_LANG >= 201703L) // Otherwise for C++17 and higher use std::string_view for Matcher<> // specializations. -#define GTEST_INTERNAL_HAS_STRING_VIEW 1 -#include namespace testing { namespace internal { -using StringView = ::std::string_view; +using StringView = std::string_view; } // namespace internal } // namespace testing -// The case where absl is configured NOT to alias std::string_view is not -// supported. -#endif // __cpp_lib_string_view #endif // GTEST_HAS_ABSL - -#ifndef GTEST_INTERNAL_HAS_STRING_VIEW -#define GTEST_INTERNAL_HAS_STRING_VIEW 0 -#endif +#define GTEST_INTERNAL_HAS_STRING_VIEW 1 #if defined(__cpp_lib_three_way_comparison) #define GTEST_INTERNAL_HAS_COMPARE_LIB 1 diff --git a/deps/googletest/src/gtest-matchers.cc b/deps/googletest/src/gtest-matchers.cc index 7e3bcc0cff38..626019e2389f 100644 --- a/deps/googletest/src/gtest-matchers.cc +++ b/deps/googletest/src/gtest-matchers.cc @@ -59,7 +59,6 @@ Matcher::Matcher(const std::string& s) { *this = Eq(s); } // s. Matcher::Matcher(const char* s) { *this = Eq(std::string(s)); } -#if GTEST_INTERNAL_HAS_STRING_VIEW // Constructs a matcher that matches a const StringView& whose value is // equal to s. Matcher::Matcher(const std::string& s) { @@ -93,6 +92,5 @@ Matcher::Matcher(const char* s) { Matcher::Matcher(internal::StringView s) { *this = Eq(std::string(s)); } -#endif // GTEST_INTERNAL_HAS_STRING_VIEW } // namespace testing diff --git a/deps/googletest/src/gtest-printers.cc b/deps/googletest/src/gtest-printers.cc index 6d1de6d9506f..975ebb829876 100644 --- a/deps/googletest/src/gtest-printers.cc +++ b/deps/googletest/src/gtest-printers.cc @@ -50,6 +50,7 @@ #include #include #include // NOLINT +#include #include #include @@ -422,6 +423,28 @@ void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { namespace { +template +size_t GetLength(const Char* s) { + return std::char_traits::length(s); +} + +#if !GTEST_HAS_STD_WSTRING + +// If GTEST_HAS_STD_WSTRING is unset because the standard library has disabled +// wide character support, std::char_traits won't be defined, which +// will cause a compile error, even if user code never actually could print a +// wide cstring. In that case, instead use `wcslen` directly. +// +// If `libc` _also_ lacks wide character support, this (and a bunch of other +// calls to wc functions) will fail to link, but only if user code actually +// uses them. +template <> +size_t GetLength(const wchar_t* s) { + return wcslen(s); +} + +#endif // GTEST_HAS_STD_WSTRING + // Prints a null-terminated C-style string to the ostream. template void PrintCStringTo(const Char* s, ostream* os) { @@ -429,7 +452,7 @@ void PrintCStringTo(const Char* s, ostream* os) { *os << "NULL"; } else { *os << ImplicitCast_(s) << " pointing to "; - PrintCharsAsStringTo(s, std::char_traits::length(s), os); + PrintCharsAsStringTo(s, GetLength(s), os); } } @@ -515,13 +538,13 @@ bool IsValidUTF8(const char* str, size_t length) { void ConditionalPrintAsText(const char* str, size_t length, ostream* os) { if (!ContainsUnprintableControlCodes(str, length) && IsValidUTF8(str, length)) { - *os << "\n As Text: \"" << ::std::string_view(str, length) << "\""; + *os << "\n As Text: \"" << std::string_view(str, length) << "\""; } } } // anonymous namespace -void PrintStringTo(::std::string_view s, ostream* os) { +void PrintStringTo(std::string_view s, ostream* os) { if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { if (GTEST_FLAG_GET(print_utf8)) { ConditionalPrintAsText(s.data(), s.size(), os); diff --git a/deps/googletest/src/gtest.cc b/deps/googletest/src/gtest.cc index 307ecc6f0b9c..3c855468268f 100644 --- a/deps/googletest/src/gtest.cc +++ b/deps/googletest/src/gtest.cc @@ -6930,7 +6930,7 @@ void ParseGoogleTestFlagsOnly(int* argc, char** argv) { std::vector positional_args; std::vector unrecognized_flags; absl::ParseAbseilFlagsOnly(*argc, argv, positional_args, unrecognized_flags); - absl::flat_hash_set unrecognized; + absl::flat_hash_set unrecognized; for (const auto& flag : unrecognized_flags) { unrecognized.insert(flag.flag_name); } diff --git a/deps/libffi/ChangeLog b/deps/libffi/ChangeLog index 0dde93e6adad..f64d5d5c46ac 100644 --- a/deps/libffi/ChangeLog +++ b/deps/libffi/ChangeLog @@ -1,3 +1,610 @@ +commit 12ffd1f9dc56fcea79d2f742f424301ae668d663 +Author: Anthony Green +Date: Sat Aug 8 18:08:29 2026 -0400 + + README: order 3.8.0 notes by decreasing importance + + Lead with new capabilities (VECTOR types, ffi_call_plan_size, ppc64 + _Complex long double), then correctness fixes by severity, then the + trampoline caching optimization. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 8f2a41d9d89dd8ee2c2438f1e2f9cf04aa9a53d4 +Author: Anthony Green +Date: Sat Aug 8 18:05:33 2026 -0400 + + Release 3.8.0 + + Bump version to 3.8.0, soname to libffi.so.8.5.0 (libtool 13:0:5) for the + new public interfaces added this cycle (FFI_TYPE_VECTOR, ffi_call_plan_size), + date the README history section, and update doc/version.texi. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit d956fe177ccfd4bb91ae7cb3ccaa0f8935a76522 +Author: Anthony Green +Date: Sat Aug 8 17:38:40 2026 -0400 + + testsuite: distribute plan_size.c + + The ffi_call_plan_size test added in #1006 was not listed in EXTRA_DIST, + so it would be omitted from release tarballs (it still runs from a git + checkout, where dejagnu globs *.c). Add it alongside the other plan_*.c + tests. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 670a0327b7d1576712a3cad7b9297f59f23d5430 +Author: Anthony Green +Date: Sat Aug 8 17:09:22 2026 -0400 + + README: note i386 BSD small-struct register return + + Follow-up to #1010, which returns small structs in registers on i386 + FreeBSD/OpenBSD but did not update the History section. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit f744bc303fa4c69f1202ce283b866ebc768e0432 +Merge: abc18be0 5b8fa3fe +Author: Anthony Green +Date: Sat Aug 8 17:09:02 2026 -0400 + + Merge pull request #1010 from DTW-Thalion/x86-bsd-small-struct-return + + x86: return small structs in registers on the BSD i386 targets + +commit abc18be0d9ba9cc37c955b317e63cd52fd0d90ee +Merge: ed742112 5f24e6a0 +Author: Anthony Green +Date: Sat Aug 8 16:57:36 2026 -0400 + + Merge pull request #1006 from rvandermeulen/call-plan-size + + call_plan: add ffi_call_plan_size to report a plan's allocation + +commit ed7421122880e4daff87f1c8623d508a2e2c5c9a +Merge: e43f2548 e6db2d38 +Author: Anthony Green +Date: Sat Aug 8 16:41:24 2026 -0400 + + Merge pull request #1009 from libffi/fix-jumptable-desync-family + + Fix FFI_TYPE_LAST/vector jump-table desyncs on ia64, ppc64 (BE ELFv2), and aarch64 + +commit e6db2d38decee8bf6321472dff5147ad311e639a +Author: Anthony Green +Date: Fri Aug 7 17:00:49 2026 -0400 + + aarch64: reject sub-4-byte vector lanes in HVA classification + + is_vfp_type() maps a homogeneous vector aggregate's lane width onto the + S/D/Q register classes via FFI_TYPE_FLOAT + intlog2(reg_size) - 2, and + encodes the result as an AARCH64_RET_* code. A lane narrower than 4 bytes + (e.g. a struct of two 2-byte vectors, which libffi's own initialize_vector + accepts) yields intlog2(reg_size) < 2, producing a code below + AARCH64_RET_S4. extend_hfa_type() then computes a negative jump-table + offset (h - AARCH64_RET_S4) and branches before its table -- a wild + computed branch during ffi_call argument marshalling. + + Such a type has no short-vector register class under AAPCS64, so reject it + in is_vfp_type() (returning 0 routes it through the generic aggregate + path). Fixing it at the source covers both the argument path + (extend_hfa_type) and the return path. Verified on aarch64 (Fedora under + qemu-aarch64): a call passing such an HVA segfaults before the fix and + returns correctly after it. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 159268174ece06f6854c6d1bca1a9b95961f6ae9 +Author: Anthony Green +Date: Fri Aug 7 08:18:51 2026 -0400 + + powerpc64: fix big-endian ELFv2 closure returns of 5/6/7-byte structs + + On big-endian ppc64 ELFv2, ffi_closure_helper_LINUX64 returns the load + codes PPC64_LD_STRUCT_5/6/7 (17/18/19) for closures returning a 5-, 6-, + or 7-byte struct, but linux64_closure.S only defined return jump-table + entries through PPC64_LD_STRUCT_3 (16). The E() macro places each 16-byte + slot with .align 4 (no .org), so codes 17/18/19 fell through into the + .Lmoredouble continuation: the closure loaded FP registers and returned + without writing r3, so the ELFv2 caller read back the computed jump + target -- a libffi code address -- as the struct value (wrong result plus + a code-pointer disclosure). Little-endian ELFv2 is unaffected (those + codes alias PPC_LD_R3/I64); big-endian ELFv1 returns structs by reference + and never emits the codes. + + Add the three missing handlers, loading the struct right-justified into + r3 per the ELFv2 convention. Verified on big-endian ppc64 ELFv2 (Adélie + Linux under qemu-ppc64): testsuite/libffi.closures/cls_{5,6,7}_1_byte.c + abort before the fix and pass after it. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 3fdd99b5d2fb4c8f940d82fc4b1e530e743c685b +Author: Anthony Green +Date: Fri Aug 7 06:11:00 2026 -0400 + + ia64: fix return jump-table desync after FFI_TYPE_LAST bump + + The .Lst_table/.Lld_table return-value dispatch tables in unix.S are + indexed by the FFI_IA64_TYPE_SMALL_STRUCT/HFA_* codes, which are + FFI_TYPE_LAST-relative, but the tables hardcoded 20 entries assuming + FFI_TYPE_LAST == FFI_TYPE_COMPLEX (15). The conditional __int128 + support added in 3.6.0 advanced FFI_TYPE_LAST to SINT128 (17), and + FFI_TYPE_VECTOR advanced it to 18, shifting SMALL_STRUCT to 19 -- so a + small-struct return dispatched to the HFA-ldouble handler's 16-byte + stfe store, an out-of-bounds write past rvalue, and HFA returns indexed + off the end of the table entirely. + + Add the missing UINT128/SINT128/VECTOR slots to both tables (pointing at + the existing not-implemented void handler, matching FFI_TYPE_COMPLEX) + and a FFI_TYPE_LAST tripwire, mirroring the pa and win64 guards. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 5b8fa3fed84ce17768eeb379f5b81663172482e9 +Author: Todd White +Date: Fri Aug 7 19:40:05 2026 -0400 + + x86: return small structs in registers on the BSD i386 targets + + i386 FreeBSD and OpenBSD return a struct of 1, 2, 4 or 8 bytes in eax and + edx, as Darwin and win32 do. ffi_prep_cif_machdep applied the size test + only under X86_WIN32 and X86_DARWIN, so on these targets it recorded + X86_RET_STRUCTPOP and allocated a return pointer the callee never writes, + and a struct return through ffi_call or through a closure read a value + that was never stored. + + configure.host already maps i?86-*-freebsd* and i?86-*-openbsd* to + TARGET=X86_FREEBSD, and include/ffi.h.in defines that name, so extend the + condition to it. Sizes 3, 6 and 12 continue to be returned in memory. + +commit 5f24e6a05574b1aa74cca77b1ecd6413a8105f62 +Author: Ryan VanderMeulen +Date: Wed Aug 5 11:09:51 2026 -0400 + + call_plan: add ffi_call_plan_size to report a plan's allocation + + ffi_call_plan is opaque, so an embedder that tracks the memory a long-lived + plan holds has no way to ask how big it is. The only options are to hardcode + a guess or to hardcode knowledge of the private struct layout, and both go + stale silently on the next release. + + The x86-64 backend records the byte count in ffi_plan at the point it is + passed to malloc, so the reported value cannot drift from the allocation it + describes; ffi_call_plan_size adds that to the handle and treats a signature + with no fast path as owning nothing beyond it. The generic backend's plan is + a bare handle, so it reports sizeof (struct ffi_call_plan). The counter lives + in ffi_plan rather than in the handle so that only plans that actually own a + move-list pay for it, and plans without one pay nothing. + + Computing the size in the query from cif->nargs instead would duplicate + build_plan's allocation formula in a second place, and would report the wrong + number if the cif were re-prepared with a different argument count after the + plan was built. + + The new symbol gets its own version node rather than joining + LIBFFI_CALL_PLAN_8.4, which shipped in 3.7.0: adding to a released node would + let a binary that needs ffi_call_plan_size look satisfiable against a 3.7.x + library that exports the node without the symbol, turning a clean link error + into a runtime failure. libtool-version is left alone, since rule 2 in that + file defers version updates to immediately before a release. + +commit e43f254881f9010a26c48f595928461c0432c7b4 +Merge: 2fd434cd 04d721cc +Author: Anthony Green +Date: Thu Aug 6 00:40:32 2026 -0400 + + Merge pull request #1008 from libffi/fix-win64-vector-small-struct-flags + + x86: fix Win64 small-struct returns broken by FFI_TYPE_VECTOR + +commit 04d721cc448316dbba5af506be46315efabd80e3 +Author: Anthony Green +Date: Wed Aug 5 22:59:29 2026 -0400 + + x86: fix Win64 small-struct returns broken by FFI_TYPE_VECTOR + + Adding FFI_TYPE_VECTOR (#1000) moved FFI_TYPE_LAST from FFI_TYPE_SINT128 + (17) to FFI_TYPE_VECTOR (18). The Win64 return pseudo-types + + FFI_TYPE_SMALL_STRUCT_1B/2B/4B = FFI_TYPE_LAST + 1..3 + + are FFI_TYPE_LAST-relative, so they shifted from 18/19/20 to 19/20/21. + The win64.S / win64_intel.S return-value dispatch is a computed jump + table indexed by cif->flags (base + flags*8) whose handlers are emitted + contiguously right after FFI_TYPE_SINT128, with no slot for value 18. + Under the sequential E() variant used by the MSVC/ml64 build, the + size-1/2/4 small-struct handlers therefore sat one 8-byte slot below the + flag values ffiw64.c now emits, so small structs returned by value were + written with the wrong width (or fell off the table into abort). This + showed up as 14 execution failures in the "Windows 64-bit Visual C++" CI + job (s55, struct3, struct_by_value_small, struct_return_2H, the small + cls_* / single_entry_structs closures, and bhaible DGTEST 47/53/55). + + Add an FFI_TYPE_VECTOR abort stub between SINT128 and SMALL_STRUCT_1B in + both tables so the jump table stays contiguous and the small-struct + entries realign with their (shifted) code values. Win64 does not marshal + vectors -- ffi_prep_cif_core rejects them since FFI_TARGET_HAS_VECTOR_TYPE + is undefined there -- so the slot is never reached at runtime. + + Also add a pa-style compile-time tripwire to src/x86/ffitarget.h so the + next generic type added bumps FFI_TYPE_LAST and #errors until the win64 + tables are updated in step. 32-bit x86 is unaffected: sysv.S indexes its + store table by the independent X86_RET_* enum, not FFI_TYPE_LAST. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 2fd434cd9ada4d3d97b355e62c3ce3a969682230 +Merge: a00279c2 2aa33761 +Author: Anthony Green +Date: Sun Aug 2 10:18:45 2026 -0400 + + Merge pull request #1005 from libffi/tramp-cache-unsupported-verdict + + tramp: cache the static trampoline "unsupported" verdict + +commit 2aa33761c0536339f9f322902b9bb3a981114724 +Merge: 76883c62 a00279c2 +Author: Anthony Green +Date: Sun Aug 2 10:18:33 2026 -0400 + + Merge branch 'master' into tramp-cache-unsupported-verdict + +commit 76883c628a5273f71fb025e75bf1076adae3bb4b +Author: Anthony Green +Date: Sun Aug 2 10:05:22 2026 -0400 + + tramp: cache the static trampoline "unsupported" verdict + + ffi_tramp_init() bailed out with a plain `return 0` when the system page + size exceeds the trampoline code table mapping, without recording the + outcome in tramp_globals.status. Because that early return was the only + failure exit that left status as UNINITIALIZED, every subsequent + ffi_tramp_alloc()/ffi_tramp_is_supported() call re-ran the full + initialization (ffi_tramp_arch(), sysconf(), etc.) instead of + short-circuiting on the cached verdict like the other two failure paths. + + The comparison is between two process-lifetime invariants -- map_size is + a compile-time constant from ffi_tramp_arch(), and page_size is fixed for + the life of the process (and only checked when sysconf() returned a valid + value) -- so it can never flip. Caching FAILED is therefore safe and + matches the intent of the status field. + + Affects hosts with pages larger than the 16K table, in practice 64K-page + aarch64 kernels, where static trampolines are correctly declined but the + decline was recomputed on every closure allocation. No functional change + on 4K/16K-page hosts. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit a00279c2dc8e191ae5136b46bf6ae0e7a8da5b7a +Author: Anthony Green +Date: Sat Aug 1 07:17:23 2026 -0400 + + Note unreleased development changes in README history + + Add a "Development source only" History block for changes on master + since 3.7.1: FFI_TYPE_VECTOR SIMD support (#1000), powerpc64 _Complex + long double (#1003), and the powerpc Darwin closure fix (#1002). + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit ce77ca107a5cb0d10d5525c9422f0207e6c79ebf +Merge: d257b084 19dbdb53 +Author: Anthony Green +Date: Sat Aug 1 07:09:17 2026 -0400 + + Merge pull request #1000 from edusperoni/feat/vector-types + + Add FFI_TYPE_VECTOR: vector (SIMD) type support with libffi-computed layout + +commit d257b08495e95248f66b7bd50dd106ea19124df9 +Merge: 333d87cf b2170647 +Author: Anthony Green +Date: Tue Jul 28 00:47:29 2026 -0400 + + Merge pull request #1004 from libffi/fix-1002-ppc-darwin-closure + + powerpc: fix Darwin closure returns broken by #951 + +commit b2170647583461f42dc2d9f201211fcafda2429f +Author: Anthony Green +Date: Mon Jul 27 20:08:15 2026 -0400 + + powerpc: fix Darwin closure returns broken by #951 + + PR #951 (840add3b) changed the shared PowerPC closure helper, + ffi_closure_helper_common, to return a small PPC_LD_* jump-table index + instead of the ffi_type*, and rewrote aix_closure.S to consume it -- but + left darwin_closure.S expecting the old ffi_type* and dereferencing it. + With the helper now returning a small integer, ffi_closure_ASM + dereferenced e.g. 0 (PPC_LD_NONE, a void return) as a pointer, faulting + on a load from address 0. This crashed essentially every closure call + -- including every gobject-introspection signal handler -- on 32- and + 64-bit PowerPC Darwin (SIGBUS at ffi_closure_ASM, dar=0; issue #1002). + + Convert darwin_closure.S to the PPC_LD_* convention, mirroring + aix_closure.S: drop the ffi_type* dereference, use the returned index + directly, and reorder the return-value jump table into PPC_LD_* order + (NONE, R3, R3R4, F32, F64, F128, U8, S8, U16, S16, and on ppc64 U32, S32). + + Darwin, unlike AIX, returns small structs by value in registers, which + the existing assembly handles (Lsmallstruct/Lfour/Lstructend). The + helper's return code is a single small integer with no room for + cif->rtype, which that assembly needs, so for a by-value struct return + the helper now stashes cif->rtype in the first parameter-save slot (dead + by return time) and returns a new PPC_LD_STRUCT code; the PPC_LD_STRUCT + fragment recovers it and drives the unchanged struct machinery. By- + reference struct returns still return PPC_LD_NONE. + + Based on the approach in a patch by Sergey Fedorov (@barracuda156); the + jump table here is reordered to the PPC_LD_* layout so that float, + double, long double, sub-word and 64-bit returns also dispatch correctly. + + I have no PowerPC Darwin hardware; the jump-table fragment offsets were + checked by assembling for powerpc and powerpc64, but runtime + confirmation on 10.5/10.6 is still needed. + + Fixes #1002. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit 333d87cf201ee279c9870fb5ad3e48e3a08aa6e5 +Merge: 46cb2e38 d7cd3a61 +Author: Anthony Green +Date: Mon Jul 27 08:52:34 2026 -0400 + + Merge pull request #1003 from libffi/fix-ppc64le-complex-longdouble + + powerpc64: implement _Complex long double for both IBM-128 and IEEE-128 + +commit d7cd3a6194885c85255c77782a05a153a42b29a5 +Author: Anthony Green +Date: Mon Jul 27 07:06:39 2026 -0400 + + powerpc64: implement _Complex long double for both IBM-128 and IEEE-128 + + Complex support for POWERPC64 ELFv2 (f0ca157, #970) defined + FFI_TARGET_HAS_COMPLEX_TYPE, which flips complex.exp from marking the + libffi.complex suite UNSUPPORTED to running it. _Complex long double + was deliberately deferred with FFI_BAD_TYPEDEF, so ffi_prep_cif failed + and every libffi.complex/*longdouble* test aborted. This was not caught + upstream because an XFAIL entry in the rlgl CI policy masked the FAILs. + + Implement both long double formats: + + - IBM-128 (double-double): each _Complex long double is passed and + returned as four doubles (real hi/lo, imag hi/lo) in f1-f4, with a + GPR shadow doubleword per FPR, and returned as a double homogeneous + aggregate. + + - IEEE binary128: real in v2, imag in v3; each half occupies a vector + register (or a 16-byte-aligned parameter save slot with two GPR + shadow doublewords) and is returned via the vector-homogeneous + small-struct path. + + discover_homogeneous_aggregate now accepts FFI_TYPE_LONGDOUBLE as a + _Complex inner type so struct-of-complex-longdouble is treated as an HFA. + Covers ffi_prep_cif, ffi_prep_args64, and the closure decode/return + paths. + + Fixes #1001. + + Co-Authored-By: Claude Opus 4.8 (1M context) + +commit debc00a0114d8530d6d691862028c607aa17dd6a +Author: Anthony Green +Date: Sun Jul 26 07:28:36 2026 -0400 + + Update doc version + +commit 19dbdb53e869e07fbff05c86d634e8c08c9a7f61 +Author: Eduardo Speroni +Date: Tue Jul 21 20:32:24 2026 -0300 + + testsuite: fix vector suite CI failures on gcc and MSVC + + Two fixes for the libffi.vector suite: + + - vector_double4.c: the non-aarch64 branch built its own void_args + array and never read the already-populated args, tripping gcc's + -Wunused-but-set-variable (an excess-errors FAIL on Linux x86-64 + with gcc; clang does not emit this warning). Use args for the + negative argument-passing check instead. + + - vector.exp: the suite only probed FFI_TARGET_HAS_VECTOR_TYPE, but + the tests are written with the GCC/Clang vector extension. On + Windows ARM64 the aarch64 port enables the feature while MSVC + cannot compile __attribute__ ((vector_size)), so every test failed + to build. Add a compile probe and mark the suite unsupported when + the compiler lacks the syntax. + +commit 71a95a2cd433b151b3fcf83a0a830eb9aa38fa3a +Author: Eduardo Speroni +Date: Tue Jul 21 16:44:26 2026 -0300 + + testsuite: add libffi.vector suite for vector (SIMD) types + + Model a new testsuite/libffi.vector/ directory on testsuite/libffi.complex: + vector.exp reuses the same dg/run-many-tests driver and skips every test as + "unsupported" on ports whose headers do not define + FFI_TARGET_HAS_VECTOR_TYPE (libffi_feature_test), so unsupported targets + still compile the gating cleanly. + + Vector types are built with the portable __attribute__((vector_size)) via a + small make_vector_type() helper (vector.h); each test cross-checks the value + returned through ffi against a direct native call. Coverage: + + - vector_float32x4 / vector_float32x2 / vector_double2 / vector_int32x4: + pass and return 8- and 16-byte float, double and integer vectors + (float32x4 is the vec4 shape of libffi/libffi#773); + - vector_args_spill: ten vectors interleaved with int/double scalars, + exhausting the vector argument registers and spilling to the stack; + - vector_vec3: Clang-only ext_vector_type(3), verifying the 12->16 byte + power-of-two padding matches a natively compiled callee (a no-op on + other compilers); + - vector_double4: on AArch64 a 32-byte vector round-trips (by reference / + in memory); elsewhere ffi_prep_cif must return FFI_BAD_TYPEDEF, checked + for both return and argument; + - vector_hva: a struct of two identical vectors (HVA) passes and returns + on both AArch64 (Q-register pair) and x86-64 (SSE struct classification); + - cls_vector: a closure receiving vector arguments and returning a vector; + - vector_validate: heterogeneous lanes, an empty vector, and a non-scalar + lane are each rejected with FFI_BAD_TYPEDEF, and a well-formed vector is + accepted with the computed power-of-two size and min(size,16) alignment. + + The files are added to testsuite/Makefile.am EXTRA_DIST, matching how + libffi.complex is distributed. + + References: libffi/libffi#414, libffi/libffi#773. + +commit 93b274cec912ac2395575a8bd4dfcb527f61599c +Author: Eduardo Speroni +Date: Tue Jul 21 16:32:04 2026 -0300 + + x86-64: marshal vector (SIMD) types per the System V psABI + + Define FFI_TARGET_HAS_VECTOR_TYPE for the SysV x86-64 backend (ffi64.c; + 32-bit x86 and the Windows ffiw64.c backend are excluded) and integrate + FFI_TYPE_VECTOR into the existing psABI classifier without restructuring + it: + + - classify_argument gains a FFI_TYPE_VECTOR case: an 8-byte vector is + one SSE eightbyte (X86_64_SSE_CLASS); a 16-byte vector is one %xmm + register (X86_64_SSE_CLASS + X86_64_SSEUP_CLASS). The existing + INTEGERSI/SSESF/SSEDF/UINT128 handling is untouched, and the SSE+SSEUP + argument marshalling already merges both eightbytes into one %xmm. + - ffi_prep_cif_machdep classifies vector returns symmetrically: 8 bytes + in %xmm0 (UNIX64_RET_XMM64), 16 bytes in %xmm0 (UNIX64_RET_XMM128). + - Vectors wider than 16 bytes return FFI_BAD_TYPEDEF from + ffi_prep_cif_machdep, for both returns and arguments. Correct + %ymm/%zmm passing needs unix64.S register-save changes and is left as + a v1 limitation rather than silently passing them in memory. + + Closures need no separate change: the closure paths reuse + classify_argument for arguments and cif->flags for the return. + + References: libffi/libffi#414. + +commit 5eaa8a389de61fc3b056f62c48ceade1931b5413 +Author: Eduardo Speroni +Date: Tue Jul 21 16:30:12 2026 -0300 + + aarch64: marshal vector (SIMD) types per AAPCS64 + + Define FFI_TARGET_HAS_VECTOR_TYPE for AArch64 and teach is_vfp_type to + classify FFI_TYPE_VECTOR, so ffi_call and closures pass and return + vectors the way AAPCS64 (and current GCC/Clang) do: + + - 8- and 16-byte vectors travel in a single V/Q register (a Short + Vector), for float, double and integer lane types alike; + - homogeneous vector aggregates -- a struct of up to four identical + 8- or 16-byte vectors -- travel in that many consecutive V/Q + registers (an HVA), e.g. struct{float32x4 a,b} in {q0,q1}; + - a bare vector wider than 16 bytes (e.g. a 32-byte double4) has no + short-vector register class, so is_vfp_type returns 0 and the + existing composite path passes it by invisible reference and returns + it in memory -- exactly what a natively compiled callee expects. + + is_simd() reports the width of one Neon register slot (a bare vector's + whole size, or one lane vector of an HVA); is_vfp_type() encodes + num_registers slots of that width onto the existing AARCH64_RET_{D,Q}* + codes via intlog2. is_hfa0/is_hfa1 recurse through FFI_TYPE_VECTOR so + HVA homogeneity is checked, and the three fundamental-type dispatch + switches (machdep return, ffi_call_int, ffi_closure_SYSV_inner) route + FFI_TYPE_VECTOR through is_vfp_type alongside FFI_TYPE_STRUCT. + + Ported from the battle-tested NativeScript aarch64 vector marshaller, + adapted to the FFI_TYPE_VECTOR API and extended so that integer-lane + vectors (e.g. int32x4) are classified into V registers too -- the + original only handled floating-point lanes. + + References: libffi/libffi#414, libffi/libffi#773 (aarch64 vec4 return). + +commit b6b8be54acc90f7db1dcf3d1c91238a5a9bca185 +Author: Eduardo Speroni +Date: Tue Jul 21 16:26:33 2026 -0300 + + core: add FFI_TYPE_VECTOR fundamental type with computed layout + + Introduce a portable API for marshalling vector (SIMD) types -- the + values produced by GCC's __attribute__((vector_size)) and Clang's + ext_vector_type. This answers the stalled PR #414 and the maintainer's + 2018 design questions + (https://sourceware.org/legacy-ml/libffi-discuss/2018/msg00020.html): + rather than requiring callers to hand-compute a vector's size and + alignment (and gating the feature behind configure), libffi now derives + the layout itself and the type code is defined unconditionally. + + A vector is described exactly like a struct: type == FFI_TYPE_VECTOR and + a NULL-terminated elements[] array, except every element must point to + the SAME fundamental scalar (float, double, or a fixed-width integer + UINT8..SINT64) and the count is the number of lanes. The caller leaves + size and alignment at zero; ffi_prep_cif computes: + + size = lane_size * count, rounded up to the next power of two + (matches Clang ext_vector_type storage: 3 x float -> 16, + 3 x double -> 32; GCC vector_size already requires pow2 + totals so it is identical there); + alignment = min(size, 16). + + Validation (identical scalar lanes, count >= 1, scalar-only) yields + FFI_BAD_TYPEDEF otherwise. + + - include/ffi.h.in: FFI_TYPE_VECTOR = 18 (after SINT128 = 17), + FFI_TYPE_LAST bumped. Defined unconditionally, no configure gating. + - src/prep_cif.c: initialize_vector() computes the layout in + initialize_aggregate; ffi_type_contains_vector() rejects vectors + (including nested in structs, argument or return) with + FFI_BAD_TYPEDEF on any port that does not define + FFI_TARGET_HAS_VECTOR_TYPE -- no aborts. Vector returns reserve the + hidden return-pointer slot like structs. + - src/raw_api.c, src/java_raw_api.c: plumb FFI_TYPE_VECTOR alongside + FFI_TYPE_STRUCT, mirroring how FFI_TYPE_COMPLEX is handled. + - src/debug.c: ffi_type_test requires elements != NULL for vectors. + - src/pa/ffitarget.h: bump the FFI_PA_TYPE_LAST tripwire; PA gates + vectors out in prep_cif so its jump tables are never reached. + - doc/libffi.texi: new "Vector Types" node documenting the API, the + computed-layout rule, the psABI framing, and the per-port support + table. + + No port defines FFI_TARGET_HAS_VECTOR_TYPE yet, so this commit rejects + every vector signature; the per-architecture ports follow. + + References: libffi/libffi#414, libffi/libffi#773. + +commit 46cb2e3871059f7f5113329ddcca818de3a8cfae +Merge: ca86812c 8cd11a77 +Author: Anthony Green +Date: Fri Jul 10 16:51:18 2026 -0400 + + Merge pull request #998 from bgilbert/tests + + testsuite: Remember to distribute tests added for 3.7.1 + +commit 8cd11a772d8a0b687f43390697baa002ae6504d5 +Author: Benjamin Gilbert +Date: Fri Jul 10 11:56:55 2026 -0700 + + testsuite: Remember to distribute tests added for 3.7.1 + +commit ca86812cd430cff3018e491ba75a4f3c9ea969d2 +Author: Anthony Green +Date: Fri Jul 10 10:56:47 2026 -0400 + + ci: Don't publish rlgl reports on tag pushes + + A tag and its commit fire two CI runs at the same SHA. Both run the + publish-reports job, which deploys a fixed-name github-pages artifact + via actions/deploy-pages; the two deployments collide and one fails + with BlobNotFound (seen on the v3.7.1 tag run). The same-SHA branch + push already publishes the reports, so gate the job off tag pushes. + + Co-Authored-By: Claude Fable 5 + commit 5c1c43091ed611fdea774374355eb938c73a9157 Author: Anthony Green Date: Fri Jul 10 09:50:53 2026 -0400 diff --git a/deps/libffi/README.md b/deps/libffi/README.md index 19f78f632b0c..797d0fd9aa8e 100644 --- a/deps/libffi/README.md +++ b/deps/libffi/README.md @@ -201,6 +201,28 @@ History See the git log for details at http://github.com/libffi/libffi. + 3.8.0 August-8-2026 + Add FFI_TYPE_VECTOR (SIMD) type support with libffi-computed + layout, for aarch64 and x86-64 (#1000, closes #773). + Add ffi_call_plan_size to report the total memory a reusable call + plan owns, for embedders that account for the memory held by + long-lived plans. + Add powerpc64 ELFv2 _Complex long double support for both + IBM-128 (double-double) and IEEE-128 formats (#1003, closes #1001). + Fix powerpc64 big-endian ELFv2 closures returning 5-, 6-, or + 7-byte structs: missing return jump-table entries produced a + wrong result and leaked a libffi code pointer. + Fix ia64 return-value jump-table desync after the FFI_TYPE_LAST + bump, which corrupted small-struct and HFA returns. + Fix powerpc Darwin closure returns broken by #951 (#1002). + Return small (1, 2, 4 or 8 byte) structs in registers on the i386 + FreeBSD and OpenBSD targets, matching the platform ABI and + fixing a segfault on struct returns through ffi_call and closures. + Cache the static trampoline "unsupported" result on hosts whose + page size exceeds the trampoline table mapping, avoiding + redundant re-initialization on every closure allocation + (e.g. 64K-page aarch64). + 3.7.1 July-10-2026 Fix aarch64 ffi_call memory corruption when passing many large structs by value. diff --git a/deps/libffi/configure b/deps/libffi/configure index e050ddc8675d..7b8a4cb375d1 100755 --- a/deps/libffi/configure +++ b/deps/libffi/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for libffi 3.7.1. +# Generated by GNU Autoconf 2.71 for libffi 3.8.0. # # Report bugs to . # @@ -621,8 +621,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='libffi' PACKAGE_TARNAME='libffi' -PACKAGE_VERSION='3.7.1' -PACKAGE_STRING='libffi 3.7.1' +PACKAGE_VERSION='3.8.0' +PACKAGE_STRING='libffi 3.8.0' PACKAGE_BUGREPORT='http://github.com/libffi/libffi/issues' PACKAGE_URL='' @@ -1417,7 +1417,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures libffi 3.7.1 to adapt to many kinds of systems. +\`configure' configures libffi 3.8.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1489,7 +1489,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libffi 3.7.1:";; + short | recursive ) echo "Configuration of libffi 3.8.0:";; esac cat <<\_ACEOF @@ -1628,7 +1628,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libffi configure 3.7.1 +libffi configure 3.8.0 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2259,7 +2259,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libffi $as_me 3.7.1, which was +It was created by libffi $as_me 3.8.0, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3233,10 +3233,10 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers fficonfig.h" -FFI_VERSION_STRING="3.7.1" -ffi_version_major=`echo "3.7.1" | cut -d. -f1` -ffi_version_minor=`echo "3.7.1" | cut -d. -f2 | sed 's/[^0-9].*//'` -ffi_version_micro=`echo "3.7.1" | cut -d. -f3 | sed 's/[^0-9].*//'` +FFI_VERSION_STRING="3.8.0" +ffi_version_major=`echo "3.8.0" | cut -d. -f1` +ffi_version_minor=`echo "3.8.0" | cut -d. -f2 | sed 's/[^0-9].*//'` +ffi_version_micro=`echo "3.8.0" | cut -d. -f3 | sed 's/[^0-9].*//'` FFI_VERSION_NUMBER=`expr ${ffi_version_major:-0} \* 10000 + ${ffi_version_minor:-0} \* 100 + ${ffi_version_micro:-0}` @@ -3986,7 +3986,7 @@ fi # Define the identity of the package. PACKAGE='libffi' - VERSION='3.7.1' + VERSION='3.8.0' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -20661,7 +20661,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by libffi $as_me 3.7.1, which was +This file was extended by libffi $as_me 3.8.0, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -20729,7 +20729,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -libffi config.status 3.7.1 +libffi config.status 3.8.0 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/deps/libffi/configure.ac b/deps/libffi/configure.ac index 3370acc3a396..826b453b8353 100644 --- a/deps/libffi/configure.ac +++ b/deps/libffi/configure.ac @@ -2,7 +2,7 @@ dnl Process this with autoconf to create configure AC_PREREQ([2.68]) -AC_INIT([libffi],[3.7.1],[http://github.com/libffi/libffi/issues]) +AC_INIT([libffi],[3.8.0],[http://github.com/libffi/libffi/issues]) AC_CONFIG_HEADERS([fficonfig.h]) dnl Derive the version macros from AC_INIT so they cannot drift when the diff --git a/deps/libffi/doc/libffi.info b/deps/libffi/doc/libffi.info index b43246f8ed4e..4c1abc85f777 100644 --- a/deps/libffi/doc/libffi.info +++ b/deps/libffi/doc/libffi.info @@ -301,6 +301,7 @@ File: libffi.info, Node: Types, Next: Multiple ABIs, Prev: Simple Example, U * Type Example:: Structure type example. * Complex:: Complex types. * Complex Type Example:: Complex type example. +* Vector Types:: Vector (SIMD) types.  File: libffi.info, Node: Primitive Types, Next: Structures, Up: Types @@ -660,7 +661,7 @@ functions ‘ffi_prep_cif’ and ‘ffi_prep_args’ abort the program if they encounter a complex type.  -File: libffi.info, Node: Complex Type Example, Prev: Complex, Up: Types +File: libffi.info, Node: Complex Type Example, Next: Vector Types, Prev: Complex, Up: Types 2.3.7 Complex Type Example -------------------------- @@ -746,6 +747,92 @@ compilers that support them: The new type descriptors can then be used like one of the built-in type descriptors in the previous example. + +File: libffi.info, Node: Vector Types, Prev: Complex Type Example, Up: Types + +2.3.8 Vector Types +------------------ + +‘libffi’ can marshal vector (SIMD) types -- the values produced by GCC's +‘__attribute__((vector_size (N)))’ and Clang's ‘ext_vector_type’ -- on +the platforms listed in the support table below. A vector is described +just like a structure, except that every element pointer refers to the +_same_ fundamental scalar type and the number of elements is the number +of vector lanes. + + -- Data type: ffi_type + ‘size_t size’ + This must be set to ‘0’. ‘libffi’ computes the storage size + (see below) from the element type and lane count. + + ‘unsigned short alignment’ + This must be set to ‘0’. ‘libffi’ computes the alignment. + + ‘unsigned short type’ + For a vector type, this must be set to ‘FFI_TYPE_VECTOR’. + + ‘ffi_type **elements’ + This is a ‘NULL’-terminated array of pointers to ‘ffi_type’ + objects. Every entry must point to the same scalar element + type, and the number of entries is the vector's lane count N + (N >= 1). The element type must be one of ‘ffi_type_float’, + ‘ffi_type_double’, or a fixed-width integer (‘ffi_type_uint8’ + through ‘ffi_type_sint64’); ‘long double’ and aggregate + element types are not permitted. + +Computed layout +............... + +Because the caller leaves ‘size’ and ‘alignment’ at ‘0’, ‘libffi’ +derives them so that applications need not encode compiler- or +platform-specific rules: + + • ‘size’ is lane\_size \times N rounded _up_ to the next power of + two. This matches Clang's ‘ext_vector_type’ storage -- for example + a three-lane ‘float’ vector occupies 16 bytes and a three-lane + ‘double’ vector occupies 32 bytes. GCC's ‘vector_size’ already + requires power-of-two byte totals, so the rule is identical there. + + • ‘alignment’ is ‘min(size, 16)’. + + If the element list is heterogeneous, empty, or uses a disallowed +element type, ‘ffi_prep_cif’ returns ‘FFI_BAD_TYPEDEF’. + +psABI framing +............. + +At the call boundary the platform's processor-specific ABI (AAPCS64 on +AArch64, the System V x86-64 psABI on x86-64) decides how a vector is +passed and returned, independently of which compiler produced it. The +historical divergence between GCC's ‘vector_size’ and Clang's +‘ext_vector_type’ concerns only in-memory _layout_ (notably the padding +of odd-lane vectors such as ‘float3’); the power-of-two size rule above +pins that layout down, so a value marshalled by ‘libffi’ matches what a +natively compiled caller or callee expects. + +Per-port support +................ + +Port Vector support +-------------------------------------------------------------------------- +AArch64 (AAPCS64) 8- and 16-byte vectors in a single V/Q register; + homogeneous vector aggregates (structs of up to + four identical 8- or 16-byte vectors) in + consecutive V/Q registers. A bare vector larger + than 16 bytes (for example a 32-byte ‘double4’) + has no short-vector register class and is passed + and returned in memory, exactly as AAPCS64 and + current compilers do. +x86-64 (System V 8- and 16-byte vectors in an SSE register (‘%xmm0’ +psABI) for returns). A bare vector larger than 16 bytes + needs ‘%ymm’/‘%zmm’ register handling that this + port does not yet implement, so ‘ffi_prep_cif’ + returns ‘FFI_BAD_TYPEDEF’ for it. +Other ports Not supported: ‘ffi_prep_cif’ returns + ‘FFI_BAD_TYPEDEF’ for any signature that mentions + a vector type, including one nested inside a + struct. +  File: libffi.info, Node: Multiple ABIs, Next: Reusable Call Plans, Prev: Types, Up: Using libffi @@ -795,6 +882,14 @@ prepared ‘ffi_cif’. is harmless. The ‘ffi_cif’ the plan was built from is not affected. + -- Function: size_t ffi_call_plan_size (ffi_call_plan *PLAN) + Returns the total number of bytes ‘libffi’ allocated for PLAN, + including any internal argument-placement data it owns. Returns + zero when PLAN is ‘NULL’. The result does not include the + ‘ffi_cif’, which the caller owns. This is intended for embedders + that account for the memory held by long-lived plans and would + otherwise have to guess at the size of an opaque type. +  File: libffi.info, Node: The Closure API, Next: Closure Example, Prev: Reusable Call Plans, Up: Using libffi @@ -1056,6 +1151,7 @@ Index * ffi_call_plan_alloc: Reusable Call Plans. (line 12) * ffi_call_plan_free: Reusable Call Plans. (line 32) * ffi_call_plan_invoke: Reusable Call Plans. (line 22) +* ffi_call_plan_size: Reusable Call Plans. (line 37) * ffi_closure_alloc: The Closure API. (line 19) * ffi_closure_free: The Closure API. (line 26) * FFI_CLOSURES: The Closure API. (line 13) @@ -1075,6 +1171,8 @@ Index * ffi_type <1>: Structures. (line 10) * ffi_type <2>: Complex. (line 15) * ffi_type <3>: Complex. (line 15) +* ffi_type <4>: Vector Types. (line 13) +* ffi_type <5>: Vector Types. (line 13) * ffi_type_complex_double: Primitive Types. (line 82) * ffi_type_complex_float: Primitive Types. (line 79) * ffi_type_complex_longdouble: Primitive Types. (line 85) @@ -1101,6 +1199,7 @@ Index * ffi_type_void: Primitive Types. (line 10) * Foreign Function Interface: Introduction. (line 31) * size_t: The Basics. (line 125) +* size_t <1>: Reusable Call Plans. (line 37) * unsigned int: The Basics. (line 122) * unsigned long: The Basics. (line 117) * void: The Basics. (line 72) @@ -1118,21 +1217,22 @@ Node: Using libffi4569 Node: The Basics5172 Node: Simple Example11346 Node: Types12403 -Node: Primitive Types12914 -Node: Structures15231 -Node: Size and Alignment16342 -Node: Arrays Unions Enums18613 -Node: Type Example21590 -Node: Complex22896 -Node: Complex Type Example24410 -Node: Multiple ABIs27462 -Node: Reusable Call Plans27849 -Node: The Closure API29566 -Node: Closure Example33908 -Node: Thread Safety35552 -Node: Memory Usage36385 -Node: Missing Features37660 -Node: Index38037 +Node: Primitive Types12967 +Node: Structures15284 +Node: Size and Alignment16395 +Node: Arrays Unions Enums18666 +Node: Type Example21643 +Node: Complex22949 +Node: Complex Type Example24463 +Node: Vector Types27536 +Node: Multiple ABIs31575 +Node: Reusable Call Plans31962 +Node: The Closure API34155 +Node: Closure Example38497 +Node: Thread Safety40141 +Node: Memory Usage40974 +Node: Missing Features42249 +Node: Index42626  End Tag Table diff --git a/deps/libffi/doc/libffi.pdf b/deps/libffi/doc/libffi.pdf index 250d34faf711..75458a8f7d5d 100644 Binary files a/deps/libffi/doc/libffi.pdf and b/deps/libffi/doc/libffi.pdf differ diff --git a/deps/libffi/doc/libffi.texi b/deps/libffi/doc/libffi.texi index 251214f890d3..4d2802c5bd97 100644 --- a/deps/libffi/doc/libffi.texi +++ b/deps/libffi/doc/libffi.texi @@ -320,6 +320,7 @@ int main() * Type Example:: Structure type example. * Complex:: Complex types. * Complex Type Example:: Complex type example. +* Vector Types:: Vector (SIMD) types. @end menu @node Primitive Types @@ -802,6 +803,93 @@ FFI_COMPLEX_TYPEDEF(uchar, unsigned char, ffi_type_uint8); The new type descriptors can then be used like one of the built-in type descriptors in the previous example. +@node Vector Types +@subsection Vector Types + +@code{libffi} can marshal vector (SIMD) types --- the values produced +by GCC's @code{__attribute__((vector_size (N)))} and Clang's +@code{ext_vector_type} --- on the platforms listed in the support table +below. A vector is described just like a structure, except that every +element pointer refers to the @emph{same} fundamental scalar type and the +number of elements is the number of vector lanes. + +@tindex ffi_type +@deftp {Data type} ffi_type +@table @code +@item size_t size +This must be set to @code{0}. @code{libffi} computes the storage size +(see below) from the element type and lane count. + +@item unsigned short alignment +This must be set to @code{0}. @code{libffi} computes the alignment. + +@item unsigned short type +For a vector type, this must be set to @code{FFI_TYPE_VECTOR}. + +@item ffi_type **elements +This is a @samp{NULL}-terminated array of pointers to @code{ffi_type} +objects. Every entry must point to the same scalar element type, and the +number of entries is the vector's lane count @math{N} (@math{N >= 1}). The +element type must be one of @code{ffi_type_float}, @code{ffi_type_double}, +or a fixed-width integer (@code{ffi_type_uint8} through +@code{ffi_type_sint64}); @code{long double} and aggregate element types are +not permitted. +@end table +@end deftp + +@subsubheading Computed layout + +Because the caller leaves @code{size} and @code{alignment} at @code{0}, +@code{libffi} derives them so that applications need not encode +compiler- or platform-specific rules: + +@itemize @bullet +@item +@code{size} is @math{lane\_size \times N} rounded @emph{up} to the next +power of two. This matches Clang's @code{ext_vector_type} storage --- for +example a three-lane @code{float} vector occupies 16 bytes and a three-lane +@code{double} vector occupies 32 bytes. GCC's @code{vector_size} already +requires power-of-two byte totals, so the rule is identical there. + +@item +@code{alignment} is @code{min(size, 16)}. +@end itemize + +If the element list is heterogeneous, empty, or uses a disallowed element +type, @code{ffi_prep_cif} returns @code{FFI_BAD_TYPEDEF}. + +@subsubheading psABI framing + +At the call boundary the platform's processor-specific ABI (AAPCS64 on +AArch64, the System V x86-64 psABI on x86-64) decides how a vector is +passed and returned, independently of which compiler produced it. The +historical divergence between GCC's @code{vector_size} and Clang's +@code{ext_vector_type} concerns only in-memory @emph{layout} (notably the +padding of odd-lane vectors such as @code{float3}); the power-of-two size +rule above pins that layout down, so a value marshalled by @code{libffi} +matches what a natively compiled caller or callee expects. + +@subsubheading Per-port support + +@multitable @columnfractions .28 .72 +@headitem Port @tab Vector support +@item AArch64 (AAPCS64) +@tab 8- and 16-byte vectors in a single V/Q register; homogeneous vector +aggregates (structs of up to four identical 8- or 16-byte vectors) in +consecutive V/Q registers. A bare vector larger than 16 bytes (for +example a 32-byte @code{double4}) has no short-vector register class and is +passed and returned in memory, exactly as AAPCS64 and current compilers do. +@item x86-64 (System V psABI) +@tab 8- and 16-byte vectors in an SSE register (@code{%xmm0} for returns). +A bare vector larger than 16 bytes needs @code{%ymm}/@code{%zmm} register +handling that this port does not yet implement, so @code{ffi_prep_cif} +returns @code{FFI_BAD_TYPEDEF} for it. +@item Other ports +@tab Not supported: @code{ffi_prep_cif} returns @code{FFI_BAD_TYPEDEF} for +any signature that mentions a vector type, including one nested inside a +struct. +@end multitable + @node Multiple ABIs @section Multiple ABIs @@ -853,6 +941,16 @@ Releases a plan returned by @code{ffi_call_plan_alloc}. Passing not affected. @end defun +@findex ffi_call_plan_size +@defun size_t ffi_call_plan_size (ffi_call_plan *@var{plan}) +Returns the total number of bytes @code{libffi} allocated for @var{plan}, +including any internal argument-placement data it owns. Returns zero when +@var{plan} is @code{NULL}. The result does not include the +@code{ffi_cif}, which the caller owns. This is intended for embedders that +account for the memory held by long-lived plans and would otherwise have to +guess at the size of an opaque type. +@end defun + @node The Closure API @section The Closure API diff --git a/deps/libffi/doc/stamp-vti b/deps/libffi/doc/stamp-vti index e755454e9c82..dc281e38e0f6 100644 --- a/deps/libffi/doc/stamp-vti +++ b/deps/libffi/doc/stamp-vti @@ -1,4 +1,4 @@ -@set UPDATED 10 July 2026 -@set UPDATED-MONTH July 2026 -@set EDITION 3.7.1 -@set VERSION 3.7.1 +@set UPDATED 8 August 2026 +@set UPDATED-MONTH August 2026 +@set EDITION 3.8.0 +@set VERSION 3.8.0 diff --git a/deps/libffi/doc/version.texi b/deps/libffi/doc/version.texi index e755454e9c82..dc281e38e0f6 100644 --- a/deps/libffi/doc/version.texi +++ b/deps/libffi/doc/version.texi @@ -1,4 +1,4 @@ -@set UPDATED 10 July 2026 -@set UPDATED-MONTH July 2026 -@set EDITION 3.7.1 -@set VERSION 3.7.1 +@set UPDATED 8 August 2026 +@set UPDATED-MONTH August 2026 +@set EDITION 3.8.0 +@set VERSION 3.8.0 diff --git a/deps/libffi/generate-headers.py b/deps/libffi/generate-headers.py index e2d2942deffb..fb58edf17b66 100644 --- a/deps/libffi/generate-headers.py +++ b/deps/libffi/generate-headers.py @@ -7,8 +7,8 @@ from pathlib import Path -LIBFFI_VERSION = '3.7.1' -LIBFFI_VERSION_NUMBER = '30701' +LIBFFI_VERSION = '3.8.0' +LIBFFI_VERSION_NUMBER = '30800' def normalize_arch(target_arch): aliases = { diff --git a/deps/libffi/include/ffi.h.in b/deps/libffi/include/ffi.h.in index 35f09cf43315..cb0a7dbcaaa3 100644 --- a/deps/libffi/include/ffi.h.in +++ b/deps/libffi/include/ffi.h.in @@ -79,9 +79,10 @@ extern "C" { #define FFI_TYPE_COMPLEX 15 #define FFI_TYPE_UINT128 16 #define FFI_TYPE_SINT128 17 +#define FFI_TYPE_VECTOR 18 /* This should always refer to the last type code (for sanity checks). */ -#define FFI_TYPE_LAST FFI_TYPE_SINT128 +#define FFI_TYPE_LAST FFI_TYPE_VECTOR #include @@ -535,7 +536,11 @@ void ffi_call(ffi_cif *cif, ffi_call_plan_alloc returns NULL only on allocation failure; a signature with no fast path is still valid and ffi_call_plan_invoke falls back to ffi_call for it. A plan is immutable once built, so it may be shared and - invoked concurrently from multiple threads. */ + invoked concurrently from multiple threads. + + ffi_call_plan_size reports the total number of bytes libffi allocated for a + plan, so that callers tracking the footprint of long-lived plans do not have + to guess at the size of an opaque type. */ typedef struct ffi_call_plan ffi_call_plan; FFI_API @@ -550,6 +555,9 @@ void ffi_call_plan_invoke (ffi_call_plan *plan, FFI_API void ffi_call_plan_free (ffi_call_plan *plan); +FFI_API +size_t ffi_call_plan_size (ffi_call_plan *plan); + FFI_API ffi_status ffi_get_struct_offsets (ffi_abi abi, ffi_type *struct_type, size_t *offsets); diff --git a/deps/libffi/libffi.map.in b/deps/libffi/libffi.map.in index f4e366eb60bc..6151f10cbdce 100644 --- a/deps/libffi/libffi.map.in +++ b/deps/libffi/libffi.map.in @@ -69,6 +69,15 @@ LIBFFI_CALL_PLAN_8.4 { ffi_call_plan_free; } LIBFFI_BASE_8.1; +/* ---------------------------------------------------------------------- + Call plan footprint query (ffi_call_plan_size). A fresh node because + LIBFFI_CALL_PLAN_8.4 has already shipped. + -------------------------------------------------------------------- */ +LIBFFI_CALL_PLAN_8.5 { + global: + ffi_call_plan_size; +} LIBFFI_CALL_PLAN_8.4; + #ifdef FFI_TARGET_HAS_COMPLEX_TYPE LIBFFI_COMPLEX_8.0 { global: diff --git a/deps/libffi/libtool-version b/deps/libffi/libtool-version index c5545eab8bf6..814c6e21d925 100644 --- a/deps/libffi/libtool-version +++ b/deps/libffi/libtool-version @@ -26,4 +26,4 @@ # release, then set age to 0. # # CURRENT:REVISION:AGE -12:1:4 +13:0:5 diff --git a/deps/libffi/src/aarch64/ffi.c b/deps/libffi/src/aarch64/ffi.c index 2e6a2ad2624c..1eb90dd565ad 100644 --- a/deps/libffi/src/aarch64/ffi.c +++ b/deps/libffi/src/aarch64/ffi.c @@ -92,6 +92,19 @@ ffi_clear_cache (void *start, void *end) #endif +/* Return the base-2 logarithm of N (N assumed to be a power of two). Used + to map a vector register width (8 or 16 bytes) onto the D-/Q-register + AARCH64_RET_* encoding. */ + +static int +intlog2 (int n) +{ + int level = 0; + while (n >>= 1) + ++level; + return level; +} + /* A subroutine of is_vfp_type. Given a structure type, return the type code of the first non-structure element. Recurse for structure elements. Return -1 if the structure is in fact empty, i.e. no nested elements. */ @@ -106,7 +119,8 @@ is_hfa0 (const ffi_type *ty) for (i = 0; elements[i]; ++i) { ret = elements[i]->type; - if (ret == FFI_TYPE_STRUCT || ret == FFI_TYPE_COMPLEX) + if (ret == FFI_TYPE_STRUCT || ret == FFI_TYPE_VECTOR + || ret == FFI_TYPE_COMPLEX) { ret = is_hfa0 (elements[i]); if (ret < 0) @@ -118,6 +132,33 @@ is_hfa0 (const ffi_type *ty) return ret; } +/* A subroutine of is_vfp_type. Return the size in bytes of the vector (SIMD) + member of TY, i.e. the width of a single Neon register slot, or 0 if TY + neither is nor contains a vector. For a bare vector this is its whole size; + for a homogeneous vector aggregate it is the size of one lane vector. */ + +static size_t +is_simd (const ffi_type *ty) +{ + ffi_type **elements; + int i; + + if (ty->type == FFI_TYPE_VECTOR) + return ty->size; + + elements = ty->elements; + if (elements != NULL) + for (i = 0; elements[i]; ++i) + { + int t = elements[i]->type; + if (t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX + || t == FFI_TYPE_VECTOR) + return is_simd (elements[i]); + } + + return 0; +} + /* A subroutine of is_vfp_type. Given a structure type, return true if all of the non-structure elements are the same as CANDIDATE. */ @@ -131,7 +172,8 @@ is_hfa1 (const ffi_type *ty, int candidate) for (i = 0; elements[i]; ++i) { int t = elements[i]->type; - if (t == FFI_TYPE_STRUCT || t == FFI_TYPE_COMPLEX) + if (t == FFI_TYPE_STRUCT || t == FFI_TYPE_VECTOR + || t == FFI_TYPE_COMPLEX) { if (!is_hfa1 (elements[i], candidate)) return 0; @@ -156,7 +198,7 @@ is_vfp_type (const ffi_type *ty) { ffi_type **elements; int candidate, i; - size_t size, ele_count; + size_t size, ele_count, simd_size; /* Quickest tests first. */ candidate = ty->type; @@ -181,18 +223,24 @@ is_vfp_type (const ffi_type *ty) } return 0; case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: break; } - /* No HFA types are smaller than 4 bytes, or larger than 64 bytes. */ + /* No HFA/HVA types are smaller than 4 bytes, or larger than 64 bytes. */ size = ty->size; if (size < 4 || size > 64) return 0; - /* Find the type of the first non-structure member. */ + /* Determine the width of the vector (SIMD) member, if any: 0 for a plain + floating-point HFA, else the size in bytes of one Neon register slot. */ + simd_size = is_simd (ty); + + /* Find the type of the first non-aggregate member. */ elements = ty->elements; candidate = elements[0]->type; - if (candidate == FFI_TYPE_STRUCT || candidate == FFI_TYPE_COMPLEX) + if (candidate == FFI_TYPE_STRUCT || candidate == FFI_TYPE_VECTOR + || candidate == FFI_TYPE_COMPLEX) { for (i = 0; ; ++i) { @@ -202,6 +250,63 @@ is_vfp_type (const ffi_type *ty) } } + if (simd_size) + { + /* Vector or homogeneous vector aggregate (HVA). A single Neon slot is + at most 16 bytes (a Q register). A bare vector wider than 16 bytes + (e.g. a 32-byte double4) has no short-vector register class under + AAPCS64, so bail and let the generic composite path pass it by + reference / return it in memory -- matching what current compilers do. + The scalar lane type does not affect register selection (an integer + and a floating-point 16-byte vector both occupy one Q register), so, + unlike the floating-point HFA path below, CANDIDATE is used only to + confirm the lanes are homogeneous. */ + size_t reg_size = simd_size; + int num_registers; + int first_level_element_type; + + /* A Neon register slot is an S (4B), D (8B) or Q (16B). A lane narrower + than 4 bytes has no short-vector register class under AAPCS64 and would + map below AARCH64_RET_S4, making extend_hfa_type() branch before its + jump table; reject it and let the generic aggregate path handle it. */ + if (reg_size < 4 || reg_size > 16 || size % reg_size != 0) + return 0; + num_registers = (int) (size / reg_size); + if (num_registers > 4) + return 0; + + /* For an aggregate, every member must itself be a vector (or nested + vector aggregate) of the same register width: this rejects a struct + that mixes a bare scalar with a vector even when the scalar's type + matches the vector's lane type. A bare vector needs no such check -- + its lanes were validated when its layout was computed. */ + if (ty->type != FFI_TYPE_VECTOR) + for (i = 0; elements[i]; ++i) + if (is_simd (elements[i]) != reg_size) + return 0; + + /* Every lane must be the identical scalar type across the whole HVA + (this rejects, e.g., an aggregate mixing float and integer vectors). */ + for (i = 0; elements[i]; ++i) + { + int t = elements[i]->type; + if (t == FFI_TYPE_STRUCT || t == FFI_TYPE_VECTOR + || t == FFI_TYPE_COMPLEX) + { + if (!is_hfa1 (elements[i], candidate)) + return 0; + } + else if (t != candidate) + return 0; + } + + /* Reuse the AARCH64_RET_{S,D,Q}* codes, which are laid out as + (type * 4) + (4 - count) with FLOAT->S(4B), DOUBLE->D(8B), + LONGDOUBLE->Q(16B). Map the register width onto that type axis. */ + first_level_element_type = FFI_TYPE_FLOAT + intlog2 ((int) reg_size) - 2; + return first_level_element_type * 4 + (4 - num_registers); + } + /* If the first member is not a floating point type, it's not an HFA. Also quickly re-check the size of the structure. */ switch (candidate) @@ -614,6 +719,7 @@ ffi_prep_cif_machdep (ffi_cif *cif) case FFI_TYPE_DOUBLE: case FFI_TYPE_LONGDOUBLE: case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: case FFI_TYPE_COMPLEX: flags = is_vfp_type (rtype); if (flags == 0) @@ -802,6 +908,7 @@ ffi_call_int (ffi_cif *cif, void (*fn)(void), void *orig_rvalue, case FFI_TYPE_DOUBLE: case FFI_TYPE_LONGDOUBLE: case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: case FFI_TYPE_COMPLEX: { h = is_vfp_type (ty); @@ -1089,6 +1196,7 @@ ffi_closure_SYSV_inner (ffi_cif *cif, case FFI_TYPE_DOUBLE: case FFI_TYPE_LONGDOUBLE: case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: case FFI_TYPE_COMPLEX: h = is_vfp_type (ty); if (h) diff --git a/deps/libffi/src/aarch64/ffitarget.h b/deps/libffi/src/aarch64/ffitarget.h index 46e2687ae7fe..8ba86799e113 100644 --- a/deps/libffi/src/aarch64/ffitarget.h +++ b/deps/libffi/src/aarch64/ffitarget.h @@ -94,6 +94,10 @@ typedef enum ffi_abi #define FFI_TARGET_HAS_COMPLEX_TYPE #endif +/* AAPCS64 passes 8- and 16-byte vectors in V/Q registers and homogeneous + vector aggregates in consecutive V/Q registers; see is_vfp_type. */ +#define FFI_TARGET_HAS_VECTOR_TYPE + #define FFI_TARGET_HAS_INT128 1 #endif diff --git a/deps/libffi/src/debug.c b/deps/libffi/src/debug.c index 63321dc013cc..cf847f3b1107 100644 --- a/deps/libffi/src/debug.c +++ b/deps/libffi/src/debug.c @@ -54,7 +54,8 @@ void ffi_type_test(ffi_type *a, const char *file, int line) FFI_ASSERT_AT(a->type <= FFI_TYPE_LAST, file, line); FFI_ASSERT_AT(a->type == FFI_TYPE_VOID || a->size > 0, file, line); FFI_ASSERT_AT(a->type == FFI_TYPE_VOID || a->alignment > 0, file, line); - FFI_ASSERT_AT((a->type != FFI_TYPE_STRUCT && a->type != FFI_TYPE_COMPLEX) + FFI_ASSERT_AT((a->type != FFI_TYPE_STRUCT && a->type != FFI_TYPE_COMPLEX + && a->type != FFI_TYPE_VECTOR) || a->elements != NULL, file, line); FFI_ASSERT_AT(a->type != FFI_TYPE_COMPLEX || (a->elements != NULL diff --git a/deps/libffi/src/ia64/ia64_flags.h b/deps/libffi/src/ia64/ia64_flags.h index 9d652cef14ce..bfe102c7d86f 100644 --- a/deps/libffi/src/ia64/ia64_flags.h +++ b/deps/libffi/src/ia64/ia64_flags.h @@ -38,3 +38,14 @@ #define FFI_IA64_TYPE_HFA_FLOAT (FFI_TYPE_LAST + 2) #define FFI_IA64_TYPE_HFA_DOUBLE (FFI_TYPE_LAST + 3) #define FFI_IA64_TYPE_HFA_LDOUBLE (FFI_TYPE_LAST + 4) + +/* Tripwire: the .Lst_table / .Lld_table return-value jump tables in unix.S place + the FFI_IA64_TYPE_* pseudo-types (which are FFI_TYPE_LAST-relative) immediately + after the generic FFI_TYPE_* codes. Adding a new generic type bumps + FFI_TYPE_LAST, shifts those codes, and desyncs the tables -- silently + misdispatching small-struct/HFA returns. When this fires: add a matching slot + for the new type to both tables in unix.S, then bump FFI_IA64_TYPE_LAST. */ +#define FFI_IA64_TYPE_LAST FFI_TYPE_VECTOR +#if FFI_TYPE_LAST != FFI_IA64_TYPE_LAST +# error "new FFI_TYPE_* added: sync the unix.S jump tables and bump FFI_IA64_TYPE_LAST" +#endif diff --git a/deps/libffi/src/ia64/unix.S b/deps/libffi/src/ia64/unix.S index 04908368c3e2..b8e347169e2e 100644 --- a/deps/libffi/src/ia64/unix.S +++ b/deps/libffi/src/ia64/unix.S @@ -553,6 +553,9 @@ ffi_closure_unix: data8 @pcrel(.Lst_void) // FFI_TYPE_STRUCT data8 @pcrel(.Lst_int64) // FFI_TYPE_POINTER data8 @pcrel(.Lst_void) // FFI_TYPE_COMPLEX (not implemented) + data8 @pcrel(.Lst_void) // FFI_TYPE_UINT128 (not implemented) + data8 @pcrel(.Lst_void) // FFI_TYPE_SINT128 (not implemented) + data8 @pcrel(.Lst_void) // FFI_TYPE_VECTOR (rejected in ffi_prep_cif_core) data8 @pcrel(.Lst_small_struct) // FFI_IA64_TYPE_SMALL_STRUCT data8 @pcrel(.Lst_hfa_float) // FFI_IA64_TYPE_HFA_FLOAT data8 @pcrel(.Lst_hfa_double) // FFI_IA64_TYPE_HFA_DOUBLE @@ -575,6 +578,9 @@ ffi_closure_unix: data8 @pcrel(.Lld_void) // FFI_TYPE_STRUCT data8 @pcrel(.Lld_int) // FFI_TYPE_POINTER data8 @pcrel(.Lld_void) // FFI_TYPE_COMPLEX (not implemented) + data8 @pcrel(.Lld_void) // FFI_TYPE_UINT128 (not implemented) + data8 @pcrel(.Lld_void) // FFI_TYPE_SINT128 (not implemented) + data8 @pcrel(.Lld_void) // FFI_TYPE_VECTOR (rejected in ffi_prep_cif_core) data8 @pcrel(.Lld_small_struct) // FFI_IA64_TYPE_SMALL_STRUCT data8 @pcrel(.Lld_hfa_float) // FFI_IA64_TYPE_HFA_FLOAT data8 @pcrel(.Lld_hfa_double) // FFI_IA64_TYPE_HFA_DOUBLE diff --git a/deps/libffi/src/java_raw_api.c b/deps/libffi/src/java_raw_api.c index 114d3e47fcde..e0a02ef27432 100644 --- a/deps/libffi/src/java_raw_api.c +++ b/deps/libffi/src/java_raw_api.c @@ -58,7 +58,8 @@ ffi_java_raw_size (ffi_cif *cif) result += 2 * FFI_SIZEOF_JAVA_RAW; break; case FFI_TYPE_STRUCT: - /* No structure parameters in Java. */ + case FFI_TYPE_VECTOR: + /* No structure or vector parameters in Java. */ abort(); case FFI_TYPE_COMPLEX: /* Not supported yet. */ diff --git a/deps/libffi/src/pa/ffitarget.h b/deps/libffi/src/pa/ffitarget.h index f6f09975cfac..aeaacc167ef2 100644 --- a/deps/libffi/src/pa/ffitarget.h +++ b/deps/libffi/src/pa/ffitarget.h @@ -89,8 +89,13 @@ typedef enum ffi_abi { to the default case and is mapped to FFI_TYPE_INT, so cif->flags never exceeds FFI_TYPE_COMPLEX and the existing tables remain sufficient. Bump FFI_PA_TYPE_LAST to the current FFI_TYPE_LAST once you have confirmed any - newly added generic type is likewise handled (or the tables extended). */ -#define FFI_PA_TYPE_LAST FFI_TYPE_SINT128 + newly added generic type is likewise handled (or the tables extended). + + FFI_TYPE_VECTOR (18) is likewise not reached here: PA does not define + FFI_TARGET_HAS_VECTOR_TYPE, so ffi_prep_cif_core rejects any vector + signature with FFI_BAD_TYPEDEF before machdep runs. Bumping the tripwire + past it is therefore safe. */ +#define FFI_PA_TYPE_LAST FFI_TYPE_VECTOR /* Tripwire: when a new generic type is added FFI_TYPE_LAST changes and this fires, forcing a review of ffi_prep_cif_machdep and the linux.S / hpux32.S diff --git a/deps/libffi/src/powerpc/darwin_closure.S b/deps/libffi/src/powerpc/darwin_closure.S index 3121e6ac26d3..08cbe4bc1389 100644 --- a/deps/libffi/src/powerpc/darwin_closure.S +++ b/deps/libffi/src/powerpc/darwin_closure.S @@ -186,19 +186,17 @@ LCFI1: /* Make the call. */ bl BLCLS_HELP - /* r3 contains the rtype pointer... save it since we will need - it later. */ - sg r3,LINKAGE_SIZE(r1) ; ffi_type * result_type - lg r0,0(r3) ; size => r0 - lhz r3,FFI_TYPE_TYPE(r3) ; type => r3 - - /* The helper will have intercepted structure returns and inserted - the caller`s destination address for structs returned by ref. */ - - /* r3 contains the return type so use it to look up in a table - so we know how to deal with each type. */ - - addi r5,r1,(SAVE_SIZE-RESULT_BYTES) /* Otherwise, our return is here. */ + /* r3 now holds a small PPC_LD_* jump-table index (see the PPC_LD_* + defines in ffi_darwin.c), not an ffi_type* as this file previously + assumed: ffi_closure_helper_common cannot return both an ffi_type* + and the dispatch index through r3, so it returns the index. The + helper has already intercepted by-reference struct returns (writing + the result to the caller`s buffer and returning PPC_LD_NONE); for a + by-value struct return it returns PPC_LD_STRUCT and stashes cif->rtype + in the first parameter-save slot, which the PPC_LD_STRUCT fragment + below recovers. */ + + addi r5,r1,(SAVE_SIZE-RESULT_BYTES) /* Our return value is here. */ bl Lget_ret_type0_addr /* Get pointer to Lret_type0 into LR. */ mflr r4 /* Move to r4. */ slwi r3,r3,4 /* Now multiply return type by 16. */ @@ -218,43 +216,60 @@ LFE1: Lget_ret_type0_addr: blrl -/* case FFI_TYPE_VOID */ +/* The fragments below are indexed by the PPC_LD_* return code that + ffi_closure_helper_common handed back in r3, so their order must match the + PPC_LD_* values in ffi_darwin.c. Each is exactly 16 bytes (four + instructions), except the final PPC_LD_STRUCT fragment. */ + +/* case PPC_LD_NONE (void, or a struct returned by reference) */ Lret_type0: b Lfinish nop nop nop -/* case FFI_TYPE_INT */ +/* case PPC_LD_R3 (one GPR: int, pointer, and on ppc64 also 64-bit ints) */ Lret_type1: lg r3,0(r5) b Lfinish nop nop -/* case FFI_TYPE_FLOAT */ +/* case PPC_LD_R3R4 (two GPRs: the 32-bit ABI`s 64-bit integer) */ Lret_type2: +#if defined(__ppc64__) + lg r3,0(r5) + lg r4,8(r5) +#else + lwz r3,0(r5) + lwz r4,4(r5) +#endif + b Lfinish + nop + +/* case PPC_LD_F32 */ +Lret_type3: lfs f1,0(r5) b Lfinish nop nop -/* case FFI_TYPE_DOUBLE */ -Lret_type3: +/* case PPC_LD_F64 */ +Lret_type4: lfd f1,0(r5) b Lfinish nop nop -/* case FFI_TYPE_LONGDOUBLE */ -Lret_type4: +/* case PPC_LD_F128 (128-bit long double: two doubles) */ +Lret_type5: lfd f1,0(r5) lfd f2,8(r5) b Lfinish nop -/* case FFI_TYPE_UINT8 */ -Lret_type5: +/* case PPC_LD_U8 */ +Lret_type6: #if defined(__ppc64__) lbz r3,7(r5) #else @@ -264,8 +279,8 @@ Lret_type5: nop nop -/* case FFI_TYPE_SINT8 */ -Lret_type6: +/* case PPC_LD_S8 */ +Lret_type7: #if defined(__ppc64__) lbz r3,7(r5) #else @@ -275,8 +290,8 @@ Lret_type6: b Lfinish nop -/* case FFI_TYPE_UINT16 */ -Lret_type7: +/* case PPC_LD_U16 */ +Lret_type8: #if defined(__ppc64__) lhz r3,6(r5) #else @@ -286,8 +301,8 @@ Lret_type7: nop nop -/* case FFI_TYPE_SINT16 */ -Lret_type8: +/* case PPC_LD_S16 */ +Lret_type9: #if defined(__ppc64__) lha r3,6(r5) #else @@ -297,77 +312,43 @@ Lret_type8: nop nop -/* case FFI_TYPE_UINT32 */ -Lret_type9: #if defined(__ppc64__) - lwz r3,4(r5) -#else - lwz r3,0(r5) -#endif - b Lfinish - nop - nop - -/* case FFI_TYPE_SINT32 */ +/* case PPC_LD_U32 (ppc64 only; the 32-bit ABI aliases U32 to PPC_LD_R3) */ Lret_type10: -#if defined(__ppc64__) lwz r3,4(r5) -#else - lwz r3,0(r5) -#endif b Lfinish nop nop -/* case FFI_TYPE_UINT64 */ +/* case PPC_LD_S32 (ppc64 only; the 32-bit ABI aliases S32 to PPC_LD_R3) */ Lret_type11: -#if defined(__ppc64__) - lg r3,0(r5) - b Lfinish - nop -#else - lwz r3,0(r5) - lwz r4,4(r5) + lwa r3,4(r5) b Lfinish -#endif nop - -/* case FFI_TYPE_SINT64 */ -Lret_type12: -#if defined(__ppc64__) - lg r3,0(r5) - b Lfinish nop -#else - lwz r3,0(r5) - lwz r4,4(r5) - b Lfinish #endif - nop -/* case FFI_TYPE_STRUCT */ -Lret_type13: +/* case PPC_LD_STRUCT (a by-value struct return). This is the final, + variable-length fragment, so it need not be padded to 16 bytes. The helper + stashed cif->rtype in the first parameter-save slot (see ffi_darwin.c), + because the small dispatch index in r3 left no room for it. */ +Lret_type_struct: + lg r6,PARENT_PARM_BASE(r1) ; cif->rtype + sg r6,LINKAGE_SIZE(r1) ; where the struct code below expects it + lg r0,0(r6) ; size => r0 #if defined(__ppc64__) lg r3,0(r5) ; we need at least this... cmpi 0,r0,4 bgt Lstructend ; not a special small case b Lsmallstruct ; see if we need more. #else - cmpwi 0,r0,4 - bgt Lfinish ; not by value - lg r3,0(r5) + lg r3,0(r5) ; a <=4-byte struct, returned in r3 b Lfinish #endif -/* case FFI_TYPE_POINTER */ -Lret_type14: - lg r3,0(r5) - b Lfinish - nop - nop #if defined(__ppc64__) Lsmallstruct: - beq Lfour ; continuation of Lret13. + beq Lfour ; continuation of Lret_type_struct. cmpi 0,r0,3 beq Lfinish ; don`t adjust this - can`t be any floats here... srdi r3,r3,48 diff --git a/deps/libffi/src/powerpc/ffi_darwin.c b/deps/libffi/src/powerpc/ffi_darwin.c index 01e2a43701d7..64449c38e156 100644 --- a/deps/libffi/src/powerpc/ffi_darwin.c +++ b/deps/libffi/src/powerpc/ffi_darwin.c @@ -60,11 +60,13 @@ struct ffi_aix_trampoline_struct { # define PPC_LD_S32 PPC_LD_R3 # define PPC_LD_PTR PPC_LD_R3 # define PPC_LD_I64 PPC_LD_R3R4 +# define PPC_LD_STRUCT 10 #else # define PPC_LD_U32 10 # define PPC_LD_S32 11 # define PPC_LD_PTR PPC_LD_R3 # define PPC_LD_I64 PPC_LD_R3 +# define PPC_LD_STRUCT 12 #endif extern void ffi_closure_ASM (void); @@ -1260,6 +1262,13 @@ ffi_closure_helper_common (ffi_cif* cif, long i, avn; ffi_dblfl * end_pfr = pfr + NUM_FPR_ARG_REGISTERS; unsigned size_al; + int struct_ret_by_value = 0; + /* When a struct is returned by value, ffi_closure_ASM's jump-table + dispatch carries only a small integer return code (see PPC_LD_* above), + with no room for cif->rtype. We hand cif->rtype back in the first + parameter-save slot -- which is dead by the time we return -- for the + PPC_LD_STRUCT fragment in darwin_closure.S to recover. */ + unsigned long * pgr0 = pgr; #if defined(POWERPC_DARWIN64) unsigned fpsused = 0; #endif @@ -1275,12 +1284,16 @@ ffi_closure_helper_common (ffi_cif* cif, rvalue = (void *) *pgr; pgr++; } + else + struct_ret_by_value = 1; #elif defined(DARWIN_PPC) if (cif->rtype->size > 4) { rvalue = (void *) *pgr; pgr++; } + else + struct_ret_by_value = 1; #else /* assume we return by ref. */ rvalue = (void *) *pgr; pgr++; @@ -1480,7 +1493,17 @@ ffi_closure_helper_common (ffi_cif* cif, switch (cif->rtype->type) { case FFI_TYPE_VOID: + return PPC_LD_NONE; case FFI_TYPE_STRUCT: + /* A by-reference struct return needs nothing further here: the result + was written straight to the caller's buffer. A by-value struct + return is loaded into registers by darwin_closure.S, which needs + cif->rtype -- hand it back in the first parameter-save slot. */ + if (struct_ret_by_value) + { + *pgr0 = (unsigned long) cif->rtype; + return PPC_LD_STRUCT; + } return PPC_LD_NONE; case FFI_TYPE_FLOAT: return PPC_LD_F32; diff --git a/deps/libffi/src/powerpc/ffi_linux64.c b/deps/libffi/src/powerpc/ffi_linux64.c index b1f1468ed5f8..e92f88c46973 100644 --- a/deps/libffi/src/powerpc/ffi_linux64.c +++ b/deps/libffi/src/powerpc/ffi_linux64.c @@ -107,8 +107,13 @@ discover_homogeneous_aggregate (ffi_abi abi, unsigned int inner_elnum = 0; unsigned int inner = discover_homogeneous_aggregate (abi, t->elements[0], &inner_elnum); - if (inner == FFI_TYPE_FLOAT || inner == FFI_TYPE_DOUBLE) + if (inner == FFI_TYPE_FLOAT || inner == FFI_TYPE_DOUBLE + || inner == FFI_TYPE_LONGDOUBLE) { + /* A _Complex of an FP base counts as two of that base: an + FP-HFA struct member. For IBM-128 long double each half is + itself two FPRs (inner_elnum == 2), so a _Complex long double + contributes four FPRs. */ *elnum = 2 * inner_elnum; return inner; } @@ -257,11 +262,17 @@ ffi_prep_cif_linux64_core (ffi_cif *cif) goto homogeneous; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - /* Only the 64-bit long double case is wired up; IBM-128 and - IEEE-binary128 _Complex are left as a follow-up. */ - if ((cif->abi & (FFI_LINUX_LONG_DOUBLE_128 - | FFI_LINUX_LONG_DOUBLE_IEEE128)) != 0) - return FFI_BAD_TYPEDEF; + if ((cif->abi & FFI_LINUX_LONG_DOUBLE_IEEE128) != 0) + { + /* IEEE-128 _Complex long double: real in v2, imag in v3. + Return via the vector-homogeneous small-struct path. */ + flags |= FLAG_RETURNS_SMST | FLAG_RETURNS_VEC; + break; + } + /* IBM-128 _Complex long double is returned like a homogeneous + aggregate of doubles: real in f1:f2, imag in f3:f4. (For a + 64-bit long double this reduces to the FFI_TYPE_DOUBLE case, + real in f1 and imag in f2.) */ flags |= FLAG_RETURNS_SMST; rtype = FFI_TYPE_DOUBLE; goto homogeneous; @@ -393,11 +404,21 @@ ffi_prep_cif_linux64_core (ffi_cif *cif) break; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - if ((cif->abi & (FFI_LINUX_LONG_DOUBLE_128 - | FFI_LINUX_LONG_DOUBLE_IEEE128)) != 0) - return FFI_BAD_TYPEDEF; - fparg_count += 2; - intarg_count += 2; + if ((cif->abi & FFI_LINUX_LONG_DOUBLE_IEEE128) != 0) + { + /* Two IEEE-128 halves: each occupies a vector register plus + two GPR shadow doublewords, the pair 16-byte aligned. */ + vecarg_count += 2; + intarg_count = (intarg_count + 1) & ~0x1; + intarg_count += 4; + if (vecarg_count > NUM_VEC_ARG_REGISTERS64) + flags |= FLAG_ARG_NEEDS_PSAVE; + break; + } + /* IBM-128: each half is a pair of FPRs, and each FPR half + consumes a GPR shadow doubleword -- four of each in total. */ + fparg_count += 4; + intarg_count += 4; if (fparg_count > NUM_FPR_ARG_REGISTERS64) flags |= FLAG_ARG_NEEDS_PSAVE; break; @@ -755,10 +776,51 @@ ffi_prep_args64 (extended_cif *ecif, unsigned long *const stack) case FFI_TYPE_COMPLEX: elt = (*ptr)->elements[0]->type; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE - /* 64-bit long double is equivalent to double; the IBM-128 and - IEEE-binary128 variants were rejected in prep_cif. */ + if (elt == FFI_TYPE_LONGDOUBLE + && (ecif->cif->abi & FFI_LINUX_LONG_DOUBLE_IEEE128) != 0) + { + /* IEEE-128 _Complex long double: each half goes in its own + vector register (or the parameter save area), 16-byte + aligned, consuming two GPR shadow doublewords. */ + float128 *cval = (float128 *) *p_argv.v; + unsigned int j; + for (j = 0; j < 2; j++) + { + next_arg.p = FFI_ALIGN (next_arg.p, 16); + if (next_arg.ul == gpr_end.ul) + next_arg.ul = rest.ul; + if (vecarg_count < NUM_VEC_ARG_REGISTERS64 && i < nfixedargs) + memcpy (vec_base.f128++, cval + j, sizeof (float128)); + else + memcpy (next_arg.f128, cval + j, sizeof (float128)); + if (++next_arg.f128 == gpr_end.f128) + next_arg.f128 = rest.f128; + vecarg_count++; + } + FFI_ASSERT (flags & FLAG_VEC_ARGUMENTS); + break; + } if (elt == FFI_TYPE_LONGDOUBLE) - elt = FFI_TYPE_DOUBLE; + { + /* IBM-128 _Complex long double: four doubles (real hi/lo, + imag hi/lo) into consecutive FPRs, each with a GPR shadow + doubleword. */ + double *cval = (double *) *p_argv.v; + unsigned int j; + for (j = 0; j < 4; j++) + { + double_tmp = cval[j]; + if (fparg_count < NUM_FPR_ARG_REGISTERS64 && i < nfixedargs) + *fpr_base.d++ = double_tmp; + else + *next_arg.d = double_tmp; + if (++next_arg.ul == gpr_end.ul) + next_arg.ul = rest.ul; + fparg_count++; + } + FFI_ASSERT (flags & FLAG_FP_ARGUMENTS); + break; + } #endif if (elt == FFI_TYPE_FLOAT) { @@ -1336,8 +1398,45 @@ ffi_closure_helper_LINUX64 (ffi_cif *cif, unsigned int j; elt = arg_types[i]->elements[0]->type; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE + if (elt == FFI_TYPE_LONGDOUBLE + && (cif->abi & FFI_LINUX_LONG_DOUBLE_IEEE128) != 0) + { + /* IEEE-128: each half arrives in a vector register (or the + 16-byte-aligned parameter save area) with two GPR shadow + doublewords. */ + float128 *cval = alloca (2 * sizeof (float128)); + if (((unsigned long) pst & 0xF) != 0) + ++pst; + for (j = 0; j < 2; j++) + { + if (pvec < end_pvec && i < nfixedargs) + memcpy (&cval[j], pvec++, sizeof (float128)); + else + memcpy (&cval[j], pst, sizeof (float128)); + pst += 2; + } + avalue[i] = cval; + break; + } if (elt == FFI_TYPE_LONGDOUBLE) - elt = FFI_TYPE_DOUBLE; + { + /* IBM-128: four doubles, each in an FPR (or one GPR shadow + doubleword) -- real hi/lo then imag hi/lo. */ + double *cval = alloca (4 * sizeof (double)); + for (j = 0; j < 4; j++) + { + if (pfr < end_pfr && i < nfixedargs) + { + cval[j] = pfr->d; + pfr++; + } + else + cval[j] = *(double *) pst; + pst++; + } + avalue[i] = cval; + break; + } #endif if (elt == FFI_TYPE_FLOAT) { @@ -1448,7 +1547,13 @@ ffi_closure_helper_LINUX64 (ffi_cif *cif, int inner = cif->rtype->elements[0]->type; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE if (inner == FFI_TYPE_LONGDOUBLE) - inner = FFI_TYPE_DOUBLE; + { + /* IEEE-128 _Complex long double returns in v2:v3; IBM-128 in + f1:f2 (real) and f3:f4 (imag), i.e. as a double HFA. */ + if ((cif->abi & FFI_LINUX_LONG_DOUBLE_IEEE128) != 0) + return PPC64_LD_VECTOR_HOMOG; + inner = FFI_TYPE_DOUBLE; + } #endif if (inner == FFI_TYPE_FLOAT) return PPC64_LD_FLOAT_HOMOG; diff --git a/deps/libffi/src/powerpc/linux64_closure.S b/deps/libffi/src/powerpc/linux64_closure.S index 405b2cfc47a1..3071bec0d22a 100644 --- a/deps/libffi/src/powerpc/linux64_closure.S +++ b/deps/libffi/src/powerpc/linux64_closure.S @@ -345,6 +345,21 @@ E PPC64_LD_STRUCT_3 lwz %r3, RETVAL+4(%r1) srd %r3, %r3, 8 epilogue + +E PPC64_LD_STRUCT_5 + ld %r3, RETVAL+0(%r1) + srdi %r3, %r3, 24 + epilogue + +E PPC64_LD_STRUCT_6 + ld %r3, RETVAL+0(%r1) + srdi %r3, %r3, 16 + epilogue + +E PPC64_LD_STRUCT_7 + ld %r3, RETVAL+0(%r1) + srdi %r3, %r3, 8 + epilogue #endif .Lmoredouble: diff --git a/deps/libffi/src/prep_cif.c b/deps/libffi/src/prep_cif.c index 1836270d1a9c..8a448ebbf81f 100644 --- a/deps/libffi/src/prep_cif.c +++ b/deps/libffi/src/prep_cif.c @@ -32,6 +32,72 @@ #define STACK_ARG_SIZE(x) FFI_ALIGN(x, FFI_SIZEOF_ARG) +/* Compute the machine-independent layout of a vector (SIMD) type. + + A vector is described exactly like a struct -- arg->elements is a + NULL-terminated array of pointers -- but every element must point to the + SAME fundamental scalar type, and the count is the number of lanes. The + caller leaves arg->size and arg->alignment as zero; libffi derives them: + + size = lane_size * lane_count, rounded UP to the next power of two + (matching Clang's ext_vector_type storage, e.g. 3 x float + -> 16; GCC's vector_size already requires power-of-two totals + so the rule is identical there); + alignment = min(size, 16). + + Only float, double and the fixed-width integer scalars (UINT8..SINT64) are + valid lane types. Anything else -- a heterogeneous element list, an + aggregate lane, long double, or a zero-length vector -- is FFI_BAD_TYPEDEF. */ + +static ffi_status +initialize_vector (ffi_type *arg) +{ + ffi_type **ptr = arg->elements; + ffi_type *elem; + size_t count = 0; + size_t total, p2; + + if (UNLIKELY (ptr == NULL || *ptr == NULL)) + return FFI_BAD_TYPEDEF; + + elem = *ptr; + switch (elem->type) + { + case FFI_TYPE_FLOAT: + case FFI_TYPE_DOUBLE: + case FFI_TYPE_UINT8: + case FFI_TYPE_SINT8: + case FFI_TYPE_UINT16: + case FFI_TYPE_SINT16: + case FFI_TYPE_UINT32: + case FFI_TYPE_SINT32: + case FFI_TYPE_UINT64: + case FFI_TYPE_SINT64: + break; + default: + return FFI_BAD_TYPEDEF; + } + + /* Every lane must be the identical scalar type. */ + for (; *ptr != NULL; ptr++) + { + if ((*ptr)->type != elem->type || (*ptr)->size != elem->size) + return FFI_BAD_TYPEDEF; + count++; + } + + if (UNLIKELY (count < 1 || elem->size == 0)) + return FFI_BAD_TYPEDEF; + + total = elem->size * count; + for (p2 = 1; p2 < total; p2 <<= 1) + ; + + arg->size = p2; + arg->alignment = p2 < 16 ? p2 : 16; + return FFI_OK; +} + /* Perform machine independent initialization of aggregate type specifications. */ @@ -42,6 +108,9 @@ static ffi_status initialize_aggregate(ffi_type *arg, size_t *offsets) if (UNLIKELY(arg == NULL || arg->elements == NULL)) return FFI_BAD_TYPEDEF; + if (arg->type == FFI_TYPE_VECTOR) + return initialize_vector (arg); + arg->size = 0; arg->alignment = 0; @@ -92,6 +161,28 @@ static ffi_status initialize_aggregate(ffi_type *arg, size_t *offsets) return FFI_OK; } +#ifndef FFI_TARGET_HAS_VECTOR_TYPE +/* Recursively test whether TY is, or contains, a vector (SIMD) type. Ports + that do not define FFI_TARGET_HAS_VECTOR_TYPE cannot marshal vectors, so + ffi_prep_cif_core rejects any signature that mentions one (directly or + nested inside a struct) with FFI_BAD_TYPEDEF rather than aborting. */ +static int +ffi_type_contains_vector (ffi_type *ty) +{ + ffi_type **p; + + if (ty == NULL) + return 0; + if (ty->type == FFI_TYPE_VECTOR) + return 1; + if (ty->type == FFI_TYPE_STRUCT && ty->elements != NULL) + for (p = ty->elements; *p != NULL; p++) + if (ffi_type_contains_vector (*p)) + return 1; + return 0; +} +#endif /* !FFI_TARGET_HAS_VECTOR_TYPE */ + #ifndef __CRIS__ /* The CRIS ABI specifies structure elements to have byte alignment only, so it completely overrides this functions, @@ -129,6 +220,15 @@ ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi, cif->nargs = ntotalargs; cif->rtype = rtype; +#ifndef FFI_TARGET_HAS_VECTOR_TYPE + /* Vector (SIMD) types are only marshalled on ports that opt in. */ + if (ffi_type_contains_vector (rtype)) + return FFI_BAD_TYPEDEF; + for (i = 0; i < ntotalargs; i++) + if (ffi_type_contains_vector (atypes[i])) + return FFI_BAD_TYPEDEF; +#endif + cif->flags = 0; #if (defined(_M_ARM64) || defined(__aarch64__)) && defined(_WIN32) cif->is_variadic = isvariadic; @@ -152,7 +252,8 @@ ffi_status FFI_HIDDEN ffi_prep_cif_core(ffi_cif *cif, ffi_abi abi, /* x86, x86-64 and s390 stack space allocation is handled in prep_machdep. */ #if !defined FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION /* Make space for the return structure pointer */ - if (cif->rtype->type == FFI_TYPE_STRUCT + if ((cif->rtype->type == FFI_TYPE_STRUCT + || cif->rtype->type == FFI_TYPE_VECTOR) #ifdef TILE && (cif->rtype->size > 10 * FFI_SIZEOF_ARG) #endif @@ -316,4 +417,11 @@ ffi_call_plan_free (ffi_call_plan *plan) free (plan); } +size_t +ffi_call_plan_size (ffi_call_plan *plan) +{ + /* The generic plan is a bare handle; there is no separate move-list. */ + return plan != NULL ? sizeof (struct ffi_call_plan) : 0; +} + #endif /* generic ffi_call_plan fallback */ diff --git a/deps/libffi/src/raw_api.c b/deps/libffi/src/raw_api.c index be156116cb0d..670d56d948a3 100644 --- a/deps/libffi/src/raw_api.c +++ b/deps/libffi/src/raw_api.c @@ -42,7 +42,7 @@ ffi_raw_size (ffi_cif *cif) for (i = cif->nargs-1; i >= 0; i--, at++) { #if !FFI_NO_STRUCTS - if ((*at)->type == FFI_TYPE_STRUCT) + if ((*at)->type == FFI_TYPE_STRUCT || (*at)->type == FFI_TYPE_VECTOR) result += FFI_ALIGN (sizeof (void*), FFI_SIZEOF_ARG); else #endif @@ -82,8 +82,9 @@ ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args) break; #endif -#if !FFI_NO_STRUCTS +#if !FFI_NO_STRUCTS case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: *args = (raw++)->ptr; break; #endif @@ -110,7 +111,7 @@ ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args) for (i = 0; i < cif->nargs; i++, tp++, args++) { #if !FFI_NO_STRUCTS - if ((*tp)->type == FFI_TYPE_STRUCT) + if ((*tp)->type == FFI_TYPE_STRUCT || (*tp)->type == FFI_TYPE_VECTOR) { *args = (raw++)->ptr; } @@ -172,6 +173,7 @@ ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw) #if !FFI_NO_STRUCTS case FFI_TYPE_STRUCT: + case FFI_TYPE_VECTOR: (raw++)->ptr = *args; break; #endif diff --git a/deps/libffi/src/tramp.c b/deps/libffi/src/tramp.c index 525f81547156..a04188858af6 100644 --- a/deps/libffi/src/tramp.c +++ b/deps/libffi/src/tramp.c @@ -417,9 +417,19 @@ ffi_tramp_init (void) &tramp_globals.map_size); tramp_globals.ntramp = tramp_globals.map_size / tramp_globals.size; + /* + * The trampoline code table is a single, fixed-size mapping. If the + * system page size is larger than that mapping, the static trampoline + * mechanism cannot be used. Both values are invariant for the life of + * the process, so cache the FAILED verdict rather than re-running the + * whole initialization on every allocation. + */ page_size = sysconf (_SC_PAGESIZE); if (page_size >= 0 && (size_t)page_size > tramp_globals.map_size) - return 0; + { + tramp_globals.status = TRAMP_GLOBALS_FAILED; + return 0; + } if (ffi_tramp_init_os ()) { diff --git a/deps/libffi/src/x86/ffi.c b/deps/libffi/src/x86/ffi.c index 27f17b0c8849..a953891362d5 100644 --- a/deps/libffi/src/x86/ffi.c +++ b/deps/libffi/src/x86/ffi.c @@ -118,7 +118,7 @@ ffi_prep_cif_machdep(ffi_cif *cif) break; case FFI_TYPE_STRUCT: { -#if defined(X86_WIN32) || defined(X86_DARWIN) +#if defined(X86_WIN32) || defined(X86_DARWIN) || defined(X86_FREEBSD) size_t size = cif->rtype->size; if (size == 1) flags = X86_RET_STRUCT_1B; diff --git a/deps/libffi/src/x86/ffi64.c b/deps/libffi/src/x86/ffi64.c index c24db38c4364..c2c78f3fbbfb 100644 --- a/deps/libffi/src/x86/ffi64.c +++ b/deps/libffi/src/x86/ffi64.c @@ -330,6 +330,25 @@ classify_argument (ffi_type *type, enum x86_64_reg_class classes[], } return words; } + case FFI_TYPE_VECTOR: + /* A Short Vector occupies SSE registers: an 8-byte vector is a single + SSE eightbyte; a 16-byte vector is one %xmm register (SSE + SSEUP). + Wider vectors would need %ymm/%zmm handling this port does not + implement; classify them as memory here and reject them outright in + ffi_prep_cif_machdep so the caller gets FFI_BAD_TYPEDEF, not a + silently wrong in-memory pass. */ + if (type->size == 8) + { + classes[0] = X86_64_SSE_CLASS; + return 1; + } + else if (type->size == 16) + { + classes[0] = X86_64_SSE_CLASS; + classes[1] = X86_64_SSEUP_CLASS; + return 2; + } + return 0; case FFI_TYPE_COMPLEX: { ffi_type *inner = type->elements[0]; @@ -533,6 +552,16 @@ ffi_prep_cif_machdep (ffi_cif *cif) } } break; + case FFI_TYPE_VECTOR: + /* An 8-byte vector returns in the low half of %xmm0; a 16-byte vector + fills %xmm0 (SSE + SSEUP). Wider vectors are unsupported here. */ + if (rtype_size == 8) + flags = UNIX64_RET_XMM64; + else if (rtype_size == 16) + flags = UNIX64_RET_XMM128; + else + return FFI_BAD_TYPEDEF; + break; case FFI_TYPE_COMPLEX: switch (rtype->elements[0]->type) { @@ -577,6 +606,15 @@ ffi_prep_cif_machdep (ffi_cif *cif) return FFI_BAD_TYPEDEF; } + /* Reject vectors wider than 16 bytes as arguments: correct %ymm/%zmm + passing needs unix64.S register-save changes that are out of scope for + this port, and classify_argument would otherwise silently treat them as + an in-memory aggregate. */ + for (i = 0, avn = cif->nargs; i < avn; i++) + if (cif->arg_types[i]->type == FFI_TYPE_VECTOR + && cif->arg_types[i]->size > 16) + return FFI_BAD_TYPEDEF; + /* Go over all arguments and determine the way they should be passed. If it's in a register and there is space for it, let that be so. If not, add it's size to the stack byte count. */ @@ -782,6 +820,7 @@ typedef struct unsigned fast; /* nonzero -> lean trampoline eligible */ unsigned retcode; /* UNIX64_RET_* (low byte of flags) for the store */ int thunk_n; /* >=0 -> ffi_gp_thunks[thunk_n], else -1 */ + unsigned alloc_bytes; /* malloc'd size, reported by ffi_call_plan_size */ ffi_move moves[]; } ffi_plan; @@ -828,7 +867,7 @@ build_plan (ffi_cif *cif) unsigned i, avn = cif->nargs; enum x86_64_reg_class classes[MAX_CLASSES]; unsigned nm, gprcount, ssecount; - size_t argp_off; + size_t argp_off, nbytes; ffi_plan *plan; int all_gp64 = 1; /* every arg is exactly one 64-bit GP move? */ @@ -848,9 +887,11 @@ build_plan (ffi_cif *cif) } /* One self-contained allocation: header + moves, released with plain free(). */ - plan = malloc (sizeof (ffi_plan) + sizeof (ffi_move) * (2 * avn + 1)); + nbytes = sizeof (ffi_plan) + sizeof (ffi_move) * (2 * avn + 1); + plan = malloc (nbytes); if (plan == NULL) return NULL; + plan->alloc_bytes = (unsigned) nbytes; nm = gprcount = ssecount = 0; argp_off = 0; @@ -1070,6 +1111,17 @@ ffi_call_plan_free (ffi_call_plan *plan) } } +size_t +ffi_call_plan_size (ffi_call_plan *plan) +{ + if (plan == NULL) + return 0; + /* The move-list carries its own size; a signature with no fast path owns + nothing beyond the handle. */ + return sizeof (struct ffi_call_plan) + + (plan->fast != NULL ? plan->fast->alloc_bytes : 0); +} + extern void ffi_call_efi64(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue); #endif diff --git a/deps/libffi/src/x86/ffitarget.h b/deps/libffi/src/x86/ffitarget.h index d702235f90fe..eaf6a910a4f5 100644 --- a/deps/libffi/src/x86/ffitarget.h +++ b/deps/libffi/src/x86/ffitarget.h @@ -58,6 +58,13 @@ #define FFI_TARGET_HAS_INT128 #endif +/* The System V x86-64 psABI passes 8- and 16-byte vectors in SSE registers; + this is implemented by the ffi64.c (FFI_UNIX64) backend only. 32-bit x86 + and the Windows x86-64 backend (ffiw64.c) do not marshal vectors. */ +#if defined(X86_64) && !defined(X86_WIN64) +#define FFI_TARGET_HAS_VECTOR_TYPE +#endif + /* ---- Generic type definitions ----------------------------------------- */ #ifndef LIBFFI_ASM @@ -138,6 +145,18 @@ typedef enum ffi_abi { #define FFI_TYPE_SMALL_STRUCT_4B (FFI_TYPE_LAST + 3) #define FFI_TYPE_MS_STRUCT (FFI_TYPE_LAST + 4) +/* Tripwire: the win64.S / win64_intel.S return-value jump tables use one + 8-byte slot per code value and place the FFI_TYPE_SMALL_STRUCT_* pseudo-types + (which are FFI_TYPE_LAST-relative) immediately after the generic FFI_TYPE_* + codes. Adding a new generic type bumps FFI_TYPE_LAST, shifts those codes, + and opens a gap in the tables that silently misaligns small-struct returns. + When this fires: add a matching E() slot for the new type in both win64.S + and win64_intel.S, then bump FFI_X86_TYPE_LAST to match. */ +#define FFI_X86_TYPE_LAST FFI_TYPE_VECTOR +#if FFI_TYPE_LAST != FFI_X86_TYPE_LAST +# error "new FFI_TYPE_* added: sync the win64.S/win64_intel.S jump tables and bump FFI_X86_TYPE_LAST" +#endif + #if defined (X86_64) || defined(X86_WIN64) \ || (defined (__x86_64__) && defined (X86_DARWIN)) /* 4 bytes of ENDBR64 + 7 bytes of LEA + 6 bytes of JMP + 7 bytes of NOP diff --git a/deps/libffi/src/x86/win64.S b/deps/libffi/src/x86/win64.S index 185f0a3048fb..f23a5fa29e8e 100644 --- a/deps/libffi/src/x86/win64.S +++ b/deps/libffi/src/x86/win64.S @@ -151,6 +151,11 @@ E(0b, FFI_TYPE_UINT128) E(0b, FFI_TYPE_SINT128) movdqu %xmm0, (%r8) epilogue +/* Win64 does not marshal vectors (ffi_prep_cif_core rejects them), but the + FFI_TYPE_SMALL_STRUCT_* codes are FFI_TYPE_LAST-relative, so this slot must + exist to keep the table contiguous and the small-struct entries aligned. */ +E(0b, FFI_TYPE_VECTOR) + call PLT(C(abort)) E(0b, FFI_TYPE_SMALL_STRUCT_1B) movb %al, (%r8) epilogue diff --git a/deps/libffi/src/x86/win64_intel.S b/deps/libffi/src/x86/win64_intel.S index e9eff00da3ce..807f5b3e98f7 100644 --- a/deps/libffi/src/x86/win64_intel.S +++ b/deps/libffi/src/x86/win64_intel.S @@ -152,6 +152,11 @@ E(0b, FFI_TYPE_UINT128) E(0b, FFI_TYPE_SINT128) movdqu xmmword ptr [r8], xmm0 epilogue +/* Win64 does not marshal vectors (ffi_prep_cif_core rejects them), but the + FFI_TYPE_SMALL_STRUCT_* codes are FFI_TYPE_LAST-relative, so this slot must + exist to keep the table contiguous and the small-struct entries aligned. */ +E(0b, FFI_TYPE_VECTOR) + call PLT(C(abort)) E(0b, FFI_TYPE_SMALL_STRUCT_1B) mov byte ptr [r8], al ; movb %al, (%r8) epilogue diff --git a/deps/libffi/testsuite/Makefile.am b/deps/libffi/testsuite/Makefile.am index c14a880959d8..702461d02418 100644 --- a/deps/libffi/testsuite/Makefile.am +++ b/deps/libffi/testsuite/Makefile.am @@ -13,15 +13,17 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \ libffi.bhaible/alignof.h libffi.bhaible/bhaible.exp libffi.bhaible/test-call.c \ libffi.bhaible/test-callback.c libffi.bhaible/testcases.c libffi.call/align_mixed.c \ libffi.call/align_stdcall.c libffi.call/bpo_38748.c libffi.call/call.exp \ + libffi.call/closure_thiscall_fastcall_pop.c \ libffi.call/err_bad_typedef.c libffi.call/ffitest.h libffi.call/float.c \ libffi.call/float1.c libffi.call/float2.c libffi.call/float3.c \ libffi.call/float4.c libffi.call/float_va.c libffi.call/i128-1.c \ libffi.call/large_struct_by_value.c libffi.call/many.c \ - libffi.call/many2.c libffi.call/many_double.c libffi.call/many_mixed.c \ + libffi.call/many2.c libffi.call/many_double.c \ + libffi.call/many_large_structs.c libffi.call/many_mixed.c \ libffi.call/many_small_structs.c \ libffi.call/negint.c libffi.call/offsets.c libffi.call/overread.c \ libffi.call/plan.c libffi.call/plan_mixed.c libffi.call/plan_spill.c \ - libffi.call/plan_struct.c libffi.call/plan_var.c \ + libffi.call/plan_struct.c libffi.call/plan_size.c libffi.call/plan_var.c \ libffi.call/pr1172638.c libffi.call/promotion.c libffi.call/pyobjc_tc.c libffi.call/return_dbl.c \ libffi.call/return_dbl1.c libffi.call/return_dbl2.c libffi.call/return_fl.c \ libffi.call/return_fl1.c libffi.call/return_fl2.c libffi.call/return_fl3.c \ @@ -90,4 +92,10 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \ libffi.complex/return_complex_float.c libffi.complex/return_complex_longdouble.c libffi.go/aa-direct.c \ libffi.go/closure1.c libffi.go/ffitest.h libffi.go/go.exp \ libffi.go/static-chain.h Makefile.am Makefile.in \ - libffi.threads/ffitest.h libffi.threads/threads.exp libffi.threads/tsan.c + libffi.threads/ffitest.h libffi.threads/threads.exp libffi.threads/tsan.c \ + libffi.vector/vector.exp libffi.vector/ffitest.h libffi.vector/vector.h \ + libffi.vector/vector_float32x4.c libffi.vector/vector_float32x2.c \ + libffi.vector/vector_double2.c libffi.vector/vector_int32x4.c \ + libffi.vector/vector_args_spill.c libffi.vector/vector_vec3.c \ + libffi.vector/vector_double4.c libffi.vector/vector_hva.c \ + libffi.vector/cls_vector.c libffi.vector/vector_validate.c diff --git a/deps/libffi/testsuite/Makefile.in b/deps/libffi/testsuite/Makefile.in index 1b29b90d3633..30b417735d5e 100644 --- a/deps/libffi/testsuite/Makefile.in +++ b/deps/libffi/testsuite/Makefile.in @@ -301,15 +301,17 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \ libffi.bhaible/alignof.h libffi.bhaible/bhaible.exp libffi.bhaible/test-call.c \ libffi.bhaible/test-callback.c libffi.bhaible/testcases.c libffi.call/align_mixed.c \ libffi.call/align_stdcall.c libffi.call/bpo_38748.c libffi.call/call.exp \ + libffi.call/closure_thiscall_fastcall_pop.c \ libffi.call/err_bad_typedef.c libffi.call/ffitest.h libffi.call/float.c \ libffi.call/float1.c libffi.call/float2.c libffi.call/float3.c \ libffi.call/float4.c libffi.call/float_va.c libffi.call/i128-1.c \ libffi.call/large_struct_by_value.c libffi.call/many.c \ - libffi.call/many2.c libffi.call/many_double.c libffi.call/many_mixed.c \ + libffi.call/many2.c libffi.call/many_double.c \ + libffi.call/many_large_structs.c libffi.call/many_mixed.c \ libffi.call/many_small_structs.c \ libffi.call/negint.c libffi.call/offsets.c libffi.call/overread.c \ libffi.call/plan.c libffi.call/plan_mixed.c libffi.call/plan_spill.c \ - libffi.call/plan_struct.c libffi.call/plan_var.c \ + libffi.call/plan_struct.c libffi.call/plan_size.c libffi.call/plan_var.c \ libffi.call/pr1172638.c libffi.call/promotion.c libffi.call/pyobjc_tc.c libffi.call/return_dbl.c \ libffi.call/return_dbl1.c libffi.call/return_dbl2.c libffi.call/return_fl.c \ libffi.call/return_fl1.c libffi.call/return_fl2.c libffi.call/return_fl3.c \ @@ -378,7 +380,13 @@ EXTRA_DIST = config/default.exp emscripten/build.sh emscripten/conftest.py \ libffi.complex/return_complex_float.c libffi.complex/return_complex_longdouble.c libffi.go/aa-direct.c \ libffi.go/closure1.c libffi.go/ffitest.h libffi.go/go.exp \ libffi.go/static-chain.h Makefile.am Makefile.in \ - libffi.threads/ffitest.h libffi.threads/threads.exp libffi.threads/tsan.c + libffi.threads/ffitest.h libffi.threads/threads.exp libffi.threads/tsan.c \ + libffi.vector/vector.exp libffi.vector/ffitest.h libffi.vector/vector.h \ + libffi.vector/vector_float32x4.c libffi.vector/vector_float32x2.c \ + libffi.vector/vector_double2.c libffi.vector/vector_int32x4.c \ + libffi.vector/vector_args_spill.c libffi.vector/vector_vec3.c \ + libffi.vector/vector_double4.c libffi.vector/vector_hva.c \ + libffi.vector/cls_vector.c libffi.vector/vector_validate.c all: all-am diff --git a/deps/libffi/testsuite/libffi.call/closure_thiscall_fastcall_pop.c b/deps/libffi/testsuite/libffi.call/closure_thiscall_fastcall_pop.c new file mode 100644 index 000000000000..9cc0b091943f --- /dev/null +++ b/deps/libffi/testsuite/libffi.call/closure_thiscall_fastcall_pop.c @@ -0,0 +1,131 @@ +/* Area: closure, ffi_prep_closure_loc + Purpose: Check i386 THISCALL/FASTCALL closures pop the stack correctly. + Limitations: i386 + GNU inline asm only; a no-op elsewhere. + PR: none. + Originator: i386 closure stack-pop accounting regression. + + THISCALL and FASTCALL are callee-clean: the closure must remove its + stack-resident arguments on return (ret $n). When a 64-bit integer or + a struct argument is placed on the stack, the closure return path used + to compute the pop as cif->bytes - narg_reg*4 with narg_reg force-bumped + to 2, discounting register slots that were never used and under-popping + the stack. A caller that relies on callee cleanup is then left with the + argument bytes where its return address should be. + + This test invokes the generated closure through a minimal callee-clean + call site and checks that ESP is balanced across the call (delta 0). + Without the fix the delta is 8 (FASTCALL uint64) or 4 (THISCALL). */ + +/* { dg-do run } */ +#include "ffitest.h" + +#if defined(__i386__) && defined(__GNUC__) && !defined(__APPLE__) + +static uint64_t received; +static int ran; + +static void +cb (ffi_cif *cif, void *resp, void **args, void *userdata) +{ + (void) cif; (void) resp; (void) userdata; + received = *(uint64_t *) args[cif->nargs - 1]; + ran++; +} + +/* Push an 8-byte stack argument, load ECX (the thiscall "this" register, + ignored by the fastcall callee), call the closure, and return how many + bytes the callee under-popped (0 == it popped exactly what was pushed). + + Every operand is read into a register up front, while ESP is still at + its incoming value, so nothing is referenced through an ESP-relative + memory operand after we start moving ESP (which would otherwise read a + stale slot, on clang at -O2 in particular). The stack is then 16-byte + aligned at the call as the i386 psABI requires, so the -O2-built closure + body may use aligned SSE without faulting; the alignment cancels out of + the delta. ESP is restored to its exact incoming value before the delta + is stored, so a wrong pop cannot corrupt our frame. Not using EBX keeps + this compatible with -fPIC; the delta is returned via memory so no free + register is needed for it. */ +static int +esp_delta (void *code, uint64_t stackarg, unsigned ecxv) +{ + unsigned delta; + unsigned lo = (unsigned) stackarg; + unsigned hi = (unsigned) (stackarg >> 32); + __asm__ volatile ( + "movl %[lo], %%eax\n\t" /* stash all operands in registers */ + "movl %[hi], %%edx\n\t" /* before ESP moves */ + "movl %[code], %%edi\n\t" + "movl %[ecxv], %%ecx\n\t" /* thiscall 'this' */ + "movl %%esp, %%esi\n\t" /* remember the real esp */ + "andl $-16, %%esp\n\t" /* 16-byte align, then bias by the */ + "subl $8, %%esp\n\t" /* 8 arg bytes so 'call' is 0 mod 16 */ + "pushl %%edx\n\t" /* high dword */ + "pushl %%eax\n\t" /* low dword */ + "calll *%%edi\n\t" + "movl %%esi, %%eax\n\t" /* recompute esp just before the */ + "andl $-16, %%eax\n\t" /* pushes... */ + "subl $8, %%eax\n\t" + "subl %%esp, %%eax\n\t" /* eax = under-popped byte count */ + "movl %%esi, %%esp\n\t" /* restore before touching memory */ + "movl %%eax, %[delta]\n\t" + : [delta] "=m" (delta) + : [lo] "m" (lo), [hi] "m" (hi), [code] "m" (code), [ecxv] "m" (ecxv) + : "memory", "cc", "eax", "ecx", "edx", "esi", "edi"); + return (int) delta; +} + +static int +check_abi (ffi_abi abi, unsigned nargs, ffi_type **atypes, unsigned ecx) +{ + ffi_cif cif; + ffi_closure *closure; + void *code; + int delta; + + closure = ffi_closure_alloc (sizeof (ffi_closure), &code); + CHECK (closure != NULL); + CHECK (ffi_prep_cif (&cif, abi, nargs, &ffi_type_void, atypes) == FFI_OK); + CHECK (ffi_prep_closure_loc (closure, &cif, cb, NULL, code) == FFI_OK); + + ran = 0; + received = 0; + delta = esp_delta (code, 0x1122334455667788ULL, ecx); + + CHECK (ran == 1); + CHECK (received == 0x1122334455667788ULL); + ffi_closure_free (closure); + return delta; +} + +int +main (void) +{ + ffi_type *fastcall_args[1] = { &ffi_type_uint64 }; + ffi_type *thiscall_args[2] = { &ffi_type_pointer, &ffi_type_uint64 }; + int d; + + /* FASTCALL void cb(uint64_t): the uint64 is stack-resident; pop must be 8. */ + d = check_abi (FFI_FASTCALL, 1, fastcall_args, 0); + printf ("FASTCALL uint64 esp delta: %d\n", d); + CHECK (d == 0); + + /* THISCALL void cb(void*, uint64_t): 'this' in ECX, uint64 on the stack; + pop must be 8 (not 4). */ + d = check_abi (FFI_THISCALL, 2, thiscall_args, 0xdeadbeef); + printf ("THISCALL this+uint64 esp delta: %d\n", d); + CHECK (d == 0); + + exit (0); +} + +#else + +int +main (void) +{ + /* Not an i386 GNU target: nothing to check here. */ + exit (0); +} + +#endif diff --git a/deps/libffi/testsuite/libffi.call/many_large_structs.c b/deps/libffi/testsuite/libffi.call/many_large_structs.c new file mode 100644 index 000000000000..9f766a9113c6 --- /dev/null +++ b/deps/libffi/testsuite/libffi.call/many_large_structs.c @@ -0,0 +1,88 @@ +/* Area: ffi_call + Purpose: Pass many large by-value structs on AArch64. + Limitations: none. + PR: none. + Originator: AArch64 large-struct stack accounting regression. + + Regression test: on AArch64, composites larger than 16 bytes are passed + by invisible reference. ffi_call copies each payload into the argument + slab (growing down from the top) and, once X0-X7 are exhausted, also + spills the by-ref pointer into the same slab (the NSAA, growing up). + The generic prep_cif budget in cif->bytes only charged the payload copy, + not the 8-byte pointer slot, so with enough large structs the two regions + collided and a later payload copy overwrote an already-spilled pointer, + leaving the callee with a corrupt pointer for a by-value argument. + Passing sixteen 32-byte (non-HFA) structs by value -- eight more than the + argument registers -- must marshal every argument intact. */ + +/* { dg-do run } */ +#include "ffitest.h" + +#define NARGS 16 +#define SSIZE 32 + +typedef struct { unsigned char b[SSIZE]; } big_struct; + +/* Sum every byte of every argument. A corrupted by-ref pointer makes the + callee read the wrong memory, so the sum no longer matches. */ +static int ABI_ATTR +sum_bytes (big_struct s0, big_struct s1, big_struct s2, big_struct s3, + big_struct s4, big_struct s5, big_struct s6, big_struct s7, + big_struct s8, big_struct s9, big_struct s10, big_struct s11, + big_struct s12, big_struct s13, big_struct s14, big_struct s15) +{ + big_struct *all[NARGS]; + int i, j, sum = 0; + + all[0] = &s0; all[1] = &s1; all[2] = &s2; all[3] = &s3; + all[4] = &s4; all[5] = &s5; all[6] = &s6; all[7] = &s7; + all[8] = &s8; all[9] = &s9; all[10] = &s10; all[11] = &s11; + all[12] = &s12; all[13] = &s13; all[14] = &s14; all[15] = &s15; + + for (i = 0; i < NARGS; i++) + for (j = 0; j < SSIZE; j++) + sum += all[i]->b[j]; + + return sum; +} + +int main (void) +{ + ffi_cif cif; + ffi_type *args[NARGS]; + void *values[NARGS]; + ffi_type bs_type; + ffi_type *bs_elements[SSIZE + 1]; + big_struct in[NARGS]; + ffi_arg result = 0; + int i, j, expected = 0; + + bs_type.size = 0; + bs_type.alignment = 0; + bs_type.type = FFI_TYPE_STRUCT; + for (i = 0; i < SSIZE; i++) + bs_elements[i] = &ffi_type_uchar; + bs_elements[SSIZE] = NULL; + bs_type.elements = bs_elements; + + /* Fill struct i with the distinct byte value (i + 1) so any pointer + mix-up between arguments changes the total. */ + for (i = 0; i < NARGS; i++) + { + for (j = 0; j < SSIZE; j++) + { + in[i].b[j] = (unsigned char) (i + 1); + expected += (i + 1); + } + args[i] = &bs_type; + values[i] = &in[i]; + } + + CHECK(ffi_prep_cif(&cif, ABI_NUM, NARGS, &ffi_type_sint, args) == FFI_OK); + + ffi_call(&cif, FFI_FN(sum_bytes), &result, values); + + CHECK((int) result == expected); + + exit(0); +} diff --git a/deps/libffi/testsuite/libffi.call/plan_size.c b/deps/libffi/testsuite/libffi.call/plan_size.c new file mode 100644 index 000000000000..b8398fbee991 --- /dev/null +++ b/deps/libffi/testsuite/libffi.call/plan_size.c @@ -0,0 +1,77 @@ +/* Area: ffi_call_plan_size + Purpose: Check that a plan reports its own allocation size, that the + size is stable across invocations, and that a NULL plan has + no footprint. + Limitations: The exact byte count is implementation defined, so this only + checks the invariants callers may rely on. + PR: none. + Originator: ffi_call_plan tests */ + +/* { dg-do run } */ +#include "ffitest.h" + +static uint64_t gp2(uint64_t a, uint64_t b) +{ + return a + b * 2; +} + +static uint64_t gp6(uint64_t a, uint64_t b, uint64_t c, + uint64_t d, uint64_t e, uint64_t f) +{ + return a + b * 2 + c * 3 + d * 4 + e * 5 + f * 6; +} + +int main (void) +{ + ffi_cif cif2, cif6; + ffi_type *args[6]; + void *values[6]; + ffi_call_plan *plan2, *plan6; + size_t size2, size6; + uint64_t a[6], r; + int i; + + for (i = 0; i < 6; i++) + { + args[i] = &ffi_type_uint64; + a[i] = (uint64_t) (i + 1); + values[i] = &a[i]; + } + + CHECK(ffi_prep_cif(&cif2, FFI_DEFAULT_ABI, 2, &ffi_type_uint64, args) + == FFI_OK); + CHECK(ffi_prep_cif(&cif6, FFI_DEFAULT_ABI, 6, &ffi_type_uint64, args) + == FFI_OK); + + /* A NULL plan has no footprint, mirroring ffi_call_plan_free(NULL). */ + CHECK(ffi_call_plan_size(NULL) == 0); + + plan2 = ffi_call_plan_alloc(&cif2); + CHECK(plan2 != NULL); + plan6 = ffi_call_plan_alloc(&cif6); + CHECK(plan6 != NULL); + + size2 = ffi_call_plan_size(plan2); + size6 = ffi_call_plan_size(plan6); + + /* Every plan owns at least its handle, and a wider signature never needs + less memory than a narrower one of the same shape. Targets without a + fast path report the same constant for both. */ + CHECK(size2 > 0); + CHECK(size6 >= size2); + + /* The plan is immutable, so querying it must not disturb invocation and + the reported size must not drift across calls. */ + ffi_call_plan_invoke(plan6, FFI_FN(gp6), &r, values); + CHECK(r == gp6(a[0], a[1], a[2], a[3], a[4], a[5])); + CHECK(ffi_call_plan_size(plan6) == size6); + + ffi_call_plan_invoke(plan2, FFI_FN(gp2), &r, values); + CHECK(r == gp2(a[0], a[1])); + CHECK(ffi_call_plan_size(plan2) == size2); + + ffi_call_plan_free(plan2); + ffi_call_plan_free(plan6); + + exit(0); +} diff --git a/deps/libffi/testsuite/libffi.vector/cls_vector.c b/deps/libffi/testsuite/libffi.vector/cls_vector.c new file mode 100644 index 000000000000..18d8806b51b5 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/cls_vector.c @@ -0,0 +1,67 @@ +/* Area: closure_call + Purpose: A closure that receives two vector arguments (and a scalar) and + returns a vector. Exercises the closure argument-extraction and + vector return paths. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef float f32x4 __attribute__((vector_size (16))); + +static void +cls_vector_fn (ffi_cif *cif __UNUSED__, void *resp, void **args, + void *userdata __UNUSED__) +{ + f32x4 a = *(f32x4 *) args[0]; + f32x4 b = *(f32x4 *) args[1]; + int scale = *(int *) args[2]; + f32x4 *r = (f32x4 *) resp; + + *r = (a + b) * (float) scale; +} + +typedef f32x4 (*cls_vector_t) (f32x4, f32x4, int); + +int +main (void) +{ + ffi_cif cif; + void *code; + ffi_closure *pcl = ffi_closure_alloc (sizeof (ffi_closure), &code); + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type *arg_types[3]; + f32x4 a = { 1, 2, 3, 4 }; + f32x4 b = { 10, 20, 30, 40 }; + f32x4 res; + int scale = 2; + int i; + + CHECK (pcl != NULL); + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 4); + + arg_types[0] = &vec_type; + arg_types[1] = &vec_type; + arg_types[2] = &ffi_type_sint; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 3, &vec_type, arg_types) + == FFI_OK); + CHECK (ffi_prep_closure_loc (pcl, &cif, cls_vector_fn, NULL, code) + == FFI_OK); + + res = ((cls_vector_t) code) (a, b, scale); + + for (i = 0; i < 4; i++) + { + float want = (a[i] + b[i]) * (float) scale; + printf ("res[%d] = %g (want %g)\n", i, (double) res[i], (double) want); + CHECK (res[i] == want); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/ffitest.h b/deps/libffi/testsuite/libffi.vector/ffitest.h new file mode 100644 index 000000000000..d27d362d6a6e --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/ffitest.h @@ -0,0 +1 @@ +#include "../libffi.call/ffitest.h" diff --git a/deps/libffi/testsuite/libffi.vector/vector.exp b/deps/libffi/testsuite/libffi.vector/vector.exp new file mode 100644 index 000000000000..a76957ee4d33 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector.exp @@ -0,0 +1,59 @@ +# Copyright (C) 2026 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING3. If not see +# . + +dg-init +libffi-init + +global srcdir subdir + +# The tests are written with the GCC/Clang vector extension +# (__attribute__ ((vector_size (N)))). A target port can support +# FFI_TYPE_VECTOR at the ABI level while the compiler under test (e.g. +# MSVC) cannot compile that syntax, so probe the compiler with an actual +# compilation, not just a preprocessor check. +proc libffi_vector_syntax_test { } { + set src "vecprobe[pid].c" + set obj "vecprobe[pid].o" + + set f [open $src "w"] + puts $f "typedef float probe_v4 __attribute__ ((vector_size (16)));" + puts $f "probe_v4 probe_var;" + puts $f "int main (void) { return 0; }" + close $f + + set lines [libffi_target_compile $src $obj object ""] + file delete $src + file delete $obj + + return [string match "" $lines] +} + +set tlist [lsort [glob -nocomplain -- $srcdir/$subdir/*.{c,cc}]] + +if { [libffi_feature_test "#ifdef FFI_TARGET_HAS_VECTOR_TYPE"] + && [libffi_vector_syntax_test] } { + run-many-tests $tlist "" +} else { + foreach test $tlist { + unsupported "$test" + } +} + +dg-finish + +# Local Variables: +# tcl-indent-level:4 +# End: diff --git a/deps/libffi/testsuite/libffi.vector/vector.h b/deps/libffi/testsuite/libffi.vector/vector.h new file mode 100644 index 000000000000..7baf832d37e4 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector.h @@ -0,0 +1,32 @@ +/* -*-c-*- */ +/* Shared helpers for the libffi vector (SIMD) tests. + + Vectors are built with the portable GCC/Clang spelling + __attribute__((vector_size (N))) so the tests compile on both compilers. + A vector ffi_type is described exactly like a struct, except every element + points at the SAME scalar ffi_type and the count is the lane count; the + caller leaves size and alignment at zero and libffi computes them. */ + +#ifndef LIBFFI_VECTOR_H +#define LIBFFI_VECTOR_H + +#include "ffitest.h" + +/* Build (into the caller-provided ELEMS array of length COUNT + 1 and the + ffi_type object TY) a vector type descriptor of COUNT lanes of scalar type + ELEM. ELEMS must have room for COUNT + 1 pointers (NULL terminator). */ +static inline void +make_vector_type (ffi_type *ty, ffi_type **elems, ffi_type *elem, + unsigned count) +{ + unsigned i; + for (i = 0; i < count; i++) + elems[i] = elem; + elems[count] = NULL; + ty->size = 0; + ty->alignment = 0; + ty->type = FFI_TYPE_VECTOR; + ty->elements = elems; +} + +#endif /* LIBFFI_VECTOR_H */ diff --git a/deps/libffi/testsuite/libffi.vector/vector_args_spill.c b/deps/libffi/testsuite/libffi.vector/vector_args_spill.c new file mode 100644 index 000000000000..dec6ab2e62af --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_args_spill.c @@ -0,0 +1,85 @@ +/* Area: ffi_call + Purpose: Pass many vector arguments interleaved with scalars, enough to + exhaust the vector argument registers and spill onto the stack. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef float f32x4 __attribute__((vector_size (16))); + +/* Ten vectors exceeds the 8 vector argument registers on both AArch64 and + x86-64, so v8/v9 are passed on the stack. The scalars are interleaved to + make sure the two register files advance independently. */ +static float +mix (int i0, f32x4 v0, f32x4 v1, double d0, f32x4 v2, f32x4 v3, + f32x4 v4, int i1, f32x4 v5, f32x4 v6, f32x4 v7, double d1, + f32x4 v8, f32x4 v9) +{ + float acc = 0; + acc += 1 * v0[0] + v0[3]; + acc += 2 * v1[0] + v1[3]; + acc += 3 * v2[0] + v2[3]; + acc += 4 * v3[0] + v3[3]; + acc += 5 * v4[0] + v4[3]; + acc += 6 * v5[0] + v5[3]; + acc += 7 * v6[0] + v6[3]; + acc += 8 * v7[0] + v7[3]; + acc += 9 * v8[0] + v8[3]; + acc += 10 * v9[0] + v9[3]; + acc += i0 + i1 + (float) d0 + (float) d1; + return acc; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type *args[14]; + void *values[14]; + f32x4 v[10]; + int i0 = 100, i1 = 7; + double d0 = 3.5, d1 = 0.25; + float r, ref; + unsigned k; + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 4); + + for (k = 0; k < 10; k++) + { + f32x4 t = { (float) (k + 1), 0, 0, (float) (100 + k) }; + v[k] = t; + } + + args[0] = &ffi_type_sint; values[0] = &i0; + args[1] = &vec_type; values[1] = &v[0]; + args[2] = &vec_type; values[2] = &v[1]; + args[3] = &ffi_type_double; values[3] = &d0; + args[4] = &vec_type; values[4] = &v[2]; + args[5] = &vec_type; values[5] = &v[3]; + args[6] = &vec_type; values[6] = &v[4]; + args[7] = &ffi_type_sint; values[7] = &i1; + args[8] = &vec_type; values[8] = &v[5]; + args[9] = &vec_type; values[9] = &v[6]; + args[10] = &vec_type; values[10] = &v[7]; + args[11] = &ffi_type_double; values[11] = &d1; + args[12] = &vec_type; values[12] = &v[8]; + args[13] = &vec_type; values[13] = &v[9]; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 14, &ffi_type_float, args) + == FFI_OK); + + ffi_call (&cif, FFI_FN (mix), &r, values); + + ref = mix (i0, v[0], v[1], d0, v[2], v[3], v[4], i1, v[5], v[6], v[7], + d1, v[8], v[9]); + printf ("r = %g (want %g)\n", (double) r, (double) ref); + CHECK (r == ref); + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_double2.c b/deps/libffi/testsuite/libffi.vector/vector_double2.c new file mode 100644 index 000000000000..dd45878b5afe --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_double2.c @@ -0,0 +1,53 @@ +/* Area: ffi_call + Purpose: Pass and return a 16-byte double2 vector (single Q/SSE reg). + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef double d2 __attribute__((vector_size (16))); + +static d2 +add_d2 (d2 a, d2 b) +{ + return a + b; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[3]; + ffi_type *args[2]; + void *values[2]; + d2 a = { 1.5, 2.5 }; + d2 b = { 10.0, 20.0 }; + d2 r, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_double, 2); + + args[0] = &vec_type; + args[1] = &vec_type; + values[0] = &a; + values[1] = &b; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 2, &vec_type, args) == FFI_OK); + CHECK (vec_type.size == 16); + CHECK (vec_type.alignment == 16); + + ffi_call (&cif, FFI_FN (add_d2), &r, values); + + ref = add_d2 (a, b); + for (i = 0; i < 2; i++) + { + printf ("r[%d] = %g (want %g)\n", i, r[i], ref[i]); + CHECK (r[i] == ref[i]); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_double4.c b/deps/libffi/testsuite/libffi.vector/vector_double4.c new file mode 100644 index 000000000000..9f4473935929 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_double4.c @@ -0,0 +1,81 @@ +/* Area: ffi_call + Purpose: A 32-byte double4 vector. On AArch64 a bare vector wider than + 16 bytes is passed by reference and returned in memory (no + short-vector register class), so the call must round-trip. On + x86-64 wider-than-16-byte vectors are not implemented, so + ffi_prep_cif must report FFI_BAD_TYPEDEF. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef double d4 __attribute__((vector_size (32))); + +/* Only called on ports that can actually marshal a 32-byte vector. */ +static d4 add_d4 (d4 a, d4 b) __UNUSED__; + +static d4 +add_d4 (d4 a, d4 b) +{ + return a + b; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type *args[2]; + + make_vector_type (&vec_type, vec_elems, &ffi_type_double, 4); + args[0] = &vec_type; + args[1] = &vec_type; + +#if defined(__aarch64__) || defined(_M_ARM64) + { + void *values[2]; + d4 a = { 1, 2, 3, 4 }; + d4 b = { 10, 20, 30, 40 }; + d4 r, ref; + int i; + + values[0] = &a; + values[1] = &b; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 2, &vec_type, args) == FFI_OK); + CHECK (vec_type.size == 32); + CHECK (vec_type.alignment == 16); + + ffi_call (&cif, FFI_FN (add_d4), &r, values); + + ref = add_d4 (a, b); + for (i = 0; i < 4; i++) + { + printf ("r[%d] = %g (want %g)\n", i, r[i], ref[i]); + CHECK (r[i] == ref[i]); + } + } +#else + { + /* x86-64 (and any other opted-in port without >16B support): the >16-byte + vector must be rejected, both as a return type and as an argument. */ + ffi_status s_ret, s_arg; + + s_ret = ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 0, &vec_type, NULL); + printf ("32-byte vector return: status %d (want %d = FFI_BAD_TYPEDEF)\n", + s_ret, FFI_BAD_TYPEDEF); + CHECK (s_ret == FFI_BAD_TYPEDEF); + + s_arg = ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args); + printf ("32-byte vector argument: status %d (want %d = FFI_BAD_TYPEDEF)\n", + s_arg, FFI_BAD_TYPEDEF); + CHECK (s_arg == FFI_BAD_TYPEDEF); + } +#endif + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_float32x2.c b/deps/libffi/testsuite/libffi.vector/vector_float32x2.c new file mode 100644 index 000000000000..f613687a2988 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_float32x2.c @@ -0,0 +1,53 @@ +/* Area: ffi_call + Purpose: Pass and return an 8-byte float32x2 vector (single D/SSE reg). + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef float f32x2 __attribute__((vector_size (8))); + +static f32x2 +add_f32x2 (f32x2 a, f32x2 b) +{ + return a + b; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[3]; + ffi_type *args[2]; + void *values[2]; + f32x2 a = { 3, 4 }; + f32x2 b = { 5, 6 }; + f32x2 r, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 2); + + args[0] = &vec_type; + args[1] = &vec_type; + values[0] = &a; + values[1] = &b; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 2, &vec_type, args) == FFI_OK); + CHECK (vec_type.size == 8); + CHECK (vec_type.alignment == 8); + + ffi_call (&cif, FFI_FN (add_f32x2), &r, values); + + ref = add_f32x2 (a, b); + for (i = 0; i < 2; i++) + { + printf ("r[%d] = %g (want %g)\n", i, (double) r[i], (double) ref[i]); + CHECK (r[i] == ref[i]); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_float32x4.c b/deps/libffi/testsuite/libffi.vector/vector_float32x4.c new file mode 100644 index 000000000000..971814aaf66c --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_float32x4.c @@ -0,0 +1,54 @@ +/* Area: ffi_call + Purpose: Pass and return a 16-byte float32x4 vector (the vec4 shape of + libffi/libffi#773). + Limitations: none. + PR: libffi/libffi#773. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef float f32x4 __attribute__((vector_size (16))); + +static f32x4 +add_f32x4 (f32x4 a, f32x4 b) +{ + return a + b; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type *args[2]; + void *values[2]; + f32x4 a = { 1, 2, 3, 4 }; + f32x4 b = { 10, 20, 30, 40 }; + f32x4 r, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 4); + + args[0] = &vec_type; + args[1] = &vec_type; + values[0] = &a; + values[1] = &b; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 2, &vec_type, args) == FFI_OK); + CHECK (vec_type.size == 16); + CHECK (vec_type.alignment == 16); + + ffi_call (&cif, FFI_FN (add_f32x4), &r, values); + + ref = add_f32x4 (a, b); + for (i = 0; i < 4; i++) + { + printf ("r[%d] = %g (want %g)\n", i, (double) r[i], (double) ref[i]); + CHECK (r[i] == ref[i]); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_hva.c b/deps/libffi/testsuite/libffi.vector/vector_hva.c new file mode 100644 index 000000000000..5f80da07dfde --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_hva.c @@ -0,0 +1,74 @@ +/* Area: ffi_call + Purpose: Pass and return a homogeneous vector aggregate: a struct of two + identical 16-byte vectors. On AArch64 this is an HVA carried in + a pair of Q registers; on x86-64 the existing SSE struct + classification handles it (four SSE eightbytes). Both round-trip. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef float f32x4 __attribute__((vector_size (16))); + +struct hva2 +{ + f32x4 a; + f32x4 b; +}; + +static struct hva2 +bump (struct hva2 s) +{ + s.a = s.a + 1; + s.b = s.b + 2; + return s; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type struct_type; + ffi_type *struct_elems[3]; + ffi_type *args[1]; + void *values[1]; + struct hva2 in = { { 1, 2, 3, 4 }, { 10, 20, 30, 40 } }; + struct hva2 out, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 4); + + struct_elems[0] = &vec_type; + struct_elems[1] = &vec_type; + struct_elems[2] = NULL; + struct_type.size = 0; + struct_type.alignment = 0; + struct_type.type = FFI_TYPE_STRUCT; + struct_type.elements = struct_elems; + + args[0] = &struct_type; + values[0] = ∈ + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &struct_type, args) + == FFI_OK); + CHECK (struct_type.size == 32); + + ffi_call (&cif, FFI_FN (bump), &out, values); + + ref = bump (in); + for (i = 0; i < 4; i++) + { + printf ("a[%d] = %g (want %g), b[%d] = %g (want %g)\n", + i, (double) out.a[i], (double) ref.a[i], + i, (double) out.b[i], (double) ref.b[i]); + CHECK (out.a[i] == ref.a[i]); + CHECK (out.b[i] == ref.b[i]); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_int32x4.c b/deps/libffi/testsuite/libffi.vector/vector_int32x4.c new file mode 100644 index 000000000000..eaa6b802bab5 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_int32x4.c @@ -0,0 +1,54 @@ +/* Area: ffi_call + Purpose: Pass and return a 16-byte int32x4 integer vector. Integer + lanes still travel in a vector register, unlike an HFA of ints. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +typedef int i32x4 __attribute__((vector_size (16))); + +static i32x4 +add_i32x4 (i32x4 a, i32x4 b) +{ + return a + b; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[5]; + ffi_type *args[2]; + void *values[2]; + i32x4 a = { 1, 2, 3, 4 }; + i32x4 b = { 5, 6, 7, 8 }; + i32x4 r, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_sint32, 4); + + args[0] = &vec_type; + args[1] = &vec_type; + values[0] = &a; + values[1] = &b; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 2, &vec_type, args) == FFI_OK); + CHECK (vec_type.size == 16); + CHECK (vec_type.alignment == 16); + + ffi_call (&cif, FFI_FN (add_i32x4), &r, values); + + ref = add_i32x4 (a, b); + for (i = 0; i < 4; i++) + { + printf ("r[%d] = %d (want %d)\n", i, r[i], ref[i]); + CHECK (r[i] == ref[i]); + } + + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_validate.c b/deps/libffi/testsuite/libffi.vector/vector_validate.c new file mode 100644 index 000000000000..d923ab465fe4 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_validate.c @@ -0,0 +1,103 @@ +/* Area: ffi_prep_cif + Purpose: Validate that malformed vector type descriptors are rejected + with FFI_BAD_TYPEDEF, and that a well-formed vector is accepted + with the computed power-of-two size and min(size,16) alignment. + Limitations: none. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +int +main (void) +{ + ffi_cif cif; + + /* Heterogeneous lanes (float mixed with double) -> FFI_BAD_TYPEDEF. */ + { + ffi_type vt; + ffi_type *elems[3]; + ffi_type *args[1]; + elems[0] = &ffi_type_float; + elems[1] = &ffi_type_double; + elems[2] = NULL; + vt.size = 0; + vt.alignment = 0; + vt.type = FFI_TYPE_VECTOR; + vt.elements = elems; + args[0] = &vt; + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args) + == FFI_BAD_TYPEDEF); + } + + /* An empty (zero-lane) vector -> FFI_BAD_TYPEDEF. */ + { + ffi_type vt; + ffi_type *elems[1]; + ffi_type *args[1]; + elems[0] = NULL; + vt.size = 0; + vt.alignment = 0; + vt.type = FFI_TYPE_VECTOR; + vt.elements = elems; + args[0] = &vt; + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args) + == FFI_BAD_TYPEDEF); + } + + /* A non-scalar (struct) lane type -> FFI_BAD_TYPEDEF. */ + { + ffi_type inner; + ffi_type *inner_elems[2]; + ffi_type vt; + ffi_type *elems[3]; + ffi_type *args[1]; + inner_elems[0] = &ffi_type_float; + inner_elems[1] = NULL; + inner.size = 0; + inner.alignment = 0; + inner.type = FFI_TYPE_STRUCT; + inner.elements = inner_elems; + elems[0] = &inner; + elems[1] = &inner; + elems[2] = NULL; + vt.size = 0; + vt.alignment = 0; + vt.type = FFI_TYPE_VECTOR; + vt.elements = elems; + args[0] = &vt; + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args) + == FFI_BAD_TYPEDEF); + } + + /* A well-formed 3 x float vector is accepted with computed layout. */ + { + ffi_type vt; + ffi_type *elems[4]; + ffi_type *args[1]; + make_vector_type (&vt, elems, &ffi_type_float, 3); + args[0] = &vt; + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args) + == FFI_OK); + CHECK (vt.size == 16); /* 12 rounded up to 16 */ + CHECK (vt.alignment == 16); /* min(16, 16) */ + } + + /* An 8-byte vector gets alignment 8 = min(8, 16). */ + { + ffi_type vt; + ffi_type *elems[3]; + ffi_type *args[1]; + make_vector_type (&vt, elems, &ffi_type_float, 2); + args[0] = &vt; + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &ffi_type_void, args) + == FFI_OK); + CHECK (vt.size == 8); + CHECK (vt.alignment == 8); + } + + printf ("vector validation ok\n"); + exit (0); +} diff --git a/deps/libffi/testsuite/libffi.vector/vector_vec3.c b/deps/libffi/testsuite/libffi.vector/vector_vec3.c new file mode 100644 index 000000000000..5a0367283023 --- /dev/null +++ b/deps/libffi/testsuite/libffi.vector/vector_vec3.c @@ -0,0 +1,73 @@ +/* Area: ffi_call + Purpose: Pass and return a three-lane float vector. Clang's + ext_vector_type(3) has 12 bytes of data padded to 16-byte + storage; libffi's power-of-two size rule must reproduce that + layout so a natively compiled callee agrees. + Limitations: Clang only (GCC's vector_size requires power-of-two totals and + rejects a 12-byte vector). A no-op on other compilers. + PR: none. + Originator: libffi vector support. */ + +/* { dg-do run } */ + +#include "vector.h" + +#ifdef __clang__ + +typedef float f3 __attribute__((ext_vector_type (3))); + +static f3 +scale3 (f3 v) +{ + f3 r; + r[0] = v[0] + 1; + r[1] = v[1] + 2; + r[2] = v[2] + 3; + return r; +} + +int +main (void) +{ + ffi_cif cif; + ffi_type vec_type; + ffi_type *vec_elems[4]; + ffi_type *args[1]; + void *values[1]; + f3 a = { 10, 20, 30 }; + f3 r, ref; + int i; + + make_vector_type (&vec_type, vec_elems, &ffi_type_float, 3); + + args[0] = &vec_type; + values[0] = &a; + + CHECK (ffi_prep_cif (&cif, FFI_DEFAULT_ABI, 1, &vec_type, args) == FFI_OK); + /* 3 x float = 12, rounded up to 16 (matches ext_vector_type storage). */ + CHECK (vec_type.size == 16); + CHECK (vec_type.alignment == 16); + CHECK (sizeof (f3) == 16); + + ffi_call (&cif, FFI_FN (scale3), &r, values); + + ref = scale3 (a); + for (i = 0; i < 3; i++) + { + printf ("r[%d] = %g (want %g)\n", i, (double) r[i], (double) ref[i]); + CHECK (r[i] == ref[i]); + } + + exit (0); +} + +#else + +int +main (void) +{ + /* ext_vector_type is a Clang extension; nothing to test elsewhere. */ + exit (0); +} + +#endif diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 1aadcf67bffe..fb7446578f57 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -396,15 +396,7 @@ std::optional CryptoErrorList::pop_front() { // ============================================================================ DataPointer DataPointer::Alloc(size_t len) { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement OPENSSL_zalloc - auto ptr = OPENSSL_malloc(len); - if (ptr == nullptr) return {}; - memset(ptr, 0, len); - return DataPointer(ptr, len); -#else return DataPointer(OPENSSL_zalloc(len), len); -#endif } DataPointer DataPointer::SecureAlloc(size_t len) { @@ -427,18 +419,11 @@ DataPointer DataPointer::SecureAlloc(size_t len) { } size_t DataPointer::GetSecureHeapUsed() { -#ifndef OPENSSL_IS_BORINGSSL return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; -#else - // BoringSSL does not have the secure heap and therefore - // will always return 0. - return 0; -#endif } DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, size_t min) { -#ifndef OPENSSL_IS_BORINGSSL switch (CRYPTO_secure_malloc_init(amount, min)) { case 0: return InitSecureHeapResult::FAILED; @@ -449,10 +434,6 @@ DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, default: return InitSecureHeapResult::FAILED; } -#else - // BoringSSL does not actually support the secure heap - return InitSecureHeapResult::FAILED; -#endif } DataPointer DataPointer::Copy(const Buffer& buffer) { @@ -539,8 +520,7 @@ bool setFipsEnabled(bool enable, CryptoErrorList* errors) { if (isFipsEnabled() == enable) return true; ClearErrorOnReturn clearErrorOnReturn(errors); #if OPENSSL_VERSION_MAJOR >= 3 - return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && - EVP_default_properties_is_fips_enabled(nullptr); + return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; #else return FIPS_mode_set(enable ? 1 : 0) == 1; #endif @@ -581,12 +561,7 @@ BignumPointer BignumPointer::New() { } BignumPointer BignumPointer::NewSecure() { -#ifdef OPENSSL_IS_BORINGSSL - // Boringssl does not implement BN_secure_new. - return New(); -#else return BignumPointer(BN_secure_new()); -#endif } BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept { @@ -2277,14 +2252,11 @@ DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; } -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define DH_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE if (codes & DH_CHECK_PUBKEY_TOO_SMALL) { return DHPointer::CheckPublicKeyResult::TOO_SMALL; } else if (codes & DH_CHECK_PUBKEY_TOO_LARGE) { return DHPointer::CheckPublicKeyResult::TOO_LARGE; } -#endif if (codes != 0) { return DHPointer::CheckPublicKeyResult::INVALID; } @@ -4289,59 +4261,6 @@ std::optional SSLPointer::verifyPeerCertificate() const { return std::nullopt; } -const char* SSLPointer::getClientHelloAlpn() const { - if (ssl_ == nullptr) return {}; -#ifndef OPENSSL_IS_BORINGSSL - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext( - get(), - TLSEXT_TYPE_application_layer_protocol_negotiation, - &buf, - &rem) || - rem < 2) { - return {}; - } - - len = (buf[0] << 8) | buf[1]; - if (len + 2 != rem) return {}; - return reinterpret_cast(buf + 3); -#else - // Boringssl doesn't have a public API for this. - return {}; -#endif -} - -const char* SSLPointer::getClientHelloServerName() const { - if (ssl_ == nullptr) return {}; -#ifndef OPENSSL_IS_BORINGSSL - const unsigned char* buf; - size_t len; - size_t rem; - - if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || - rem <= 2) { - return {}; - } - - len = (*buf << 8) | *(buf + 1); - if (len + 2 != rem) return {}; - rem = len; - - if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; - rem--; - if (rem <= 2) return {}; - len = (*(buf + 3) << 8) | *(buf + 4); - if (len + 2 > rem) return {}; - return reinterpret_cast(buf + 5); -#else - // Boringssl doesn't have a public API for this. - return {}; -#endif -} - std::optional SSLPointer::GetServerName( const SSL* ssl) { if (ssl == nullptr) return std::nullopt; @@ -4387,6 +4306,13 @@ std::optional SSLPointer::getNegotiatedGroup() const { const char* group = SSL_get0_group_name(get()); if (group == nullptr) return std::nullopt; return group; +#elif defined(OPENSSL_IS_BORINGSSL) + if (!ssl_) return std::nullopt; + const int nid = SSL_get_negotiated_group(get()); + if (nid == NID_undef) return std::nullopt; + const char* group = OBJ_nid2sn(nid); + if (group == nullptr) return std::nullopt; + return group; #else return std::nullopt; #endif @@ -4411,19 +4337,17 @@ std::optional SSLPointer::getCipherVersion() const { } std::optional SSLPointer::getSecurityLevel() { -#ifndef OPENSSL_IS_BORINGSSL auto ctx = SSLCtxPointer::New(); if (!ctx) return std::nullopt; +#ifdef OPENSSL_IS_BORINGSSL + return SSL_CTX_get_security_level(ctx.get()); +#else auto ssl = SSLPointer::New(ctx); if (!ssl) return std::nullopt; return SSL_get_security_level(ssl); -#else - // OPENSSL_TLS_SECURITY_LEVEL is not defined in BoringSSL - // so assume it is the default OPENSSL_TLS_SECURITY_LEVEL value. - return 1; -#endif // OPENSSL_IS_BORINGSSL +#endif } SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} @@ -4482,12 +4406,79 @@ bool SSLCtxPointer::setCipherSuites(const char* ciphers) { // ============================================================================ +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV +Cipher::Cipher(DeleteFnPtr cipher) + : cipher_(cipher.get()), fetched_cipher_(std::move(cipher)) {} +#endif + +Cipher::Cipher(const Cipher& other) : cipher_(other.cipher_) { +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + if (other.fetched_cipher_ != nullptr) { + if (EVP_CIPHER_up_ref(other.fetched_cipher_.get()) == 1) { + fetched_cipher_.reset(other.fetched_cipher_.get()); + } else { + cipher_ = nullptr; + } + } +#endif +} + +Cipher& Cipher::operator=(const Cipher& other) { + if (this == &other) return *this; +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + if (other.fetched_cipher_ != nullptr) { + if (EVP_CIPHER_up_ref(other.fetched_cipher_.get()) == 1) { + fetched_cipher_.reset(other.fetched_cipher_.get()); + } else { + fetched_cipher_.reset(); + cipher_ = nullptr; + return *this; + } + } else { + fetched_cipher_.reset(); + } +#endif + cipher_ = other.cipher_; + return *this; +} + const Cipher Cipher::FromName(const char* name) { - return Cipher(EVP_get_cipherbyname(name)); + const EVP_CIPHER* cipher = EVP_get_cipherbyname(name); + if (cipher != nullptr) return Cipher(cipher); + +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + MarkPopErrorOnReturn mark_pop_error_on_return; + DeleteFnPtr fetched( + EVP_CIPHER_fetch(nullptr, name, nullptr)); + if (fetched == nullptr) return Cipher(); + + const int mode = EVP_CIPHER_mode(fetched.get()); + const bool is_siv_mode = +#if OPENSSL_WITH_AES_SIV + mode == EVP_CIPH_SIV_MODE || +#endif +#if OPENSSL_WITH_AES_GCM_SIV + mode == EVP_CIPH_GCM_SIV_MODE || +#endif + false; + if (is_siv_mode) return Cipher(std::move(fetched)); + + return Cipher(); +#else + return Cipher(); +#endif } const Cipher Cipher::FromNid(int nid) { - return Cipher(EVP_get_cipherbynid(nid)); + const EVP_CIPHER* cipher = EVP_get_cipherbynid(nid); + if (cipher != nullptr) return Cipher(cipher); + +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + const char* name = OBJ_nid2sn(nid); + if (name != nullptr) return FromName(name); +#endif + + return Cipher(); } const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { @@ -4541,6 +4532,24 @@ bool Cipher::isOcbMode() const { return getMode() == EVP_CIPH_OCB_MODE; } +bool Cipher::isSivMode() const { + if (!cipher_) return false; +#if OPENSSL_WITH_AES_SIV + return getMode() == EVP_CIPH_SIV_MODE; +#else + return false; +#endif +} + +bool Cipher::isGcmSivMode() const { + if (!cipher_) return false; +#if OPENSSL_WITH_AES_GCM_SIV + return getMode() == EVP_CIPH_GCM_SIV_MODE; +#else + return false; +#endif +} + bool Cipher::isStreamMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_STREAM_CIPHER; @@ -4595,6 +4604,14 @@ std::string_view Cipher::getModeLabel() const { return "ocb"; case EVP_CIPH_OFB_MODE: return "ofb"; +#if OPENSSL_WITH_AES_SIV + case EVP_CIPH_SIV_MODE: + return "siv"; +#endif +#if OPENSSL_WITH_AES_GCM_SIV + case EVP_CIPH_GCM_SIV_MODE: + return "gcm-siv"; +#endif case EVP_CIPH_WRAP_MODE: return "wrap"; case EVP_CIPH_XTS_MODE: @@ -4609,7 +4626,16 @@ const char* Cipher::getName() const { if (!cipher_) return {}; // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. - return OBJ_nid2sn(getNid()); + const int nid = getNid(); + if (nid != NID_undef) { + const char* name = OBJ_nid2sn(nid); + if (name != nullptr) return name; + } +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + return EVP_CIPHER_get0_name(cipher_); +#else + return {}; +#endif } bool Cipher::isSupportedAuthenticatedMode() const { @@ -4618,6 +4644,12 @@ bool Cipher::isSupportedAuthenticatedMode() const { case EVP_CIPH_GCM_MODE: #ifndef OPENSSL_NO_OCB case EVP_CIPH_OCB_MODE: +#endif +#if OPENSSL_WITH_AES_SIV + case EVP_CIPH_SIV_MODE: +#endif +#if OPENSSL_WITH_AES_GCM_SIV + case EVP_CIPH_GCM_SIV_MODE: #endif return true; case EVP_CIPH_STREAM_CIPHER: @@ -4731,6 +4763,24 @@ bool CipherCtxPointer::isWrapMode() const { return getMode() == EVP_CIPH_WRAP_MODE; } +bool CipherCtxPointer::isSivMode() const { + if (!ctx_) return false; +#if OPENSSL_WITH_AES_SIV + return getMode() == EVP_CIPH_SIV_MODE; +#else + return false; +#endif +} + +bool CipherCtxPointer::isGcmSivMode() const { + if (!ctx_) return false; +#if OPENSSL_WITH_AES_GCM_SIV + return getMode() == EVP_CIPH_GCM_SIV_MODE; +#else + return false; +#endif +} + bool CipherCtxPointer::isChaCha20Poly1305() const { if (!ctx_) return false; return getNid() == NID_chacha20_poly1305; @@ -5651,9 +5701,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key, if (!key) return {}; EVPKeyCtxPointer ctx = key.newCtx(); + const Digest& mgf1_digest = + params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest; if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || - (params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) || - !ctx.setRsaMgf1Md(params.digest)))) { + (params.digest != nullptr && + (!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) { return {}; } @@ -5692,7 +5744,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key, if (!key) return {}; EVPKeyCtxPointer ctx = key.newCtx(); if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || - (params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) { + (params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) || + (params.mgf1_digest != nullptr && + !ctx.setRsaMgf1Md(params.mgf1_digest))) { return {}; } @@ -6175,6 +6229,22 @@ struct CipherCallbackContext { void operator()(const char* name) { cb(name); } }; +#if OPENSSL_WITH_AES_SIV +constexpr const char* kProviderOnlyAesSivCiphers[] = { + "aes-128-siv", + "aes-192-siv", + "aes-256-siv", +}; +#endif + +#if OPENSSL_WITH_AES_GCM_SIV +constexpr const char* kProviderOnlyAesGcmSivCiphers[] = { + "aes-128-gcm-siv", + "aes-192-gcm-siv", + "aes-256-gcm-siv", +}; +#endif + #if OPENSSL_VERSION_MAJOR >= 3 template , #endif &context); +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + auto maybe_push_provider_only_cipher = [&](const char* name) { + EVP_CIPHER* cipher = EVP_CIPHER_fetch(nullptr, name, nullptr); + if (cipher == nullptr) return; + EVP_CIPHER_free(cipher); + context.cb(name); + }; +#endif +#if OPENSSL_WITH_AES_SIV + for (const char* name : kProviderOnlyAesSivCiphers) { + maybe_push_provider_only_cipher(name); + } +#endif +#if OPENSSL_WITH_AES_GCM_SIV + for (const char* name : kProviderOnlyAesGcmSivCiphers) { + maybe_push_provider_only_cipher(name); + } +#endif #endif } @@ -6980,6 +7068,9 @@ std::pair X509Name::Iterator::operator*() const { unsigned char* value_str; int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); + if (value_str_size < 0) [[unlikely]] { + return {{}, {}}; + } std::string out(reinterpret_cast(value_str), value_str_size); OPENSSL_free(value_str); // free after copy diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 53302394f38b..58e32cc18fc7 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -105,6 +105,18 @@ #define OPENSSL_WITH_EVP_MAC 0 #endif +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 0) +#define OPENSSL_WITH_AES_SIV 1 +#else +#define OPENSSL_WITH_AES_SIV 0 +#endif + +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 2) +#define OPENSSL_WITH_AES_GCM_SIV 1 +#else +#define OPENSSL_WITH_AES_GCM_SIV 0 +#endif + #if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_PREREQ(3, 2) #define OPENSSL_WITH_SIGNATURE_CONTEXT_STRING 1 #else @@ -437,9 +449,12 @@ class Cipher final { Cipher() = default; Cipher(const EVP_CIPHER* cipher) : cipher_(cipher) {} - Cipher(const Cipher&) = default; - Cipher& operator=(const Cipher&) = default; + Cipher(const Cipher& other); + Cipher& operator=(const Cipher& other); inline Cipher& operator=(const EVP_CIPHER* cipher) { +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + fetched_cipher_.reset(); +#endif cipher_ = cipher; return *this; } @@ -462,6 +477,8 @@ class Cipher final { bool isCtrMode() const; bool isCcmMode() const; bool isOcbMode() const; + bool isSivMode() const; + bool isGcmSivMode() const; bool isStreamMode() const; bool isChaCha20Poly1305() const; @@ -508,6 +525,7 @@ class Cipher final { struct CipherParams { int padding; Digest digest; + Digest mgf1_digest; const Buffer label; }; @@ -532,6 +550,10 @@ class Cipher final { private: const EVP_CIPHER* cipher_ = nullptr; +#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV + explicit Cipher(DeleteFnPtr cipher); + DeleteFnPtr fetched_cipher_; +#endif }; // ============================================================================ @@ -932,6 +954,8 @@ class CipherCtxPointer final { bool isOcbMode() const; bool isCcmMode() const; bool isWrapMode() const; + bool isSivMode() const; + bool isGcmSivMode() const; bool isChaCha20Poly1305() const; bool update(const Buffer& in, @@ -1230,9 +1254,9 @@ class DHPointer final { UNABLE_TO_CHECK_GENERATOR = 0x04, NOT_SUITABLE_GENERATOR = 0x08, Q_NOT_PRIME = 0x10, -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE INVALID_Q = 0x20, +#ifndef OPENSSL_IS_BORINGSSL + // BoringSSL does not define DH_CHECK_INVALID_J_VALUE. INVALID_J = 0x40, MODULUS_TOO_SMALL = 0x80, MODULUS_TOO_LARGE = 0x100, @@ -1243,14 +1267,9 @@ class DHPointer final { enum class CheckPublicKeyResult { NONE, -#ifndef OPENSSL_IS_BORINGSSL - // Boringssl does not define DH_R_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE - TOO_SMALL = DH_R_CHECK_PUBKEY_TOO_SMALL, - TOO_LARGE = DH_R_CHECK_PUBKEY_TOO_LARGE, - INVALID = DH_R_CHECK_PUBKEY_INVALID, -#else - INVALID = DH_R_INVALID_PUBKEY, -#endif + TOO_SMALL, + TOO_LARGE, + INVALID, CHECK_FAILED = 512, }; // Check to see if the given public key is suitable for this DH instance. @@ -1345,9 +1364,6 @@ class SSLPointer final { bool setSession(const SSLSessionPointer& session); bool setSniContext(const SSLCtxPointer& ctx) const; - const char* getClientHelloAlpn() const; - const char* getClientHelloServerName() const; - std::optional getServerName() const; X509View getCertificate() const; EVPKeyPointer getPeerTempKey() const; diff --git a/deps/openssl/openssl.gyp b/deps/openssl/openssl.gyp index 4e16412a0283..144085fd33df 100644 --- a/deps/openssl/openssl.gyp +++ b/deps/openssl/openssl.gyp @@ -36,7 +36,8 @@ # VC-WIN64-ARM inherits from VC-noCE-common that has no asms. 'includes': ['./openssl_no_asm.gypi'], }, 'gas_version and v(gas_version) >= v("2.26") or ' - 'nasm_version and v(nasm_version) >= v("2.11.8")', { + 'nasm_version and v(nasm_version) >= v("2.11.8") or ' + 'llvm_version and v(llvm_version) >= v("8.0")', { # Require AVX512IFMA supported. See # https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_ia32cap.html # Currently crypto/poly1305/asm/poly1305-x86_64.pl requires AVX512IFMA. @@ -114,7 +115,8 @@ # VC-WIN64-ARM inherits from VC-noCE-common that has no asms. 'includes': ['./openssl-fips_no_asm.gypi'], }, 'gas_version and v(gas_version) >= v("2.26") or ' - 'nasm_version and v(nasm_version) >= v("2.11.8")', { + 'nasm_version and v(nasm_version) >= v("2.11.8") or ' + 'llvm_version and v(llvm_version) >= v("8.0")', { # Require AVX512IFMA supported. See # https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_ia32cap.html # Currently crypto/poly1305/asm/poly1305-x86_64.pl requires AVX512IFMA. diff --git a/deps/perfetto/LICENSE b/deps/perfetto/LICENSE index cbdc2881d57d..681d40008ee3 100644 --- a/deps/perfetto/LICENSE +++ b/deps/perfetto/LICENSE @@ -224,6 +224,26 @@ Files: src/trace_processor/perfetto_sql/stdlib/chromium/*, protos/third_party/ch OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------ + +Files: src/trace_processor/perfetto_sql/syntaqlite/syntaqlite_perfetto.{c, h} + + Copyright 2025 The syntaqlite Authors. All rights reserved. + + Machine-generated amalgamation of syntaqlite runtime + Perfetto dialect + sources (https://github.com/LalitMaganti/syntaqlite). Portions derive + from SQLite's public-domain `parse.y` grammar. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + ------------------ Files: src/trace_processor/perfetto_sql/preprocessor/preprocessor_grammar.{c, h} diff --git a/deps/perfetto/VERSION b/deps/perfetto/VERSION index 517ac763657c..06b0aef2d7b5 100644 --- a/deps/perfetto/VERSION +++ b/deps/perfetto/VERSION @@ -1 +1 @@ -54.0 +57.2 diff --git a/deps/perfetto/perfetto.gyp b/deps/perfetto/perfetto.gyp index 3836f3424cbd..083d0b386dd2 100644 --- a/deps/perfetto/perfetto.gyp +++ b/deps/perfetto/perfetto.gyp @@ -9,6 +9,7 @@ { 'target_name': 'perfetto_sdk', 'type': 'static_library', + 'toolsets': ['host', 'target'], 'include_dirs': [ 'sdk' ], 'direct_dependent_settings': { # Use like `#include "perfetto.h"` diff --git a/deps/perfetto/sdk/perfetto.cc b/deps/perfetto/sdk/perfetto.cc index 2ccd11f7b831..4df59d2c46dd 100644 --- a/deps/perfetto/sdk/perfetto.cc +++ b/deps/perfetto/sdk/perfetto.cc @@ -560,6 +560,7 @@ struct std::hash<::perfetto::base::StringView> { #include #include #include +#include #include #include @@ -723,7 +724,9 @@ std::vector SplitString(const std::string& text, const std::string& delimiter); std::string StripPrefix(const std::string& str, const std::string& prefix); std::string StripSuffix(const std::string& str, const std::string& suffix); +std::string_view TrimWhitespace(std::string_view str); std::string TrimWhitespace(const std::string& str); +std::string_view TrimWhitespace(const char* str); std::string ToLower(const std::string& str); std::string ToUpper(const std::string& str); std::string StripChars(const std::string& str, @@ -1544,10 +1547,10 @@ std::optional Base64Decode(const char* src, size_t src_size) { } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/crash_keys.cc -// gen_amalgamated begin header: include/perfetto/ext/base/crash_keys.h +// gen_amalgamated begin source: src/base/cpu_info.cc +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1562,154 +1565,42 @@ std::optional Base64Decode(const char* src, size_t src_size) { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ - -#include -#include +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ #include -#include - -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" - -// Crash keys are very simple global variables with static-storage that -// are reported on crash time for managed crashes (CHECK/FATAL/Watchdog). -// - Translation units can define a CrashKey and register it at some point -// during initialization. -// - CrashKey instances must be long-lived. They should really be just global -// static variable in the anonymous namespace. -// Example: -// subsystem_1.cc -// CrashKey g_client_id("ipc_client_id"); -// ... -// OnIpcReceived(client_id) { -// g_client_id.Set(client_id); -// ... // Process the IPC -// g_client_id.Clear(); -// } -// Or equivalently: -// OnIpcReceived(client_id) { -// auto scoped_key = g_client_id.SetScoped(client_id); -// ... // Process the IPC -// } -// -// If a crash happens while processing the IPC, the crash report will -// have a line "ipc_client_id: 42". -// -// Thread safety considerations: -// CrashKeys can be registered and set/cleared from any thread. -// There is no compelling use-case to have full acquire/release consistency when -// setting a key. This means that if a thread crashes immediately after a -// crash key has been set on another thread, the value printed on the crash -// report could be incomplete. The code guarantees defined behavior and does -// not rely on null-terminated string (in the worst case 32 bytes of random -// garbage will be printed out). - -// The tests live in logging_unittest.cc. +#include +#include +#include namespace perfetto { namespace base { -constexpr size_t kCrashKeyMaxStrSize = 32; - -// CrashKey instances must be long lived -class CrashKey { - public: - class ScopedClear { - public: - explicit ScopedClear(CrashKey* k) : key_(k) {} - ~ScopedClear() { - if (key_) - key_->Clear(); - } - ScopedClear(const ScopedClear&) = delete; - ScopedClear& operator=(const ScopedClear&) = delete; - ScopedClear& operator=(ScopedClear&&) = delete; - ScopedClear(ScopedClear&& other) noexcept : key_(other.key_) { - other.key_ = nullptr; - } - - private: - CrashKey* key_; - }; - - // constexpr so it can be used in the anon namespace without requiring a - // global constructor. - // |name| must be a long-lived string. - constexpr explicit CrashKey(const char* name) - : registered_{}, type_(Type::kUnset), name_(name), str_value_{} {} - CrashKey(const CrashKey&) = delete; - CrashKey& operator=(const CrashKey&) = delete; - CrashKey(CrashKey&&) = delete; - CrashKey& operator=(CrashKey&&) = delete; - - enum class Type : uint8_t { kUnset = 0, kInt, kStr }; - - void Clear() { - int_value_.store(0, std::memory_order_relaxed); - type_.store(Type::kUnset, std::memory_order_relaxed); - } - - void Set(int64_t value) { - int_value_.store(value, std::memory_order_relaxed); - type_.store(Type::kInt, std::memory_order_relaxed); - if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) - Register(); - } - - void Set(StringView sv) { - size_t len = std::min(sv.size(), sizeof(str_value_) - 1); - for (size_t i = 0; i < len; ++i) - str_value_[i].store(sv.data()[i], std::memory_order_relaxed); - str_value_[len].store('\0', std::memory_order_relaxed); - type_.store(Type::kStr, std::memory_order_relaxed); - if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) - Register(); - } - - ScopedClear SetScoped(int64_t value) PERFETTO_WARN_UNUSED_RESULT { - Set(value); - return ScopedClear(this); - } - - ScopedClear SetScoped(StringView sv) PERFETTO_WARN_UNUSED_RESULT { - Set(sv); - return ScopedClear(this); - } - - void Register(); - - int64_t int_value() const { - return int_value_.load(std::memory_order_relaxed); - } - size_t ToString(char* dst, size_t len); - - private: - std::atomic registered_; - std::atomic type_; - const char* const name_; - union { - std::atomic str_value_[kCrashKeyMaxStrSize]; - std::atomic int_value_; - }; +struct CpuInfo { + std::string processor; + uint32_t cpu_index = 0; + std::optional implementer; + std::optional architecture; + std::optional variant; + std::optional part; + std::optional revision; + uint64_t features = 0; + char arm_cpuid[32] = {}; }; -// Fills |dst| with a string containing one line for each crash key -// (excluding the unset ones). -// Returns number of chars written, without counting the NUL terminator. -// This is used in logging.cc when emitting the crash report abort message. -size_t SerializeCrashKeys(char* dst, size_t len); +// Parses the contents of the input string into per-CPU entries. +std::vector ParseCpuInfo(std::string proc_cpu_info); -void UnregisterAllCrashKeysForTesting(); +// Reads /proc/cpuinfo and parses it into per-CPU entries. +std::vector ReadCpuInfo(); } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info_features_allowlist.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1724,91 +1615,29 @@ void UnregisterAllCrashKeysForTesting(); * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/ext/base/crash_keys.h" - -#include - -#include -#include - -// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ namespace perfetto { namespace base { -namespace { - -constexpr size_t kMaxKeys = 32; - -std::atomic g_keys[kMaxKeys]{}; -std::atomic g_num_keys{}; -} // namespace - -void CrashKey::Register() { - // If doesn't matter if we fail below. If there are no slots left, don't - // keep trying re-registering on every Set(), the outcome won't change. - - // If two threads raced on the Register(), avoid registering the key twice. - if (registered_.exchange(true)) - return; - - uint32_t slot = g_num_keys.fetch_add(1); - if (slot >= kMaxKeys) { - PERFETTO_LOG("Too many crash keys registered"); - return; - } - g_keys[slot].store(this); -} - -// Returns the number of chars written, without counting the \0. -size_t CrashKey::ToString(char* dst, size_t len) { - if (len > 0) - *dst = '\0'; - switch (type_.load(std::memory_order_relaxed)) { - case Type::kUnset: - break; - case Type::kInt: - return SprintfTrunc(dst, len, "%s: %" PRId64 "\n", name_, - int_value_.load(std::memory_order_relaxed)); - case Type::kStr: - char buf[sizeof(str_value_)]; - for (size_t i = 0; i < sizeof(str_value_); i++) - buf[i] = str_value_[i].load(std::memory_order_relaxed); - - // Don't assume |str_value_| is properly null-terminated. - return SprintfTrunc(dst, len, "%s: %.*s\n", name_, int(sizeof(buf)), buf); - } - return 0; -} - -void UnregisterAllCrashKeysForTesting() { - g_num_keys.store(0); - for (auto& key : g_keys) - key.store(nullptr); -} - -size_t SerializeCrashKeys(char* dst, size_t len) { - size_t written = 0; - uint32_t num_keys = g_num_keys.load(); - if (len > 0) - *dst = '\0'; - for (uint32_t i = 0; i < num_keys && written < len; i++) { - CrashKey* key = g_keys[i].load(); - if (!key) - continue; // Can happen if we hit this between the add and the store. - written += key->ToString(dst + written, len - written); - } - PERFETTO_DCHECK(written <= len); - PERFETTO_DCHECK(len == 0 || dst[written] == '\0'); - return written; -} +// APPEND ONLY. DO NOT EVER REMOVE ENTRIES FROM THIS ARRAY OR REORDER. +// This array is used both by traced_probes and trace_processor to index the +// cpuinfo flags. Changing the order will break trace_processor compatibility +// with old traces. +constexpr const char* kCpuInfoFeatures[] = { + "mte", // DO NOT REMOVE/REODER. + "mte3", // DO NOT REMOVE/REODER. +}; } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/ctrl_c_handler.cc -// gen_amalgamated begin header: include/perfetto/ext/base/ctrl_c_handler.h + +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h +// gen_amalgamated begin header: include/perfetto/base/status.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1823,106 +1652,107 @@ size_t SerializeCrashKeys(char* dst, size_t len) { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ +#define INCLUDE_PERFETTO_BASE_STATUS_H_ + +#include +#include +#include +#include + +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" namespace perfetto { namespace base { -// On Linux/Android/Mac: installs SIGINT + SIGTERM signal handlers. -// On Windows: installs a SetConsoleCtrlHandler() handler. -// The passed handler must be async safe. -using CtrlCHandlerFunction = void (*)(); -void InstallCtrlCHandler(CtrlCHandlerFunction); +// Represents either the success or the failure message of a function. +// This can used as the return type of functions which would usually return an +// bool for success or int for errno but also wants to add some string context +// (ususally for logging). +// +// Similar to absl::Status, an optional "payload" can also be included with more +// context about the error. This allows passing additional metadata about the +// error (e.g. location of errors, potential mitigations etc). +class PERFETTO_EXPORT_COMPONENT Status { + public: + Status() : ok_(true) {} + explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { + PERFETTO_CHECK(!message_.empty()); + } -} // namespace base -} // namespace perfetto + // Copy operations. + Status(const Status&) = default; + Status& operator=(const Status&) = default; -#endif // INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + // Move operations. The moved-from state is valid but unspecified. + Status(Status&&) noexcept = default; + Status& operator=(Status&&) = default; -// gen_amalgamated expanded: #include "perfetto/ext/base/ctrl_c_handler.h" + bool ok() const { return ok_; } -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" + // When ok() is false this returns the error message. Returns the empty string + // otherwise. + const std::string& message() const { return message_; } + const char* c_message() const { return message_.c_str(); } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include + ////////////////////////////////////////////////////////////////////////////// + // Payload Management APIs + ////////////////////////////////////////////////////////////////////////////// -#include -#else -#include -#include -#endif + // Payloads can be attached to error statuses to provide additional context. + // + // Payloads are (key, value) pairs, where the key is a string acting as a + // unique "type URL" and the value is an opaque string. The "type URL" should + // be unique, follow the format of a URL and, ideally, documentation on how to + // interpret its associated data should be available. + // + // To attach a payload to a status object, call `Status::SetPayload()`. + // Similarly, to extract the payload from a status, call + // `Status::GetPayload()`. + // + // Note: the payload APIs are only meaningful to call when the status is an + // error. Otherwise, all methods are noops. -namespace perfetto { -namespace base { + // Gets the payload for the given |type_url| if one exists. + // + // Will always return std::nullopt if |ok()|. + std::optional GetPayload(std::string_view type_url) const; -namespace { -CtrlCHandlerFunction g_handler = nullptr; + // Sets the payload for the given key. The key should + // + // Will always do nothing if |ok()|. + void SetPayload(std::string_view type_url, std::string value); -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -BOOL WINAPI Trampoline(DWORD type) { - if (type == CTRL_C_EVENT) { - g_handler(); - return TRUE; - } - return FALSE; -} -#endif -} // namespace + // Erases the payload for the given string and returns true if the payload + // existed and was erased. + // + // Will always do nothing if |ok()|. + bool ErasePayload(std::string_view type_url); -void InstallCtrlCHandler(CtrlCHandlerFunction handler) { - PERFETTO_CHECK(g_handler == nullptr); - g_handler = handler; + private: + struct Payload { + std::string type_url; + std::string payload; + }; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - ::SetConsoleCtrlHandler(Trampoline, true); -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) - // Setup signal handler. - struct sigaction sa{}; + bool ok_ = false; + std::string message_; + std::vector payloads_; +}; -// Glibc headers for sa_sigaction trigger this. -#pragma GCC diagnostic push -#if defined(__clang__) -#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion" -#endif - sa.sa_handler = [](int) { g_handler(); }; -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) - sa.sa_flags = static_cast(SA_RESETHAND | SA_RESTART); -#else // POSIX-compliant - sa.sa_flags = static_cast(SA_RESETHAND); -#endif -#pragma GCC diagnostic pop - sigaction(SIGINT, &sa, nullptr); - sigaction(SIGTERM, &sa, nullptr); -#else - // Do nothing on NaCL and Fuchsia. - ignore_result(handler); -#endif +// Returns a status object which represents the Ok status. +inline Status OkStatus() { + return Status(); } +Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); + } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/event_fd.cc -// gen_amalgamated begin header: include/perfetto/ext/base/event_fd.h + +#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ // gen_amalgamated begin header: include/perfetto/ext/base/scoped_file.h /* * Copyright (C) 2017 The Android Open Source Project @@ -2061,58 +1891,198 @@ using ScopedDir = ScopedResource; * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ -#define INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ + +#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. +#include + +#include +#include +#include +#include +#include // gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/status.h" // gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" namespace perfetto { namespace base { -// A waitable event that can be used with poll/select. -// This is really a wrapper around eventfd_create with a pipe-based fallback -// for other platforms where eventfd is not supported. -class EventFd { - public: - EventFd(); - ~EventFd(); - EventFd(EventFd&&) noexcept = default; - EventFd& operator=(EventFd&&) = default; +class TaskRunner; - // The non-blocking file descriptor that can be polled to wait for the event. - PlatformHandle fd() const { return event_handle_.get(); } +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +using FileOpenMode = int; +inline constexpr char kDevNull[] = "NUL"; +inline constexpr char kFopenReadFlag[] = "r"; +#else +using FileOpenMode = mode_t; +inline constexpr char kDevNull[] = "/dev/null"; +inline constexpr char kFopenReadFlag[] = "re"; +#endif - // Can be called from any thread. - void Notify(); +constexpr FileOpenMode kFileModeInvalid = static_cast(-1); - // Can be called from any thread. If more Notify() are queued a Clear() call - // can clear all of them (up to 16 per call). - void Clear(); +// Cross-platform variant of ReadFileDescriptor() that takes a PlatformHandle. +// On Windows normalizes ERROR_BROKEN_PIPE to EOF so behavior matches POSIX. +bool ReadPlatformHandle(PlatformHandle, std::string* out); - private: - // The eventfd, when eventfd is supported, otherwise this is the read end of - // the pipe for fallback mode. - ScopedPlatformHandle event_handle_; +// Reads from |fd|, appending what is currently available into |*out|. +// Returns: +// True: EOF reached (all writers of |fd| have closed their end). +// False: read error. On a non-blocking |fd| this includes EAGAIN (no data +// currently available but writers are still alive); callers can check +// IsAgain(errno) and retry on the next readability notification. +bool ReadFileDescriptor(int fd, std::string* out); -// QNX is specified because it is a non-Linux UNIX platform but it -// still sets the PERFETTO_OS_LINUX flag to be as compatible as possible -// with the Linux build. -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - // On Mac and other non-Linux UNIX platforms a pipe-based fallback is used. - // The write end of the wakeup pipe. - ScopedFile write_fd_; -#endif +// Convenience wrapper around ReadFileDescriptor() that takes a FILE*. +bool ReadFileStream(FILE* f, std::string* out); + +// Opens |path| read-only and reads its contents into |*out|. +// Returns false if the file cannot be opened. +bool ReadFile(const std::string& path, std::string* out); + +// A wrapper around read(2). It deals with Linux vs Windows includes. It also +// deals with handling EINTR. Has the same semantics of UNIX's read(2). +ssize_t Read(int fd, void* dst, size_t dst_size); + +// Call write until all data is written or an error is detected. +// +// man 2 write: +// If a write() is interrupted by a signal handler before any bytes are +// written, then the call fails with the error EINTR; if it is +// interrupted after at least one byte has been written, the call +// succeeds, and returns the number of bytes written. +ssize_t WriteAll(int fd, const void* buf, size_t count); + +// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, +// rewinds it to the beginning, copies the content, and restores the offset. +// |fd_in| can't be a pipe, socket of FIFO. +base::Status CopyFileContents(int fd_in, int fd_out); + +ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); + +ScopedFile OpenFile(const std::string& path, + int flags, + FileOpenMode = kFileModeInvalid); +ScopedFstream OpenFstream(const std::string& path, const std::string& mode); + +// This is an alias for close(). It's to avoid leaking windows.h in headers. +// Exported because ScopedFile is used in the /include/ext API by Chromium +// component builds. +int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); + +bool FlushFile(int fd); + +// Returns true if mkdir succeeds, false if it fails (see errno in that case). +// `mode` is the permission bits for the new directory; it is ignored on +// Windows. +bool Mkdir(const std::string& path, uint32_t mode = 0755); + +// Calls rmdir() on UNIX, _rmdir() on Windows. +bool Rmdir(const std::string& path); + +// Removes a file: unlink() on UNIX, _unlink() on Windows. Takes a const char* +// and is async-signal-safe on POSIX, so it's callable from a signal handler. +bool Unlink(const char* path); + +// Wrapper around access(path, F_OK). +bool FileExists(const std::string& path); + +// Gets the extension for a filename. If the file has two extensions, returns +// only the last one (foo.pb.gz => .gz). Returns empty string if there is no +// extension. +std::string GetFileExtension(const std::string& filename); + +// Returns the basename component of a path (the final component after the last +// directory separator). Behaves like man 2 basename, but works with both '/' +// and '\' separators for cross-platform compatibility. +// Examples: +// Basename("/usr/bin/ls") => "ls" +// Basename("/usr/bin/") => "bin" +// Basename("/") => "/" +// Basename("foo") => "foo" +// Basename("") => "." +// Basename("C:\\Windows\\System32") => "System32" +std::string Basename(const std::string& path); + +// Returns the directory component of a path (everything up to but not +// including the final component). Behaves like man 2 dirname, but works with +// both '/' and '\' separators for cross-platform compatibility. +// Examples: +// Dirname("/usr/bin/ls") => "/usr/bin" +// Dirname("/usr/bin") => "/usr" +// Dirname("/") => "/" +// Dirname("foo") => "." +// Dirname("") => "." +// Dirname("C:\\Windows\\System32") => "C:\\Windows" +std::string Dirname(const std::string& path); + +// Puts the path to all files under |dir_path| in |output|, recursively walking +// subdirectories. File paths are relative to |dir_path|. Only files are +// included, not directories. Path separator is always '/', even on windows (not +// '\'). +base::Status ListFilesRecursive(const std::string& dir_path, + std::vector& output); + +// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names +// are relative to |dir_path| and do not include the path separator. Returns +// only directories, not files. Works on both Unix and Windows. +base::Status ListDirectories(const std::string& dir_path, + std::vector& output); + +// Sets |path|'s owner group to |group_name| and permission mode bits to +// |mode_bits|. +base::Status SetFilePermissions(const std::string& path, + const std::string& group_name, + const std::string& mode_bits); + +// Returns the size of the file located at |path|, or nullopt in case of error. +std::optional GetFileSize(const std::string& path); + +// Returns the size of the open file |fd|, or nullopt in case of error. +std::optional GetFileSize(PlatformHandle fd); + +// This class uses inotify (on Linux/Android) to watch for the creation of +// files in the filesystem. When the specified file is created, it triggers a +// callback function. +// Destroying the returned unique_ptr will automatically unregister the watch. +// +// Note: This only works with filesystem paths (not abstract sockets or other +// special file types). +// It's only supported on Linux and Android, it's a no-op (returns nullptr) on +// other platforms. +// +// Usage: +// auto watch = LinuxFileWatch::WatchFileCreation( +// task_runner, "/tmp/my_file", []() { +// // Called when /tmp/my_file is created +// }); +class LinuxFileWatch { + public: + // Creates a watcher for file creation. Returns nullptr if the path is not a + // valid filesystem path or if the platform doesn't support inotify. The + // callback will be invoked on the provided TaskRunner when the file is + // created. + static std::unique_ptr WatchFileCreation( + TaskRunner*, + const char* path, + std::function callback); + + virtual ~LinuxFileWatch(); + + protected: + LinuxFileWatch() = default; }; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/pipe.h +#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/string_splitter.h /* * Copyright (C) 2018 The Android Open Source Project * @@ -2129,42 +2099,93 @@ class EventFd { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ -#define INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ -// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" +#include namespace perfetto { namespace base { -class Pipe { +// C++ version of strtok(). Splits a string without making copies or any heap +// allocations. Destructs the original string passed in input. +// Supports the special case of using \0 as a delimiter. +// The token returned in output are valid as long as the input string is valid. +class StringSplitter { public: - enum Flags { - kBothBlock = 0, -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - kBothNonBlock, - kRdNonBlock, - kWrNonBlock, -#endif + // Whether an empty string (two delimiters side-to-side) is a valid token. + enum class EmptyTokenMode { + DISALLOW_EMPTY_TOKENS, + ALLOW_EMPTY_TOKENS, + + DEFAULT = DISALLOW_EMPTY_TOKENS, }; - static Pipe Create(Flags = kBothBlock); + // Can take ownership of the string if passed via std::move(), e.g.: + // StringSplitter(std::move(str), '\n'); + StringSplitter(std::string, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); - Pipe(); - Pipe(Pipe&&) noexcept; - Pipe& operator=(Pipe&&); + // Splits a C-string. The input string will be forcefully null-terminated (so + // str[size - 1] should be == '\0' or the last char will be truncated). + StringSplitter(char* str, + size_t size, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); - ScopedPlatformHandle rd; - ScopedPlatformHandle wr; + // Splits the current token from an outer StringSplitter instance. This is to + // chain splitters as follows: + // for (base::StringSplitter lines(x, '\n'); ss.Next();) + // for (base::StringSplitter words(&lines, ' '); words.Next();) + StringSplitter(StringSplitter*, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); + + // Returns true if a token is found (in which case it will be stored in + // cur_token()), false if no more tokens are found. + bool Next(); + + // Returns the next token if found (in which case it will be stored in + // cur_token()), nullptr if no more tokens are found. + char* NextToken() { return Next() ? cur_token() : nullptr; } + + // Returns the current token iff last call to Next() returned true. In this + // case it guarantees that the returned string is always null terminated. + // In all other cases (before the 1st call to Next() and after Next() returns + // false) returns nullptr. + char* cur_token() { return cur_; } + + // Returns the length of the current token (excluding the null terminator). + size_t cur_token_size() const { return cur_size_; } + + // Return the untokenized remainder of the input string that occurs after the + // current token. + char* remainder() { return next_; } + + // Returns the size of the untokenized input + size_t remainder_size() { return static_cast(end_ - next_); } + + private: + StringSplitter(const StringSplitter&) = delete; + StringSplitter& operator=(const StringSplitter&) = delete; + void Initialize(char* str, size_t size); + + std::string str_; + char* cur_; + size_t cur_size_; + char* next_; + char* end_; // STL-style, points one past the last char. + const char delimiter_; + const EmptyTokenMode empty_token_mode_; }; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2179,110 +2200,166 @@ class Pipe { * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" - -#include -#include - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include - -#include -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) -#include -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -#include -#include -#else // Mac, Fuchsia and other non-Linux UNIXes -#include -#endif +#include +#include +#include +#include -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/event_fd.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/pipe.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/cpu_info.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/cpu_info_features_allowlist.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/file_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_splitter.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" // gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" namespace perfetto { namespace base { +namespace { -EventFd::~EventFd() = default; +// Key for default processor string in /proc/cpuinfo as seen on arm. Note the +// uppercase P. +const char kDefaultProcessor[] = "Processor"; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -EventFd::EventFd() { - event_handle_.reset( - CreateEventA(/*lpEventAttributes=*/nullptr, /*bManualReset=*/true, - /*bInitialState=*/false, /*bInitialState=*/nullptr)); -} +// Key for processor entry in /proc/cpuinfo. Used to determine whether a group +// of lines describes a CPU. +const char kProcessor[] = "processor"; -void EventFd::Notify() { - if (!SetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. - PERFETTO_DFATAL("EventFd::Notify()"); -} +// Key for CPU implementer in /proc/cpuinfo. Arm only. +const char kImplementer[] = "CPU implementer"; -void EventFd::Clear() { - if (!ResetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. - PERFETTO_DFATAL("EventFd::Clear()"); -} +// Key for CPU architecture in /proc/cpuinfo. Arm only. +const char kArchitecture[] = "CPU architecture"; -#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +// Key for CPU variant in /proc/cpuinfo. Arm only. +const char kVariant[] = "CPU variant"; -EventFd::EventFd() { - event_handle_.reset(eventfd(/*initval=*/0, EFD_CLOEXEC | EFD_NONBLOCK)); - PERFETTO_CHECK(event_handle_); -} +// Key for CPU part in /proc/cpuinfo. Arm only. +const char kPart[] = "CPU part"; -void EventFd::Notify() { - const uint64_t value = 1; - ssize_t ret = write(event_handle_.get(), &value, sizeof(value)); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Notify()"); -} +// Key for CPU revision in /proc/cpuinfo. Arm only. +const char kRevision[] = "CPU revision"; -void EventFd::Clear() { - uint64_t value; - ssize_t ret = - PERFETTO_EINTR(read(event_handle_.get(), &value, sizeof(value))); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Clear()"); +// Key for feature flags in /proc/cpuinfo. Arm calls them Features, +// Intel calls them Flags. +const char kFeatures[] = "Features"; +const char kFlags[] = "Flags"; + +std::string ReadFile(const std::string& path) { + std::string contents; + if (!base::ReadFile(path, &contents)) + return ""; + return contents; } -#else +} // namespace -EventFd::EventFd() { - // Make the pipe non-blocking so that we never block the waking thread (either - // the main thread or another one) when scheduling a wake-up. - Pipe pipe = Pipe::Create(Pipe::kBothNonBlock); - event_handle_ = ScopedPlatformHandle(std::move(pipe.rd).release()); - write_fd_ = std::move(pipe.wr); -} +std::vector ParseCpuInfo(std::string proc_cpu_info) { + std::vector cpus; + std::string processor = "unknown"; + + std::optional cpu_index; + std::optional implementer; + std::optional architecture; + std::optional variant; + std::optional part; + std::optional revision; + uint64_t features = 0; + uint32_t next_cpu_index = 0; + + auto flush_cpu = [&] { + if (cpu_index.has_value()) { + CpuInfo cpu{}; + cpu.processor = processor; + cpu.cpu_index = *cpu_index; + cpu.implementer = implementer; + cpu.architecture = architecture; + cpu.variant = variant; + cpu.part = part; + cpu.revision = revision; + cpu.features = features; +#if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) + if (cpu.implementer && cpu.part) { + std::string cpuid = + base::Uint64ToHexStringNoPrefix(cpu.implementer.value()) + + base::Uint64ToHexStringNoPrefix(cpu.part.value()); + if (cpu.variant) { + cpuid += base::Uint64ToHexStringNoPrefix(cpu.variant.value()); + if (cpu.revision) { + cpuid += base::Uint64ToHexStringNoPrefix(cpu.revision.value()); + } + } + base::StringCopy(cpu.arm_cpuid, cpuid.c_str(), sizeof(cpu.arm_cpuid)); + } +#endif // PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) + cpus.emplace_back(std::move(cpu)); + next_cpu_index++; + } + cpu_index = std::nullopt; + implementer = std::nullopt; + architecture = std::nullopt; + variant = std::nullopt; + part = std::nullopt; + revision = std::nullopt; + features = 0; + }; -void EventFd::Notify() { - const uint64_t value = 1; - ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t)); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Notify()"); + for (base::StringSplitter lines( + std::move(proc_cpu_info), '\n', + base::StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS); + lines.Next();) { + std::string line(lines.cur_token(), lines.cur_token_size()); + if (line.empty() && cpu_index.has_value()) { + flush_cpu(); + continue; + } + + auto splits = base::SplitString(line, ":"); + if (splits.size() != 2) + continue; + std::string key = + base::StripSuffix(base::StripChars(splits[0], "\t", ' '), " "); + std::string value = base::StripPrefix(splits[1], " "); + + if (key == kDefaultProcessor) { + processor = value; + } else if (key == kProcessor) { + cpu_index = base::StringToUInt32(value); + } else if (key == kImplementer) { + implementer = base::CStringToUInt32(value.data(), 16); + } else if (key == kArchitecture) { + architecture = base::CStringToUInt32(value.data(), 10); + } else if (key == kVariant) { + variant = base::CStringToUInt32(value.data(), 16); + } else if (key == kPart) { + part = base::CStringToUInt32(value.data(), 16); + } else if (key == kRevision) { + revision = base::CStringToUInt32(value.data(), 10); + } else if (key == kFeatures || key == kFlags) { + for (base::StringSplitter ss(value.data(), ' '); ss.Next();) { + for (size_t i = 0; i < base::ArraySize(kCpuInfoFeatures); ++i) { + if (strcmp(ss.cur_token(), kCpuInfoFeatures[i]) == 0) { + static_assert(base::ArraySize(kCpuInfoFeatures) < 64); + features |= 1ull << i; + } + } + } + } + } + + flush_cpu(); + return cpus; } -void EventFd::Clear() { - // Drain the byte(s) written to the wake-up pipe. We can potentially read - // more than one byte if several wake-ups have been scheduled. - char buffer[16]; - ssize_t ret = - PERFETTO_EINTR(read(event_handle_.get(), &buffer[0], sizeof(buffer))); - if (ret <= 0 && errno != EAGAIN) - PERFETTO_DFATAL("EventFd::Clear()"); +std::vector ReadCpuInfo() { + return ParseCpuInfo(ReadFile("/proc/cpuinfo")); } -#endif } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/file_utils.cc -// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h -// gen_amalgamated begin header: include/perfetto/base/status.h +// gen_amalgamated begin source: src/base/crash_keys.cc +// gen_amalgamated begin header: include/perfetto/ext/base/crash_keys.h /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2297,107 +2374,695 @@ void EventFd::Clear() { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ -#define INCLUDE_PERFETTO_BASE_STATUS_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ -#include -#include -#include -#include +#include +#include + +#include +#include // gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" + +// Crash keys are very simple global variables with static-storage that +// are reported on crash time for managed crashes (CHECK/FATAL/Watchdog). +// - Translation units can define a CrashKey and register it at some point +// during initialization. +// - CrashKey instances must be long-lived. They should really be just global +// static variable in the anonymous namespace. +// Example: +// subsystem_1.cc +// CrashKey g_client_id("ipc_client_id"); +// ... +// OnIpcReceived(client_id) { +// g_client_id.Set(client_id); +// ... // Process the IPC +// g_client_id.Clear(); +// } +// Or equivalently: +// OnIpcReceived(client_id) { +// auto scoped_key = g_client_id.SetScoped(client_id); +// ... // Process the IPC +// } +// +// If a crash happens while processing the IPC, the crash report will +// have a line "ipc_client_id: 42". +// +// Thread safety considerations: +// CrashKeys can be registered and set/cleared from any thread. +// There is no compelling use-case to have full acquire/release consistency when +// setting a key. This means that if a thread crashes immediately after a +// crash key has been set on another thread, the value printed on the crash +// report could be incomplete. The code guarantees defined behavior and does +// not rely on null-terminated string (in the worst case 32 bytes of random +// garbage will be printed out). + +// The tests live in logging_unittest.cc. namespace perfetto { namespace base { -// Represents either the success or the failure message of a function. -// This can used as the return type of functions which would usually return an -// bool for success or int for errno but also wants to add some string context -// (ususally for logging). -// -// Similar to absl::Status, an optional "payload" can also be included with more -// context about the error. This allows passing additional metadata about the -// error (e.g. location of errors, potential mitigations etc). -class PERFETTO_EXPORT_COMPONENT Status { +constexpr size_t kCrashKeyMaxStrSize = 32; + +// CrashKey instances must be long lived +class CrashKey { public: - Status() : ok_(true) {} - explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { - PERFETTO_CHECK(!message_.empty()); - } + class ScopedClear { + public: + explicit ScopedClear(CrashKey* k) : key_(k) {} + ~ScopedClear() { + if (key_) + key_->Clear(); + } + ScopedClear(const ScopedClear&) = delete; + ScopedClear& operator=(const ScopedClear&) = delete; + ScopedClear& operator=(ScopedClear&&) = delete; + ScopedClear(ScopedClear&& other) noexcept : key_(other.key_) { + other.key_ = nullptr; + } - // Copy operations. - Status(const Status&) = default; - Status& operator=(const Status&) = default; + private: + CrashKey* key_; + }; - // Move operations. The moved-from state is valid but unspecified. - Status(Status&&) noexcept = default; - Status& operator=(Status&&) = default; + // constexpr so it can be used in the anon namespace without requiring a + // global constructor. + // |name| must be a long-lived string. + constexpr explicit CrashKey(const char* name) + : registered_{}, type_(Type::kUnset), name_(name), str_value_{} {} + CrashKey(const CrashKey&) = delete; + CrashKey& operator=(const CrashKey&) = delete; + CrashKey(CrashKey&&) = delete; + CrashKey& operator=(CrashKey&&) = delete; - bool ok() const { return ok_; } + enum class Type : uint8_t { kUnset = 0, kInt, kStr }; - // When ok() is false this returns the error message. Returns the empty string - // otherwise. - const std::string& message() const { return message_; } - const char* c_message() const { return message_.c_str(); } + void Clear() { + int_value_.store(0, std::memory_order_relaxed); + type_.store(Type::kUnset, std::memory_order_relaxed); + } - ////////////////////////////////////////////////////////////////////////////// - // Payload Management APIs - ////////////////////////////////////////////////////////////////////////////// + void Set(int64_t value) { + int_value_.store(value, std::memory_order_relaxed); + type_.store(Type::kInt, std::memory_order_relaxed); + if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) + Register(); + } - // Payloads can be attached to error statuses to provide additional context. - // - // Payloads are (key, value) pairs, where the key is a string acting as a - // unique "type URL" and the value is an opaque string. The "type URL" should - // be unique, follow the format of a URL and, ideally, documentation on how to - // interpret its associated data should be available. - // - // To attach a payload to a status object, call `Status::SetPayload()`. - // Similarly, to extract the payload from a status, call - // `Status::GetPayload()`. - // - // Note: the payload APIs are only meaningful to call when the status is an - // error. Otherwise, all methods are noops. + void Set(StringView sv) { + size_t len = std::min(sv.size(), sizeof(str_value_) - 1); + for (size_t i = 0; i < len; ++i) + str_value_[i].store(sv.data()[i], std::memory_order_relaxed); + str_value_[len].store('\0', std::memory_order_relaxed); + type_.store(Type::kStr, std::memory_order_relaxed); + if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed))) + Register(); + } - // Gets the payload for the given |type_url| if one exists. - // - // Will always return std::nullopt if |ok()|. - std::optional GetPayload(std::string_view type_url) const; + ScopedClear SetScoped(int64_t value) PERFETTO_WARN_UNUSED_RESULT { + Set(value); + return ScopedClear(this); + } - // Sets the payload for the given key. The key should - // - // Will always do nothing if |ok()|. - void SetPayload(std::string_view type_url, std::string value); + ScopedClear SetScoped(StringView sv) PERFETTO_WARN_UNUSED_RESULT { + Set(sv); + return ScopedClear(this); + } - // Erases the payload for the given string and returns true if the payload - // existed and was erased. - // - // Will always do nothing if |ok()|. - bool ErasePayload(std::string_view type_url); + void Register(); + + int64_t int_value() const { + return int_value_.load(std::memory_order_relaxed); + } + size_t ToString(char* dst, size_t len); private: - struct Payload { - std::string type_url; - std::string payload; + std::atomic registered_; + std::atomic type_; + const char* const name_; + union { + std::atomic str_value_[kCrashKeyMaxStrSize]; + std::atomic int_value_; }; +}; - bool ok_ = false; - std::string message_; - std::vector payloads_; +// Fills |dst| with a string containing one line for each crash key +// (excluding the unset ones). +// Returns number of chars written, without counting the NUL terminator. +// This is used in logging.cc when emitting the crash report abort message. +size_t SerializeCrashKeys(char* dst, size_t len); + +void UnregisterAllCrashKeysForTesting(); + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/crash_keys.h" + +#include + +#include +#include + +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" + +namespace perfetto { +namespace base { + +namespace { + +constexpr size_t kMaxKeys = 32; + +std::atomic g_keys[kMaxKeys]{}; +std::atomic g_num_keys{}; +} // namespace + +void CrashKey::Register() { + // If doesn't matter if we fail below. If there are no slots left, don't + // keep trying re-registering on every Set(), the outcome won't change. + + // If two threads raced on the Register(), avoid registering the key twice. + if (registered_.exchange(true)) + return; + + uint32_t slot = g_num_keys.fetch_add(1); + if (slot >= kMaxKeys) { + PERFETTO_LOG("Too many crash keys registered"); + return; + } + g_keys[slot].store(this); +} + +// Returns the number of chars written, without counting the \0. +size_t CrashKey::ToString(char* dst, size_t len) { + if (len > 0) + *dst = '\0'; + switch (type_.load(std::memory_order_relaxed)) { + case Type::kUnset: + break; + case Type::kInt: + return SprintfTrunc(dst, len, "%s: %" PRId64 "\n", name_, + int_value_.load(std::memory_order_relaxed)); + case Type::kStr: + char buf[sizeof(str_value_)]; + for (size_t i = 0; i < sizeof(str_value_); i++) + buf[i] = str_value_[i].load(std::memory_order_relaxed); + + // Don't assume |str_value_| is properly null-terminated. + return SprintfTrunc(dst, len, "%s: %.*s\n", name_, int(sizeof(buf)), buf); + } + return 0; +} + +void UnregisterAllCrashKeysForTesting() { + g_num_keys.store(0); + for (auto& key : g_keys) + key.store(nullptr); +} + +size_t SerializeCrashKeys(char* dst, size_t len) { + size_t written = 0; + uint32_t num_keys = g_num_keys.load(); + if (len > 0) + *dst = '\0'; + for (uint32_t i = 0; i < num_keys && written < len; i++) { + CrashKey* key = g_keys[i].load(); + if (!key) + continue; // Can happen if we hit this between the add and the store. + written += key->ToString(dst + written, len - written); + } + PERFETTO_DCHECK(written <= len); + PERFETTO_DCHECK(len == 0 || dst[written] == '\0'); + return written; +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/ctrl_c_handler.cc +// gen_amalgamated begin header: include/perfetto/ext/base/ctrl_c_handler.h +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ + +namespace perfetto { +namespace base { + +// On Linux/Android/Mac: installs SIGINT + SIGTERM signal handlers. +// On Windows: installs a SetConsoleCtrlHandler() handler. +// The passed handler must be async safe. +using CtrlCHandlerFunction = void (*)(); +void InstallCtrlCHandler(CtrlCHandlerFunction); + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/ctrl_c_handler.h" + +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include + +#include +#else +#include +#include +#endif + +namespace perfetto { +namespace base { + +namespace { +CtrlCHandlerFunction g_handler = nullptr; + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +BOOL WINAPI Trampoline(DWORD type) { + if (type == CTRL_C_EVENT) { + g_handler(); + return TRUE; + } + return FALSE; +} +#endif +} // namespace + +void InstallCtrlCHandler(CtrlCHandlerFunction handler) { + PERFETTO_CHECK(g_handler == nullptr); + g_handler = handler; + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + ::SetConsoleCtrlHandler(Trampoline, true); +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) + // Setup signal handler. + struct sigaction sa{}; + +// Glibc headers for sa_sigaction trigger this. +#pragma GCC diagnostic push +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion" +#endif + sa.sa_handler = [](int) { g_handler(); }; +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) + sa.sa_flags = static_cast(SA_RESETHAND | SA_RESTART); +#else // POSIX-compliant + sa.sa_flags = static_cast(SA_RESETHAND); +#endif +#pragma GCC diagnostic pop + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); +#else + // Do nothing on NaCL and Fuchsia. + ignore_result(handler); +#endif +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/dynamic_string_writer.cc +// gen_amalgamated begin header: include/perfetto/ext/base/dynamic_string_writer.h +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" + +namespace perfetto { +namespace base { + +// A helper class which writes formatted data to a string buffer. +// This is used in the trace processor where we write O(GBs) of strings and +// sprintf is too slow. +class DynamicStringWriter { + public: + using ScopedCString = std::unique_ptr; + + // Creates a string buffer from a char buffer and length. + DynamicStringWriter() {} + + // Appends n instances of a char to the buffer. + void AppendChar(char in, size_t n = 1) { buffer_.append(n, in); } + + // Appends a length delimited string to the buffer. + void AppendString(const char* in, size_t n) { buffer_.append(in, n); } + + void AppendStringView(StringView sv) { AppendString(sv.data(), sv.size()); } + + // Appends a null-terminated string literal to the buffer. + template + inline void AppendLiteral(const char (&in)[N]) { + AppendString(in, N - 1); + } + + // Appends a StringView to the buffer. + void AppendString(StringView data) { + buffer_.append(data.data(), data.size()); + } + + // Appends an integer to the buffer. + void AppendInt(int64_t value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%" PRId64, value); + AppendString(buf.string_view()); + } + + // Appends an integer to the buffer, padding with |padchar| if the number of + // digits of the integer is less than |padding|. + template + void AppendPaddedInt(int64_t sign_value) { + const bool negate = std::signbit(static_cast(sign_value)); + uint64_t absolute_value; + if (sign_value == std::numeric_limits::min()) { + absolute_value = + static_cast(std::numeric_limits::max()) + 1; + } else { + absolute_value = static_cast(std::abs(sign_value)); + } + AppendPaddedIntImpl(absolute_value, negate); + } + + void AppendUnsignedInt(uint64_t value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%" PRIu64, value); + AppendString(buf.string_view()); + } + + template + void AppendPaddedUnsignedInt(uint64_t value) { + AppendPaddedIntImpl(value, false); + } + + template + void AppendPaddedHexInt(IntType value, char padchar, uint64_t padding) { + using UnsignedType = std::make_unsigned_t; + constexpr size_t kMaxHexDigits = sizeof(IntType) * 2; + constexpr size_t kBufferSize = 32; + auto size_needed = + kMaxHexDigits > padding ? kMaxHexDigits : static_cast(padding); + PERFETTO_DCHECK(size_needed <= kBufferSize); + + std::array data; + constexpr char hex_asc[] = "0123456789abcdef"; + + size_t idx = size_needed - 1; + auto uvalue = static_cast(value); + do { + data[idx--] = hex_asc[uvalue & 0xF]; + uvalue >>= 4; + } while (uvalue != 0); + + if (padding > 0) { + const auto num_digits = static_cast(size_needed - 1 - idx); + // std::max() needed to work around GCC not being able to tell that + // padding > 0. + for (auto i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { + data[idx--] = padchar; + } + } + AppendString(&data[idx + 1], size_needed - idx - 1); + } + + // Appends a hex integer to the buffer. + template + void AppendHexInt(IntType value) { + constexpr size_t STACK_BUFFER_SIZE = 64; + StackString buf("%" PRIx64, value); + AppendString(buf.string_view()); + } + + void AppendHexString(const uint8_t* data, size_t size, char separator); + + void AppendHexString(StringView data, char separator) { + AppendHexString(reinterpret_cast(data.data()), data.size(), + separator); + } + + // Appends a double to the buffer. + void AppendDouble(double value) { + constexpr size_t STACK_BUFFER_SIZE = 32; + StackString buf("%.16g", value); + AppendString(buf.string_view()); + } + + void AppendBool(bool value) { + if (value) { + AppendLiteral("true"); + return; + } + AppendLiteral("false"); + } + + StringView GetStringView() { + return StringView(buffer_.c_str(), buffer_.size()); + } + + ScopedCString CreateStringCopy() const { + size_t n = buffer_.size(); + char* dup = reinterpret_cast(malloc(n + 1)); + if (dup) { + memcpy(dup, buffer_.data(), n); + dup[n] = '\0'; + } + return {dup, free}; + } + + size_t pos() const { return buffer_.size(); } + + void Clear() { buffer_.clear(); } + + private: + template + void AppendPaddedIntImpl(uint64_t absolute_value, bool negate) { + // Need to add 2 to the number of digits to account for minus sign and + // rounding down of digits10. + constexpr auto kMaxDigits = std::numeric_limits::digits10 + 2; + constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding; + + char data[kSizeNeeded]; + + size_t idx; + for (idx = kSizeNeeded - 1; absolute_value >= 10;) { + char digit = absolute_value % 10; + absolute_value /= 10; + data[idx--] = digit + '0'; + } + data[idx--] = static_cast(absolute_value) + '0'; + + if (padding > 0) { + size_t num_digits = kSizeNeeded - 1 - idx; + // std::max() needed to work around GCC not being able to tell that + // padding > 0. + for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { + data[idx--] = padchar; + } + } + + if (negate) + AppendChar('-'); + AppendString(&data[idx + 1], kSizeNeeded - idx - 1); + } + + std::string buffer_; }; -// Returns a status object which represents the Ok status. -inline Status OkStatus() { - return Status(); -} +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_DYNAMIC_STRING_WRITER_H_ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// gen_amalgamated expanded: #include "perfetto/ext/base/dynamic_string_writer.h" + +#include +#include +#include + +namespace perfetto { +namespace base { + +void DynamicStringWriter::AppendHexString(const uint8_t* data, + size_t size, + char separator) { + // Truncate to 64 bytes, as this is the maximum supported by the Linux + // kernel's vsnprintf implementation. + size_t printed_size = std::min(size, size_t{64}); + + if (printed_size) { + AppendPaddedHexInt(data[0], '0', 2); + } + for (size_t pos = 1; pos < printed_size; pos++) { + AppendChar(separator); + AppendPaddedHexInt(data[pos], '0', 2); + } +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/event_fd.cc +// gen_amalgamated begin header: include/perfetto/ext/base/event_fd.h +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +#define INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ + +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" + +namespace perfetto { +namespace base { + +// A waitable event that can be used with poll/select. +// This is really a wrapper around eventfd_create with a pipe-based fallback +// for other platforms where eventfd is not supported. +class EventFd { + public: + EventFd(); + ~EventFd(); + EventFd(EventFd&&) noexcept = default; + EventFd& operator=(EventFd&&) = default; + + // The non-blocking file descriptor that can be polled to wait for the event. + PlatformHandle fd() const { return event_handle_.get(); } + + // Can be called from any thread. + void Notify(); + + // Can be called from any thread. If more Notify() are queued a Clear() call + // can clear all of them (up to 16 per call). + void Clear(); + + private: + // The eventfd, when eventfd is supported, otherwise this is the read end of + // the pipe for fallback mode. + ScopedPlatformHandle event_handle_; -Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); +// QNX is specified because it is a non-Linux UNIX platform but it +// still sets the PERFETTO_OS_LINUX flag to be as compatible as possible +// with the Linux build. +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + // On Mac and other non-Linux UNIX platforms a pipe-based fallback is used. + // The write end of the wakeup pipe. + ScopedFile write_fd_; +#endif +}; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/pipe.h /* * Copyright (C) 2018 The Android Open Source Project * @@ -2414,177 +3079,156 @@ Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ - -#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. -#include - -#include -#include -#include -#include -#include +#ifndef INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +#define INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/status.h" +// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" // gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" namespace perfetto { namespace base { -class TaskRunner; - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -using FileOpenMode = int; -inline constexpr char kDevNull[] = "NUL"; -inline constexpr char kFopenReadFlag[] = "r"; -#else -using FileOpenMode = mode_t; -inline constexpr char kDevNull[] = "/dev/null"; -inline constexpr char kFopenReadFlag[] = "re"; +class Pipe { + public: + enum Flags { + kBothBlock = 0, +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + kBothNonBlock, + kRdNonBlock, + kWrNonBlock, #endif + }; -constexpr FileOpenMode kFileModeInvalid = static_cast(-1); + static Pipe Create(Flags = kBothBlock); -bool ReadPlatformHandle(PlatformHandle, std::string* out); -bool ReadFileDescriptor(int fd, std::string* out); -bool ReadFileStream(FILE* f, std::string* out); -bool ReadFile(const std::string& path, std::string* out); + Pipe(); + Pipe(Pipe&&) noexcept; + Pipe& operator=(Pipe&&); -// A wrapper around read(2). It deals with Linux vs Windows includes. It also -// deals with handling EINTR. Has the same semantics of UNIX's read(2). -ssize_t Read(int fd, void* dst, size_t dst_size); + ScopedPlatformHandle rd; + ScopedPlatformHandle wr; +}; -// Call write until all data is written or an error is detected. -// -// man 2 write: -// If a write() is interrupted by a signal handler before any bytes are -// written, then the call fails with the error EINTR; if it is -// interrupted after at least one byte has been written, the call -// succeeds, and returns the number of bytes written. -ssize_t WriteAll(int fd, const void* buf, size_t count); +} // namespace base +} // namespace perfetto -// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, -// rewinds it to the beginning, copies the content, and restores the offset. -// |fd_in| can't be a pipe, socket of FIFO. -base::Status CopyFileContents(int fd_in, int fd_out); +#endif // INCLUDE_PERFETTO_EXT_BASE_PIPE_H_ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -ScopedFile OpenFile(const std::string& path, - int flags, - FileOpenMode = kFileModeInvalid); -ScopedFstream OpenFstream(const std::string& path, const std::string& mode); +#include +#include -// This is an alias for close(). It's to avoid leaking windows.h in headers. -// Exported because ScopedFile is used in the /include/ext API by Chromium -// component builds. -int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include -bool FlushFile(int fd); +#include +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_QNX) +#include +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +#include +#include +#else // Mac, Fuchsia and other non-Linux UNIXes +#include +#endif -// Returns true if mkdir succeeds, false if it fails (see errno in that case). -bool Mkdir(const std::string& path); +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/event_fd.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/pipe.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" -// Calls rmdir() on UNIX, _rmdir() on Windows. -bool Rmdir(const std::string& path); +namespace perfetto { +namespace base { -// Wrapper around access(path, F_OK). -bool FileExists(const std::string& path); +EventFd::~EventFd() = default; -// Gets the extension for a filename. If the file has two extensions, returns -// only the last one (foo.pb.gz => .gz). Returns empty string if there is no -// extension. -std::string GetFileExtension(const std::string& filename); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +EventFd::EventFd() { + event_handle_.reset( + CreateEventA(/*lpEventAttributes=*/nullptr, /*bManualReset=*/true, + /*bInitialState=*/false, /*bInitialState=*/nullptr)); +} -// Returns the basename component of a path (the final component after the last -// directory separator). Behaves like man 2 basename, but works with both '/' -// and '\' separators for cross-platform compatibility. -// Examples: -// Basename("/usr/bin/ls") => "ls" -// Basename("/usr/bin/") => "bin" -// Basename("/") => "/" -// Basename("foo") => "foo" -// Basename("") => "." -// Basename("C:\\Windows\\System32") => "System32" -std::string Basename(const std::string& path); +void EventFd::Notify() { + if (!SetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. + PERFETTO_DFATAL("EventFd::Notify()"); +} -// Returns the directory component of a path (everything up to but not -// including the final component). Behaves like man 2 dirname, but works with -// both '/' and '\' separators for cross-platform compatibility. -// Examples: -// Dirname("/usr/bin/ls") => "/usr/bin" -// Dirname("/usr/bin") => "/usr" -// Dirname("/") => "/" -// Dirname("foo") => "." -// Dirname("") => "." -// Dirname("C:\\Windows\\System32") => "C:\\Windows" -std::string Dirname(const std::string& path); +void EventFd::Clear() { + if (!ResetEvent(event_handle_.get())) // 0: fail, !0: success, unlike UNIX. + PERFETTO_DFATAL("EventFd::Clear()"); +} -// Puts the path to all files under |dir_path| in |output|, recursively walking -// subdirectories. File paths are relative to |dir_path|. Only files are -// included, not directories. Path separator is always '/', even on windows (not -// '\'). -base::Status ListFilesRecursive(const std::string& dir_path, - std::vector& output); +#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names -// are relative to |dir_path| and do not include the path separator. Returns -// only directories, not files. Works on both Unix and Windows. -base::Status ListDirectories(const std::string& dir_path, - std::vector& output); +EventFd::EventFd() { + event_handle_.reset(eventfd(/*initval=*/0, EFD_CLOEXEC | EFD_NONBLOCK)); + PERFETTO_CHECK(event_handle_); +} -// Sets |path|'s owner group to |group_name| and permission mode bits to -// |mode_bits|. -base::Status SetFilePermissions(const std::string& path, - const std::string& group_name, - const std::string& mode_bits); +void EventFd::Notify() { + const uint64_t value = 1; + ssize_t ret = write(event_handle_.get(), &value, sizeof(value)); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Notify()"); +} -// Returns the size of the file located at |path|, or nullopt in case of error. -std::optional GetFileSize(const std::string& path); +void EventFd::Clear() { + uint64_t value; + ssize_t ret = + PERFETTO_EINTR(read(event_handle_.get(), &value, sizeof(value))); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Clear()"); +} -// Returns the size of the open file |fd|, or nullopt in case of error. -std::optional GetFileSize(PlatformHandle fd); +#else -// This class uses inotify (on Linux/Android) to watch for the creation of -// files in the filesystem. When the specified file is created, it triggers a -// callback function. -// Destroying the returned unique_ptr will automatically unregister the watch. -// -// Note: This only works with filesystem paths (not abstract sockets or other -// special file types). -// It's only supported on Linux and Android, it's a no-op (returns nullptr) on -// other platforms. -// -// Usage: -// auto watch = LinuxFileWatch::WatchFileCreation( -// task_runner, "/tmp/my_file", []() { -// // Called when /tmp/my_file is created -// }); -class LinuxFileWatch { - public: - // Creates a watcher for file creation. Returns nullptr if the path is not a - // valid filesystem path or if the platform doesn't support inotify. The - // callback will be invoked on the provided TaskRunner when the file is - // created. - static std::unique_ptr WatchFileCreation( - TaskRunner*, - const char* path, - std::function callback); +EventFd::EventFd() { + // Make the pipe non-blocking so that we never block the waking thread (either + // the main thread or another one) when scheduling a wake-up. + Pipe pipe = Pipe::Create(Pipe::kBothNonBlock); + event_handle_ = ScopedPlatformHandle(std::move(pipe.rd).release()); + write_fd_ = std::move(pipe.wr); +} - virtual ~LinuxFileWatch(); +void EventFd::Notify() { + const uint64_t value = 1; + ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t)); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Notify()"); +} - protected: - LinuxFileWatch() = default; -}; +void EventFd::Clear() { + // Drain the byte(s) written to the wake-up pipe. We can potentially read + // more than one byte if several wake-ups have been scheduled. + char buffer[16]; + ssize_t ret = + PERFETTO_EINTR(read(event_handle_.get(), &buffer[0], sizeof(buffer))); + if (ret <= 0 && errno != EAGAIN) + PERFETTO_DFATAL("EventFd::Clear()"); +} +#endif } // namespace base } // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +// gen_amalgamated begin source: src/base/file_utils.cc // gen_amalgamated begin header: include/perfetto/base/task_runner.h /* * Copyright (C) 2017 The Android Open Source Project @@ -3121,11 +3765,12 @@ bool FlushFile(int fd) { #endif } -bool Mkdir(const std::string& path) { +bool Mkdir(const std::string& path, uint32_t mode) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + base::ignore_result(mode); return _mkdir(path.c_str()) == 0; #else - return mkdir(path.c_str(), 0755) == 0; + return mkdir(path.c_str(), mode) == 0; #endif } @@ -3137,6 +3782,14 @@ bool Rmdir(const std::string& path) { #endif } +bool Unlink(const char* path) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + return _unlink(path) == 0; +#else + return unlink(path) == 0; +#endif +} + int CloseFile(int fd) { return close(fd); } @@ -3265,6 +3918,9 @@ base::Status ListFilesRecursive(const std::string& dir_path, struct stat dirstat; std::string full_path = cur_dir + dirent->d_name; PERFETTO_CHECK(stat(full_path.c_str(), &dirstat) == 0); + // MSan's stat() interceptor on glibc 2.35+ does not mark the output + // buffer as initialized (the syscall goes through statx). + PERFETTO_MSAN_UNPOISON(&dirstat, sizeof(dirstat)); if (S_ISDIR(dirstat.st_mode)) { dir_queue.push_back(full_path + '/'); } else if (S_ISREG(dirstat.st_mode)) { @@ -3596,275 +4252,6 @@ LinuxFileWatch::~LinuxFileWatch() = default; #endif // OS_LINUX || OS_ANDROID -} // namespace base -} // namespace perfetto -// gen_amalgamated begin source: src/base/fixed_string_writer.cc -// gen_amalgamated begin header: include/perfetto/ext/base/fixed_string_writer.h -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ - -#include - -#include -#include -#include -#include -#include -#include - -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" - -namespace perfetto { -namespace base { - -// A helper class which writes formatted data to a string buffer. -// This is used in the trace processor where we write O(GBs) of strings and -// sprintf is too slow. -class FixedStringWriter { - public: - // Creates a string buffer from a char buffer and length. - FixedStringWriter(char* buffer, size_t size) : buffer_(buffer), size_(size) {} - - // Appends n instances of a char to the buffer. - void AppendChar(char in, size_t n = 1) { - PERFETTO_DCHECK(pos_ + n <= size_); - memset(&buffer_[pos_], in, n); - pos_ += n; - } - - // Appends a length delimited string to the buffer. - void AppendString(const char* in, size_t n) { - PERFETTO_DCHECK(pos_ + n <= size_); - memcpy(&buffer_[pos_], in, n); - pos_ += n; - } - - void AppendStringView(StringView sv) { AppendString(sv.data(), sv.size()); } - - // Appends a null-terminated string literal to the buffer. - template - inline void AppendLiteral(const char (&in)[N]) { - AppendString(in, N - 1); - } - - // Appends a StringView to the buffer. - void AppendString(StringView data) { AppendString(data.data(), data.size()); } - - // Appends an integer to the buffer. - void AppendInt(int64_t value) { AppendPaddedInt<'0', 0>(value); } - - // Appends an integer to the buffer, padding with |padchar| if the number of - // digits of the integer is less than |padding|. - template - void AppendPaddedInt(int64_t sign_value) { - const bool negate = std::signbit(static_cast(sign_value)); - uint64_t absolute_value; - if (sign_value == std::numeric_limits::min()) { - absolute_value = - static_cast(std::numeric_limits::max()) + 1; - } else { - absolute_value = static_cast(std::abs(sign_value)); - } - AppendPaddedInt(absolute_value, negate); - } - - void AppendUnsignedInt(uint64_t value) { - AppendPaddedUnsignedInt<'0', 0>(value); - } - - // Appends an unsigned integer to the buffer, padding with |padchar| if the - // number of digits of the integer is less than |padding|. - template - void AppendPaddedUnsignedInt(uint64_t value) { - AppendPaddedInt(value, false); - } - - template - void AppendPaddedHexInt(IntType value, char padchar, uint64_t padding) { - using UnsignedType = std::make_unsigned_t; - constexpr size_t kMaxHexDigits = sizeof(IntType) * 2; - // 32 bytes is more than enough for any integer type (max 16 hex digits for - // 64-bit) - constexpr size_t kBufferSize = 32; - auto size_needed = - kMaxHexDigits > padding ? kMaxHexDigits : static_cast(padding); - PERFETTO_DCHECK(size_needed <= kBufferSize); - PERFETTO_DCHECK(pos_ + size_needed <= size_); - - std::array data; - constexpr char hex_asc[] = "0123456789abcdef"; - - size_t idx = size_needed - 1; - auto uvalue = static_cast(value); - do { - data[idx--] = hex_asc[uvalue & 0xF]; - uvalue >>= 4; - } while (uvalue != 0); - - if (padding > 0) { - const auto num_digits = static_cast(size_needed - 1 - idx); - // std::max() needed to work around GCC not being able to tell that - // padding > 0. - for (auto i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { - data[idx--] = padchar; - } - } - AppendString(&data[idx + 1], size_needed - idx - 1); - } - - // Appends a hex integer to the buffer. - template - void AppendHexInt(IntType value) { - AppendPaddedHexInt(value, '0', 0); - } - - // Appends a hex string to the buffer. - void AppendHexString(const uint8_t* data, size_t size, char separator); - - void AppendHexString(StringView data, char separator) { - AppendHexString(reinterpret_cast(data.data()), data.size(), - separator); - } - - // Appends a double to the buffer. - void AppendDouble(double value) { - // TODO(lalitm): trying to optimize this is premature given we almost never - // print doubles. Reevaluate this in the future if we do print them more. - size_t res = base::SprintfTrunc(buffer_ + pos_, size_ - pos_, "%lf", value); - PERFETTO_DCHECK(pos_ + res <= size_); - pos_ += res; - } - - void AppendBool(bool value) { - if (value) { - AppendLiteral("true"); - return; - } - AppendLiteral("false"); - } - - StringView GetStringView() { - PERFETTO_DCHECK(pos_ <= size_); - return StringView(buffer_, pos_); - } - - char* CreateStringCopy() { - char* dup = reinterpret_cast(malloc(pos_ + 1)); - if (dup) { - memcpy(dup, buffer_, pos_); - dup[pos_] = '\0'; - } - return dup; - } - - size_t pos() const { return pos_; } - size_t size() const { return size_; } - void reset() { pos_ = 0; } - - private: - template - void AppendPaddedInt(uint64_t absolute_value, bool negate) { - // Need to add 2 to the number of digits to account for minus sign and - // rounding down of digits10. - constexpr auto kMaxDigits = std::numeric_limits::digits10 + 2; - constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding; - PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_); - - char data[kSizeNeeded]; - - size_t idx; - for (idx = kSizeNeeded - 1; absolute_value >= 10;) { - char digit = absolute_value % 10; - absolute_value /= 10; - data[idx--] = digit + '0'; - } - data[idx--] = static_cast(absolute_value) + '0'; - - if (padding > 0) { - size_t num_digits = kSizeNeeded - 1 - idx; - // std::max() needed to work around GCC not being able to tell that - // padding > 0. - for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) { - data[idx--] = padchar; - } - } - - if (negate) - buffer_[pos_++] = '-'; - AppendString(&data[idx + 1], kSizeNeeded - idx - 1); - } - - char* buffer_ = nullptr; - size_t size_ = 0; - size_t pos_ = 0; -}; - -} // namespace base -} // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_FIXED_STRING_WRITER_H_ -/* - * Copyright (C) 2026 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// gen_amalgamated expanded: #include "perfetto/ext/base/fixed_string_writer.h" - -#include -#include -#include - -namespace perfetto { -namespace base { - -void FixedStringWriter::AppendHexString(const uint8_t* data, - size_t size, - char separator) { - // Truncate to 64 bytes, as this is the maximum supported by the Linux - // kernel's vsnprintf implementation. - size_t printed_size = std::min(size, size_t{64}); - // Remove trailing separator from calculation if printed_size > 0. - size_t max_chars = printed_size * 3 - (printed_size > 0 ? 1 : 0); - PERFETTO_DCHECK(pos_ + max_chars <= size_); - - if (printed_size) { - AppendPaddedHexInt(data[0], '0', 2); - } - for (size_t pos = 1; pos < printed_size; pos++) { - AppendChar(separator); - AppendPaddedHexInt(data[pos], '0', 2); - } -} - } // namespace base } // namespace perfetto // gen_amalgamated begin source: src/base/getopt_compat.cc @@ -3997,6 +4384,36 @@ const option* LookupShortOpt(const std::vector