diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9abee6864b8ee6..78d06c501e8835 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "microsoft.dotnet.xharness.cli": { - "version": "9.0.0-prerelease.24203.1", + "version": "9.0.0-prerelease.24208.1", "commands": [ "xharness" ] diff --git a/.github/workflows/bump-chrome-version.yml b/.github/workflows/bump-chrome-version.yml index 65427d63f657ce..fc2f09cf3d01f7 100644 --- a/.github/workflows/bump-chrome-version.yml +++ b/.github/workflows/bump-chrome-version.yml @@ -27,7 +27,7 @@ jobs: run: >- make -C src/mono/wasm build-tasks && PATH=$PWD/.dotnet:$PATH dotnet build eng/testing/bump-chrome-version.proj -p:Configuration=Release && - git add eng/testing/ChromeVersions.props && + git add eng/testing/BrowserVersions.props && cat eng/testing/bump-chrome-pr.env >> "$GITHUB_ENV" - name: Check for changes diff --git a/.github/workflows/locker.yml b/.github/workflows/locker.yml index 2b7ab003275844..bea3f2fa09f983 100644 --- a/.github/workflows/locker.yml +++ b/.github/workflows/locker.yml @@ -20,13 +20,14 @@ permissions: jobs: main: runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'dotnet' }} steps: - name: Checkout Actions uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions - ref: cd16cd2aad6ba2da74bb6c6f7293adddd579a90e + ref: 066bee9cefa6f0b4bf306040ff36fc7d96a6d56d # locker action commit sha - name: Install Actions run: npm install --production --prefix ./actions - name: Run Locker diff --git a/Directory.Build.targets b/Directory.Build.targets index f731eedc390c36..1161d409dec1ab 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -86,9 +86,10 @@ - + $(SystemReflectionMetadataLoadContextVersion) + $(SystemTextJsonVersion) - + https://github.com/dotnet/icu - 1441a3fcbfa87c94b98a27605b06db7dd862f3e4 + 0ea0175965771285846b5d077bebe5946036a595 https://github.com/dotnet/msquic @@ -12,9 +12,9 @@ https://github.com/dotnet/wcf 7f504aabb1988e9a093c1e74d8040bd52feb2f01 - + https://github.com/dotnet/emsdk - c42ff642a91a8aa4345de1728c3fa585ec13e1e6 + 19c9523f5c2dd091b49959700723af795d6ad2b4 https://github.com/dotnet/llvm-project @@ -58,24 +58,24 @@ a045dd54a4c44723c215d992288160eb1401bb7f - + https://github.com/dotnet/cecil - 9c8ea966df62f764523b51772763e74e71040a92 + 861f49c137941b9722a43e5993ccac7716c8528c - + https://github.com/dotnet/cecil - 9c8ea966df62f764523b51772763e74e71040a92 + 861f49c137941b9722a43e5993ccac7716c8528c - + https://github.com/dotnet/emsdk - c42ff642a91a8aa4345de1728c3fa585ec13e1e6 + 19c9523f5c2dd091b49959700723af795d6ad2b4 - + https://github.com/dotnet/emsdk - c42ff642a91a8aa4345de1728c3fa585ec13e1e6 + 19c9523f5c2dd091b49959700723af795d6ad2b4 @@ -85,146 +85,146 @@ - + https://github.com/dotnet/source-build-externals - 83566118e44922c30d146654d42c7c3745cc119d + 5a273649709de76f61957e3d69e1f031e5ac82e2 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e https://github.com/dotnet/llvm-project @@ -282,59 +282,59 @@ https://github.com/dotnet/llvm-project 26f8c30340764cfa7fa9090dc01a36c222bf09c1 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/runtime - ec4437be46d8b90bc9fa6740c556bd860d9fe5ab + 85fbd98765c47a867564fff6ae18cc92423cdc66 - + https://github.com/dotnet/xharness - 28af9496b0e260f7e66ec549b39f1410ee9743d1 + 50b43ece7daf9f8a88ac16a95a4f8647a4c71c4b - + https://github.com/dotnet/xharness - 28af9496b0e260f7e66ec549b39f1410ee9743d1 + 50b43ece7daf9f8a88ac16a95a4f8647a4c71c4b - + https://github.com/dotnet/xharness - 28af9496b0e260f7e66ec549b39f1410ee9743d1 + 50b43ece7daf9f8a88ac16a95a4f8647a4c71c4b - + https://github.com/dotnet/arcade - 87b015b938e5400d6e57afd7650348c17a764b73 + 8ec8057ac5073b6b2e3fcb0a33d588d2a3357ad3 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization @@ -352,48 +352,48 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization 78a5b978e1965c1335edb4b9a22bc4d6ff5a77a6 - + https://github.com/dotnet/hotreload-utils - 668ee30182fea845064853c46be5f54ac6efd110 + 4670b9e37293570f8d93d6af40c4710e2686bf67 - + https://github.com/dotnet/runtime-assets - f4d56683216389e84003fabcc73b929ba5012e3d + 30b6a8d9d3af5681e4caef1ea453619a4b0e9f2e - + https://github.com/dotnet/roslyn - 84c5476ef3111c9abd78d43e65063280bb7202d9 + ca66296efa86bd8078508fe7b38b91b415364f78 - + https://github.com/dotnet/roslyn - 84c5476ef3111c9abd78d43e65063280bb7202d9 + ca66296efa86bd8078508fe7b38b91b415364f78 - + https://github.com/dotnet/roslyn - 84c5476ef3111c9abd78d43e65063280bb7202d9 + ca66296efa86bd8078508fe7b38b91b415364f78 - + https://github.com/dotnet/roslyn-analyzers - 4d72fc19879fbc78a12d3a84ed60e7d17777d8b7 + b07c100bfc66013a8444172d00cfa04c9ceb5a97 - + https://github.com/dotnet/roslyn-analyzers - 4d72fc19879fbc78a12d3a84ed60e7d17777d8b7 + b07c100bfc66013a8444172d00cfa04c9ceb5a97 - + https://github.com/dotnet/roslyn - 84c5476ef3111c9abd78d43e65063280bb7202d9 + ca66296efa86bd8078508fe7b38b91b415364f78 - + https://github.com/dotnet/sdk - 219a6fc9954d632d7c119b31d59ff1516ff04d98 + cf8c24575410adf397c0823fd7061f9451049ea1 - + https://github.com/dotnet/sdk - 219a6fc9954d632d7c119b31d59ff1516ff04d98 + cf8c24575410adf397c0823fd7061f9451049ea1 @@ -410,9 +410,9 @@ https://github.com/NuGet/NuGet.Client 8fef55f5a55a3b4f2c96cd1a9b5ddc51d4b927f8 - + https://github.com/dotnet/installer - 0bfd2dd757482b30745b799ee0a92cad3d8f5b50 + 7380c301c1ce7d022dd365dd78c4004a2881edaf diff --git a/eng/Versions.props b/eng/Versions.props index ecb7db8e76cbab..46b53f8ae070c9 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -34,17 +34,17 @@ - 3.11.0-beta1.24212.1 - 9.0.0-preview.24212.1 + 3.11.0-beta1.24216.2 + 9.0.0-preview.24216.2 - 4.11.0-1.24214.4 - 4.11.0-1.24214.4 - 4.11.0-1.24214.4 + 4.11.0-1.24215.10 + 4.11.0-1.24215.10 + 4.11.0-1.24215.10 - 9.0.100-preview.4.24175.4 + 9.0.100-preview.4.24215.1 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 2.6.7-beta.24212.4 - 9.0.0-beta.24212.4 - 2.6.7-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 - 9.0.0-beta.24212.4 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 2.6.7-beta.24217.1 + 9.0.0-beta.24217.1 + 2.6.7-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 + 9.0.0-beta.24217.1 1.4.0 6.0.0-preview.1.102 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 6.0.0 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 6.0.0 1.1.1 @@ -119,38 +119,39 @@ 8.0.0 5.0.0 4.5.5 - 9.0.0-preview.4.24201.1 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 + 9.0.0-preview.4.24215.1 6.0.0 5.0.0 5.0.0 5.0.0 7.0.0 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 6.0.0 7.0.0 4.5.4 4.5.0 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 8.0.0 + 8.0.0 8.0.0 8.0.0 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 - 9.0.0-beta.24205.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 + 9.0.0-beta.24215.1 1.0.0-prerelease.24106.4 1.0.0-prerelease.24106.4 @@ -178,10 +179,10 @@ 1.4.0 17.4.0-preview-20220707-01 - 9.0.0-prerelease.24203.1 - 9.0.0-prerelease.24203.1 - 9.0.0-prerelease.24203.1 - 9.0.0-alpha.0.24201.1 + 9.0.0-prerelease.24208.1 + 9.0.0-prerelease.24208.1 + 9.0.0-prerelease.24208.1 + 9.0.0-alpha.0.24215.1 3.12.0 4.5.0 6.0.0 @@ -207,11 +208,11 @@ 8.0.0-preview-20230918.1 - 0.11.4-alpha.24168.1 + 0.11.4-alpha.24215.1 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 - 9.0.0-preview.4.24201.1 + 9.0.0-preview.4.24215.1 2.3.5 9.0.0-alpha.1.24167.3 @@ -234,9 +235,9 @@ Note: when the name is updated, make sure to update dependency name in eng/pipelines/common/xplat-setup.yml like - DarcDependenciesChanged.Microsoft_NET_Workload_Emscripten_Current_Manifest-9_0_100_Transport --> - 9.0.0-preview.4.24209.5 + 9.0.0-preview.4.24215.3 $(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion) - 9.0.0-preview.4.24209.5 + 9.0.0-preview.4.24215.3 1.1.87-gba258badda 1.0.0-v3.14.0.5722 @@ -253,7 +254,7 @@ 3.1.7 1.0.406601 - 9.0.100-preview.4.24208.2 + 9.0.100-preview.4.24215.2 $(MicrosoftDotnetSdkInternalVersion) diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index c63e17e863ed17..761acc5eb624c6 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -210,7 +210,7 @@ jobs: - task: 1ES.PublishPipelineArtifact@1 inputs: targetPath: 'artifacts/log' - artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)_Attempt$(System.JobAttempt)') }} displayName: 'Publish logs' continueOnError: true condition: always() diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml index ba9ba49303292a..52b4d05d3f8dd6 100644 --- a/eng/common/templates-official/job/onelocbuild.yml +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -56,7 +56,7 @@ jobs: # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml index c918720931f49a..2180e97a284f84 100644 --- a/eng/common/templates-official/job/source-build.yml +++ b/eng/common/templates-official/job/source-build.yml @@ -52,7 +52,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2-pt + image: 1es-mariner-2 os: linux ${{ if ne(parameters.platform.pool, '') }}: diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml index d286e956bdfa40..da1f40958b450d 100644 --- a/eng/common/templates-official/post-build/post-build.yml +++ b/eng/common/templates-official/post-build/post-build.yml @@ -110,7 +110,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: @@ -150,7 +150,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -208,7 +208,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml index beab7d1bfba062..1f308b24efc43d 100644 --- a/eng/common/templates-official/variables/pool-providers.yml +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# image: 1es-windows-2022-pt +# image: 1es-windows-2022 variables: # Coalesce the target and source branches so we know when a PR targets a release branch diff --git a/eng/pipelines/common/evaluate-default-paths.yml b/eng/pipelines/common/evaluate-default-paths.yml index edbc1c618f6066..d954a1ddacbb57 100644 --- a/eng/pipelines/common/evaluate-default-paths.yml +++ b/eng/pipelines/common/evaluate-default-paths.yml @@ -7,7 +7,7 @@ parameters: _const_paths: _wasm_specific_only: [ eng/testing/bump-chrome-version.proj - eng/testing/ChromeVersions.props + eng/testing/BrowserVersions.props eng/testing/WasmRunner* eng/testing/WasiRunner* eng/testing/scenarios/BuildWasiAppsJobsList.txt @@ -22,6 +22,7 @@ parameters: src/libraries/sendtohelix-browser.targets src/libraries/sendtohelix-wasi.targets src/libraries/sendtohelix-wasm.targets + src/libraries/System.Runtime.InteropServices.JavaScript/* src/mono/mono/**/*wasm* src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/* src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/* @@ -57,7 +58,7 @@ parameters: ] _wasm_chrome: [ eng/testing/bump-chrome-version.proj - eng/testing/ChromeVersions.props + eng/testing/BrowserVersions.props ] _perf_pipeline_specific_only: [ eng/pipelines/runtime-wasm-perf.yml diff --git a/eng/pipelines/common/templates/pipeline-with-resources.yml b/eng/pipelines/common/templates/pipeline-with-resources.yml index 5ae3a3f7a38f56..b9db26f6cb151c 100644 --- a/eng/pipelines/common/templates/pipeline-with-resources.yml +++ b/eng/pipelines/common/templates/pipeline-with-resources.yml @@ -17,7 +17,7 @@ extends: containers: linux_arm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-arm + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-net8.0 env: ROOTFS_DIR: /crossrootfs/arm @@ -33,17 +33,17 @@ extends: ROOTFS_DIR: /crossrootfs/arm64 linux_musl_x64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-amd64-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-amd64-alpine-net8.0 env: ROOTFS_DIR: /crossrootfs/x64 linux_musl_arm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-arm-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm-alpine-net8.0 env: ROOTFS_DIR: /crossrootfs/arm linux_musl_arm64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-arm64-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-arm64-alpine-net8.0 env: ROOTFS_DIR: /crossrootfs/arm64 @@ -56,12 +56,12 @@ extends: image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-android-docker linux_x64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-amd64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-amd64-net8.0 env: ROOTFS_DIR: /crossrootfs/x64 linux_x86: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-2.0-cross-x86 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-cross-x86-net8.0 env: ROOTFS_DIR: /crossrootfs/x86 diff --git a/eng/pipelines/common/templates/wasm-library-aot-tests.yml b/eng/pipelines/common/templates/wasm-library-aot-tests.yml index 9937d44ef21f67..cc86f4fd710fb2 100644 --- a/eng/pipelines/common/templates/wasm-library-aot-tests.yml +++ b/eng/pipelines/common/templates/wasm-library-aot-tests.yml @@ -31,7 +31,9 @@ jobs: shouldRunSmokeOnly: ${{ parameters.shouldRunSmokeOnly }} shouldContinueOnError: ${{ parameters.shouldContinueOnError }} scenarios: - - ${{ if eq(platform, 'browser_wasm_win') }}: - - WasmTestOnBrowser - - ${{ if ne(platform, 'browser_wasm_win') }}: + - ${{ if eq(platform, 'browser_wasm') }}: - normal + - ${{ if eq(platform, 'browser_wasm_win') }}: + - WasmTestOnChrome + - ${{ if or(eq(platform, 'wasi_wasm_win'), eq(platform, 'wasi_wasm')) }}: + - WasmTestOnWasmtime diff --git a/eng/pipelines/common/templates/wasm-library-tests.yml b/eng/pipelines/common/templates/wasm-library-tests.yml index 3765b35c30af96..b3193f4a495a59 100644 --- a/eng/pipelines/common/templates/wasm-library-tests.yml +++ b/eng/pipelines/common/templates/wasm-library-tests.yml @@ -61,7 +61,7 @@ jobs: - name: _wasmRunSmokeTestsOnlyArg value: /p:RunSmokeTestsOnly=$(shouldRunSmokeOnlyVar) - name: chromeInstallArg - ${{ if containsValue(parameters.scenarios, 'wasmtestonbrowser') }}: + ${{ if containsValue(parameters.scenarios, 'WasmTestOnChrome') }}: value: /p:InstallChromeForTests=true ${{ else }}: value: '' diff --git a/eng/pipelines/coreclr/templates/run-performance-job.yml b/eng/pipelines/coreclr/templates/run-performance-job.yml index 65b1cb3b78443c..2b61558e2fc97f 100644 --- a/eng/pipelines/coreclr/templates/run-performance-job.yml +++ b/eng/pipelines/coreclr/templates/run-performance-job.yml @@ -93,6 +93,7 @@ jobs: echo "** Installing prerequistes **"; echo "** Waiting for dpkg to unlock (up to 2 minutes) **" && timeout 2m bash -c 'while sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do if [ -z "$printed" ]; then echo "Waiting for dpkg lock to be released... Lock is held by: $(ps -o cmd= -p $(sudo fuser /var/lib/dpkg/lock-frontend))"; printed=1; fi; echo "Waiting 5 seconds to check again"; sleep 5; done;' && + sudo apt-get remove -y lttng-modules-dkms && sudo apt-get -y install python3-pip && python3 -m pip install --user -U pip && sudo apt-get -y install python3-venv && @@ -194,4 +195,4 @@ jobs: WorkItemDirectory: '$(WorkItemDirectory)' # WorkItemDirectory can not be empty, so we send it some docs to keep it happy CorrelationPayloadDirectory: '$(PayloadDirectory)' # it gets checked out to a folder with shorter path than WorkItemDirectory so we can avoid file name too long exceptions ProjectFile: ${{ parameters.projectFile }} - osGroup: ${{ parameters.osGroup }} \ No newline at end of file + osGroup: ${{ parameters.osGroup }} diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml index fc8d757233cd45..ec77f76a85828c 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml @@ -90,7 +90,7 @@ jobs: isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} scenarios: - normal - - WasmTestOnBrowser + - WasmTestOnChrome # this only runs on the extra pipeline - template: /eng/pipelines/common/templates/wasm-library-tests.yml @@ -114,7 +114,7 @@ jobs: isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} scenarios: - - WasmTestOnBrowser + - WasmTestOnChrome - WasmTestOnNodeJS # EAT Library tests - only run on linux @@ -198,7 +198,7 @@ jobs: alwaysRun: true scenarios: - normal - - WasmTestOnBrowser + - WasmTestOnChrome - WasmTestOnNodeJS # Hybrid Globalization AOT tests @@ -215,7 +215,7 @@ jobs: alwaysRun: true scenarios: - normal - - WasmTestOnBrowser + - WasmTestOnChrome - WasmTestOnNodeJS - ${{ if and(ne(parameters.isRollingBuild, true), ne(parameters.excludeNonLibTests, true), ne(parameters.debuggerTestsOnly, true)) }}: diff --git a/eng/pipelines/runtime-wasm-perf.yml b/eng/pipelines/runtime-wasm-perf.yml index 91e508e2c9669d..39645a501ecf25 100644 --- a/eng/pipelines/runtime-wasm-perf.yml +++ b/eng/pipelines/runtime-wasm-perf.yml @@ -16,7 +16,7 @@ pr: - eng/pipelines/coreclr/templates/run-perf* - eng/pipelines/coreclr/templates/run-scenarios-job.yml - eng/testing/performance/* - - eng/testing/ChromeVersions.props + - eng/testing/BrowserVersions.props variables: - template: /eng/pipelines/common/variables.yml diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index ace914f9ecf60b..284a2aafb0888a 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -824,7 +824,7 @@ extends: extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) scenarios: - normal - - WasmTestOnBrowser + - WasmTestOnChrome - template: /eng/pipelines/common/templates/wasm-library-tests.yml parameters: @@ -833,7 +833,7 @@ extends: alwaysRun: ${{ variables.isRollingBuild }} extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) scenarios: - - WasmTestOnBrowser + - WasmTestOnChrome # Library tests with full threading - template: /eng/pipelines/common/templates/wasm-library-tests.yml @@ -846,7 +846,7 @@ extends: alwaysRun: ${{ variables.isRollingBuild }} shouldRunSmokeOnly: onLibrariesAndIllinkChanges scenarios: - - WasmTestOnBrowser + - WasmTestOnChrome #- WasmTestOnNodeJS - this is not supported yet, https://github.com/dotnet/runtime/issues/85592 # EAT Library tests - only run on linux @@ -935,7 +935,7 @@ extends: shouldRunSmokeOnly: true alwaysRun: ${{ variables.isRollingBuild }} scenarios: - - normal + - WasmTestOnWasmtime - template: /eng/pipelines/common/templates/simple-wasm-build-tests.yml parameters: diff --git a/eng/testing/ChromeVersions.props b/eng/testing/BrowserVersions.props similarity index 83% rename from eng/testing/ChromeVersions.props rename to eng/testing/BrowserVersions.props index bb40a987e44df6..b1e85302a8ed3a 100644 --- a/eng/testing/ChromeVersions.props +++ b/eng/testing/BrowserVersions.props @@ -8,5 +8,7 @@ 1250580 https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1250586 12.3.219 + 124.0.2 + 0.34.0 diff --git a/eng/testing/WasiRunnerAOTTemplate.sh b/eng/testing/WasiRunnerAOTTemplate.sh deleted file mode 100644 index b45c553b67c566..00000000000000 --- a/eng/testing/WasiRunnerAOTTemplate.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -# SetCommands defined in eng\testing\tests.wasm.targets -[[SetCommands]] -[[SetCommandsEcho]] - -EXECUTION_DIR=$(dirname $0) -if [[ -n "$3" ]]; then - SCENARIO=$3 -fi - -export PATH=$PREPEND_PATH:$PATH - -if [[ -z "$HELIX_WORKITEM_UPLOAD_ROOT" ]]; then - XHARNESS_OUT="$EXECUTION_DIR/xharness-output" -else - XHARNESS_OUT="$HELIX_WORKITEM_UPLOAD_ROOT/xharness-output" -fi - -if [[ -n "$XHARNESS_CLI_PATH" ]]; then - # When running in CI, we only have the .NET runtime available - # We need to call the XHarness CLI DLL directly via dotnet exec - HARNESS_RUNNER="dotnet exec $XHARNESS_CLI_PATH" -else - HARNESS_RUNNER="dotnet xharness" -fi - -if [[ -z "$XHARNESS_COMMAND" ]]; then - XHARNESS_COMMAND="test" -fi - -echo PATH=$PATH -echo EXECUTION_DIR=$EXECUTION_DIR -echo SCENARIO=$SCENARIO -echo XHARNESS_OUT=$XHARNESS_OUT -echo XHARNESS_CLI_PATH=$XHARNESS_CLI_PATH -echo HARNESS_RUNNER=$HARNESS_RUNNER -echo XHARNESS_COMMAND=$XHARNESS_COMMAND -echo XHARNESS_ARGS=$XHARNESS_ARGS - -function _buildAOTFunc() -{ - local projectFile=$1 - local binLog=$2 - shift 2 - - time dotnet msbuild $projectFile /bl:$binLog $* - local buildExitCode=$? - - echo "\n** Performance summary for the build **\n" - dotnet msbuild $binLog -clp:PerformanceSummary -v:q -nologo - if [[ "$(uname -s)" == "Linux" && $buildExitCode -ne 0 ]]; then - echo "\nLast few messages from dmesg:\n" - local lastLines=`dmesg | tail -n 20` - echo $lastLines - - if [[ "$lastLines" =~ "oom-kill" ]]; then - return 9200 # OOM - fi - fi - - echo - echo - - if [[ $buildExitCode -ne 0 ]]; then - return 9100 # aot build failure - fi - - return 0 -} - -pushd $EXECUTION_DIR - -# ========================= BEGIN Test Execution ============================= -echo ----- start $(date) =============== To repro directly: ===================================================== -echo pushd $EXECUTION_DIR -# RunCommands defined in eng\testing\tests.wasm.targets -[[RunCommandsEcho]] -echo popd -echo =========================================================================================================== -pushd $EXECUTION_DIR -# RunCommands defined in eng\testing\tests.wasm.targets -[[RunCommands]] -_exitCode=$? -popd -echo ----- end $(date) ----- exit code $_exitCode ---------------------------------------------------------- - -echo "XHarness artifacts: $XHARNESS_OUT" - -exit $_exitCode diff --git a/eng/testing/WasiRunnerTemplate.sh b/eng/testing/WasiRunnerTemplate.sh index 7a3752d321172a..66e6731e03f26b 100644 --- a/eng/testing/WasiRunnerTemplate.sh +++ b/eng/testing/WasiRunnerTemplate.sh @@ -47,6 +47,37 @@ echo HARNESS_RUNNER=$HARNESS_RUNNER echo XHARNESS_COMMAND=$XHARNESS_COMMAND echo XHARNESS_ARGS=$XHARNESS_ARGS +function _buildAOTFunc() +{ + local projectFile=$1 + local binLog=$2 + shift 2 + + time dotnet msbuild $projectFile /bl:$binLog $* + local buildExitCode=$? + + echo "\n** Performance summary for the build **\n" + dotnet msbuild $binLog -clp:PerformanceSummary -v:q -nologo + if [[ "$(uname -s)" == "Linux" && $buildExitCode -ne 0 ]]; then + echo "\nLast few messages from dmesg:\n" + local lastLines=`dmesg | tail -n 20` + echo $lastLines + + if [[ "$lastLines" =~ "oom-kill" ]]; then + return 9200 # OOM + fi + fi + + echo + echo + + if [[ $buildExitCode -ne 0 ]]; then + return 9100 # aot build failure + fi + + return 0 +} + pushd $EXECUTION_DIR # ========================= BEGIN Test Execution ============================= diff --git a/eng/testing/WasmRunnerAOTTemplate.sh b/eng/testing/WasmRunnerAOTTemplate.sh deleted file mode 100644 index 1e17a2a4770edf..00000000000000 --- a/eng/testing/WasmRunnerAOTTemplate.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env bash - -# SetCommands defined in eng\testing\tests.wasm.targets -[[SetCommands]] -[[SetCommandsEcho]] - -export PATH="$HOME/.jsvu/bin:$PATH" -export PATH=$PREPEND_PATH:$PATH - -EXECUTION_DIR=$(dirname $0) -if [[ -n "$3" ]]; then - SCENARIO=$3 -fi - -if [[ -z "$HELIX_WORKITEM_UPLOAD_ROOT" ]]; then - XHARNESS_OUT="$EXECUTION_DIR/xharness-output" -else - XHARNESS_OUT="$HELIX_WORKITEM_UPLOAD_ROOT/xharness-output" -fi - -if [[ -n "$XHARNESS_CLI_PATH" ]]; then - # When running in CI, we only have the .NET runtime available - # We need to call the XHarness CLI DLL directly via dotnet exec - HARNESS_RUNNER="dotnet exec $XHARNESS_CLI_PATH" -else - HARNESS_RUNNER="dotnet xharness" -fi - -if [[ -z "$XHARNESS_COMMAND" ]]; then - if [[ "$SCENARIO" == "WasmTestOnBrowser" || "$SCENARIO" == "wasmtestonbrowser" ]]; then - XHARNESS_COMMAND="test-browser" - else - XHARNESS_COMMAND="test" - fi -fi - -if [[ "$XHARNESS_COMMAND" == "test" ]]; then - if [[ -z "$JS_ENGINE_ARGS" ]]; then - JS_ENGINE_ARGS="--engine-arg=--stack-trace-limit=1000" - if [[ "$SCENARIO" != "WasmTestOnNodeJS" && "$SCENARIO" != "wasmtestonnodejs" ]]; then - JS_ENGINE_ARGS="$JS_ENGINE_ARGS --engine-arg=--module" - fi - if [[ "$SCENARIO" == "WasmTestOnNodeJS" || "$SCENARIO" == "wasmtestonnodejs" ]]; then - JS_ENGINE_ARGS="$JS_ENGINE_ARGS --engine-arg=--experimental-wasm-eh" - fi - fi - - if [[ -z "$MAIN_JS" ]]; then - MAIN_JS="--js-file=test-main.js" - fi - - if [[ -z "$JS_ENGINE" ]]; then - if [[ "$SCENARIO" == "WasmTestOnNodeJS" || "$SCENARIO" == "wasmtestonnodejs" ]]; then - JS_ENGINE="--engine=NodeJS" - else - JS_ENGINE="--engine=V8" - if [[ -n "$V8_PATH_FOR_TESTS" ]]; then - JS_ENGINE_ARGS="$JS_ENGINE_ARGS --js-engine-path=$V8_PATH_FOR_TESTS" - fi - fi - fi -fi - -if [[ -z "$XHARNESS_ARGS" ]]; then - XHARNESS_ARGS="$JS_ENGINE $JS_ENGINE_ARGS $MAIN_JS" -fi - -echo PATH=$PATH -echo EXECUTION_DIR=$EXECUTION_DIR -echo SCENARIO=$SCENARIO -echo XHARNESS_OUT=$XHARNESS_OUT -echo XHARNESS_CLI_PATH=$XHARNESS_CLI_PATH -echo HARNESS_RUNNER=$HARNESS_RUNNER -echo XHARNESS_COMMAND=$XHARNESS_COMMAND -echo MAIN_JS=$MAIN_JS -echo JS_ENGINE=$JS_ENGINE -echo JS_ENGINE_ARGS=$JS_ENGINE_ARGS -echo XHARNESS_ARGS=$XHARNESS_ARGS - -function _buildAOTFunc() -{ - local projectFile=$1 - local binLog=$2 - shift 2 - - time dotnet msbuild $projectFile /bl:$binLog $* - local buildExitCode=$? - - echo "\n** Performance summary for the build **\n" - dotnet msbuild $binLog -clp:PerformanceSummary -v:q -nologo - if [[ "$(uname -s)" == "Linux" && $buildExitCode -ne 0 ]]; then - echo "\nLast few messages from dmesg:\n" - local lastLines=`dmesg | tail -n 20` - echo $lastLines - - if [[ "$lastLines" =~ "oom-kill" ]]; then - return 9200 # OOM - fi - fi - - echo - echo - - if [[ $buildExitCode -ne 0 ]]; then - return 9100 # aot build failure - fi - - return 0 -} - - -pushd $EXECUTION_DIR - -# ========================= BEGIN Test Execution ============================= -echo ----- start $(date) =============== To repro directly: ===================================================== -echo pushd $EXECUTION_DIR -# RunCommands defined in eng\testing\tests.wasm.targets -[[RunCommandsEcho]] -echo popd -echo =========================================================================================================== -pushd $EXECUTION_DIR -# RunCommands defined in eng\testing\tests.wasm.targets -[[RunCommands]] -_exitCode=$? -popd -echo ----- end $(date) ----- exit code $_exitCode ---------------------------------------------------------- - -echo "XHarness artifacts: $XHARNESS_OUT" - -exit $_exitCode diff --git a/eng/testing/WasmRunnerTemplate.cmd b/eng/testing/WasmRunnerTemplate.cmd index f92cee17cc9df7..0c7f3dc2195d26 100644 --- a/eng/testing/WasmRunnerTemplate.cmd +++ b/eng/testing/WasmRunnerTemplate.cmd @@ -27,7 +27,7 @@ if [%XHARNESS_CLI_PATH%] NEQ [] ( ) if [%XHARNESS_COMMAND%] == [] ( - if /I [%SCENARIO%]==[WasmTestOnBrowser] ( + if /I [%SCENARIO%]==[WasmTestOnChrome] ( set XHARNESS_COMMAND=test-browser ) else ( set XHARNESS_COMMAND=test diff --git a/eng/testing/WasmRunnerTemplate.sh b/eng/testing/WasmRunnerTemplate.sh index 4f5856546fc56b..bd7f1faadf3556 100644 --- a/eng/testing/WasmRunnerTemplate.sh +++ b/eng/testing/WasmRunnerTemplate.sh @@ -26,7 +26,7 @@ else fi if [[ -z "$XHARNESS_COMMAND" ]]; then - if [[ "$SCENARIO" == "WasmTestOnBrowser" || "$SCENARIO" == "wasmtestonbrowser" ]]; then + if [[ "$SCENARIO" == "WasmTestOnChrome" || "$SCENARIO" == "wasmtestonchrome" ]]; then XHARNESS_COMMAND="test-browser" else XHARNESS_COMMAND="test" @@ -88,6 +88,38 @@ echo JS_ENGINE=$JS_ENGINE echo JS_ENGINE_ARGS=$JS_ENGINE_ARGS echo XHARNESS_ARGS=$XHARNESS_ARGS +function _buildAOTFunc() +{ + local projectFile=$1 + local binLog=$2 + shift 2 + + time dotnet msbuild $projectFile /bl:$binLog $* + local buildExitCode=$? + + echo "\n** Performance summary for the build **\n" + dotnet msbuild $binLog -clp:PerformanceSummary -v:q -nologo + if [[ "$(uname -s)" == "Linux" && $buildExitCode -ne 0 ]]; then + echo "\nLast few messages from dmesg:\n" + local lastLines=`dmesg | tail -n 20` + echo $lastLines + + if [[ "$lastLines" =~ "oom-kill" ]]; then + return 9200 # OOM + fi + fi + + echo + echo + + if [[ $buildExitCode -ne 0 ]]; then + return 9100 # aot build failure + fi + + return 0 +} + + pushd $EXECUTION_DIR # ========================= BEGIN Test Execution ============================= diff --git a/eng/testing/bump-chrome-version.proj b/eng/testing/bump-chrome-version.proj index 334e37bfd32ba7..77fc525f8c8034 100644 --- a/eng/testing/bump-chrome-version.proj +++ b/eng/testing/bump-chrome-version.proj @@ -3,7 +3,7 @@ - $(RepositoryEngineeringDir)testing\ChromeVersions.props + $(RepositoryEngineeringDir)testing\BrowserVersions.props $(RepositoryEngineeringDir)testing\bump-chrome-pr.env diff --git a/eng/testing/helix-extension-example.targets b/eng/testing/helix-extension-example.targets index e14095c075678b..77b3490d5001ae 100644 --- a/eng/testing/helix-extension-example.targets +++ b/eng/testing/helix-extension-example.targets @@ -1,5 +1,5 @@ - + $(HelixExtensionTargets);_AddHelixCrypoItems <_CryptoProjectName>System.Security.Cryptography.Tests diff --git a/eng/testing/performance/performance-setup.sh b/eng/testing/performance/performance-setup.sh index ff04015375a388..489b3a22cccd5f 100755 --- a/eng/testing/performance/performance-setup.sh +++ b/eng/testing/performance/performance-setup.sh @@ -451,7 +451,7 @@ if [[ -n "$wasm_bundle_directory" ]]; then # get required version if [[ -z "$v8_version" ]]; then - v8_version=`grep linux_V8Version $source_directory/eng/testing/ChromeVersions.props | sed -e 's,.*>\([^\<]*\)<.*,\1,g' | cut -d. -f 1-3` + v8_version=`grep linux_V8Version $source_directory/eng/testing/BrowserVersions.props | sed -e 's,.*>\([^\<]*\)<.*,\1,g' | cut -d. -f 1-3` echo "V8 version: $v8_version" fi if [[ -z "$javascript_engine_path" ]]; then diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index 982b8589e76c14..ccfc96f5211cd1 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -26,7 +26,10 @@ true + '$(Scenario)' == 'WasmTestOnChrome'">true + true - <_WasmBrowserPathForTests Condition="'$(BROWSER_PATH_FOR_TESTS)' != ''">$(BROWSER_PATH_FOR_TESTS) - <_WasmBrowserPathForTests Condition="'$(_WasmBrowserPathForTests)' == '' and '$(InstallChromeForTests)' == 'true'">$(ChromeBinaryPath) + <_WasmChromePathForTests Condition="'$(CHROME_PATH_FOR_TESTS)' != ''">$(CHROME_PATH_FOR_TESTS) + <_WasmChromePathForTests Condition="'$(_WasmChromePathForTests)' == '' and '$(InstallChromeForTests)' == 'true'">$(ChromeBinaryPath) + + <_WasmFirefoxPathForTests Condition="'$(FIREFOX_PATH_FOR_TESTS)' != ''">$(FIREFOX_PATH_FOR_TESTS) + <_WasmFirefoxPathForTests Condition="'$(_WasmFirefoxPathForTests)' == '' and '$(InstallFirefoxForTests)' == 'true'">$(FirefoxBinaryPath) <_WasmJSEnginePathForTests Condition="'$(V8_PATH_FOR_TESTS)' != ''">$(V8_PATH_FOR_TESTS) <_WasmJSEnginePathForTests Condition="'$(_WasmJSEnginePathForTests)' == '' and '$(InstallV8ForTests)' == 'true'">$(V8BinaryPath) @@ -107,7 +113,8 @@ <_XHarnessArgs >$(_XHarnessArgs) -s dotnet.native.js.symbols <_XHarnessArgs Condition="'$(_UseWasmSymbolicator)' == 'true'" >$(_XHarnessArgs) --symbol-patterns wasm-symbol-patterns.txt <_XHarnessArgs Condition="'$(_UseWasmSymbolicator)' == 'true'" >$(_XHarnessArgs) --symbolicator WasmSymbolicator.dll,Microsoft.WebAssembly.Internal.SymbolicatorWrapperForXHarness - <_XHarnessArgs Condition="'$(_WasmBrowserPathForTests)' != ''" >$(_XHarnessArgs) "--browser-path=$(_WasmBrowserPathForTests)" + <_XHarnessArgs Condition="'$(_WasmChromePathForTests)' != ''" >$(_XHarnessArgs) "--browser-path=$(_WasmChromePathForTests)" + <_XHarnessArgs Condition="'$(_WasmFirefoxPathForTests)' != ''" >$(_XHarnessArgs) "--browser-path=$(_WasmFirefoxPathForTests)" <_XHarnessArgs Condition="'$(WasmXHarnessTestsTimeout)' != ''" >$(_XHarnessArgs) "--timeout=$(WasmXHarnessTestsTimeout)" <_XHarnessArgs Condition="'$(WasmXHarnessVerbosity)' != ''" >$(_XHarnessArgs) --verbosity=$(WasmXHarnessVerbosity) <_XHarnessArgs Condition="'$(WasmXHarnessArgsCli)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgsCli) @@ -252,6 +259,7 @@ + @@ -259,6 +267,7 @@ + diff --git a/eng/testing/tests.targets b/eng/testing/tests.targets index d2a13edf8555cc..4ffbd60c365dc0 100644 --- a/eng/testing/tests.targets +++ b/eng/testing/tests.targets @@ -9,11 +9,9 @@ AppleHelixRunnerTemplate.sh AppleRunnerTemplate.sh AndroidRunnerTemplate.sh - WasiRunnerAOTTemplate.sh - WasiRunnerTemplate.sh + WasiRunnerTemplate.sh WasiRunnerTemplate.cmd - WasmRunnerAOTTemplate.sh - WasmRunnerTemplate.sh + WasmRunnerTemplate.sh WasmRunnerTemplate.cmd BionicRunnerTemplate.sh BionicRunnerTemplate.cmd diff --git a/eng/testing/wasm-provisioning.targets b/eng/testing/wasm-provisioning.targets index 4852fd5ecc2ec7..47076a23770dd3 100644 --- a/eng/testing/wasm-provisioning.targets +++ b/eng/testing/wasm-provisioning.targets @@ -11,21 +11,30 @@ false + false false - $(ArtifactsBinDir)firefox\ - $([MSBuild]::NormalizePath($(FirefoxDir), '.install-firefox-$(FirefoxRevision).stamp')) <_BrowserStampDir>$(ArtifactsBinDir)\ Build - + - 108.0.1 - https://ftp.mozilla.org/pub/firefox/releases/$(FirefoxRevision)/linux-x86_64/en-US/firefox-$(FirefoxRevision).tar.bz2 + https://ftp.mozilla.org/pub/firefox/releases/$(linux_FirefoxRevision)/linux-x86_64/en-US/firefox-$(linux_FirefoxRevision).tar.bz2 + https://github.com/mozilla/geckodriver/releases/download/v$(linux_GeckoDriverRevision)/geckodriver-v$(linux_GeckoDriverRevision)-linux64.tar.gz firefox + geckodriver + $(ArtifactsBinDir)firefox\ + firefox + geckodriver + $(ArtifactsBinDir)geckodriver\ + $([MSBuild]::NormalizePath($(FirefoxDir), '.install-firefox-$(linux_FirefoxRevision).stamp')) + $([MSBuild]::NormalizePath($(GeckoDriverDir), '.install-geckodriver-$(linux_GeckoDriverRevision).stamp')) + + $([MSBuild]::NormalizePath($(FirefoxDir), $(FirefoxDirName), $(FirefoxBinaryName))) + $([MSBuild]::NormalizePath($(GeckoDriverDir), $(GeckoDriverDirName), $(GeckoDriverBinaryName))) @@ -84,12 +93,6 @@ $([MSBuild]::NormalizePath($(V8Dir), $(V8BinaryName))) - - 108.0.1 - https://ftp.mozilla.org/pub/firefox/releases/$(FirefoxRevision)/linux-x86_64/en-US/firefox-$(FirefoxRevision).tar.bz2 - firefox - - @@ -97,7 +100,7 @@ + Text="No %24(ChromeVersion) set. This can be set in eng/testing/BrowserVersions.props" /> @@ -125,7 +128,7 @@ + Text="No %24(ChromeVersion) set. This can be set in eng/testing/BrowserVersions.props" /> @@ -188,6 +191,9 @@ export __SCRIPT_DIR=%24( cd -- "%24( dirname -- "%24{BASH_SOURCE[0]}" )" &> + + @@ -205,4 +211,36 @@ export __SCRIPT_DIR=%24( cd -- "%24( dirname -- "%24{BASH_SOURCE[0]}" )" &> + + + + <_StampFile Include="$(_BrowserStampDir).install-geckodriver*.stamp" /> + + + + + + + + + + + + + + + + <_GeckoDriverBinaryPath>$([MSBuild]::NormalizePath($(GeckoDriverDir), $(GeckoDriverBinaryName))) + + + + + + + + diff --git a/global.json b/global.json index 83e43bb2a46732..4348e778e29b42 100644 --- a/global.json +++ b/global.json @@ -8,11 +8,11 @@ "dotnet": "9.0.100-preview.3.24204.13" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24212.4", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24212.4", - "Microsoft.DotNet.SharedFramework.Sdk": "9.0.0-beta.24212.4", + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24217.1", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24217.1", + "Microsoft.DotNet.SharedFramework.Sdk": "9.0.0-beta.24217.1", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.4.0", - "Microsoft.NET.Sdk.IL": "9.0.0-preview.4.24201.1" + "Microsoft.NET.Sdk.IL": "9.0.0-preview.4.24215.1" } } diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 9ef1024c449da8..fdd395fe520a01 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -39,6 +39,7 @@ $(TargetArchitecture) arm + AnyCPU true false @@ -59,28 +60,21 @@ - x64 - false $(DefineConstants);TARGET_AMD64 - x86 $(DefineConstants);TARGET_X86 - arm $(DefineConstants);TARGET_ARM - AnyCPU $(DefineConstants);TARGET_ARM64 - AnyCPU $(DefineConstants);TARGET_LOONGARCH64 - AnyCPU $(DefineConstants);TARGET_RISCV64 diff --git a/src/coreclr/debug/daccess/CMakeLists.txt b/src/coreclr/debug/daccess/CMakeLists.txt index 5332e957c9eca1..9ed71521d4283d 100644 --- a/src/coreclr/debug/daccess/CMakeLists.txt +++ b/src/coreclr/debug/daccess/CMakeLists.txt @@ -1,5 +1,7 @@ add_definitions(-DFEATURE_NO_HOST) +add_subdirectory(${CLR_SRC_NATIVE_DIR}/managed/cdacreader/cmake ${CLR_ARTIFACTS_OBJ_DIR}/cdacreader) + include_directories(BEFORE ${VM_DIR}) include_directories(BEFORE ${VM_DIR}/${ARCH_SOURCES_DIR}) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -12,6 +14,7 @@ if(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_HOST_UNIX) set(DACCESS_SOURCES + cdac.cpp dacdbiimpl.cpp dacdbiimpllocks.cpp dacdbiimplstackwalk.cpp @@ -40,6 +43,7 @@ convert_to_absolute_path(DACCESS_SOURCES ${DACCESS_SOURCES}) add_library_clr(daccess ${DACCESS_SOURCES}) set_target_properties(daccess PROPERTIES DAC_COMPONENT TRUE) target_precompile_headers(daccess PRIVATE [["stdafx.h"]]) +target_link_libraries(daccess PRIVATE cdacreader_api) add_dependencies(daccess eventing_headers) diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp new file mode 100644 index 00000000000000..78625bf67f2d72 --- /dev/null +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "cdac.h" +#include +#include +#include "dbgutil.h" +#include + +#define CDAC_LIB_NAME MAKEDLLNAME_W(W("cdacreader")) + +namespace +{ + bool TryLoadCDACLibrary(HMODULE *phCDAC) + { + // Load cdacreader from next to DAC binary + PathString path; + if (FAILED(GetClrModuleDirectory(path))) + return false; + + path.Append(CDAC_LIB_NAME); + *phCDAC = CLRLoadLibrary(path.GetUnicode()); + if (*phCDAC == NULL) + return false; + + return true; + } + + int ReadFromTargetCallback(uint64_t addr, uint8_t* dest, uint32_t count, void* context) + { + CDAC* cdac = reinterpret_cast(context); + return cdac->ReadFromTarget(addr, dest, count); + } +} + +CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target) +{ + HMODULE cdacLib; + if (!TryLoadCDACLibrary(&cdacLib)) + return CDAC::Invalid(); + + return CDAC{cdacLib, descriptorAddr, target}; +} + +CDAC::CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target) + : m_module(module) + , m_target{target} +{ + if (m_module == NULL) + { + m_cdac_handle = NULL; + return; + } + + decltype(&cdac_reader_init) init = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_init")); + decltype(&cdac_reader_get_sos_interface) getSosInterface = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_get_sos_interface")); + _ASSERTE(init != nullptr && getSosInterface != nullptr); + + init(descriptorAddr, &ReadFromTargetCallback, this, &m_cdac_handle); + getSosInterface(m_cdac_handle, &m_sos); +} + +CDAC::~CDAC() +{ + if (m_cdac_handle != NULL) + { + decltype(&cdac_reader_free) free = reinterpret_cast(::GetProcAddress(m_module, "cdac_reader_free")); + _ASSERTE(free != nullptr); + free(m_cdac_handle); + } + + if (m_module != NULL) + ::FreeLibrary(m_module); +} + +IUnknown* CDAC::SosInterface() +{ + return m_sos; +} + +int CDAC::ReadFromTarget(uint64_t addr, uint8_t* dest, uint32_t count) +{ + HRESULT hr = ReadFromDataTarget(m_target, addr, dest, count); + if (FAILED(hr)) + return hr; + + return S_OK; +} diff --git a/src/coreclr/debug/daccess/cdac.h b/src/coreclr/debug/daccess/cdac.h new file mode 100644 index 00000000000000..54418dc549f1f0 --- /dev/null +++ b/src/coreclr/debug/daccess/cdac.h @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef CDAC_H +#define CDAC_H + +class CDAC final +{ +public: // static + static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget); + + static CDAC Invalid() + { + return CDAC{nullptr, 0, nullptr}; + } + +public: + CDAC(const CDAC&) = delete; + CDAC& operator=(const CDAC&) = delete; + + CDAC(CDAC&& other) + : m_module{ other.m_module } + , m_cdac_handle{ other.m_cdac_handle } + , m_target{ other.m_target } + , m_sos{ other.m_sos.Extract() } + { + other.m_module = NULL; + other.m_cdac_handle = 0; + other.m_target = NULL; + other.m_sos = NULL; + } + + CDAC& operator=(CDAC&& other) + { + m_module = other.m_module; + m_cdac_handle = other.m_cdac_handle; + m_target = other.m_target; + m_sos = other.m_sos.Extract(); + + other.m_module = NULL; + other.m_cdac_handle = 0; + other.m_target = NULL; + other.m_sos = NULL; + + return *this; + } + + ~CDAC(); + + bool IsValid() const + { + return m_module != NULL && m_cdac_handle != 0; + } + + // This does not AddRef the returned interface + IUnknown* SosInterface(); + int ReadFromTarget(uint64_t addr, uint8_t* dest, uint32_t count); + +private: + CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target); + +private: + HMODULE m_module; + intptr_t m_cdac_handle; + ICorDebugDataTarget* m_target; + NonVMComHolder m_sos; +}; + +#endif // CDAC_H diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index ca8c76bf5b81f7..66ddb02f888eec 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -23,6 +23,8 @@ #include "dwreport.h" #include "primitives.h" #include "dbgutil.h" +#include "cdac.h" +#include #ifdef USE_DAC_TABLE_RVA #include @@ -3034,6 +3036,7 @@ class DacStreamManager //---------------------------------------------------------------------------- ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget/*=0*/) + : m_cdac{CDAC::Invalid()} { SUPPORTS_DAC_HOST_ONLY; // ctor does no marshalling - don't check with DacCop @@ -3123,7 +3126,6 @@ ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLe // see ClrDataAccess::VerifyDlls for details. m_fEnableDllVerificationAsserts = false; #endif - } ClrDataAccess::~ClrDataAccess(void) @@ -5491,6 +5493,28 @@ ClrDataAccess::Initialize(void) IfFailRet(GetDacGlobalValues()); IfFailRet(DacGetHostVtPtrs()); + CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC"); + if (enable.IsSet()) + { + DWORD val; + if (enable.TryAsInteger(10, val) && val == 1) + { + // TODO: [cdac] Get contract descriptor from exported symbol + uint64_t contractDescriptorAddr = 0; + //if (TryGetSymbol(m_pTarget, m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr)) + { + m_cdac = CDAC::Create(contractDescriptorAddr, m_pTarget); + if (m_cdac.IsValid()) + { + // Get SOS interfaces from the cDAC if available. + IUnknown* unk = m_cdac.SosInterface(); + (void)unk->QueryInterface(__uuidof(ISOSDacInterface), (void**)&m_cdacSos); + (void)unk->QueryInterface(__uuidof(ISOSDacInterface9), (void**)&m_cdacSos9); + } + } + } + } + // // DAC is now setup and ready to use // diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index e698eed4c1803b..081ff88da81572 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -794,6 +794,7 @@ class DacStreamManager; #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS +#include "cdac.h" //---------------------------------------------------------------------------- // @@ -1208,7 +1209,7 @@ class ClrDataAccess CLRDATA_ADDRESS *allocLimit); // ISOSDacInterface13 - virtual HRESULT STDMETHODCALLTYPE TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind kind, VISITHEAP pCallback); + virtual HRESULT STDMETHODCALLTYPE TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, LoaderHeapKind kind, VISITHEAP pCallback); virtual HRESULT STDMETHODCALLTYPE GetDomainLoaderAllocator(CLRDATA_ADDRESS domainAddress, CLRDATA_ADDRESS *pLoaderAllocator); virtual HRESULT STDMETHODCALLTYPE GetLoaderAllocatorHeapNames(int count, const char **ppNames, int *pNeeded); virtual HRESULT STDMETHODCALLTYPE GetLoaderAllocatorHeaps(CLRDATA_ADDRESS loaderAllocator, int count, CLRDATA_ADDRESS *pLoaderHeaps, LoaderHeapKind *pKinds, int *pNeeded); @@ -1221,13 +1222,15 @@ class ClrDataAccess virtual HRESULT STDMETHODCALLTYPE GetStaticBaseAddress(CLRDATA_ADDRESS methodTable, CLRDATA_ADDRESS *nonGCStaticsAddress, CLRDATA_ADDRESS *GCStaticsAddress); virtual HRESULT STDMETHODCALLTYPE GetThreadStaticBaseAddress(CLRDATA_ADDRESS methodTable, CLRDATA_ADDRESS thread, CLRDATA_ADDRESS *nonGCStaticsAddress, CLRDATA_ADDRESS *GCStaticsAddress); virtual HRESULT STDMETHODCALLTYPE GetMethodTableInitializationFlags(CLRDATA_ADDRESS methodTable, MethodTableInitializationFlags *initializationStatus); - + // // ClrDataAccess. // HRESULT Initialize(void); + HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); + BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX HRESULT GetWatsonBuckets(DWORD dwThreadId, GenericModeBlock * pGM); @@ -1414,6 +1417,10 @@ class ClrDataAccess ULONG32 m_instanceAge; bool m_debugMode; + CDAC m_cdac; + NonVMComHolder m_cdacSos; + NonVMComHolder m_cdacSos9; + #ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS protected: @@ -1964,7 +1971,7 @@ class DacMemoryEnumerator : public DefaultCOMImplGetThreadStoreData(threadStoreData); + if (FAILED(hr)) + { + hr = GetThreadStoreDataImpl(threadStoreData); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + DacpThreadStoreData threadStoreDataLocal; + HRESULT hrLocal = GetThreadStoreDataImpl(&threadStoreDataLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(threadStoreData->threadCount == threadStoreDataLocal.threadCount); + _ASSERTE(threadStoreData->unstartedThreadCount == threadStoreDataLocal.unstartedThreadCount); + _ASSERTE(threadStoreData->backgroundThreadCount == threadStoreDataLocal.backgroundThreadCount); + _ASSERTE(threadStoreData->pendingThreadCount == threadStoreDataLocal.pendingThreadCount); + _ASSERTE(threadStoreData->deadThreadCount == threadStoreDataLocal.deadThreadCount); + _ASSERTE(threadStoreData->fHostConfig == threadStoreDataLocal.fHostConfig); + _ASSERTE(threadStoreData->firstThread == threadStoreDataLocal.firstThread); + _ASSERTE(threadStoreData->finalizerThread == threadStoreDataLocal.finalizerThread); + _ASSERTE(threadStoreData->gcThread == threadStoreDataLocal.gcThread); + } +#endif } else { - // initialize the fields of our local structure - threadStoreData->threadCount = threadStore->m_ThreadCount; - threadStoreData->unstartedThreadCount = threadStore->m_UnstartedThreadCount; - threadStoreData->backgroundThreadCount = threadStore->m_BackgroundThreadCount; - threadStoreData->pendingThreadCount = threadStore->m_PendingThreadCount; - threadStoreData->deadThreadCount = threadStore->m_DeadThreadCount; - threadStoreData->fHostConfig = FALSE; - - // identify the "important" threads - threadStoreData->firstThread = HOST_CDADDR(threadStore->m_ThreadList.GetHead()); - threadStoreData->finalizerThread = HOST_CDADDR(g_pFinalizerThread); - threadStoreData->gcThread = HOST_CDADDR(g_pSuspensionThread); + hr = GetThreadStoreDataImpl(threadStoreData); } SOSDacLeave(); return hr; } +HRESULT ClrDataAccess::GetThreadStoreDataImpl(struct DacpThreadStoreData *threadStoreData) +{ + ThreadStore* threadStore = ThreadStore::s_pThreadStore; + if (!threadStore) + return E_UNEXPECTED; + + // initialize the fields of our local structure + threadStoreData->threadCount = threadStore->m_ThreadCount; + threadStoreData->unstartedThreadCount = threadStore->m_UnstartedThreadCount; + threadStoreData->backgroundThreadCount = threadStore->m_BackgroundThreadCount; + threadStoreData->pendingThreadCount = threadStore->m_PendingThreadCount; + threadStoreData->deadThreadCount = threadStore->m_DeadThreadCount; + threadStoreData->fHostConfig = FALSE; + + // identify the "important" threads + threadStoreData->firstThread = HOST_CDADDR(threadStore->m_ThreadList.GetHead()); + threadStoreData->finalizerThread = HOST_CDADDR(g_pFinalizerThread); + threadStoreData->gcThread = HOST_CDADDR(g_pSuspensionThread); + + return S_OK; +} + HRESULT ClrDataAccess::GetStressLogAddress(CLRDATA_ADDRESS *stressLog) { @@ -4951,7 +4986,15 @@ HRESULT ClrDataAccess::GetBreakingChangeVersion(int* pVersion) if (pVersion == nullptr) return E_INVALIDARG; - *pVersion = SOS_BREAKING_CHANGE_VERSION; + if (m_cdacSos9 != nullptr && SUCCEEDED(m_cdacSos9->GetBreakingChangeVersion(pVersion))) + { + _ASSERTE(*pVersion == SOS_BREAKING_CHANGE_VERSION); + } + else + { + *pVersion = SOS_BREAKING_CHANGE_VERSION; + } + return S_OK; } @@ -5406,10 +5449,10 @@ HRESULT STDMETHODCALLTYPE ClrDataAccess::GetStaticBaseAddress(CLRDATA_ADDRESS me { if (!nonGCStaticsAddress && !GCStaticsAddress) return E_POINTER; - + if (!methodTable) return E_INVALIDARG; - + SOSDacEnter(); PTR_MethodTable mTable = PTR_MethodTable(TO_TADDR(methodTable)); @@ -5440,13 +5483,13 @@ HRESULT STDMETHODCALLTYPE ClrDataAccess::GetThreadStaticBaseAddress(CLRDATA_ADDR { if (!nonGCStaticsAddress && !GCStaticsAddress) return E_POINTER; - + if (!methodTable) return E_INVALIDARG; if (!threadPtr) return E_INVALIDARG; - + SOSDacEnter(); PTR_MethodTable mTable = PTR_MethodTable(TO_TADDR(methodTable)); diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index b95946881f10ed..5b4faf05edb44f 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -113,7 +113,6 @@ BEGIN COR_E_FILELOAD "Unable to load file '%1'." COR_E_ASSEMBLYEXPECTED "The module '%1' was expected to contain an assembly manifest." FUSION_E_REF_DEF_MISMATCH "The located assembly's manifest definition with name '%1' does not match the assembly reference." - FUSION_E_PRIVATE_ASM_DISALLOWED "Assembly '%1' is required to be strongly named." FUSION_E_INVALID_NAME "The given assembly name, '%1', was invalid." FUSION_E_APP_DOMAIN_LOCKED "The requested assembly version conflicts with what is already bound in the app domain or specified in the manifest." IDS_EE_HASH_VAL_FAILED "Hash validation failed for file or assembly '%1'." diff --git a/src/coreclr/ilasm/writer.cpp b/src/coreclr/ilasm/writer.cpp index 6405461a9d396f..20d70c57204f4f 100644 --- a/src/coreclr/ilasm/writer.cpp +++ b/src/coreclr/ilasm/writer.cpp @@ -34,6 +34,14 @@ HRESULT Assembler::InitMetaData() if (FAILED(hr)) goto exit; + if(m_wzMetadataVersion) + { + VARIANT optionValue; + V_VT(&optionValue) = VT_BSTR; + V_BSTR(&optionValue) = m_wzMetadataVersion; // IMetaDataDispenserEx does not require proper BSTR + hr = m_pDisp->SetOption(MetaDataRuntimeVersion, &optionValue); + } + hr = m_pDisp->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataEmit3, (IUnknown **)&m_pEmitter); if (FAILED(hr)) diff --git a/src/coreclr/inc/corerror.xml b/src/coreclr/inc/corerror.xml index 1d4129305fa680..68c9a1aa5dfb3c 100644 --- a/src/coreclr/inc/corerror.xml +++ b/src/coreclr/inc/corerror.xml @@ -172,12 +172,6 @@ The located assembly's manifest definition does not match the assembly reference. - - FUSION_E_PRIVATE_ASM_DISALLOWED - "A strongly-named assembly is required." - A strongly-named assembly is required. - - FUSION_E_INVALID_NAME "The given assembly name was invalid." diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 35f7d2f0bdf862..ff82759f6aab64 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2709,6 +2709,11 @@ class ICorStaticInfo CORINFO_CLASS_HANDLE cls ) = 0; + // Returns whether a class handle represents a Nullable type, if that can be statically determined. + virtual TypeCompareState isNullableType( + CORINFO_CLASS_HANDLE cls + ) = 0; + // Returns TypeCompareState::Must if cls is known to be an enum. // For enums with known exact type returns the underlying // type in underlyingType when the provided pointer is diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 2348162d948545..98b47dced2beb0 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -344,6 +344,9 @@ bool isMoreSpecificType( bool isExactType( CORINFO_CLASS_HANDLE cls) override; +TypeCompareState isNullableType( + CORINFO_CLASS_HANDLE cls) override; + TypeCompareState isEnum( CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 0e9f6e2940bcb3..743abdaff86aea 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* 3c216494-65f8-49e2-b69a-7f272193bcc6 */ - 0x3c216494, - 0x65f8, - 0x49e2, - {0xb6, 0x9a, 0x7f, 0x27, 0x21, 0x93, 0xbc, 0xc6} +constexpr GUID JITEEVersionIdentifier = { /* 8f046bcb-ca5f-4692-9277-898b71cb7938 */ + 0x8f046bcb, + 0xca5f, + 0x4692, + {0x92, 0x77, 0x89, 0x8b, 0x71, 0xcb, 0x79, 0x38} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index 30c499518e007c..2e2573a911993d 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -85,6 +85,7 @@ DEF_CLR_API(compareTypesForCast) DEF_CLR_API(compareTypesForEquality) DEF_CLR_API(isMoreSpecificType) DEF_CLR_API(isExactType) +DEF_CLR_API(isNullableType) DEF_CLR_API(isEnum) DEF_CLR_API(getParentType) DEF_CLR_API(getChildType) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 77af720739ecee..85db2ec5efffa5 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -804,6 +804,15 @@ bool WrapICorJitInfo::isExactType( return temp; } +TypeCompareState WrapICorJitInfo::isNullableType( + CORINFO_CLASS_HANDLE cls) +{ + API_ENTER(isNullableType); + TypeCompareState temp = wrapHnd->isNullableType(cls); + API_LEAVE(isNullableType); + return temp; +} + TypeCompareState WrapICorJitInfo::isEnum( CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) diff --git a/src/coreclr/jit/abi.cpp b/src/coreclr/jit/abi.cpp index e80533fc783a06..fd899b899546b1 100644 --- a/src/coreclr/jit/abi.cpp +++ b/src/coreclr/jit/abi.cpp @@ -281,10 +281,7 @@ bool ABIPassingInformation::IsSplitAcrossRegistersAndStack() const // ABIPassingInformation ABIPassingInformation::FromSegment(Compiler* comp, const ABIPassingSegment& segment) { - ABIPassingInformation info; - info.NumSegments = 1; - info.Segments = new (comp, CMK_ABI) ABIPassingSegment(segment); - return info; + return {1, new (comp, CMK_ABI) ABIPassingSegment(segment)}; } #ifdef DEBUG diff --git a/src/coreclr/jit/abi.h b/src/coreclr/jit/abi.h index 1e51a14d9c09a5..97232bf911c4a3 100644 --- a/src/coreclr/jit/abi.h +++ b/src/coreclr/jit/abi.h @@ -48,9 +48,16 @@ struct ABIPassingInformation // multiple register segments and a struct segment. // - On Windows x64, all parameters always fit into one stack slot or // register, and thus always have NumSegments == 1 + // - On RISC-V, structs can be split out over 2 segments, each can be an integer/float register or a stack slot unsigned NumSegments = 0; ABIPassingSegment* Segments = nullptr; + ABIPassingInformation(unsigned numSegments = 0, ABIPassingSegment* segments = nullptr) + : NumSegments(numSegments) + , Segments(segments) + { + } + bool HasAnyRegisterSegment() const; bool HasAnyStackSegment() const; bool HasExactlyOneRegisterSegment() const; @@ -77,7 +84,7 @@ class RegisterQueue { } - unsigned Count() + unsigned Count() const { return m_numRegs - m_index; } @@ -179,6 +186,22 @@ class Arm32Classifier WellKnownArg wellKnownParam); }; +class RiscV64Classifier +{ + const ClassifierInfo& m_info; + RegisterQueue m_intRegs; + RegisterQueue m_floatRegs; + unsigned m_stackArgSize = 0; + +public: + RiscV64Classifier(const ClassifierInfo& info); + + ABIPassingInformation Classify(Compiler* comp, + var_types type, + ClassLayout* structLayout, + WellKnownArg wellKnownParam); +}; + #if defined(TARGET_X86) typedef X86Classifier PlatformClassifier; #elif defined(WINDOWS_AMD64_ABI) @@ -189,6 +212,8 @@ typedef SysVX64Classifier PlatformClassifier; typedef Arm64Classifier PlatformClassifier; #elif defined(TARGET_ARM) typedef Arm32Classifier PlatformClassifier; +#elif defined(TARGET_RISCV64) +typedef RiscV64Classifier PlatformClassifier; #endif #ifdef SWIFT_SUPPORT diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index d55e21b078e9e6..b3d52ce9773e48 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -769,9 +769,9 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse } else if (curAssertion->op1.kind == O1K_ARR_BND) { - printf("[idx:"); + printf("[idx: " FMT_VN, curAssertion->op1.bnd.vnIdx); vnStore->vnDump(this, curAssertion->op1.bnd.vnIdx); - printf(";len:"); + printf("; len: " FMT_VN, curAssertion->op1.bnd.vnLen); vnStore->vnDump(this, curAssertion->op1.bnd.vnLen); printf("]"); } diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index 500b5274b6f41c..168f29cca084dd 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -587,10 +587,6 @@ struct FlowEdge // The destination of the control flow BasicBlock* m_destBlock; - // Edge weights - weight_t m_edgeWeightMin; - weight_t m_edgeWeightMax; - // Likelihood that m_sourceBlock transfers control along this edge. // Values in range [0..1] weight_t m_likelihood; @@ -606,8 +602,6 @@ struct FlowEdge : m_nextPredEdge(rest) , m_sourceBlock(sourceBlock) , m_destBlock(destBlock) - , m_edgeWeightMin(0) - , m_edgeWeightMax(0) , m_likelihood(0) , m_dupCount(0) #ifdef DEBUG @@ -655,24 +649,6 @@ struct FlowEdge m_destBlock = newBlock; } - weight_t edgeWeightMin() const - { - return m_edgeWeightMin; - } - - weight_t edgeWeightMax() const - { - return m_edgeWeightMax; - } - - // These two methods are used to set new values for edge weights. - // They return false if the newWeight is not between the current [min..max] - // when slop is non-zero we allow for the case where our weights might be off by 'slop' - // - bool setEdgeWeightMinChecked(weight_t newWeight, BasicBlock* bDst, weight_t slop, bool* wbUsedSlop); - bool setEdgeWeightMaxChecked(weight_t newWeight, BasicBlock* bDst, weight_t slop, bool* wbUsedSlop); - void setEdgeWeights(weight_t newMinWeight, weight_t newMaxWeight, BasicBlock* bDst); - weight_t getLikelihood() const { assert(m_likelihoodSet); diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index d4e8d58db741b7..8695545cc934a3 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2655,6 +2655,48 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) genProduceReg(tree); return; } + else if (op2->OperIs(GT_ROR) && op2->isContained()) + { + assert(varTypeIsIntegral(tree)); + + GenTree* a = op1; + GenTree* b = op2->gtGetOp1(); + GenTree* c = op2->gtGetOp2(); + + // The rotate amount needs to be contained as well + assert(c->isContained() && c->IsCnsIntOrI()); + + instruction ins = genGetInsForOper(tree->OperGet(), targetType); + insOpts opt = INS_OPTS_NONE; + + if ((tree->gtFlags & GTF_SET_FLAGS) != 0) + { + // A subset of operations can still set flags + + switch (oper) + { + case GT_AND: + { + ins = INS_ands; + break; + } + + default: + { + noway_assert(!"Unexpected BinaryOp with GTF_SET_FLAGS set"); + } + } + } + + assert(op2->OperIs(GT_ROR)); + opt = INS_OPTS_ROR; + + emit->emitIns_R_R_R_I(ins, emitActualTypeSize(tree), targetReg, a->GetRegNum(), b->GetRegNum(), + c->AsIntConCommon()->IconValue(), opt); + + genProduceReg(tree); + return; + } else if (op2->OperIs(GT_CAST) && op2->isContained()) { assert(varTypeIsIntegral(tree)); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 9660b0c98be9d7..b982ae640e5556 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1831,8 +1831,7 @@ void CodeGen::genGenerateMachineCode() if (compiler->fgHaveProfileWeights()) { - printf("; with %s: edge weights are %s, and fgCalledCount is " FMT_WT "\n", - compiler->compGetPgoSourceName(), compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", + printf("; with %s: fgCalledCount is " FMT_WT "\n", compiler->compGetPgoSourceName(), compiler->fgCalledCount); } diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 0b373a94fd3f80..145c074b8fc681 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -1657,7 +1657,7 @@ void CodeGen::genConsumeRegs(GenTree* tree) } #endif // FEATURE_HW_INTRINSICS #endif // TARGET_XARCH - else if (tree->OperIs(GT_BITCAST, GT_NEG, GT_CAST, GT_LSH, GT_RSH, GT_RSZ, GT_BSWAP, GT_BSWAP16)) + else if (tree->OperIs(GT_BITCAST, GT_NEG, GT_CAST, GT_LSH, GT_RSH, GT_RSZ, GT_ROR, GT_BSWAP, GT_BSWAP16)) { genConsumeRegs(tree->gtGetOp1()); } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index d534caf2d3adb2..a32867660a01c3 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4910,9 +4910,9 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // DoPhase(this, PHASE_GS_COOKIE, &Compiler::gsPhase); - // Compute the block and edge weights + // Compute the block weights // - DoPhase(this, PHASE_COMPUTE_EDGE_WEIGHTS, &Compiler::fgComputeBlockAndEdgeWeights); + DoPhase(this, PHASE_COMPUTE_BLOCK_WEIGHTS, &Compiler::fgComputeBlockWeights); if (UsesFunclets()) { @@ -5148,10 +5148,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // update the flowgraph if we modified it during the optimization phase // DoPhase(this, PHASE_OPT_UPDATE_FLOW_GRAPH, &Compiler::fgUpdateFlowGraphPhase); - - // Recompute the edge weight if we have modified the flow graph - // - DoPhase(this, PHASE_COMPUTE_EDGE_WEIGHTS2, &Compiler::fgComputeEdgeWeights); } // Iterate if requested, resetting annotations first. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 8f15dbe77446ec..819c20629de134 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1593,12 +1593,11 @@ enum API_ICorJitInfo_Names enum class ProfileChecks : unsigned int { CHECK_NONE = 0, - CHECK_CLASSIC = 1 << 0, // check "classic" jit weights - CHECK_HASLIKELIHOOD = 1 << 1, // check all FlowEdges for hasLikelihood - CHECK_LIKELIHOODSUM = 1 << 2, // check block successor likelihoods sum to 1 - CHECK_LIKELY = 1 << 3, // fully check likelihood based weights - RAISE_ASSERT = 1 << 4, // assert on check failure - CHECK_ALL_BLOCKS = 1 << 5, // check blocks even if bbHasProfileWeight is false + CHECK_HASLIKELIHOOD = 1 << 0, // check all FlowEdges for hasLikelihood + CHECK_LIKELIHOODSUM = 1 << 1, // check block succesor likelihoods sum to 1 + CHECK_LIKELY = 1 << 2, // fully check likelihood based weights + RAISE_ASSERT = 1 << 3, // assert on check failure + CHECK_ALL_BLOCKS = 1 << 4, // check blocks even if bbHasProfileWeight is false }; inline constexpr ProfileChecks operator ~(ProfileChecks a) @@ -5207,14 +5206,10 @@ class Compiler // - Rationalization links all nodes into linear form which is kept until // the end of compilation. The first and last nodes are stored in the block. NodeThreading fgNodeThreading; - bool fgCanRelocateEHRegions; // true if we are allowed to relocate the EH regions - bool fgEdgeWeightsComputed; // true after we have called fgComputeEdgeWeights - bool fgHaveValidEdgeWeights; // true if we were successful in computing all of the edge weights - bool fgSlopUsedInEdgeWeights; // true if their was some slop used when computing the edge weights - bool fgRangeUsedInEdgeWeights; // true if some of the edgeWeight are expressed in Min..Max form - weight_t fgCalledCount; // count of the number of times this method was called - // This is derived from the profile data - // or is BB_UNITY_WEIGHT when we don't have profile data + bool fgCanRelocateEHRegions; // true if we are allowed to relocate the EH regions + weight_t fgCalledCount; // count of the number of times this method was called + // This is derived from the profile data + // or is BB_UNITY_WEIGHT when we don't have profile data bool fgFuncletsCreated; // true if the funclet creation phase has been run @@ -5693,6 +5688,9 @@ class Compiler // Adds the exception sets for the current tree node which is performing a division or modulus operation void fgValueNumberAddExceptionSetForDivision(GenTree* tree); + // Compute exceptions for a division operation + ValueNumPair fgValueNumberDivisionExceptions(genTreeOps oper, GenTree* dividend, GenTree* divisor); + // Adds the exception set for the current tree node which is performing a overflow checking operation void fgValueNumberAddExceptionSetForOverflow(GenTree* tree); @@ -6047,10 +6045,9 @@ class Compiler #ifdef DEBUG void fgPrintEdgeWeights(); #endif - PhaseStatus fgComputeBlockAndEdgeWeights(); + PhaseStatus fgComputeBlockWeights(); bool fgComputeMissingBlockWeights(weight_t* returnWeight); bool fgComputeCalledCount(weight_t returnWeight); - PhaseStatus fgComputeEdgeWeights(); bool fgReorderBlocks(bool useProfile); @@ -6868,7 +6865,6 @@ class Compiler BasicBlock* optTryAdvanceLoopCompactionInsertionPoint(FlowGraphNaturalLoop* loop, BasicBlock* insertionPoint, BasicBlock* top, BasicBlock* bottom); bool optCreatePreheader(FlowGraphNaturalLoop* loop); void optSetWeightForPreheaderOrExit(FlowGraphNaturalLoop* loop, BasicBlock* block); - weight_t optEstimateEdgeLikelihood(BasicBlock* from, BasicBlock* to, bool* fromProfile); bool optCanonicalizeExits(FlowGraphNaturalLoop* loop); bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit); diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 950313286d26fe..6f2783f889721c 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -55,7 +55,7 @@ CompPhaseNameMacro(PHASE_MORPH_GLOBAL, "Morph - Global", CompPhaseNameMacro(PHASE_POST_MORPH, "Post-Morph", false, -1, false) CompPhaseNameMacro(PHASE_MORPH_END, "Morph - Finish", false, -1, true) CompPhaseNameMacro(PHASE_GS_COOKIE, "GS Cookie", false, -1, false) -CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS, "Compute edge weights (1, false)",false, -1, false) +CompPhaseNameMacro(PHASE_COMPUTE_BLOCK_WEIGHTS, "Compute block weights", false, -1, false) CompPhaseNameMacro(PHASE_CREATE_FUNCLETS, "Create EH funclets", false, -1, false) CompPhaseNameMacro(PHASE_HEAD_TAIL_MERGE, "Head and tail merge", false, -1, false) CompPhaseNameMacro(PHASE_MERGE_THROWS, "Merge throw blocks", false, -1, false) @@ -95,7 +95,6 @@ CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN, "Assertion prop", CompPhaseNameMacro(PHASE_IF_CONVERSION, "If conversion", false, -1, false) CompPhaseNameMacro(PHASE_VN_BASED_DEAD_STORE_REMOVAL,"VN-based dead store removal", false, -1, false) CompPhaseNameMacro(PHASE_OPT_UPDATE_FLOW_GRAPH, "Update flow graph opt pass", false, -1, false) -CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS2, "Compute edge weights (2, false)",false, -1, false) CompPhaseNameMacro(PHASE_STRESS_SPLIT_TREE, "Stress gtSplitTree", false, -1, false) CompPhaseNameMacro(PHASE_EXPAND_RTLOOKUPS, "Expand runtime lookups", false, -1, true) CompPhaseNameMacro(PHASE_EXPAND_STATIC_INIT, "Expand static init", false, -1, true) diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 8cbecbee7ca2c1..1638dfec507324 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -23,12 +23,8 @@ void Compiler::fgInit() /* We haven't yet computed the bbPreds lists */ fgPredsComputed = false; - /* We haven't yet computed the edge weight */ - fgEdgeWeightsComputed = false; - fgHaveValidEdgeWeights = false; - fgSlopUsedInEdgeWeights = false; - fgRangeUsedInEdgeWeights = true; - fgCalledCount = BB_ZERO_WEIGHT; + /* We haven't yet computed block weights */ + fgCalledCount = BB_ZERO_WEIGHT; /* Initialize the basic block list */ @@ -5446,6 +5442,7 @@ BasicBlock* Compiler::fgConnectFallThrough(BasicBlock* bSrc, BasicBlock* bDst) // Add a new block after bSrc which jumps to 'bDst' jmpBlk = fgNewBBafter(BBJ_ALWAYS, bSrc, true); FlowEdge* const oldEdge = bSrc->GetFalseEdge(); + // Access the likelihood of oldEdge before // it gets reset by SetTargetEdge below. // @@ -5457,29 +5454,9 @@ BasicBlock* Compiler::fgConnectFallThrough(BasicBlock* bSrc, BasicBlock* bDst) // When adding a new jmpBlk we will set the bbWeight and bbFlags // - if (fgHaveValidEdgeWeights && fgHaveProfileWeights()) + if (fgHaveProfileWeights()) { - jmpBlk->bbWeight = (newEdge->edgeWeightMin() + newEdge->edgeWeightMax()) / 2; - if (bSrc->bbWeight == BB_ZERO_WEIGHT) - { - jmpBlk->bbWeight = BB_ZERO_WEIGHT; - } - - if (jmpBlk->bbWeight == BB_ZERO_WEIGHT) - { - jmpBlk->SetFlags(BBF_RUN_RARELY); - } - - weight_t weightDiff = (newEdge->edgeWeightMax() - newEdge->edgeWeightMin()); - weight_t slop = BasicBlock::GetSlopFraction(bSrc, bDst); - // - // If the [min/max] values for our edge weight is within the slop factor - // then we will set the BBF_PROF_WEIGHT flag for the block - // - if (weightDiff <= slop) - { - jmpBlk->SetFlags(BBF_PROF_WEIGHT); - } + jmpBlk->setBBProfileWeight(newEdge->getLikelyWeight()); } else { diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 6311d189186bdd..c70573b5879a45 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -28,26 +28,17 @@ void Compiler::fgPrintEdgeWeights() printf(FMT_BB " ", bSrc->bbNum); - if (edge->edgeWeightMin() < BB_MAX_WEIGHT) + const weight_t weight = edge->getLikelyWeight(); + + if (weight < BB_MAX_WEIGHT) { - printf("(%f", edge->edgeWeightMin()); + printf("(%f)", weight); } else { - printf("(MAX"); - } - if (edge->edgeWeightMin() != edge->edgeWeightMax()) - { - if (edge->edgeWeightMax() < BB_MAX_WEIGHT) - { - printf("..%f", edge->edgeWeightMax()); - } - else - { - printf("..MAX"); - } + printf("(MAX)"); } - printf(")"); + if (edge->getNextPredEdge() != nullptr) { printf(", "); @@ -735,7 +726,6 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) JITDUMP("Writing out flow graph %s phase %s\n", (pos == PhasePosition::PrePhase) ? "before" : "after", PhaseNames[phase]); - bool validWeights = fgHaveValidEdgeWeights; double weightDivisor = (double)BasicBlock::getCalledCount(this); const char* escapedString; const char* regionString = "NONE"; @@ -791,14 +781,6 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) { fprintf(fgxFile, "\n hasLoops=\"true\""); } - if (validWeights) - { - fprintf(fgxFile, "\n validEdgeWeights=\"true\""); - if (!fgSlopUsedInEdgeWeights && !fgRangeUsedInEdgeWeights) - { - fprintf(fgxFile, "\n exactEdgeWeights=\"true\""); - } - } if (fgFirstColdBlock != nullptr) { fprintf(fgxFile, "\n firstColdBlock=\"%d\"", fgFirstColdBlock->bbNum); @@ -1081,11 +1063,8 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) fprintf(fgxFile, " ["); } - if (validWeights) - { - weight_t edgeWeight = (edge->edgeWeightMin() + edge->edgeWeightMax()) / 2; - fprintf(fgxFile, "%slabel=\"%7.2f\"", sep, (double)edgeWeight / weightDivisor); - } + const weight_t edgeWeight = edge->getLikelyWeight(); + fprintf(fgxFile, "%slabel=\"%7.2f\"", sep, (double)edgeWeight / weightDivisor); fprintf(fgxFile, "];\n"); } @@ -1106,32 +1085,22 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) fprintf(fgxFile, "\n switchDefault=\"true\""); } } - if (validWeights) - { - weight_t edgeWeight = (edge->edgeWeightMin() + edge->edgeWeightMax()) / 2; - fprintf(fgxFile, "\n weight="); - fprintfDouble(fgxFile, ((double)edgeWeight) / weightDivisor); - if (edge->edgeWeightMin() != edge->edgeWeightMax()) + const weight_t edgeWeight = edge->getLikelyWeight(); + fprintf(fgxFile, "\n weight="); + fprintfDouble(fgxFile, ((double)edgeWeight) / weightDivisor); + + if (edgeWeight > 0) + { + if (edgeWeight < bSource->bbWeight) { - fprintf(fgxFile, "\n minWeight="); - fprintfDouble(fgxFile, ((double)edge->edgeWeightMin()) / weightDivisor); - fprintf(fgxFile, "\n maxWeight="); - fprintfDouble(fgxFile, ((double)edge->edgeWeightMax()) / weightDivisor); + fprintf(fgxFile, "\n out="); + fprintfDouble(fgxFile, ((double)edgeWeight) / sourceWeightDivisor); } - - if (edgeWeight > 0) + if (edgeWeight < bTarget->bbWeight) { - if (edgeWeight < bSource->bbWeight) - { - fprintf(fgxFile, "\n out="); - fprintfDouble(fgxFile, ((double)edgeWeight) / sourceWeightDivisor); - } - if (edgeWeight < bTarget->bbWeight) - { - fprintf(fgxFile, "\n in="); - fprintfDouble(fgxFile, ((double)edgeWeight) / targetWeightDivisor); - } + fprintf(fgxFile, "\n in="); + fprintfDouble(fgxFile, ((double)edgeWeight) / targetWeightDivisor); } } } diff --git a/src/coreclr/jit/fgflow.cpp b/src/coreclr/jit/fgflow.cpp index f4f650f6b5d341..c1060e1373d606 100644 --- a/src/coreclr/jit/fgflow.cpp +++ b/src/coreclr/jit/fgflow.cpp @@ -191,42 +191,6 @@ FlowEdge* Compiler::fgAddRefPred(BasicBlock* block, BasicBlock* blockPred, FlowE // flow->setLikelihood(oldEdge->getLikelihood()); } - - if (fgHaveValidEdgeWeights) - { - // We are creating an edge from blockPred to block - // and we have already computed the edge weights, so - // we will try to setup this new edge with valid edge weights. - // - if (oldEdge != nullptr) - { - // If our caller has given us the old edge weights - // then we will use them. - // - flow->setEdgeWeights(oldEdge->edgeWeightMin(), oldEdge->edgeWeightMax(), block); - } - else - { - // Set the max edge weight to be the minimum of block's or blockPred's weight - // - weight_t newWeightMax = min(block->bbWeight, blockPred->bbWeight); - - // If we are inserting a conditional block the minimum weight is zero, - // otherwise it is the same as the edge's max weight. - if (blockPred->NumSucc() > 1) - { - flow->setEdgeWeights(BB_ZERO_WEIGHT, newWeightMax, block); - } - else - { - flow->setEdgeWeights(flow->edgeWeightMax(), newWeightMax, block); - } - } - } - else - { - flow->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, block); - } } // Pred list should (still) be ordered. diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 33ee13bf9b6895..c1b919d37830a2 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -1449,33 +1449,14 @@ bool Compiler::fgOptimizeBranchToEmptyUnconditional(BasicBlock* block, BasicBloc // // When we optimize a branch to branch we need to update the profile weight - // of bDest by subtracting out the block/edge weight of the path that is being optimized. + // of bDest by subtracting out the weight of the path that is being optimized. // - if (fgHaveValidEdgeWeights && bDest->hasProfileWeight()) + if (bDest->hasProfileWeight()) { - FlowEdge* edge1 = fgGetPredForBlock(bDest, block); - noway_assert(edge1 != nullptr); + FlowEdge* const edge = fgGetPredForBlock(bDest, block); + noway_assert(edge != nullptr); - weight_t edgeWeight; - - if (edge1->edgeWeightMin() != edge1->edgeWeightMax()) - { - // - // We only have an estimate for the edge weight - // - edgeWeight = (edge1->edgeWeightMin() + edge1->edgeWeightMax()) / 2; - // - // Clear the profile weight flag - // - bDest->RemoveFlags(BBF_PROF_WEIGHT); - } - else - { - // - // We only have the exact edge weight - // - edgeWeight = edge1->edgeWeightMin(); - } + const weight_t edgeWeight = edge->getLikelyWeight(); // // Update the bDest->bbWeight @@ -1489,36 +1470,6 @@ bool Compiler::fgOptimizeBranchToEmptyUnconditional(BasicBlock* block, BasicBloc bDest->bbWeight = BB_ZERO_WEIGHT; bDest->SetFlags(BBF_RUN_RARELY); // Set the RarelyRun flag } - - FlowEdge* edge2 = bDest->GetTargetEdge(); - - if (edge2 != nullptr) - { - // - // Update the edge2 min/max weights - // - weight_t newEdge2Min; - weight_t newEdge2Max; - - if (edge2->edgeWeightMin() > edge1->edgeWeightMin()) - { - newEdge2Min = edge2->edgeWeightMin() - edge1->edgeWeightMin(); - } - else - { - newEdge2Min = BB_ZERO_WEIGHT; - } - - if (edge2->edgeWeightMax() > edge1->edgeWeightMin()) - { - newEdge2Max = edge2->edgeWeightMax() - edge1->edgeWeightMin(); - } - else - { - newEdge2Max = BB_ZERO_WEIGHT; - } - edge2->setEdgeWeights(newEdge2Min, newEdge2Max, bDest); - } } // Optimize the JUMP to empty unconditional JUMP to go to the new target @@ -1709,7 +1660,7 @@ bool Compiler::fgOptimizeEmptyBlock(BasicBlock* block) break; } - // When using profile weights, fgComputeEdgeWeights expects the first non-internal block to have profile + // When using profile weights, fgComputeCalledCount expects the first non-internal block to have profile // weight. // Make sure we don't break that invariant. if (fgIsUsingProfileWeights() && block->hasProfileWeight() && !block->HasFlag(BBF_INTERNAL)) @@ -1805,29 +1756,24 @@ bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block) { // // When we optimize a branch to branch we need to update the profile weight - // of bDest by subtracting out the block/edge weight of the path that is being optimized. + // of bDest by subtracting out the block weight of the path that is being optimized. // + FlowEdge* const oldEdge = *jmpTab; + if (fgIsUsingProfileWeights() && bDest->hasProfileWeight()) { - if (fgHaveValidEdgeWeights) + weight_t const branchThroughWeight = oldEdge->getLikelyWeight(); + if (bDest->bbWeight > branchThroughWeight) { - FlowEdge* edge = *jmpTab; - weight_t branchThroughWeight = edge->edgeWeightMin(); - - if (bDest->bbWeight > branchThroughWeight) - { - bDest->bbWeight -= branchThroughWeight; - } - else - { - bDest->bbWeight = BB_ZERO_WEIGHT; - bDest->SetFlags(BBF_RUN_RARELY); - } + bDest->bbWeight -= branchThroughWeight; + } + else + { + bDest->bbSetRunRarely(); } } // Update the switch jump table - FlowEdge* const oldEdge = *jmpTab; fgRemoveRefPred(oldEdge); FlowEdge* const newEdge = fgAddRefPred(bNewDest, block, oldEdge); *jmpTab = newEdge; @@ -3041,47 +2987,9 @@ bool Compiler::fgOptimizeSwitchJumps() newBlock->setBBProfileWeight(blockToNewBlockWeight); - blockToTargetEdge->setEdgeWeights(blockToTargetWeight, blockToTargetWeight, dominantTarget); blockToTargetEdge->setLikelihood(fraction); - blockToNewBlockEdge->setEdgeWeights(blockToNewBlockWeight, blockToNewBlockWeight, block); blockToNewBlockEdge->setLikelihood(max(0.0, 1.0 - fraction)); - // There may be other switch cases that lead to this same block, but there's just - // one edge in the flowgraph. So we need to subtract off the profile data that now flows - // along the peeled edge. - // - for (FlowEdge* pred = dominantTarget->bbPreds; pred != nullptr; pred = pred->getNextPredEdge()) - { - if (pred->getSourceBlock() == newBlock) - { - if (pred->getDupCount() == 1) - { - // The only switch case leading to the dominant target was the one we peeled. - // So the edge from the switch now has zero weight. - // - pred->setEdgeWeights(BB_ZERO_WEIGHT, BB_ZERO_WEIGHT, dominantTarget); - } - else - { - // Other switch cases also lead to the dominant target. - // Subtract off the weight we transferred to the peel. - // - weight_t newMinWeight = pred->edgeWeightMin() - blockToTargetWeight; - weight_t newMaxWeight = pred->edgeWeightMax() - blockToTargetWeight; - - if (newMinWeight < BB_ZERO_WEIGHT) - { - newMinWeight = BB_ZERO_WEIGHT; - } - if (newMaxWeight < BB_ZERO_WEIGHT) - { - newMaxWeight = BB_ZERO_WEIGHT; - } - pred->setEdgeWeights(newMinWeight, newMaxWeight, dominantTarget); - } - } - } - // For now we leave the switch as is, since there's no way // to indicate that one of the cases is now unreachable. // @@ -3633,43 +3541,19 @@ bool Compiler::fgReorderBlocks(bool useProfile) // bool moveDestUp = true; - if (fgHaveValidEdgeWeights) - { - // - // The edge bPrev -> bDest must have a higher minimum weight - // than every other edge into bDest - // - FlowEdge* edgeFromPrev = bPrev->GetTargetEdge(); - noway_assert(edgeFromPrev != nullptr); + // + // The edge bPrev -> bDest must have a higher weight + // than every other edge into bDest + // + weight_t const weightToBeat = bPrev->GetTargetEdge()->getLikelyWeight(); - // Examine all of the other edges into bDest - for (FlowEdge* const edge : bDest->PredEdges()) - { - if (edge != edgeFromPrev) - { - if (edge->edgeWeightMax() >= edgeFromPrev->edgeWeightMin()) - { - moveDestUp = false; - break; - } - } - } - } - else + // Examine all of the other edges into bDest + for (FlowEdge* const edge : bDest->PredEdges()) { - // - // The block bPrev must have a higher weight - // than every other block that goes into bDest - // - - // Examine all of the other edges into bDest - for (BasicBlock* const predBlock : bDest->PredBlocks()) + if (edge->getLikelyWeight() > weightToBeat) { - if ((predBlock != bPrev) && (predBlock->bbWeight >= bPrev->bbWeight)) - { - moveDestUp = false; - break; - } + moveDestUp = false; + break; } } @@ -3703,98 +3587,43 @@ bool Compiler::fgReorderBlocks(bool useProfile) { noway_assert(bPrev->KindIs(BBJ_COND)); // - // We will reverse branch if the taken-jump to bDest ratio (i.e. 'takenRatio') - // is more than 51% + // We will reverse branch if the true edge's likelihood is more than 51%. // - // We will setup profHotWeight to be maximum bbWeight that a block - // could have for us not to want to reverse the conditional branch + // We will set up profHotWeight to be maximum bbWeight that a block + // could have for us not to want to reverse the conditional branch. // // We will consider all blocks that have less weight than profHotWeight to be - // uncommonly run blocks as compared with the hot path of bPrev taken-jump to bDest + // uncommonly run blocks compared to the weight of bPrev's true edge. + // + // We will check if bPrev's true edge weight + // is more than twice bPrev's false edge weight. + // + // bPrev --> [BB04, weight 100] + // | \. + // falseEdge ---------------> O \. + // [likelihood=0.33] V \. + // block --> [BB05, weight 33] \. + // \. + // trueEdge ------------------------------> O + // [likelihood=0.67] | + // V + // bDest ---------------> [BB08, weight 67] // - if (fgHaveValidEdgeWeights) + assert(bPrev->FalseTargetIs(block)); + FlowEdge* trueEdge = bPrev->GetTrueEdge(); + FlowEdge* falseEdge = bPrev->GetFalseEdge(); + noway_assert(trueEdge != nullptr); + noway_assert(falseEdge != nullptr); + + // If we take the true branch more than half the time, we will reverse the branch. + if (trueEdge->getLikelihood() < 0.51) { - // We have valid edge weights, however even with valid edge weights - // we may have a minimum and maximum range for each edges value - // - // We will check that the min weight of the bPrev to bDest edge - // is more than twice the max weight of the bPrev to block edge. - // - // bPrev --> [BB04, weight 31] - // | \. - // edgeToBlock -------------> O \. - // [min=8,max=10] V \. - // block --> [BB05, weight 10] \. - // \. - // edgeToDest ----------------------------> O - // [min=21,max=23] | - // V - // bDest ---------------> [BB08, weight 21] - // - assert(bPrev->FalseTargetIs(block)); - FlowEdge* edgeToDest = bPrev->GetTrueEdge(); - FlowEdge* edgeToBlock = bPrev->GetFalseEdge(); - noway_assert(edgeToDest != nullptr); - noway_assert(edgeToBlock != nullptr); - // - // Calculate the taken ratio - // A takenRation of 0.10 means taken 10% of the time, not taken 90% of the time - // A takenRation of 0.50 means taken 50% of the time, not taken 50% of the time - // A takenRation of 0.90 means taken 90% of the time, not taken 10% of the time - // - double takenCount = - ((double)edgeToDest->edgeWeightMin() + (double)edgeToDest->edgeWeightMax()) / 2.0; - double notTakenCount = - ((double)edgeToBlock->edgeWeightMin() + (double)edgeToBlock->edgeWeightMax()) / 2.0; - double totalCount = takenCount + notTakenCount; - - // If the takenRatio (takenCount / totalCount) is greater or equal to 51% then we will reverse - // the branch - if (takenCount < (0.51 * totalCount)) - { - reorderBlock = false; - } - else - { - // set profHotWeight - profHotWeight = (edgeToBlock->edgeWeightMin() + edgeToBlock->edgeWeightMax()) / 2 - 1; - } + reorderBlock = false; } else { - // We don't have valid edge weight so we will be more conservative - // We could have bPrev, block or bDest as part of a loop and thus have extra weight - // - // We will do two checks: - // 1. Check that the weight of bDest is at least two times more than block - // 2. Check that the weight of bPrev is at least three times more than block - // - // bPrev --> [BB04, weight 31] - // | \. - // V \. - // block --> [BB05, weight 10] \. - // \. - // | - // V - // bDest ---------------> [BB08, weight 21] - // - // For this case weightDest is calculated as (21+1)/2 or 11 - // and weightPrev is calculated as (31+2)/3 also 11 - // - // Generally both weightDest and weightPrev should calculate - // the same value unless bPrev or bDest are part of a loop - // - weight_t weightDest = bDest->isMaxBBWeight() ? bDest->bbWeight : (bDest->bbWeight + 1) / 2; - weight_t weightPrev = bPrev->isMaxBBWeight() ? bPrev->bbWeight : (bPrev->bbWeight + 2) / 3; - - // select the lower of weightDest and weightPrev - profHotWeight = (weightDest < weightPrev) ? weightDest : weightPrev; - - // if the weight of block is greater (or equal) to profHotWeight then we don't reverse the cond - if (block->bbWeight >= profHotWeight) - { - reorderBlock = false; - } + // set profHotWeight + profHotWeight = falseEdge->getLikelyWeight() - 1; } } } @@ -4906,9 +4735,9 @@ bool Compiler::fgUpdateFlowGraph(bool doTailDuplication /* = false */, bool isPh // if (fgIsUsingProfileWeights()) { - // if block and bdest are in different hot/cold regions we can't do this optimization + // if block and bDest are in different hot/cold regions we can't do this optimization // because we can't allow fall-through into the cold region. - if (!fgEdgeWeightsComputed || fgInDifferentRegions(block, bDest)) + if (fgInDifferentRegions(block, bDest)) { optimizeJump = false; } diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index 7a46e14155b116..79cd63e3ac3b51 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -4257,265 +4257,17 @@ bool Compiler::fgIncorporateEdgeCounts() return e.IsGood(); } -//------------------------------------------------------------------------ -// setEdgeWeightMinChecked: possibly update minimum edge weight -// -// Arguments: -// newWeight - proposed new weight -// bDst - destination block for edge -// slop - profile slush fund -// wbUsedSlop [out] - true if we tapped into the slush fund -// -// Returns: -// true if the edge weight was adjusted -// false if the edge weight update was inconsistent with the -// edge's current [min,max} -// -bool FlowEdge::setEdgeWeightMinChecked(weight_t newWeight, BasicBlock* bDst, weight_t slop, bool* wbUsedSlop) -{ - // Negative weights are nonsensical. - // - // If we can't cover the deficit with slop, fail. - // If we can, set the new weight to zero. - // - bool usedSlop = false; - - if (newWeight < BB_ZERO_WEIGHT) - { - if ((newWeight + slop) < BB_ZERO_WEIGHT) - { - return false; - } - - newWeight = BB_ZERO_WEIGHT; - usedSlop = true; - } - - bool result = false; - - if ((newWeight <= m_edgeWeightMax) && (newWeight >= m_edgeWeightMin)) - { - m_edgeWeightMin = newWeight; - result = true; - } - else if (slop > 0) - { - // We allow for a small amount of inaccuracy in block weight counts. - if (m_edgeWeightMax < newWeight) - { - // We have already determined that this edge's weight - // is less than newWeight, so we just allow for the slop - if (newWeight <= (m_edgeWeightMax + slop)) - { - result = true; - usedSlop = true; - - if (m_edgeWeightMax != BB_ZERO_WEIGHT) - { - // We will raise m_edgeWeightMin and Max towards newWeight - m_edgeWeightMin = m_edgeWeightMax; - m_edgeWeightMax = newWeight; - } - } - } - else if (m_edgeWeightMin > newWeight) - { - // We have already determined that this edge's weight - // is more than newWeight, so we just allow for the slop - if ((newWeight + slop) >= m_edgeWeightMin) - { - result = true; - usedSlop = true; - - if (m_edgeWeightMax != BB_ZERO_WEIGHT) - { - // We will lower m_edgeWeightMin towards newWeight - // But not below zero. - // - m_edgeWeightMin = max(BB_ZERO_WEIGHT, newWeight); - } - } - } - - // If we are returning true then we should have adjusted the range so that - // the newWeight is in new range [Min..Max] or fgEdgeWeightMax is zero. - // - if (result) - { - assert((m_edgeWeightMax == BB_ZERO_WEIGHT) || - ((newWeight <= m_edgeWeightMax) && (newWeight >= m_edgeWeightMin))); - } - } - - if (result && usedSlop && (wbUsedSlop != nullptr)) - { - *wbUsedSlop = true; - } - -#if DEBUG - if (result) - { - JITDUMP("Updated min weight of " FMT_BB " -> " FMT_BB " to [" FMT_WT ".." FMT_WT "]\n", getSourceBlock()->bbNum, - bDst->bbNum, m_edgeWeightMin, m_edgeWeightMax); - } - else - { - JITDUMP("Not adjusting min weight of " FMT_BB " -> " FMT_BB "; new value " FMT_WT " not in range [" FMT_WT - ".." FMT_WT "] (+/- " FMT_WT ")\n", - getSourceBlock()->bbNum, bDst->bbNum, newWeight, m_edgeWeightMin, m_edgeWeightMax, slop); - result = false; // break here - } -#endif // DEBUG - - return result; -} - -//------------------------------------------------------------------------ -// setEdgeWeightMaxChecked: possibly update maximum edge weight -// -// Arguments: -// newWeight - proposed new weight -// bDst - destination block for edge -// slop - profile slush fund -// wbUsedSlop [out] - true if we tapped into the slush fund -// -// Returns: -// true if the edge weight was adjusted -// false if the edge weight update was inconsistent with the -// edge's current [min,max} -// -bool FlowEdge::setEdgeWeightMaxChecked(weight_t newWeight, BasicBlock* bDst, weight_t slop, bool* wbUsedSlop) -{ - // Negative weights are nonsensical. - // - // If we can't cover the deficit with slop, fail. - // If we can, set the new weight to zero. - // - bool usedSlop = false; - - if (newWeight < BB_ZERO_WEIGHT) - { - if ((newWeight + slop) < BB_ZERO_WEIGHT) - { - return false; - } - - newWeight = BB_ZERO_WEIGHT; - usedSlop = true; - } - - bool result = false; - - if ((newWeight >= m_edgeWeightMin) && (newWeight <= m_edgeWeightMax)) - { - m_edgeWeightMax = newWeight; - result = true; - } - else if (slop > 0) - { - // We allow for a small amount of inaccuracy in block weight counts. - if (m_edgeWeightMax < newWeight) - { - // We have already determined that this edge's weight - // is less than newWeight, so we just allow for the slop - if (newWeight <= (m_edgeWeightMax + slop)) - { - result = true; - usedSlop = true; - - if (m_edgeWeightMax != BB_ZERO_WEIGHT) - { - // We will allow this to raise m_edgeWeightMax towards newWeight - m_edgeWeightMax = newWeight; - } - } - } - else if (m_edgeWeightMin > newWeight) - { - // We have already determined that this edge's weight - // is more than newWeight, so we just allow for the slop - if ((newWeight + slop) >= m_edgeWeightMin) - { - result = true; - usedSlop = true; - - if (m_edgeWeightMax != BB_ZERO_WEIGHT) - { - // We will allow this to lower m_edgeWeightMin and Max towards newWeight - m_edgeWeightMax = m_edgeWeightMin; - m_edgeWeightMin = newWeight; - } - } - } - - // If we are returning true then we should have adjusted the range so that - // the newWeight is in new range [Min..Max] or fgEdgeWeightMax is zero - if (result) - { - assert((m_edgeWeightMax == BB_ZERO_WEIGHT) || - ((newWeight <= m_edgeWeightMax) && (newWeight >= m_edgeWeightMin))); - } - } - - if (result && usedSlop && (wbUsedSlop != nullptr)) - { - *wbUsedSlop = true; - } - -#if DEBUG - if (result) - { - JITDUMP("Updated max weight of " FMT_BB " -> " FMT_BB " to [" FMT_WT ".." FMT_WT "]\n", getSourceBlock()->bbNum, - bDst->bbNum, m_edgeWeightMin, m_edgeWeightMax); - } - else - { - JITDUMP("Not adjusting max weight of " FMT_BB " -> " FMT_BB "; new value " FMT_WT " not in range [" FMT_WT - ".." FMT_WT "] (+/- " FMT_WT ")\n", - getSourceBlock()->bbNum, bDst->bbNum, newWeight, m_edgeWeightMin, m_edgeWeightMax, slop); - result = false; // break here - } -#endif // DEBUG - - return result; -} - -//------------------------------------------------------------------------ -// setEdgeWeights: Sets the minimum lower (m_edgeWeightMin) value -// and the maximum upper (m_edgeWeightMax) value -// Asserts that the max value is greater or equal to the min value -// -// Arguments: -// theMinWeight - the new minimum lower (m_edgeWeightMin) -// theMaxWeight - the new maximum upper (m_edgeWeightMin) -// bDst - the destination block for the edge -// -void FlowEdge::setEdgeWeights(weight_t theMinWeight, weight_t theMaxWeight, BasicBlock* bDst) -{ - assert(theMinWeight <= theMaxWeight); - assert(theMinWeight >= 0.0); - assert(theMaxWeight >= 0.0); - - JITDUMP("Setting edge weights for " FMT_BB " -> " FMT_BB " to [" FMT_WT " .. " FMT_WT "]\n", - getSourceBlock()->bbNum, bDst->bbNum, theMinWeight, theMaxWeight); - - m_edgeWeightMin = theMinWeight; - m_edgeWeightMax = theMaxWeight; -} - //------------------------------------------------------------- -// fgComputeBlockAndEdgeWeights: determine weights for blocks -// and optionally for edges +// fgComputeBlockWeights: determine weights for blocks // // Returns: // Suitable phase status // -PhaseStatus Compiler::fgComputeBlockAndEdgeWeights() +PhaseStatus Compiler::fgComputeBlockWeights() { const bool usingProfileWeights = fgIsUsingProfileWeights(); bool madeChanges = false; fgModified = false; - fgHaveValidEdgeWeights = false; fgCalledCount = BB_UNITY_WEIGHT; #if DEBUG @@ -4539,13 +4291,6 @@ PhaseStatus Compiler::fgComputeBlockAndEdgeWeights() JITDUMP(" -- no profile data, so using default called count\n"); } - PhaseStatus edgeStatus = fgComputeEdgeWeights(); - - if (edgeStatus != PhaseStatus::MODIFIED_NOTHING) - { - return edgeStatus; - } - return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } @@ -4785,405 +4530,6 @@ bool Compiler::fgComputeCalledCount(weight_t returnWeight) return madeChanges; } -//------------------------------------------------------------- -// fgComputeEdgeWeights: compute edge weights from block weights -// -// Returns: -// Suitable phase status -// -PhaseStatus Compiler::fgComputeEdgeWeights() -{ - const bool isOptimizing = opts.OptimizationEnabled(); - const bool usingProfileWeights = fgIsUsingProfileWeights(); - - if (!isOptimizing || !usingProfileWeights) - { - JITDUMP(" -- not optimizing or no profile data, so not computing edge weights\n"); - return PhaseStatus::MODIFIED_NOTHING; - } - - BasicBlock* bSrc; - BasicBlock* bDst; - weight_t slop; - unsigned goodEdgeCountCurrent = 0; - unsigned goodEdgeCountPrevious = 0; - bool inconsistentProfileData = false; - bool hasIncompleteEdgeWeights = false; - bool usedSlop = false; - unsigned numEdges = 0; - unsigned iterations = 0; - - JITDUMP("Initial weight assignments\n\n"); - - // Now we will compute the initial m_edgeWeightMin and m_edgeWeightMax values - for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->Next()) - { - weight_t bDstWeight = bDst->bbWeight; - - // We subtract out the called count so that bDstWeight is - // the sum of all edges that go into this block from this method. - // - if (bDst == fgFirstBB) - { - bDstWeight -= fgCalledCount; - } - - for (FlowEdge* const edge : bDst->PredEdges()) - { - bool assignOK = true; - - bSrc = edge->getSourceBlock(); - // We are processing the control flow edge (bSrc -> bDst) - - numEdges++; - - // - // If the bSrc or bDst blocks do not have exact profile weights - // then we must reset any values that they currently have - // - - if (!bSrc->hasProfileWeight() || !bDst->hasProfileWeight()) - { - edge->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, bDst); - } - - slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1; - switch (bSrc->GetKind()) - { - case BBJ_ALWAYS: - case BBJ_EHCATCHRET: - case BBJ_CALLFINALLY: - case BBJ_CALLFINALLYRET: - // We know the exact edge weight - assignOK &= edge->setEdgeWeightMinChecked(bSrc->bbWeight, bDst, slop, &usedSlop); - assignOK &= edge->setEdgeWeightMaxChecked(bSrc->bbWeight, bDst, slop, &usedSlop); - break; - - case BBJ_COND: - case BBJ_SWITCH: - case BBJ_EHFINALLYRET: - case BBJ_EHFAULTRET: - case BBJ_EHFILTERRET: - if (edge->edgeWeightMax() > bSrc->bbWeight) - { - // The maximum edge weight to block can't be greater than the weight of bSrc - assignOK &= edge->setEdgeWeightMaxChecked(bSrc->bbWeight, bDst, slop, &usedSlop); - } - break; - - default: - // We should never have an edge that starts from one of these jump kinds - noway_assert(!"Unexpected bbKind"); - break; - } - - // The maximum edge weight to block can't be greater than the weight of bDst - if (edge->edgeWeightMax() > bDstWeight) - { - assignOK &= edge->setEdgeWeightMaxChecked(bDstWeight, bDst, slop, &usedSlop); - } - - if (!assignOK) - { - // Here we have inconsistent profile data - inconsistentProfileData = true; - // No point in continuing - goto EARLY_EXIT; - } - } - } - - fgEdgeCount = numEdges; - - iterations = 0; - - do - { - JITDUMP("\nSolver pass %u\n", iterations); - - iterations++; - goodEdgeCountPrevious = goodEdgeCountCurrent; - goodEdgeCountCurrent = 0; - hasIncompleteEdgeWeights = false; - - JITDUMP("\n -- step 1 --\n"); - for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->Next()) - { - for (FlowEdge* const edge : bDst->PredEdges()) - { - bool assignOK = true; - - // We are processing the control flow edge (bSrc -> bDst) - bSrc = edge->getSourceBlock(); - - slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1; - if (bSrc->KindIs(BBJ_COND)) - { - weight_t diff; - FlowEdge* otherEdge; - BasicBlock* otherDst; - if (bSrc->FalseTargetIs(bDst)) - { - otherEdge = bSrc->GetTrueEdge(); - } - else - { - otherEdge = bSrc->GetFalseEdge(); - } - otherDst = otherEdge->getDestinationBlock(); - - // If we see min/max violations, just give up on the computations - // - const bool edgeWeightSensible = edge->edgeWeightMin() <= edge->edgeWeightMax(); - const bool otherEdgeWeightSensible = otherEdge->edgeWeightMin() <= otherEdge->edgeWeightMax(); - - assignOK &= edgeWeightSensible && otherEdgeWeightSensible; - - if (assignOK) - { - // Adjust edge->m_edgeWeightMin up or adjust otherEdge->m_edgeWeightMax down - diff = bSrc->bbWeight - (edge->edgeWeightMin() + otherEdge->edgeWeightMax()); - if (diff > 0) - { - assignOK &= - edge->setEdgeWeightMinChecked(edge->edgeWeightMin() + diff, bDst, slop, &usedSlop); - } - else if (diff < 0) - { - assignOK &= otherEdge->setEdgeWeightMaxChecked(otherEdge->edgeWeightMax() + diff, otherDst, - slop, &usedSlop); - } - - // Adjust otherEdge->m_edgeWeightMin up or adjust edge->m_edgeWeightMax down - diff = bSrc->bbWeight - (otherEdge->edgeWeightMin() + edge->edgeWeightMax()); - if (diff > 0) - { - assignOK &= otherEdge->setEdgeWeightMinChecked(otherEdge->edgeWeightMin() + diff, otherDst, - slop, &usedSlop); - } - else if (diff < 0) - { - assignOK &= - edge->setEdgeWeightMaxChecked(edge->edgeWeightMax() + diff, bDst, slop, &usedSlop); - } - } - - if (!assignOK) - { - // Here we have inconsistent profile data - inconsistentProfileData = true; - // No point in continuing - goto EARLY_EXIT; - } -#ifdef DEBUG - // Now edge->m_edgeWeightMin and otherEdge->m_edgeWeightMax) should add up to bSrc->bbWeight - diff = bSrc->bbWeight - (edge->edgeWeightMin() + otherEdge->edgeWeightMax()); - - if (!((-slop) <= diff) && (diff <= slop)) - { - JITDUMP("Edge weight discrepancy: " FMT_BB "[" FMT_WT "] -> {" FMT_BB "[min:" FMT_WT - "], " FMT_BB "[max: " FMT_WT "]} diff " FMT_WT " exceeds slop " FMT_WT "\n", - bSrc->bbNum, bSrc->bbWeight, bDst->bbNum, edge->edgeWeightMin(), otherDst->bbNum, - otherEdge->edgeWeightMax(), diff, slop); - } - - // Now otherEdge->m_edgeWeightMin and edge->m_edgeWeightMax) should add up to bSrc->bbWeight - diff = bSrc->bbWeight - (otherEdge->edgeWeightMin() + edge->edgeWeightMax()); - if (!((-slop) <= diff) && (diff <= slop)) - { - JITDUMP("Edge weight discrepancy: " FMT_BB "[" FMT_WT "] -> {" FMT_BB "[max:" FMT_WT - "], " FMT_BB "[min: " FMT_WT "]} diff " FMT_WT " exceeds slop " FMT_WT "\n", - bSrc->bbNum, bSrc->bbWeight, bDst->bbNum, edge->edgeWeightMax(), otherDst->bbNum, - otherEdge->edgeWeightMin(), diff, slop); - } -#endif // DEBUG - } - } - } - - JITDUMP("\n -- step 2 --\n"); - - for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->Next()) - { - weight_t bDstWeight = bDst->bbWeight; - - if (bDstWeight == BB_MAX_WEIGHT) - { - inconsistentProfileData = true; - // No point in continuing - goto EARLY_EXIT; - } - else - { - // We subtract out the called count so that bDstWeight is - // the sum of all edges that go into this block from this method. - // - if (bDst == fgFirstBB) - { - bDstWeight -= fgCalledCount; - } - - weight_t minEdgeWeightSum = 0; - weight_t maxEdgeWeightSum = 0; - - // Calculate the sums of the minimum and maximum edge weights - for (FlowEdge* const edge : bDst->PredEdges()) - { - maxEdgeWeightSum += edge->edgeWeightMax(); - minEdgeWeightSum += edge->edgeWeightMin(); - } - - // maxEdgeWeightSum is the sum of all m_edgeWeightMax values into bDst - // minEdgeWeightSum is the sum of all m_edgeWeightMin values into bDst - - for (FlowEdge* const edge : bDst->PredEdges()) - { - bool assignOK = true; - - // We are processing the control flow edge (bSrc -> bDst) - bSrc = edge->getSourceBlock(); - slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1; - - // otherMaxEdgesWeightSum is the sum of all of the other edges m_edgeWeightMax values - // This can be used to compute a lower bound for our minimum edge weight - // - weight_t const otherMaxEdgesWeightSum = maxEdgeWeightSum - edge->edgeWeightMax(); - - if (otherMaxEdgesWeightSum >= BB_ZERO_WEIGHT) - { - if (bDstWeight >= otherMaxEdgesWeightSum) - { - // minWeightCalc is our minWeight when every other path to bDst takes it's m_edgeWeightMax - // value - weight_t minWeightCalc = (weight_t)(bDstWeight - otherMaxEdgesWeightSum); - if (minWeightCalc > edge->edgeWeightMin()) - { - assignOK &= edge->setEdgeWeightMinChecked(minWeightCalc, bDst, slop, &usedSlop); - } - } - } - - // otherMinEdgesWeightSum is the sum of all of the other edges m_edgeWeightMin values - // This can be used to compute an upper bound for our maximum edge weight - // - weight_t const otherMinEdgesWeightSum = minEdgeWeightSum - edge->edgeWeightMin(); - - if (otherMinEdgesWeightSum >= BB_ZERO_WEIGHT) - { - if (bDstWeight >= otherMinEdgesWeightSum) - { - // maxWeightCalc is our maxWeight when every other path to bDst takes it's m_edgeWeightMin - // value - weight_t maxWeightCalc = (weight_t)(bDstWeight - otherMinEdgesWeightSum); - if (maxWeightCalc < edge->edgeWeightMax()) - { - assignOK &= edge->setEdgeWeightMaxChecked(maxWeightCalc, bDst, slop, &usedSlop); - } - } - } - - if (!assignOK) - { - // Here we have inconsistent profile data - JITDUMP("Inconsistent profile data at " FMT_BB " -> " FMT_BB ": dest weight " FMT_WT - ", min/max into dest is " FMT_WT "/" FMT_WT ", edge " FMT_WT "/" FMT_WT "\n", - bSrc->bbNum, bDst->bbNum, bDstWeight, minEdgeWeightSum, maxEdgeWeightSum, - edge->edgeWeightMin(), edge->edgeWeightMax()); - - inconsistentProfileData = true; - // No point in continuing - goto EARLY_EXIT; - } - - // When m_edgeWeightMin equals m_edgeWeightMax we have a "good" edge weight - if (edge->edgeWeightMin() == edge->edgeWeightMax()) - { - // Count how many "good" edge weights we have - // Each time through we should have more "good" weights - // We exit the while loop when no longer find any new "good" edges - goodEdgeCountCurrent++; - } - else - { - // Remember that we have seen at least one "Bad" edge weight - // so that we will repeat the while loop again - hasIncompleteEdgeWeights = true; - } - } - } - } - - assert(!inconsistentProfileData); // Should use EARLY_EXIT when it is false. - - if (numEdges == goodEdgeCountCurrent) - { - noway_assert(hasIncompleteEdgeWeights == false); - break; - } - - } while (hasIncompleteEdgeWeights && (goodEdgeCountCurrent > goodEdgeCountPrevious) && (iterations < 8)); - -EARLY_EXIT:; - -#ifdef DEBUG - if (verbose) - { - if (inconsistentProfileData) - { - printf("fgComputeEdgeWeights() found inconsistent profile data, not using the edge weights\n"); - } - else - { - if (hasIncompleteEdgeWeights) - { - printf("fgComputeEdgeWeights() was able to compute exact edge weights for %3d of the %3d edges, using " - "%d passes.\n", - goodEdgeCountCurrent, numEdges, iterations); - } - else - { - printf("fgComputeEdgeWeights() was able to compute exact edge weights for all of the %3d edges, using " - "%d passes.\n", - numEdges, iterations); - } - - fgPrintEdgeWeights(); - } - } -#endif // DEBUG - - fgSlopUsedInEdgeWeights = usedSlop; - fgRangeUsedInEdgeWeights = false; - - // See if any edge weight are expressed in [min..max] form - - for (BasicBlock* const bDst : Blocks()) - { - if (bDst->bbPreds != nullptr) - { - for (FlowEdge* const edge : bDst->PredEdges()) - { - // This is the control flow edge (edge->getBlock() -> bDst) - - if (edge->edgeWeightMin() != edge->edgeWeightMax()) - { - fgRangeUsedInEdgeWeights = true; - break; - } - } - if (fgRangeUsedInEdgeWeights) - { - break; - } - } - } - - fgHaveValidEdgeWeights = !inconsistentProfileData; - fgEdgeWeightsComputed = true; - - return PhaseStatus::MODIFIED_EVERYTHING; -} - //------------------------------------------------------------------------ // fgProfileWeightsEqual: check if two profile weights are equal // (or nearly so) @@ -5314,14 +4660,13 @@ bool Compiler::fgDebugCheckProfileWeights(ProfileChecks checks) // and/or // new likelihood based weights. // - const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC); - const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY); - const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); - const bool verifyLikelihoodSum = hasFlag(checks, ProfileChecks::CHECK_LIKELIHOODSUM); - const bool assertOnFailure = hasFlag(checks, ProfileChecks::RAISE_ASSERT) && fgPgoConsistent; - const bool checkAllBlocks = hasFlag(checks, ProfileChecks::CHECK_ALL_BLOCKS); + const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY); + const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); + const bool verifyLikelihoodSum = hasFlag(checks, ProfileChecks::CHECK_LIKELIHOODSUM); + const bool assertOnFailure = hasFlag(checks, ProfileChecks::RAISE_ASSERT) && fgPgoConsistent; + const bool checkAllBlocks = hasFlag(checks, ProfileChecks::CHECK_ALL_BLOCKS); - if (!(verifyClassicWeights || verifyLikelyWeights || verifyHasLikelihood)) + if (!verifyLikelyWeights && !verifyHasLikelihood) { JITDUMP("[profile weight checks disabled]\n"); return true; @@ -5469,7 +4814,7 @@ bool Compiler::fgDebugCheckProfileWeights(ProfileChecks checks) // Verify overall entry-exit balance. // - if (verifyClassicWeights || verifyLikelyWeights) + if (verifyLikelyWeights) { // If there's a try, significant weight might pass along exception edges. // We don't model that, and it can throw off entry-exit balance. @@ -5498,7 +4843,7 @@ bool Compiler::fgDebugCheckProfileWeights(ProfileChecks checks) { JITDUMP("No blocks were profiled, so nothing to check\n"); } - else if (verifyClassicWeights || verifyLikelyWeights) + else if (verifyLikelyWeights) { JITDUMP("Profile is self-consistent (%d profiled blocks, %d unprofiled)\n", profiledBlocks, unprofiledBlocks); @@ -5544,18 +4889,15 @@ bool Compiler::fgDebugCheckProfileWeights(ProfileChecks checks) // bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks checks) { - const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC); - const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY); - const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); + const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY); + const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); - if (!(verifyClassicWeights || verifyLikelyWeights || verifyHasLikelihood)) + if (!verifyLikelyWeights && !verifyHasLikelihood) { return true; } weight_t const blockWeight = block->bbWeight; - weight_t incomingWeightMin = 0; - weight_t incomingWeightMax = 0; weight_t incomingLikelyWeight = 0; unsigned missingLikelyWeight = 0; bool foundPreds = false; @@ -5563,8 +4905,6 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks for (FlowEdge* const predEdge : block->PredEdges()) { - incomingWeightMin += predEdge->edgeWeightMin(); - incomingWeightMax += predEdge->edgeWeightMax(); if (predEdge->hasLikelihood()) { if (BasicBlock::sameHndRegion(block, predEdge->getSourceBlock())) @@ -5592,42 +4932,15 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks if (block->isBBCallFinallyPairTail()) { incomingLikelyWeight = block->Prev()->bbWeight; - incomingWeightMin = incomingLikelyWeight; - incomingWeightMax = incomingLikelyWeight; foundEHPreds = false; } - bool classicWeightsValid = true; - bool likelyWeightsValid = true; + bool likelyWeightsValid = true; // If we have EH preds we may not have consistent incoming flow. // if (foundPreds && !foundEHPreds) { - if (verifyClassicWeights) - { - if (!fgProfileWeightsConsistent(incomingWeightMin, incomingWeightMax)) - { - JITDUMP(" " FMT_BB " - incoming min " FMT_WT " inconsistent with incoming max " FMT_WT "\n", - block->bbNum, incomingWeightMin, incomingWeightMax); - classicWeightsValid = false; - } - - if (!fgProfileWeightsConsistent(blockWeight, incomingWeightMin)) - { - JITDUMP(" " FMT_BB " - block weight " FMT_WT " inconsistent with incoming min " FMT_WT "\n", - block->bbNum, blockWeight, incomingWeightMin); - classicWeightsValid = false; - } - - if (!fgProfileWeightsConsistent(blockWeight, incomingWeightMax)) - { - JITDUMP(" " FMT_BB " - block weight " FMT_WT " inconsistent with incoming max " FMT_WT "\n", - block->bbNum, blockWeight, incomingWeightMax); - classicWeightsValid = false; - } - } - if (verifyLikelyWeights) { if (!fgProfileWeightsConsistentOrSmall(blockWeight, incomingLikelyWeight)) @@ -5649,7 +4962,7 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks } } - return classicWeightsValid && likelyWeightsValid; + return likelyWeightsValid; } //------------------------------------------------------------------------ @@ -5668,17 +4981,15 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks // bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks checks) { - const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC); - const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); - const bool verifyLikelihoodSum = hasFlag(checks, ProfileChecks::CHECK_LIKELIHOODSUM); + const bool verifyHasLikelihood = hasFlag(checks, ProfileChecks::CHECK_HASLIKELIHOOD); + const bool verifyLikelihoodSum = hasFlag(checks, ProfileChecks::CHECK_LIKELIHOODSUM); - if (!(verifyClassicWeights || verifyHasLikelihood || verifyLikelihoodSum)) + if (!verifyHasLikelihood && !verifyLikelihoodSum) { return true; } - bool classicWeightsValid = true; - bool likelyWeightsValid = true; + bool likelyWeightsValid = true; // We want switch targets unified, but not EH edges. // @@ -5687,8 +4998,6 @@ bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks if ((numSuccs > 0) && !block->KindIs(BBJ_EHFINALLYRET, BBJ_EHFAULTRET, BBJ_EHFILTERRET)) { weight_t const blockWeight = block->bbWeight; - weight_t outgoingWeightMin = 0; - weight_t outgoingWeightMax = 0; weight_t outgoingLikelihood = 0; // Walk successor edges and add up flow counts. @@ -5701,9 +5010,6 @@ bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks assert(succEdge != nullptr); BasicBlock* succBlock = succEdge->getDestinationBlock(); - outgoingWeightMin += succEdge->edgeWeightMin(); - outgoingWeightMax += succEdge->edgeWeightMax(); - if (succEdge->hasLikelihood()) { outgoingLikelihood += succEdge->getLikelihood(); @@ -5718,32 +5024,7 @@ bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks if (missingEdges > 0) { JITDUMP(" " FMT_BB " - missing %d successor edges\n", block->bbNum, missingEdges); - classicWeightsValid = false; - likelyWeightsValid = false; - } - - if (verifyClassicWeights) - { - if (!fgProfileWeightsConsistent(outgoingWeightMin, outgoingWeightMax)) - { - JITDUMP(" " FMT_BB " - outgoing min " FMT_WT " inconsistent with outgoing max " FMT_WT "\n", - block->bbNum, outgoingWeightMin, outgoingWeightMax); - classicWeightsValid = false; - } - - if (!fgProfileWeightsConsistent(blockWeight, outgoingWeightMin)) - { - JITDUMP(" " FMT_BB " - block weight " FMT_WT " inconsistent with outgoing min " FMT_WT "\n", - block->bbNum, blockWeight, outgoingWeightMin); - classicWeightsValid = false; - } - - if (!fgProfileWeightsConsistent(blockWeight, outgoingWeightMax)) - { - JITDUMP(" " FMT_BB " - block weight " FMT_WT " inconsistent with outgoing max " FMT_WT "\n", - block->bbNum, blockWeight, outgoingWeightMax); - classicWeightsValid = false; - } + likelyWeightsValid = false; } if (verifyHasLikelihood) @@ -5796,7 +5077,7 @@ bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks } } - return classicWeightsValid && likelyWeightsValid; + return likelyWeightsValid; } #endif // DEBUG diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 7fba06f2b7b3c0..07681c75c0e95c 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -884,7 +884,21 @@ var_types Compiler::impImportCall(OPCODE opcode, } //------------------------------------------------------------------------- - // The main group of arguments + // The main group of arguments, and the this pointer. + + // 'this' is pushed on the IL stack before all call args, but if this is a + // constrained call 'this' is a byref that may need to be dereferenced. + // That dereference should happen _after_ all args, so we need to spill + // them if they can interfere. + bool hasThis; + hasThis = ((mflags & CORINFO_FLG_STATIC) == 0) && ((sig->callConv & CORINFO_CALLCONV_EXPLICITTHIS) == 0) && + ((opcode != CEE_NEWOBJ) || (newobjThis != nullptr)); + + if (hasThis && (constraintCallThisTransform == CORINFO_DEREF_THIS)) + { + impSpillSideEffects(false, CHECK_SPILL_ALL DEBUGARG( + "constrained call requires dereference for 'this' right before call")); + } impPopCallArgs(sig, call->AsCall()); if (extraArg.Node != nullptr) @@ -904,8 +918,7 @@ var_types Compiler::impImportCall(OPCODE opcode, //------------------------------------------------------------------------- // The "this" pointer - if (((mflags & CORINFO_FLG_STATIC) == 0) && ((sig->callConv & CORINFO_CALLCONV_EXPLICITTHIS) == 0) && - !((opcode == CEE_NEWOBJ) && (newobjThis == nullptr))) + if (hasThis) { GenTree* obj; @@ -4717,6 +4730,14 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, ClassLayout* toLayout = nullptr; var_types toType = TypeHandleToVarType(toTypeHnd, &toLayout); + if (fromType == TYP_REF || info.compCompHnd->isNullableType(fromTypeHnd) != TypeCompareState::MustNot || + toType == TYP_REF || info.compCompHnd->isNullableType(toTypeHnd) != TypeCompareState::MustNot) + { + // Fallback to the software implementation to throw when the types fail a "default(T) is not null" + // check. + return nullptr; + } + unsigned fromSize = fromLayout != nullptr ? fromLayout->GetSize() : genTypeSize(fromType); unsigned toSize = toLayout != nullptr ? toLayout->GetSize() : genTypeSize(toType); @@ -4729,8 +4750,6 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, return nullptr; } - assert((fromType != TYP_REF) && (toType != TYP_REF)); - GenTree* op1 = impPopStack().val; op1 = impImplicitR4orR8Cast(op1, fromType); diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 23bfec85efd5be..700cd33bd6bf2f 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -158,9 +158,13 @@ CONFIG_INTEGER(JitPrintInlinedMethodsVerbose, W("JitPrintInlinedMethodsVerboseLe CONFIG_METHODSET(JitPrintInlinedMethods, W("JitPrintInlinedMethods")) CONFIG_METHODSET(JitPrintDevirtualizedMethods, W("JitPrintDevirtualizedMethods")) - // -1: just do internal checks (CHECK_HASLIKELIHOOD | CHECK_LIKELIHOODSUM | RAISE_ASSERT) -// Else bitflag of ProfileChecks enum. +// Else bitflag: +// - 0x1: check edges have likelihoods +// - 0x2: check edge likelihoods sum to 1.0 +// - 0x4: fully check likelihoods +// - 0x8: assert on check failure +// - 0x10: check block profile weights CONFIG_INTEGER(JitProfileChecks, W("JitProfileChecks"), -1) CONFIG_INTEGER(JitRequired, W("JITRequired"), -1) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 3f1031f6a2cccd..5eb39435a51543 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1804,7 +1804,8 @@ void Compiler::lvaClassifyParameterABI() } else #endif -#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) || \ + defined(TARGET_RISCV64) { PlatformClassifier classifier(cInfo); lvaClassifyParameterABI(classifier); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 9731331885becf..cbdc886ee2802b 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -295,6 +295,74 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN return false; } + if (childNode->OperIs(GT_ROL, GT_ROR)) + { + // Find "a op (b rotate cns)" + + if (childNode->gtGetOp1()->isContained()) + { + // Cannot contain if the childs op1 is already contained + return false; + } + + GenTree* rotateAmountNode = childNode->gtGetOp2(); + + if (!rotateAmountNode->IsCnsIntOrI()) + { + // Cannot contain if the childs op2 is not a constant + return false; + } + + const ssize_t wrapAmount = (static_cast(genTypeSize(parentNode)) * BITS_PER_BYTE); + assert((wrapAmount == 32) || (wrapAmount == 64)); + + // Rotation is circular, so normalize to [0, wrapAmount - 1] + ssize_t rotateAmount = rotateAmountNode->AsIntCon()->IconValue() % wrapAmount; + assert((rotateAmount >= 0) && (rotateAmount <= (wrapAmount - 1))); + + if (childNode->OperIs(GT_ROL)) + { + // The actual instructions only encode rotate right but + // since rotating left by 1 is equivalen to rotating + // right by (rotateAmount - 1), we can fix things here. + + childNode->SetOper(GT_ROR); + rotateAmount = wrapAmount - rotateAmount; + } + + rotateAmountNode->AsIntCon()->SetIconValue(rotateAmount); + assert(childNode->OperIs(GT_ROR)); + + if (parentNode->OperIs(GT_AND)) + { + // These operations can still report flags + + if (IsInvariantInRange(childNode, parentNode)) + { + assert(rotateAmountNode->isContained()); + return true; + } + } + + if ((parentNode->gtFlags & GTF_SET_FLAGS) != 0) + { + // Cannot contain if the parent operation needs to set flags + return false; + } + + if (parentNode->OperIs(GT_OR, GT_XOR)) + { + if (IsInvariantInRange(childNode, parentNode)) + { + assert(rotateAmountNode->isContained()); + return true; + } + } + + // TODO: Handle BIC/BICS, EON, MVN, ORN, TST + return false; + } + if (childNode->OperIs(GT_NEG)) { // If we have a contained LSH, RSH or RSZ, we can still contain NEG if the parent is a EQ or NE. diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index d1825f44696611..3ee0f88ee2214f 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3511,11 +3511,11 @@ int LinearScan::BuildOperandUses(GenTree* node, regMaskTP candidates) // ANDs may be contained in a chain. return BuildBinaryUses(node->AsOp(), candidates); } - if (node->OperIs(GT_NEG, GT_CAST, GT_LSH, GT_RSH, GT_RSZ)) + if (node->OperIs(GT_NEG, GT_CAST, GT_LSH, GT_RSH, GT_RSZ, GT_ROR)) { // NEG can be contained for mneg on arm64 // CAST and LSH for ADD with sign/zero extension - // LSH, RSH, and RSZ for various "shifted register" instructions on arm64 + // LSH, RSH, RSZ, and ROR for various "shifted register" instructions on arm64 return BuildOperandUses(node->gtGetOp1(), candidates); } #endif diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index fd06ede79cb946..c8b08540276384 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13213,80 +13213,28 @@ Compiler::FoldResult Compiler::fgFoldConditional(BasicBlock* block) block->SetKindAndTargetEdge(BBJ_ALWAYS, block->GetFalseEdge()); } - if (fgHaveValidEdgeWeights) + // We examine the taken edge (block -> bTaken) + // if block has valid profile weight and bTaken does not we try to adjust bTaken's weight + // else if bTaken has valid profile weight and block does not we try to adjust block's weight + // We can only adjust the block weights when (the edge block -> bTaken) is the only edge into bTaken + // + if (block->hasProfileWeight()) { - // We are removing an edge from block to bNotTaken - // and we have already computed the edge weights, so - // we will try to adjust some of the weights - // - BasicBlock* bUpdated = nullptr; // non-NULL if we updated the weight of an internal block - - // We examine the taken edge (block -> bTaken) - // if block has valid profile weight and bTaken does not we try to adjust bTaken's weight - // else if bTaken has valid profile weight and block does not we try to adjust block's weight - // We can only adjust the block weights when (the edge block -> bTaken) is the only edge into bTaken - // - if (block->hasProfileWeight()) - { - // The edge weights for (block -> bTaken) are 100% of block's weight - - edgeTaken->setEdgeWeights(block->bbWeight, block->bbWeight, bTaken); - - if (!bTaken->hasProfileWeight()) - { - if ((bTaken->countOfInEdges() == 1) || (bTaken->bbWeight < block->bbWeight)) - { - // Update the weight of bTaken - bTaken->inheritWeight(block); - bUpdated = bTaken; - } - } - } - else if (bTaken->hasProfileWeight()) + if (!bTaken->hasProfileWeight()) { - if (bTaken->countOfInEdges() == 1) + if ((bTaken->countOfInEdges() == 1) || (bTaken->bbWeight < block->bbWeight)) { - // There is only one in edge to bTaken - edgeTaken->setEdgeWeights(bTaken->bbWeight, bTaken->bbWeight, bTaken); - - // Update the weight of block - block->inheritWeight(bTaken); - bUpdated = block; + // Update the weight of bTaken + bTaken->inheritWeight(block); } } - - if (bUpdated != nullptr) + } + else if (bTaken->hasProfileWeight()) + { + if (bTaken->countOfInEdges() == 1) { - weight_t newMinWeight; - weight_t newMaxWeight; - - FlowEdge* edge; - // Now fix the weights of the edges out of 'bUpdated' - switch (bUpdated->GetKind()) - { - case BBJ_COND: - edge = bUpdated->GetFalseEdge(); - newMaxWeight = bUpdated->bbWeight; - newMinWeight = min(edge->edgeWeightMin(), newMaxWeight); - edge->setEdgeWeights(newMinWeight, newMaxWeight, bUpdated->GetFalseTarget()); - - edge = bUpdated->GetTrueEdge(); - newMaxWeight = bUpdated->bbWeight; - newMinWeight = min(edge->edgeWeightMin(), newMaxWeight); - edge->setEdgeWeights(newMinWeight, newMaxWeight, bUpdated->GetFalseTarget()); - break; - - case BBJ_ALWAYS: - edge = bUpdated->GetTargetEdge(); - newMaxWeight = bUpdated->bbWeight; - newMinWeight = min(edge->edgeWeightMin(), newMaxWeight); - edge->setEdgeWeights(newMinWeight, newMaxWeight, bUpdated->Next()); - break; - - default: - // We don't handle BBJ_SWITCH - break; - } + // Update the weight of block + block->inheritWeight(bTaken); } } diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 07da4731252ff6..0524fc5d2022c6 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1348,11 +1348,9 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() (1.0 - origB1TrueLikelihood) + origB1TrueLikelihood * origB2FalseEdge->getLikelihood(); } - // Fix B1 true edge likelihood and min/max weights + // Fix B1 true edge likelihood // origB1TrueEdge->setLikelihood(newB1TrueLikelihood); - weight_t const newB1TrueWeight = m_b1->bbWeight * newB1TrueLikelihood; - origB1TrueEdge->setEdgeWeights(newB1TrueWeight, newB1TrueWeight, m_b1->GetTrueTarget()); assert(m_b1->KindIs(BBJ_COND)); assert(m_b2->KindIs(BBJ_COND)); @@ -1367,11 +1365,9 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() FlowEdge* const newB1FalseEdge = origB2FalseEdge; m_b1->SetFalseEdge(newB1FalseEdge); - // Fix B1 false edge likelihood and min/max weights. + // Fix B1 false edge likelihood // newB1FalseEdge->setLikelihood(1.0 - newB1TrueLikelihood); - weight_t const newB1FalseWeight = m_b1->bbWeight * (1.0 - newB1TrueLikelihood); - newB1FalseEdge->setEdgeWeights(newB1FalseWeight, newB1FalseWeight, m_b1->GetTrueTarget()); } // Get rid of the second block diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 09921b2d3aebe8..f2de86fcdef21f 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2315,54 +2315,6 @@ bool Compiler::optInvertWhileLoop(BasicBlock* block) weightTop); bTest->inheritWeight(bTop); - // Determine the new edge weights. - // - // We project the next/jump ratio for block and bTest by using - // the original likelihoods out of bTest. - // - // Note "next" is the loop top block, not bTest's bbNext, - // we'll call this latter block "after". - // - weight_t const testToNextLikelihood = min(1.0, weightTop / weightTest); - weight_t const testToAfterLikelihood = 1.0 - testToNextLikelihood; - - // Adjust edges out of bTest (which now has weight weightTop) - // - weight_t const testToNextWeight = weightTop * testToNextLikelihood; - weight_t const testToAfterWeight = weightTop * testToAfterLikelihood; - - FlowEdge* const edgeTestToNext = bTest->GetTrueEdge(); - FlowEdge* const edgeTestToAfter = bTest->GetFalseEdge(); - - JITDUMP("Setting weight of " FMT_BB " -> " FMT_BB " to " FMT_WT " (iterate loop)\n", bTest->bbNum, bTop->bbNum, - testToNextWeight); - JITDUMP("Setting weight of " FMT_BB " -> " FMT_BB " to " FMT_WT " (exit loop)\n", bTest->bbNum, - bTest->Next()->bbNum, testToAfterWeight); - - edgeTestToNext->setEdgeWeights(testToNextWeight, testToNextWeight, bTop); - edgeTestToAfter->setEdgeWeights(testToAfterWeight, testToAfterWeight, bTest->GetFalseTarget()); - - // Adjust edges out of block, using the same distribution. - // - JITDUMP("Profile weight of " FMT_BB " remains unchanged at " FMT_WT "\n", block->bbNum, weightBlock); - - weight_t const blockToNextLikelihood = testToNextLikelihood; - weight_t const blockToAfterLikelihood = testToAfterLikelihood; - - weight_t const blockToNextWeight = weightBlock * blockToNextLikelihood; - weight_t const blockToAfterWeight = weightBlock * blockToAfterLikelihood; - - FlowEdge* const edgeBlockToNext = bNewCond->GetFalseEdge(); - FlowEdge* const edgeBlockToAfter = bNewCond->GetTrueEdge(); - - JITDUMP("Setting weight of " FMT_BB " -> " FMT_BB " to " FMT_WT " (enter loop)\n", bNewCond->bbNum, - bNewCond->GetFalseTarget()->bbNum, blockToNextWeight); - JITDUMP("Setting weight of " FMT_BB " -> " FMT_BB " to " FMT_WT " (avoid loop)\n", bNewCond->bbNum, - bNewCond->GetTrueTarget()->bbNum, blockToAfterWeight); - - edgeBlockToNext->setEdgeWeights(blockToNextWeight, blockToNextWeight, bNewCond->GetFalseTarget()); - edgeBlockToAfter->setEdgeWeights(blockToAfterWeight, blockToAfterWeight, bNewCond->GetTrueTarget()); - #ifdef DEBUG // If we're checking profile data, see if profile for the two target blocks is consistent. // @@ -3232,91 +3184,6 @@ bool Compiler::optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit) return true; } -//----------------------------------------------------------------------------- -// optEstimateEdgeLikelihood: Given a block "from" that may transfer control to -// "to", estimate the likelihood that this will happen taking profile into -// account if available. -// -// Parameters: -// from - From block -// to - To block -// fromProfile - [out] Whether or not the estimate is based on profile data -// -// Returns: -// Estimated likelihood of the edge being taken. -// -weight_t Compiler::optEstimateEdgeLikelihood(BasicBlock* from, BasicBlock* to, bool* fromProfile) -{ - *fromProfile = (from->HasFlag(BBF_PROF_WEIGHT) != BBF_EMPTY) && (to->HasFlag(BBF_PROF_WEIGHT) != BBF_EMPTY); - if (!fgIsUsingProfileWeights() || !from->HasFlag(BBF_PROF_WEIGHT) || !to->HasFlag(BBF_PROF_WEIGHT) || - from->KindIs(BBJ_ALWAYS)) - { - return 1.0 / from->NumSucc(this); - } - - bool useEdgeWeights = fgHaveValidEdgeWeights; - - weight_t takenCount = 0; - weight_t notTakenCount = 0; - - if (useEdgeWeights) - { - from->VisitRegularSuccs(this, [&, to](BasicBlock* succ) { - *fromProfile &= succ->hasProfileWeight(); - FlowEdge* edge = fgGetPredForBlock(succ, from); - weight_t edgeWeight = (edge->edgeWeightMin() + edge->edgeWeightMax()) / 2.0; - - if (succ == to) - { - takenCount += edgeWeight; - } - else - { - notTakenCount += edgeWeight; - } - return BasicBlockVisit::Continue; - }); - - // Watch out for cases where edge weights were not properly maintained - // so that it appears no profile flow goes to 'to'. - // - useEdgeWeights = !fgProfileWeightsConsistent(takenCount, BB_ZERO_WEIGHT); - } - - if (!useEdgeWeights) - { - takenCount = 0; - notTakenCount = 0; - - from->VisitRegularSuccs(this, [&, to](BasicBlock* succ) { - *fromProfile &= succ->hasProfileWeight(); - if (succ == to) - { - takenCount += succ->bbWeight; - } - else - { - notTakenCount += succ->bbWeight; - } - - return BasicBlockVisit::Continue; - }); - } - - if (!*fromProfile) - { - return 1.0 / from->NumSucc(this); - } - - if (fgProfileWeightsConsistent(takenCount, BB_ZERO_WEIGHT)) - { - return 0; - } - - weight_t likelihood = takenCount / (takenCount + notTakenCount); - return likelihood; -} - //----------------------------------------------------------------------------- // optSetWeightForPreheaderOrExit: Set the weight of a newly created preheader // or exit, after it has been added to the flowgraph. @@ -3327,52 +3194,34 @@ weight_t Compiler::optEstimateEdgeLikelihood(BasicBlock* from, BasicBlock* to, b // void Compiler::optSetWeightForPreheaderOrExit(FlowGraphNaturalLoop* loop, BasicBlock* block) { - bool hasProfWeight = true; - - assert(block->GetUniqueSucc() != nullptr); - // Inherit first estimate from the target target; optEstimateEdgeLikelihood - // may use it in its estimate if we do not have edge weights to estimate - // from (we also assume the edges into 'block' already inherited their edge - // weights from the previous edge). - block->inheritWeight(block->GetTarget()); + bool hasProfWeight = true; + weight_t newWeight = BB_ZERO_WEIGHT; - weight_t newWeight = BB_ZERO_WEIGHT; - for (FlowEdge* edge : block->PredEdges()) + for (FlowEdge* const edge : block->PredEdges()) { - BasicBlock* predBlock = edge->getSourceBlock(); - - bool fromProfile = false; - weight_t likelihood = optEstimateEdgeLikelihood(predBlock, block, &fromProfile); - hasProfWeight &= fromProfile; - - weight_t contribution = predBlock->bbWeight * likelihood; - JITDUMP(" Estimated likelihood " FMT_BB " -> " FMT_BB " to be " FMT_WT " (contribution: " FMT_WT ")\n", - predBlock->bbNum, block->bbNum, likelihood, contribution); - - newWeight += contribution; - - // Normalize pred -> new block weight - edge->setEdgeWeights(contribution, contribution, block); + newWeight += edge->getLikelyWeight(); + hasProfWeight &= edge->getSourceBlock()->hasProfileWeight(); } - block->RemoveFlags(BBF_PROF_WEIGHT | BBF_RUN_RARELY); - block->bbWeight = newWeight; + if (hasProfWeight) { block->SetFlags(BBF_PROF_WEIGHT); } + else + { + block->RemoveFlags(BBF_PROF_WEIGHT); + } if (newWeight == BB_ZERO_WEIGHT) { block->SetFlags(BBF_RUN_RARELY); - return; } - - // Normalize block -> target weight - FlowEdge* const edgeFromBlock = block->GetTargetEdge(); - assert(edgeFromBlock != nullptr); - edgeFromBlock->setEdgeWeights(block->bbWeight, block->bbWeight, block->GetTarget()); + else + { + block->RemoveFlags(BBF_RUN_RARELY); + } } /***************************************************************************** diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 2328dba7d63688..e1046de8b99863 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -353,7 +353,7 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree* return; } - if (DoesOverflow(block, treeIndex)) + if (DoesOverflow(block, treeIndex, range)) { JITDUMP("Method determined to overflow.\n"); return; @@ -773,6 +773,22 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse isConstantAssertion = true; } + // Current assertion asserts a bounds check does not throw + else if (curAssertion->IsBoundsCheckNoThrow()) + { + ValueNum indexVN = curAssertion->op1.bnd.vnIdx; + ValueNum lenVN = curAssertion->op1.bnd.vnLen; + if (normalLclVN == indexVN) + { + isUnsigned = true; + cmpOper = GT_LT; + limit = Limit(Limit::keBinOpArray, lenVN, 0); + } + else + { + continue; + } + } // Current assertion is not supported, ignore it else { @@ -782,7 +798,8 @@ void RangeCheck::MergeEdgeAssertions(ValueNum normalLclVN, ASSERT_VALARG_TP asse assert(limit.IsBinOpArray() || limit.IsConstant()); // Make sure the assertion is of the form != 0 or == 0 if it isn't a constant assertion. - if (!isConstantAssertion && (curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT))) + if (!isConstantAssertion && (curAssertion->assertionKind != Compiler::OAK_NO_THROW) && + (curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT))) { continue; } @@ -1235,17 +1252,17 @@ bool RangeCheck::MultiplyOverflows(Limit& limit1, Limit& limit2) } // Does the bin operation overflow. -bool RangeCheck::DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop) +bool RangeCheck::DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop, const Range& range) { GenTree* op1 = binop->gtGetOp1(); GenTree* op2 = binop->gtGetOp2(); - if (!m_pSearchPath->Lookup(op1) && DoesOverflow(block, op1)) + if (!m_pSearchPath->Lookup(op1) && DoesOverflow(block, op1, range)) { return true; } - if (!m_pSearchPath->Lookup(op2) && DoesOverflow(block, op2)) + if (!m_pSearchPath->Lookup(op2) && DoesOverflow(block, op2, range)) { return true; } @@ -1279,7 +1296,7 @@ bool RangeCheck::DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop) } // Check if the var definition the rhs involves arithmetic that overflows. -bool RangeCheck::DoesVarDefOverflow(GenTreeLclVarCommon* lcl) +bool RangeCheck::DoesVarDefOverflow(BasicBlock* block, GenTreeLclVarCommon* lcl, const Range& range) { LclSsaVarDsc* ssaDef = GetSsaDefStore(lcl); if (ssaDef == nullptr) @@ -1291,10 +1308,25 @@ bool RangeCheck::DoesVarDefOverflow(GenTreeLclVarCommon* lcl) } return true; } - return DoesOverflow(ssaDef->GetBlock(), ssaDef->GetDefNode()->Data()); + + // We can use intermediate assertions about the local to prove that any + // overflow on this path does not matter for the range computed. + Range assertionRange = Range(Limit(Limit::keUnknown)); + MergeAssertion(block, lcl, &assertionRange DEBUGARG(0)); + + // But only if the range from the assertion is more strict than the global + // range computed; otherwise we might still have used the def's value to + // tighten the range of the global range. + Range merged = RangeOps::Merge(range, assertionRange, false); + if (merged.LowerLimit().Equals(range.LowerLimit()) && merged.UpperLimit().Equals(range.UpperLimit())) + { + return false; + } + + return DoesOverflow(ssaDef->GetBlock(), ssaDef->GetDefNode()->Data(), range); } -bool RangeCheck::DoesPhiOverflow(BasicBlock* block, GenTree* expr) +bool RangeCheck::DoesPhiOverflow(BasicBlock* block, GenTree* expr, const Range& range) { for (GenTreePhi::Use& use : expr->AsPhi()->Uses()) { @@ -1303,7 +1335,7 @@ bool RangeCheck::DoesPhiOverflow(BasicBlock* block, GenTree* expr) { continue; } - if (DoesOverflow(block, arg)) + if (DoesOverflow(block, arg, range)) { return true; } @@ -1311,17 +1343,30 @@ bool RangeCheck::DoesPhiOverflow(BasicBlock* block, GenTree* expr) return false; } -bool RangeCheck::DoesOverflow(BasicBlock* block, GenTree* expr) +//------------------------------------------------------------------------ +// DoesOverflow: Check if the computation of "expr" may have overflowed. +// +// Arguments: +// block - the block that contains `expr` +// expr - expression to check overflow of +// range - range that we believe "expr" to be in without accounting for +// overflow; used to ignore potential overflow on paths where +// we can prove the value is in this range regardless. +// +// Return value: +// True if the computation may have involved an impactful overflow. +// +bool RangeCheck::DoesOverflow(BasicBlock* block, GenTree* expr, const Range& range) { bool overflows = false; if (!GetOverflowMap()->Lookup(expr, &overflows)) { - overflows = ComputeDoesOverflow(block, expr); + overflows = ComputeDoesOverflow(block, expr, range); } return overflows; } -bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) +bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr, const Range& range) { JITDUMP("Does overflow [%06d]?\n", Compiler::dspTreeID(expr)); m_pSearchPath->Set(expr, block, SearchPath::Overwrite); @@ -1343,17 +1388,17 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) } else if (expr->OperIs(GT_COMMA)) { - overflows = ComputeDoesOverflow(block, expr->gtEffectiveVal()); + overflows = ComputeDoesOverflow(block, expr->gtEffectiveVal(), range); } // Check if the var def has rhs involving arithmetic that overflows. else if (expr->IsLocal()) { - overflows = DoesVarDefOverflow(expr->AsLclVarCommon()); + overflows = DoesVarDefOverflow(block, expr->AsLclVarCommon(), range); } // Check if add overflows. else if (expr->OperIs(GT_ADD, GT_MUL)) { - overflows = DoesBinOpOverflow(block, expr->AsOp()); + overflows = DoesBinOpOverflow(block, expr->AsOp(), range); } // These operators don't overflow. // Actually, GT_LSH can overflow so it depends on the analysis done in ComputeRangeForBinOp @@ -1364,11 +1409,11 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr) // Walk through phi arguments to check if phi arguments involve arithmetic that overflows. else if (expr->OperIs(GT_PHI)) { - overflows = DoesPhiOverflow(block, expr); + overflows = DoesPhiOverflow(block, expr, range); } else if (expr->OperIs(GT_CAST)) { - overflows = ComputeDoesOverflow(block, expr->gtGetOp1()); + overflows = ComputeDoesOverflow(block, expr->gtGetOp1(), range); } GetOverflowMap()->Set(expr, overflows, OverflowMap::Overwrite); m_pSearchPath->Remove(expr); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 9d7b064387174a..de44b88d3ae52d 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -199,7 +199,7 @@ struct Limit return false; } - bool Equals(Limit& l) + bool Equals(const Limit& l) const { switch (type) { @@ -262,11 +262,21 @@ struct Range { } + const Limit& UpperLimit() const + { + return uLimit; + } + Limit& UpperLimit() { return uLimit; } + const Limit& LowerLimit() const + { + return lLimit; + } + Limit& LowerLimit() { return lLimit; @@ -440,12 +450,12 @@ struct RangeOps // Given two ranges "r1" and "r2", do a Phi merge. If "monIncreasing" is true, // then ignore the dependent variables for the lower bound but not for the upper bound. - static Range Merge(Range& r1, Range& r2, bool monIncreasing) + static Range Merge(const Range& r1, const Range& r2, bool monIncreasing) { - Limit& r1lo = r1.LowerLimit(); - Limit& r1hi = r1.UpperLimit(); - Limit& r2lo = r2.LowerLimit(); - Limit& r2hi = r2.UpperLimit(); + const Limit& r1lo = r1.LowerLimit(); + const Limit& r1hi = r1.UpperLimit(); + const Limit& r2lo = r2.LowerLimit(); + const Limit& r2hi = r2.UpperLimit(); // Take care of lo part. Range result = Limit(Limit::keUnknown); @@ -689,19 +699,19 @@ class RangeCheck bool MultiplyOverflows(Limit& limit1, Limit& limit2); // Does the binary operation between the operands overflow? Check recursively. - bool DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop); + bool DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop, const Range& range); // Do the phi operands involve a definition that could overflow? - bool DoesPhiOverflow(BasicBlock* block, GenTree* expr); + bool DoesPhiOverflow(BasicBlock* block, GenTree* expr, const Range& range); // Find the def of the "expr" local and recurse on the arguments if any of them involve a // calculation that overflows. - bool DoesVarDefOverflow(GenTreeLclVarCommon* lcl); + bool DoesVarDefOverflow(BasicBlock* block, GenTreeLclVarCommon* lcl, const Range& range); - bool ComputeDoesOverflow(BasicBlock* block, GenTree* expr); + bool ComputeDoesOverflow(BasicBlock* block, GenTree* expr, const Range& range); - // Does the current "expr" which is a use involve a definition, that overflows. - bool DoesOverflow(BasicBlock* block, GenTree* tree); + // Does the current "expr", which is a use, involve a definition that overflows. + bool DoesOverflow(BasicBlock* block, GenTree* tree, const Range& range); // Widen the range by first checking if the induction variable is monotonically increasing. // Requires "pRange" to be partially computed. diff --git a/src/coreclr/jit/targetriscv64.cpp b/src/coreclr/jit/targetriscv64.cpp index 5c51f66f83c402..29f71dc76d8fa2 100644 --- a/src/coreclr/jit/targetriscv64.cpp +++ b/src/coreclr/jit/targetriscv64.cpp @@ -24,4 +24,154 @@ const regNumber fltArgRegs [] = {REG_FLTARG_0, REG_FLTARG_1, REG_FLTARG_2, REG_F const regMaskTP fltArgMasks[] = {RBM_FLTARG_0, RBM_FLTARG_1, RBM_FLTARG_2, RBM_FLTARG_3, RBM_FLTARG_4, RBM_FLTARG_5, RBM_FLTARG_6, RBM_FLTARG_7 }; // clang-format on +//----------------------------------------------------------------------------- +// RiscV64Classifier: +// Construct a new instance of the RISC-V 64 ABI classifier. +// +// Parameters: +// info - Info about the method being classified. +// +RiscV64Classifier::RiscV64Classifier(const ClassifierInfo& info) + : m_info(info) + , m_intRegs(intArgRegs, ArrLen(intArgRegs)) + , m_floatRegs(fltArgRegs, ArrLen(fltArgRegs)) +{ +} + +//----------------------------------------------------------------------------- +// Classify: +// Classify a parameter for the RISC-V 64 ABI. +// +// Parameters: +// comp - Compiler instance +// type - The type of the parameter +// structLayout - The layout of the struct. Expected to be non-null if +// varTypeIsStruct(type) is true. +// wellKnownParam - Well known type of the parameter (if it may affect its ABI classification) +// +// Returns: +// Classification information for the parameter. +// +ABIPassingInformation RiscV64Classifier::Classify(Compiler* comp, + var_types type, + ClassLayout* structLayout, + WellKnownArg /*wellKnownParam*/) +{ + assert(!m_info.IsVarArgs); // TODO: varargs currently not supported on RISC-V + + StructFloatFieldInfoFlags flags = STRUCT_NO_FLOAT_FIELD; + unsigned intFields = 0, floatFields = 0; + unsigned passedSize; + + if (varTypeIsStruct(type)) + { + passedSize = structLayout->GetSize(); + if (passedSize > MAX_PASS_MULTIREG_BYTES) + { + passedSize = TARGET_POINTER_SIZE; // pass by reference + } + else if (!structLayout->IsBlockLayout()) + { + flags = (StructFloatFieldInfoFlags)comp->info.compCompHnd->getRISCV64PassStructInRegisterFlags( + structLayout->GetClassHandle()); + + if ((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0) + { + floatFields = 1; + } + else if ((flags & STRUCT_FLOAT_FIELD_ONLY_TWO) != 0) + { + floatFields = 2; + } + else if (flags != STRUCT_NO_FLOAT_FIELD) + { + assert((flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_SECOND)) != 0); + floatFields = 1; + intFields = 1; + } + } + } + else + { + assert(genTypeSize(type) <= TARGET_POINTER_SIZE); + + if (varTypeIsFloating(type)) + floatFields = 1; + + passedSize = genTypeSize(type); + } + + assert((floatFields > 0) || (intFields == 0)); + + auto PassSlot = [this](bool inFloatReg, unsigned offset, unsigned size) -> ABIPassingSegment { + assert(size > 0); + assert(size <= TARGET_POINTER_SIZE); + if (inFloatReg) + { + return ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), offset, size); + } + else if (m_intRegs.Count() > 0) + { + return ABIPassingSegment::InRegister(m_intRegs.Dequeue(), offset, size); + } + else + { + assert((m_stackArgSize % TARGET_POINTER_SIZE) == 0); + ABIPassingSegment seg = ABIPassingSegment::OnStack(m_stackArgSize, offset, size); + m_stackArgSize += TARGET_POINTER_SIZE; + return seg; + } + }; + + if ((floatFields > 0) && (m_floatRegs.Count() >= floatFields) && (m_intRegs.Count() >= intFields)) + { + // Hardware floating-point calling convention + if ((floatFields == 1) && (intFields == 0)) + { + if (flags == STRUCT_NO_FLOAT_FIELD) + assert(varTypeIsFloating(type)); // standalone floating-point real + else + assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0); // struct containing just one FP real + + return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), 0, + passedSize)); + } + else + { + assert(varTypeIsStruct(type)); + assert((floatFields + intFields) == 2); + assert(flags != STRUCT_NO_FLOAT_FIELD); + assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) == 0); + + unsigned firstSize = ((flags & STRUCT_FIRST_FIELD_SIZE_IS8) != 0) ? 8 : 4; + unsigned secondSize = ((flags & STRUCT_SECOND_FIELD_SIZE_IS8) != 0) ? 8 : 4; + unsigned offset = max(firstSize, secondSize); // TODO: cover empty fields and custom offsets / alignments + + bool isFirstFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_FIRST)) != 0; + bool isSecondFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_SECOND)) != 0; + assert(isFirstFloat || isSecondFloat); + + return {2, new (comp, CMK_ABI) ABIPassingSegment[]{PassSlot(isFirstFloat, 0, firstSize), + PassSlot(isSecondFloat, offset, secondSize)}}; + } + } + else + { + // Integer calling convention + if (passedSize <= TARGET_POINTER_SIZE) + { + return ABIPassingInformation::FromSegment(comp, PassSlot(false, 0, passedSize)); + } + else + { + assert(varTypeIsStruct(type)); + return {2, new (comp, CMK_ABI) + ABIPassingSegment[]{PassSlot(false, 0, TARGET_POINTER_SIZE), + PassSlot(false, TARGET_POINTER_SIZE, passedSize - TARGET_POINTER_SIZE)}}; + } + } + + unreached(); +} + #endif // TARGET_RISCV64 diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 1fd85a96840d9b..0808d9d9711178 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -13169,10 +13169,101 @@ bool Compiler::fgValueNumberHelperCall(GenTreeCall* call) // InvalidCastExc for these is set in VNForCast break; + case CORINFO_HELP_READYTORUN_CHKCAST: + { + // This helper casts to a class determined by the entry point. + ssize_t addrValue = (ssize_t)call->gtEntryPoint.addr; + ValueNum callAddrVN = vnStore->VNForHandle(addrValue, GTF_ICON_FTN_ADDR); + vnpExc = vnStore->VNPExcSetSingleton( + vnStore->VNPairForFunc(TYP_REF, VNF_R2RInvalidCastExc, + vnStore->VNPNormalPair( + call->gtArgs.GetUserArgByIndex(0)->GetNode()->gtVNPair), + ValueNumPair(callAddrVN, callAddrVN))); + break; + } + + case CORINFO_HELP_GETSHARED_GCSTATIC_BASE: + case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE: + case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE: + case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE: + case CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_DYNAMICCLASS: + // These all take (Module*, class ID) as parameters. + // + // Strictly speaking the exact possible exception thrown by the + // static constructor depends on heap state too, but given that + // the constructor is only invoked once we can model that for + // the same class the same exceptions are thrown. Downstream + // code like CSE/copy prop that makes use VNs innately need to + // establish some form of dominance around the individual trees + // that makes this ok. + // + vnpExc = vnStore->VNPExcSetSingleton( + vnStore->VNPairForFunc(TYP_REF, VNF_ClassInitExc, + vnStore->VNPNormalPair( + call->gtArgs.GetUserArgByIndex(0)->GetNode()->gtVNPair), + vnStore->VNPNormalPair( + call->gtArgs.GetUserArgByIndex(1)->GetNode()->gtVNPair))); + break; + +#ifdef FEATURE_READYTORUN + case CORINFO_HELP_READYTORUN_GCSTATIC_BASE: + case CORINFO_HELP_READYTORUN_NONGCSTATIC_BASE: + case CORINFO_HELP_READYTORUN_THREADSTATIC_BASE: + case CORINFO_HELP_READYTORUN_NONGCTHREADSTATIC_BASE: + case CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE: + { + // These are uniquely determined by the entry point. + ssize_t addrValue = (ssize_t)call->gtEntryPoint.addr; + ValueNum callAddrVN = vnStore->VNForHandle(addrValue, GTF_ICON_FTN_ADDR); + vnpExc = vnStore->VNPExcSetSingleton( + vnStore->VNPairForFunc(TYP_REF, VNF_R2RClassInitExc, ValueNumPair(callAddrVN, callAddrVN))); + break; + } +#endif + + case CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_GCSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_NONGCSTATIC_BASE: + // These take class handles as parameters. + vnpExc = vnStore->VNPExcSetSingleton( + vnStore->VNPairForFunc(TYP_REF, VNF_ClassInitGenericExc, + vnStore->VNPNormalPair( + call->gtArgs.GetUserArgByIndex(0)->GetNode()->gtVNPair))); + break; + + case CORINFO_HELP_DIV: + case CORINFO_HELP_LDIV: + vnpExc = fgValueNumberDivisionExceptions(GT_DIV, call->gtArgs.GetUserArgByIndex(0)->GetNode(), + call->gtArgs.GetUserArgByIndex(1)->GetNode()); + break; + case CORINFO_HELP_MOD: + case CORINFO_HELP_LMOD: + vnpExc = fgValueNumberDivisionExceptions(GT_MOD, call->gtArgs.GetUserArgByIndex(0)->GetNode(), + call->gtArgs.GetUserArgByIndex(1)->GetNode()); + break; + case CORINFO_HELP_UDIV: + case CORINFO_HELP_ULDIV: + vnpExc = fgValueNumberDivisionExceptions(GT_UDIV, call->gtArgs.GetUserArgByIndex(0)->GetNode(), + call->gtArgs.GetUserArgByIndex(1)->GetNode()); + break; + case CORINFO_HELP_UMOD: + case CORINFO_HELP_ULMOD: + vnpExc = fgValueNumberDivisionExceptions(GT_UMOD, call->gtArgs.GetUserArgByIndex(0)->GetNode(), + call->gtArgs.GetUserArgByIndex(1)->GetNode()); + break; + default: // Setup vnpExc with the information that multiple different exceptions - // could be generated by this helper - vnpExc = vnStore->VNPExcSetSingleton(vnStore->VNPairForFunc(TYP_REF, VNF_HelperMultipleExc)); + // could be generated by this helper, in an opaque way + vnpExc = + vnStore->VNPExcSetSingleton(vnStore->VNPairForFunc(TYP_REF, VNF_HelperOpaqueExc, + vnStore->VNPairForExpr(compCurBB, TYP_I_IMPL))); + break; } } @@ -13350,10 +13441,31 @@ void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree // void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) { - genTreeOps oper = tree->OperGet(); + ValueNumPair exceptions = fgValueNumberDivisionExceptions(tree->OperGet(), tree->gtGetOp1(), tree->gtGetOp2()); + // Unpack, Norm,Exc for the tree's VN + ValueNumPair vnpTreeNorm; + ValueNumPair vnpTreeExc; + vnStore->VNPUnpackExc(tree->gtVNPair, &vnpTreeNorm, &vnpTreeExc); + ValueNumPair newExcSet = vnStore->VNPExcSetUnion(vnpTreeExc, exceptions); + tree->gtVNPair = vnStore->VNPWithExc(vnpTreeNorm, newExcSet); +} + +//-------------------------------------------------------------------------------- +// fgValueNumberDivisionExceptions +// Compute exception set for a division operation +// +// Arguments: +// oper - Division operation (signed/unsigned division/modulo) +// dividend - Tree representing dividend +// divisor - Tree representing divisor +// +// Return Value: +// VNP representing exception set +// +ValueNumPair Compiler::fgValueNumberDivisionExceptions(genTreeOps oper, GenTree* dividend, GenTree* divisor) +{ // A Divide By Zero exception may be possible. - // The divisor is held in tree->AsOp()->gtOp2 // bool isUnsignedOper = (oper == GT_UDIV) || (oper == GT_UMOD); bool needDivideByZeroExcLib = true; @@ -13362,19 +13474,19 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) bool needArithmeticExcCon = !isUnsignedOper; // Determine if we have a 32-bit or 64-bit divide operation - var_types typ = genActualType(tree->TypeGet()); + var_types typ = genActualType(dividend); assert((typ == TYP_INT) || (typ == TYP_LONG)); - // Retrieve the Norm VN for op2 to use it for the DivideByZeroExc - ValueNumPair vnpOp2Norm = vnStore->VNPNormalPair(tree->AsOp()->gtOp2->gtVNPair); - ValueNum vnOp2NormLib = vnpOp2Norm.GetLiberal(); - ValueNum vnOp2NormCon = vnpOp2Norm.GetConservative(); + // Retrieve the Norm VN for divisor to use it for the DivideByZeroExc + ValueNumPair vnpDisivorNorm = vnStore->VNPNormalPair(divisor->gtVNPair); + ValueNum vnDivisorNormLib = vnpDisivorNorm.GetLiberal(); + ValueNum vnDivisorNormCon = vnpDisivorNorm.GetConservative(); if (typ == TYP_INT) { - if (vnStore->IsVNConstant(vnOp2NormLib)) + if (vnStore->IsVNConstant(vnDivisorNormLib)) { - INT32 kVal = vnStore->ConstantValue(vnOp2NormLib); + INT32 kVal = vnStore->ConstantValue(vnDivisorNormLib); if (kVal != 0) { needDivideByZeroExcLib = false; @@ -13384,9 +13496,9 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) needArithmeticExcLib = false; } } - if (vnStore->IsVNConstant(vnOp2NormCon)) + if (vnStore->IsVNConstant(vnDivisorNormCon)) { - INT32 kVal = vnStore->ConstantValue(vnOp2NormCon); + INT32 kVal = vnStore->ConstantValue(vnDivisorNormCon); if (kVal != 0) { needDivideByZeroExcCon = false; @@ -13399,9 +13511,9 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) } else // (typ == TYP_LONG) { - if (vnStore->IsVNConstant(vnOp2NormLib)) + if (vnStore->IsVNConstant(vnDivisorNormLib)) { - INT64 kVal = vnStore->ConstantValue(vnOp2NormLib); + INT64 kVal = vnStore->ConstantValue(vnDivisorNormLib); if (kVal != 0) { needDivideByZeroExcLib = false; @@ -13411,9 +13523,9 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) needArithmeticExcLib = false; } } - if (vnStore->IsVNConstant(vnOp2NormCon)) + if (vnStore->IsVNConstant(vnDivisorNormCon)) { - INT64 kVal = vnStore->ConstantValue(vnOp2NormCon); + INT64 kVal = vnStore->ConstantValue(vnDivisorNormCon); if (kVal != 0) { needDivideByZeroExcCon = false; @@ -13426,26 +13538,26 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) } // Retrieve the Norm VN for op1 to use it for the ArithmeticExc - ValueNumPair vnpOp1Norm = vnStore->VNPNormalPair(tree->AsOp()->gtOp1->gtVNPair); - ValueNum vnOp1NormLib = vnpOp1Norm.GetLiberal(); - ValueNum vnOp1NormCon = vnpOp1Norm.GetConservative(); + ValueNumPair vnpDividendNorm = vnStore->VNPNormalPair(dividend->gtVNPair); + ValueNum vnDividendNormLib = vnpDividendNorm.GetLiberal(); + ValueNum vnDividendNormCon = vnpDividendNorm.GetConservative(); if (needArithmeticExcLib || needArithmeticExcCon) { if (typ == TYP_INT) { - if (vnStore->IsVNConstant(vnOp1NormLib)) + if (vnStore->IsVNConstant(vnDividendNormLib)) { - INT32 kVal = vnStore->ConstantValue(vnOp1NormLib); + INT32 kVal = vnStore->ConstantValue(vnDividendNormLib); if (!isUnsignedOper && (kVal != INT32_MIN)) { needArithmeticExcLib = false; } } - if (vnStore->IsVNConstant(vnOp1NormCon)) + if (vnStore->IsVNConstant(vnDividendNormCon)) { - INT32 kVal = vnStore->ConstantValue(vnOp1NormCon); + INT32 kVal = vnStore->ConstantValue(vnDividendNormCon); if (!isUnsignedOper && (kVal != INT32_MIN)) { @@ -13455,18 +13567,18 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) } else // (typ == TYP_LONG) { - if (vnStore->IsVNConstant(vnOp1NormLib)) + if (vnStore->IsVNConstant(vnDividendNormLib)) { - INT64 kVal = vnStore->ConstantValue(vnOp1NormLib); + INT64 kVal = vnStore->ConstantValue(vnDividendNormLib); if (!isUnsignedOper && (kVal != INT64_MIN)) { needArithmeticExcLib = false; } } - if (vnStore->IsVNConstant(vnOp1NormCon)) + if (vnStore->IsVNConstant(vnDividendNormCon)) { - INT64 kVal = vnStore->ConstantValue(vnOp1NormCon); + INT64 kVal = vnStore->ConstantValue(vnDividendNormCon); if (!isUnsignedOper && (kVal != INT64_MIN)) { @@ -13477,41 +13589,31 @@ void Compiler::fgValueNumberAddExceptionSetForDivision(GenTree* tree) } // Unpack, Norm,Exc for the tree's VN - ValueNumPair vnpTreeNorm; - ValueNumPair vnpTreeExc; ValueNumPair vnpDivZeroExc = ValueNumStore::VNPForEmptyExcSet(); ValueNumPair vnpArithmExc = ValueNumStore::VNPForEmptyExcSet(); - vnStore->VNPUnpackExc(tree->gtVNPair, &vnpTreeNorm, &vnpTreeExc); - if (needDivideByZeroExcLib) { vnpDivZeroExc.SetLiberal( - vnStore->VNExcSetSingleton(vnStore->VNForFunc(TYP_REF, VNF_DivideByZeroExc, vnOp2NormLib))); + vnStore->VNExcSetSingleton(vnStore->VNForFunc(TYP_REF, VNF_DivideByZeroExc, vnDivisorNormLib))); } if (needDivideByZeroExcCon) { vnpDivZeroExc.SetConservative( - vnStore->VNExcSetSingleton(vnStore->VNForFunc(TYP_REF, VNF_DivideByZeroExc, vnOp2NormCon))); + vnStore->VNExcSetSingleton(vnStore->VNForFunc(TYP_REF, VNF_DivideByZeroExc, vnDivisorNormCon))); } if (needArithmeticExcLib) { vnpArithmExc.SetLiberal(vnStore->VNExcSetSingleton( - vnStore->VNForFuncNoFolding(TYP_REF, VNF_ArithmeticExc, vnOp1NormLib, vnOp2NormLib))); + vnStore->VNForFuncNoFolding(TYP_REF, VNF_ArithmeticExc, vnDividendNormLib, vnDivisorNormLib))); } if (needArithmeticExcCon) { vnpArithmExc.SetConservative(vnStore->VNExcSetSingleton( - vnStore->VNForFuncNoFolding(TYP_REF, VNF_ArithmeticExc, vnOp1NormLib, vnOp2NormCon))); + vnStore->VNForFuncNoFolding(TYP_REF, VNF_ArithmeticExc, vnDividendNormLib, vnDivisorNormCon))); } - // Combine vnpDivZeroExc with the exception set of tree - ValueNumPair newExcSet = vnStore->VNPExcSetUnion(vnpTreeExc, vnpDivZeroExc); - // Combine vnpArithmExc with the newExcSet - newExcSet = vnStore->VNPExcSetUnion(newExcSet, vnpArithmExc); - - // Updated VN for tree, it now includes DivideByZeroExc and/or ArithmeticExc - tree->gtVNPair = vnStore->VNPWithExc(vnpTreeNorm, newExcSet); + return vnStore->VNPExcSetUnion(vnpDivZeroExc, vnpArithmExc); } //-------------------------------------------------------------------------------- diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index 128935847defdc..392cd58611e6e1 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -65,9 +65,14 @@ ValueNumFuncDef(ConvOverflowExc, 2, false, false, false, false) // Cast conve // - (shifted left one bit; low bit encode whether source is unsigned.) ValueNumFuncDef(DivideByZeroExc, 1, false, false, false, false) // Division by zero check. Args: 0: divisor value, throws when it is zero ValueNumFuncDef(IndexOutOfRangeExc, 2, false, false, false, false) // Array bounds check, Args: 0: array length; 1: index value, throws when the bounds check fails. -ValueNumFuncDef(InvalidCastExc, 2, false, false, false, false) // CastClass check, Args: 0: ref value being cast; 1: handle of type being cast to, throws when the cast fails. +ValueNumFuncDef(InvalidCastExc, 2, false, false, false, false) // CastClass check, Args: 0: ref value being cast; 1: handle of type being cast to +ValueNumFuncDef(R2RInvalidCastExc, 2, false, false, false, false) // CastClass check, Args: 0: ref value being cast; 1: entry point of R2R cast helper ValueNumFuncDef(NewArrOverflowExc, 1, false, false, false, false) // Raises Integer overflow when Arg 0 is negative -ValueNumFuncDef(HelperMultipleExc, 0, false, false, false, false) // Represents one or more different exceptions that could be thrown by a Jit Helper method +ValueNumFuncDef(ClassInitExc, 2, false, false, false, false) // Represents exceptions thrown by static constructor for class. Args: 0: VN of module, 1: VN of class ID +ValueNumFuncDef(R2RClassInitExc, 1, false, false, false, false) // Represents exceptions thrown by static constructor for class. Args: 0: VN of R2R entry point +ValueNumFuncDef(ClassInitGenericExc, 2, false, false, false, false)// Represents exceptions thrown by static constructor for generic class. Args: 0: VN of class handle +ValueNumFuncDef(HelperOpaqueExc, 1, false, false, false, false) // Represents opaque exceptions could be thrown by a JIT helper. + // Args: 0: Input to helper that uniquely determines exceptions thrown. ValueNumFuncDef(Abs, 1, false, false, false, false) ValueNumFuncDef(Acos, 1, false, false, false, false) diff --git a/src/coreclr/nativeaot/Directory.Build.props b/src/coreclr/nativeaot/Directory.Build.props index 42e6f968e0e1e9..d3277aa58ece73 100644 --- a/src/coreclr/nativeaot/Directory.Build.props +++ b/src/coreclr/nativeaot/Directory.Build.props @@ -21,6 +21,7 @@ $(TargetArchitecture) arm + AnyCPU true false @@ -78,20 +79,15 @@ - x64 - false TARGET_64BIT;TARGET_AMD64;$(DefineConstants) - x86 TARGET_32BIT;TARGET_X86;$(DefineConstants) - arm TARGET_32BIT;TARGET_ARM;$(DefineConstants) - AnyCPU TARGET_64BIT;TARGET_ARM64;$(DefineConstants) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 9d09ab7e105928..770514a671adfc 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -55,11 +55,11 @@ CP0001 - T:Internal.Metadata.NativeFormat.ConstantBoxedEnumValue + T:Internal.Metadata.NativeFormat.ConstantEnumValue CP0001 - T:Internal.Metadata.NativeFormat.ConstantBoxedEnumValueHandle + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle CP0001 diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/MetadataReaderExtensions.NativeFormat.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/MetadataReaderExtensions.NativeFormat.cs index eb4289695020ec..e5f82238f0b51e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/MetadataReaderExtensions.NativeFormat.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/MetadataReaderExtensions.NativeFormat.cs @@ -141,9 +141,9 @@ public static bool IsConstructor(ref Method method, MetadataReader reader) return nameHandle.StringEquals(ConstructorInfo.ConstructorName, reader) || nameHandle.StringEquals(ConstructorInfo.TypeConstructorName, reader); } - private static Exception ParseBoxedEnumConstantValue(this ConstantBoxedEnumValueHandle handle, MetadataReader reader, out object value) + private static Exception ParseEnumConstantValue(this ConstantEnumValueHandle handle, MetadataReader reader, out object value) { - ConstantBoxedEnumValue record = handle.GetConstantBoxedEnumValue(reader); + ConstantEnumValue record = handle.GetConstantEnumValue(reader); Exception? exception = null; Type? enumType = record.Type.TryResolve(reader, new TypeContext(null, null), ref exception)?.ToType(); @@ -317,9 +317,9 @@ public static Exception TryParseConstantValue(this Handle handle, MetadataReader case HandleType.ConstantReferenceValue: value = null; return null; - case HandleType.ConstantBoxedEnumValue: + case HandleType.ConstantEnumValue: { - return handle.ToConstantBoxedEnumValueHandle(reader).ParseBoxedEnumConstantValue(reader, out value); + return handle.ToConstantEnumValueHandle(reader).ParseEnumConstantValue(reader, out value); } default: { diff --git a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc index 67c5f9d48fec1d..df0e680abdd4fb 100644 --- a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc +++ b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc @@ -14,7 +14,6 @@ BEGIN MSG_FOR_URT_HR(HOST_E_INVALIDOPERATION) "Invalid operation." MSG_FOR_URT_HR(HOST_E_CLRNOTAVAILABLE) "CLR has been disabled due to unrecoverable error." MSG_FOR_URT_HR(FUSION_E_REF_DEF_MISMATCH) "The located assembly's manifest definition does not match the assembly reference." - MSG_FOR_URT_HR(FUSION_E_PRIVATE_ASM_DISALLOWED) "A strongly-named assembly is required." MSG_FOR_URT_HR(FUSION_E_INVALID_NAME) "The given assembly name was invalid." MSG_FOR_URT_HR(FUSION_E_APP_DOMAIN_LOCKED) "The requested assembly version conflicts with what is already bound in the app domain or specified in the manifest." MSG_FOR_URT_HR(COR_E_LOADING_REFERENCE_ASSEMBLY) "Reference assemblies cannot be loaded for execution." diff --git a/src/coreclr/pal/prebuilt/inc/corerror.h b/src/coreclr/pal/prebuilt/inc/corerror.h index 12d3490aaf09d8..0b475679050fdd 100644 --- a/src/coreclr/pal/prebuilt/inc/corerror.h +++ b/src/coreclr/pal/prebuilt/inc/corerror.h @@ -41,7 +41,6 @@ #define HOST_E_INVALIDOPERATION EMAKEHR(0x1022) #define HOST_E_CLRNOTAVAILABLE EMAKEHR(0x1023) #define FUSION_E_REF_DEF_MISMATCH EMAKEHR(0x1040) -#define FUSION_E_PRIVATE_ASM_DISALLOWED EMAKEHR(0x1044) #define FUSION_E_INVALID_NAME EMAKEHR(0x1047) #define FUSION_E_APP_DOMAIN_LOCKED EMAKEHR(0x1053) #define COR_E_LOADING_REFERENCE_ASSEMBLY EMAKEHR(0x1058) diff --git a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/Generator/SchemaDef.cs b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/Generator/SchemaDef.cs index daea57e6e4a1b2..371b9488a7d396 100644 --- a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/Generator/SchemaDef.cs +++ b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/Generator/SchemaDef.cs @@ -315,6 +315,13 @@ from primitiveType in PrimitiveTypes members: new MemberDef[] { new MemberDef(name: "Value", typeName: "string") } + ), + new RecordDef( + name: "ConstantEnumValue", + members: new MemberDef[] { + new MemberDef("Value", EnumConstantValue, MemberDefFlags.RecordRef | MemberDefFlags.Child), + new MemberDef("Type", TypeDefOrRefOrSpec, MemberDefFlags.RecordRef) + } ) } ) @@ -630,13 +637,6 @@ from primitiveType in PrimitiveTypes new MemberDef("Value", TypeDefOrRefOrSpecOrConstant, MemberDefFlags.RecordRef), } ), - new RecordDef( - name: "ConstantBoxedEnumValue", - members: new MemberDef[] { - new MemberDef("Value", EnumConstantValue, MemberDefFlags.RecordRef | MemberDefFlags.Child), - new MemberDef("Type", TypeDefOrRefOrSpec, MemberDefFlags.RecordRef) - } - ), new RecordDef( name: "GenericParameter", members: new MemberDef[] { diff --git a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/MdBinaryReaderGen.cs b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/MdBinaryReaderGen.cs index bbac55d7083047..6f0d97a9c38834 100644 --- a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/MdBinaryReaderGen.cs +++ b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/MdBinaryReaderGen.cs @@ -300,15 +300,6 @@ public static uint Read(this NativeReader reader, uint offset, out ConstantBoole return offset; } // Read - public static uint Read(this NativeReader reader, uint offset, out ConstantBoxedEnumValueHandle handle) - { - uint value; - offset = reader.DecodeUnsigned(offset, out value); - handle = new ConstantBoxedEnumValueHandle((int)value); - handle._Validate(); - return offset; - } // Read - public static uint Read(this NativeReader reader, uint offset, out ConstantByteArrayHandle handle) { uint value; @@ -372,6 +363,15 @@ public static uint Read(this NativeReader reader, uint offset, out ConstantEnumA return offset; } // Read + public static uint Read(this NativeReader reader, uint offset, out ConstantEnumValueHandle handle) + { + uint value; + offset = reader.DecodeUnsigned(offset, out value); + handle = new ConstantEnumValueHandle((int)value); + handle._Validate(); + return offset; + } // Read + public static uint Read(this NativeReader reader, uint offset, out ConstantHandleArrayHandle handle) { uint value; diff --git a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderCommonGen.cs b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderCommonGen.cs index bb7fefd7816ced..a9359f373de576 100644 --- a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderCommonGen.cs +++ b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderCommonGen.cs @@ -94,14 +94,14 @@ public enum HandleType : byte ByReferenceSignature = 0x2, ConstantBooleanArray = 0x3, ConstantBooleanValue = 0x4, - ConstantBoxedEnumValue = 0x5, - ConstantByteArray = 0x6, - ConstantByteValue = 0x7, - ConstantCharArray = 0x8, - ConstantCharValue = 0x9, - ConstantDoubleArray = 0xa, - ConstantDoubleValue = 0xb, - ConstantEnumArray = 0xc, + ConstantByteArray = 0x5, + ConstantByteValue = 0x6, + ConstantCharArray = 0x7, + ConstantCharValue = 0x8, + ConstantDoubleArray = 0x9, + ConstantDoubleValue = 0xa, + ConstantEnumArray = 0xb, + ConstantEnumValue = 0xc, ConstantHandleArray = 0xd, ConstantInt16Array = 0xe, ConstantInt16Value = 0xf, diff --git a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderGen.cs b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderGen.cs index 7bf0d91f44e556..3fe7c55c0c2b9c 100644 --- a/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderGen.cs +++ b/src/coreclr/tools/Common/Internal/Metadata/NativeFormat/NativeFormatReaderGen.cs @@ -501,130 +501,6 @@ public override string ToString() } // ToString } // ConstantBooleanValueHandle -#if SYSTEM_PRIVATE_CORELIB - [CLSCompliant(false)] -#endif - public partial struct ConstantBoxedEnumValue - { - internal MetadataReader _reader; - internal ConstantBoxedEnumValueHandle _handle; - - public ConstantBoxedEnumValueHandle Handle - { - get - { - return _handle; - } - } // Handle - /// One of: ConstantByteValue, ConstantSByteValue, ConstantInt16Value, ConstantUInt16Value, ConstantInt32Value, ConstantUInt32Value, ConstantInt64Value, ConstantUInt64Value - - public Handle Value - { - get - { - return _value; - } - } // Value - - internal Handle _value; - /// One of: TypeDefinition, TypeReference, TypeSpecification - - public Handle Type - { - get - { - return _type; - } - } // Type - - internal Handle _type; - } // ConstantBoxedEnumValue - -#if SYSTEM_PRIVATE_CORELIB - [CLSCompliant(false)] -#endif - public partial struct ConstantBoxedEnumValueHandle - { - public override bool Equals(object obj) - { - if (obj is ConstantBoxedEnumValueHandle) - return _value == ((ConstantBoxedEnumValueHandle)obj)._value; - else if (obj is Handle) - return _value == ((Handle)obj)._value; - else - return false; - } // Equals - - public bool Equals(ConstantBoxedEnumValueHandle handle) - { - return _value == handle._value; - } // Equals - - public bool Equals(Handle handle) - { - return _value == handle._value; - } // Equals - - public override int GetHashCode() - { - return (int)_value; - } // GetHashCode - - internal int _value; - - internal ConstantBoxedEnumValueHandle(Handle handle) : this(handle._value) - { - } - - internal ConstantBoxedEnumValueHandle(int value) - { - HandleType hType = (HandleType)(value >> 24); - Debug.Assert(hType == 0 || hType == HandleType.ConstantBoxedEnumValue || hType == HandleType.Null); - _value = (value & 0x00FFFFFF) | (((int)HandleType.ConstantBoxedEnumValue) << 24); - _Validate(); - } - - public static implicit operator Handle(ConstantBoxedEnumValueHandle handle) - { - return new Handle(handle._value); - } // Handle - - internal int Offset - { - get - { - return (this._value & 0x00FFFFFF); - } - } // Offset - - public ConstantBoxedEnumValue GetConstantBoxedEnumValue(MetadataReader reader) - { - return reader.GetConstantBoxedEnumValue(this); - } // GetConstantBoxedEnumValue - - public bool IsNull(MetadataReader reader) - { - return reader.IsNull(this); - } // IsNull - - public Handle ToHandle(MetadataReader reader) - { - return reader.ToHandle(this); - } // ToHandle - - [System.Diagnostics.Conditional("DEBUG")] - internal void _Validate() - { - if ((HandleType)((_value & 0xFF000000) >> 24) != HandleType.ConstantBoxedEnumValue) - throw new ArgumentException(); - } // _Validate - - public override string ToString() - { - return string.Format("{0:X8}", _value); - } // ToString - } // ConstantBoxedEnumValueHandle - #if SYSTEM_PRIVATE_CORELIB [CLSCompliant(false)] #endif @@ -1419,6 +1295,128 @@ public override string ToString() } // ToString } // ConstantEnumArrayHandle +#if SYSTEM_PRIVATE_CORELIB + [CLSCompliant(false)] +#endif + public partial struct ConstantEnumValue + { + internal MetadataReader _reader; + internal ConstantEnumValueHandle _handle; + + public ConstantEnumValueHandle Handle + { + get + { + return _handle; + } + } // Handle + + public Handle Value + { + get + { + return _value; + } + } // Value + + internal Handle _value; + + public Handle Type + { + get + { + return _type; + } + } // Type + + internal Handle _type; + } // ConstantEnumValue + +#if SYSTEM_PRIVATE_CORELIB + [CLSCompliant(false)] +#endif + public partial struct ConstantEnumValueHandle + { + public override bool Equals(object obj) + { + if (obj is ConstantEnumValueHandle) + return _value == ((ConstantEnumValueHandle)obj)._value; + else if (obj is Handle) + return _value == ((Handle)obj)._value; + else + return false; + } // Equals + + public bool Equals(ConstantEnumValueHandle handle) + { + return _value == handle._value; + } // Equals + + public bool Equals(Handle handle) + { + return _value == handle._value; + } // Equals + + public override int GetHashCode() + { + return (int)_value; + } // GetHashCode + + internal int _value; + + internal ConstantEnumValueHandle(Handle handle) : this(handle._value) + { + } + + internal ConstantEnumValueHandle(int value) + { + HandleType hType = (HandleType)(value >> 24); + Debug.Assert(hType == 0 || hType == HandleType.ConstantEnumValue || hType == HandleType.Null); + _value = (value & 0x00FFFFFF) | (((int)HandleType.ConstantEnumValue) << 24); + _Validate(); + } + + public static implicit operator Handle(ConstantEnumValueHandle handle) + { + return new Handle(handle._value); + } // Handle + + internal int Offset + { + get + { + return (this._value & 0x00FFFFFF); + } + } // Offset + + public ConstantEnumValue GetConstantEnumValue(MetadataReader reader) + { + return reader.GetConstantEnumValue(this); + } // GetConstantEnumValue + + public bool IsNull(MetadataReader reader) + { + return reader.IsNull(this); + } // IsNull + + public Handle ToHandle(MetadataReader reader) + { + return reader.ToHandle(this); + } // ToHandle + + [System.Diagnostics.Conditional("DEBUG")] + internal void _Validate() + { + if ((HandleType)((_value & 0xFF000000) >> 24) != HandleType.ConstantEnumValue) + throw new ArgumentException(); + } // _Validate + + public override string ToString() + { + return string.Format("{0:X8}", _value); + } // ToString + } // ConstantEnumValueHandle + #if SYSTEM_PRIVATE_CORELIB [CLSCompliant(false)] #endif @@ -3676,7 +3674,7 @@ public Handle Constructor } // Constructor internal Handle _constructor; - /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value + /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantEnumValue, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value public HandleCollection FixedArguments { @@ -3982,7 +3980,7 @@ public FieldSignatureHandle Signature } // Signature internal FieldSignatureHandle _signature; - /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value + /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantEnumValue, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value public Handle DefaultValue { @@ -5487,7 +5485,7 @@ public Handle Type } // Type internal Handle _type; - /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value + /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantEnumValue, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value public Handle Value { @@ -5906,7 +5904,7 @@ public ConstantStringValueHandle Name } // Name internal ConstantStringValueHandle _name; - /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value + /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantEnumValue, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value public Handle DefaultValue { @@ -6182,7 +6180,7 @@ public MethodSemanticsHandleCollection MethodSemantics } // MethodSemantics internal MethodSemanticsHandleCollection _methodSemantics; - /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value + /// One of: TypeDefinition, TypeReference, TypeSpecification, ConstantBooleanArray, ConstantBooleanValue, ConstantByteArray, ConstantByteValue, ConstantCharArray, ConstantCharValue, ConstantDoubleArray, ConstantDoubleValue, ConstantEnumArray, ConstantEnumValue, ConstantHandleArray, ConstantInt16Array, ConstantInt16Value, ConstantInt32Array, ConstantInt32Value, ConstantInt64Array, ConstantInt64Value, ConstantReferenceValue, ConstantSByteArray, ConstantSByteValue, ConstantSingleArray, ConstantSingleValue, ConstantStringArray, ConstantStringValue, ConstantUInt16Array, ConstantUInt16Value, ConstantUInt32Array, ConstantUInt32Value, ConstantUInt64Array, ConstantUInt64Value public Handle DefaultValue { @@ -9833,11 +9831,6 @@ public ConstantBooleanValueHandle ToConstantBooleanValueHandle(MetadataReader re return new ConstantBooleanValueHandle(this); } // ToConstantBooleanValueHandle - public ConstantBoxedEnumValueHandle ToConstantBoxedEnumValueHandle(MetadataReader reader) - { - return new ConstantBoxedEnumValueHandle(this); - } // ToConstantBoxedEnumValueHandle - public ConstantByteArrayHandle ToConstantByteArrayHandle(MetadataReader reader) { return new ConstantByteArrayHandle(this); @@ -9873,6 +9866,11 @@ public ConstantEnumArrayHandle ToConstantEnumArrayHandle(MetadataReader reader) return new ConstantEnumArrayHandle(this); } // ToConstantEnumArrayHandle + public ConstantEnumValueHandle ToConstantEnumValueHandle(MetadataReader reader) + { + return new ConstantEnumValueHandle(this); + } // ToConstantEnumValueHandle + public ConstantHandleArrayHandle ToConstantHandleArrayHandle(MetadataReader reader) { return new ConstantHandleArrayHandle(this); @@ -10246,17 +10244,6 @@ public ConstantBooleanValue GetConstantBooleanValue(ConstantBooleanValueHandle h return record; } // GetConstantBooleanValue - public ConstantBoxedEnumValue GetConstantBoxedEnumValue(ConstantBoxedEnumValueHandle handle) - { - ConstantBoxedEnumValue record; - record._reader = this; - record._handle = handle; - var offset = (uint)handle.Offset; - offset = _streamReader.Read(offset, out record._value); - offset = _streamReader.Read(offset, out record._type); - return record; - } // GetConstantBoxedEnumValue - public ConstantByteArray GetConstantByteArray(ConstantByteArrayHandle handle) { ConstantByteArray record; @@ -10328,6 +10315,17 @@ public ConstantEnumArray GetConstantEnumArray(ConstantEnumArrayHandle handle) return record; } // GetConstantEnumArray + public ConstantEnumValue GetConstantEnumValue(ConstantEnumValueHandle handle) + { + ConstantEnumValue record; + record._reader = this; + record._handle = handle; + var offset = (uint)handle.Offset; + offset = _streamReader.Read(offset, out record._value); + offset = _streamReader.Read(offset, out record._type); + return record; + } // GetConstantEnumValue + public ConstantHandleArray GetConstantHandleArray(ConstantHandleArrayHandle handle) { ConstantHandleArray record; @@ -10952,11 +10950,6 @@ internal Handle ToHandle(ConstantBooleanValueHandle handle) return new Handle(handle._value); } // ToHandle - internal Handle ToHandle(ConstantBoxedEnumValueHandle handle) - { - return new Handle(handle._value); - } // ToHandle - internal Handle ToHandle(ConstantByteArrayHandle handle) { return new Handle(handle._value); @@ -10992,6 +10985,11 @@ internal Handle ToHandle(ConstantEnumArrayHandle handle) return new Handle(handle._value); } // ToHandle + internal Handle ToHandle(ConstantEnumValueHandle handle) + { + return new Handle(handle._value); + } // ToHandle + internal Handle ToHandle(ConstantHandleArrayHandle handle) { return new Handle(handle._value); @@ -11267,11 +11265,6 @@ internal ConstantBooleanValueHandle ToConstantBooleanValueHandle(Handle handle) return new ConstantBooleanValueHandle(handle._value); } // ToConstantBooleanValueHandle - internal ConstantBoxedEnumValueHandle ToConstantBoxedEnumValueHandle(Handle handle) - { - return new ConstantBoxedEnumValueHandle(handle._value); - } // ToConstantBoxedEnumValueHandle - internal ConstantByteArrayHandle ToConstantByteArrayHandle(Handle handle) { return new ConstantByteArrayHandle(handle._value); @@ -11307,6 +11300,11 @@ internal ConstantEnumArrayHandle ToConstantEnumArrayHandle(Handle handle) return new ConstantEnumArrayHandle(handle._value); } // ToConstantEnumArrayHandle + internal ConstantEnumValueHandle ToConstantEnumValueHandle(Handle handle) + { + return new ConstantEnumValueHandle(handle._value); + } // ToConstantEnumValueHandle + internal ConstantHandleArrayHandle ToConstantHandleArrayHandle(Handle handle) { return new ConstantHandleArrayHandle(handle._value); @@ -11582,11 +11580,6 @@ internal bool IsNull(ConstantBooleanValueHandle handle) return (handle._value & 0x00FFFFFF) == 0; } // IsNull - internal bool IsNull(ConstantBoxedEnumValueHandle handle) - { - return (handle._value & 0x00FFFFFF) == 0; - } // IsNull - internal bool IsNull(ConstantByteArrayHandle handle) { return (handle._value & 0x00FFFFFF) == 0; @@ -11622,6 +11615,11 @@ internal bool IsNull(ConstantEnumArrayHandle handle) return (handle._value & 0x00FFFFFF) == 0; } // IsNull + internal bool IsNull(ConstantEnumValueHandle handle) + { + return (handle._value & 0x00FFFFFF) == 0; + } // IsNull + internal bool IsNull(ConstantHandleArrayHandle handle) { return (handle._value & 0x00FFFFFF) == 0; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 9366e1aa65b6d3..7719b39aa7e852 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2941,6 +2941,14 @@ private bool isExactType(CORINFO_CLASS_STRUCT_* cls) return _compilation.IsEffectivelySealed(type); } + private TypeCompareState isNullableType(CORINFO_CLASS_STRUCT_* cls) + { + TypeDesc type = HandleToObject(cls); + Debug.Assert(!type.IsGenericParameter); + + return type.IsNullable ? TypeCompareState.Must : TypeCompareState.MustNot; + } + private TypeCompareState isEnum(CORINFO_CLASS_STRUCT_* cls, CORINFO_CLASS_STRUCT_** underlyingType) { Debug.Assert(cls != null); @@ -2951,11 +2959,7 @@ private TypeCompareState isEnum(CORINFO_CLASS_STRUCT_* cls, CORINFO_CLASS_STRUCT } TypeDesc type = HandleToObject(cls); - - if (type.IsGenericParameter) - { - return TypeCompareState.May; - } + Debug.Assert(!type.IsGenericParameter); if (type.IsEnum) { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 09ea54f989fde5..481c22864356c1 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -1210,6 +1210,21 @@ private static byte _isExactType(IntPtr thisHandle, IntPtr* ppException, CORINFO } } + [UnmanagedCallersOnly] + private static TypeCompareState _isNullableType(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* cls) + { + var _this = GetThis(thisHandle); + try + { + return _this.isNullableType(cls); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static TypeCompareState _isEnum(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* cls, CORINFO_CLASS_STRUCT_** underlyingType) { @@ -2579,7 +2594,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 174); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 175); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_notifyMethodInfoUsage; @@ -2662,99 +2677,100 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[78] = (delegate* unmanaged)&_compareTypesForEquality; callbacks[79] = (delegate* unmanaged)&_isMoreSpecificType; callbacks[80] = (delegate* unmanaged)&_isExactType; - callbacks[81] = (delegate* unmanaged)&_isEnum; - callbacks[82] = (delegate* unmanaged)&_getParentType; - callbacks[83] = (delegate* unmanaged)&_getChildType; - callbacks[84] = (delegate* unmanaged)&_isSDArray; - callbacks[85] = (delegate* unmanaged)&_getArrayRank; - callbacks[86] = (delegate* unmanaged)&_getArrayIntrinsicID; - callbacks[87] = (delegate* unmanaged)&_getArrayInitializationData; - callbacks[88] = (delegate* unmanaged)&_canAccessClass; - callbacks[89] = (delegate* unmanaged)&_printFieldName; - callbacks[90] = (delegate* unmanaged)&_getFieldClass; - callbacks[91] = (delegate* unmanaged)&_getFieldType; - callbacks[92] = (delegate* unmanaged)&_getFieldOffset; - callbacks[93] = (delegate* unmanaged)&_getFieldInfo; - callbacks[94] = (delegate* unmanaged)&_getThreadLocalFieldInfo; - callbacks[95] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; - callbacks[96] = (delegate* unmanaged)&_getThreadLocalStaticInfo_NativeAOT; - callbacks[97] = (delegate* unmanaged)&_isFieldStatic; - callbacks[98] = (delegate* unmanaged)&_getArrayOrStringLength; - callbacks[99] = (delegate* unmanaged)&_getBoundaries; - callbacks[100] = (delegate* unmanaged)&_setBoundaries; - callbacks[101] = (delegate* unmanaged)&_getVars; - callbacks[102] = (delegate* unmanaged)&_setVars; - callbacks[103] = (delegate* unmanaged)&_reportRichMappings; - callbacks[104] = (delegate* unmanaged)&_reportMetadata; - callbacks[105] = (delegate* unmanaged)&_allocateArray; - callbacks[106] = (delegate* unmanaged)&_freeArray; - callbacks[107] = (delegate* unmanaged)&_getArgNext; - callbacks[108] = (delegate* unmanaged)&_getArgType; - callbacks[109] = (delegate* unmanaged)&_getExactClasses; - callbacks[110] = (delegate* unmanaged)&_getArgClass; - callbacks[111] = (delegate* unmanaged)&_getHFAType; - callbacks[112] = (delegate* unmanaged)&_runWithErrorTrap; - callbacks[113] = (delegate* unmanaged)&_runWithSPMIErrorTrap; - callbacks[114] = (delegate* unmanaged)&_getEEInfo; - callbacks[115] = (delegate* unmanaged)&_getJitTimeLogFilename; - callbacks[116] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[117] = (delegate* unmanaged)&_printMethodName; - callbacks[118] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[119] = (delegate* unmanaged)&_getMethodHash; - callbacks[120] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[121] = (delegate* unmanaged)&_getSwiftLowering; - callbacks[122] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; - callbacks[123] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; - callbacks[124] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[125] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[126] = (delegate* unmanaged)&_getHelperFtn; - callbacks[127] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[128] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[129] = (delegate* unmanaged)&_getMethodSync; - callbacks[130] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[131] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[132] = (delegate* unmanaged)&_embedClassHandle; - callbacks[133] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[134] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[135] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[136] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[137] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[138] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[139] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; - callbacks[140] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[141] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[142] = (delegate* unmanaged)&_getCallInfo; - callbacks[143] = (delegate* unmanaged)&_getClassDomainID; - callbacks[144] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[145] = (delegate* unmanaged)&_getObjectContent; - callbacks[146] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[147] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[148] = (delegate* unmanaged)&_canGetVarArgsHandle; - callbacks[149] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[150] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[151] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[152] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[153] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[154] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[155] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[156] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[157] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[158] = (delegate* unmanaged)&_allocMem; - callbacks[159] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[160] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[161] = (delegate* unmanaged)&_allocGCInfo; - callbacks[162] = (delegate* unmanaged)&_setEHcount; - callbacks[163] = (delegate* unmanaged)&_setEHinfo; - callbacks[164] = (delegate* unmanaged)&_logMsg; - callbacks[165] = (delegate* unmanaged)&_doAssert; - callbacks[166] = (delegate* unmanaged)&_reportFatalError; - callbacks[167] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[168] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[169] = (delegate* unmanaged)&_recordCallSite; - callbacks[170] = (delegate* unmanaged)&_recordRelocation; - callbacks[171] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[172] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[173] = (delegate* unmanaged)&_getJitFlags; + callbacks[81] = (delegate* unmanaged)&_isNullableType; + callbacks[82] = (delegate* unmanaged)&_isEnum; + callbacks[83] = (delegate* unmanaged)&_getParentType; + callbacks[84] = (delegate* unmanaged)&_getChildType; + callbacks[85] = (delegate* unmanaged)&_isSDArray; + callbacks[86] = (delegate* unmanaged)&_getArrayRank; + callbacks[87] = (delegate* unmanaged)&_getArrayIntrinsicID; + callbacks[88] = (delegate* unmanaged)&_getArrayInitializationData; + callbacks[89] = (delegate* unmanaged)&_canAccessClass; + callbacks[90] = (delegate* unmanaged)&_printFieldName; + callbacks[91] = (delegate* unmanaged)&_getFieldClass; + callbacks[92] = (delegate* unmanaged)&_getFieldType; + callbacks[93] = (delegate* unmanaged)&_getFieldOffset; + callbacks[94] = (delegate* unmanaged)&_getFieldInfo; + callbacks[95] = (delegate* unmanaged)&_getThreadLocalFieldInfo; + callbacks[96] = (delegate* unmanaged)&_getThreadLocalStaticBlocksInfo; + callbacks[97] = (delegate* unmanaged)&_getThreadLocalStaticInfo_NativeAOT; + callbacks[98] = (delegate* unmanaged)&_isFieldStatic; + callbacks[99] = (delegate* unmanaged)&_getArrayOrStringLength; + callbacks[100] = (delegate* unmanaged)&_getBoundaries; + callbacks[101] = (delegate* unmanaged)&_setBoundaries; + callbacks[102] = (delegate* unmanaged)&_getVars; + callbacks[103] = (delegate* unmanaged)&_setVars; + callbacks[104] = (delegate* unmanaged)&_reportRichMappings; + callbacks[105] = (delegate* unmanaged)&_reportMetadata; + callbacks[106] = (delegate* unmanaged)&_allocateArray; + callbacks[107] = (delegate* unmanaged)&_freeArray; + callbacks[108] = (delegate* unmanaged)&_getArgNext; + callbacks[109] = (delegate* unmanaged)&_getArgType; + callbacks[110] = (delegate* unmanaged)&_getExactClasses; + callbacks[111] = (delegate* unmanaged)&_getArgClass; + callbacks[112] = (delegate* unmanaged)&_getHFAType; + callbacks[113] = (delegate* unmanaged)&_runWithErrorTrap; + callbacks[114] = (delegate* unmanaged)&_runWithSPMIErrorTrap; + callbacks[115] = (delegate* unmanaged)&_getEEInfo; + callbacks[116] = (delegate* unmanaged)&_getJitTimeLogFilename; + callbacks[117] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[118] = (delegate* unmanaged)&_printMethodName; + callbacks[119] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[120] = (delegate* unmanaged)&_getMethodHash; + callbacks[121] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[122] = (delegate* unmanaged)&_getSwiftLowering; + callbacks[123] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; + callbacks[124] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; + callbacks[125] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[126] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[127] = (delegate* unmanaged)&_getHelperFtn; + callbacks[128] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[129] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[130] = (delegate* unmanaged)&_getMethodSync; + callbacks[131] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[132] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[133] = (delegate* unmanaged)&_embedClassHandle; + callbacks[134] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[135] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[136] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[137] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[138] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[139] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[140] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; + callbacks[141] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[142] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[143] = (delegate* unmanaged)&_getCallInfo; + callbacks[144] = (delegate* unmanaged)&_getClassDomainID; + callbacks[145] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[146] = (delegate* unmanaged)&_getObjectContent; + callbacks[147] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[148] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[149] = (delegate* unmanaged)&_canGetVarArgsHandle; + callbacks[150] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[151] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[152] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[153] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[154] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[155] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[156] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[157] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[158] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[159] = (delegate* unmanaged)&_allocMem; + callbacks[160] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[161] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[162] = (delegate* unmanaged)&_allocGCInfo; + callbacks[163] = (delegate* unmanaged)&_setEHcount; + callbacks[164] = (delegate* unmanaged)&_setEHinfo; + callbacks[165] = (delegate* unmanaged)&_logMsg; + callbacks[166] = (delegate* unmanaged)&_doAssert; + callbacks[167] = (delegate* unmanaged)&_reportFatalError; + callbacks[168] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[169] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[170] = (delegate* unmanaged)&_recordCallSite; + callbacks[171] = (delegate* unmanaged)&_recordRelocation; + callbacks[172] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[173] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[174] = (delegate* unmanaged)&_getJitFlags; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 8d6ecabaa77c57..86674ab2b59244 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -244,6 +244,7 @@ FUNCTIONS TypeCompareState compareTypesForEquality(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) bool isMoreSpecificType(CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2) bool isExactType(CORINFO_CLASS_HANDLE cls) + TypeCompareState isNullableType(CORINFO_CLASS_HANDLE cls) TypeCompareState isEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) CORINFO_CLASS_HANDLE getParentType(CORINFO_CLASS_HANDLE cls) CorInfoType getChildType(CORINFO_CLASS_HANDLE clsHnd, CORINFO_CLASS_HANDLE* clsRet) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 1340d8669ca08a..ad84c511393d69 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -536,7 +536,7 @@ public static bool HandleCall( { // We could try to analyze if the type is known, but for now making sure this works for canonical arrays is enough. TypeDesc canonArrayType = reflectionMarker.Factory.TypeSystemContext.CanonType.MakeArrayType(); - reflectionMarker.Dependencies.Add(reflectionMarker.Factory.NativeLayout.TemplateTypeLayout(canonArrayType), "Array.CreateInstance was called"); + reflectionMarker.MarkType(diagnosticContext.Origin, canonArrayType, "Array.CreateInstance was called"); goto case IntrinsicId.None; } @@ -656,6 +656,12 @@ public static bool HandleCall( if (Intrinsics.GetIntrinsicIdForMethod(callingMethodDefinition) == IntrinsicId.RuntimeReflectionExtensions_GetMethodInfo) break; + if (param.IsEmpty()) + { + // The static value is unknown and the below `foreach` won't execute + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ReflectedDelegate(null), "Delegate.Method access on unknown delegate type"); + } + foreach (var valueNode in param.AsEnumerable()) { TypeDesc? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index 68e8b5f6f0c39b..a18251e401fc03 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Immutable; using System.Diagnostics; using System.Reflection.Metadata; @@ -151,6 +152,12 @@ private static void AddDependenciesDueToCustomAttributes(ref DependencyList depe // worth the hassle: the input was invalid. The most important thing is that we // don't crash the compilation. } + catch (BadImageFormatException) + { + // System.Reflection.Metadata will throw BadImageFormatException if the blob is malformed. + // This can happen if e.g. underlying type of an enum changes between versions and + // we can no longer decode the custom attribute blob. + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectedTypeNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectedTypeNode.cs index fa37372f4c6dff..0a6327db7e545d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectedTypeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectedTypeNode.cs @@ -34,10 +34,17 @@ public ReflectedTypeNode(TypeDesc type) public override IEnumerable GetStaticDependencies(NodeFactory factory) { - return new DependencyListEntry[] - { - new DependencyListEntry(factory.MaximallyConstructableType(_type), "Reflection target"), - }; + var result = new DependencyList + { + new DependencyListEntry(factory.MaximallyConstructableType(_type), "Reflection target"), + }; + + if (_type.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + GenericTypesTemplateMap.GetTemplateTypeDependencies(ref result, factory, _type); + } + + return result; } protected override string GetName(NodeFactory factory) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index 19952d1209b659..bd70b1660db3f5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -1030,7 +1030,12 @@ public bool IsReflectionBlocked(TypeDesc type) case TypeFlags.Array: case TypeFlags.Pointer: case TypeFlags.ByRef: - return IsReflectionBlocked(((ParameterizedType)type).ParameterType); + TypeDesc parameterType = ((ParameterizedType)type).ParameterType; + + if (parameterType.IsCanonicalDefinitionType(CanonicalFormKind.Any)) + return false; + + return IsReflectionBlocked(parameterType); case TypeFlags.FunctionPointer: MethodSignature pointerSignature = ((FunctionPointerType)type).Signature; diff --git a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/ILCompiler/Metadata/Transform.CustomAttribute.cs b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/ILCompiler/Metadata/Transform.CustomAttribute.cs index 60f74b9402bb80..311d19d6f73c27 100644 --- a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/ILCompiler/Metadata/Transform.CustomAttribute.cs +++ b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/ILCompiler/Metadata/Transform.CustomAttribute.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Collections.Immutable; using Internal.Metadata.NativeFormat.Writer; @@ -11,6 +10,7 @@ using Debug = System.Diagnostics.Debug; using NamedArgumentMemberKind = Internal.Metadata.NativeFormat.NamedArgumentMemberKind; +using UnreachableException = System.Diagnostics.UnreachableException; namespace ILCompiler.Metadata { @@ -20,52 +20,60 @@ private List HandleCustomAttributes(Cts.Ecma.EcmaModule module, { List customAttributes = new List(attributes.Count); - var attributeTypeProvider = new Cts.Ecma.CustomAttributeTypeProvider(module); - - Ecma.MetadataReader reader = module.MetadataReader; - foreach (var attributeHandle in attributes) { if (!_policy.GeneratesMetadata(module, attributeHandle)) continue; - Ecma.CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - // TODO-NICE: We can intern the attributes based on the CA constructor and blob bytes - - Cts.MethodDesc constructor = module.GetMethod(attribute.Constructor); - var decodedValue = attribute.DecodeValue(attributeTypeProvider); - - customAttributes.Add(HandleCustomAttribute(constructor, decodedValue)); + customAttributes.Add(HandleCustomAttribute(module, attributeHandle)); } return customAttributes; } - private CustomAttribute HandleCustomAttribute(Cts.MethodDesc constructor, Ecma.CustomAttributeValue decodedValue) + private CustomAttribute HandleCustomAttribute(Cts.Ecma.EcmaModule module, Ecma.CustomAttributeHandle attributeHandle) { + Ecma.MetadataReader reader = module.MetadataReader; + Ecma.CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + + Cts.MethodDesc constructor = module.GetMethod(attribute.Constructor); + CustomAttribute result = new CustomAttribute { Constructor = HandleQualifiedMethod(constructor), }; - result.FixedArguments.Capacity = decodedValue.FixedArguments.Length; - foreach (var decodedArgument in decodedValue.FixedArguments) + Ecma.BlobReader valueReader = reader.GetBlobReader(attribute.Value); + + ushort prolog = valueReader.ReadUInt16(); // Version + Debug.Assert(prolog == 1); + + Cts.MethodSignature sig = constructor.Signature; + result.FixedArguments.Capacity = sig.Length; + foreach (Cts.TypeDesc paramType in sig) { - var fixedArgument = HandleCustomAttributeConstantValue(decodedArgument.Type, decodedArgument.Value); + var fixedArgument = paramType.IsArray ? + HandleCustomAttributeConstantArray(module, TypeDescToSerializationTypeCode(((Cts.ArrayType)paramType).ElementType), ref valueReader) : + HandleCustomAttributeConstantValue(module, TypeDescToSerializationTypeCode(paramType), ref valueReader); result.FixedArguments.Add(fixedArgument); } - result.NamedArguments.Capacity = decodedValue.NamedArguments.Length; - foreach (var decodedArgument in decodedValue.NamedArguments) + ushort numNamed = valueReader.ReadUInt16(); + result.NamedArguments.Capacity = numNamed; + for (int i = 0; i < numNamed; i++) { + byte flag = valueReader.ReadByte(); + Cts.TypeDesc type = SerializationTypeToType(module, ref valueReader); var namedArgument = new NamedArgument { - Flags = decodedArgument.Kind == Ecma.CustomAttributeNamedArgumentKind.Field ? + Flags = flag == (byte)Ecma.CustomAttributeNamedArgumentKind.Field ? NamedArgumentMemberKind.Field : NamedArgumentMemberKind.Property, - Name = HandleString(decodedArgument.Name), - Type = HandleType(decodedArgument.Type), - Value = HandleCustomAttributeConstantValue(decodedArgument.Type, decodedArgument.Value) + Type = HandleType(type), + Name = HandleString(valueReader.ReadSerializedString()), + Value = type.IsArray ? + HandleCustomAttributeConstantArray(module, TypeDescToSerializationTypeCode(((Cts.ArrayType)type).ElementType), ref valueReader) : + HandleCustomAttributeConstantValue(module, TypeDescToSerializationTypeCode(type), ref valueReader) }; result.NamedArguments.Add(namedArgument); } @@ -73,142 +81,186 @@ private CustomAttribute HandleCustomAttribute(Cts.MethodDesc constructor, Ecma.C return result; } - private MetadataRecord HandleCustomAttributeConstantValue(Cts.TypeDesc type, object value) + private static Ecma.SerializationTypeCode TypeDescToSerializationTypeCode(Cts.TypeDesc type) { + Debug.Assert((int)Cts.TypeFlags.Boolean == (int)Ecma.SerializationTypeCode.Boolean); + switch (type.UnderlyingType.Category) { - case Cts.TypeFlags.Boolean: - return new ConstantBooleanValue { Value = (bool)value }; - case Cts.TypeFlags.Byte: - return new ConstantByteValue { Value = (byte)value }; - case Cts.TypeFlags.Char: - return new ConstantCharValue { Value = (char)value }; - case Cts.TypeFlags.Double: - return new ConstantDoubleValue { Value = (double)value }; - case Cts.TypeFlags.Int16: - return new ConstantInt16Value { Value = (short)value }; - case Cts.TypeFlags.Int32: - return new ConstantInt32Value { Value = (int)value }; - case Cts.TypeFlags.Int64: - return new ConstantInt64Value { Value = (long)value }; - case Cts.TypeFlags.SByte: - return new ConstantSByteValue { Value = (sbyte)value }; - case Cts.TypeFlags.Single: - return new ConstantSingleValue { Value = (float)value }; - case Cts.TypeFlags.UInt16: - return new ConstantUInt16Value { Value = (ushort)value }; - case Cts.TypeFlags.UInt32: - return new ConstantUInt32Value { Value = (uint)value }; - case Cts.TypeFlags.UInt64: - return new ConstantUInt64Value { Value = (ulong)value }; + case Cts.TypeFlags.Single: return Ecma.SerializationTypeCode.Single; + case Cts.TypeFlags.Double: return Ecma.SerializationTypeCode.Double; + case <= Cts.TypeFlags.UInt64: return (Ecma.SerializationTypeCode)type.UnderlyingType.Category; + default: + if (type.IsObject) + return Ecma.SerializationTypeCode.TaggedObject; + + if (type.IsString) + return Ecma.SerializationTypeCode.String; + + if (type is not Cts.MetadataType { Name: "Type", Namespace: "System" }) + throw new UnreachableException(); + + return Ecma.SerializationTypeCode.Type; } + } + + private static Cts.TypeDesc SerializationTypeToType(Cts.Ecma.EcmaModule module, ref Ecma.BlobReader valueReader) + { + Ecma.SerializationTypeCode typeCode = valueReader.ReadSerializationTypeCode(); - if (value == null) + switch (typeCode) { - return new ConstantReferenceValue(); + case Ecma.SerializationTypeCode.Type: return module.Context.SystemModule.GetType("System", "Type"); + case Ecma.SerializationTypeCode.SZArray: return module.Context.GetArrayType(SerializationTypeToType(module, ref valueReader)); + case Ecma.SerializationTypeCode.Enum: return Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(module, valueReader.ReadSerializedString()); + case Ecma.SerializationTypeCode.String: return module.Context.GetWellKnownType(Cts.WellKnownType.String); + case Ecma.SerializationTypeCode.TaggedObject: return module.Context.GetWellKnownType(Cts.WellKnownType.Object); + case Ecma.SerializationTypeCode.Single: return module.Context.GetWellKnownType(Cts.WellKnownType.Single); + case Ecma.SerializationTypeCode.Double: return module.Context.GetWellKnownType(Cts.WellKnownType.Double); + case <= Ecma.SerializationTypeCode.UInt64: return module.Context.GetWellKnownType((Cts.WellKnownType)typeCode); } - if (type.IsString) + Cts.ThrowHelper.ThrowBadImageFormatException(); + return null; // unreached + } + + private MetadataRecord HandleCustomAttributeConstantValue(Cts.Ecma.EcmaModule module, Ecma.SerializationTypeCode typeCode, ref Ecma.BlobReader valueReader) + { + if (typeCode == Ecma.SerializationTypeCode.TaggedObject) { - return HandleString((string)value); + typeCode = valueReader.ReadSerializationTypeCode(); } - if (type.IsSzArray) + if (typeCode == Ecma.SerializationTypeCode.Enum) { - return HandleCustomAttributeConstantArray( - (Cts.ArrayType)type, - (ImmutableArray>)value); + Cts.TypeDesc enumType = Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(module, valueReader.ReadSerializedString()); + return new ConstantEnumValue + { + Value = HandleCustomAttributeConstantValue(module, TypeDescToSerializationTypeCode(enumType), ref valueReader), + Type = HandleType(enumType) + }; } - Debug.Assert(value is Cts.TypeDesc); - Debug.Assert(type is Cts.MetadataType - && ((Cts.MetadataType)type).Name == "Type" - && ((Cts.MetadataType)type).Namespace == "System"); + if (typeCode == Ecma.SerializationTypeCode.String) + { + string s = valueReader.ReadSerializedString(); + return s == null ? new ConstantReferenceValue() : HandleString(s); + } + + if (typeCode == Ecma.SerializationTypeCode.Type) + { + string s = valueReader.ReadSerializedString(); + return s == null ? new ConstantReferenceValue() : HandleType(Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(module, s)); + } - return HandleType((Cts.TypeDesc)value); + return typeCode switch + { + Ecma.SerializationTypeCode.Boolean => new ConstantBooleanValue { Value = valueReader.ReadBoolean() }, + Ecma.SerializationTypeCode.Char => new ConstantCharValue { Value = valueReader.ReadChar() }, + Ecma.SerializationTypeCode.Byte => new ConstantByteValue { Value = valueReader.ReadByte() }, + Ecma.SerializationTypeCode.SByte => new ConstantSByteValue { Value = valueReader.ReadSByte() }, + Ecma.SerializationTypeCode.Int16 => new ConstantInt16Value { Value = valueReader.ReadInt16() }, + Ecma.SerializationTypeCode.UInt16 => new ConstantUInt16Value { Value = valueReader.ReadUInt16() }, + Ecma.SerializationTypeCode.Int32 => new ConstantInt32Value { Value = valueReader.ReadInt32() }, + Ecma.SerializationTypeCode.UInt32 => new ConstantUInt32Value { Value = valueReader.ReadUInt32() }, + Ecma.SerializationTypeCode.Int64 => new ConstantInt64Value { Value = valueReader.ReadInt64() }, + Ecma.SerializationTypeCode.UInt64 => new ConstantUInt64Value { Value = valueReader.ReadUInt64() }, + Ecma.SerializationTypeCode.Single => new ConstantSingleValue { Value = valueReader.ReadSingle() }, + Ecma.SerializationTypeCode.Double => new ConstantDoubleValue { Value = valueReader.ReadDouble() }, + Ecma.SerializationTypeCode.SZArray => HandleCustomAttributeConstantArray(module, valueReader.ReadSerializationTypeCode(), ref valueReader), + _ => throw new UnreachableException() + }; } - private MetadataRecord HandleCustomAttributeConstantArray( - Cts.ArrayType type, ImmutableArray> value) + private MetadataRecord HandleCustomAttributeConstantArray(Cts.Ecma.EcmaModule module, Ecma.SerializationTypeCode elementTypeCode, ref Ecma.BlobReader valueReader) { - Cts.TypeDesc elementType = type.ElementType; - - if (elementType.IsEnum) + if (elementTypeCode == Ecma.SerializationTypeCode.Enum) { - Cts.TypeSystemContext context = type.Context; + Cts.TypeDesc enumType = Cts.CustomAttributeTypeNameParser.GetTypeByCustomAttributeTypeName(module, valueReader.ReadSerializedString()); return new ConstantEnumArray { - ElementType = HandleType(elementType), - Value = HandleCustomAttributeConstantArray(context.GetArrayType(elementType.UnderlyingType), value), + ElementType = HandleType(enumType), + Value = HandleCustomAttributeConstantArray(module, TypeDescToSerializationTypeCode(enumType), ref valueReader), }; } - switch (elementType.Category) - { - case Cts.TypeFlags.Boolean: - return new ConstantBooleanArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Byte: - return new ConstantByteArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Char: - return new ConstantCharArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Double: - return new ConstantDoubleArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Int16: - return new ConstantInt16Array { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Int32: - return new ConstantInt32Array { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Int64: - return new ConstantInt64Array { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.SByte: - return new ConstantSByteArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.Single: - return new ConstantSingleArray { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.UInt16: - return new ConstantUInt16Array { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.UInt32: - return new ConstantUInt32Array { Value = GetCustomAttributeConstantArrayElements(value) }; - case Cts.TypeFlags.UInt64: - return new ConstantUInt64Array { Value = GetCustomAttributeConstantArrayElements(value) }; + int count = valueReader.ReadInt32(); + if (count == -1) + { + return new ConstantReferenceValue(); } - if (elementType.IsString) + if (elementTypeCode is Ecma.SerializationTypeCode.String) { - var record = new ConstantStringArray(); - record.Value.Capacity = value.Length; - foreach (var element in value) + var handleArray = new ConstantStringArray(); + handleArray.Value.Capacity = count; + for (int i = 0; i < count; i++) { - MetadataRecord elementRecord = element.Value == null ? - (MetadataRecord)new ConstantReferenceValue() : HandleString((string)element.Value); - record.Value.Add(elementRecord); + string val = valueReader.ReadSerializedString(); + handleArray.Value.Add(val == null ? new ConstantReferenceValue() : HandleString(val)); } - return record; + return handleArray; } - var result = new ConstantHandleArray(); - result.Value.Capacity = value.Length; - for (int i = 0; i < value.Length; i++) + if (elementTypeCode is Ecma.SerializationTypeCode.TaggedObject or Ecma.SerializationTypeCode.Type) { - MetadataRecord elementRecord = HandleCustomAttributeConstantValue(value[i].Type, value[i].Value); - if (value[i].Type.IsEnum) + var handleArray = new ConstantHandleArray(); + handleArray.Value.Capacity = count; + for (int i = 0; i < count; i++) { - elementRecord = new ConstantBoxedEnumValue - { - Value = elementRecord, - Type = HandleType(value[i].Type) - }; + Ecma.SerializationTypeCode typecode = elementTypeCode == Ecma.SerializationTypeCode.Type ? Ecma.SerializationTypeCode.Type : valueReader.ReadSerializationTypeCode(); + handleArray.Value.Add(HandleCustomAttributeConstantValue(module, typecode, ref valueReader)); } - result.Value.Add(elementRecord); + return handleArray; } - return result; + return elementTypeCode switch + { + Ecma.SerializationTypeCode.Boolean => new ConstantBooleanArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Char => new ConstantCharArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.SByte => new ConstantSByteArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Byte => new ConstantByteArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Int16 => new ConstantInt16Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.UInt16 => new ConstantUInt16Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Int32 => new ConstantInt32Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.UInt32 => new ConstantUInt32Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Int64 => new ConstantInt64Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.UInt64 => new ConstantUInt64Array { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Single => new ConstantSingleArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + Ecma.SerializationTypeCode.Double => new ConstantDoubleArray { Value = GetCustomAttributeConstantArrayElements(ref valueReader, count) }, + _ => throw new UnreachableException() + }; } - private static TValue[] GetCustomAttributeConstantArrayElements(ImmutableArray> value) + private static TValue[] GetCustomAttributeConstantArrayElements(ref Ecma.BlobReader blobReader, int count) { - TValue[] result = new TValue[value.Length]; - for (int i = 0; i < value.Length; i++) + TValue[] result = new TValue[count]; + for (int i = 0; i < count; i++) { - result[i] = (TValue)value[i].Value; + if (typeof(TValue) == typeof(bool)) + result[i] = (TValue)(object)blobReader.ReadBoolean(); + if (typeof(TValue) == typeof(char)) + result[i] = (TValue)(object)blobReader.ReadChar(); + if (typeof(TValue) == typeof(sbyte)) + result[i] = (TValue)(object)blobReader.ReadSByte(); + if (typeof(TValue) == typeof(byte)) + result[i] = (TValue)(object)blobReader.ReadByte(); + if (typeof(TValue) == typeof(short)) + result[i] = (TValue)(object)blobReader.ReadInt16(); + if (typeof(TValue) == typeof(ushort)) + result[i] = (TValue)(object)blobReader.ReadUInt16(); + if (typeof(TValue) == typeof(int)) + result[i] = (TValue)(object)blobReader.ReadInt32(); + if (typeof(TValue) == typeof(uint)) + result[i] = (TValue)(object)blobReader.ReadUInt32(); + if (typeof(TValue) == typeof(long)) + result[i] = (TValue)(object)blobReader.ReadInt64(); + if (typeof(TValue) == typeof(ulong)) + result[i] = (TValue)(object)blobReader.ReadUInt64(); + if (typeof(TValue) == typeof(float)) + result[i] = (TValue)(object)blobReader.ReadSingle(); + if (typeof(TValue) == typeof(double)) + result[i] = (TValue)(object)blobReader.ReadDouble(); } return result; } diff --git a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/MdBinaryWriterGen.cs b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/MdBinaryWriterGen.cs index 74c013f1a99cb2..65173a6e7f6ddd 100644 --- a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/MdBinaryWriterGen.cs +++ b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/MdBinaryWriterGen.cs @@ -367,28 +367,6 @@ public static void Write(this NativeWriter writer, List va } } // Write - public static void Write(this NativeWriter writer, ConstantBoxedEnumValue record) - { - if (record != null) - writer.WriteUnsigned((uint)record.Handle.Offset); - else - writer.WriteUnsigned(0); - } // Write - - public static void Write(this NativeWriter writer, List values) - { - if (values == null) - { - writer.WriteUnsigned(0); - return; - } - writer.WriteUnsigned((uint)values.Count); - foreach (ConstantBoxedEnumValue value in values) - { - writer.Write(value); - } - } // Write - public static void Write(this NativeWriter writer, ConstantByteArray record) { if (record != null) @@ -543,6 +521,28 @@ public static void Write(this NativeWriter writer, List value } } // Write + public static void Write(this NativeWriter writer, ConstantEnumValue record) + { + if (record != null) + writer.WriteUnsigned((uint)record.Handle.Offset); + else + writer.WriteUnsigned(0); + } // Write + + public static void Write(this NativeWriter writer, List values) + { + if (values == null) + { + writer.WriteUnsigned(0); + return; + } + writer.WriteUnsigned((uint)values.Count); + foreach (ConstantEnumValue value in values) + { + writer.Write(value); + } + } // Write + public static void Write(this NativeWriter writer, ConstantHandleArray record) { if (record != null) diff --git a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/NativeFormatWriterGen.cs b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/NativeFormatWriterGen.cs index 3ea8d18d6a5cfd..50750233913581 100644 --- a/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/NativeFormatWriterGen.cs +++ b/src/coreclr/tools/aot/ILCompiler.MetadataTransform/Internal/Metadata/NativeFormat/Writer/NativeFormatWriterGen.cs @@ -303,88 +303,6 @@ internal static ConstantBooleanValueHandle AsHandle(ConstantBooleanValue record) public bool Value; } // ConstantBooleanValue - public partial class ConstantBoxedEnumValue : MetadataRecord - { - public override HandleType HandleType - { - get - { - return HandleType.ConstantBoxedEnumValue; - } - } // HandleType - - internal override void Visit(IRecordVisitor visitor) - { - Value = visitor.Visit(this, Value); - Type = visitor.Visit(this, Type); - } // Visit - - public override sealed bool Equals(Object obj) - { - if (Object.ReferenceEquals(this, obj)) return true; - var other = obj as ConstantBoxedEnumValue; - if (other == null) return false; - if (!Object.Equals(Value, other.Value)) return false; - if (!Object.Equals(Type, other.Type)) return false; - return true; - } // Equals - - public override sealed int GetHashCode() - { - if (_hash != 0) - return _hash; - EnterGetHashCode(); - int hash = 879057725; - hash = ((hash << 13) - (hash >> 19)) ^ (Value == null ? 0 : Value.GetHashCode()); - hash = ((hash << 13) - (hash >> 19)) ^ (Type == null ? 0 : Type.GetHashCode()); - LeaveGetHashCode(); - _hash = hash; - return _hash; - } // GetHashCode - - internal override void Save(NativeWriter writer) - { - Debug.Assert(Value == null || - Value.HandleType == HandleType.ConstantByteValue || - Value.HandleType == HandleType.ConstantSByteValue || - Value.HandleType == HandleType.ConstantInt16Value || - Value.HandleType == HandleType.ConstantUInt16Value || - Value.HandleType == HandleType.ConstantInt32Value || - Value.HandleType == HandleType.ConstantUInt32Value || - Value.HandleType == HandleType.ConstantInt64Value || - Value.HandleType == HandleType.ConstantUInt64Value); - writer.Write(Value); - Debug.Assert(Type == null || - Type.HandleType == HandleType.TypeDefinition || - Type.HandleType == HandleType.TypeReference || - Type.HandleType == HandleType.TypeSpecification); - writer.Write(Type); - } // Save - - internal static ConstantBoxedEnumValueHandle AsHandle(ConstantBoxedEnumValue record) - { - if (record == null) - { - return new ConstantBoxedEnumValueHandle(0); - } - else - { - return record.Handle; - } - } // AsHandle - - internal new ConstantBoxedEnumValueHandle Handle - { - get - { - return new ConstantBoxedEnumValueHandle(HandleOffset); - } - } // Handle - - public MetadataRecord Value; - public MetadataRecord Type; - } // ConstantBoxedEnumValue - public partial class ConstantByteArray : MetadataRecord { public override HandleType HandleType @@ -841,6 +759,75 @@ internal static ConstantEnumArrayHandle AsHandle(ConstantEnumArray record) public MetadataRecord Value; } // ConstantEnumArray + public partial class ConstantEnumValue : MetadataRecord + { + public override HandleType HandleType + { + get + { + return HandleType.ConstantEnumValue; + } + } // HandleType + + internal override void Visit(IRecordVisitor visitor) + { + Value = visitor.Visit(this, Value); + Type = visitor.Visit(this, Type); + } // Visit + + public override sealed bool Equals(Object obj) + { + if (Object.ReferenceEquals(this, obj)) return true; + var other = obj as ConstantEnumValue; + if (other == null) return false; + if (!Object.Equals(Value, other.Value)) return false; + if (!Object.Equals(Type, other.Type)) return false; + return true; + } // Equals + + public override sealed int GetHashCode() + { + if (_hash != 0) + return _hash; + EnterGetHashCode(); + int hash = 1536570168; + hash = ((hash << 13) - (hash >> 19)) ^ (Value == null ? 0 : Value.GetHashCode()); + hash = ((hash << 13) - (hash >> 19)) ^ (Type == null ? 0 : Type.GetHashCode()); + LeaveGetHashCode(); + _hash = hash; + return _hash; + } // GetHashCode + + internal override void Save(NativeWriter writer) + { + writer.Write(Value); + writer.Write(Type); + } // Save + + internal static ConstantEnumValueHandle AsHandle(ConstantEnumValue record) + { + if (record == null) + { + return new ConstantEnumValueHandle(0); + } + else + { + return record.Handle; + } + } // AsHandle + + internal new ConstantEnumValueHandle Handle + { + get + { + return new ConstantEnumValueHandle(HandleOffset); + } + } // Handle + + public MetadataRecord Value; + public MetadataRecord Type; + } // ConstantEnumValue + public partial class ConstantHandleArray : MetadataRecord { public override HandleType HandleType @@ -2214,6 +2201,7 @@ internal override void Save(NativeWriter writer) handle.HandleType == HandleType.ConstantDoubleArray || handle.HandleType == HandleType.ConstantDoubleValue || handle.HandleType == HandleType.ConstantEnumArray || + handle.HandleType == HandleType.ConstantEnumValue || handle.HandleType == HandleType.ConstantHandleArray || handle.HandleType == HandleType.ConstantInt16Array || handle.HandleType == HandleType.ConstantInt16Value || @@ -2426,6 +2414,7 @@ internal override void Save(NativeWriter writer) DefaultValue.HandleType == HandleType.ConstantDoubleArray || DefaultValue.HandleType == HandleType.ConstantDoubleValue || DefaultValue.HandleType == HandleType.ConstantEnumArray || + DefaultValue.HandleType == HandleType.ConstantEnumValue || DefaultValue.HandleType == HandleType.ConstantHandleArray || DefaultValue.HandleType == HandleType.ConstantInt16Array || DefaultValue.HandleType == HandleType.ConstantInt16Value || @@ -3341,6 +3330,7 @@ internal override void Save(NativeWriter writer) Value.HandleType == HandleType.ConstantDoubleArray || Value.HandleType == HandleType.ConstantDoubleValue || Value.HandleType == HandleType.ConstantEnumArray || + Value.HandleType == HandleType.ConstantEnumValue || Value.HandleType == HandleType.ConstantHandleArray || Value.HandleType == HandleType.ConstantInt16Array || Value.HandleType == HandleType.ConstantInt16Value || @@ -3606,6 +3596,7 @@ internal override void Save(NativeWriter writer) DefaultValue.HandleType == HandleType.ConstantDoubleArray || DefaultValue.HandleType == HandleType.ConstantDoubleValue || DefaultValue.HandleType == HandleType.ConstantEnumArray || + DefaultValue.HandleType == HandleType.ConstantEnumValue || DefaultValue.HandleType == HandleType.ConstantHandleArray || DefaultValue.HandleType == HandleType.ConstantInt16Array || DefaultValue.HandleType == HandleType.ConstantInt16Value || @@ -3801,6 +3792,7 @@ internal override void Save(NativeWriter writer) DefaultValue.HandleType == HandleType.ConstantDoubleArray || DefaultValue.HandleType == HandleType.ConstantDoubleValue || DefaultValue.HandleType == HandleType.ConstantEnumArray || + DefaultValue.HandleType == HandleType.ConstantEnumValue || DefaultValue.HandleType == HandleType.ConstantHandleArray || DefaultValue.HandleType == HandleType.ConstantInt16Array || DefaultValue.HandleType == HandleType.ConstantInt16Value || diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 754394f16ee9eb..db1d0a721d1f15 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -92,6 +92,7 @@ struct JitInterfaceCallbacks TypeCompareState (* compareTypesForEquality)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); bool (* isMoreSpecificType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls1, CORINFO_CLASS_HANDLE cls2); bool (* isExactType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); + TypeCompareState (* isNullableType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); TypeCompareState (* isEnum)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType); CORINFO_CLASS_HANDLE (* getParentType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); CorInfoType (* getChildType)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE clsHnd, CORINFO_CLASS_HANDLE* clsRet); @@ -992,6 +993,15 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual TypeCompareState isNullableType( + CORINFO_CLASS_HANDLE cls) +{ + CorInfoExceptionClass* pException = nullptr; + TypeCompareState temp = _callbacks->isNullableType(_thisHandle, &pException, cls); + if (pException != nullptr) throw pException; + return temp; +} + virtual TypeCompareState isEnum( CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index c3445291eeb931..75c1ac1b6f52a2 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -158,6 +158,7 @@ LWM(PrintMethodName, DWORDLONG, Agnostic_PrintResult) LWM(IsValueClass, DWORDLONG, DWORD) LWM(IsMoreSpecificType, DLDL, DWORD) LWM(IsExactType, DWORDLONG, DWORD) +LWM(IsNullableType, DWORDLONG, DWORD) LWM(IsEnum, DWORDLONG, DLD) LWM(PInvokeMarshalingRequired, MethodOrSigInfoValue, DWORD) LWM(ResolveToken, Agnostic_CORINFO_RESOLVED_TOKENin, ResolveTokenValue) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 7b0ddc8ded5ae2..05ad1c23a6a2c9 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -5920,6 +5920,28 @@ bool MethodContext::repIsExactType(CORINFO_CLASS_HANDLE cls) return value != 0; } +void MethodContext::recIsNullableType(CORINFO_CLASS_HANDLE cls, TypeCompareState result) +{ + if (IsNullableType == nullptr) + IsNullableType = new LightWeightMap(); + + DWORDLONG key = CastHandle(cls); + DWORD value = (DWORD)result; + IsNullableType->Add(key, value); + DEBUG_REC(dmpIsNullableType(key, value)); +} +void MethodContext::dmpIsNullableType(DWORDLONG key, DWORD value) +{ + printf("IsNullableType key cls-%016" PRIX64 ", value res-%d", key, value); +} +TypeCompareState MethodContext::repIsNullableType(CORINFO_CLASS_HANDLE cls) +{ + DWORDLONG key = CastHandle(cls); + DWORD value = LookupByKeyOrMiss(IsNullableType, key, ": key %016" PRIX64 "", key); + DEBUG_REP(dmpIsNullableType(key, value)); + return (TypeCompareState)value; +} + void MethodContext::recIsEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE underlyingType, TypeCompareState result) { if (IsEnum == nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index cf9c235ae987ea..a530d2f58385be 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -723,6 +723,10 @@ class MethodContext void dmpIsExactType(DWORDLONG key, DWORD value); bool repIsExactType(CORINFO_CLASS_HANDLE cls); + void recIsNullableType(CORINFO_CLASS_HANDLE cls, TypeCompareState result); + void dmpIsNullableType(DWORDLONG key, DWORD value); + TypeCompareState repIsNullableType(CORINFO_CLASS_HANDLE cls); + void recIsEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE underlyingType, TypeCompareState result); void dmpIsEnum(DWORDLONG key, DLD value); TypeCompareState repIsEnum(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType); @@ -1165,6 +1169,7 @@ enum mcPackets Packet_NotifyMethodInfoUsage = 214, Packet_IsExactType = 215, Packet_GetSwiftLowering = 216, + Packet_IsNullableType = 217, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index d53002a42914b1..d39379089794f5 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -914,6 +914,15 @@ bool interceptor_ICJI::isExactType(CORINFO_CLASS_HANDLE cls) return temp; } +// Returns whether a class handle represents a Nullable type, if that can be statically determined. +TypeCompareState interceptor_ICJI::isNullableType(CORINFO_CLASS_HANDLE cls) +{ + mc->cr->AddCall("isNullableType"); + TypeCompareState temp = original_ICorJitInfo->isNullableType(cls); + mc->recIsNullableType(cls, temp); + return temp; +} + // Returns TypeCompareState::Must if cls is known to be an enum. // For enums with known exact type returns the underlying // type in underlyingType when the provided pointer is diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index ef1b277e805333..eebb80ab6b9dfa 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -659,6 +659,13 @@ bool interceptor_ICJI::isExactType( return original_ICorJitInfo->isExactType(cls); } +TypeCompareState interceptor_ICJI::isNullableType( + CORINFO_CLASS_HANDLE cls) +{ + mcs->AddCall("isNullableType"); + return original_ICorJitInfo->isNullableType(cls); +} + TypeCompareState interceptor_ICJI::isEnum( CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 55aef651273476..6a2ca48d87cd79 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -578,6 +578,12 @@ bool interceptor_ICJI::isExactType( return original_ICorJitInfo->isExactType(cls); } +TypeCompareState interceptor_ICJI::isNullableType( + CORINFO_CLASS_HANDLE cls) +{ + return original_ICorJitInfo->isNullableType(cls); +} + TypeCompareState interceptor_ICJI::isEnum( CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE* underlyingType) diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index f23c8e12f86618..08814336a74b1f 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -764,6 +764,13 @@ bool MyICJI::isExactType(CORINFO_CLASS_HANDLE cls) return jitInstance->mc->repIsExactType(cls); } +// Returns true if a class handle represents a Nullable type. +TypeCompareState MyICJI::isNullableType(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("isNullableType"); + return jitInstance->mc->repIsNullableType(cls); +} + // Returns TypeCompareState::Must if cls is known to be an enum. // For enums with known exact type returns the underlying // type in underlyingType when the provided pointer is diff --git a/src/coreclr/utilcode/ex.cpp b/src/coreclr/utilcode/ex.cpp index 7b65514b83d0bb..adee9ba55618ca 100644 --- a/src/coreclr/utilcode/ex.cpp +++ b/src/coreclr/utilcode/ex.cpp @@ -574,7 +574,6 @@ LPCSTR Exception::GetHRSymbolicName(HRESULT hr) CASE_HRESULT(COR_E_CANNOTUNLOADAPPDOMAIN) CASE_HRESULT(MSEE_E_ASSEMBLYLOADINPROGRESS) CASE_HRESULT(FUSION_E_REF_DEF_MISMATCH) - CASE_HRESULT(FUSION_E_PRIVATE_ASM_DISALLOWED) CASE_HRESULT(FUSION_E_INVALID_NAME) CASE_HRESULT(CLDB_E_FILE_BADREAD) CASE_HRESULT(CLDB_E_FILE_BADWRITE) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 07f24361dcccae..59a4783648e2a7 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -4528,13 +4528,6 @@ AppDomain::RaiseAssemblyResolveEvent( } GCPROTECT_END(); - if (pAssembly != NULL) - { - // Check that the public key token matches the one specified in the spec - // MatchPublicKeys throws as appropriate - pSpec->MatchPublicKeys(pAssembly); - } - RETURN pAssembly; } // AppDomain::RaiseAssemblyResolveEvent diff --git a/src/coreclr/vm/assemblyspec.cpp b/src/coreclr/vm/assemblyspec.cpp index 6144f000a8e055..a4872b732097b9 100644 --- a/src/coreclr/vm/assemblyspec.cpp +++ b/src/coreclr/vm/assemblyspec.cpp @@ -295,55 +295,6 @@ void AssemblySpec::InitializeAssemblyNameRef(_In_ BINDER_SPACE::AssemblyName* as spec.AssemblyNameInit(assemblyNameRef); } - -// Check if the supplied assembly's public key matches up with the one in the Spec, if any -// Throws an appropriate exception in case of a mismatch -void AssemblySpec::MatchPublicKeys(Assembly *pAssembly) -{ - CONTRACTL - { - INSTANCE_CHECK; - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - // Check that the public keys are the same as in the AR. - if (!IsStrongNamed()) - return; - - const void *pbPublicKey; - DWORD cbPublicKey; - pbPublicKey = pAssembly->GetPublicKey(&cbPublicKey); - if (cbPublicKey == 0) - ThrowHR(FUSION_E_PRIVATE_ASM_DISALLOWED); - - if (IsAfPublicKey(m_dwFlags)) - { - if ((m_cbPublicKeyOrToken != cbPublicKey) || - memcmp(m_pbPublicKeyOrToken, pbPublicKey, m_cbPublicKeyOrToken)) - { - ThrowHR(FUSION_E_REF_DEF_MISMATCH); - } - } - else - { - // Ref has a token - StrongNameToken strongNameToken; - - IfFailThrow(StrongNameTokenFromPublicKey((BYTE*)pbPublicKey, - cbPublicKey, - &strongNameToken)); - - if ((m_cbPublicKeyOrToken != StrongNameToken::SIZEOF_TOKEN) || - memcmp(m_pbPublicKeyOrToken, &strongNameToken, StrongNameToken::SIZEOF_TOKEN)) - { - ThrowHR(FUSION_E_REF_DEF_MISMATCH); - } - } -} - Assembly *AssemblySpec::LoadAssembly(FileLoadLevel targetLevel, BOOL fThrowOnFileNotFound) { CONTRACTL diff --git a/src/coreclr/vm/assemblyspec.hpp b/src/coreclr/vm/assemblyspec.hpp index a39b7123597118..23a7c96e647565 100644 --- a/src/coreclr/vm/assemblyspec.hpp +++ b/src/coreclr/vm/assemblyspec.hpp @@ -193,8 +193,6 @@ class AssemblySpec : public BaseAssemblySpec static void InitializeAssemblyNameRef(_In_ BINDER_SPACE::AssemblyName* assemblyName, _Out_ ASSEMBLYNAMEREF* assemblyNameRef); public: - void MatchPublicKeys(Assembly *pAssembly); - AppDomain *GetAppDomain() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 05b0b94036562c..9719e7e6dc1424 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -5655,8 +5655,6 @@ VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pE args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(throwable); args[ARGNUM_1] = PTR_TO_ARGHOLDER(&exInfo); - FirstChanceExceptionNotification(); - pThread->IncPreventAbort(); //Ex.RhThrowEx(throwable, &exInfo) @@ -8127,6 +8125,7 @@ static void NotifyExceptionPassStarted(StackFrameIterator *pThis, Thread *pThrea { GCX_COOP(); pThread->SafeSetThrowables(pExInfo->m_exception); + FirstChanceExceptionNotification(); EEToProfilerExceptionInterfaceWrapper::ExceptionThrown(pThread); } else // pExInfo->m_passNumber == 2 diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index c8ac3350b2e912..1e7ffc1ca57089 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -4658,6 +4658,31 @@ bool CEEInfo::isExactType(CORINFO_CLASS_HANDLE cls) return result; } +// Returns whether a class handle represents a Nullable type, if that can be statically determined. +TypeCompareState CEEInfo::isNullableType(CORINFO_CLASS_HANDLE cls) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + TypeHandle typeHandle = TypeHandle(); + + TypeCompareState result = TypeCompareState::May; + + JIT_TO_EE_TRANSITION(); + + if (typeHandle != TypeHandle(g_pCanonMethodTableClass)) + { + TypeHandle VMClsHnd(cls); + result = Nullable::IsNullableType(VMClsHnd) ? TypeCompareState::Must : TypeCompareState::MustNot; + } + + EE_TO_JIT_TRANSITION(); + return result; +} + /*********************************************************************/ // Returns TypeCompareState::Must if cls is known to be an enum. // For enums with known exact type returns the underlying diff --git a/src/coreclr/vm/rexcep.h b/src/coreclr/vm/rexcep.h index 3f2c1cee117ae3..648a08865d73be 100644 --- a/src/coreclr/vm/rexcep.h +++ b/src/coreclr/vm/rexcep.h @@ -144,7 +144,6 @@ DEFINE_EXCEPTION(g_SystemNS, FieldAccessException, false, C DEFINE_EXCEPTION(g_IONS, FileLoadException, true, COR_E_FILELOAD, FUSION_E_INVALID_NAME, - FUSION_E_PRIVATE_ASM_DISALLOWED, FUSION_E_REF_DEF_MISMATCH, HRESULT_FROM_WIN32(ERROR_TOO_MANY_OPEN_FILES), HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), HRESULT_FROM_WIN32(ERROR_LOCK_VIOLATION), diff --git a/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj b/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj index fec324d46cccbf..55a33eb7e24232 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj +++ b/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj @@ -20,7 +20,7 @@ - + @@ -35,5 +35,3 @@ - - diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs index 9cbf1ee2c34785..a55e2ca19f0e15 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.InteropServices; internal static partial class Interop { internal static partial class Sys { + [StructLayout(LayoutKind.Sequential)] internal unsafe struct IOVector { public byte* Base; diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs new file mode 100644 index 00000000000000..be4888bbfb743f --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReceiveSocketError")] + internal static unsafe partial SocketError ReceiveSocketError(SafeHandle socket, MessageHeader* messageHeader); + } +} diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 589bc56352bb7f..ed40ac83a8a4fb 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -335,13 +335,12 @@ internal sealed class SafeFreeCredential_SECURITY : SafeFreeCredentials { #pragma warning disable 0649 // This is used only by SslStream but it is included elsewhere - public X509Certificate? LocalCertificate; + public bool HasLocalCertificate; #pragma warning restore 0649 public SafeFreeCredential_SECURITY() : base() { } protected override bool ReleaseHandle() { - LocalCertificate?.Dispose(); return Interop.SspiCli.FreeCredentialsHandle(ref _handle) == 0; } } diff --git a/src/libraries/Common/src/System/HResults.cs b/src/libraries/Common/src/System/HResults.cs index f7ec9f94a628a3..bbc434b87289c9 100644 --- a/src/libraries/Common/src/System/HResults.cs +++ b/src/libraries/Common/src/System/HResults.cs @@ -127,7 +127,6 @@ internal static partial class HResults internal const int CTL_E_PATHNOTFOUND = unchecked((int)0x800A004C); internal const int CTL_E_FILENOTFOUND = unchecked((int)0x800A0035); internal const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047); - internal const int FUSION_E_PRIVATE_ASM_DISALLOWED = unchecked((int)0x80131044); internal const int FUSION_E_REF_DEF_MISMATCH = unchecked((int)0x80131040); internal const int ERROR_TOO_MANY_OPEN_FILES = unchecked((int)0x80070004); internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020); diff --git a/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs b/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs index bad2ddd2dd73fc..3bbba47040b01b 100644 --- a/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs +++ b/src/libraries/Common/tests/System/Net/Capability.Security.Unix.cs @@ -9,6 +9,10 @@ public static partial class Capability { public static bool IsNtlmInstalled() { + if (OperatingSystem.IsBrowser()) + { + return false; + } return // Linux bionic uses managed NTLM implementation (OperatingSystem.IsLinux() && RuntimeInformation.RuntimeIdentifier.StartsWith("linux-bionic-", StringComparison.Ordinal)) || diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs index d9be1bdf931f0b..0fe06a4670ef99 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs @@ -73,12 +73,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => [InlineData(15)] public async Task LargeSingleHeader_ThrowsException(int maxResponseHeadersLength) { - if (UseVersion == HttpVersion30) - { - // [ActiveIssue("https://github.com/dotnet/runtime/issues/94507")] - return; - } - using HttpClientHandler handler = CreateHttpClientHandler(); handler.MaxResponseHeadersLength = maxResponseHeadersLength; diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/LocalEchoServer.helix.targets b/src/libraries/Common/tests/System/Net/Prerequisites/LocalEchoServer.helix.targets index 80f1eb750e10f1..630642240b705f 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/LocalEchoServer.helix.targets +++ b/src/libraries/Common/tests/System/Net/Prerequisites/LocalEchoServer.helix.targets @@ -1,7 +1,7 @@ + '$(Scenario)' == 'WasmTestOnChrome' or '$(Scenario)' == 'WasmTestOnNodeJS')"> $(HelixExtensionTargets);_AddMiddlewarePayload $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'NetCoreServer', '$(Configuration)', '$(AspNetCoreAppCurrent)')) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 3c93c2b4670989..ec6645cec0c319 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -152,6 +152,8 @@ public static int SlowRuntimeTimeoutModifier public static bool IsNotBrowserDomSupported => !IsBrowserDomSupported; public static bool IsWebSocketSupported => IsEnvironmentVariableTrue("IsWebSocketSupported"); public static bool IsNodeJS => IsEnvironmentVariableTrue("IsNodeJS"); + public static bool IsFirefox => IsEnvironmentVariableTrue("IsFirefox"); + public static bool IsChromium => IsEnvironmentVariableTrue("IsChromium"); public static bool IsNotNodeJS => !IsNodeJS; public static bool IsNodeJSOnWindows => GetNodeJSPlatform() == "win32"; public static bool LocalEchoServerIsNotAvailable => !LocalEchoServerIsAvailable; diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/InMemoryDirectoryInfo.cs b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/InMemoryDirectoryInfo.cs index 4d41468a173eb3..8d6c9c3f33e4aa 100644 --- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/InMemoryDirectoryInfo.cs +++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/InMemoryDirectoryInfo.cs @@ -51,13 +51,14 @@ private InMemoryDirectoryInfo(string rootDir, IEnumerable? files, bool n // normalize foreach (string file in files) { + string fileWithNormalSeparators = file.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); if (Path.IsPathRooted(file)) { - fileList.Add(Path.GetFullPath(file.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); + fileList.Add(Path.GetFullPath(fileWithNormalSeparators)); } else { - fileList.Add(Path.Combine(normalizedRoot, file.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))); + fileList.Add(Path.GetFullPath(Path.Combine(normalizedRoot, fileWithNormalSeparators))); } } diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs index 0d9ca6ce1e20ad..e8bcdacdb9558b 100644 --- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs +++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.FileSystemGlobbing.Abstractions; using Microsoft.Extensions.FileSystemGlobbing.Tests.TestUtility; using Xunit; @@ -851,5 +852,54 @@ public void VerifyInMemoryDirectoryInfo_IsNotEmpty() Assert.Equal(1, fileSystemInfos.Count()); } + + [Theory] + [InlineData("./sdk/9.0.100-preview.4.24207.1/.version")] + [InlineData("././sdk/9.0.100-preview.4.24207.1/.version")] + public void VerifyFiles_RedundantSegment_HasMatches(string file) + { + foreach (string pattern in new[] { "**/*", "./", file }) + { + var matcher = new Matcher(); + matcher.AddInclude(pattern); + Assert.True(matcher.Match(file).HasMatches); + Assert.True(matcher.Match([file]).HasMatches); + Assert.True(matcher.Match("X:/foo", file).HasMatches); + Assert.True(matcher.Match("X:/foo", [file]).HasMatches); + } + } + + [ConditionalFact] + public void VerifyFiles_ParentRedundantSegment_HasMatches() + { + string file = "sdk/9.0.100-preview.4.24207.1/.version"; + foreach (string pattern in new[] { "**/*", "./", file }) + { + var matcher = new Matcher(); + matcher.AddInclude(pattern); + Assert.True(matcher.Match("X:/foo", $"../foo/{file}").HasMatches); + Assert.True(matcher.Match("X:/foo", [$"../foo/{file}"]).HasMatches); + } + } + + [ConditionalFact] + public void VerifyFiles_ParentRedundantSegment_CurrentDirectory_HasMatches() + { + string cwd = Environment.CurrentDirectory; + string cwdFolderName = new DirectoryInfo(cwd).Name; + if (cwd == cwdFolderName) // cwd is root, we can't do ../C:/ + { + throw new SkipTestException($"CurrentDirectory {cwd} is the root directory."); + } + + string file = "sdk/9.0.100-preview.4.24207.1/.version"; + foreach (string pattern in new[] { "**/*", "./", file }) + { + var matcher = new Matcher(); + matcher.AddInclude(pattern); + Assert.True(matcher.Match($"../{cwdFolderName}/{file}").HasMatches); + Assert.True(matcher.Match([$"../{cwdFolderName}/{file}"]).HasMatches); + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs index 3b725f1cb59e03..663b766ba4c92a 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs @@ -25,7 +25,7 @@ public void EnsureConsoleLoggerOptions_ConfigureOptions_SupportsAllProperties() Assert.Equal(3, typeof(ConsoleFormatterOptions).GetProperties(flags).Length); Assert.Equal(5, typeof(SimpleConsoleFormatterOptions).GetProperties(flags).Length); Assert.Equal(4, typeof(JsonConsoleFormatterOptions).GetProperties(flags).Length); - Assert.Equal(6, typeof(JsonWriterOptions).GetProperties(flags).Length); + Assert.Equal(7, typeof(JsonWriterOptions).GetProperties(flags).Length); } [Theory] diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs index fec2d737c8d2c8..60188dc2ce4f02 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs @@ -273,11 +273,13 @@ public void AddSystemdConsole_OutsideConfig_TakesProperty() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AddJsonConsole_ChangeProperties_IsReadFromLoggingConfiguration() { + var newLine = Environment.NewLine.Length is 2 ? "\n" : "\r\n"; var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { new KeyValuePair("Console:FormatterOptions:TimestampFormat", "HH:mm "), new KeyValuePair("Console:FormatterOptions:UseUtcTimestamp", "true"), new KeyValuePair("Console:FormatterOptions:IncludeScopes", "true"), new KeyValuePair("Console:FormatterOptions:JsonWriterOptions:Indented", "true"), + new KeyValuePair("Console:FormatterOptions:JsonWriterOptions:NewLine", newLine), }).Build(); var loggerProvider = new ServiceCollection() @@ -296,11 +298,13 @@ public void AddJsonConsole_ChangeProperties_IsReadFromLoggingConfiguration() Assert.True(formatter.FormatterOptions.UseUtcTimestamp); Assert.True(formatter.FormatterOptions.IncludeScopes); Assert.True(formatter.FormatterOptions.JsonWriterOptions.Indented); + Assert.Equal(newLine, formatter.FormatterOptions.JsonWriterOptions.NewLine); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AddJsonConsole_OutsideConfig_TakesProperty() { + var newLine = Environment.NewLine.Length is 2 ? "\n" : "\r\n"; var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { new KeyValuePair("Console:FormatterOptions:TimestampFormat", "HH:mm "), new KeyValuePair("Console:FormatterOptions:UseUtcTimestamp", "true"), @@ -314,7 +318,8 @@ public void AddJsonConsole_OutsideConfig_TakesProperty() o.JsonWriterOptions = new JsonWriterOptions() { Indented = false, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + NewLine = newLine }; }) ) @@ -329,6 +334,7 @@ public void AddJsonConsole_OutsideConfig_TakesProperty() Assert.True(formatter.FormatterOptions.UseUtcTimestamp); Assert.True(formatter.FormatterOptions.IncludeScopes); Assert.False(formatter.FormatterOptions.JsonWriterOptions.Indented); + Assert.Equal(newLine, formatter.FormatterOptions.JsonWriterOptions.NewLine); Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, formatter.FormatterOptions.JsonWriterOptions.Encoder); } diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentQueueTests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentQueueTests.cs index 49ab211a30590b..eb0299a260eb03 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentQueueTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentQueueTests.cs @@ -130,7 +130,7 @@ public void MultipleProducerConsumer_AllItemsTransferred(int producers, int cons }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)); } - Task.WaitAll(tasks.ToArray()); + Task.WaitAll(tasks); Assert.Equal(producers * (itemsPerProducer * (itemsPerProducer + 1) / 2), sum); } diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs index 5cbd89d5a7705e..8ef602113be272 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessWaitingTests.cs @@ -86,7 +86,7 @@ public void MultipleProcesses_ParallelStartKillWait() p.WaitForExit(WaitInMS); } }; - Task.WaitAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)).ToArray()); + Task.WaitAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work))); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] @@ -108,7 +108,7 @@ public async Task MultipleProcesses_ParallelStartKillWaitAsync() } }; - await Task.WhenAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work)).ToArray()); + await Task.WhenAll(Enumerable.Range(0, Tasks).Select(_ => Task.Run(work))); } [Theory] diff --git a/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs b/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs index bec9d28b7b426e..f27056100697cc 100644 --- a/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs +++ b/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs @@ -55,6 +55,7 @@ public AsnContentException(string? message, System.Exception? inner) { } } public static partial class AsnDecoder { + public static int? DecodeLength(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int bytesConsumed) { throw null; } public static byte[] ReadBitString(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int unusedBitCount, out int bytesConsumed, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static bool ReadBoolean(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int bytesConsumed, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static string ReadCharacterString(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, System.Formats.Asn1.UniversalTagNumber encodingType, out int bytesConsumed, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } @@ -74,6 +75,7 @@ public static partial class AsnDecoder public static void ReadSequence(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int contentOffset, out int contentLength, out int bytesConsumed, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static void ReadSetOf(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int contentOffset, out int contentLength, out int bytesConsumed, bool skipSortOrderValidation = false, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static System.DateTimeOffset ReadUtcTime(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int bytesConsumed, int twoDigitYearMax = 2049, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } + public static bool TryDecodeLength(System.ReadOnlySpan source, System.Formats.Asn1.AsnEncodingRules ruleSet, out int? decodedLength, out int bytesConsumed) { throw null; } public static bool TryReadBitString(System.ReadOnlySpan source, System.Span destination, System.Formats.Asn1.AsnEncodingRules ruleSet, out int unusedBitCount, out int bytesConsumed, out int bytesWritten, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static bool TryReadCharacterString(System.ReadOnlySpan source, System.Span destination, System.Formats.Asn1.AsnEncodingRules ruleSet, System.Formats.Asn1.UniversalTagNumber encodingType, out int bytesConsumed, out int charsWritten, System.Formats.Asn1.Asn1Tag? expectedTag = default(System.Formats.Asn1.Asn1Tag?)) { throw null; } public static bool TryReadCharacterStringBytes(System.ReadOnlySpan source, System.Span destination, System.Formats.Asn1.AsnEncodingRules ruleSet, System.Formats.Asn1.Asn1Tag expectedTag, out int bytesConsumed, out int bytesWritten) { throw null; } diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.cs index 6a0bc94a94786b..28e55b114beca5 100644 --- a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.cs +++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.cs @@ -206,6 +206,86 @@ private static ReadOnlySpan GetPrimitiveContentSpan( return ret; } + /// + /// Decodes the data in as a length value under the specified + /// encoding rules. + /// + /// The buffer containing encoded data. + /// The encoding constraints to use when interpreting the data. + /// + /// When this method returns, the number of bytes from the beginning of + /// that contributed to the length. + /// This parameter is treated as uninitialized. + /// + /// + /// The decoded value of the length, or if the + /// encoded length represents the indefinite length. + /// + /// + /// is not a known value. + /// + /// + /// does not decode as a length under the specified encoding rules. + /// + /// + /// This method only processes the length portion of an ASN.1/BER Tag-Length-Value triplet, + /// so needs to have already sliced off the encoded tag. + /// + public static int? DecodeLength( + ReadOnlySpan source, + AsnEncodingRules ruleSet, + out int bytesConsumed) + { + CheckEncodingRules(ruleSet); + + // Use locals for the outs to hide the intermediate calculations from an out to a field. + int? ret = ReadLength(source, ruleSet, out int read); + bytesConsumed = read; + return ret; + } + + /// + /// Attempts to decode the data in as a length value under the specified + /// encoding rules. + /// + /// The buffer containing encoded data. + /// The encoding constraints to use when interpreting the data. + /// + /// When this method returns, the decoded value of the length, or if the + /// encoded length represents the indefinite length. + /// This parameter is treated as uninitialized. + /// + /// + /// When this method returns, the number of bytes from the beginning of + /// that contributed to the length. + /// This parameter is treated as uninitialized. + /// + /// + /// if the buffer represents a valid length under the specified encoding rules; + /// otherwise, + /// + /// + /// is not a known value. + /// + /// + /// This method only processes the length portion of an ASN.1/BER Tag-Length-Value triplet, + /// so needs to have already sliced off the encoded tag. + /// + public static bool TryDecodeLength( + ReadOnlySpan source, + AsnEncodingRules ruleSet, + out int? decodedLength, + out int bytesConsumed) + { + CheckEncodingRules(ruleSet); + + // Use locals for the outs to hide the intermediate calculations from an out to a field. + bool ret = TryReadLength(source, ruleSet, out int? decoded, out int read); + bytesConsumed = read; + decodedLength = decoded; + return ret; + } + private static bool TryReadLength( ReadOnlySpan source, AsnEncodingRules ruleSet, diff --git a/src/libraries/System.Formats.Asn1/tests/Reader/ReadLength.cs b/src/libraries/System.Formats.Asn1/tests/Reader/ReadLength.cs index e2238ce7c687b8..50dd7f4f95708d 100644 --- a/src/libraries/System.Formats.Asn1/tests/Reader/ReadLength.cs +++ b/src/libraries/System.Formats.Asn1/tests/Reader/ReadLength.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection; using Test.Cryptography; using Xunit; @@ -9,15 +8,46 @@ namespace System.Formats.Asn1.Tests.Reader { public sealed class ReadLength { - private delegate Asn1Tag ReadTagAndLengthDelegate( + private static Asn1Tag ReadTagAndLength( ReadOnlySpan source, AsnEncodingRules ruleSet, out int? parsedLength, - out int bytesRead); + out int bytesRead) + { + Asn1Tag tag = Asn1Tag.Decode(source, out int tagLength); + parsedLength = AsnDecoder.DecodeLength(source.Slice(tagLength), ruleSet, out int lengthLength); + bytesRead = tagLength + lengthLength; + return tag; + } + + private static bool TryReadTagAndLength( + ReadOnlySpan source, + AsnEncodingRules ruleSet, + out Asn1Tag tag, + out int? parsedLength, + out int bytesRead) + { + Asn1Tag localTag = Asn1Tag.Decode(source, out int tagLength); + + bool read = AsnDecoder.TryDecodeLength( + source.Slice(tagLength), + ruleSet, + out parsedLength, + out int lengthLength); + + if (read) + { + tag = localTag; + bytesRead = tagLength + lengthLength; + } + else + { + tag = default; + bytesRead = default; + } - private static ReadTagAndLengthDelegate ReadTagAndLength = (ReadTagAndLengthDelegate) - typeof(AsnDecoder).GetMethod("ReadTagAndLength", BindingFlags.Static | BindingFlags.NonPublic) - .CreateDelegate(typeof(ReadTagAndLengthDelegate)); + return read; + } [Theory] [InlineData(4, 0, "0400")] @@ -39,6 +69,13 @@ public static void MinimalPrimitiveLength(int tagValue, int length, string input Assert.False(tag.IsConstructed, "tag.IsConstructed"); Assert.Equal(tagValue, tag.TagValue); Assert.Equal(length, parsedLength.Value); + + Assert.True(TryReadTagAndLength(inputBytes, rules, out tag, out parsedLength, out bytesRead)); + + Assert.Equal(inputBytes.Length, bytesRead); + Assert.False(tag.IsConstructed, "tag.IsConstructed"); + Assert.Equal(tagValue, tag.TagValue); + Assert.Equal(length, parsedLength.Value); } } @@ -51,10 +88,64 @@ public static void ReadWithUnknownRuleSet(int invalidRuleSetValue) Assert.Throws( () => new AsnReader(data, (AsnEncodingRules)invalidRuleSetValue)); + + Assert.Throws( + () => ReadTagAndLength(data, (AsnEncodingRules)invalidRuleSetValue, out _, out _)); + + Assert.Throws( + () => TryReadTagAndLength(data, (AsnEncodingRules)invalidRuleSetValue, out _, out _, out _)); + } + + private static void ReadValid( + ReadOnlySpan source, + AsnEncodingRules ruleSet, + int? expectedLength, + int expectedBytesRead = -1) + { + if (expectedBytesRead < 0) + { + expectedBytesRead = source.Length; + } + + ReadTagAndLength( + source, + ruleSet, + out int? length, + out int bytesRead); + + Assert.Equal(expectedBytesRead, bytesRead); + Assert.Equal(expectedLength, length); + + bool read = TryReadTagAndLength( + source, + ruleSet, + out _, + out length, + out bytesRead); + + Assert.True(read); + Assert.Equal(expectedBytesRead, bytesRead); + Assert.Equal(expectedLength, length); + } + + private static void ReadInvalid(byte[] source, AsnEncodingRules ruleSet) + { + Assert.Throws( + () => ReadTagAndLength(source, ruleSet, out _, out _)); + + Asn1Tag tag; + int? decodedLength; + int bytesConsumed; + + Assert.False( + TryReadTagAndLength(source, ruleSet, out tag, out decodedLength, out bytesConsumed)); + + Assert.True(tag == default); + Assert.Null(decodedLength); + Assert.Equal(0, bytesConsumed); } [Theory] - [InlineData("")] [InlineData("05")] [InlineData("0481")] [InlineData("048201")] @@ -64,8 +155,9 @@ public static void ReadWithInsufficientData(string inputHex) { byte[] inputData = inputHex.HexToByteArray(); - Assert.Throws( - () => ReadTagAndLength(inputData, AsnEncodingRules.DER, out _, out _)); + ReadInvalid(inputData, AsnEncodingRules.BER); + ReadInvalid(inputData, AsnEncodingRules.CER); + ReadInvalid(inputData, AsnEncodingRules.DER); } [Theory] @@ -73,9 +165,6 @@ public static void ReadWithInsufficientData(string inputHex) [InlineData("0xFF-BER", AsnEncodingRules.BER, "04FF")] [InlineData("0xFF-CER", AsnEncodingRules.CER, "04FF")] [InlineData("0xFF-DER", AsnEncodingRules.DER, "04FF")] - [InlineData("CER definite constructed", AsnEncodingRules.CER, "30820500")] - [InlineData("BER indefinite primitive", AsnEncodingRules.BER, "0480" + "0000")] - [InlineData("CER indefinite primitive", AsnEncodingRules.CER, "0480" + "0000")] [InlineData("DER indefinite primitive", AsnEncodingRules.DER, "0480" + "0000")] [InlineData("DER non-minimal 0", AsnEncodingRules.DER, "048100")] [InlineData("DER non-minimal 7F", AsnEncodingRules.DER, "04817F")] @@ -102,10 +191,28 @@ public static void InvalidLengths( { _ = description; byte[] inputData = inputHex.HexToByteArray(); - AsnReader reader = new AsnReader(inputData, rules); - Assert.Throws( - () => ReadTagAndLength(inputData, rules, out _, out _)); + ReadInvalid(inputData, rules); + } + + [Theory] + [InlineData("CER definite constructed", AsnEncodingRules.CER, 0x0500, 4, "30820500")] + [InlineData("BER indefinite primitive", AsnEncodingRules.BER, null, 2, "0480" + "0000")] + [InlineData("CER indefinite primitive", AsnEncodingRules.CER, null, 2, "0480" + "0000")] + public static void ContextuallyInvalidLengths( + string description, + AsnEncodingRules rules, + int? expectedLength, + int expectedBytesRead, + string inputHex) + { + // These inputs will all throw from AsnDecoder.ReadTagAndLength, but require + // the tag as context. + + _ = description; + byte[] inputData = inputHex.HexToByteArray(); + + ReadValid(inputData, rules, expectedLength, expectedBytesRead); } [Theory] @@ -117,18 +224,8 @@ public static void IndefiniteLength(AsnEncodingRules ruleSet) // NULL // End-of-Contents byte[] data = { 0x30, 0x80, 0x05, 0x00, 0x00, 0x00 }; - AsnReader reader = new AsnReader(data, ruleSet); - - Asn1Tag tag = ReadTagAndLength( - data, - ruleSet, - out int? length, - out int bytesRead); - Assert.Equal(2, bytesRead); - Assert.False(length.HasValue, "length.HasValue"); - Assert.Equal((int)UniversalTagNumber.Sequence, tag.TagValue); - Assert.True(tag.IsConstructed, "tag.IsConstructed"); + ReadValid(data, ruleSet, null, 2); } [Theory] @@ -138,42 +235,25 @@ public static void IndefiniteLength(AsnEncodingRules ruleSet) public static void BerNonMinimalLength(int expectedLength, string inputHex) { byte[] inputData = inputHex.HexToByteArray(); - AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER); - Asn1Tag tag = ReadTagAndLength( - inputData, - AsnEncodingRules.BER, - out int? length, - out int bytesRead); - - Assert.Equal(inputData.Length, bytesRead); - Assert.Equal(expectedLength, length.Value); - // ReadTagAndLength doesn't move the _data span forward. - Assert.True(reader.HasData, "reader.HasData"); + ReadValid(inputData, AsnEncodingRules.BER, expectedLength); + ReadInvalid(inputData, AsnEncodingRules.CER); + ReadInvalid(inputData, AsnEncodingRules.DER); } [Theory] - [InlineData(AsnEncodingRules.BER, 4, 0, 5, "0483000000" + "0500")] - [InlineData(AsnEncodingRules.DER, 1, 1, 2, "0101" + "FF")] - [InlineData(AsnEncodingRules.CER, 0x10, null, 2, "3080" + "0500" + "0000")] + [InlineData(AsnEncodingRules.BER, 0, 5, "0483000000" + "0500")] + [InlineData(AsnEncodingRules.DER, 1, 2, "0101" + "FF")] + [InlineData(AsnEncodingRules.CER, null, 2, "3080" + "0500" + "0000")] public static void ReadWithDataRemaining( AsnEncodingRules ruleSet, - int tagValue, int? expectedLength, int expectedBytesRead, string inputHex) { byte[] inputData = inputHex.HexToByteArray(); - Asn1Tag tag = ReadTagAndLength( - inputData, - ruleSet, - out int? length, - out int bytesRead); - - Assert.Equal(expectedBytesRead, bytesRead); - Assert.Equal(tagValue, tag.TagValue); - Assert.Equal(expectedLength, length); + ReadValid(inputData, ruleSet, expectedLength, expectedBytesRead); } } } diff --git a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index e07ea71438a4fd..507e4d5a0fa34e 100644 --- a/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/libraries/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -7,7 +7,7 @@ - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browserornodejs/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj index 7715606fcfdd4d..eb974f44651a74 100644 --- a/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj +++ b/src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj @@ -7,7 +7,7 @@ - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browserornodejs/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs index b064ef28ee14b7..28bbf8c1326e3a 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CurrentUserOnly.cs @@ -131,7 +131,7 @@ public static void CreateMultipleServers_ConnectMultipleClients_MultipleThreads( })); } - Task.WaitAll(tasks.ToArray()); + Task.WaitAll(tasks); } [Theory] diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d2e1b54dcafa84..c0eda6ab1a57ed 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -1634,6 +1634,8 @@ public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http2(ITest } [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/91757")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/101015")] public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http3 : SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength { public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Http3(ITestOutputHelper output) : base(output) { } @@ -4024,7 +4026,6 @@ public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http2(ITestOutputH protected override Version UseVersion => HttpVersion.Version20; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3 : HttpClientHandlerTest { @@ -4032,7 +4033,6 @@ public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3(ITestOutputHelper outp protected override Version UseVersion => HttpVersion.Version30; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandlerTest_Cookies_Http3 : HttpClientHandlerTest_Cookies { @@ -4040,7 +4040,6 @@ public SocketsHttpHandlerTest_Cookies_Http3(ITestOutputHelper output) : base(out protected override Version UseVersion => HttpVersion.Version30; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3 : HttpClientHandlerTest_Headers { @@ -4048,7 +4047,6 @@ public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3(ITestOutputHel protected override Version UseVersion => HttpVersion.Version30; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3 : SocketsHttpHandler_Cancellation_Test { @@ -4056,7 +4054,6 @@ public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3(ITestOutputH protected override Version UseVersion => HttpVersion.Version30; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3 : HttpClientHandler_AltSvc_Test { @@ -4064,7 +4061,6 @@ public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3(ITestOutputHelper protected override Version UseVersion => HttpVersion.Version30; } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3 : HttpClientHandler_Finalization_Test { @@ -4328,7 +4324,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( }; policy.ExtraStore.AddRange(caCerts); - policy.CustomTrustStore.Add(caCerts[caCerts.Count -1]); + policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]); socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); @@ -4490,7 +4486,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => options: new GenericLoopbackOptions() { UseSsl = true }); } - + } public sealed class SocketsHttpHandler_HttpRequestErrorTest_Http11 : SocketsHttpHandler_HttpRequestErrorTest @@ -4533,7 +4529,6 @@ await Http11LoopbackServerFactory.Singleton.CreateClientAndServerAsync(async uri } } - [Collection(nameof(DisableParallelization))] [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))] public sealed class SocketsHttpHandler_HttpRequestErrorTest_Http30 : SocketsHttpHandler_HttpRequestErrorTest { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 26cc220014655f..fd09f7db0b2282 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -20,7 +20,7 @@ - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browserornodejs/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs index e97e4baaf8208a..d5962dfba4d096 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs @@ -16,8 +16,8 @@ using System.Net.Quic; using Microsoft.Quic; -[assembly:SupportedOSPlatform("windows")] -[assembly:SupportedOSPlatform("linux")] +[assembly: SupportedOSPlatform("windows")] +[assembly: SupportedOSPlatform("linux")] namespace HttpStress { @@ -186,24 +186,6 @@ private static async Task Run(Configuration config) Console.WriteLine("Query Parameters: " + config.MaxParameters); Console.WriteLine(); - if (config.HttpVersion == HttpVersion.Version30 && IsQuicSupported) - { - unsafe - { - // If the system gets overloaded, MsQuic has a tendency to drop incoming connections, see https://github.com/dotnet/runtime/issues/55979. - // So in case we're running H/3 stress test, we're using the same hack as for System.Net.Quic tests, which increases the time limit for pending operations in MsQuic thread pool. - object msQuicApiInstance = msQuicApiType.GetProperty("Api", BindingFlags.NonPublic | BindingFlags.Static)!.GetGetMethod(true)!.Invoke(null, Array.Empty())!; - QUIC_API_TABLE* apiTable = (QUIC_API_TABLE*)(Pointer.Unbox(msQuicApiType.GetProperty("ApiTable")!.GetGetMethod()!.Invoke(msQuicApiInstance, Array.Empty())!)); - QUIC_SETTINGS settings = default(QUIC_SETTINGS); - settings.IsSet.MaxWorkerQueueDelayUs = 1; - settings.MaxWorkerQueueDelayUs = 2_500_000u; // 2.5s, 10x the default - if (MsQuic.StatusFailed(apiTable->SetParam(null, MsQuic.QUIC_PARAM_GLOBAL_SETTINGS, (uint)sizeof(QUIC_SETTINGS), (byte*)&settings))) - { - Console.WriteLine($"Unable to set MsQuic MaxWorkerQueueDelayUs."); - } - } - } - StressServer? server = null; if (config.RunMode.HasFlag(RunMode.server)) { diff --git a/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj index 99f83f5fa17b4c..0cfd246889fa19 100644 --- a/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj +++ b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj @@ -41,6 +41,14 @@ Link="Common\System\Net\SocketProtocolSupportPal.Unix.cs" /> + + + + @@ -48,8 +56,14 @@ Link="Common\Interop\Unix\Interop.Errors.cs" /> + + + + diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs index d535734a2f7753..cef4453630b651 100644 --- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs +++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs @@ -102,6 +102,12 @@ private static Socket GetRawSocket(SocketConfig socketConfig) { // If it is not multicast, use Connect to scope responses only to the target address. socket.Connect(socketConfig.EndPoint); + unsafe + { + int opt = 1; + // setsockopt(fd, IPPROTO_IP, IP_RECVERR, &value, sizeof(int)) + socket.SetRawSocketOption(0, 11, new ReadOnlySpan(&opt, sizeof(int))); + } } #pragma warning restore 618 @@ -232,11 +238,12 @@ private static bool TryGetPingReply( return true; } - private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options) + private static unsafe PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options) { SocketConfig socketConfig = GetSocketConfig(address, buffer, timeout, options); using (Socket socket = GetRawSocket(socketConfig)) { + Span socketAddress = stackalloc byte[SocketAddress.GetMaximumAddressSize(address.AddressFamily)]; int ipHeaderLength = socketConfig.IsIpv4 ? MinIpHeaderLengthInBytes : 0; try { @@ -270,6 +277,29 @@ private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byt { return CreatePingReply(IPStatus.PacketTooBig); } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.HostUnreachable) + { + // This happens on Linux where we explicitly subscribed to error messages + // We should be able to get more info by getting extended socket error from error queue. + + Interop.Sys.MessageHeader header = default; + + SocketError result; + fixed (byte* sockAddr = &MemoryMarshal.GetReference(socketAddress)) + { + header.SocketAddress = sockAddr; + header.SocketAddressLen = socketAddress.Length; + header.IOVectors = null; + header.IOVectorCount = 0; + + result = Interop.Sys.ReceiveSocketError(socket.SafeHandle, &header); + } + + if (result == SocketError.Success && header.SocketAddressLen > 0) + { + return CreatePingReply(IPStatus.TtlExpired, IPEndPointExtensions.GetIPAddress(socketAddress.Slice(0, header.SocketAddressLen))); + } + } // We have exceeded our timeout duration, and no reply has been received. return CreatePingReply(IPStatus.TimedOut); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs index 829c279969c2ea..28c326b7b65fb0 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs @@ -62,7 +62,7 @@ private MsQuicApi(QUIC_API_TABLE* apiTable) internal static string? NotSupportedReason { get; } // workaround for https://github.com/microsoft/msquic/issues/4132 - internal static bool SupportsAsyncCertValidation => Version >= new Version(2, 4, 0); + internal static bool SupportsAsyncCertValidation => Version >= new Version(2, 3, 5); internal static bool UsesSChannelBackend { get; } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index b2042adffe33b4..41ac5e41da24de 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -357,10 +357,8 @@ public async Task UntrustedClientCertificateFails() } } - static bool SupportsAsyncCertValidation => QuicTestCollection.MsQuicVersion >= new Version(2, 4); - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/99074", typeof(MsQuicTests), nameof(SupportsAsyncCertValidation))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/99074")] public async Task CertificateCallbackThrowPropagates() { using CancellationTokenSource cts = new CancellationTokenSource(PassingTestTimeout); diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs index f8dd160acb00b7..5125c72e0ca8d1 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs @@ -15,7 +15,7 @@ namespace System.Net.Quic.Tests; -[CollectionDefinition(nameof(QuicTestCollection), DisableParallelization = true)] +[CollectionDefinition(nameof(QuicTestCollection))] public unsafe class QuicTestCollection : ICollectionFixture, IDisposable { public static bool IsSupported => QuicListener.IsSupported && QuicConnection.IsSupported; diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs index 27224b4e4be046..53437a62322521 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs @@ -105,7 +105,7 @@ internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _credentialsHan // This is TLS Resumed session. Windows can fail to query the local cert bellow. // Instead, we will determine the usage form used credentials. SafeFreeCredential_SECURITY creds = (SafeFreeCredential_SECURITY)_credentialsHandle!; - return creds.LocalCertificate != null; + return creds.HasLocalCertificate; } SafeFreeCertContext? localContext = null; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index db9a70e5a6bbe9..615ccd01167e3e 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -233,8 +233,7 @@ public static SafeFreeCredentials AcquireCredentialsHandle(SslAuthenticationOpti if (newCredentialsRequested && sslAuthenticationOptions.CertificateContext != null) { SafeFreeCredential_SECURITY handle = (SafeFreeCredential_SECURITY)cred; - // We need to create copy to avoid Disposal issue. - handle.LocalCertificate = new X509Certificate2(sslAuthenticationOptions.CertificateContext.TargetCertificate); + handle.HasLocalCertificate = true; } return cred; diff --git a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj index 2c7762cc6bc861..057726e2a8a8e6 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj +++ b/src/libraries/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj @@ -9,7 +9,7 @@ - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browserornodejs/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj index 4024dc05baff3c..c4b8a5a4545e83 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj +++ b/src/libraries/System.Net.WebSockets.Client/tests/wasm/System.Net.WebSockets.Client.Wasm.Tests.csproj @@ -9,7 +9,7 @@ --background-throttling - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browseronly/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 2bc7699526df53..c4504f140527a3 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4316,4 +4316,7 @@ Blocking wait is not supported on the JS interop threads. + + Emitting debug info is not supported for this member. + diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index 6007efe778ce21..9b18d4a125ed21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -418,7 +418,6 @@ internal ref TValue FindValue(TKey key) i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. do { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 // Test in if to drop range check for following array access if ((uint)i >= (uint)entries.Length) { @@ -450,7 +449,6 @@ internal ref TValue FindValue(TKey key) i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. do { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 // Test in if to drop range check for following array access if ((uint)i >= (uint)entries.Length) { @@ -535,15 +533,8 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) comparer == null) { // ValueType: Devirtualize with EqualityComparer.Default intrinsic - while (true) + while ((uint)i < (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - break; - } - if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) { if (behavior == InsertionBehavior.OverwriteExisting) @@ -574,15 +565,8 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) else { Debug.Assert(comparer is not null); - while (true) + while ((uint)i < (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - break; - } - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { if (behavior == InsertionBehavior.OverwriteExisting) @@ -690,15 +674,8 @@ internal static class CollectionsMarshalHelper comparer == null) { // ValueType: Devirtualize with EqualityComparer.Default intrinsic - while (true) + while ((uint)i < (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - break; - } - if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) { exists = true; @@ -720,15 +697,8 @@ internal static class CollectionsMarshalHelper else { Debug.Assert(comparer is not null); - while (true) + while ((uint)i < (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test uint in if rather than loop condition to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - break; - } - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { exists = true; diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 83ea307fc8570a..50a0d5b19d5acd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -323,28 +323,28 @@ public static unsafe bool Contains(this Span span, T value) where T : IEqu { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -367,28 +367,28 @@ public static unsafe bool Contains(this ReadOnlySpan span, T value) where { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -623,25 +623,25 @@ public static unsafe int IndexOf(this Span span, T value) where T : IEquat if (sizeof(T) == sizeof(byte)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(short)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(int)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(long)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } @@ -690,28 +690,28 @@ public static unsafe int LastIndexOf(this Span span, T value) where T : IE { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -826,28 +826,28 @@ public static unsafe int IndexOfAnyExcept(this ReadOnlySpan span, T value) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -873,16 +873,16 @@ public static unsafe int IndexOfAnyExcept(this ReadOnlySpan span, T value0 { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -909,18 +909,18 @@ public static unsafe int IndexOfAnyExcept(this ReadOnlySpan span, T value0 { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -937,20 +937,20 @@ private static unsafe int IndexOfAnyExcept(this ReadOnlySpan span, T value { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), + Unsafe.BitCast(value3), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), + Unsafe.BitCast(value3), span.Length); } } @@ -1135,28 +1135,28 @@ public static unsafe int LastIndexOfAnyExcept(this ReadOnlySpan span, T va { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -1182,16 +1182,16 @@ public static unsafe int LastIndexOfAnyExcept(this ReadOnlySpan span, T va { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -1218,18 +1218,18 @@ public static unsafe int LastIndexOfAnyExcept(this ReadOnlySpan span, T va { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -1246,20 +1246,20 @@ private static unsafe int LastIndexOfAnyExcept(this ReadOnlySpan span, T v { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), + Unsafe.BitCast(value3), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), + Unsafe.BitCast(value3), span.Length); } } @@ -1399,8 +1399,8 @@ public static int IndexOfAnyInRange(this ReadOnlySpan span, T lowInclusive { return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1408,8 +1408,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1417,8 +1417,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1426,8 +1426,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } } @@ -1465,8 +1465,8 @@ public static int IndexOfAnyExceptInRange(this ReadOnlySpan span, T lowInc { return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1474,8 +1474,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1483,8 +1483,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1492,8 +1492,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.IndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } } @@ -1531,8 +1531,8 @@ public static int LastIndexOfAnyInRange(this ReadOnlySpan span, T lowInclu { return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1540,8 +1540,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1549,8 +1549,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1558,8 +1558,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } } @@ -1597,8 +1597,8 @@ public static int LastIndexOfAnyExceptInRange(this ReadOnlySpan span, T lo { return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1606,8 +1606,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1615,8 +1615,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } @@ -1624,8 +1624,8 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), { return SpanHelpers.LastIndexOfAnyExceptInRangeUnsignedNumber( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref lowInclusive), - Unsafe.As(ref highInclusive), + Unsafe.BitCast(lowInclusive), + Unsafe.BitCast(highInclusive), span.Length); } } @@ -1702,25 +1702,25 @@ public static unsafe int IndexOf(this ReadOnlySpan span, T value) where T if (sizeof(T) == sizeof(byte)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(short)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(int)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); if (sizeof(T) == sizeof(long)) return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } @@ -1769,28 +1769,28 @@ public static unsafe int LastIndexOf(this ReadOnlySpan span, T value) wher { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(int)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (sizeof(T) == sizeof(long)) { return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } @@ -1844,16 +1844,16 @@ public static unsafe int IndexOfAny(this Span span, T value0, T value1) wh { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -1877,18 +1877,18 @@ public static unsafe int IndexOfAny(this Span span, T value0, T value1, T { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -1938,16 +1938,16 @@ public static unsafe int IndexOfAny(this ReadOnlySpan span, T value0, T va { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -1971,18 +1971,18 @@ public static unsafe int IndexOfAny(this ReadOnlySpan span, T value0, T va { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -2150,16 +2150,16 @@ public static unsafe int LastIndexOfAny(this Span span, T value0, T value1 { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -2183,18 +2183,18 @@ public static unsafe int LastIndexOfAny(this Span span, T value0, T value1 { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -2235,16 +2235,16 @@ public static unsafe int LastIndexOfAny(this ReadOnlySpan span, T value0, { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), span.Length); } } @@ -2268,18 +2268,18 @@ public static unsafe int LastIndexOfAny(this ReadOnlySpan span, T value0, { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } else if (sizeof(T) == sizeof(short)) { return SpanHelpers.LastIndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + Unsafe.BitCast(value0), + Unsafe.BitCast(value1), + Unsafe.BitCast(value2), span.Length); } } @@ -3382,8 +3382,8 @@ public static unsafe void Replace(this Span span, T oldValue, T newValue) SpanHelpers.ReplaceValueType( ref src, ref src, - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3394,8 +3394,8 @@ public static unsafe void Replace(this Span span, T oldValue, T newValue) SpanHelpers.ReplaceValueType( ref src, ref src, - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3405,8 +3405,8 @@ public static unsafe void Replace(this Span span, T oldValue, T newValue) SpanHelpers.ReplaceValueType( ref src, ref src, - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3416,8 +3416,8 @@ public static unsafe void Replace(this Span span, T oldValue, T newValue) SpanHelpers.ReplaceValueType( ref src, ref src, - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3469,8 +3469,8 @@ public static unsafe void Replace(this ReadOnlySpan source, Span destin SpanHelpers.ReplaceValueType( ref Unsafe.As(ref src), ref Unsafe.As(ref dst), - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3480,8 +3480,8 @@ ref Unsafe.As(ref dst), SpanHelpers.ReplaceValueType( ref Unsafe.As(ref src), ref Unsafe.As(ref dst), - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3490,8 +3490,8 @@ ref Unsafe.As(ref dst), SpanHelpers.ReplaceValueType( ref Unsafe.As(ref src), ref Unsafe.As(ref dst), - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3500,8 +3500,8 @@ ref Unsafe.As(ref dst), SpanHelpers.ReplaceValueType( ref Unsafe.As(ref src), ref Unsafe.As(ref dst), - Unsafe.As(ref oldValue), - Unsafe.As(ref newValue), + Unsafe.BitCast(oldValue), + Unsafe.BitCast(newValue), length); return; } @@ -3976,28 +3976,28 @@ public static int Count(this ReadOnlySpan span, T value) where T : IEquata { return SpanHelpers.CountValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (Unsafe.SizeOf() == sizeof(short)) { return SpanHelpers.CountValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (Unsafe.SizeOf() == sizeof(int)) { return SpanHelpers.CountValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } else if (Unsafe.SizeOf() == sizeof(long)) { return SpanHelpers.CountValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + Unsafe.BitCast(value), span.Length); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs index b56f69381450fc..826fda1e2c5a04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using System.Diagnostics.SymbolStore; namespace System.Reflection.Emit { @@ -200,6 +201,66 @@ public virtual LocalBuilder DeclareLocal(Type localType) [CLSCompliant(false)] public void Emit(OpCode opcode, sbyte arg) => Emit(opcode, (byte)arg); + /// + /// Marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// The document for which the sequence point is being defined. + /// The line where the sequence point begins. + /// The column in the line where the sequence point begins. + /// The line where the sequence point ends. + /// The column in the line where the sequence point ends. + /// is . + /// is not valid. + /// + /// is not within range [0, 0x20000000) or + /// is not within range [0, 0x20000000) or lower than or + /// is not within range [0, 0x10000) or + /// is not within range [0, 0x10000) or + /// equal to and it is not hidden sequence point and lower than or equal to . + /// + /// Emitting debug info is not supported." + public void MarkSequencePoint(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) + { + ArgumentNullException.ThrowIfNull(document); + + if (startLine < 0 || startLine >= 0x20000000) + { + throw new ArgumentOutOfRangeException(nameof(startLine)); + } + + if (endLine < 0 || endLine >= 0x20000000 || startLine > endLine) + { + throw new ArgumentOutOfRangeException(nameof(endLine)); + } + + if (startColumn < 0 || startColumn >= 0x10000) + { + throw new ArgumentOutOfRangeException(nameof(startColumn)); + } + + if (endColumn < 0 || endColumn >= 0x10000 || + (startLine == endLine && startLine != 0xfeefee && startColumn >= endColumn)) + { + throw new ArgumentOutOfRangeException(nameof(endColumn)); + } + + MarkSequencePointCore(document, startLine, startColumn, endLine, endColumn); + } + + /// + /// When overridden in a derived class, marks a sequence point in the Microsoft intermediate language (MSIL) stream. + /// + /// The document for which the sequence point is being defined. + /// The line where the sequence point begins. + /// The column in the line where the sequence point begins. + /// The line where the sequence point ends. + /// The column in the line where the sequence point ends. + /// is not valid. + /// Emitting debug info is not supported." + /// The parameters validated in the caller: . + protected virtual void MarkSequencePointCore(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) => + throw new NotSupportedException(SR.NotSupported_EmitDebugInfo); + #endregion #endregion diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs index 7c38c287bf22b5..e5e86ab883f615 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs @@ -12,5 +12,27 @@ public abstract class LocalBuilder : LocalVariableInfo /// This constructor is invoked by derived classes. /// protected LocalBuilder() { } + + /// + /// Sets the name of this local variable. + /// + /// The name of the local variable + /// The is null. + /// The containing type has been created with CreateType() or + /// containing type doesn't support symbol writing." + public void SetLocalSymInfo(string name) + { + ArgumentNullException.ThrowIfNull(name); + + SetLocalSymInfoCore(name); + } + + /// + /// When overridden in a derived class, sets the name of this local variable. + /// + /// The name of the local variable. + /// Emitting debug info is not supported." + /// The containing type has been created with CreateType()." + protected virtual void SetLocalSymInfoCore(string name) => throw new NotSupportedException(SR.NotSupported_EmitDebugInfo); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs index 21f68439ee3927..f97935b24d7bd9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ModuleBuilder.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; using System.Runtime.InteropServices; namespace System.Reflection.Emit @@ -24,6 +26,45 @@ public EnumBuilder DefineEnum(string name, TypeAttributes visibility, Type under return DefineEnumCore(name, visibility, underlyingType); } + /// + /// Defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This can be Empty + /// The GUID that identifies the document language vendor. This is not used. + /// The GUID that identifies the document language vendor. This is not used. + /// The defined document. + /// is . + /// This method is called on a dynamic module that is not a persisted module. + [EditorBrowsable(EditorBrowsableState.Never)] + public ISymbolDocumentWriter DefineDocument(string url, Guid language, Guid languageVendor, Guid documentType) => + DefineDocument(url, language); + + /// + /// Defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This is optional + /// The defined document. + /// is . + /// This method is called on a dynamic module that is not a persisted module. + public ISymbolDocumentWriter DefineDocument(string url, Guid language = default) + { + ArgumentException.ThrowIfNullOrEmpty(url); + + return DefineDocumentCore(url, language); + } + + /// + /// When override in a derived class, defines a document for source. + /// + /// The URL for the document. + /// The GUID that identifies the document language. This is optional + /// The defined document. + /// This method is called on a dynamic module that is not a debug module. + protected virtual ISymbolDocumentWriter DefineDocumentCore(string url, Guid language = default) => + throw new InvalidOperationException(SR.InvalidOperation_NotADebugModule); + protected abstract EnumBuilder DefineEnumCore(string name, TypeAttributes visibility, Type underlyingType); public MethodBuilder DefineGlobalMethod(string name, MethodAttributes attributes, Type? returnType, Type[]? parameterTypes) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index d33471813491ac..63471d9f919675 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -129,6 +129,9 @@ internal static bool IsPrimitiveType(this CorElementType et) [Intrinsic] internal static bool IsKnownConstant(char t) => false; + + [Intrinsic] + internal static bool IsKnownConstant(T t) where T : struct => false; #pragma warning restore IDE0060 } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs index 901d354cfc7c84..e59b30fd7633fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs @@ -242,15 +242,14 @@ public static bool AreSame([AllowNull] ref readonly T left, [AllowNull] ref r /// /// Reinterprets the given value of type as a value of type . /// - /// The size of and are not the same. + /// The sizes of and are not the same + /// or the type parameters are not s. [Intrinsic] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TTo BitCast(TFrom source) - where TFrom : struct - where TTo : struct { - if (sizeof(TFrom) != sizeof(TTo)) + if (sizeof(TFrom) != sizeof(TTo) || default(TFrom) is null || default(TTo) is null) { ThrowHelper.ThrowNotSupportedException(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs index 74ef8257749aa1..25bc1f195fa447 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs @@ -818,7 +818,6 @@ public static IntPtr GetHINSTANCE(Module m) }; } case HResults.FUSION_E_INVALID_NAME: - case HResults.FUSION_E_PRIVATE_ASM_DISALLOWED: case HResults.FUSION_E_REF_DEF_MISMATCH: case HResults.ERROR_TOO_MANY_OPEN_FILES: case HResults.ERROR_SHARING_VIOLATION: diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Packed.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Packed.cs index 99232b3a7f7d78..70d4e22acb5c9a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Packed.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Packed.cs @@ -27,7 +27,7 @@ public static unsafe bool CanUsePackedIndexOf(T value) Debug.Assert(RuntimeHelpers.IsBitwiseEquatable()); Debug.Assert(sizeof(T) == sizeof(ushort)); - return *(ushort*)&value - 1u < 254u; + return Unsafe.BitCast(value) - 1u < 254u; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index 0bf32e62744023..9ef07d114da67e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; #pragma warning disable 8500 // sizeof of managed types @@ -225,7 +224,7 @@ public static int IndexOf(ref T searchSpace, int searchSpaceLength, ref T val } // Adapted from IndexOf(...) - public static unsafe bool Contains(ref T searchSpace, T value, int length) where T : IEquatable? + public static bool Contains(ref T searchSpace, T value, int length) where T : IEquatable? { Debug.Assert(length >= 0); @@ -297,7 +296,7 @@ public static unsafe bool Contains(ref T searchSpace, T value, int length) wh return true; } - public static unsafe int IndexOf(ref T searchSpace, T value, int length) where T : IEquatable? + public static int IndexOf(ref T searchSpace, T value, int length) where T : IEquatable? { Debug.Assert(length >= 0); @@ -1304,11 +1303,11 @@ public static int SequenceCompareTo(ref T first, int firstLength, ref T secon } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe bool ContainsValueType(ref T searchSpace, T value, int length) where T : struct, INumber + internal static bool ContainsValueType(ref T searchSpace, T value, int length) where T : struct, INumber { if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value)) { - return PackedSpanHelpers.Contains(ref Unsafe.As(ref searchSpace), *(short*)&value, length); + return PackedSpanHelpers.Contains(ref Unsafe.As(ref searchSpace), Unsafe.BitCast(value), length); } return NonPackedContainsValueType(ref searchSpace, value, length); @@ -1478,15 +1477,15 @@ internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value, int => IndexOfValueType>(ref searchSpace, value, length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOfValueType(ref TValue searchSpace, TValue value, int length) + private static int IndexOfValueType(ref TValue searchSpace, TValue value, int length) where TValue : struct, INumber where TNegator : struct, INegator { if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value)) { return typeof(TNegator) == typeof(DontNegate) - ? PackedSpanHelpers.IndexOf(ref Unsafe.As(ref searchSpace), *(char*)&value, length) - : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), *(char*)&value, length); + ? PackedSpanHelpers.IndexOf(ref Unsafe.As(ref searchSpace), Unsafe.BitCast(value), length) + : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), Unsafe.BitCast(value), length); } return NonPackedIndexOfValueType(ref searchSpace, value, length); @@ -1665,24 +1664,33 @@ internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T => IndexOfAnyValueType>(ref searchSpace, value0, value1, length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) where TValue : struct, INumber where TNegator : struct, INegator { if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1)) { - if ((*(char*)&value0 ^ *(char*)&value1) == 0x20) + char char0 = Unsafe.BitCast(value0); + char char1 = Unsafe.BitCast(value1); + + if (RuntimeHelpers.IsKnownConstant(value0) && RuntimeHelpers.IsKnownConstant(value1)) { - char lowerCase = (char)Math.Max(*(char*)&value0, *(char*)&value1); + // If the values differ only in the 0x20 bit, we can optimize the search by reducing the number of comparisons. + // This optimization only applies to a small subset of values and the throughput difference is not too significant. + // We avoid introducing per-call overhead for non-constant values by guarding this optimization behind RuntimeHelpers.IsKnownConstant. + if ((char0 ^ char1) == 0x20) + { + char lowerCase = (char)Math.Max(char0, char1); - return typeof(TNegator) == typeof(DontNegate) - ? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As(ref searchSpace), lowerCase, length) - : PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As(ref searchSpace), lowerCase, length); + return typeof(TNegator) == typeof(DontNegate) + ? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As(ref searchSpace), lowerCase, length) + : PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As(ref searchSpace), lowerCase, length); + } } return typeof(TNegator) == typeof(DontNegate) - ? PackedSpanHelpers.IndexOfAny(ref Unsafe.As(ref searchSpace), *(char*)&value0, *(char*)&value1, length) - : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), *(char*)&value0, *(char*)&value1, length); + ? PackedSpanHelpers.IndexOfAny(ref Unsafe.As(ref searchSpace), char0, char1, length) + : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), char0, char1, length); } return NonPackedIndexOfAnyValueType(ref searchSpace, value0, value1, length); @@ -1882,15 +1890,15 @@ internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) where TValue : struct, INumber where TNegator : struct, INegator { if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1) && PackedSpanHelpers.CanUsePackedIndexOf(value2)) { return typeof(TNegator) == typeof(DontNegate) - ? PackedSpanHelpers.IndexOfAny(ref Unsafe.As(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length) - : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length); + ? PackedSpanHelpers.IndexOfAny(ref Unsafe.As(ref searchSpace), Unsafe.BitCast(value0), Unsafe.BitCast(value1), Unsafe.BitCast(value2), length) + : PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As(ref searchSpace), Unsafe.BitCast(value0), Unsafe.BitCast(value1), Unsafe.BitCast(value2), length); } return NonPackedIndexOfAnyValueType(ref searchSpace, value0, value1, value2, length); @@ -3466,15 +3474,15 @@ internal static int IndexOfAnyExceptInRangeUnsignedNumber(ref T searchSpace, IndexOfAnyInRangeUnsignedNumber>(ref searchSpace, lowInclusive, highInclusive, length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) + private static int IndexOfAnyInRangeUnsignedNumber(ref T searchSpace, T lowInclusive, T highInclusive, int length) where T : struct, IUnsignedNumber, IComparisonOperators where TNegator : struct, INegator { if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(ushort) && PackedSpanHelpers.CanUsePackedIndexOf(lowInclusive) && PackedSpanHelpers.CanUsePackedIndexOf(highInclusive) && highInclusive >= lowInclusive) { ref char charSearchSpace = ref Unsafe.As(ref searchSpace); - char charLowInclusive = *(char*)&lowInclusive; - char charRange = (char)(*(char*)&highInclusive - charLowInclusive); + char charLowInclusive = Unsafe.BitCast(lowInclusive); + char charRange = (char)(Unsafe.BitCast(highInclusive) - charLowInclusive); return typeof(TNegator) == typeof(DontNegate) ? PackedSpanHelpers.IndexOfAnyInRange(ref charSearchSpace, charLowInclusive, charRange, length) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index b8a2196ec69b30..fa03b43aff87cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -4681,14 +4681,13 @@ internal void RemoveContinuation(object continuationObject) // could be TaskCont [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static void WaitAll(params Task[] tasks) { -#if DEBUG - bool waitResult = -#endif - WaitAllCore(tasks, Timeout.Infinite, default); + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } -#if DEBUG + bool waitResult = WaitAllCore(tasks, Timeout.Infinite, default); Debug.Assert(waitResult, "expected wait to succeed"); -#endif } /// @@ -4730,6 +4729,11 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout); } + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } + return WaitAllCore(tasks, (int)totalMilliseconds, default); } @@ -4763,6 +4767,11 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static bool WaitAll(Task[] tasks, int millisecondsTimeout) { + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } + return WaitAllCore(tasks, millisecondsTimeout, default); } @@ -4792,6 +4801,11 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static void WaitAll(Task[] tasks, CancellationToken cancellationToken) { + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } + WaitAllCore(tasks, Timeout.Infinite, cancellationToken); } @@ -4831,18 +4845,48 @@ public static void WaitAll(Task[] tasks, CancellationToken cancellationToken) /// [UnsupportedOSPlatform("browser")] [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger - public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) => - WaitAllCore(tasks, millisecondsTimeout, cancellationToken); + public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (tasks is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } - // Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger - // to be able to see the method on the stack and inspect arguments). + return WaitAllCore(tasks, millisecondsTimeout, cancellationToken); + } + + /// Waits for all of the provided objects to complete execution unless the wait is cancelled. + /// An of Task instances on which to wait. + /// A to observe while waiting for the tasks to complete. + /// The argument is null. + /// The argument contains a null element. + /// One or more of the objects in tasks has been disposed. + /// The was canceled. + /// + /// At least one of the instances was canceled. If a task was canceled, the + /// contains an in its collection. + /// [UnsupportedOSPlatform("browser")] - private static bool WaitAllCore(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) + public static void WaitAll(IEnumerable tasks, CancellationToken cancellationToken = default) { - if (tasks == null) + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); } + + ReadOnlySpan span = + tasks is List list ? CollectionsMarshal.AsSpan(list) : + tasks is Task[] array ? array : + CollectionsMarshal.AsSpan(new List(tasks)); + + WaitAllCore(span, Timeout.Infinite, cancellationToken); + } + + // Separated out to allow it to be optimized (caller is marked NoOptimization for VS parallel debugger + // to be able to see the method on the stack and inspect arguments). + [UnsupportedOSPlatform("browser")] + private static bool WaitAllCore(ReadOnlySpan tasks, int millisecondsTimeout, CancellationToken cancellationToken) + { if (millisecondsTimeout < -1) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); @@ -4919,11 +4963,6 @@ private static bool WaitAllCore(Task[] tasks, int millisecondsTimeout, Cancellat if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1); } } - - // We need to prevent the tasks array from being GC'ed until we come out of the wait. - // This is necessary so that the Parallel Debugger can traverse it during the long wait and - // deduce waiter/waitee relationships - GC.KeepAlive(tasks); } // Now that we're done and about to exit, if the wait completed and if we have diff --git a/src/libraries/System.Private.Xml/tests/System.Private.Xml.Tests.csproj b/src/libraries/System.Private.Xml/tests/System.Private.Xml.Tests.csproj index 30fe5d7a261a46..24a6ecf7559f1f 100644 --- a/src/libraries/System.Private.Xml/tests/System.Private.Xml.Tests.csproj +++ b/src/libraries/System.Private.Xml/tests/System.Private.Xml.Tests.csproj @@ -10,7 +10,7 @@ - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browserornodejs/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs index f79ee4288df6ce..c5dcbd65fb1888 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs @@ -54,6 +54,8 @@ public virtual void EmitWriteLine(string value) { } public abstract void EndExceptionBlock(); public abstract void EndScope(); public abstract void MarkLabel(System.Reflection.Emit.Label loc); + public void MarkSequencePoint(System.Diagnostics.SymbolStore.ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) { throw null; } + protected virtual void MarkSequencePointCore(System.Diagnostics.SymbolStore.ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) { throw null; } public virtual void ThrowException([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] System.Type excType) { } public abstract void UsingNamespace(string usingNamespace); } @@ -73,6 +75,8 @@ protected LocalBuilder() { } public override bool IsPinned { get { throw null; } } public override int LocalIndex { get { throw null; } } public override System.Type LocalType { get { throw null; } } + public void SetLocalSymInfo(string name) { throw null; } + protected virtual void SetLocalSymInfoCore(string name) { throw null; } } public abstract partial class ParameterBuilder { diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj index 6894be6dab8f9f..4364c5c0b56ddf 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.csproj @@ -9,5 +9,6 @@ + diff --git a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs index 3690bf72a4c876..995ab5679fc736 100644 --- a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs +++ b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.cs @@ -398,6 +398,10 @@ protected ModuleBuilder() { } public override string ScopeName { get { throw null; } } public void CreateGlobalFunctions() { } protected abstract void CreateGlobalFunctionsCore(); + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocument(string url, Guid language, Guid languageVendor, Guid documentType) { throw null; } + public System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocument(string url, System.Guid language = default) { throw null; } + protected virtual System.Diagnostics.SymbolStore.ISymbolDocumentWriter DefineDocumentCore(string url, System.Guid language = default) { throw null; } public System.Reflection.Emit.EnumBuilder DefineEnum(string name, System.Reflection.TypeAttributes visibility, System.Type underlyingType) { throw null; } protected abstract System.Reflection.Emit.EnumBuilder DefineEnumCore(string name, System.Reflection.TypeAttributes visibility, System.Type underlyingType); public System.Reflection.Emit.MethodBuilder DefineGlobalMethod(string name, System.Reflection.MethodAttributes attributes, System.Reflection.CallingConventions callingConvention, System.Type? returnType, System.Type[]? parameterTypes) { throw null; } @@ -479,9 +483,11 @@ public PersistedAssemblyBuilder(System.Reflection.AssemblyName name, System.Refl public override System.Reflection.Module ManifestModule { get { throw null; } } [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] protected override System.Reflection.Emit.ModuleBuilder DefineDynamicModuleCore(string name) { throw null; } - protected override System.Reflection.Emit.ModuleBuilder? GetDynamicModuleCore(string name) { throw null; } [System.CLSCompliantAttribute(false)] public System.Reflection.Metadata.Ecma335.MetadataBuilder GenerateMetadata(out System.Reflection.Metadata.BlobBuilder ilStream, out System.Reflection.Metadata.BlobBuilder mappedFieldData) { throw null; } + [System.CLSCompliantAttribute(false)] + public System.Reflection.Metadata.Ecma335.MetadataBuilder GenerateMetadata(out System.Reflection.Metadata.BlobBuilder ilStream, out System.Reflection.Metadata.BlobBuilder mappedFieldData, out System.Reflection.Metadata.Ecma335.MetadataBuilder pdbBuilder) { throw null; } + protected override System.Reflection.Emit.ModuleBuilder? GetDynamicModuleCore(string name) { throw null; } public override System.Reflection.AssemblyName GetName(bool copiedName) { throw null; } public void Save(string assemblyFileName) { throw null; } public void Save(System.IO.Stream stream) { throw null; } diff --git a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj index 44f0feefe67db2..89a1505b49ceab 100644 --- a/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj +++ b/src/libraries/System.Reflection.Emit/ref/System.Reflection.Emit.csproj @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index 4ab50a83684c01..6103dbef1f159d 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -297,4 +297,10 @@ Not supported in an array method of a type definition that is not complete. + + Invalid source document. + + + Unmatching symbol scope. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj index 0c34ef45f44862..57b328a0bbde26 100644 --- a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj +++ b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj @@ -23,6 +23,8 @@ + + diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index dcb92654e124b7..d4b40387e30e20 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.SymbolStore; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -17,6 +18,8 @@ internal sealed class ILGeneratorImpl : ILGenerator private readonly BlobBuilder _builder; private readonly InstructionEncoder _il; private readonly ControlFlowBuilder _cfBuilder; + private readonly Scope _scope; // scope of the entire method body + private Scope _currentScope; private bool _hasDynamicStackAllocation; private int _maxStackDepth; private int _currentStackDepth; // Current stack labelStartDepth @@ -24,10 +27,11 @@ internal sealed class ILGeneratorImpl : ILGenerator // Adjustment to add to _maxStackDepth for incorrect/invalid IL. For example, when branch // instructions branches backward with non zero stack depths targeting the same label. private int _depthAdjustment; - private List _locals = new(); + private int _localCount; private Dictionary _labelTable = new(2); private List> _memberReferences = new(); private List _exceptionStack = new(); + private Dictionary> _documentToSequencePoints = new(); internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) { @@ -37,13 +41,18 @@ internal ILGeneratorImpl(MethodBuilderImpl methodBuilder, int size) _builder = new BlobBuilder(Math.Max(size, DefaultSize)); _cfBuilder = new ControlFlowBuilder(); _il = new InstructionEncoder(_builder, _cfBuilder); + _scope = new Scope(_il.Offset, parent: null); + _currentScope = _scope; } internal int GetMaxStack() => Math.Min(ushort.MaxValue, _maxStackDepth + _depthAdjustment); internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; - internal List Locals => _locals; + internal List Locals => _scope.GetAllLocals(); + internal int LocalCount => _localCount; + internal Scope Scope => _scope; + internal Dictionary> DocumentToSequencePoints => _documentToSequencePoints; public override int ILOffset => _il.Offset; @@ -198,16 +207,19 @@ public override void BeginFinallyBlock() public override void BeginScope() { - // TODO: No-op, will be implemented wit PDB support + _currentScope._children ??= new List(); + Scope newScope = new Scope(_il.Offset, _currentScope); + _currentScope._children.Add(newScope); + _currentScope = newScope; } public override LocalBuilder DeclareLocal(Type localType, bool pinned) { ArgumentNullException.ThrowIfNull(localType); - LocalBuilder local = new LocalBuilderImpl(_locals.Count, localType, _methodBuilder, pinned); - _locals.Add(local); - + _currentScope._locals ??= new List(); + LocalBuilder local = new LocalBuilderImpl(_localCount++, localType, _methodBuilder, pinned); + _currentScope._locals.Add(local); return local; } @@ -731,7 +743,13 @@ public override void EndExceptionBlock() public override void EndScope() { - // TODO: No-op, will be implemented wit PDB support + if (_currentScope._parent == null) + { + throw new InvalidOperationException(SR.InvalidOperation_UnmatchingSymScope); + } + + _currentScope._endOffset = _il.Offset; + _currentScope = _currentScope._parent; } public override void MarkLabel(Label loc) @@ -777,9 +795,32 @@ public override void MarkLabel(Label loc) } } + protected override void MarkSequencePointCore(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) + { + if (document is SymbolDocumentWriter symbolDoc) + { + if (_documentToSequencePoints.TryGetValue(symbolDoc, out List? sequencePoints)) + { + sequencePoints.Add(new SequencePoint(_il.Offset, startLine, startColumn, endLine, endColumn)); + } + else + { + sequencePoints = new List { new SequencePoint(_il.Offset, startLine, startColumn, endLine, endColumn) }; + _documentToSequencePoints.Add(symbolDoc, sequencePoints); + } + } + else + { + throw new ArgumentException(SR.InvalidOperation_InvalidDocument, nameof(document)); + } + } + public override void UsingNamespace(string usingNamespace) { - // TODO: No-op, will be implemented wit PDB support + ArgumentException.ThrowIfNullOrEmpty(usingNamespace); + + _currentScope._importNamespaces ??= new List(); + _currentScope._importNamespaces.Add(usingNamespace); } } @@ -817,4 +858,40 @@ internal LabelInfo(LabelHandle metaLabel) internal int _startDepth; // Stack labelStartDepth, with -1 meaning unknown. internal LabelHandle _metaLabel; } + + internal sealed class Scope + { + internal Scope(int offset, Scope? parent) + { + _startOffset = offset; + _parent = parent; + } + + internal Scope? _parent; + internal List? _children; + internal List? _locals; + internal List? _importNamespaces; + internal int _startOffset; + internal int _endOffset; + + internal List GetAllLocals() + { + List locals = new List(); + + if (_locals != null) + { + locals.AddRange(_locals); + } + + if (_children != null) + { + foreach (Scope child in _children) + { + locals.AddRange(child.GetAllLocals()); + } + } + + return locals; + } + } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs index fa9bffbec4345c..406d0812047633 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs @@ -10,6 +10,7 @@ internal sealed class LocalBuilderImpl : LocalBuilder private readonly Type _localType; private readonly MethodInfo _method; private readonly bool _isPinned; + private string? _name; #endregion #region Constructor @@ -24,6 +25,19 @@ internal LocalBuilderImpl(int index, Type type, MethodInfo method, bool isPinned #region Internal Members internal MethodInfo GetMethodBuilder() => _method; + internal string? Name => _name; + #endregion + + #region LocalBuilder Override + protected override void SetLocalSymInfoCore(string name) + { + if (_method.DeclaringType is TypeBuilder typeBuilder && typeBuilder.IsCreated()) + { + throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); + } + + _name = name; + } #endregion #region LocalVariableInfo Override diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index 0349186b717fef..b78526adda13cd 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; @@ -25,6 +26,7 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private readonly Guid _moduleVersionId; private Dictionary? _moduleReferences; private List? _customAttributes; + private Dictionary _docHandles = new(); private int _nextTypeDefRowId = 1; private int _nextMethodDefRowId = 1; private int _nextFieldDefRowId = 1; @@ -34,6 +36,7 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private bool _coreTypesFullyPopulated; private bool _hasGlobalBeenCreated; private Type?[]? _coreTypes; + private MetadataBuilder _pdbBuilder = new(); private static readonly Type[] s_coreTypes = { typeof(void), typeof(object), typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(string), typeof(nint), typeof(nuint), typeof(TypedReference) }; @@ -108,7 +111,7 @@ internal Type GetTypeFromCoreAssembly(CoreTypeId typeId) return null; } - internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuilder fieldDataBuilder) + internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuilder fieldDataBuilder, out MetadataBuilder pdbBuilder) { // Add module metadata ModuleDefinitionHandle moduleHandle = _metadataBuilder.AddModule( @@ -190,6 +193,8 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder, BlobBuil { AddGenericTypeParametersAndConstraintsCustomAttributes(param._parentHandle, param); } + + pdbBuilder = _pdbBuilder; } private void WriteInterfaceImplementations(TypeBuilderImpl typeBuilder, TypeDefinitionHandle typeHandle) @@ -339,7 +344,8 @@ private void PopulateTokensForTypesAndItsMembers() } } - private void WriteMethods(List methods, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + private void WriteMethods(List methods, List genericParams, + MethodBodyStreamEncoder methodBodyEncoder) { foreach (MethodBuilderImpl method in methods) { @@ -348,9 +354,10 @@ private void WriteMethods(List methods, List methods, List>.Enumerator enumerator = il.DocumentToSequencePoints.GetEnumerator(); + if (il.DocumentToSequencePoints.Count > 1) + { + // sequence points spans multiple docs + _pdbBuilder.AddMethodDebugInformation(default, PopulateMultiDocSequencePointsBlob(enumerator, localSignatureHandle)); + } + else // single document sequence point + { + int previousNonHiddenStartLine = -1; + int previousNonHiddenStartColumn = -1; + enumerator.MoveNext(); + BlobBuilder spBlobBuilder = new BlobBuilder(); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(localSignatureHandle)); + PopulateSequencePointsBlob(spBlobBuilder, enumerator.Current.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + _pdbBuilder.AddMethodDebugInformation(GetDocument(enumerator.Current.Key), _pdbBuilder.GetOrAddBlob(spBlobBuilder)); + } + } + + Scope scope = il.Scope; + scope._endOffset = il.ILOffset; // Outer most scope covers the entire method body, so haven't closed by the user. + + AddLocalScope(methodHandle, parentImport: default, MetadataTokens.LocalVariableHandle(_pdbBuilder.GetRowCount(TableIndex.LocalVariable) + 1), scope); + } + + private BlobHandle PopulateMultiDocSequencePointsBlob(Dictionary>.Enumerator enumerator, StandaloneSignatureHandle localSignature) + { + BlobBuilder spBlobBuilder = new BlobBuilder(); + int previousNonHiddenStartLine = -1; + int previousNonHiddenStartColumn = -1; + enumerator.MoveNext(); + KeyValuePair> pair = enumerator.Current; + + // header: + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(localSignature)); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetDocument(pair.Key))); + + // First sequence point record + PopulateSequencePointsBlob(spBlobBuilder, pair.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + + while (enumerator.MoveNext()) + { + pair = enumerator.Current; + spBlobBuilder.WriteCompressedInteger(0); + spBlobBuilder.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetDocument(pair.Key))); + PopulateSequencePointsBlob(spBlobBuilder, pair.Value, ref previousNonHiddenStartLine, ref previousNonHiddenStartColumn); + } + + return _pdbBuilder.GetOrAddBlob(spBlobBuilder); + } + + private static void PopulateSequencePointsBlob(BlobBuilder spBlobBuilder, List sequencePoints, ref int previousNonHiddenStartLine, ref int previousNonHiddenStartColumn) + { + for (int i = 0; i < sequencePoints.Count; i++) + { + // IL offset delta: + if (i > 0) + { + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].Offset - sequencePoints[i - 1].Offset); + } + else + { + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].Offset); + } + + if (sequencePoints[i].IsHidden) + { + spBlobBuilder.WriteUInt16(0); + continue; + } + + // Delta Lines & Columns: + SerializeDeltaLinesAndColumns(spBlobBuilder, sequencePoints[i]); + + // delta Start Lines & Columns: + if (previousNonHiddenStartLine < 0) + { + Debug.Assert(previousNonHiddenStartColumn < 0); + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].StartLine); + spBlobBuilder.WriteCompressedInteger(sequencePoints[i].StartColumn); + } + else + { + spBlobBuilder.WriteCompressedSignedInteger(sequencePoints[i].StartLine - previousNonHiddenStartLine); + spBlobBuilder.WriteCompressedSignedInteger(sequencePoints[i].StartColumn - previousNonHiddenStartColumn); + } + + previousNonHiddenStartLine = sequencePoints[i].StartLine; + previousNonHiddenStartColumn = sequencePoints[i].StartColumn; + } + } + + private void AddLocalScope(MethodDefinitionHandle methodHandle, ImportScopeHandle parentImport, LocalVariableHandle firstLocalVariableHandle, Scope scope) + { + parentImport = GetImportScopeHandle(scope._importNamespaces, parentImport); + firstLocalVariableHandle = GetLocalVariableHandle(scope._locals, firstLocalVariableHandle); + _pdbBuilder.AddLocalScope(methodHandle, parentImport, firstLocalVariableHandle, + constantList: MetadataTokens.LocalConstantHandle(1), scope._startOffset, scope._endOffset - scope._startOffset); + + if (scope._children != null) + { + foreach (Scope childScope in scope._children) + { + AddLocalScope(methodHandle, parentImport, MetadataTokens.LocalVariableHandle(_pdbBuilder.GetRowCount(TableIndex.LocalVariable) + 1), childScope); + } + } + } + + private LocalVariableHandle GetLocalVariableHandle(List? locals, LocalVariableHandle firstLocalHandleOfLastScope) + { + if (locals != null) + { + bool firstLocalSet = false; + foreach (LocalBuilderImpl local in locals) + { + if (!string.IsNullOrEmpty(local.Name)) + { + LocalVariableHandle localHandle = _pdbBuilder.AddLocalVariable(LocalVariableAttributes.None, local.LocalIndex, + local.Name == null ? _pdbBuilder.GetOrAddString(string.Empty) : _pdbBuilder.GetOrAddString(local.Name)); + if (!firstLocalSet) + { + firstLocalHandleOfLastScope = localHandle; + firstLocalSet = true; + } + } + } + } + + return firstLocalHandleOfLastScope; + } + + private ImportScopeHandle GetImportScopeHandle(List? importNamespaces, ImportScopeHandle parent) + { + if (importNamespaces == null) + { + return default; + } + + BlobBuilder importBlob = new BlobBuilder(); + + foreach (string importNs in importNamespaces) + { + importBlob.WriteByte((byte)ImportDefinitionKind.ImportNamespace); + importBlob.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_pdbBuilder.GetOrAddBlobUTF8(importNs))); + } + + return _pdbBuilder.AddImportScope(parent, _pdbBuilder.GetOrAddBlob(importBlob)); + } + + private static void SerializeDeltaLinesAndColumns(BlobBuilder spBuilder, SequencePoint sequencePoint) + { + int deltaLines = sequencePoint.EndLine - sequencePoint.StartLine; + int deltaColumns = sequencePoint.EndColumn - sequencePoint.StartColumn; + + // only hidden sequence points have zero width + Debug.Assert(deltaLines != 0 || deltaColumns != 0 || sequencePoint.IsHidden); + + spBuilder.WriteCompressedInteger(deltaLines); + + if (deltaLines == 0) + { + spBuilder.WriteCompressedInteger(deltaColumns); + } + else + { + Debug.Assert(deltaLines > 0); + spBuilder.WriteCompressedSignedInteger(deltaColumns); + } + } + + private DocumentHandle GetDocument(SymbolDocumentWriter docWriter) + { + if (!_docHandles.TryGetValue(docWriter, out DocumentHandle handle)) + { + handle = AddDocument(docWriter.URL, docWriter.Language, docWriter.HashAlgorithm, docWriter.Hash); + _docHandles.Add(docWriter, handle); + } + + return handle; + } + + private DocumentHandle AddDocument(string url, Guid language, Guid hashAlgorithm, byte[]? hash) => + _pdbBuilder.AddDocument( + name: _pdbBuilder.GetOrAddDocumentName(url), + hashAlgorithm: hashAlgorithm == default ? default : _pdbBuilder.GetOrAddGuid(hashAlgorithm), + hash: hash == null ? default : _metadataBuilder.GetOrAddBlob(hash), + language: language == default ? default : _pdbBuilder.GetOrAddGuid(language)); + private void FillMemberReferences(ILGeneratorImpl il) { foreach (KeyValuePair pair in il.GetMemberReferences()) @@ -892,8 +1095,7 @@ private EntityHandle GetHandleForMember(MemberInfo member) } private static bool IsConstructedFromTypeBuilder(Type type) => type.IsConstructedGenericType && - (type.GetGenericTypeDefinition() is TypeBuilderImpl || - ContainsTypeBuilder(type.GetGenericArguments())); + (type.GetGenericTypeDefinition() is TypeBuilderImpl || ContainsTypeBuilder(type.GetGenericArguments())); internal static bool ContainsTypeBuilder(Type[] genericArguments) { @@ -1158,5 +1360,10 @@ private static SignatureCallingConvention GetSignatureConvention(CallingConventi CallingConvention.FastCall => SignatureCallingConvention.FastCall, _ => SignatureCallingConvention.Default, }; + + protected override ISymbolDocumentWriter DefineDocumentCore(string url, Guid language = default) + { + return new SymbolDocumentWriter(url, language); + } } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs new file mode 100644 index 00000000000000..f82fea8986ff2f --- /dev/null +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SequencePoint.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Emit +{ + internal sealed class SequencePoint + { + public const int HiddenLine = 0xfeefee; + + public int Offset { get; } + public int StartLine { get; } + public int EndLine { get; } + public int StartColumn { get; } + public int EndColumn { get; } + + public SequencePoint(int offset, int startLine, int startColumn, int endLine, int endColumn) + { + Offset = offset; + StartLine = startLine; + EndLine = endLine; + StartColumn = startColumn; + EndColumn = endColumn; + } + + public bool IsHidden => StartLine == HiddenLine; + } +} diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs new file mode 100644 index 00000000000000..a07b44620fa85f --- /dev/null +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/Pdb/SymbolDocumentWriter.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.SymbolStore; + +namespace System.Reflection.Emit +{ + internal sealed class SymbolDocumentWriter : ISymbolDocumentWriter + { + internal readonly Guid _language; + internal readonly string _url; + private Guid _hashAlgorithm; + private byte[]? _hash; + private byte[]? _source; + + internal string URL => _url; + internal Guid Language => _language; + internal Guid HashAlgorithm => _hashAlgorithm; + internal byte[]? Hash => _hash; + internal byte[]? Source => _source; + + public SymbolDocumentWriter(string url, Guid language) + { + _language = language; + _url = url; + } + + public void SetCheckSum(Guid algorithmId, byte[] checkSum) + { + _hashAlgorithm = algorithmId; + _hash = checkSum; + } + + public void SetSource(byte[] source) + { + _source = source; + } + } +} diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs index 12fbf4704b7bac..ad127094936ee4 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/PersistedAssemblyBuilder.cs @@ -98,11 +98,10 @@ private void SaveInternal(Stream stream) { ArgumentNullException.ThrowIfNull(stream); - PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData); + PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out _); WritePEImage(stream, ilStream, fieldData); } - /// /// Generates the metadata for the . /// @@ -110,16 +109,33 @@ private void SaveInternal(Stream stream) /// Outputs bytes that includes all field RVA data defined in the assembly. /// A that includes all members defined in the Assembly. /// A module not defined for the assembly. - /// The metadata already populated for the assembly before. + /// The metadata already populated for the assembly previously. [CLSCompliant(false)] public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData) { - PopulateAssemblyMetadata(out ilStream, out mappedFieldData); + PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out _); + + return _metadataBuilder; + } + + /// + /// Generates the metadata for the . + /// + /// Outputs bytes that includes all method's IL (body) emitted. + /// Outputs bytes that includes all field RVA data defined in the assembly. + /// Outputs that includes PDB metadata. + /// A that includes all members defined in the Assembly. + /// A module not defined for the assembly. + /// The metadata already populated for the assembly previously. + [CLSCompliant(false)] + public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData, out MetadataBuilder pdbBuilder) + { + PopulateAssemblyMetadata(out ilStream, out mappedFieldData, out pdbBuilder); return _metadataBuilder; } - private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData) + private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, out MetadataBuilder pdbBuilder) { if (_module == null) { @@ -147,7 +163,7 @@ private void PopulateAssemblyMetadata(out BlobBuilder ilStream, out BlobBuilder ); _module.WriteCustomAttributes(_customAttributes, assemblyHandle); - _module.AppendMetadata(new MethodBodyStreamEncoder(ilStream), fieldData); + _module.AppendMetadata(new MethodBodyStreamEncoder(ilStream), fieldData, out pdbBuilder); _isMetadataPopulated = true; } diff --git a/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs b/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs new file mode 100644 index 00000000000000..abdbf5e4047b58 --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/PortablePdb/ILGeneratorScopesAndSequencePointsTests.cs @@ -0,0 +1,372 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.SymbolStore; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public class ILGeneratorScopesAndSequencePointsTests + { + [Fact] + public void SetLocalSymInfo_UsingNamespace_Validations() + { + ModuleBuilder mb = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly2"), typeof(object).Assembly).DefineDynamicModule("MyModule2"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ILGenerator il = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static).GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + Assert.Throws("usingNamespace", () => il.UsingNamespace(null)); + Assert.Throws("usingNamespace", () => il.UsingNamespace(string.Empty)); + Assert.Throws("name", () => local.SetLocalSymInfo(null)); + il.Emit(OpCodes.Ret); + tb.CreateType(); + Assert.Throws(() => local.SetLocalSymInfo("myInt1")); // type created + } + + [Fact] + public void LocalWithoutSymInfoWillNotAddedToLocalVariablesTable() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il = method.GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("myInt1"); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + LocalBuilder local2 = il.DeclareLocal(typeof(string)); + il.Emit(OpCodes.Ldstr, "MyAssembly"); + il.Emit(OpCodes.Stloc, local2); + LocalBuilder local3 = il.DeclareLocal(typeof(int)); + local3.SetLocalSymInfo("myInt2"); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Stloc_2); + il.Emit(OpCodes.Ldloc_2); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + MethodDebugInformation mdi = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + SequencePointCollection.Enumerator spcEnumerator = mdi.GetSequencePoints().GetEnumerator(); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + [Fact] + public void LocalsNamespacesWithinNestedScopes() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il = method.GetILGenerator(); + LocalBuilder local = il.DeclareLocal(typeof(int)); + il.UsingNamespace("System"); + local.SetLocalSymInfo("myInt1"); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + LocalBuilder local2 = il.DeclareLocal(typeof(string)); + local2.SetLocalSymInfo("myString"); + il.Emit(OpCodes.Ldstr, "MyAssembly"); + il.Emit(OpCodes.Stloc, local2); + il.BeginScope(); + il.UsingNamespace("System.Reflection"); + LocalBuilder local3 = il.DeclareLocal(typeof(AssemblyName)); + local3.SetLocalSymInfo("myAssembly"); + il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Newobj, typeof(AssemblyName).GetConstructor([typeof(string)])); + il.Emit(OpCodes.Stloc_2); + il.BeginScope(); + LocalBuilder local4 = il.DeclareLocal(typeof(int)); + local4.SetLocalSymInfo("myInt2"); + LocalBuilder local5 = il.DeclareLocal(typeof(int)); + local5.SetLocalSymInfo("myInt3"); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Stloc_3); + il.Emit(OpCodes.Ldc_I4_5); + il.Emit(OpCodes.Stloc_S, 4); + il.Emit(OpCodes.Ldloc_S, 4); + il.Emit(OpCodes.Ldloc_3); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc_0); + il.EndScope(); + il.UsingNamespace("System.Reflection.Emit"); + il.EndScope(); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + MethodDebugInformation mdi = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + SequencePointCollection.Enumerator spcEnumerator = mdi.GetSequencePoints().GetEnumerator(); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(35, localScope.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myString", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + ImportScope importScope = reader.GetImportScope(localScope.ImportScope); + Assert.True(importScope.Parent.IsNil); + ImportDefinitionCollection.Enumerator importEnumerator = importScope.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + ImportDefinition importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + BlobReader blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System", blobReader.ReadUTF8(blobReader.Length)); + Assert.False(importEnumerator.MoveNext()); + + Assert.True(localScopes.MoveNext()); + LocalScope innerScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(10, innerScope.StartOffset); + Assert.Equal(33, innerScope.EndOffset); + localEnumerator = innerScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myAssembly", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + ImportScope innerImport = reader.GetImportScope(innerScope.ImportScope); + Assert.Equal(importScope, reader.GetImportScope(innerImport.Parent)); + importEnumerator = innerImport.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection", blobReader.ReadUTF8(blobReader.Length)); + Assert.True(importEnumerator.MoveNext()); + importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection.Emit", blobReader.ReadUTF8(blobReader.Length)); + Assert.False(importEnumerator.MoveNext()); + + Assert.True(localScopes.MoveNext()); + LocalScope innerMost = reader.GetLocalScope(localScopes.Current); + Assert.Equal(17, innerMost.StartOffset); + Assert.Equal(33, innerMost.EndOffset); + localEnumerator = innerMost.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("myInt3", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + + Assert.True(innerMost.ImportScope.IsNil); + } + + [Fact] + public void DefineDocument_MarkSequencePoint_Validations() + { + ModuleBuilder runtimeModule = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("T"), AssemblyBuilderAccess.Run).DefineDynamicModule("T"); + Assert.Throws(() => runtimeModule.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp)); + + ModuleBuilder mb = new PersistedAssemblyBuilder(new AssemblyName("Assembly"), typeof(object).Assembly).DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("Type", TypeAttributes.Public | TypeAttributes.Class); + Assert.Throws("url", () => mb.DefineDocument(null)); + Assert.Throws("url", () => mb.DefineDocument(string.Empty)); + + ILGenerator il = tb.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static).GetILGenerator(); + Assert.Throws("document", () => il.MarkSequencePoint(null, 0, 0, 0, 1)); + Assert.Throws("document", () => il.MarkSequencePoint(new TestDocument(), 0, 0, 0, 1)); + Assert.Throws("startLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), -1, 1, 1, 1)); + Assert.Throws("startColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, -1, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, -1, 1)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, -1)); + Assert.Throws("startLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 0x20000000, 1, 1, 1)); + Assert.Throws("startColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 0x10000, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 0x20000000, 1)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, 0x10000)); + Assert.Throws("endColumn", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 1, 1)); + Assert.Throws("endLine", () => il.MarkSequencePoint(mb.DefineDocument("MySourceFile.cs"), 1, 1, 0, 1)); + } + + [Fact] + public void MultipleDocumentsAndSequencePoints() + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcDoc1 = mb.DefineDocument("MySource1.cs", SymLanguageType.CSharp); + ISymbolDocumentWriter srcDoc2 = mb.DefineDocument("MySource2.cs", SymLanguageType.CSharp); + ISymbolDocumentWriter srcDoc3 = mb.DefineDocument("MySource3.cs", SymLanguageType.CSharp); + MethodBuilder method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il1 = method.GetILGenerator(); + LocalBuilder local = il1.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyInt"); + il1.MarkSequencePoint(srcDoc2, 7, 0, 7, 20); + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Ldarg_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc1, 8, 0, 9, 18); + il1.Emit(OpCodes.Ldc_I4_2); + il1.Emit(OpCodes.Stloc_1); + il1.MarkSequencePoint(srcDoc1, 0xfeefee, 0, 0xfeefee, 0); // hidden sequence point + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc1, 11, 1, 11, 20); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcDoc3, 5, 0, 5, 20); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ret); + + MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il2 = entryPoint.GetILGenerator(); + il2.Emit(OpCodes.Ldc_I4_S, 10); + il2.Emit(OpCodes.Ldc_I4_1); + il2.Emit(OpCodes.Call, method); + il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])); + il2.Emit(OpCodes.Ret); + tb.CreateType(); + + MetadataBuilder mdb = ab.GenerateMetadata(out BlobBuilder _, out BlobBuilder _, out MetadataBuilder pdbMetadata); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, mdb.GetRowCounts(), default); + pdbBuilder.Serialize(portablePdbBlob); + using TempFile pdbFile = TempFile.Create(); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + MetadataReader reader = provider.GetMetadataReader(); + DocumentHandleCollection.Enumerator docEnumerator = reader.Documents.GetEnumerator(); + Assert.Equal(3, reader.Documents.Count); + Assert.True(docEnumerator.MoveNext()); + Document doc1 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource2.cs", reader.GetString(doc1.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc1.Language)); + Assert.True(docEnumerator.MoveNext()); + Document doc2 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource1.cs", reader.GetString(doc2.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc2.Language)); + Assert.True(docEnumerator.MoveNext()); + Document doc3 = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySource3.cs", reader.GetString(doc3.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc3.Language)); + Assert.False(docEnumerator.MoveNext()); + + MethodDebugInformation mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + Assert.True(mdi1.Document.IsNil); + SequencePointCollection.Enumerator spcEnumerator = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spcEnumerator.MoveNext()); + SequencePoint sp = spcEnumerator.Current; + Assert.Equal(doc1, reader.GetDocument(sp.Document)); + Assert.Equal(7, sp.StartLine); + Assert.False(sp.IsHidden); + Assert.Equal(0, sp.Offset); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(7, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.Equal(4, sp.Offset); + Assert.Equal(8, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(9, sp.EndLine); + Assert.Equal(18, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.True(sp.IsHidden); + Assert.Equal(6, sp.Offset); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc2, reader.GetDocument(sp.Document)); + Assert.Equal(10, sp.Offset); + Assert.Equal(11, sp.StartLine); + Assert.Equal(1, sp.StartColumn); + Assert.Equal(11, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spcEnumerator.MoveNext()); + sp = spcEnumerator.Current; + Assert.Equal(doc3, reader.GetDocument(sp.Document)); + Assert.Equal(24, sp.Offset); + Assert.Equal(5, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(5, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.False(spcEnumerator.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope locals = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, locals.StartOffset); + Assert.Equal(16, locals.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = locals.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + private class TestDocument : ISymbolDocumentWriter + { + public void SetCheckSum(Guid algorithmId, byte[] checkSum) => throw new NotImplementedException(); + public void SetSource(byte[] source) => throw new NotImplementedException(); + } + } +} diff --git a/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs b/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs new file mode 100644 index 00000000000000..e1ce56f151d81c --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/PortablePdb/PortablePdbStandalonePdbTest.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Diagnostics.SymbolStore; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public class PortablePdbStandalonePdbTest + { + [Fact] + public void CreateStandalonePDBAndVerifyTest() + { + using (TempFile pdbFile = TempFile.Create()) + using (TempFile file = TempFile.Create()) + { + MethodBuilder method1, entryPoint; + MetadataBuilder metadataBuilder = GenerateAssemblyAndMetadata(out method1, out entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata); + MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, metadataBuilder.GetRowCounts(), entryPointHandle); + BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob); + using var pdbFileStream = new FileStream(pdbFile.Path, FileMode.Create, FileAccess.Write); + portablePdbBlob.WriteContentTo(pdbFileStream); + pdbFileStream.Close(); + + DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder(); + debugDirectoryBuilder.AddCodeViewEntry(pdbFile.Path, pdbContentId, pdbBuilder.FormatVersion); + + ManagedPEBuilder peBuilder = new ManagedPEBuilder( + header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage), + metadataRootBuilder: new MetadataRootBuilder(metadataBuilder), + ilStream: ilStream, + debugDirectoryBuilder: debugDirectoryBuilder, + entryPoint: entryPointHandle); + + BlobBuilder peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + using var assemblyFileStream = new FileStream(file.Path, FileMode.Create, FileAccess.Write); + peBlob.WriteContentTo(assemblyFileStream); + assemblyFileStream.Close(); + + using var fs = new FileStream(pdbFile.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs); + ValidatePDB(method1, entryPoint, provider.GetMetadataReader()); + } + } + + private static void ValidatePDB(MethodBuilder method, MethodBuilder entryPoint, MetadataReader reader) + { + DocumentHandleCollection.Enumerator docEnumerator = reader.Documents.GetEnumerator(); + Assert.Equal(1, reader.Documents.Count); + Assert.True(docEnumerator.MoveNext()); + Document doc = reader.GetDocument(docEnumerator.Current); + Assert.Equal("MySourceFile.cs", reader.GetString(doc.Name)); + Assert.Equal(SymLanguageType.CSharp, reader.GetGuid(doc.Language)); + Assert.Equal(default, reader.GetGuid(doc.HashAlgorithm)); + Assert.False(docEnumerator.MoveNext()); + + MethodDebugInformation mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(method.MetadataToken)); + Assert.Equal(doc, reader.GetDocument(mdi1.Document)); + SequencePointCollection.Enumerator spce = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spce.MoveNext()); + SequencePoint sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(7, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(7, sp.EndLine); + Assert.Equal(20, sp.EndColumn); + Assert.True(spce.MoveNext()); + sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(8, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(9, sp.EndLine); + Assert.Equal(18, sp.EndColumn); + Assert.False(spce.MoveNext()); + + LocalScopeHandleCollection.Enumerator localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(method.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + LocalScope localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(12, localScope.EndOffset); + LocalVariableHandleCollection.Enumerator localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + + Assert.True(localScope.ImportScope.IsNil); + + Assert.True(localScopes.MoveNext()); + localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(4, localScope.StartOffset); + Assert.Equal(10, localScope.EndOffset); + localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyInt2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + + ImportScope importScope = reader.GetImportScope(localScope.ImportScope); + Assert.True(importScope.Parent.IsNil); + ImportDefinitionCollection.Enumerator importEnumerator = importScope.GetImports().GetEnumerator(); + Assert.True(importEnumerator.MoveNext()); + ImportDefinition importDef = importEnumerator.Current; + Assert.Equal(ImportDefinitionKind.ImportNamespace, importDef.Kind); + BlobReader blobReader = reader.GetBlobReader(importDef.TargetNamespace); + Assert.Equal("System.Reflection", blobReader.ReadUTF8(blobReader.Length)); + + mdi1 = reader.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(entryPoint.MetadataToken)); + Assert.Equal(doc, reader.GetDocument(mdi1.Document)); + spce = mdi1.GetSequencePoints().GetEnumerator(); + Assert.True(spce.MoveNext()); + sp = spce.Current; + Assert.False(sp.IsHidden); + Assert.Equal(12, sp.StartLine); + Assert.Equal(0, sp.StartColumn); + Assert.Equal(12, sp.EndLine); + Assert.Equal(37, sp.EndColumn); + Assert.False(spce.MoveNext()); + + localScopes = reader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)).GetEnumerator(); + Assert.True(localScopes.MoveNext()); + localScope = reader.GetLocalScope(localScopes.Current); + Assert.Equal(0, localScope.StartOffset); + Assert.Equal(21, localScope.EndOffset); + localEnumerator = localScope.GetLocalVariables().GetEnumerator(); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyLoc1", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.True(localEnumerator.MoveNext()); + Assert.Equal("MyLoc2", reader.GetString(reader.GetLocalVariable(localEnumerator.Current).Name)); + Assert.False(localEnumerator.MoveNext()); + Assert.False(localScopes.MoveNext()); + } + + private static MetadataBuilder GenerateAssemblyAndMetadata(out MethodBuilder method, + out MethodBuilder entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata) + { + PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly2"), typeof(object).Assembly); + ModuleBuilder mb = ab.DefineDynamicModule("MyModule2"); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class); + ISymbolDocumentWriter srcdoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp); + method = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]); + ILGenerator il1 = method.GetILGenerator(); + LocalBuilder local = il1.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyInt"); + il1.MarkSequencePoint(srcdoc, 7, 0, 7, 20); + il1.Emit(OpCodes.Ldarg_0); + il1.Emit(OpCodes.Ldarg_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.MarkSequencePoint(srcdoc, 8, 0, 9, 18); + il1.BeginScope(); + il1.UsingNamespace("System.Reflection"); + LocalBuilder local2 = il1.DeclareLocal(typeof(int)); + local2.SetLocalSymInfo("MyInt2"); + il1.Emit(OpCodes.Ldc_I4_2); + il1.Emit(OpCodes.Stloc_1); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ldloc_1); + il1.Emit(OpCodes.Add); + il1.Emit(OpCodes.Stloc_0); + il1.EndScope(); + il1.Emit(OpCodes.Ldloc_0); + il1.Emit(OpCodes.Ret); + + entryPoint = tb.DefineMethod("Mm", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il2 = entryPoint.GetILGenerator(); + local = il2.DeclareLocal(typeof(int)); + local.SetLocalSymInfo("MyLoc1"); + il2.MarkSequencePoint(srcdoc, 12, 0, 12, 37); + local2 = il2.DeclareLocal(typeof(int)); + local2.SetLocalSymInfo("MyLoc2"); + il2.Emit(OpCodes.Ldc_I4_S, 10); + il2.Emit(OpCodes.Stloc_0); + il2.Emit(OpCodes.Ldc_I4_1); + il2.Emit(OpCodes.Stloc_1); + il2.Emit(OpCodes.Ldloc_0); + il2.Emit(OpCodes.Ldloc_1); + il2.Emit(OpCodes.Call, method); + il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])); + il2.Emit(OpCodes.Ret); + tb.CreateType(); + return ab.GenerateMetadata(out ilStream, out BlobBuilder _, out pdbMetadata); + } + + [Fact] + public void CreateEmbeddedPDBAndVerifyTest() + { + using (TempFile file = TempFile.Create()) + { + MethodBuilder method1, entryPoint; + MetadataBuilder metadataBuilder = GenerateAssemblyAndMetadata(out method1, out entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata); + MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken); + + BlobBuilder portablePdbBlob = new BlobBuilder(); + PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, metadataBuilder.GetRowCounts(), entryPointHandle); + + BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob); + DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder(); + debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(portablePdbBlob, pdbBuilder.FormatVersion); + + ManagedPEBuilder peBuilder = new ManagedPEBuilder( + header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage), + metadataRootBuilder: new MetadataRootBuilder(metadataBuilder), + ilStream: ilStream, + debugDirectoryBuilder: debugDirectoryBuilder, + entryPoint: entryPointHandle); + + BlobBuilder peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + using var fileStream = new FileStream(file.Path, FileMode.Create, FileAccess.Write); + peBlob.WriteContentTo(fileStream); + fileStream.Close(); + + using var fs = new FileStream(file.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var peReader = new PEReader(fs); + ImmutableArray entries = peReader.ReadDebugDirectory(); + Assert.Equal(1, entries.Length); + Assert.Equal(DebugDirectoryEntryType.EmbeddedPortablePdb, entries[0].Type); + + using MetadataReaderProvider provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entries[0]); + ValidatePDB(method1, entryPoint, provider.GetMetadataReader()); + } + } + } +} diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 5317b25145a55d..56d7b3869a1665 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -73,6 +73,8 @@ + + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index c7bb4a81d3bd56..6bb74c0f42fe8a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -203,9 +203,11 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) } } - // this is always running on I/O thread, so it will not throw PNSE // it's also OK to block here, because we know we will only block shortly, as this is just race with the other thread. - holder.CallbackReady?.Wait(); + if (holder.CallbackReady != null) + { + Thread.ForceBlockingWait(static (b) => ((ManualResetEventSlim)b!).Wait(), holder.CallbackReady); + } lock (ctx) { @@ -289,6 +291,7 @@ public static void BeforeSyncJSExport(JSMarshalerArgument* arguments_buffer) try { var ctx = arg_exc.AssertCurrentThreadContext(); + // note that this method is only executed when the caller is on another thread, via mono_wasm_invoke_jsexport_sync -> mono_wasm_install_js_worker_interop_wrapper ctx.IsPendingSynchronousCall = true; if (ctx.IsMainThread) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index f2d908d947074f..f6be095eecab92 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -307,7 +307,7 @@ internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span #if FEATURE_WASM_MANAGED_THREADS else { - if (targetContext.IsPendingSynchronousCall && targetContext.IsMainThread) + if (targetContext.IsPendingSynchronousCall && targetContext.IsMainThread && !signature.IsDiscardNoWait) { throw new PlatformNotSupportedException("Cannot call synchronous JS function from inside a synchronous call to a C# method."); } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs index 096259d8a0bce7..10153ee995ecc8 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTest.WebSocket.cs @@ -39,7 +39,6 @@ await executor.Execute(async () => [Theory, MemberData(nameof(GetTargetThreads2x))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/100931")] public async Task WebSocketClient_ResponseCloseInDifferentThread(Executor executor1, Executor executor2) { using var cts = CreateTestCaseTimeoutSource(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs index 77aef0857a8d2e..c9b8304978a4fa 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestBase.cs @@ -180,6 +180,9 @@ static void LocalCtsIgnoringCall(Action action) new NamedCall { IsBlocking = false, Name = "new Timer", Call = delegate (CancellationToken ct) { new Timer((_) => { }, null, 1, -1); }}, + new NamedCall { IsBlocking = false, Name = "JSType.DiscardNoWait", Call = delegate (CancellationToken ct) { + WebWorkerTestHelper.Log("DiscardNoWait"); + }}, // things which should throw PNSE on sync JSExport and JSWebWorker new NamedCall { IsBlocking = true, Name = "Task.Wait", Call = delegate (CancellationToken ct) { Task.Delay(30, ct).Wait(ct); }}, diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs index 6da4b4c69fa51c..91028ff5a6ebd9 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs @@ -263,6 +263,14 @@ public static StatementSyntax SkipInitOrDefaultInit(TypePositionInfo info, StubC } } + public static StatementSyntax DefaultInit(TypePositionInfo info, StubCodeContext context) + { + // Assign out params to default + return AssignmentStatement( + IdentifierName(info.InstanceIdentifier), + LiteralExpression(SyntaxKind.DefaultLiteralExpression, Token(SyntaxKind.DefaultKeyword))); + } + /// /// Get the marshalling direction for a given in a given . /// For example, an out parameter is marshalled in the direction in a stub, diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs index fd7ea63dac01af..2d693334005d85 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/VariableDeclarations.cs @@ -26,7 +26,7 @@ public static VariableDeclarations GenerateDeclarationsForManagedToUnmanaged(Bou if (info.RefKind == RefKind.Out) { - initializations.Add(MarshallerHelpers.SkipInitOrDefaultInit(info, context)); + initializations.Add(MarshallerHelpers.DefaultInit(info, context)); } // Declare variables for parameters diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/UnmanagedToManagedCustomMarshallingTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/UnmanagedToManagedCustomMarshallingTests.cs index 8defdfd8c4e331..af89c22e25fd3b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/UnmanagedToManagedCustomMarshallingTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/UnmanagedToManagedCustomMarshallingTests.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Threading; +using Microsoft.Diagnostics.Runtime; using SharedTypes; using Xunit; using static ComInterfaceGenerator.Tests.UnmanagedToManagedCustomMarshallingTests; @@ -156,11 +157,11 @@ public unsafe void ValidateArrayElementsAndOutParameterNotFreed_Stateless() try { var values = new int[] { 1, 32, 63, 124, 255 }; - int freeCalls = IntWrapperMarshallerToIntWithFreeCounts.NumCallsToFree; - - NativeExportsNE.UnmanagedToManagedCustomMarshalling.SumAndSetNativeObjectData(wrapper, values, values.Length, out int _); - + int oldValue = startingValue + 100; + NativeExportsNE.UnmanagedToManagedCustomMarshalling.SumAndSetNativeObjectData(wrapper, values, values.Length, out oldValue); + Assert.Equal(values.Sum(), impl.GetData().i); + Assert.Equal(startingValue, oldValue); Assert.Equal(freeCalls, IntWrapperMarshallerToIntWithFreeCounts.NumCallsToFree); } finally @@ -181,11 +182,11 @@ public unsafe void ValidateArrayElementsByRefFreed_Stateless() try { var values = new int[] { 1, 32, 63, 124, 255 }; - int freeCalls = IntWrapperMarshallerToIntWithFreeCounts.NumCallsToFree; - - NativeExportsNE.UnmanagedToManagedCustomMarshalling.SumAndSetNativeObjectData(wrapper, ref values, values.Length, out int _); - + int oldValue = startingValue + 100; + NativeExportsNE.UnmanagedToManagedCustomMarshalling.SumAndSetNativeObjectData(wrapper, ref values, values.Length, out oldValue); + Assert.Equal(values.Sum(), impl.GetData().i); + Assert.Equal(startingValue, oldValue); Assert.Equal(freeCalls + values.Length, IntWrapperMarshallerToIntWithFreeCounts.NumCallsToFree); } finally diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/RefParameters.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/RefParameters.cs new file mode 100644 index 00000000000000..3e845103eb2bff --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/RefParameters.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using SharedTypes; +using Xunit; + +namespace LibraryImportGenerator.IntegrationTests +{ + partial class NativeExportsNE + { + public partial class RefParameters + { + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "out_params")] + public static partial int OutParameters(out int values, out IntFields numValues); + } + } + public class RefParameters + { + [Fact] + public void OutParametersAreDefaultInitialized() + { + int myInt = 12; + IntFields myIntFields = new IntFields() { a = 12, b = 12, c = 12 }; + NativeExportsNE.RefParameters.OutParameters(out myInt, out myIntFields); + Debug.Assert(myInt == default); + Debug.Assert(myIntFields.a == default && myIntFields.b == default && myIntFields.c == default); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/RefParameters.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/RefParameters.cs new file mode 100644 index 00000000000000..5868826a359e4a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/RefParameters.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using SharedTypes; + +namespace NativeExports +{ + public static unsafe class RefParameters + { + [UnmanagedCallersOnly(EntryPoint = "out_params")] + public static void EnsureOutParamIsDefault(int* ri, [DNNE.C99Type("struct int_fields*")]IntFields* rif) + { + int i = *ri; + Debug.Assert(i == default); + IntFields _if = *rif; + Debug.Assert(_if.a == default && _if.b == default && _if.c == default); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/VirtualMethodTables.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/VirtualMethodTables.cs index a8e6458fb0ab5a..bc3cac9d0a3c21 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/VirtualMethodTables.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/VirtualMethodTables.cs @@ -198,6 +198,7 @@ public static void ExchangeNativeObjectData([DNNE.C99Type("struct INativeObject* [DNNE.C99DeclCode("struct INativeObject;")] public static void SumAndSetData([DNNE.C99Type("struct INativeObject*")] NativeObjectInterface* obj, int* values, int numValues, int* oldValue) { + Debug.Assert(*oldValue == default); obj->VTable->sumAndSetData(obj, values, numValues, oldValue); } @@ -205,6 +206,7 @@ public static void SumAndSetData([DNNE.C99Type("struct INativeObject*")] NativeO [DNNE.C99DeclCode("struct INativeObject;")] public static void SumAndSetDataWithRef([DNNE.C99Type("struct INativeObject*")] NativeObjectInterface* obj, int** values, int numValues, int* oldValue) { + Debug.Assert(*oldValue == default); obj->VTable->sumAndSetDataWithRef(obj, values, numValues, oldValue); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e8ef65ea314449..1080983cb9bb2c 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13299,7 +13299,7 @@ public static partial class Unsafe [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")] public static T? As(object? o) where T : class? { throw null; } public static ref TTo As(ref TFrom source) { throw null; } - public static TTo BitCast(TFrom source) where TFrom : struct where TTo : struct { throw null; } + public static TTo BitCast(TFrom source) { throw null; } public static System.IntPtr ByteOffset([System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T target) { throw null; } [System.CLSCompliantAttribute(false)] public static void CopyBlock(ref byte destination, ref readonly byte source, uint byteCount) { } @@ -15307,6 +15307,8 @@ public void Wait(System.Threading.CancellationToken cancellationToken) { } public bool Wait(System.TimeSpan timeout) { throw null; } public bool Wait(System.TimeSpan timeout, System.Threading.CancellationToken cancellationToken) { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static void WaitAll(System.Collections.Generic.IEnumerable tasks, System.Threading.CancellationToken cancellationToken = default) { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static void WaitAll(params System.Threading.Tasks.Task[] tasks) { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static bool WaitAll(System.Threading.Tasks.Task[] tasks, int millisecondsTimeout) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs index ce6e5ecddf5602..c3c7f3c465e413 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs @@ -237,5 +237,43 @@ public void CustomAttributeProvider () Assert.Equal(typeof(MyParameterAttribute), customAttributesData[0].AttributeType); Assert.Equal(typeof(MyPropertyAttribute), customAttributesData[1].AttributeType); } + + internal enum EnumForArrays { Zero, One, Two } + + [CustomAttributeWithObjects(EnumForArrays.One, new object[] { EnumForArrays.One }, new EnumForArrays[] { EnumForArrays.Two }, + B1 = EnumForArrays.One, B2 = new object[] { EnumForArrays.One }, B3 = new EnumForArrays[] { EnumForArrays.Two })] + internal class ClassWithObjectEnums { } + + internal class CustomAttributeWithObjectsAttribute(object a1, object a2, object a3) : Attribute + { + public object A1 => a1; + public object A2 => a2; + public object A3 => a3; + + public object B1 { get; set; } + public object B2 { get; set; } + public object B3 { get; set; } + } + + [Fact] + public void BoxedEnumAttributes() + { + CustomAttributeWithObjectsAttribute att = typeof(ClassWithObjectEnums) + .GetCustomAttribute()!; + + Assert.Equal(typeof(EnumForArrays), att.A1.GetType()); + Assert.Equal(typeof(object[]), att.A2.GetType()); + Assert.Equal(typeof(EnumForArrays), ((object[])att.A2)[0].GetType()); + Assert.Equal(EnumForArrays.One, ((object[])att.A2)[0]); + Assert.Equal(typeof(EnumForArrays[]), att.A3.GetType()); + Assert.Equal(EnumForArrays.Two, ((EnumForArrays[])att.A3)[0]); + + Assert.Equal(typeof(EnumForArrays), att.B1.GetType()); + Assert.Equal(typeof(object[]), att.B2.GetType()); + Assert.Equal(typeof(EnumForArrays), ((object[])att.B2)[0].GetType()); + Assert.Equal(EnumForArrays.One, ((object[])att.B2)[0]); + Assert.Equal(typeof(EnumForArrays[]), att.B3.GetType()); + Assert.Equal(EnumForArrays.Two, ((EnumForArrays[])att.B3)[0]); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.CompilerServices.Unsafe.Tests/UnsafeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.CompilerServices.Unsafe.Tests/UnsafeTests.cs index 955bc189b43314..70d55cd6f69850 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.CompilerServices.Unsafe.Tests/UnsafeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.CompilerServices.Unsafe.Tests/UnsafeTests.cs @@ -1166,6 +1166,18 @@ public static unsafe void BitCast() Assert.Throws(() => Unsafe.BitCast(5)); Assert.Throws(() => Unsafe.BitCast(5)); + // Conversion to/from a class should fail + + Assert.Throws(() => Unsafe.BitCast(string.Empty)); + Assert.Throws(() => Unsafe.BitCast(42)); + Assert.Throws(() => Unsafe.BitCast(string.Empty)); + + // Conversion to/from nullable value types should fail + + Assert.Throws(() => Unsafe.BitCast(42)); + Assert.Throws(() => Unsafe.BitCast(42)); + Assert.Throws(() => Unsafe.BitCast(42)); + // Conversion between floating-point and same sized integral should succeed Assert.Equal(0x8000_0000u, Unsafe.BitCast(-0.0f)); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs index 1ef1c6557a749d..cb89e2343a4c39 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs @@ -7,6 +7,7 @@ using System.IO; using System.Net.Http; using System.Reflection; +using System.Reflection.Emit; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; @@ -654,6 +655,24 @@ public void AssemblyResolve_IsNotCalledForCoreLibResources() }).Dispose(); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void AssemblyResolve_IgnoresStrongNameMismatches() + { + RemoteExecutor.Invoke(() => + { + AppDomain.CurrentDomain.AssemblyResolve += + (sender, e) => + { + if (!e.Name.StartsWith("MyAssembly")) + return null; + + return AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run); + }; + Assembly.Load("MyAssembly, PublicKeyToken=1234567890ABCDEF"); + }).Dispose(); + } + class CorrectlyPropagatesException : Exception { public CorrectlyPropagatesException(string message) : base(message) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index 4f30dfd0a09b0d..fd47fb81eb7424 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -63,13 +63,13 @@ public void CurrentManagedThreadId_DifferentForActiveThreads() { var ids = new HashSet(); Barrier b = new Barrier(10); - Task.WaitAll((from i in Enumerable.Range(0, b.ParticipantCount) - select Task.Factory.StartNew(() => - { - b.SignalAndWait(); - lock (ids) ids.Add(Environment.CurrentManagedThreadId); - b.SignalAndWait(); - }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray()); + Task.WaitAll(from i in Enumerable.Range(0, b.ParticipantCount) + select Task.Factory.StartNew(() => + { + b.SignalAndWait(); + lock (ids) ids.Add(Environment.CurrentManagedThreadId); + b.SignalAndWait(); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)); Assert.Equal(b.ParticipantCount, ids.Count); } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/Exceptions.HResults.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/Exceptions.HResults.cs index 777ffaebe963d5..0bf947e802e793 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/Exceptions.HResults.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/Exceptions.HResults.cs @@ -18,7 +18,6 @@ public static class HResults public const int COR_E_FILELOAD = unchecked((int)0x80131621); public const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047); - public const int FUSION_E_PRIVATE_ASM_DISALLOWED = unchecked((int)0x80131044); public const int FUSION_E_REF_DEF_MISMATCH = unchecked((int)0x80131040); public const int ERROR_TOO_MANY_OPEN_FILES = unchecked((int)0x80070004); public const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/FileLoadException.InteropTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/FileLoadException.InteropTests.cs index 9d449775aa692c..53b2f704547f00 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/FileLoadException.InteropTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/IO/FileLoadException.InteropTests.cs @@ -13,7 +13,6 @@ public static class FileLoadExceptionInteropTests [Theory] [InlineData(HResults.COR_E_FILELOAD)] [InlineData(HResults.FUSION_E_INVALID_NAME)] - [InlineData(HResults.FUSION_E_PRIVATE_ASM_DISALLOWED)] [InlineData(HResults.FUSION_E_REF_DEF_MISMATCH)] [InlineData(HResults.ERROR_TOO_MANY_OPEN_FILES)] [InlineData(HResults.ERROR_SHARING_VIOLATION)] diff --git a/src/libraries/System.Runtime/tests/System.Security.SecureString.Tests/SecureStringTests.cs b/src/libraries/System.Runtime/tests/System.Security.SecureString.Tests/SecureStringTests.cs index ed094b312326cb..6fabfc8cfe3c1a 100644 --- a/src/libraries/System.Runtime/tests/System.Security.SecureString.Tests/SecureStringTests.cs +++ b/src/libraries/System.Runtime/tests/System.Security.SecureString.Tests/SecureStringTests.cs @@ -499,7 +499,7 @@ public static void Grow_Large() break; } } - })).ToArray()); + }))); } } diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CESchedulerPairTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CESchedulerPairTests.cs index 7bc44998523a44..0dd20961e6e682 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CESchedulerPairTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CESchedulerPairTests.cs @@ -244,7 +244,7 @@ public static void TestMaxItemsPerTask(int maxConcurrency, int maxItemsPerTask, } //finally wait for all of the tasks, to ensure they all executed properly - Task.WaitAll(taskList.ToArray()); + Task.WaitAll(taskList); if (!completeBeforeTaskWait) { @@ -292,7 +292,7 @@ public static void TestLowerConcurrencyLevel() //finally unblock the blocjedTask and wait for all of the tasks, to ensure they all executed properly blockReaderTaskEvent.Set(); - Task.WaitAll(taskList.ToArray()); + Task.WaitAll(taskList); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -325,7 +325,7 @@ public static void TestConcurrentBlockage() } blockExclusiveTaskEvent.Set(); - Task.WaitAll(taskList.ToArray()); + Task.WaitAll(taskList); } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] @@ -482,7 +482,7 @@ public static void TestSchedulerNesting() } // Wait for all tasks to complete, then complete the schedulers - Task.WaitAll(tasks.ToArray()); + Task.WaitAll(tasks); foreach (var cesp in cesps) { cesp.Complete(); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs index cb8adbc1792587..61d5e4fc01794a 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs @@ -220,7 +220,6 @@ public TaskWaitAllAnyTest(TestParameters_WaitAllAny parameters) /// /// The method that will run the scenario /// - /// internal void RealRun() { //create the tasks @@ -325,7 +324,6 @@ private void CreateTask() /// - the returned index form WaitAny should correspond to a completed task /// - in case of Cancelled and Exception tests the right exceptions should be got for WaitAll /// - /// private void Verify() { // verification for WaitAll @@ -1255,5 +1253,43 @@ public static void TaskWaitAllAny58() TaskWaitAllAnyTest test = new TaskWaitAllAnyTest(parameters); test.RealRun(); } + + [Fact] + public static void TaskWaitAll_Enumerable_InvalidArguments() + { + AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)null)); + AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)[null])); + AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)[Task.CompletedTask, null])); + AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)[null, Task.CompletedTask])); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void TaskWaitAll_Enumerable_Canceled() + { + var tcs = new TaskCompletionSource(); + + Assert.Throws(() => Task.WaitAll((IEnumerable)[tcs.Task], new CancellationToken(true))); + + using var cts = new CancellationTokenSource(1); + Assert.Throws(() => Task.WaitAll((IEnumerable)[Task.CompletedTask, tcs.Task], cts.Token)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void TaskWaitAll_Enumerable_AllComplete() + { + Task.WaitAll((IEnumerable)[]); + Task.WaitAll((IEnumerable)[Task.CompletedTask]); + Task.WaitAll((IEnumerable)[Task.CompletedTask, Task.CompletedTask]); + + Task.WaitAll((IEnumerable)(Task[])[Task.CompletedTask, Task.CompletedTask]); + + Task.WaitAll((List)[]); + Task.WaitAll((List)[Task.CompletedTask]); + Task.WaitAll((List)[Task.CompletedTask, Task.CompletedTask]); + + Task.WaitAll((IEnumerable)[Task.Delay(1)]); + Task.WaitAll((List)[Task.Delay(1)]); + Task.WaitAll((IEnumerable)[Task.Delay(1), Task.CompletedTask, Task.Delay(1), Task.CompletedTask, Task.Delay(1)]); + } } } diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs index d3c7d0dfc1e62c..f1c76e7674f07a 100644 --- a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs +++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs @@ -150,5 +150,10 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults) /// instead of numeric serialization for all enum types encountered in its type graph. /// public bool UseStringEnumConverter { get; set; } + + /// + /// Specifies the default value of when set. + /// + public string? NewLine { get; set; } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 7dbc4de3a57f59..12173a0e917de9 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1147,6 +1147,9 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti if (optionsSpec.MaxDepth is int maxDepth) writer.WriteLine($"MaxDepth = {maxDepth},"); + if (optionsSpec.NewLine is string newLine) + writer.WriteLine($"NewLine = {FormatStringLiteral(newLine)},"); + if (optionsSpec.NumberHandling is JsonNumberHandling numberHandling) writer.WriteLine($"NumberHandling = {FormatNumberHandling(numberHandling)},"); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 9ae7ea968178dd..5d94f860cd785f 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -272,6 +272,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN bool? ignoreReadOnlyProperties = null; bool? includeFields = null; int? maxDepth = null; + string? newLine = null; JsonNumberHandling? numberHandling = null; JsonObjectCreationHandling? preferredObjectCreationHandling = null; bool? propertyNameCaseInsensitive = null; @@ -344,6 +345,10 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN maxDepth = (int)namedArg.Value.Value!; break; + case nameof(JsonSourceGenerationOptionsAttribute.NewLine): + newLine = (string)namedArg.Value.Value!; + break; + case nameof(JsonSourceGenerationOptionsAttribute.NumberHandling): numberHandling = (JsonNumberHandling)namedArg.Value.Value!; break; @@ -411,6 +416,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN IgnoreReadOnlyProperties = ignoreReadOnlyProperties, IncludeFields = includeFields, MaxDepth = maxDepth, + NewLine = newLine, NumberHandling = numberHandling, PreferredObjectCreationHandling = preferredObjectCreationHandling, PropertyNameCaseInsensitive = propertyNameCaseInsensitive, diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs index 82ad9ff711af14..e16bebb6ceb49e 100644 --- a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs @@ -36,6 +36,8 @@ public sealed record SourceGenerationOptionsSpec public required int? MaxDepth { get; init; } + public required string? NewLine { get; init; } + public required JsonNumberHandling? NumberHandling { get; init; } public required JsonObjectCreationHandling? PreferredObjectCreationHandling { get; init; } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index c0f23522b137d2..0ff5bdc90a66ee 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -385,6 +385,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { } public bool IncludeFields { get { throw null; } set { } } public bool IsReadOnly { get { throw null; } } public int MaxDepth { get { throw null; } set { } } + public string NewLine { get { throw null; } set { } } public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } } public System.Text.Json.Serialization.JsonObjectCreationHandling PreferredObjectCreationHandling { get { throw null; } set { } } public bool PropertyNameCaseInsensitive { get { throw null; } set { } } @@ -445,6 +446,7 @@ public partial struct JsonWriterOptions public bool Indented { get { throw null; } set { } } public char IndentCharacter { get { throw null; } set { } } public int IndentSize { get { throw null; } set { } } + public string NewLine { get { throw null; } set { } } public int MaxDepth { readonly get { throw null; } set { } } public bool SkipValidation { get { throw null; } set { } } } @@ -1072,6 +1074,7 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau public bool IgnoreReadOnlyProperties { get { throw null; } set { } } public bool IncludeFields { get { throw null; } set { } } public int MaxDepth { get { throw null; } set { } } + public string? NewLine { get { throw null; } set { } } public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } } public System.Text.Json.Serialization.JsonObjectCreationHandling PreferredObjectCreationHandling { get { throw null; } set { } } public bool PropertyNameCaseInsensitive { get { throw null; } set { } } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 54d82b5f057850..3654ae7dbd3807 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -717,4 +717,7 @@ Indentation size must be between {0} and {1}. + + New line can be only "\n" or "\r\n". + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 078a6a4b83777e..7fe47499c1aa85 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs @@ -28,6 +28,9 @@ internal static partial class JsonConstants public const byte UtcOffsetToken = (byte)'Z'; public const byte TimePrefix = (byte)'T'; + public const string NewLineLineFeed = "\n"; + public const string NewLineCarriageReturnLineFeed = "\r\n"; + // \u2028 and \u2029 are considered respectively line and paragraph separators // UTF-8 representation for them is E2, 80, A8/A9 public const byte StartingByteOfNonStandardSeparator = 0xE2; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index ad529fa034fb9f..c8fd040736dbb9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -504,6 +504,7 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right) left._unmappedMemberHandling == right._unmappedMemberHandling && left._defaultBufferSize == right._defaultBufferSize && left._maxDepth == right._maxDepth && + left.NewLine == right.NewLine && // Read through property due to lazy initialization of the backing field left._allowOutOfOrderMetadataProperties == right._allowOutOfOrderMetadataProperties && left._allowTrailingCommas == right._allowTrailingCommas && left._ignoreNullValues == right._ignoreNullValues && @@ -561,6 +562,7 @@ public int GetHashCode(JsonSerializerOptions options) AddHashCode(ref hc, options._unmappedMemberHandling); AddHashCode(ref hc, options._defaultBufferSize); AddHashCode(ref hc, options._maxDepth); + AddHashCode(ref hc, options.NewLine); // Read through property due to lazy initialization of the backing field AddHashCode(ref hc, options._allowOutOfOrderMetadataProperties); AddHashCode(ref hc, options._allowTrailingCommas); AddHashCode(ref hc, options._ignoreNullValues); @@ -591,13 +593,13 @@ static void AddListHashCode(ref HashCode hc, ConfigurationList? static void AddHashCode(ref HashCode hc, TValue? value) { - if (typeof(TValue).IsValueType) + if (typeof(TValue).IsSealed) { hc.Add(value); } else { - Debug.Assert(!typeof(TValue).IsSealed, "Sealed reference types like string should not use this method."); + // Use the built-in hashcode for types that could be overriding GetHashCode(). hc.Add(RuntimeHelpers.GetHashCode(value)); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 246d3ab303fcea..e7123e3a4431e2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -89,6 +89,7 @@ public static JsonSerializerOptions Web private bool _ignoreReadOnlyProperties; private bool _ignoreReadonlyFields; private bool _includeFields; + private string? _newLine; private bool _propertyNameCaseInsensitive; private bool _writeIndented; private char _indentCharacter = JsonConstants.DefaultIndentCharacter; @@ -141,6 +142,7 @@ public JsonSerializerOptions(JsonSerializerOptions options) _ignoreReadOnlyProperties = options._ignoreReadOnlyProperties; _ignoreReadonlyFields = options._ignoreReadonlyFields; _includeFields = options._includeFields; + _newLine = options._newLine; _propertyNameCaseInsensitive = options._propertyNameCaseInsensitive; _writeIndented = options._writeIndented; _indentCharacter = options._indentCharacter; @@ -750,6 +752,33 @@ public ReferenceHandler? ReferenceHandler } } + /// + /// Gets or sets the new line string to use when is . + /// The default is the value of . + /// + /// + /// Thrown when the new line string is . + /// + /// + /// Thrown when the new line string is not \n or \r\n. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public string NewLine + { + get + { + return _newLine ??= Environment.NewLine; + } + set + { + JsonWriterHelper.ValidateNewLine(value); + VerifyMutable(); + _newLine = value; + } + } + /// /// Returns true if options uses compatible built-in resolvers or a combination of compatible built-in resolvers. /// @@ -970,6 +999,7 @@ internal JsonWriterOptions GetWriterOptions() IndentCharacter = IndentCharacter, IndentSize = IndentSize, MaxDepth = EffectiveMaxDepth, + NewLine = NewLine, #if !DEBUG SkipValidation = true #endif diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 58bd0a71724911..6976d42b967bc9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -12,6 +12,12 @@ internal static partial class ThrowHelper // If the exception source is this value, the serializer will re-throw as JsonException. public const string ExceptionSourceValueToRethrowAsJsonException = "System.Text.Json.Rethrowable"; + [DoesNotReturn] + public static void ThrowArgumentOutOfRangeException_NewLine(string parameterName) + { + throw GetArgumentOutOfRangeException(parameterName, SR.InvalidNewLine); + } + [DoesNotReturn] public static void ThrowArgumentOutOfRangeException_IndentCharacter(string parameterName) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs index c34c8cd3d672f9..b5dccf9423be6d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs @@ -36,6 +36,16 @@ public static void WriteIndentation(Span buffer, int indent, byte indentBy } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateNewLine(string value) + { + if (value is null) + ThrowHelper.ThrowArgumentNullException(nameof(value)); + + if (value is not JsonConstants.NewLineLineFeed and not JsonConstants.NewLineCarriageReturnLineFeed) + ThrowHelper.ThrowArgumentOutOfRangeException_NewLine(nameof(value)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ValidateIndentCharacter(char value) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs index 1d89e5d6aa1499..2a96d748ad54ae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs @@ -13,6 +13,8 @@ namespace System.Text.Json /// public struct JsonWriterOptions { + private static readonly string s_alternateNewLine = Environment.NewLine.Length == 2 ? JsonConstants.NewLineLineFeed : JsonConstants.NewLineCarriageReturnLineFeed; + internal const int DefaultMaxDepth = 1000; private int _maxDepth; @@ -68,11 +70,11 @@ public char IndentCharacter /// is out of the allowed range. public int IndentSize { - readonly get => EncodeIndentSize((_optionsMask & IndentSizeMask) >> 3); + readonly get => EncodeIndentSize((_optionsMask & IndentSizeMask) >> OptionsBitCount); set { JsonWriterHelper.ValidateIndentSize(value); - _optionsMask = (_optionsMask & ~IndentSizeMask) | (EncodeIndentSize(value) << 3); + _optionsMask = (_optionsMask & ~IndentSizeMask) | (EncodeIndentSize(value) << OptionsBitCount); } } @@ -135,11 +137,36 @@ public bool SkipValidation } } + /// + /// Gets or sets the new line string to use when is . + /// The default is the value of . + /// + /// + /// Thrown when the new line string is . + /// + /// + /// Thrown when the new line string is not \n or \r\n. + /// + public string NewLine + { + get => (_optionsMask & NewLineBit) != 0 ? s_alternateNewLine : Environment.NewLine; + set + { + JsonWriterHelper.ValidateNewLine(value); + if (value != Environment.NewLine) + _optionsMask |= NewLineBit; + else + _optionsMask &= ~NewLineBit; + } + } + internal bool IndentedOrNotSkipValidation => (_optionsMask & (IndentBit | SkipValidationBit)) != SkipValidationBit; // Equivalent to: Indented || !SkipValidation; + private const int OptionsBitCount = 4; private const int IndentBit = 1; private const int SkipValidationBit = 2; - private const int IndentCharacterBit = 4; - private const int IndentSizeMask = JsonConstants.MaximumIndentSize << 3; + private const int NewLineBit = 4; + private const int IndentCharacterBit = 8; + private const int IndentSizeMask = JsonConstants.MaximumIndentSize << OptionsBitCount; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs index 1a1b3edb2ca805..c97d1cb1546211 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs @@ -284,11 +284,11 @@ private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnl int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length); - Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - indent - encodedLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - indent - encodedLength - 7 - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, 1 colon, and 1 space => indent + escapedPropertyName.Length + encodedLength + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding. - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + encodedLength + 7 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + encodedLength + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -334,11 +334,11 @@ private void WriteBase64Indented(ReadOnlySpan escapedPropertyName, ReadOnl int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - encodedLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - encodedLength - 7 - _newLineLength); // 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, 1 colon, and 1 space => indent + escapedPropertyName.Length + encodedLength + 6 // Optionally, 1 list separator, and 1-2 bytes for new line. - int maxRequired = indent + escapedPropertyName.Length + encodedLength + 7 + s_newLineLength; + int maxRequired = indent + escapedPropertyName.Length + encodedLength + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index 392facd7d85ee2..9e1f07e25e019a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -286,11 +286,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -335,10 +335,10 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 313693b3b7665f..38809447b91b09 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -285,11 +285,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -334,10 +334,10 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index 3f1af56067cd9b..aadc70a39b5f04 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -279,11 +279,11 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -325,10 +325,10 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index eee0535903cfa1..4a58e3057ac6cb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -283,11 +283,11 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -329,10 +329,10 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 133c95ece5d865..f3b6b4847f2ddd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -283,11 +283,11 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -329,10 +329,10 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index 1ded7b8f3f6a15..004a0d228eea8a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -287,11 +287,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 7 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -337,10 +337,10 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs index c09aaa40c8a23e..95a0a67451641c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs @@ -91,10 +91,10 @@ private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, b int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + 5; // 2 quotes, 1 colon, 1 space, and 1 start token - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { @@ -163,11 +163,11 @@ private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, b int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - _newLineLength); // All ASCII, 2 quotes, 1 colon, 1 space, and 1 start token => indent + escapedPropertyName.Length + 5 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index aff0da471b1a3f..bf41ed2e526482 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -430,11 +430,11 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + value.Length + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -476,10 +476,10 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + value.Length + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index 82694f738cc76c..633d0e1c86aaee 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -355,11 +355,11 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -401,10 +401,10 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index 16629ea424f4cf..3c4179d5906ebd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -190,11 +190,11 @@ private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyN Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue - 5 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue - 5 - indent - _newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -378,10 +378,10 @@ private void WriteStringIndentedPropertyName(ReadOnlySpan escapedPropertyN Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedPropertyName.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { @@ -1516,11 +1516,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); + Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - _newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 7 + s_newLineLength; + int maxRequired = indent + ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -1566,10 +1566,10 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + escapedValue.Length + 6; // 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { @@ -1617,11 +1617,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 7 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -1668,11 +1668,11 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnl Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length <= JsonConstants.MaxEscapedTokenSize); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - _newLineLength); // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 7 + s_newLineLength; + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 7 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index 37aad462665995..68536e35cc5d66 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -364,11 +364,11 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - _newLineLength); // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 5 + s_newLineLength; + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 5 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -410,10 +410,10 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - _newLineLength); int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs index e4b4a877bbd561..a425049e9c28a5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs @@ -94,7 +94,7 @@ private void WriteBase64Indented(ReadOnlySpan bytes) // as a length longer than that would overflow int.MaxValue when Base64 encoded. However, we // also need the indentation + 2 quotes, and optionally a list separate and 1-2 bytes for a new line. // Validate the encoded bytes length won't overflow with all of the length. - int extraSpaceRequired = indent + 3 + s_newLineLength; + int extraSpaceRequired = indent + 3 + _newLineLength; int maxLengthAllowed = int.MaxValue / 4 * 3 - extraSpaceRequired; if (bytes.Length > maxLengthAllowed) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs index 93f11451f454d7..aa62396df6c876 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs @@ -107,11 +107,11 @@ private void WriteCommentIndented(ReadOnlySpan value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - s_newLineLength); + Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - _newLineLength); // All ASCII, /*...*/ => escapedValue.Length + 4 // Optionally, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + s_newLineLength; + int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -214,10 +214,10 @@ private void WriteCommentIndented(ReadOnlySpan utf8Value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - s_newLineLength); + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - _newLineLength); int minRequired = indent + utf8Value.Length + 4; // /*...*/ - int maxRequired = minRequired + s_newLineLength; // Optionally, 1-2 bytes for new line + int maxRequired = minRequired + _newLineLength; // Optionally, 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs index ad8a887cdf55a0..5366496ca74ec7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs @@ -69,7 +69,7 @@ private void WriteStringValueIndented(DateTime value) Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line - int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs index 1ee48004f21fd8..0fa10649ac2412 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs @@ -70,7 +70,7 @@ private void WriteStringValueIndented(DateTimeOffset value) Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line - int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs index 35089030c2d454..65bfff3b6c6e1d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs @@ -65,7 +65,7 @@ private void WriteNumberValueIndented(decimal value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs index b638931ce12293..03fab27688d0dc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs @@ -69,7 +69,7 @@ private void WriteNumberValueIndented(double value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs index c1755c7c4dc5d3..7342704d3479ae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs @@ -69,7 +69,7 @@ private void WriteNumberValueIndented(float value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs index f4c902e184af4c..c2f8bb8ff82aad 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs @@ -69,9 +69,9 @@ private void WriteNumberValueIndented(ReadOnlySpan utf8Value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - s_newLineLength); + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - _newLineLength); - int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + utf8Value.Length + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs index 695cc83e03680b..fedbc0959f34c9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs @@ -70,7 +70,7 @@ private void WriteStringValueIndented(Guid value) Debug.Assert(indent <= _indentLength * _options.MaxDepth); // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line - int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + s_newLineLength; + int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs index 8c0dee44e785e4..fdda2ce6837e12 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs @@ -89,7 +89,7 @@ private void WriteLiteralIndented(ReadOnlySpan utf8Value) Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(utf8Value.Length <= 5); - int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + utf8Value.Length + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs index b2126de77fb2f1..85d37a858b9c39 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs @@ -78,7 +78,7 @@ private void WriteNumberValueIndented(long value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs index 4ede0fc9bce938..5ff4064d2b59bd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs @@ -145,11 +145,11 @@ private void WriteStringIndented(ReadOnlySpan escapedValue) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - s_newLineLength); + Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - _newLineLength); // All ASCII, 2 quotes => indent + escapedValue.Length + 2 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding - int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3 + s_newLineLength; + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3 + _newLineLength; if (_memory.Length - BytesPending < maxRequired) { @@ -292,10 +292,10 @@ private void WriteStringIndented(ReadOnlySpan escapedValue) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - s_newLineLength); + Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - _newLineLength); int minRequired = indent + escapedValue.Length + 2; // 2 quotes - int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = minRequired + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs index 82ec4677b2224e..eaabba59a7a2ab 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs @@ -80,7 +80,7 @@ private void WriteNumberValueIndented(ulong value) int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); - int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line if (_memory.Length - BytesPending < maxRequired) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index 315b7a30b8e207..fcf3ab2d4c2547 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -34,9 +34,6 @@ namespace System.Text.Json [DebuggerDisplay("{DebuggerDisplay,nq}")] public sealed partial class Utf8JsonWriter : IDisposable, IAsyncDisposable { - // Depending on OS, either '\r\n' OR '\n' - private static readonly int s_newLineLength = Environment.NewLine.Length; - private const int DefaultGrowthSize = 4096; private const int InitialGrowthSize = 256; @@ -62,6 +59,9 @@ public sealed partial class Utf8JsonWriter : IDisposable, IAsyncDisposable private byte _indentByte; private int _indentLength; + // A length of 1 will emit LF for indented writes, a length of 2 will emit CRLF. Other values are invalid. + private int _newLineLength; + /// /// Returns the amount of bytes written by the so far /// that have not yet been flushed to the output and committed. @@ -151,6 +151,9 @@ private void SetOptions(JsonWriterOptions options) _indentByte = (byte)_options.IndentCharacter; _indentLength = options.IndentSize; + Debug.Assert(options.NewLine is "\n" or "\r\n", "Invalid NewLine string."); + _newLineLength = options.NewLine.Length; + if (_options.MaxDepth == 0) { _options.MaxDepth = JsonWriterOptions.DefaultMaxDepth; // If max depth is not set, revert to the default depth. @@ -1023,8 +1026,9 @@ private void WriteEndIndented(byte token) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteNewLine(Span output) { - // Write '\r\n' OR '\n', depending on OS - if (s_newLineLength == 2) + // Write '\r\n' OR '\n', depending on the configured new line string + Debug.Assert(_newLineLength is 1 or 2, "Invalid new line length."); + if (_newLineLength == 2) { output[BytesPending++] = JsonConstants.CarriageReturn; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs index 0b24bf6ef67672..74aee9393a2f23 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs @@ -71,6 +71,7 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions() IgnoreReadOnlyProperties = true, IncludeFields = true, MaxDepth = 1024, + NewLine = "\n", NumberHandling = JsonNumberHandling.WriteAsString, PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace, PropertyNameCaseInsensitive = true, @@ -101,6 +102,7 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions() IgnoreReadOnlyProperties = true, IncludeFields = true, MaxDepth = 1024, + NewLine = "\n", NumberHandling = JsonNumberHandling.WriteAsString, PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace, PropertyNameCaseInsensitive = true, diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs index 127ca9601150db..5c742d6294b689 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonWriterOptionsTests.cs @@ -19,6 +19,7 @@ public static void JsonWriterOptionsDefaultCtor() IndentSize = 2, SkipValidation = false, MaxDepth = 0, + NewLine = Environment.NewLine, }; Assert.Equal(expectedOption, options); } @@ -35,16 +36,17 @@ public static void JsonWriterOptionsCtor() IndentSize = 2, SkipValidation = false, MaxDepth = 0, + NewLine = Environment.NewLine, }; Assert.Equal(expectedOption, options); } [Theory] - [InlineData(true, '\t', 1, true, 0)] - [InlineData(true, ' ', 127, false, 1)] - [InlineData(false, ' ', 0, true, 1024)] - [InlineData(false, ' ', 4, false, 1024 * 1024)] - public static void JsonWriterOptions(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth) + [InlineData(true, '\t', 1, true, 0, "\n")] + [InlineData(true, ' ', 127, false, 1, "\r\n")] + [InlineData(false, ' ', 0, true, 1024, "\n")] + [InlineData(false, ' ', 4, false, 1024 * 1024, "\r\n")] + public static void JsonWriterOptions(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth, string newLine) { var options = new JsonWriterOptions(); options.Indented = indented; @@ -52,6 +54,7 @@ public static void JsonWriterOptions(bool indented, char indentCharacter, int in options.IndentSize = indentSize; options.SkipValidation = skipValidation; options.MaxDepth = maxDepth; + options.NewLine = newLine; var expectedOption = new JsonWriterOptions { @@ -60,16 +63,17 @@ public static void JsonWriterOptions(bool indented, char indentCharacter, int in IndentSize = indentSize, SkipValidation = skipValidation, MaxDepth = maxDepth, + NewLine = newLine, }; Assert.Equal(expectedOption, options); } [Theory] - [InlineData(true, '\t', 1, true, 0)] - [InlineData(true, ' ', 127, false, 1)] - [InlineData(false, ' ', 0, true, 1024)] - [InlineData(false, ' ', 4, false, 1024 * 1024)] - public static void JsonWriterOptions_Properties(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth) + [InlineData(true, '\t', 1, true, 0, "\n")] + [InlineData(true, ' ', 127, false, 1, "\r\n")] + [InlineData(false, ' ', 0, true, 1024, "\n")] + [InlineData(false, ' ', 4, false, 1024 * 1024, "\r\n")] + public static void JsonWriterOptions_Properties(bool indented, char indentCharacter, int indentSize, bool skipValidation, int maxDepth, string newLine) { var options = new JsonWriterOptions(); options.Indented = indented; @@ -77,12 +81,14 @@ public static void JsonWriterOptions_Properties(bool indented, char indentCharac options.IndentSize = indentSize; options.SkipValidation = skipValidation; options.MaxDepth = maxDepth; + options.NewLine = newLine; Assert.Equal(indented, options.Indented); Assert.Equal(indentCharacter, options.IndentCharacter); Assert.Equal(indentSize, options.IndentSize); Assert.Equal(skipValidation, options.SkipValidation); Assert.Equal(maxDepth, options.MaxDepth); + Assert.Equal(newLine, options.NewLine); } [Fact] @@ -95,6 +101,7 @@ public static void JsonWriterOptions_DefaultValues() Assert.Equal(2, options.IndentSize); Assert.False(options.SkipValidation); Assert.Equal(0, options.MaxDepth); + Assert.Equal(Environment.NewLine, options.NewLine); } [Fact] @@ -123,6 +130,10 @@ public static void JsonWriterOptions_MultipleValues() options.MaxDepth = defaultOptions.MaxDepth; Assert.Equal(defaultOptions.MaxDepth, options.MaxDepth); + options.NewLine = Environment.NewLine.Length == 1 ? "\r\n" : "\n"; + options.NewLine = defaultOptions.NewLine; + Assert.Equal(defaultOptions.NewLine, options.NewLine); + Assert.Equal(defaultOptions, options); } @@ -157,5 +168,30 @@ public static void JsonWriterOptions_IndentSize_OutOfRange(int size) var options = new JsonWriterOptions(); Assert.Throws(() => options.IndentSize = size); } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("\r")] + [InlineData("\n\n")] + [InlineData("\r\n\r\n")] + [InlineData("0")] + [InlineData("a")] + [InlineData("foo")] + [InlineData("$")] + [InlineData(".")] + [InlineData("\u03b1")] + public static void JsonWriterOptions_NewLine_InvalidNewLine(string value) + { + var options = new JsonWriterOptions(); + Assert.Throws(() => options.NewLine = value); + } + + [Fact] + public static void JsonWriterOptions_NewLine_Null_Throws() + { + var options = new JsonWriterOptions(); + Assert.Throws(() => options.NewLine = null); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs index 877c44d183a120..64d96934ed7804 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs @@ -367,6 +367,7 @@ public static void JsonSerializerOptions_EqualityComparer_ChangingAnySettingShou yield return (GetProp(nameof(JsonSerializerOptions.IgnoreReadOnlyFields)), true); yield return (GetProp(nameof(JsonSerializerOptions.IncludeFields)), true); yield return (GetProp(nameof(JsonSerializerOptions.MaxDepth)), 11); + yield return (GetProp(nameof(JsonSerializerOptions.NewLine)), Environment.NewLine.Length is 1 ? "\r\n" : "\n"); yield return (GetProp(nameof(JsonSerializerOptions.PropertyNamingPolicy)), JsonNamingPolicy.CamelCase); yield return (GetProp(nameof(JsonSerializerOptions.PropertyNameCaseInsensitive)), true); yield return (GetProp(nameof(JsonSerializerOptions.ReadCommentHandling)), JsonCommentHandling.Skip); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs index 0cca85c6cc033c..b0c8e41f7179d0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs @@ -516,6 +516,57 @@ public static void MaxDepthRead() Assert.Throws(() => JsonSerializer.Deserialize(BasicCompany.s_data, options)); } + [Theory] + [InlineData(new char[] { '\n' }, '\r')] + [InlineData(new char[] { '\r', '\n' }, null)] + public static void NewLine(char[] newLineChars, char? notExpectedNewLineChars) + { + var obj = new BasicCompany(); + obj.Initialize(); + + var newLine = new string(newLineChars); + var options = new JsonSerializerOptions(); + var json = JsonSerializer.Serialize(obj, options); + Assert.DoesNotContain(newLine, json); + + // Set custom newLine with indentation enabled + options = new JsonSerializerOptions(); + options.WriteIndented = true; + options.NewLine = newLine; + json = JsonSerializer.Serialize(obj, options); + Assert.Contains(newLine, json); + + if (notExpectedNewLineChars is { } notExpected) + { + Assert.DoesNotContain(notExpected, json); + } + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("\r")] + [InlineData("\n\n")] + [InlineData("\r\n\r\n")] + [InlineData("0")] + [InlineData("a")] + [InlineData("foo")] + [InlineData("$")] + [InlineData(".")] + [InlineData("\u03b1")] + public static void TestNewLineInvalid(string value) + { + var options = new JsonSerializerOptions(); + Assert.Throws("value", () => options.NewLine = value); + } + + [Fact] + public static void TestNewLineNullThrows() + { + var options = new JsonSerializerOptions(); + Assert.Throws("value", () => options.NewLine = null); + } + private class TestClassForEncoding { public string MyString { get; set; } @@ -1416,7 +1467,14 @@ and not nameof(JsonSerializerOptions.IsReadOnly)) // Property is not structural } else if (propertyType == typeof(string)) { - property.SetValue(options, "\t"); + if (property.Name is nameof(JsonSerializerOptions.NewLine)) + { + property.SetValue(options, "\n"); + } + else + { + property.SetValue(options, "\t"); + } } else if (propertyType == typeof(IList)) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs index 643762183c713e..7ccda56d737a2a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonWriterTests.cs @@ -1442,7 +1442,7 @@ private static string GetExpectedLargeString(JsonWriterOptions options) json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } [Theory] @@ -2683,12 +2683,12 @@ public void InvalidJsonContinueShouldSucceed(bool formatted) for (int i = 0; i < 100; i++) { if (options.Indented) - sb.Append(Environment.NewLine); + sb.Append(options.NewLine); sb.Append("]"); } sb.Append(","); if (options.Indented) - sb.Append(Environment.NewLine); + sb.Append(options.NewLine); sb.Append("[]"); JsonTestHelper.AssertContents(sb.ToString(), output); @@ -2722,7 +2722,7 @@ public void WriteSeparateProperties(JsonWriterOptions options) json.Flush(); - string expectedStr = HandleIndent(stringWriter.ToString(), options); + string expectedStr = HandleFormatting(stringWriter.ToString(), options); using var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -3082,7 +3082,7 @@ public void Writing3MBBase64Bytes(JsonWriterOptions options) Base64.EncodeToUtf8(value, base64StringUtf8, out _, out int bytesWritten); string expectedValue = Encoding.UTF8.GetString(base64StringUtf8.AsSpan(0, bytesWritten).ToArray()); - string expectedJson = options.Indented ? $"{{{Environment.NewLine}{GetIndentText(options)}\"foo\": \"{expectedValue}\"{Environment.NewLine}}}" : $"{{\"foo\":\"{expectedValue}\"}}"; + string expectedJson = options.Indented ? $"{{{options.NewLine}{GetIndentText(options)}\"foo\": \"{expectedValue}\"{options.NewLine}}}" : $"{{\"foo\":\"{expectedValue}\"}}"; var output = new ArrayBufferWriter(1024); @@ -3558,14 +3558,14 @@ public void WritePartialHelloWorld(JsonWriterOptions options) Assert.Equal(0, jsonUtf8.BytesCommitted); if (options.Indented) - Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + Assert.Equal(26 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesPending); jsonUtf8.Flush(); if (options.Indented) - Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space + Assert.Equal(26 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesCommitted); @@ -3575,19 +3575,19 @@ public void WritePartialHelloWorld(JsonWriterOptions options) jsonUtf8.WriteEndObject(); if (options.Indented) - Assert.Equal(26 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); + Assert.Equal(26 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesCommitted); else Assert.Equal(26, jsonUtf8.BytesCommitted); if (options.Indented) - Assert.Equal(27 + options.IndentSize + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + Assert.Equal(27 + options.IndentSize + (2 * options.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(27, jsonUtf8.BytesPending); jsonUtf8.Flush(); if (options.Indented) - Assert.Equal(53 + (2 * options.IndentSize) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space + Assert.Equal(53 + (2 * options.IndentSize) + (3 * options.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(53, jsonUtf8.BytesCommitted); @@ -3675,14 +3675,14 @@ public void WritePartialBase64String(JsonWriterOptions options) Assert.Equal(0, jsonUtf8.BytesCommitted); if (options.Indented) - Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + Assert.Equal(17 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(17, jsonUtf8.BytesPending); jsonUtf8.Flush(); if (options.Indented) - Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space + Assert.Equal(17 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(17, jsonUtf8.BytesCommitted); @@ -3692,19 +3692,19 @@ public void WritePartialBase64String(JsonWriterOptions options) jsonUtf8.WriteEndObject(); if (options.Indented) - Assert.Equal(17 + options.IndentSize + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); + Assert.Equal(17 + options.IndentSize + options.NewLine.Length + 1, jsonUtf8.BytesCommitted); else Assert.Equal(17, jsonUtf8.BytesCommitted); if (options.Indented) - Assert.Equal(18 + options.IndentSize + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space + Assert.Equal(18 + options.IndentSize + (2 * options.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else Assert.Equal(18, jsonUtf8.BytesPending); jsonUtf8.Flush(); if (options.Indented) - Assert.Equal(35 + (2 * options.IndentSize) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space + Assert.Equal(35 + (2 * options.IndentSize) + (3 * options.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(35, jsonUtf8.BytesCommitted); @@ -4078,7 +4078,7 @@ private static string GetCommentExpectedString(JsonWriterOptions options) json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } [Theory] @@ -6970,7 +6970,7 @@ private static string GetHelloWorldExpectedString(JsonWriterOptions options, str json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetBase64ExpectedString(JsonWriterOptions options, string propertyName, byte[] value) @@ -6998,7 +6998,7 @@ private static string GetBase64ExpectedString(JsonWriterOptions options, string json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetCommentInArrayExpectedString(JsonWriterOptions options, string comment) @@ -7032,7 +7032,7 @@ private static string GetCommentInArrayExpectedString(JsonWriterOptions options, json.WriteComment(comment); json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetCommentInObjectExpectedString(JsonWriterOptions options, string comment) @@ -7098,7 +7098,7 @@ private static string GetCommentInObjectExpectedString(JsonWriterOptions options json.WriteComment(comment); json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStringsExpectedString(JsonWriterOptions options, string value) @@ -7118,7 +7118,7 @@ private static string GetStringsExpectedString(JsonWriterOptions options, string json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetEscapedExpectedString(JsonWriterOptions options, string propertyName, string value, StringEscapeHandling escaping, bool escape = true) @@ -7136,7 +7136,7 @@ private static string GetEscapedExpectedString(JsonWriterOptions options, string json.WriteEnd(); json.Flush(); - return HandleIndent(stringWriter.ToString(), options); + return HandleFormatting(stringWriter.ToString(), options); } } @@ -7160,7 +7160,7 @@ private static string GetCustomExpectedString(JsonWriterOptions options) json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStartEndExpectedString(JsonWriterOptions options) @@ -7180,7 +7180,7 @@ private static string GetStartEndExpectedString(JsonWriterOptions options) json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStartEndWithPropertyArrayExpectedString(JsonWriterOptions options) @@ -7201,7 +7201,7 @@ private static string GetStartEndWithPropertyArrayExpectedString(JsonWriterOptio json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStartEndWithPropertyArrayExpectedString(string key, JsonWriterOptions options, bool escape = false) @@ -7223,7 +7223,7 @@ private static string GetStartEndWithPropertyArrayExpectedString(string key, Jso json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStartEndWithPropertyObjectExpectedString(JsonWriterOptions options) @@ -7244,7 +7244,7 @@ private static string GetStartEndWithPropertyObjectExpectedString(JsonWriterOpti json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetStartEndWithPropertyObjectExpectedString(string key, JsonWriterOptions options, bool escape = false) @@ -7266,7 +7266,7 @@ private static string GetStartEndWithPropertyObjectExpectedString(string key, Js json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetArrayWithPropertyExpectedString(JsonWriterOptions options) @@ -7286,7 +7286,7 @@ private static string GetArrayWithPropertyExpectedString(JsonWriterOptions optio json.WriteEndObject(); json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetBooleanExpectedString(JsonWriterOptions options, string keyString, bool value, bool escape = false) @@ -7316,7 +7316,7 @@ private static string GetBooleanExpectedString(JsonWriterOptions options, string json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetNullExpectedString(JsonWriterOptions options, string keyString, bool escape = false) @@ -7346,7 +7346,7 @@ private static string GetNullExpectedString(JsonWriterOptions options, string ke json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetPropertyExpectedString(JsonWriterOptions options, T value) @@ -7366,7 +7366,7 @@ private static string GetPropertyExpectedString(JsonWriterOptions options, T json.Flush(); - return HandleIndent(sb.ToString(), options); + return HandleFormatting(sb.ToString(), options); } private static string GetNumbersExpectedString(JsonWriterOptions options, string keyString, int[] ints, uint[] uints, long[] longs, ulong[] ulongs, float[] floats, double[] doubles, decimal[] decimals, bool escape = false) @@ -7432,7 +7432,7 @@ private static string GetNumbersExpectedString(JsonWriterOptions options, string json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetExpectedString_RelaxedEscaping(bool prettyPrint, string keyString) @@ -7492,7 +7492,7 @@ private static string GetGuidsExpectedString(JsonWriterOptions options, string k json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetNumbersExpectedString(JsonWriterOptions options, int numberOfElements, T value) @@ -7514,7 +7514,7 @@ private static string GetNumbersExpectedString(JsonWriterOptions options, int json.Flush(); - return HandleIndent(sb.ToString(), options); + return HandleFormatting(sb.ToString(), options); } private static string GetDatesExpectedString(JsonWriterOptions options, string keyString, DateTime[] dates, bool escape = false) @@ -7546,7 +7546,7 @@ private static string GetDatesExpectedString(JsonWriterOptions options, string k json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static string GetDatesExpectedString(JsonWriterOptions options, string keyString, DateTimeOffset[] dates, bool escape = false) @@ -7578,7 +7578,7 @@ private static string GetDatesExpectedString(JsonWriterOptions options, string k json.Flush(); - return HandleIndent(Encoding.UTF8.GetString(ms.ToArray()), options); + return HandleFormatting(Encoding.UTF8.GetString(ms.ToArray()), options); } private static void CompensateWhitespaces(bool prettyPrint, JsonTextWriter json, TextWriter streamWriter, int whitespaceCount = 1) @@ -7599,9 +7599,16 @@ private static void CompensateNewLine(bool prettyPrint, JsonTextWriter json, Tex } } - private static string HandleIndent(string text, JsonWriterOptions options) + private static string HandleFormatting(string text, JsonWriterOptions options) { - return text.Replace(" ", GetIndentText(options)); + var normalized = text.Replace(" ", GetIndentText(options)); + + if (options.NewLine != Environment.NewLine) + { + normalized = normalized.Replace(Environment.NewLine, options.NewLine); + } + + return normalized; } private static string GetIndentText(JsonWriterOptions options) => new(options.IndentCharacter, options.IndentSize); @@ -7628,13 +7635,15 @@ private static IEnumerable JsonOptions() from skipValidation in new[] { true, false } from indentCharacter in indented ? new char?[] { null, ' ', '\t' } : [] from indentSize in indented ? new int?[] { null, 0, 1, 2, 127 } : [] - select CreateOptions(indented, indentCharacter, indentSize, skipValidation); + from newLine in indented ? new string?[] { null, "\n", "\r\n" } : [] + select CreateOptions(indented, indentCharacter, indentSize, skipValidation, newLine); - static JsonWriterOptions CreateOptions(bool indented, char? indentCharacter, int? indentSize, bool skipValidation) + static JsonWriterOptions CreateOptions(bool indented, char? indentCharacter, int? indentSize, bool skipValidation, string? newLine) { var options = new JsonWriterOptions { Indented = indented, SkipValidation = skipValidation }; if (indentCharacter is not null) options.IndentCharacter = (char)indentCharacter; if (indentSize is not null) options.IndentSize = (int)indentSize; + if (newLine is not null) options.NewLine = newLine; return options; } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs index c79f4de0c7cd49..cac2753615e172 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Threading.Tasks; using Xunit; namespace System.Text.RegularExpressions.Tests @@ -41,7 +42,7 @@ public void EnumerateMatches_Ctor_Invalid() [Theory] [MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))] - public void EnumerateMatches_Lookahead(RegexEngine engine) + public async Task EnumerateMatches_Lookahead(RegexEngine engine) { if (RegexHelpers.IsNonBacktracking(engine)) { @@ -49,23 +50,27 @@ public void EnumerateMatches_Lookahead(RegexEngine engine) return; } - const string Pattern = @"\b(?!un)\w+\b"; - const string Input = "unite one unethical ethics use untie ultimate"; + Test("unite one unethical ethics use untie ultimate", + await RegexHelpers.GetRegexAsync(engine, @"\b(?!un)\w+\b", RegexOptions.IgnoreCase), + ["one", "ethics", "use", "ultimate"]); - Regex r = RegexHelpers.GetRegexAsync(engine, Pattern, RegexOptions.IgnoreCase).GetAwaiter().GetResult(); - int count = 0; - string[] expectedMatches = ["one", "ethics", "use", "ultimate"]; - ReadOnlySpan span = Input.AsSpan(); - foreach (ValueMatch match in r.EnumerateMatches(span)) + static void Test(string input, Regex r, string[] expectedMatches) { - Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString()); + ReadOnlySpan span = input.AsSpan(); + + int count = 0; + foreach (ValueMatch match in r.EnumerateMatches(span)) + { + Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString()); + } + + Assert.Equal(expectedMatches.Length, count); } - Assert.Equal(4, count); } [Theory] [MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))] - public void EnumerateMatches_Lookbehind(RegexEngine engine) + public async Task EnumerateMatches_Lookbehind(RegexEngine engine) { if (RegexHelpers.IsNonBacktracking(engine)) { @@ -73,36 +78,45 @@ public void EnumerateMatches_Lookbehind(RegexEngine engine) return; } - const string Pattern = @"(?<=\b20)\d{2}\b"; - const string Input = "2010 1999 1861 2140 2009"; + Test("2010 1999 1861 2140 2009", + await RegexHelpers.GetRegexAsync(engine, @"(?<=\b20)\d{2}\b", RegexOptions.IgnoreCase), + ["10", "09"]); - Regex r = RegexHelpers.GetRegexAsync(engine, Pattern, RegexOptions.IgnoreCase).GetAwaiter().GetResult(); - int count = 0; - string[] expectedMatches = ["10", "09"]; - ReadOnlySpan span = Input.AsSpan(); - foreach (ValueMatch match in r.EnumerateMatches(span)) + static void Test(string input, Regex r, string[] expectedMatches) { - Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString()); + ReadOnlySpan span = input.AsSpan(); + + int count = 0; + foreach (ValueMatch match in r.EnumerateMatches(span)) + { + Assert.Equal(expectedMatches[count++], span.Slice(match.Index, match.Length).ToString()); + } + + Assert.Equal(expectedMatches.Length, count); } - Assert.Equal(2, count); } [Theory] [MemberData(nameof(RegexHelpers.AvailableEngines_MemberData), MemberType = typeof(RegexHelpers))] - public void EnumerateMatches_CheckIndex(RegexEngine engine) + public async Task EnumerateMatches_CheckIndex(RegexEngine engine) { - const string Pattern = @"e{2}\w\b"; - const string Input = "needing a reed"; - - Regex r = RegexHelpers.GetRegexAsync(engine, Pattern).GetAwaiter().GetResult(); - int count = 0; - string[] expectedMatches = ["eed"]; - int[] expectedIndex = [11]; - ReadOnlySpan span = Input.AsSpan(); - foreach (ValueMatch match in r.EnumerateMatches(span)) + Test("needing a reed", + await RegexHelpers.GetRegexAsync(engine, @"e{2}\w\b"), + ["eed"], + [11]); + + static void Test(string input, Regex r, string[] expectedMatches, int[] expectedIndex) { - Assert.Equal(expectedMatches[count], span.Slice(match.Index, match.Length).ToString()); - Assert.Equal(expectedIndex[count++], match.Index); + ReadOnlySpan span = input.AsSpan(); + + int count = 0; + foreach (ValueMatch match in r.EnumerateMatches(span)) + { + Assert.Equal(expectedMatches[count], span.Slice(match.Index, match.Length).ToString()); + Assert.Equal(expectedIndex[count], match.Index); + + count++; + } } } } @@ -111,19 +125,24 @@ public partial class RegexMultipleMatchTests { [Theory] [MemberData(nameof(Matches_TestData))] - public void EnumerateMatches(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected) + public async Task EnumerateMatches(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected) { - Regex regexAdvanced = RegexHelpers.GetRegexAsync(engine, pattern, options).GetAwaiter().GetResult(); - int count = 0; - ReadOnlySpan span = input.AsSpan(); - foreach (ValueMatch match in regexAdvanced.EnumerateMatches(span)) + Test(input, expected, await RegexHelpers.GetRegexAsync(engine, pattern, options)); + + static void Test(string input, CaptureData[] expected, Regex regexAdvanced) { - Assert.Equal(expected[count].Index, match.Index); - Assert.Equal(expected[count].Length, match.Length); - Assert.Equal(expected[count].Value, span.Slice(match.Index, match.Length).ToString()); - count++; + int count = 0; + ReadOnlySpan span = input.AsSpan(); + foreach (ValueMatch match in regexAdvanced.EnumerateMatches(span)) + { + Assert.Equal(expected[count].Index, match.Index); + Assert.Equal(expected[count].Length, match.Length); + Assert.Equal(expected[count].Value, span.Slice(match.Index, match.Length).ToString()); + count++; + } + + Assert.Equal(expected.Length, count); } - Assert.Equal(expected.Length, count); } } @@ -131,15 +150,20 @@ public partial class RegexMatchTests { [Theory] [MemberData(nameof(Match_Count_TestData))] - public void EnumerateMatches_Count(RegexEngine engine, string pattern, string input, int expectedCount) + public async Task EnumerateMatches_Count(RegexEngine engine, string pattern, string input, int expectedCount) { - Regex r = RegexHelpers.GetRegexAsync(engine, pattern).GetAwaiter().GetResult(); - int count = 0; - foreach (ValueMatch _ in r.EnumerateMatches(input)) + Test(input, expectedCount, await RegexHelpers.GetRegexAsync(engine, pattern)); + + static void Test(string input, int expectedCount, Regex r) { - count++; + int count = 0; + foreach (ValueMatch _ in r.EnumerateMatches(input)) + { + count++; + } + + Assert.Equal(expectedCount, count); } - Assert.Equal(expectedCount, count); } } @@ -147,55 +171,56 @@ public partial class RegexCountTests { [Theory] [MemberData(nameof(Count_ReturnsExpectedCount_TestData))] - public void EnumerateMatches_ReturnsExpectedCount(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount) + public async Task EnumerateMatches_ReturnsExpectedCount(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount) { - Regex r = RegexHelpers.GetRegexAsync(engine, pattern, options).GetAwaiter().GetResult(); - - int count; - - count = 0; - foreach (ValueMatch _ in r.EnumerateMatches(input, startat)) - { - count++; - } - Assert.Equal(expectedCount, count); - - bool isDefaultStartAt = startat == ((options & RegexOptions.RightToLeft) != 0 ? input.Length : 0); - if (!isDefaultStartAt) - { - return; - } + Test(engine, pattern, input, startat, options, expectedCount, await RegexHelpers.GetRegexAsync(engine, pattern, options)); - if (options == RegexOptions.None && engine == RegexEngine.Interpreter) + static void Test(RegexEngine engine, string pattern, string input, int startat, RegexOptions options, int expectedCount, Regex r) { - count = 0; - foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern)) + int count = 0; + foreach (ValueMatch _ in r.EnumerateMatches(input, startat)) { count++; } Assert.Equal(expectedCount, count); - } - switch (engine) - { - case RegexEngine.Interpreter: - case RegexEngine.Compiled: - case RegexEngine.NonBacktracking: - RegexOptions engineOptions = RegexHelpers.OptionsFromEngine(engine); - count = 0; - foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions)) - { - count++; - } - Assert.Equal(expectedCount, count); + bool isDefaultStartAt = startat == ((options & RegexOptions.RightToLeft) != 0 ? input.Length : 0); + if (!isDefaultStartAt) + { + return; + } + if (options == RegexOptions.None && engine == RegexEngine.Interpreter) + { count = 0; - foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout)) + foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern)) { count++; } Assert.Equal(expectedCount, count); - break; + } + + switch (engine) + { + case RegexEngine.Interpreter: + case RegexEngine.Compiled: + case RegexEngine.NonBacktracking: + RegexOptions engineOptions = RegexHelpers.OptionsFromEngine(engine); + count = 0; + foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions)) + { + count++; + } + Assert.Equal(expectedCount, count); + + count = 0; + foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout)) + { + count++; + } + Assert.Equal(expectedCount, count); + break; + } } } } diff --git a/src/libraries/System.Threading/tests/InterlockedTests.cs b/src/libraries/System.Threading/tests/InterlockedTests.cs index b9f61749c80bf5..ae9cd493f4dffa 100644 --- a/src/libraries/System.Threading/tests/InterlockedTests.cs +++ b/src/libraries/System.Threading/tests/InterlockedTests.cs @@ -810,7 +810,7 @@ public void MemoryBarrierProcessWide() } })); } - Task.WaitAll(threads.ToArray()); + Task.WaitAll(threads); Assert.Equal(1000*1000, count); } diff --git a/src/libraries/externals.csproj b/src/libraries/externals.csproj index 7f85d5d67e31fc..6aa753ec79b239 100644 --- a/src/libraries/externals.csproj +++ b/src/libraries/externals.csproj @@ -85,6 +85,10 @@ + + + diff --git a/src/libraries/sendtohelix-browser.targets b/src/libraries/sendtohelix-browser.targets index f710cb8893df73..fa87acbd506e26 100644 --- a/src/libraries/sendtohelix-browser.targets +++ b/src/libraries/sendtohelix-browser.targets @@ -10,7 +10,7 @@ - In this `wasm.helix.targets` file, add to $(HelixExtensionTargets) to run your custom target - + $(HelixExtensionTargets);_AddHelixCrypoItems - Useful properties to condition on: $(Scenario), $(IsRunningLibraryTests) @@ -35,7 +35,7 @@ $(Scenario)- true - true + true @@ -56,7 +56,8 @@ true true false - true + true + true true true @@ -91,31 +92,33 @@ - - + + - - - + + + + - - + + - - - + + + + @@ -176,24 +179,25 @@ - - - - - - - - + + + - - - + + + + + + @@ -222,8 +226,9 @@ - <_WasmWorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnBrowser'" /> - <_WasmWorkItem Include="$(TestArchiveRoot)browserornodejs/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnBrowser'" /> + <_WasmWorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome'" /> + <_WasmWorkItem Include="$(TestArchiveRoot)chromeonly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome'" /> + <_WasmWorkItem Include="$(TestArchiveRoot)browserornodejs/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome'" /> <_WasmWorkItem Include="$(TestArchiveRoot)browserornodejs/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnNodeJS'" /> <_WasmWorkItem Include="$(TestArchiveRoot)nodejsonly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnNodeJS'" /> @@ -246,7 +251,7 @@ <_WasmSampleZipFile Condition="'$(Scenario)' == 'normal' or '$(Scenario)' == ''" Include="$(TestArchiveRoot)runonly/**/*.Console.V8.*.Sample.zip" /> <_WasmSampleZipFile Condition="'$(Scenario)' == 'WasmTestOnNodeJS'" Include="$(TestArchiveRoot)runonly/**/*.Console.Node.*.Sample.zip" /> - <_WasmSampleZipFile Condition="'$(Scenario)' == 'WasmTestOnBrowser'" Include="$(TestArchiveRoot)runonly/**/*.Browser.*.Sample.zip" /> + <_WasmSampleZipFile Condition="'$(Scenario)' == 'WasmTestOnChrome'" Include="$(TestArchiveRoot)runonly/**/*.Browser.*.Sample.zip" /> %(Identity) diff --git a/src/libraries/sendtohelix-wasi.targets b/src/libraries/sendtohelix-wasi.targets index bedb1d4192a8ab..f89da35e17e3e8 100644 --- a/src/libraries/sendtohelix-wasi.targets +++ b/src/libraries/sendtohelix-wasi.targets @@ -10,7 +10,7 @@ - In this `wasm.helix.targets` file, add to $(HelixExtensionTargets) to run your custom target - + $(HelixExtensionTargets);_AddHelixCrypoItems - Useful properties to condition on: $(Scenario), $(IsRunningLibraryTests) @@ -31,8 +31,8 @@ - true - wasmtime + true + wasmtime true <_ShippingPackagesPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir), 'packages', $(Configuration), 'Shipping')) @@ -104,7 +104,10 @@ + + + @@ -127,7 +130,7 @@ - <_WasiWorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnBrowser'" /> + <_WasiWorkItem Include="$(TestArchiveRoot)wasmtimeonly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnWasmtime'" /> %(Identity) $(HelixCommand) @@ -137,7 +140,7 @@ - <_WasiSampleZipFile Condition="'$(Scenario)' == 'normal' or '$(Scenario)' == ''" Include="$(TestArchiveRoot)runonly/**/*.Console.V8.*.Sample.zip" /> + <_WasiSampleZipFile Condition="'$(Scenario)' == 'WasmTestOnWasmtime'" Include="$(TestArchiveRoot)runonly/**/Wasi.*.Sample.zip" /> %(Identity) diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index 7c668b03471249..e3efcd584ce4f0 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -115,11 +115,11 @@ setting up the per-scenario environment. --> - + - + diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 95f7f110cda509..905dc83eb08dee 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -26,6 +26,7 @@ + AnyCPU true false @@ -48,40 +49,30 @@ - x64 - false $(DefineConstants);TARGET_AMD64 - x86 $(DefineConstants);TARGET_X86 - arm $(DefineConstants);TARGET_ARMV6 - arm $(DefineConstants);TARGET_ARM - AnyCPU $(DefineConstants);TARGET_ARM64 - AnyCPU $(DefineConstants);TARGET_RISCV64 - AnyCPU $(DefineConstants);TARGET_S390X - AnyCPU $(DefineConstants);TARGET_WASM - AnyCPU $(DefineConstants);TARGET_POWERPC64 diff --git a/src/mono/browser/README.md b/src/mono/browser/README.md index 4fb494a86b199d..ced0882ae83155 100644 --- a/src/mono/browser/README.md +++ b/src/mono/browser/README.md @@ -362,18 +362,18 @@ npm update --lockfile-version=1 Tests are run with V8, Chrome, node, and wasmtime for the various jobs. -- V8: the version used is from `eng/testing/ChromeVersions.props`. This is used for all the library tests, and WBT, but *not* runtime tests. +- V8: the version used is from `eng/testing/BrowserVersions.props`. This is used for all the library tests, and WBT, but *not* runtime tests. - Chrome: Same as V8. - Node: fixed version from emsdk - wasmtime - fixed version in `src/mono/wasi/wasi-sdk-version.txt`. -### `eng/testing/ChromeVersions.props` +### `eng/testing/BrowserVersions.props` This file is updated once a week by a github action `.github/workflows/bump-chrome-version.yml`, and the version is obtained by `src/tasks/WasmBuildTasks/GetChromeVersions.cs` task. # Perf pipeline -- V8 version used to run the microbenchmarks is from `eng/testing/ChromeVersions.props` +- V8 version used to run the microbenchmarks is from `eng/testing/BrowserVersions.props` TBD diff --git a/src/mono/browser/debugger/DebuggerTestSuite/ChromeProvider.cs b/src/mono/browser/debugger/DebuggerTestSuite/ChromeProvider.cs index 17eedf748ed729..1da9fbca98075d 100644 --- a/src/mono/browser/debugger/DebuggerTestSuite/ChromeProvider.cs +++ b/src/mono/browser/debugger/DebuggerTestSuite/ChromeProvider.cs @@ -28,8 +28,8 @@ internal class ChromeProvider : WasmHostProvider private DebuggerProxy? _debuggerProxy; private static readonly Lazy s_browserPath = new(() => { - string artifactsBinDir = Path.Combine(Path.GetDirectoryName(typeof(ChromeProvider).Assembly.Location)!, "..", "..", ".."); - return BrowserLocator.FindChrome(artifactsBinDir, "BROWSER_PATH_FOR_TESTS"); + string artifactsBinDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(ChromeProvider).Assembly.Location)!, "..", "..", "..")); + return BrowserLocator.FindChrome(artifactsBinDir, "CHROME_PATH_FOR_TESTS"); }); private static readonly string[] s_messagesToFilterOut = new[] { diff --git a/src/mono/browser/debugger/DebuggerTestSuite/FirefoxProvider.cs b/src/mono/browser/debugger/DebuggerTestSuite/FirefoxProvider.cs index b64fe84ca3d92f..e854b63b0ef74c 100644 --- a/src/mono/browser/debugger/DebuggerTestSuite/FirefoxProvider.cs +++ b/src/mono/browser/debugger/DebuggerTestSuite/FirefoxProvider.cs @@ -23,8 +23,8 @@ internal class FirefoxProvider : WasmHostProvider private FirefoxDebuggerProxy? _firefoxDebuggerProxy; private static readonly Lazy s_browserPath = new(() => { - string artifactsBinDir = Path.Combine(Path.GetDirectoryName(typeof(ChromeProvider).Assembly.Location)!, "..", "..", ".."); - return BrowserLocator.FindFirefox(artifactsBinDir, "BROWSER_PATH_FOR_TESTS"); + string artifactsBinDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(ChromeProvider).Assembly.Location)!, "..", "..", "..")); + return BrowserLocator.FindFirefox(artifactsBinDir, "FIREFOX_PATH_FOR_TESTS"); }); public FirefoxProvider(string id, ILogger logger) : base(id, logger) diff --git a/src/mono/browser/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj b/src/mono/browser/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj index f305f84eb7539d..008a633de76263 100644 --- a/src/mono/browser/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj +++ b/src/mono/browser/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj @@ -52,12 +52,12 @@ - - + + - + diff --git a/src/mono/browser/debugger/Wasm.Debugger.Tests/wasm.helix.targets b/src/mono/browser/debugger/Wasm.Debugger.Tests/wasm.helix.targets index e645a2c42c4d5d..4c36a8937fc1fd 100644 --- a/src/mono/browser/debugger/Wasm.Debugger.Tests/wasm.helix.targets +++ b/src/mono/browser/debugger/Wasm.Debugger.Tests/wasm.helix.targets @@ -1,6 +1,7 @@ - true + true + true $(DebuggerHost)- true <_DebuggerTestsWorkItemTimeout Condition="'$(Scenario)' == 'WasmDebuggerTests'">00:50:00 diff --git a/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html b/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html index 9d04b58b665c55..e140e64e6ad4cd 100644 --- a/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html @@ -1,4 +1,4 @@ - + diff --git a/src/mono/browser/debugger/tests/debugger-test/non-wasm-page.html b/src/mono/browser/debugger/tests/debugger-test/non-wasm-page.html index 2894375a79c809..1442fc9b6af961 100644 --- a/src/mono/browser/debugger/tests/debugger-test/non-wasm-page.html +++ b/src/mono/browser/debugger/tests/debugger-test/non-wasm-page.html @@ -1,4 +1,4 @@ - + diff --git a/src/mono/browser/debugger/tests/debugger-test/wasm-page-without-assets.html b/src/mono/browser/debugger/tests/debugger-test/wasm-page-without-assets.html index 49a836c8e88598..9fcdbf4570c1ee 100644 --- a/src/mono/browser/debugger/tests/debugger-test/wasm-page-without-assets.html +++ b/src/mono/browser/debugger/tests/debugger-test/wasm-page-without-assets.html @@ -1,4 +1,4 @@ - + diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index ed64d5c541d4b8..61ae0be40c7686 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -99,6 +99,7 @@ const fn_signatures: SigLine[] = [ [true, "mono_jiterp_get_size_of_stackval", "number", []], [true, "mono_jiterp_parse_option", "number", ["string"]], [true, "mono_jiterp_get_options_as_json", "number", []], + [true, "mono_jiterp_get_option_as_int", "number", ["string"]], [true, "mono_jiterp_get_options_version", "number", []], [true, "mono_jiterp_adjust_abort_count", "number", ["number", "number"]], [true, "mono_jiterp_register_jit_call_thunk", "void", ["number", "number"]], @@ -228,6 +229,7 @@ export interface t_Cwraps { mono_jiterp_type_get_raw_value_size(type: MonoType): number; mono_jiterp_parse_option(name: string): number; mono_jiterp_get_options_as_json(): number; + mono_jiterp_get_option_as_int(name: string): number; mono_jiterp_get_options_version(): number; mono_jiterp_adjust_abort_count(opcode: number, delta: number): number; mono_jiterp_register_jit_call_thunk(cinfo: number, func: number): void; diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 16df07663ea7e2..510aa61fcc91cd 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -32,6 +32,7 @@ declare interface EmscriptenModule { UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; + lengthBytesUTF8(str: string): number; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; addFunction(fn: Function, signature: string): number; diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index d26e3c8b57ce20..577b1131974d50 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -24,6 +24,8 @@ import { jiterpreter_dump_stats } from "./jiterpreter"; import { forceDisposeProxies } from "./gc-handles"; import { mono_wasm_dump_threads } from "./pthreads"; +import { threads_c_functions as tcwraps } from "./cwraps"; + export let runtimeList: RuntimeList; function initializeExports (globalObjects: GlobalObjects): RuntimeAPI { @@ -43,6 +45,7 @@ function initializeExports (globalObjects: GlobalObjects): RuntimeAPI { }; if (WasmEnableThreads) { rh.dumpThreads = mono_wasm_dump_threads; + rh.mono_wasm_print_thread_dump = () => tcwraps.mono_wasm_print_thread_dump(); } Object.assign(runtimeHelpers, rh); diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index e71c3700e09d88..f3f6e6a9f6b48d 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -9,7 +9,6 @@ import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; import { mono_log_error, mono_log_info } from "./logging"; import { localHeapViewU8, localHeapViewU32 } from "./memory"; -import { utf8ToString } from "./strings"; import { JiterpNumberMode, BailoutReason, JiterpreterTable, JiterpCounter, JiterpMember, OpcodeInfoType @@ -2015,15 +2014,13 @@ export function getOptions () { } function updateOptions () { - const pJson = cwraps.mono_jiterp_get_options_as_json(); - const json = utf8ToString(pJson); - Module._free(pJson); - const blob = JSON.parse(json); - optionTable = {}; for (const k in optionNames) { - const info = optionNames[k]; - (optionTable)[k] = blob[info]; + const value = cwraps.mono_jiterp_get_option_as_int(optionNames[k]); + if (value > -2147483647) + (optionTable)[k] = value; + else + mono_log_info(`Failed to retrieve value of option ${optionNames[k]}`); } } diff --git a/src/mono/browser/runtime/loader/exit.ts b/src/mono/browser/runtime/loader/exit.ts index 735c4a9576d8cd..ecb2b46bb1f1f8 100644 --- a/src/mono/browser/runtime/loader/exit.ts +++ b/src/mono/browser/runtime/loader/exit.ts @@ -68,6 +68,13 @@ function onAbort (reason: any) { if (originalOnAbort) { originalOnAbort(reason || loaderHelpers.exitReason); } + if (WasmEnableThreads && loaderHelpers.config?.dumpThreadsOnNonZeroExit && runtimeHelpers.mono_wasm_print_thread_dump && loaderHelpers.exitCode === undefined) { + try { + runtimeHelpers.mono_wasm_print_thread_dump(); + } catch (e) { + // ignore + } + } mono_exit(1, reason || loaderHelpers.exitReason); } diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index e5579bfeba0f8c..3e83f751c0b8ad 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -237,6 +237,7 @@ export type RuntimeHelpers = { jiterpreter_dump_stats?: (concise?: boolean) => void, forceDisposeProxies: (disposeMethods: boolean, verbose: boolean) => void, dumpThreads: () => void, + mono_wasm_print_thread_dump: () => void, } export type AOTProfilerOptions = { diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js index 90b332aa5218a3..1ada6f3cdde10f 100644 --- a/src/mono/browser/test-main.js +++ b/src/mono/browser/test-main.js @@ -24,6 +24,8 @@ export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof dotnet export const ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_SIDECAR; // we redefine what ENVIRONMENT_IS_WORKER, we replace it in emscripten internals, so that sidecar works export const ENVIRONMENT_IS_WEB = typeof window == "object" || (ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_NODE); export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; +export const isFirefox = !!(ENVIRONMENT_IS_WEB && navigator.userAgent.includes("Firefox")); +export const isChromium = !!(ENVIRONMENT_IS_WEB && navigator.userAgentData && navigator.userAgentData.brands.some(b => b.brand === "Google Chrome" || b.brand === "Microsoft Edge" || b.brand === "Chromium")); if (ENVIRONMENT_IS_NODE && process.versions.node.split(".")[0] < 14) { throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`); @@ -51,8 +53,14 @@ if (!ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WEB && typeof globalThis.crypto === } } -if (ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER) { - console.log("Running '" + globalThis.navigator.userAgent + "' at: \n" + globalThis.location.href + "\n"); +if (ENVIRONMENT_IS_WEB && isFirefox) { + Error.stackTraceLimit = 1000; +} + +// as soon as possible, see https://github.com/dotnet/runtime/issues/101169 +if (ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER && !isFirefox) { + console.log("Running in: " + globalThis.navigator.userAgent); + console.log("Running at: " + globalThis.location.href); } let v8args; @@ -179,6 +187,8 @@ function processArguments(incomingArguments, runArgs) { // cheap way to let the testing infrastructure know we're running in a browser context (or not) runArgs.environmentVariables["IsBrowserDomSupported"] = ENVIRONMENT_IS_WEB.toString().toLowerCase(); runArgs.environmentVariables["IsNodeJS"] = ENVIRONMENT_IS_NODE.toString().toLowerCase(); + runArgs.environmentVariables["IsFirefox"] = isFirefox.toString().toLowerCase(); + runArgs.environmentVariables["IsChromium"] = isChromium.toString().toLowerCase(); return runArgs; } @@ -312,6 +322,13 @@ async function run() { App.runtime = await dotnet.create(); App.runArgs = runArgs + // after console proxy was setup, see https://github.com/dotnet/runtime/issues/101169 + if (ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER && isFirefox) { + console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); + console.log("Running in: " + globalThis.navigator.userAgent); + console.log("Running at: " + globalThis.location.href); + } + console.info("Initializing dotnet version " + App.runtime.runtimeBuildInfo.productVersion + " commit hash " + App.runtime.runtimeBuildInfo.gitHash); for (let i = 0; i < runArgs.profilers.length; ++i) { diff --git a/src/mono/mono/eglib/glib.h b/src/mono/mono/eglib/glib.h index d6c1e2e59a9a9c..7957b6949c130e 100644 --- a/src/mono/mono/eglib/glib.h +++ b/src/mono/mono/eglib/glib.h @@ -759,6 +759,7 @@ const char * g_get_assertion_message (void); #define g_message(...) g_log_disabled (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, __FILE__, __LINE__) #define g_debug(...) g_log_disabled (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, __FILE__, __LINE__) #endif +#define g_warning_dont_trim(...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, __VA_ARGS__) typedef void (*GLogFunc) (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); typedef void (*GPrintFunc) (const gchar *string); diff --git a/src/mono/mono/metadata/CMakeLists.txt b/src/mono/mono/metadata/CMakeLists.txt index 773381d6c1c9ad..9efd5ac0079c24 100644 --- a/src/mono/mono/metadata/CMakeLists.txt +++ b/src/mono/mono/metadata/CMakeLists.txt @@ -42,6 +42,11 @@ else() set(metadata_platform_sources ${metadata_unix_sources}) endif() +set(imported_native_sources + ../../../native/containers/dn-simdhash.c + ../../../native/containers/dn-simdhash-string-ptr.c + ../../../native/containers/dn-simdhash-u32-ptr.c) + set(metadata_common_sources appdomain.c domain.c @@ -195,7 +200,7 @@ elseif(MONO_GC STREQUAL "boehm") set(metadata_compile_definitions "HAVE_BOEHM_GC") endif() -set(metadata_sources "${metadata_platform_sources};${metadata_common_sources};${metadata_gc_dependent_sources};${metadata_gc_sources};${ilgen_sources}") +set(metadata_sources "${metadata_platform_sources};${metadata_common_sources};${metadata_gc_dependent_sources};${metadata_gc_sources};${ilgen_sources};${imported_native_sources}") if(HOST_WIN32 AND NOT DISABLE_SHARED_LIBS) add_library(metadata_objects_shared OBJECT ${metadata_sources}) diff --git a/src/mono/mono/metadata/class.c b/src/mono/mono/metadata/class.c index 05da1332e3012f..5ae4f1981d38ac 100644 --- a/src/mono/mono/metadata/class.c +++ b/src/mono/mono/metadata/class.c @@ -3037,18 +3037,22 @@ mono_image_init_name_cache (MonoImage *image) const char *name; const char *nspace; guint32 visib, nspace_index; - GHashTable *name_cache2, *nspace_table, *the_name_cache; + dn_simdhash_u32_ptr_t *name_cache2; + dn_simdhash_string_ptr_t *nspace_table, *the_name_cache; if (image->name_cache) return; - the_name_cache = g_hash_table_new (g_str_hash, g_str_equal); + // TODO: Figure out a good initial capacity for this table by doing a scan, + // or just pre-reserve a reasonable amount of space based on how many nspaces + // an image typically has + the_name_cache = dn_simdhash_string_ptr_new (0, NULL); if (image_is_dynamic (image)) { mono_image_lock (image); if (image->name_cache) { /* Somebody initialized it before us */ - g_hash_table_destroy (the_name_cache); + dn_simdhash_free (the_name_cache); } else { mono_atomic_store_release (&image->name_cache, the_name_cache); } @@ -3057,7 +3061,7 @@ mono_image_init_name_cache (MonoImage *image) } /* Temporary hash table to avoid lookups in the nspace_table */ - name_cache2 = g_hash_table_new (NULL, NULL); + name_cache2 = dn_simdhash_u32_ptr_new (0, NULL); /* FIXME: metadata-update */ int rows = table_info_get_rows (t); @@ -3074,14 +3078,13 @@ mono_image_init_name_cache (MonoImage *image) nspace = mono_metadata_string_heap (image, cols [MONO_TYPEDEF_NAMESPACE]); nspace_index = cols [MONO_TYPEDEF_NAMESPACE]; - nspace_table = (GHashTable *)g_hash_table_lookup (name_cache2, GUINT_TO_POINTER (nspace_index)); - if (!nspace_table) { - nspace_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (the_name_cache, (char*)nspace, nspace_table); - g_hash_table_insert (name_cache2, GUINT_TO_POINTER (nspace_index), - nspace_table); + if (!dn_simdhash_u32_ptr_try_get_value (name_cache2, nspace_index, (void **)&nspace_table)) { + // FIXME: Compute an appropriate capacity for this table to avoid growing it + nspace_table = dn_simdhash_string_ptr_new (0, NULL); + dn_simdhash_string_ptr_try_add (the_name_cache, nspace, nspace_table); + dn_simdhash_u32_ptr_try_add (name_cache2, nspace_index, nspace_table); } - g_hash_table_insert (nspace_table, (char *) name, GUINT_TO_POINTER (i)); + dn_simdhash_string_ptr_try_add (nspace_table, name, GUINT_TO_POINTER (i)); } /* Load type names from EXPORTEDTYPES table */ @@ -3102,23 +3105,22 @@ mono_image_init_name_cache (MonoImage *image) nspace = mono_metadata_string_heap (image, exptype_cols [MONO_EXP_TYPE_NAMESPACE]); nspace_index = exptype_cols [MONO_EXP_TYPE_NAMESPACE]; - nspace_table = (GHashTable *)g_hash_table_lookup (name_cache2, GUINT_TO_POINTER (nspace_index)); - if (!nspace_table) { - nspace_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (the_name_cache, (char*)nspace, nspace_table); - g_hash_table_insert (name_cache2, GUINT_TO_POINTER (nspace_index), - nspace_table); + if (!dn_simdhash_u32_ptr_try_get_value (name_cache2, nspace_index, (void **)&nspace_table)) { + // FIXME: Compute an appropriate capacity for this table to avoid growing it + nspace_table = dn_simdhash_string_ptr_new (0, NULL); + dn_simdhash_string_ptr_try_add (the_name_cache, nspace, nspace_table); + dn_simdhash_u32_ptr_try_add (name_cache2, nspace_index, nspace_table); } - g_hash_table_insert (nspace_table, (char *) name, GUINT_TO_POINTER (mono_metadata_make_token (MONO_TABLE_EXPORTEDTYPE, i + 1))); + dn_simdhash_string_ptr_try_add (nspace_table, name, GUINT_TO_POINTER (mono_metadata_make_token (MONO_TABLE_EXPORTEDTYPE, i + 1))); } } - g_hash_table_destroy (name_cache2); + dn_simdhash_free (name_cache2); mono_image_lock (image); if (image->name_cache) { /* Somebody initialized it before us */ - g_hash_table_destroy (the_name_cache); + dn_simdhash_free (the_name_cache); } else { mono_atomic_store_release (&image->name_cache, the_name_cache); } @@ -3133,23 +3135,19 @@ void mono_image_add_to_name_cache (MonoImage *image, const char *nspace, const char *name, guint32 index) { - GHashTable *nspace_table; - GHashTable *name_cache; - guint32 old_index; + dn_simdhash_string_ptr_t *nspace_table, *name_cache; mono_image_init_name_cache (image); mono_image_lock (image); name_cache = image->name_cache; - if (!(nspace_table = (GHashTable *)g_hash_table_lookup (name_cache, nspace))) { - nspace_table = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (name_cache, (char *)nspace, (char *)nspace_table); + if (!dn_simdhash_string_ptr_try_get_value (name_cache, nspace, (void **)&nspace_table)) { + nspace_table = dn_simdhash_string_ptr_new (0, NULL); + dn_simdhash_string_ptr_try_add (name_cache, nspace, nspace_table); } - if ((old_index = GPOINTER_TO_UINT (g_hash_table_lookup (nspace_table, (char*) name)))) - g_error ("overrwritting old token %x on image %s for type %s::%s", old_index, image->name, nspace, name); - - g_hash_table_insert (nspace_table, (char *) name, GUINT_TO_POINTER (index)); + if (!dn_simdhash_string_ptr_try_add (nspace_table, name, GUINT_TO_POINTER (index))) + g_error ("overrwritting old token ? on image %s for type %s::%s", image->name, nspace, name); mono_image_unlock (image); } @@ -3160,9 +3158,8 @@ typedef struct { } FindAllUserData; static void -find_all_nocase (gpointer key, gpointer value, gpointer user_data) +find_all_nocase (const char *name, gpointer value, gpointer user_data) { - char *name = (char*)key; FindAllUserData *data = (FindAllUserData*)user_data; if (mono_utf8_strcasecmp (name, (char*)data->key) == 0) data->values = g_slist_prepend (data->values, value); @@ -3174,9 +3171,8 @@ typedef struct { } FindUserData; static void -find_nocase (gpointer key, gpointer value, gpointer user_data) +find_nocase (const char *name, gpointer value, gpointer user_data) { - char *name = (char*)key; FindUserData *data = (FindUserData*)user_data; if (!data->value && (mono_utf8_strcasecmp (name, (char*)data->key) == 0)) @@ -3303,7 +3299,7 @@ search_modules (MonoImage *image, const char *name_space, const char *name, gboo static MonoClass * mono_class_from_name_checked_aux (MonoImage *image, const char* name_space, const char *name, GHashTable* visited_images, gboolean case_sensitive, MonoError *error) { - GHashTable *nspace_table = NULL; + dn_simdhash_string_ptr_t *nspace_table = NULL; MonoImage *loaded_image = NULL; guint32 token = 0; MonoClass *klass; @@ -3350,10 +3346,11 @@ mono_class_from_name_checked_aux (MonoImage *image, const char* name_space, cons mono_image_lock (image); if (case_sensitive) { - nspace_table = (GHashTable *)g_hash_table_lookup (image->name_cache, name_space); - - if (nspace_table) - token = GPOINTER_TO_UINT (g_hash_table_lookup (nspace_table, name)); + if (dn_simdhash_string_ptr_try_get_value (image->name_cache, name_space, (void **)&nspace_table)) { + void * temp; + if (dn_simdhash_string_ptr_try_get_value (nspace_table, name, &temp)) + token = GPOINTER_TO_UINT(temp); + } } else { FindAllUserData all_user_data = { name_space, NULL }; FindUserData user_data = { name, NULL }; @@ -3361,12 +3358,12 @@ mono_class_from_name_checked_aux (MonoImage *image, const char* name_space, cons // We're forced to check all matching namespaces, not just the first one found, // because our desired type could be in any of the ones that match case-insensitively. - g_hash_table_foreach (image->name_cache, find_all_nocase, &all_user_data); + dn_simdhash_string_ptr_foreach (image->name_cache, find_all_nocase, &all_user_data); values = all_user_data.values; while (values && !user_data.value) { - nspace_table = (GHashTable*)values->data; - g_hash_table_foreach (nspace_table, find_nocase, &user_data); + nspace_table = (dn_simdhash_string_ptr_t *)values->data; + dn_simdhash_string_ptr_foreach (nspace_table, find_nocase, &user_data); values = values->next; } diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 2fbed9bbe44df4..0e29ed10ffedee 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -6164,16 +6164,16 @@ void ves_icall_System_Environment_FailFast (MonoStringHandle message, MonoExceptionHandle exception, MonoStringHandle errorSource, MonoError *error) { if (MONO_HANDLE_IS_NULL (errorSource)) { - g_warning ("Process terminated."); + g_warning_dont_trim ("Process terminated."); } else { char *errorSourceMsg = mono_string_handle_to_utf8 (errorSource, error); - g_warning ("Process terminated. %s", errorSourceMsg); + g_warning_dont_trim ("Process terminated. %s", errorSourceMsg); g_free (errorSourceMsg); } if (!MONO_HANDLE_IS_NULL (message)) { char *msg = mono_string_handle_to_utf8 (message, error); - g_warning (msg); + g_warning_dont_trim (msg); g_free (msg); } diff --git a/src/mono/mono/metadata/image.c b/src/mono/mono/metadata/image.c index 23333b1b2a9764..b40199eb308782 100644 --- a/src/mono/mono/metadata/image.c +++ b/src/mono/mono/metadata/image.c @@ -1969,6 +1969,12 @@ free_hash_table (gpointer key, gpointer val, gpointer user_data) g_hash_table_destroy ((GHashTable*)val); } +static void +free_simdhash_table (const char *key, gpointer val, gpointer user_data) +{ + dn_simdhash_free ((dn_simdhash_t*)val); +} + /* static void free_mr_signatures (gpointer key, gpointer val, gpointer user_data) @@ -2128,8 +2134,8 @@ mono_image_close_except_pools (MonoImage *image) if (image->ptr_cache) g_hash_table_destroy (image->ptr_cache); if (image->name_cache) { - g_hash_table_foreach (image->name_cache, free_hash_table, NULL); - g_hash_table_destroy (image->name_cache); + dn_simdhash_string_ptr_foreach (image->name_cache, free_simdhash_table, NULL); + dn_simdhash_free (image->name_cache); } free_hash (image->icall_wrapper_cache); diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 5022d57c40878c..8c1dfd803b578d 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -19,6 +19,8 @@ #include #include "mono/utils/mono-conc-hashtable.h" #include "mono/utils/refcount.h" +// for dn_simdhash_string_ptr_t and dn_simdhash_u32_ptr_t +#include "../native/containers/dn-simdhash-specializations.h" struct _MonoType { union { @@ -438,7 +440,7 @@ struct _MonoImage { /* * Indexes namespaces to hash tables that map class name to typedef token. */ - GHashTable *name_cache; /*protected by the image lock*/ + dn_simdhash_string_ptr_t *name_cache; /*protected by the image lock*/ /* * Indexed by MonoClass diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index ed745518559ba9..c517703415eab9 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -997,10 +997,10 @@ mono_metadata_table_bounds_check_slow (MonoImage *image, int table_index, int to if (G_LIKELY (GINT_TO_UINT32(token_index) <= table_info_get_rows (&image->tables [table_index]))) return FALSE; - if (G_LIKELY (!image->has_updates)) - return TRUE; + if (G_LIKELY (!image->has_updates)) + return TRUE; - return mono_metadata_update_table_bounds_check (image, table_index, token_index); + return mono_metadata_update_table_bounds_check (image, table_index, token_index); } void @@ -1094,7 +1094,7 @@ get_blob_heap (MonoImage *image) static gboolean mono_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, guint32 orig_index, MonoImage **image_out, guint32 *index_out) { - return mono_metadata_update_delta_heap_lookup (base_image, get_heap, orig_index, image_out, index_out); + return mono_metadata_update_delta_heap_lookup (base_image, get_heap, orig_index, image_out, index_out); } /** @@ -6451,12 +6451,12 @@ mono_metadata_events_from_typedef (MonoImage *meta, guint32 index, guint *end_id } start = mono_metadata_decode_row_col (tdef, loc.result, MONO_EVENT_MAP_EVENTLIST); - /* - * metadata-update: note this next line needs block needs to look at the number of rows in - * EventMap and Event of the base image. Updates will add rows for new properties, - * but they won't be contiguous. if we set end to the number of rows in the updated - * Property table, the range will include properties from some other class - */ + /* + * metadata-update: note this next line needs block needs to look at the number of rows in + * EventMap and Event of the base image. Updates will add rows for new properties, + * but they won't be contiguous. if we set end to the number of rows in the updated + * Property table, the range will include properties from some other class + */ if (loc.result + 1 < table_info_get_rows (tdef)) { end = mono_metadata_decode_row_col (tdef, loc.result + 1, MONO_EVENT_MAP_EVENTLIST) - 1; } else { @@ -6569,12 +6569,12 @@ mono_metadata_properties_from_typedef (MonoImage *meta, guint32 index, guint *en } start = mono_metadata_decode_row_col (tdef, loc.result, MONO_PROPERTY_MAP_PROPERTY_LIST); - /* - * metadata-update: note this next line needs block needs to look at the number of rows in - * PropertyMap and Property of the base image. Updates will add rows for new properties, - * but they won't be contiguous. if we set end to the number of rows in the updated - * Property table, the range will include properties from some other class - */ + /* + * metadata-update: note this next line needs block needs to look at the number of rows in + * PropertyMap and Property of the base image. Updates will add rows for new properties, + * but they won't be contiguous. if we set end to the number of rows in the updated + * Property table, the range will include properties from some other class + */ if (loc.result + 1 < table_info_get_rows (&meta->tables [MONO_TABLE_PROPERTYMAP])) { end = mono_metadata_decode_row_col (tdef, loc.result + 1, MONO_PROPERTY_MAP_PROPERTY_LIST) - 1; } else { @@ -7088,10 +7088,10 @@ mono_metadata_get_marshal_info (MonoImage *meta, guint32 idx, gboolean is_field) gboolean found = tdef->base && mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, table_locator); - if (G_UNLIKELY (meta->has_updates)) { - if (!found && !mono_metadata_update_metadata_linear_search (meta, tdef, &loc, table_locator)) - return NULL; - } + if (G_UNLIKELY (meta->has_updates)) { + if (!found && !mono_metadata_update_metadata_linear_search (meta, tdef, &loc, table_locator)) + return NULL; + } return mono_metadata_blob_heap (meta, mono_metadata_decode_row_col (tdef, loc.result, MONO_FIELD_MARSHAL_NATIVE_TYPE)); } @@ -8055,3 +8055,12 @@ mono_metadata_get_method_params (MonoImage *image, uint32_t method_idx, uint32_t return param_index; } + +// Required by dn_simdhash +void +dn_simdhash_assert_fail (const char *file, int line, const char *condition); + +void +dn_simdhash_assert_fail (const char *file, int line, const char *condition) { + mono_assertion_message (file, line, condition); +} diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index a4e435ceeb7898..7de2bf84d84852 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -4098,6 +4098,8 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause // Not created from interpreted code g_assert (del->method); del_imethod = mono_interp_get_imethod (del->method); + if (del->target && m_method_is_virtual (del->method)) + del_imethod = get_virtual_method (del_imethod, del->target->vtable); del->interp_method = del_imethod; del->interp_invoke_impl = del_imethod; } else { diff --git a/src/mono/mono/mini/interp/jiterpreter.c b/src/mono/mono/mini/interp/jiterpreter.c index 12678e10e828aa..43b71e70cd3d50 100644 --- a/src/mono/mono/mini/interp/jiterpreter.c +++ b/src/mono/mono/mini/interp/jiterpreter.c @@ -1021,8 +1021,28 @@ mono_jiterp_get_options_as_json () return mono_options_get_as_json (); } +EMSCRIPTEN_KEEPALIVE gint32 +mono_jiterp_get_option_as_int (const char *name) +{ + MonoOptionType type; + void *value_address; + + if (!mono_options_get (name, &type, &value_address)) + return INT32_MIN; + + switch (type) { + case MONO_OPTION_BOOL: + case MONO_OPTION_BOOL_READONLY: + return (*(guint8 *)value_address) != 0; + case MONO_OPTION_INT: + return *(gint32 *)value_address; + default: + return INT32_MIN; + } +} + EMSCRIPTEN_KEEPALIVE int -mono_jiterp_object_has_component_size (MonoObject ** ppObj) +mono_jiterp_object_has_component_size (MonoObject **ppObj) { MonoObject *obj = *ppObj; if (!obj) diff --git a/src/mono/mono/utils/atomic.h b/src/mono/mono/utils/atomic.h index 7c7c684ab94eb9..7d1d127f60c5ce 100644 --- a/src/mono/mono/utils/atomic.h +++ b/src/mono/mono/utils/atomic.h @@ -115,7 +115,7 @@ mono_atomic_cas_i64 (volatile gint64 *dest, gint64 exch, gint64 comp) (void)atomic_compare_exchange_strong ((volatile atomic_llong *)dest, (long long*)&comp, exch); return comp; #else -#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC +#error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif } @@ -188,7 +188,7 @@ mono_atomic_xchg_i64 (volatile gint64 *dest, gint64 exch) g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2); return atomic_exchange ((volatile atomic_llong *)dest, exch); #else -#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC +#error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif } @@ -216,7 +216,7 @@ mono_atomic_fetch_add_i64 (volatile gint64 *dest, gint64 add) g_static_assert (sizeof (atomic_llong) == sizeof (*dest) && ATOMIC_LLONG_LOCK_FREE == 2); return atomic_fetch_add ((volatile atomic_llong *)dest, add); #else -#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC +#error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif } @@ -250,7 +250,7 @@ mono_atomic_load_i64 (volatile gint64 *src) g_static_assert (sizeof (atomic_llong) == sizeof (*src) && ATOMIC_LLONG_LOCK_FREE == 2); return atomic_load ((volatile atomic_llong *)src); #else -#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC +#error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif } @@ -292,7 +292,7 @@ mono_atomic_store_i64 (volatile gint64 *dst, gint64 val) g_static_assert (sizeof (atomic_llong) == sizeof (*dst) && ATOMIC_LLONG_LOCK_FREE == 2); atomic_store ((volatile atomic_llong *)dst, val); #else -#error gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC +#error "gint64 not same size atomic_llong or atomic_long, don't define MONO_USE_STDATOMIC" #endif } diff --git a/src/mono/mono/utils/options.c b/src/mono/mono/utils/options.c index 2a8dc6f60480bd..eb94a7c220574a 100644 --- a/src/mono/mono/utils/options.c +++ b/src/mono/mono/utils/options.c @@ -10,13 +10,6 @@ #include "options.h" #include "mono/utils/mono-error-internals.h" -typedef enum { - MONO_OPTION_BOOL, - MONO_OPTION_BOOL_READONLY, - MONO_OPTION_INT, - MONO_OPTION_STRING -} MonoOptionType; - /* Define flags */ #define DEFINE_OPTION_FULL(option_type, ctype, c_name, cmd_name, def_value, comment) \ ctype mono_opt_##c_name = def_value; @@ -333,3 +326,22 @@ mono_options_get_as_json (void) g_string_free(result, FALSE); return result_str; } + +gboolean +mono_options_get (const char *name, MonoOptionType *type, void **value_address) +{ + GHashTable *hash = get_option_hash (); + OptionData *meta = (OptionData *)g_hash_table_lookup (hash, name); + + if (!meta) { + if (value_address) + *value_address = NULL; + return FALSE; + } + + if (type) + *type = meta->option_type; + if (value_address) + *value_address = meta->addr; + return TRUE; +} diff --git a/src/mono/mono/utils/options.h b/src/mono/mono/utils/options.h index e7f2906eeb052c..27667e114af107 100644 --- a/src/mono/mono/utils/options.h +++ b/src/mono/mono/utils/options.h @@ -22,6 +22,13 @@ MONO_BEGIN_DECLS #include "options-def.h" MONO_END_DECLS +typedef enum { + MONO_OPTION_BOOL, + MONO_OPTION_BOOL_READONLY, + MONO_OPTION_INT, + MONO_OPTION_STRING +} MonoOptionType; + extern int mono_options_version; void mono_options_print_usage (void); @@ -31,4 +38,7 @@ void mono_options_parse_options (const char **args, int argc, int *out_argc, GPt /* returns a json blob representing the current values of all options */ char * mono_options_get_as_json (void); +gboolean +mono_options_get (const char *name, MonoOptionType *type, void **value_address); + #endif diff --git a/src/mono/sample/wasi/Directory.Build.props b/src/mono/sample/wasi/Directory.Build.props index 28a09db931ed21..53c8d42bae8e20 100644 --- a/src/mono/sample/wasi/Directory.Build.props +++ b/src/mono/sample/wasi/Directory.Build.props @@ -7,6 +7,7 @@ wasm wasi-wasm --> + $(TestArchiveRoot)wasmtimeonly/ diff --git a/src/mono/sample/wasm/DefaultBrowserSample.targets b/src/mono/sample/wasm/DefaultBrowserSample.targets index 2862ef1ddb7613..72db6bf9ce2ed4 100644 --- a/src/mono/sample/wasm/DefaultBrowserSample.targets +++ b/src/mono/sample/wasm/DefaultBrowserSample.targets @@ -7,8 +7,9 @@ -1 true $(WasmXHarnessArgs) --web-server-use-cop - $(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) $(WasmXHarnessArgs) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll + $(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) $(WasmXHarnessArgs) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll true + $(TestArchiveRoot)chromeonly/ diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs index e7be07f71c89e1..fc22d03b303bcc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs @@ -28,7 +28,7 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur [InlineData("Release")] public async Task DefaultTemplate_WithoutWorkload(string config) { - string id = $"blz_no_workload_{config}_{GetRandomId()}_{s_unicodeChar}"; + string id = $"blz_no_workload_{config}_{GetRandomId()}_{s_unicodeChars}"; CreateBlazorWasmTemplateProject(id); BlazorBuild(new BlazorBuildOptions(id, config)); @@ -63,7 +63,7 @@ public static TheoryData TestDataForDefaultTemplate_WithWorkload(b public void DefaultTemplate_NoAOT_WithWorkload(string config, bool testUnicode) { string id = testUnicode ? - $"blz_no_aot_{config}_{GetRandomId()}_{s_unicodeChar}" : + $"blz_no_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : $"blz_no_aot_{config}_{GetRandomId()}"; CreateBlazorWasmTemplateProject(id); @@ -84,7 +84,7 @@ public void DefaultTemplate_NoAOT_WithWorkload(string config, bool testUnicode) public void DefaultTemplate_AOT_WithWorkload(string config, bool testUnicode) { string id = testUnicode ? - $"blz_aot_{config}_{GetRandomId()}_{s_unicodeChar}" : + $"blz_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : $"blz_aot_{config}_{GetRandomId()}"; CreateBlazorWasmTemplateProject(id); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs index 10717b334174b9..eeaf368640e926 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs @@ -33,7 +33,7 @@ public MiscTests3(ITestOutputHelper output, SharedBuildPerTestClassFixture build public async Task WithDllImportInMainAssembly(string config, bool build, bool publish) { // Based on https://github.com/dotnet/runtime/issues/59255 - string id = $"blz_dllimp_{config}_{s_unicodeChar}"; + string id = $"blz_dllimp_{config}_{s_unicodeChars}"; if (build && publish) id += "build_then_publish"; else if (build) diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs index c29233c698441a..6772c540962c9c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs @@ -22,7 +22,7 @@ internal class BrowserRunner : IAsyncDisposable private static readonly Lazy s_chromePath = new(() => { string artifactsBinDir = Path.Combine(Path.GetDirectoryName(typeof(BuildTestBase).Assembly.Location)!, "..", "..", "..", ".."); - return BrowserLocator.FindChrome(artifactsBinDir, "BROWSER_PATH_FOR_TESTS"); + return BrowserLocator.FindChrome(artifactsBinDir, "CHROME_PATH_FOR_TESTS"); }); public IPlaywright? Playwright { get; private set; } diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs index d171631c2c5e04..f287fa4574164a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs @@ -158,7 +158,7 @@ void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, BuildArgs b { if (testUnicode) { - string projectNameCore = buildArgs.ProjectName.Trim(new char[] {s_unicodeChar}); + string projectNameCore = buildArgs.ProjectName.Replace(s_unicodeChars, ""); TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll -> {projectNameCore}\S+.dll.bc", buildOutput, contains: expectAOT); TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll.bc -> {projectNameCore}\S+.dll.o", buildOutput, contains: expectAOT); } diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index f871c3d2bfe7b2..03db9aed601187 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -29,7 +29,7 @@ public abstract class BuildTestBase : IClassFixture - appendUnicode ? $"{prefix}_{config}_{s_unicodeChar}" : $"{prefix}_{config}"; + appendUnicode ? $"{prefix}_{config}_{s_unicodeChars}" : $"{prefix}_{config}"; } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs index 2c56227c6693a3..4d5db19dc53f34 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs @@ -55,7 +55,7 @@ protected TestMainJsTestBase(ITestOutputHelper output, SharedBuildPerTestClassFi Path.Combine(_projectDir, "test-main.js") ); - File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @""); + File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @""); } else if (_projectDir is null) { diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 81110b4568ff49..b122de85fd7ec9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -30,8 +30,10 @@ - <_WasmBrowserPathForTests Condition="'$(BROWSER_PATH_FOR_TESTS)' != ''">$(BROWSER_PATH_FOR_TESTS) - <_WasmBrowserPathForTests Condition="'$(_WasmBrowserPathForTests)' == '' and '$(InstallChromeForTests)' == 'true'">$(ChromeBinaryPath) + <_WasmChromePathForTests Condition="'$(CHROME_PATH_FOR_TESTS)' != ''">$(CHROME_PATH_FOR_TESTS) + <_WasmChromePathForTests Condition="'$(_WasmChromePathForTests)' == '' and '$(InstallChromeForTests)' == 'true'">$(ChromeBinaryPath) + <_WasmFirefoxPathForTests Condition="'$(FIREFOX_PATH_FOR_TESTS)' != ''">$(FIREFOX_PATH_FOR_TESTS) + <_WasmFirefoxPathForTests Condition="'$(_WasmFirefoxPathForTests)' == '' and '$(InstallFirefoxForTests)' == 'true'">$(FirefoxBinaryPath) RunScriptTemplate.cmd RunScriptTemplate.sh @@ -57,6 +59,10 @@ + + + + @@ -67,8 +73,11 @@ - - + + + + + @@ -104,8 +113,8 @@ - - + + diff --git a/src/native/containers/containers.cmake b/src/native/containers/containers.cmake index dd8829e3bf0426..16c41eab5619f8 100644 --- a/src/native/containers/containers.cmake +++ b/src/native/containers/containers.cmake @@ -8,6 +8,11 @@ list(APPEND SHARED_CONTAINER_SOURCES dn-queue.c dn-umap.c dn-vector.c + # FIXME: Including these here causes a linker collision with sgen metadata + # dn-simdhash.c + # dn-simdhash-string-ptr.c + # dn-simdhash-u32-ptr.c + # dn-simdhash-ptr-ptr.c ) list(APPEND SHARED_CONTAINER_HEADERS @@ -24,4 +29,11 @@ list(APPEND SHARED_CONTAINER_HEADERS dn-vector-ptr.h dn-vector-t.h dn-vector-types.h + dn-simdhash.h + dn-simdhash-specialization.h + dn-simdhash-specialization-declarations.h + dn-simdhash-specializations.h + dn-simdhash-arch.h + dn-simdhash-string-ptr.h + dn-simdhash-utils.h ) diff --git a/src/native/containers/dn-simdhash-arch.h b/src/native/containers/dn-simdhash-arch.h new file mode 100644 index 00000000000000..d99288e3476ac3 --- /dev/null +++ b/src/native/containers/dn-simdhash-arch.h @@ -0,0 +1,249 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DN_SIMDHASH_ARCH_H__ +#define __DN_SIMDHASH_ARCH_H__ + +// #define DN_SIMDHASH_WARNINGS 1 + +// HACK: for better language server parsing +#include "dn-simdhash.h" + +#if defined(__clang__) || defined (__GNUC__) // use vector intrinsics + +#if defined(__wasm_simd128__) +#include +#elif defined(_M_AMD64) || defined(_M_X64) || (_M_IX86_FP == 2) || defined(__SSE2__) +#include +#elif defined(__ARM_NEON) +#include +#elif defined(__wasm) +#define DN_SIMDHASH_USE_SCALAR_FALLBACK 1 +#ifdef DN_SIMDHASH_WARNINGS +#pragma message("WARNING: Building dn_simdhash for WASM without -msimd128! Performance will be terrible!") +#endif +#else +#define DN_SIMDHASH_USE_SCALAR_FALLBACK 1 +#ifdef DN_SIMDHASH_WARNINGS +#pragma message("WARNING: Unsupported architecture for dn_simdhash! Performance will be terrible!") +#endif +#endif + +// extract/replace lane opcodes require constant indices on some target architectures, +// and in some cases it is profitable to do a single-byte memory load/store instead of +// a full vector load/store, so we expose both layouts as a union + +typedef uint8_t dn_u8x16 __attribute__ ((vector_size (DN_SIMDHASH_VECTOR_WIDTH), aligned(DN_SIMDHASH_VECTOR_WIDTH))); +typedef union { + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) dn_u8x16 vec; +#if defined(_M_AMD64) || defined(_M_X64) || (_M_IX86_FP == 2) || defined(__SSE2__) + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) __m128i m128; +#endif + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) uint8_t values[DN_SIMDHASH_VECTOR_WIDTH]; +} dn_simdhash_suffixes; + +#ifdef DN_SIMDHASH_USE_SCALAR_FALLBACK +typedef uint8_t dn_simdhash_search_vector; +#else +typedef dn_simdhash_suffixes dn_simdhash_search_vector; +#endif + +// Extracting lanes from a vector register on x86/x64 has horrible latency, +// so it's better to do regular byte loads from the stack +#if defined(__wasm_simd128__) +// For wasm with -msimd128, clang generates truly bizarre load/store code +// where it does two byte memory loads, then a vector load, then two +// lane insertions to write the byte loads into the loaded vector +// before finally passing it to find_first_matching_suffix. So we have to vec[]. +// See https://github.com/llvm/llvm-project/issues/87398#issuecomment-2050696298 +// Also see https://github.com/llvm/llvm-project/issues/88460 +#define dn_simdhash_extract_lane(suffixes, lane) \ + suffixes.vec[lane] +#else +#define dn_simdhash_extract_lane(suffixes, lane) \ + suffixes.values[lane] +#endif + +static DN_FORCEINLINE(uint32_t) +ctz (uint32_t value) +{ + // __builtin_ctz is undefined for 0 + if (value == 0) + return 32; + return (uint32_t)__builtin_ctz(value); +} + +static DN_FORCEINLINE(dn_simdhash_search_vector) +build_search_vector (uint8_t needle) +{ +#ifdef DN_SIMDHASH_USE_SCALAR_FALLBACK + return needle; +#else + dn_simdhash_suffixes result; + // this produces a splat in wasm, and the other architectures are fine too + dn_u8x16 needles = { + needle, needle, needle, needle, needle, needle, needle, needle, + needle, needle, needle, needle, needle, needle, needle, needle + }; + result.vec = needles; + return result; +#endif +} + +// returns an index in range 0-13 on match, 14-32 if no match +static DN_FORCEINLINE(uint32_t) +find_first_matching_suffix ( + dn_simdhash_search_vector needle, + // Only used by the vectorized implementations; discarded by scalar. + dn_simdhash_suffixes haystack, + // HACK: Pass the address of haystack.values directly, for scalar fallback. + // Without this, clang makes a full unaligned copy of haystack before calling us. + // Discarded by the vectorized implementations. + uint8_t haystack_values[DN_SIMDHASH_VECTOR_WIDTH], + uint32_t count +) { +#if defined(__wasm_simd128__) + return ctz(wasm_i8x16_bitmask(wasm_i8x16_eq(needle.vec, haystack.vec))); +#elif defined(_M_AMD64) || defined(_M_X64) || (_M_IX86_FP == 2) || defined(__SSE2__) + return ctz(_mm_movemask_epi8(_mm_cmpeq_epi8(needle.m128, haystack.m128))); +#elif defined(__ARM_NEON) + dn_simdhash_suffixes match_vector; + // Completely untested. + static const dn_simdhash_suffixes byte_mask = { + 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128 + }; + union { + uint8_t b[4]; + uint32_t u; + } msb; + match_vector.vec = vceqq_u8(needle.vec, haystack.vec); + dn_simdhash_suffixes masked; + masked.vec = vandq_u8(match_vector.vec, byte_mask.vec); + msb.b[0] = vaddv_u8(vget_low_u8(masked.vec)); + msb.b[1] = vaddv_u8(vget_high_u8(masked.vec)); + return ctz(msb.u); +#else + // HACK: We can't put this in a common helper function without introducing a temporary + // unaligned copy-from-table-to-stack in wasm-without-simd +#define ITER(offset) \ + if (needle == haystack_values[offset]) \ + return offset; + + // It is safe to unroll this without bounds checks + // One would expect this to blow out the branch predictor, but in my testing + // it's significantly faster when there is no match, and slightly faster + // for cases where there is a match. + // Looping from 0-count is slower than this in my testing, even though it's + // going to check fewer suffixes most of the time - probably due to the + // comparison against count for each suffix. + // FIXME: If we move this into the specialization header, we can limit the + // number of unrolled iterations to the number of keys in the bucket. + ITER(0); + ITER(1); + ITER(2); + ITER(3); + ITER(4); + ITER(5); + ITER(6); + ITER(7); + ITER(8); + ITER(9); + ITER(10); + ITER(11); + ITER(12); + ITER(13); +#undef ITER + return 32; +#endif +} + +#elif defined(_M_AMD64) || defined(_M_X64) || (_M_IX86_FP == 2) || defined(__SSE2__) +// neither clang or gcc, but we have SSE2 available, so assume this is MSVC on x86 or x86-64 +// msvc neon intrinsics don't seem to expose a 128-bit wide vector so there's no neon in here +#include // for _BitScanForward + +static DN_FORCEINLINE(uint32_t) +ctz (uint32_t value) +{ + unsigned long result = 0; + if (_BitScanForward(&result, value)) + return (uint32_t)result; + else + return 32; +} + +#include + +typedef union { + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) __m128i m128; + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) uint8_t values[DN_SIMDHASH_VECTOR_WIDTH]; +} dn_simdhash_suffixes; + +typedef dn_simdhash_suffixes dn_simdhash_search_vector; + +#define dn_simdhash_extract_lane(suffixes, lane) \ + suffixes.values[lane] + +static DN_FORCEINLINE(dn_simdhash_search_vector) +build_search_vector (uint8_t needle) +{ + dn_simdhash_suffixes result; + result.m128 = _mm_set1_epi8(needle); + return result; +} + +// returns an index in range 0-13 on match, 14-32 if no match +static DN_FORCEINLINE(uint32_t) +find_first_matching_suffix_internal ( + __m128i needle, __m128i haystack, + uint32_t count +) { + return ctz(_mm_movemask_epi8(_mm_cmpeq_epi8(needle, haystack))); +} + +// use a macro to discard haystack_values, otherwise MSVC's codegen is worse +#define find_first_matching_suffix(needle, haystack, haystack_values, count) \ + find_first_matching_suffix_internal(needle.m128, haystack.m128, count) + +#else // unknown compiler and/or unknown non-simd arch + +#define DN_SIMDHASH_USE_SCALAR_FALLBACK 1 + +#ifdef DN_SIMDHASH_WARNINGS +#pragma message("WARNING: Unsupported architecture/compiler for dn_simdhash! Performance will be terrible!") +#endif + +typedef struct { + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) uint8_t values[DN_SIMDHASH_VECTOR_WIDTH]; +} dn_simdhash_suffixes; + +typedef uint8_t dn_simdhash_search_vector; + +#define dn_simdhash_extract_lane(suffixes, lane) \ + suffixes.values[lane] + +static DN_FORCEINLINE(dn_simdhash_search_vector) +build_search_vector (uint8_t needle) +{ + return needle; +} + +// returns an index in range 0-14 on match, 32 if no match +static DN_FORCEINLINE(uint32_t) +find_first_matching_suffix ( + dn_simdhash_search_vector needle, dn_simdhash_suffixes haystack, + uint8_t haystack_values[DN_SIMDHASH_VECTOR_WIDTH], uint32_t count +) { + // TODO: It might be profitable to hand-unroll this loop, but right now doing so + // hits a bug in clang and generates really bad WASM. + // HACK: We can't put this in a common helper function without introducing a temporary + // unaligned copy-from-table-to-stack in wasm-without-simd + for (uint32_t i = 0; i < count; i++) + if (needle == haystack_values[i]) + return i; + return 32; +} + +#endif // end of clang/gcc or msvc or fallback + +#endif // __DN_SIMDHASH_ARCH_H__ diff --git a/src/native/containers/dn-simdhash-ght-compatible.c b/src/native/containers/dn-simdhash-ght-compatible.c new file mode 100644 index 00000000000000..330f2a14f7385a --- /dev/null +++ b/src/native/containers/dn-simdhash-ght-compatible.c @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef NO_CONFIG_H +#include +#endif +#include "dn-simdhash.h" + +#include "dn-simdhash-utils.h" +#include "dn-simdhash-ght-compatible.h" + +typedef struct dn_simdhash_ght_data { + dn_simdhash_ght_hash_func hash_func; + dn_simdhash_ght_equal_func key_equal_func; + dn_simdhash_ght_destroy_func key_destroy_func; + dn_simdhash_ght_destroy_func value_destroy_func; +} dn_simdhash_ght_data; + +static inline uint32_t +dn_simdhash_ght_hash (dn_simdhash_ght_data data, void * key) +{ + dn_simdhash_ght_hash_func hash_func = data.hash_func; + if (hash_func) + return (uint32_t)hash_func(key); + else + // FIXME: Seed + return MurmurHash3_32_ptr(key, 0); +} + +static inline int32_t +dn_simdhash_ght_equals (dn_simdhash_ght_data data, void * lhs, void * rhs) +{ + dn_simdhash_ght_equal_func equal_func = data.key_equal_func; + if (equal_func) + return equal_func(lhs, rhs); + else + return lhs == rhs; +} + +static inline void +dn_simdhash_ght_removed (dn_simdhash_ght_data data, void * key, void * value) +{ + dn_simdhash_ght_destroy_func key_destroy_func = data.key_destroy_func, + value_destroy_func = data.value_destroy_func; + if (key_destroy_func) + key_destroy_func((void *)key); + if (value_destroy_func) + value_destroy_func((void *)value); +} + +static inline void +dn_simdhash_ght_replaced (dn_simdhash_ght_data data, void * old_key, void * new_key, void * old_value, void * new_value) +{ + if (old_key != new_key) { + dn_simdhash_ght_destroy_func key_destroy_func = data.key_destroy_func; + if (key_destroy_func) + key_destroy_func((void *)old_key); + } + + if (old_value != new_value) { + dn_simdhash_ght_destroy_func value_destroy_func = data.value_destroy_func; + if (value_destroy_func) + value_destroy_func((void *)old_value); + } +} + +#define DN_SIMDHASH_T dn_simdhash_ght +#define DN_SIMDHASH_KEY_T void * +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_INSTANCE_DATA_T dn_simdhash_ght_data +#define DN_SIMDHASH_KEY_HASHER dn_simdhash_ght_hash +#define DN_SIMDHASH_KEY_EQUALS dn_simdhash_ght_equals +#define DN_SIMDHASH_ON_REMOVE dn_simdhash_ght_removed +#define DN_SIMDHASH_ON_REPLACE dn_simdhash_ght_replaced +#if SIZEOF_VOID_P == 8 +#define DN_SIMDHASH_BUCKET_CAPACITY 11 +#else +#define DN_SIMDHASH_BUCKET_CAPACITY 12 +#endif +#define DN_SIMDHASH_NO_DEFAULT_NEW 1 + +#include "dn-simdhash-specialization.h" +#include "dn-simdhash-ght-compatible.h" + +dn_simdhash_ght_t * +dn_simdhash_ght_new ( + dn_simdhash_ght_hash_func hash_func, dn_simdhash_ght_equal_func key_equal_func, + uint32_t capacity, dn_allocator_t *allocator +) +{ + dn_simdhash_ght_t *hash = dn_simdhash_new_internal(&DN_SIMDHASH_T_META, DN_SIMDHASH_T_VTABLE, capacity, allocator); + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).hash_func = hash_func; + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).key_equal_func = key_equal_func; + return hash; +} + +dn_simdhash_ght_t * +dn_simdhash_ght_new_full ( + dn_simdhash_ght_hash_func hash_func, dn_simdhash_ght_equal_func key_equal_func, + dn_simdhash_ght_destroy_func key_destroy_func, dn_simdhash_ght_destroy_func value_destroy_func, + uint32_t capacity, dn_allocator_t *allocator +) +{ + dn_simdhash_ght_t *hash = dn_simdhash_new_internal(&DN_SIMDHASH_T_META, DN_SIMDHASH_T_VTABLE, capacity, allocator); + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).hash_func = hash_func; + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).key_equal_func = key_equal_func; + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).key_destroy_func = key_destroy_func; + dn_simdhash_instance_data(dn_simdhash_ght_data, hash).value_destroy_func = value_destroy_func; + return hash; +} + +void +dn_simdhash_ght_insert_replace ( + dn_simdhash_ght_t *hash, + void * key, void * value, + int32_t overwrite_key +) +{ + check_self(hash); + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key); + dn_simdhash_insert_mode imode = overwrite_key + ? DN_SIMDHASH_INSERT_MODE_OVERWRITE_KEY_AND_VALUE + : DN_SIMDHASH_INSERT_MODE_OVERWRITE_VALUE; + + dn_simdhash_insert_result ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, imode); + if (ok == DN_SIMDHASH_INSERT_NEED_TO_GROW) { + dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, dn_simdhash_capacity(hash) + 1); + if (old_buffers.buckets) { + DN_SIMDHASH_REHASH_INTERNAL(hash, old_buffers); + dn_simdhash_free_buffers(old_buffers); + } + ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, imode); + } + + switch (ok) { + case DN_SIMDHASH_INSERT_OK_ADDED_NEW: + hash->count++; + return; + case DN_SIMDHASH_INSERT_OK_OVERWROTE_EXISTING: + return; + // We should always return one of the first two + case DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT: + case DN_SIMDHASH_INSERT_NEED_TO_GROW: + default: + assert(0); + return; + } +} diff --git a/src/native/containers/dn-simdhash-ght-compatible.h b/src/native/containers/dn-simdhash-ght-compatible.h new file mode 100644 index 00000000000000..b99d67477ccc5b --- /dev/null +++ b/src/native/containers/dn-simdhash-ght-compatible.h @@ -0,0 +1,29 @@ +typedef void (*dn_simdhash_ght_destroy_func) (void * data); +typedef unsigned int (*dn_simdhash_ght_hash_func) (const void * key); +typedef int32_t (*dn_simdhash_ght_equal_func) (const void * a, const void * b); + +dn_simdhash_ght_t * +dn_simdhash_ght_new ( + dn_simdhash_ght_hash_func hash_func, dn_simdhash_ght_equal_func key_equal_func, + uint32_t capacity, dn_allocator_t *allocator +); + +dn_simdhash_ght_t * +dn_simdhash_ght_new_full ( + dn_simdhash_ght_hash_func hash_func, dn_simdhash_ght_equal_func key_equal_func, + dn_simdhash_ght_destroy_func key_destroy_func, dn_simdhash_ght_destroy_func value_destroy_func, + uint32_t capacity, dn_allocator_t *allocator +); + +// compatible with g_hash_table_insert_replace +void +dn_simdhash_ght_insert_replace ( + dn_simdhash_ght_t *hash, + void * key, void * value, + int32_t overwrite_key +); + +// compatibility shims for the g_hash_table_ versions in glib.h +#define dn_simdhash_ght_insert(h,k,v) dn_simdhash_ght_insert_replace ((h),(k),(v),FALSE) +#define dn_simdhash_ght_replace(h,k,v) dn_simdhash_ght_insert_replace ((h),(k),(v),TRUE) +#define dn_simdhash_ght_add(h,k) dn_simdhash_ght_insert_replace ((h),(k),(k),TRUE) diff --git a/src/native/containers/dn-simdhash-ptr-ptr.c b/src/native/containers/dn-simdhash-ptr-ptr.c new file mode 100644 index 00000000000000..25e7530d39dc48 --- /dev/null +++ b/src/native/containers/dn-simdhash-ptr-ptr.c @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef NO_CONFIG_H +#include +#endif +#include "dn-simdhash.h" + +#include "dn-simdhash-utils.h" + +#define DN_SIMDHASH_T dn_simdhash_ptr_ptr +#define DN_SIMDHASH_KEY_T void * +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_KEY_HASHER(hash, key) (MurmurHash3_32_ptr(key, 0)) +#define DN_SIMDHASH_KEY_EQUALS(hash, lhs, rhs) (lhs == rhs) +#if SIZEOF_VOID_P == 8 +#define DN_SIMDHASH_BUCKET_CAPACITY 11 +#else +#define DN_SIMDHASH_BUCKET_CAPACITY 12 +#endif + +#include "dn-simdhash-specialization.h" diff --git a/src/native/containers/dn-simdhash-specialization-declarations.h b/src/native/containers/dn-simdhash-specialization-declarations.h new file mode 100644 index 00000000000000..585f2094ee58c8 --- /dev/null +++ b/src/native/containers/dn-simdhash-specialization-declarations.h @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Gluing macro expansions together requires nested macro invocation :/ +#ifndef DN_SIMDHASH_GLUE +#define DN_SIMDHASH_GLUE_INNER(a, b) a ## b +#define DN_SIMDHASH_GLUE(a,b) DN_SIMDHASH_GLUE_INNER(a, b) +#endif +#ifndef DN_SIMDHASH_GLUE_3 +#define DN_SIMDHASH_GLUE_3_INNER(a, b, c) a ## b ## c +#define DN_SIMDHASH_GLUE_3(a, b, c) DN_SIMDHASH_GLUE_3_INNER(a, b, c) +#endif + +#ifndef DN_SIMDHASH_ACCESSOR_SUFFIX +#define DN_SIMDHASH_ACCESSOR_SUFFIX +#endif + +// We generate unique names for each specialization so that they will be easy to distinguish +// when debugging, profiling, or disassembling. Otherwise they would have linker-assigned names +#define DN_SIMDHASH_T_NAME DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_t) +#define DN_SIMDHASH_T_PTR DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_t *) +#define DN_SIMDHASH_T_VTABLE DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_vtable) +#define DN_SIMDHASH_T_META DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_meta) +#define DN_SIMDHASH_SCAN_BUCKET_INTERNAL DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_scan_bucket_internal) +#define DN_SIMDHASH_FIND_VALUE_INTERNAL DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_find_value_internal) +#define DN_SIMDHASH_TRY_INSERT_INTERNAL DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_try_insert_internal) +#define DN_SIMDHASH_REHASH_INTERNAL DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_rehash_internal) +#define DN_SIMDHASH_NEW DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_new) +#define DN_SIMDHASH_TRY_ADD DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_add,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_ADD_WITH_HASH DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_add_with_hash,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_GET_VALUE DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_get_value,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_GET_VALUE_WITH_HASH DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_get_value_with_hash,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_REMOVE DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_remove,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_REMOVE_WITH_HASH DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_remove_with_hash,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_REPLACE_VALUE DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_replace_value,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_TRY_REPLACE_VALUE_WITH_HASH DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_try_replace_value_with_hash,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_FOREACH DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_foreach,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_FOREACH_FUNC DN_SIMDHASH_GLUE_3(DN_SIMDHASH_T,_foreach_func,DN_SIMDHASH_ACCESSOR_SUFFIX) +#define DN_SIMDHASH_DESTROY_ALL DN_SIMDHASH_GLUE(DN_SIMDHASH_T,_destroy_all) + +typedef void (*DN_SIMDHASH_FOREACH_FUNC) (DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T value, void *user_data); + +// Declare a specific alias so intellisense gives more helpful info +typedef dn_simdhash_t DN_SIMDHASH_T_NAME; + +#ifndef DN_SIMDHASH_NO_DEFAULT_NEW +DN_SIMDHASH_T_PTR +DN_SIMDHASH_NEW (uint32_t capacity, dn_allocator_t *allocator); +#endif + +uint8_t +DN_SIMDHASH_TRY_ADD (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T value); + +uint8_t +DN_SIMDHASH_TRY_ADD_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T value); + +uint8_t +DN_SIMDHASH_TRY_GET_VALUE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T *result); + +uint8_t +DN_SIMDHASH_TRY_GET_VALUE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T *result); + +uint8_t +DN_SIMDHASH_TRY_REMOVE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key); + +uint8_t +DN_SIMDHASH_TRY_REMOVE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash); + +uint8_t +DN_SIMDHASH_TRY_REPLACE_VALUE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T new_value); + +uint8_t +DN_SIMDHASH_TRY_REPLACE_VALUE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T new_value); + +void +DN_SIMDHASH_FOREACH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_FOREACH_FUNC func, void *user_data); diff --git a/src/native/containers/dn-simdhash-specialization.h b/src/native/containers/dn-simdhash-specialization.h new file mode 100644 index 00000000000000..093ffaf20a7a4e --- /dev/null +++ b/src/native/containers/dn-simdhash-specialization.h @@ -0,0 +1,584 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifdef __DN_SIMDHASH_SPECIALIZATION_H__ +#error Specialization header already included +#else +#define __DN_SIMDHASH_SPECIALIZATION_H__ +#endif + +#include "dn-simdhash.h" +#include "dn-simdhash-utils.h" +#include "dn-simdhash-arch.h" + +#ifndef DN_SIMDHASH_T +#error Expected DN_SIMDHASH_T definition i.e. dn_simdhash_string_ptr +#endif + +#ifndef DN_SIMDHASH_KEY_T +#error Expected DN_SIMDHASH_KEY_T definition i.e. const char * +#endif + +#ifndef DN_SIMDHASH_VALUE_T +#error Expected DN_SIMDHASH_VALUE_T definition i.e. int +#endif + +// If specified, we pass instance data to the handlers by-value, otherwise we +// pass the pointer to the hash itself by-value. This is enough to allow clang +// to hoist the load of the instance data out of the key scan loop, though it +// won't hoist it all the way out of the bucket scan loop. +#ifndef DN_SIMDHASH_INSTANCE_DATA_T +#define DN_SIMDHASH_GET_DATA(hash) (hash) +#define DN_SIMDHASH_INSTANCE_DATA_T DN_SIMDHASH_T_PTR +#else // DN_SIMDHASH_INSTANCE_DATA_T +#define DN_SIMDHASH_GET_DATA(hash) dn_simdhash_instance_data(DN_SIMDHASH_INSTANCE_DATA_T, hash) +#endif // DN_SIMDHASH_INSTANCE_DATA_T + +#ifndef DN_SIMDHASH_KEY_HASHER +#error Expected DN_SIMDHASH_KEY_HASHER definition with signature: uint32_t (DN_SIMDHASH_INSTANCE_DATA_T data, KEY_T key) +#endif + +#ifndef DN_SIMDHASH_KEY_EQUALS +#error Expected DN_SIMDHASH_KEY_EQUALS definition with signature: int (DN_SIMDHASH_INSTANCE_DATA_T data, KEY_T lhs, KEY_T rhs) that returns 1 for match +#endif + +#ifndef DN_SIMDHASH_ON_REPLACE +#define DN_SIMDHASH_HAS_REPLACE_HANDLER 0 +#define DN_SIMDHASH_ON_REPLACE(data, old_key, new_key, old_value, new_value) +#else // DN_SIMDHASH_ON_REPLACE +#define DN_SIMDHASH_HAS_REPLACE_HANDLER 1 +#ifndef DN_SIMDHASH_ON_REMOVE +#error Expected DN_SIMDHASH_ON_REMOVE(data, key, value) to be defined. +#endif +#endif // DN_SIMDHASH_ON_REPLACE + +#ifndef DN_SIMDHASH_ON_REMOVE +#define DN_SIMDHASH_HAS_REMOVE_HANDLER 0 +#define DN_SIMDHASH_ON_REMOVE(data, key, value) +#else // DN_SIMDHASH_ON_REMOVE +#define DN_SIMDHASH_HAS_REMOVE_HANDLER 1 +#ifndef DN_SIMDHASH_ON_REPLACE +#error Expected DN_SIMDHASH_ON_REPLACE(data, old_key, new_key, old_value, new_value) to be defined. +#endif +#endif // DN_SIMDHASH_ON_REMOVE + +#ifndef DN_SIMDHASH_BUCKET_CAPACITY +// TODO: Find some way to automatically select an ideal bucket capacity based on key size. +// Some sort of trick using _Generic? +#define DN_SIMDHASH_BUCKET_CAPACITY DN_SIMDHASH_DEFAULT_BUCKET_CAPACITY +#endif + +#include "dn-simdhash-specialization-declarations.h" + +static_assert(DN_SIMDHASH_BUCKET_CAPACITY <= DN_SIMDHASH_MAX_BUCKET_CAPACITY, "Maximum bucket capacity exceeded"); +static_assert(DN_SIMDHASH_BUCKET_CAPACITY > 1, "Bucket capacity too low"); + +// We set bucket_size_bytes to sizeof() this struct so that we can let the compiler +// generate the most optimal code possible when we're manipulating pointers to it - +// that is, it can do mul-by-constant instead of mul-by-(hash->meta.etc) +typedef struct bucket_t { + _Alignas(DN_SIMDHASH_VECTOR_WIDTH) dn_simdhash_suffixes suffixes; + DN_SIMDHASH_KEY_T keys[DN_SIMDHASH_BUCKET_CAPACITY]; +} bucket_t; + +static_assert((sizeof (bucket_t) % DN_SIMDHASH_VECTOR_WIDTH) == 0, "Bucket size is not vector aligned"); + + +// While we've inlined these constants into the specialized code we're generating, +// the generic code in dn-simdhash.c needs them, so we put them in this meta header +// that is referenced by every hash instance. +dn_simdhash_meta_t DN_SIMDHASH_T_META = { + DN_SIMDHASH_BUCKET_CAPACITY, + sizeof(bucket_t), + sizeof(DN_SIMDHASH_KEY_T), + sizeof(DN_SIMDHASH_VALUE_T), + sizeof(DN_SIMDHASH_INSTANCE_DATA_T), +}; + + +static DN_FORCEINLINE(void) +check_self (DN_SIMDHASH_T_PTR self) +{ +#ifdef NDEBUG + // In release builds, just nullcheck. Checking meta adds measurable overhead. + dn_simdhash_assert(self); +#else + // Verifies both that the self-ptr is non-null and that the meta pointer matches + // what it should be. This detects passing the wrong kind of simdhash_t pointer + // to one of the APIs, since C doesn't have fully type-safe pointers. + uint8_t ok = self && (self->meta == &DN_SIMDHASH_T_META); + dn_simdhash_assert(ok); +#endif +} + + +static DN_FORCEINLINE(bucket_t *) +address_of_bucket (dn_simdhash_buffers_t buffers, uint32_t bucket_index) +{ + return &((bucket_t *)buffers.buckets)[bucket_index]; +} + +static DN_FORCEINLINE(DN_SIMDHASH_VALUE_T *) +address_of_value (dn_simdhash_buffers_t buffers, uint32_t value_slot_index) +{ + return &((DN_SIMDHASH_VALUE_T *)buffers.values)[value_slot_index]; +} + +#define DN_SIMDHASH_SCAN_BUCKET_NO_OVERFLOW -1 +#define DN_SIMDHASH_SCAN_BUCKET_OVERFLOWED -2 + +// This helper is used to locate the first matching key in a given bucket, so that add +// operations don't potentially have to scan the whole table twice when hashes collide +// On success: returns index (0-n) +// On failure: returns -1 if bucket has not overflowed; -2 if it has +static DN_FORCEINLINE(int) +DN_SIMDHASH_SCAN_BUCKET_INTERNAL (DN_SIMDHASH_T_PTR hash, bucket_t *restrict bucket, DN_SIMDHASH_KEY_T needle, dn_simdhash_search_vector search_vector) +{ +#ifdef _MSC_VER + // MSVC won't do efficient lane extractions if we eager load the vector, + // so just operate through the pointer instead. + #define bucket_suffixes (bucket->suffixes) +#elif !defined(DN_SIMDHASH_USE_SCALAR_FALLBACK) + // Perform an eager load of the vector if SIMD is in use, even though we do + // byte loads to extract lanes on non-wasm platforms. It's faster on x64 for + // a reason I can't identify, and it significantly improves wasm codegen + dn_simdhash_suffixes bucket_suffixes = bucket->suffixes; +#else + // Load through the pointer instead. An eager load just copies to the stack for + // no good reason. + #define bucket_suffixes (bucket->suffixes) +#endif + uint8_t count = dn_simdhash_extract_lane(bucket_suffixes, DN_SIMDHASH_COUNT_SLOT), + overflow_count = dn_simdhash_extract_lane(bucket_suffixes, DN_SIMDHASH_CASCADED_SLOT); + // We could early-out here when count==0, but it doesn't appear to meaningfully improve + // search performance to do so, and might actually worsen it + uint32_t index = find_first_matching_suffix(search_vector, bucket_suffixes, bucket_suffixes.values, count); + for (; index < count; index++) { + // FIXME: Could be profitable to manually hoist the data load outside of the loop, + // if not out of SCAN_BUCKET_INTERNAL entirely. Clang appears to do LICM on it. + // It's better to index bucket->keys each iteration inside the loop than to precompute + // a pointer outside and bump the pointer, because in many cases the bucket will be + // empty, and in many other cases it will have one match. Putting the index inside the + // loop means that for empty/no-match buckets we don't do the index calculation at all. + if (DN_SIMDHASH_KEY_EQUALS(DN_SIMDHASH_GET_DATA(hash), needle, bucket->keys[index])) + return index; + } + +#undef bucket_suffixes + + if (overflow_count) + return DN_SIMDHASH_SCAN_BUCKET_OVERFLOWED; + else + return DN_SIMDHASH_SCAN_BUCKET_NO_OVERFLOW; +} + +// Helper macros so that we can optimize and change scan logic more easily +#define BEGIN_SCAN_BUCKETS(initial_index, bucket_index, bucket_address) \ + { \ + uint32_t bucket_index = initial_index, scan_buckets_length = buffers.buckets_length; \ + bucket_t *restrict bucket_address = address_of_bucket(buffers, bucket_index); \ + do { + +#define END_SCAN_BUCKETS(initial_index, bucket_index, bucket_address) \ + bucket_index++; \ + bucket_address++; \ + /* Wrap around if we hit the last bucket. */ \ + if (bucket_index >= scan_buckets_length) { \ + bucket_index = 0; \ + bucket_address = address_of_bucket(buffers, 0); \ + } \ + /* if bucket_index == initial_index, we reached our starting point */ \ + } while (bucket_index != initial_index); \ + } + +#define BEGIN_SCAN_PAIRS(buffers, key_address, value_address) \ + bucket_t *scan_bucket_address = address_of_bucket(buffers, 0); \ + for ( \ + uint32_t scan_i = 0, scan_bc = buffers.buckets_length, scan_value_slot_base = 0; \ + scan_i < scan_bc; scan_i++, scan_bucket_address++, scan_value_slot_base += DN_SIMDHASH_BUCKET_CAPACITY \ + ) { \ + uint32_t scan_c = dn_simdhash_bucket_count(scan_bucket_address->suffixes); \ + for (uint32_t scan_j = 0; scan_j < scan_c; scan_j++) { \ + DN_SIMDHASH_KEY_T *key_address = &scan_bucket_address->keys[scan_j]; \ + DN_SIMDHASH_VALUE_T *value_address = address_of_value(buffers, scan_value_slot_base + scan_j); + +#define END_SCAN_PAIRS(buffers, key_address, value_address) \ + } \ + } + +// FIXME: inline? might improve performance for bucket overflow, but would +// increase code size, and maybe blow out icache. clang seems to inline it anyway. +static void +adjust_cascaded_counts (dn_simdhash_buffers_t buffers, uint32_t first_bucket_index, uint32_t last_bucket_index, uint8_t increase) +{ + BEGIN_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + if (bucket_index == last_bucket_index) + break; + + uint8_t cascaded_count = dn_simdhash_bucket_cascaded_count(bucket_address->suffixes); + if (cascaded_count < 255) { + if (increase) + dn_simdhash_bucket_set_cascaded_count(bucket_address->suffixes, cascaded_count + 1); + else { + dn_simdhash_assert(cascaded_count > 0); + dn_simdhash_bucket_set_cascaded_count(bucket_address->suffixes, cascaded_count - 1); + } + } + END_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) +} + +static DN_SIMDHASH_VALUE_T * +DN_SIMDHASH_FIND_VALUE_INTERNAL (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash) +{ + dn_simdhash_buffers_t buffers = hash->buffers; + uint8_t suffix = dn_simdhash_select_suffix(key_hash); + uint32_t first_bucket_index = dn_simdhash_select_bucket_index(buffers, key_hash); + dn_simdhash_search_vector search_vector = build_search_vector(suffix); + + BEGIN_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + int index_in_bucket = DN_SIMDHASH_SCAN_BUCKET_INTERNAL(hash, bucket_address, key, search_vector); + if (index_in_bucket >= 0) { + uint32_t value_slot_index = (bucket_index * DN_SIMDHASH_BUCKET_CAPACITY) + index_in_bucket; + return address_of_value(buffers, value_slot_index); + } else if (index_in_bucket == DN_SIMDHASH_SCAN_BUCKET_NO_OVERFLOW) { + return NULL; + } + END_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + + return NULL; +} + +typedef enum dn_simdhash_insert_mode { + // Ensures that no matching key exists in the hash, then adds the key/value pair + DN_SIMDHASH_INSERT_MODE_ENSURE_UNIQUE, + // If a matching key exists in the hash, overwrite its value but leave the key alone + DN_SIMDHASH_INSERT_MODE_OVERWRITE_VALUE, + // If a matching key exists in the hash, overwrite both the key and the value + DN_SIMDHASH_INSERT_MODE_OVERWRITE_KEY_AND_VALUE, + // Do not scan for existing matches before adding the new key/value pair. + DN_SIMDHASH_INSERT_MODE_REHASHING, +} dn_simdhash_insert_mode; + +static void +do_overwrite ( + DN_SIMDHASH_T_PTR hash, uint32_t bucket_index, bucket_t *bucket_address, int index_in_bucket, + DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T value, uint8_t overwrite_key +) { + DN_SIMDHASH_KEY_T *key_ptr = &bucket_address->keys[index_in_bucket]; + DN_SIMDHASH_VALUE_T *value_ptr = address_of_value(hash->buffers, (bucket_index * DN_SIMDHASH_BUCKET_CAPACITY) + index_in_bucket); +#if DN_SIMDHASH_HAS_REPLACE_HANDLER + DN_SIMDHASH_KEY_T old_key = *key_ptr; + DN_SIMDHASH_VALUE_T old_value = *value_ptr; +#endif + if (overwrite_key) + *key_ptr = key; + *value_ptr = value; +#if DN_SIMDHASH_HAS_REPLACE_HANDLER + DN_SIMDHASH_ON_REPLACE(DN_SIMDHASH_GET_DATA(hash), old_key, overwrite_key ? key : old_key, old_value, value); +#endif +} + +static dn_simdhash_insert_result +DN_SIMDHASH_TRY_INSERT_INTERNAL (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T value, dn_simdhash_insert_mode mode) +{ + // HACK: Early out. Better to grow without scanning here. + // We're comparing with the computed grow_at_count threshold to maintain an appropriate load factor + if (hash->count >= hash->grow_at_count) { + // printf ("hash->count %d >= hash->grow_at_count %d\n", hash->count, hash->grow_at_count); + return DN_SIMDHASH_INSERT_NEED_TO_GROW; + } + + dn_simdhash_buffers_t buffers = hash->buffers; + uint8_t suffix = dn_simdhash_select_suffix(key_hash); + uint32_t first_bucket_index = dn_simdhash_select_bucket_index(hash->buffers, key_hash); + dn_simdhash_search_vector search_vector = build_search_vector(suffix); + + BEGIN_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + // If necessary, check the current bucket for the key + if (mode != DN_SIMDHASH_INSERT_MODE_REHASHING) { + int index_in_bucket = DN_SIMDHASH_SCAN_BUCKET_INTERNAL(hash, bucket_address, key, search_vector); + if (index_in_bucket >= 0) { + if ( + (mode == DN_SIMDHASH_INSERT_MODE_OVERWRITE_KEY_AND_VALUE) || + (mode == DN_SIMDHASH_INSERT_MODE_OVERWRITE_VALUE) + ) { + do_overwrite ( + hash, bucket_index, bucket_address, index_in_bucket, + key, value, (mode == DN_SIMDHASH_INSERT_MODE_OVERWRITE_KEY_AND_VALUE) + ); + return DN_SIMDHASH_INSERT_OK_OVERWROTE_EXISTING; + } else + return DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT; + } + } + + // The current bucket doesn't contain the key, or duplicate checks are disabled (for rehashing), + // so attempt to insert into the bucket + uint8_t new_index = dn_simdhash_bucket_count(bucket_address->suffixes); + if (new_index < DN_SIMDHASH_BUCKET_CAPACITY) { + // Calculate key address early to reduce odds of a stall + DN_SIMDHASH_KEY_T *restrict key_slot_address = &bucket_address->keys[new_index]; + // We found a bucket with space, so claim the first free slot + dn_simdhash_bucket_set_count(bucket_address->suffixes, new_index + 1); + dn_simdhash_bucket_set_suffix(bucket_address->suffixes, new_index, suffix); + // Now store the key, it's probably in the same cache line as the count/suffix + *key_slot_address = key; + // Now store the value, it's in a different cache line + uint32_t value_slot_index = (bucket_index * DN_SIMDHASH_BUCKET_CAPACITY) + new_index; + DN_SIMDHASH_VALUE_T *restrict value_slot_address = address_of_value(buffers, value_slot_index); + *value_slot_address = value; + // printf("Inserted [%zd, %zd] in bucket %d at index %d\n", key, value, bucket_index, new_index); + // If we cascaded out of our original target bucket, scan through our probe path + // and increase the cascade counters. We have to wait until now to do that, because + // during the process of getting here we may end up finding a duplicate, which would + // leave the cascade counters in a corrupted state + adjust_cascaded_counts(buffers, first_bucket_index, bucket_index, 1); + return DN_SIMDHASH_INSERT_OK_ADDED_NEW; + } + + // The current bucket is full, so try the next bucket. + END_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + + return DN_SIMDHASH_INSERT_NEED_TO_GROW; +} + +static void +DN_SIMDHASH_REHASH_INTERNAL (DN_SIMDHASH_T_PTR hash, dn_simdhash_buffers_t old_buffers) +{ + BEGIN_SCAN_PAIRS(old_buffers, key_address, value_address) + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), *key_address); + // This theoretically can't fail, since we just grew the container and we + // wrap around to the beginning when there's a collision in the last bucket. + dn_simdhash_insert_result ok = DN_SIMDHASH_TRY_INSERT_INTERNAL( + hash, *key_address, key_hash, + *value_address, + DN_SIMDHASH_INSERT_MODE_REHASHING + ); + dn_simdhash_assert(ok == DN_SIMDHASH_INSERT_OK_ADDED_NEW); + END_SCAN_PAIRS(old_buffers, key_address, value_address) +} + +#if DN_SIMDHASH_HAS_REMOVE_HANDLER +static void +DN_SIMDHASH_DESTROY_ALL (DN_SIMDHASH_T_PTR hash) +{ + dn_simdhash_buffers_t buffers = hash->buffers; + BEGIN_SCAN_PAIRS(buffers, key_address, value_address) + DN_SIMDHASH_ON_REMOVE(DN_SIMDHASH_GET_DATA(hash), *key_address, *value_address); + END_SCAN_PAIRS(buffers, key_address, value_address) +} +#endif + + +// TODO: Store this by-reference instead of inline in the hash? +dn_simdhash_vtable_t DN_SIMDHASH_T_VTABLE = { + DN_SIMDHASH_REHASH_INTERNAL, +#if DN_SIMDHASH_HAS_REMOVE_HANDLER + DN_SIMDHASH_DESTROY_ALL, +#else + NULL, +#endif +}; + + +#ifndef DN_SIMDHASH_NO_DEFAULT_NEW +DN_SIMDHASH_T_PTR +DN_SIMDHASH_NEW (uint32_t capacity, dn_allocator_t *allocator) +{ + // If this isn't satisfied, the generic code will allocate incorrectly sized buffers + // HACK: Use static_assert because for some reason assert produces unused variable warnings only on CI + struct silence_nuisance_msvc_warning { bucket_t a, b; }; + static_assert( + sizeof(struct silence_nuisance_msvc_warning) == (sizeof(bucket_t) * 2), + "Inconsistent spacing/sizing for bucket_t" + ); + + return dn_simdhash_new_internal(&DN_SIMDHASH_T_META, DN_SIMDHASH_T_VTABLE, capacity, allocator); +} +#endif + +uint8_t +DN_SIMDHASH_TRY_ADD (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T value) +{ + check_self(hash); + + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key); + return DN_SIMDHASH_TRY_ADD_WITH_HASH(hash, key, key_hash, value); +} + +uint8_t +DN_SIMDHASH_TRY_ADD_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T value) +{ + check_self(hash); + + dn_simdhash_insert_result ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, DN_SIMDHASH_INSERT_MODE_ENSURE_UNIQUE); + if (ok == DN_SIMDHASH_INSERT_NEED_TO_GROW) { + dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, dn_simdhash_capacity(hash) + 1); + if (old_buffers.buckets) { + DN_SIMDHASH_REHASH_INTERNAL(hash, old_buffers); + dn_simdhash_free_buffers(old_buffers); + } + ok = DN_SIMDHASH_TRY_INSERT_INTERNAL(hash, key, key_hash, value, DN_SIMDHASH_INSERT_MODE_ENSURE_UNIQUE); + } + + switch (ok) { + case DN_SIMDHASH_INSERT_OK_ADDED_NEW: + hash->count++; + return 1; + case DN_SIMDHASH_INSERT_OK_OVERWROTE_EXISTING: + // This shouldn't happen + dn_simdhash_assert(!"Overwrote an existing item while adding"); + return 1; + case DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT: + return 0; + case DN_SIMDHASH_INSERT_NEED_TO_GROW: + // We should always have enough space after growing once. + default: + dn_simdhash_assert(!"Failed to add a new item but there was no existing item"); + return 0; + } +} + +uint8_t +DN_SIMDHASH_TRY_GET_VALUE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T *result) +{ + check_self(hash); + + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key); + return DN_SIMDHASH_TRY_GET_VALUE_WITH_HASH(hash, key, key_hash, result); +} + +uint8_t +DN_SIMDHASH_TRY_GET_VALUE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T *result) +{ + check_self(hash); + + DN_SIMDHASH_VALUE_T *value_ptr = DN_SIMDHASH_FIND_VALUE_INTERNAL(hash, key, key_hash); + if (!value_ptr) + return 0; + if (result) + *result = *value_ptr; + return 1; +} + +uint8_t +DN_SIMDHASH_TRY_REMOVE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key) +{ + check_self(hash); + + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key); + return DN_SIMDHASH_TRY_REMOVE_WITH_HASH(hash, key, key_hash); +} + +uint8_t +DN_SIMDHASH_TRY_REMOVE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash) +{ + check_self(hash); + + dn_simdhash_buffers_t buffers = hash->buffers; + uint8_t suffix = dn_simdhash_select_suffix(key_hash); + uint32_t first_bucket_index = dn_simdhash_select_bucket_index(buffers, key_hash); + dn_simdhash_search_vector search_vector = build_search_vector(suffix); + + BEGIN_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + int index_in_bucket = DN_SIMDHASH_SCAN_BUCKET_INTERNAL(hash, bucket_address, key, search_vector); + if (index_in_bucket >= 0) { + // We found the item. Replace it with the last item in the bucket, then erase + // the last item in the bucket. This ensures sequential scans still work. + uint8_t bucket_count = dn_simdhash_bucket_count(bucket_address->suffixes), + replacement_index_in_bucket = bucket_count - 1; + uint32_t value_slot_index = (bucket_index * DN_SIMDHASH_BUCKET_CAPACITY) + index_in_bucket, + replacement_value_slot_index = (bucket_index * DN_SIMDHASH_BUCKET_CAPACITY) + replacement_index_in_bucket; + + DN_SIMDHASH_VALUE_T *value_address = address_of_value(buffers, value_slot_index); + DN_SIMDHASH_VALUE_T *replacement_address = address_of_value(buffers, replacement_value_slot_index); + DN_SIMDHASH_KEY_T *key_address = &bucket_address->keys[index_in_bucket]; + DN_SIMDHASH_KEY_T *replacement_key_address = &bucket_address->keys[replacement_index_in_bucket]; + +#if DN_SIMDHASH_HAS_REMOVE_HANDLER + // Store for later, so we can run the callback after we're done removing the item + DN_SIMDHASH_VALUE_T value = *value_address; + // The key used for lookup may not be the key that was actually stored inside us, + // so make sure we store the one that was inside and destroy that one + DN_SIMDHASH_KEY_T actual_key = *key_address; +#endif + + hash->count--; + + // Update count first + dn_simdhash_bucket_set_count(bucket_address->suffixes, bucket_count - 1); + // Rotate replacement suffix from the end of the bucket to here + dn_simdhash_bucket_set_suffix( + bucket_address->suffixes, index_in_bucket, + bucket_address->suffixes.values[replacement_index_in_bucket] + ); + // Zero replacement suffix's old slot so it won't produce false positives in scans + dn_simdhash_bucket_set_suffix( + bucket_address->suffixes, replacement_index_in_bucket, 0 + ); + // Rotate replacement value from the end of the bucket to here + *value_address = *replacement_address; + // Rotate replacement key from the end of the bucket to here + *key_address = *replacement_key_address; + // Erase replacement key/value's old slots + // Skipped because memset is slow on wasm + // memset(replacement_key_address, 0, sizeof(DN_SIMDHASH_KEY_T)); + // memset(replacement_address, 0, sizeof(DN_SIMDHASH_VALUE_T)); + + // If this item cascaded out of its original target bucket, we need + // to go through all the buckets we visited on the way here and reduce + // their cascade counters (if possible), to maintain better scan performance. + if (bucket_index != first_bucket_index) + adjust_cascaded_counts(buffers, first_bucket_index, bucket_index, 0); + +#if DN_SIMDHASH_HAS_REMOVE_HANDLER + // We've finished removing the item, so we're in a consistent state and can notify + DN_SIMDHASH_ON_REMOVE(DN_SIMDHASH_GET_DATA(hash), actual_key, value); +#endif + + return 1; + } else if (index_in_bucket == DN_SIMDHASH_SCAN_BUCKET_NO_OVERFLOW) + return 0; + END_SCAN_BUCKETS(first_bucket_index, bucket_index, bucket_address) + + return 0; +} + +uint8_t +DN_SIMDHASH_TRY_REPLACE_VALUE (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, DN_SIMDHASH_VALUE_T new_value) +{ + check_self(hash); + + uint32_t key_hash = DN_SIMDHASH_KEY_HASHER(DN_SIMDHASH_GET_DATA(hash), key); + return DN_SIMDHASH_TRY_REPLACE_VALUE_WITH_HASH(hash, key, key_hash, new_value); +} + +uint8_t +DN_SIMDHASH_TRY_REPLACE_VALUE_WITH_HASH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_KEY_T key, uint32_t key_hash, DN_SIMDHASH_VALUE_T new_value) +{ + check_self(hash); + + DN_SIMDHASH_VALUE_T *value_ptr = DN_SIMDHASH_FIND_VALUE_INTERNAL(hash, key, key_hash); + if (!value_ptr) + return 0; +#if DN_SIMDHASH_HAS_REPLACE_HANDLER + DN_SIMDHASH_VALUE_T old_value = *value_ptr; +#endif + *value_ptr = new_value; +#if DN_SIMDHASH_HAS_REPLACE_HANDLER + DN_SIMDHASH_ON_REPLACE(DN_SIMDHASH_GET_DATA(hash), key, key, old_value, new_value); +#endif + return 1; +} + +void +DN_SIMDHASH_FOREACH (DN_SIMDHASH_T_PTR hash, DN_SIMDHASH_FOREACH_FUNC func, void *user_data) +{ + check_self(hash); + dn_simdhash_assert(func); + + dn_simdhash_buffers_t buffers = hash->buffers; + BEGIN_SCAN_PAIRS(buffers, key_address, value_address) + func(*key_address, *value_address, user_data); + END_SCAN_PAIRS(buffers, key_address, value_address) +} diff --git a/src/native/containers/dn-simdhash-specializations.h b/src/native/containers/dn-simdhash-specializations.h new file mode 100644 index 00000000000000..4966c7575d19a0 --- /dev/null +++ b/src/native/containers/dn-simdhash-specializations.h @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DN_SIMDHASH_SPECIALIZATIONS_H__ +#define __DN_SIMDHASH_SPECIALIZATIONS_H__ + +#include "dn-simdhash.h" + +typedef struct dn_simdhash_str_key dn_simdhash_str_key; + +#define DN_SIMDHASH_T dn_simdhash_string_ptr +#define DN_SIMDHASH_KEY_T dn_simdhash_str_key +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_ACCESSOR_SUFFIX _raw + +#include "dn-simdhash-specialization-declarations.h" + +#undef DN_SIMDHASH_T +#undef DN_SIMDHASH_KEY_T +#undef DN_SIMDHASH_VALUE_T +#undef DN_SIMDHASH_ACCESSOR_SUFFIX + +#include "dn-simdhash-string-ptr.h" + + +#define DN_SIMDHASH_T dn_simdhash_u32_ptr +#define DN_SIMDHASH_KEY_T uint32_t +#define DN_SIMDHASH_VALUE_T void * + +#include "dn-simdhash-specialization-declarations.h" + +#undef DN_SIMDHASH_T +#undef DN_SIMDHASH_KEY_T +#undef DN_SIMDHASH_VALUE_T + + +#define DN_SIMDHASH_T dn_simdhash_ptr_ptr +#define DN_SIMDHASH_KEY_T void * +#define DN_SIMDHASH_VALUE_T void * + +#include "dn-simdhash-specialization-declarations.h" + +#undef DN_SIMDHASH_T +#undef DN_SIMDHASH_KEY_T +#undef DN_SIMDHASH_VALUE_T + + +#define DN_SIMDHASH_T dn_simdhash_ght +#define DN_SIMDHASH_KEY_T void * +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_NO_DEFAULT_NEW 1 + +#include "dn-simdhash-specialization-declarations.h" + +#undef DN_SIMDHASH_T +#undef DN_SIMDHASH_KEY_T +#undef DN_SIMDHASH_VALUE_T +#undef DN_SIMDHASH_NO_DEFAULT_NEW + +#include "dn-simdhash-ght-compatible.h" + +#endif diff --git a/src/native/containers/dn-simdhash-string-ptr.c b/src/native/containers/dn-simdhash-string-ptr.c new file mode 100644 index 00000000000000..87c00f40f01822 --- /dev/null +++ b/src/native/containers/dn-simdhash-string-ptr.c @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef NO_CONFIG_H +#include +#endif +#include "dn-simdhash.h" + +#include "dn-simdhash-utils.h" + +typedef struct dn_simdhash_str_key { + const char *text; + // We keep a precomputed hash to speed up rehashing and scans. + uint32_t hash; +#if SIZEOF_VOID_P == 8 + // HACK: Perfect cache alignment isn't possible for a 12-byte struct, so pad it to 16 bytes + uint32_t padding; +#endif +} dn_simdhash_str_key; + +static inline int32_t +dn_simdhash_str_equal (dn_simdhash_str_key v1, dn_simdhash_str_key v2) +{ + if (v1.text == v2.text) + return 1; + return strcmp(v1.text, v2.text) == 0; +} + +static inline uint32_t +dn_simdhash_str_hash (dn_simdhash_str_key v1) +{ + return v1.hash; +} + +#define DN_SIMDHASH_T dn_simdhash_string_ptr +#define DN_SIMDHASH_KEY_T dn_simdhash_str_key +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_KEY_HASHER(hash, key) dn_simdhash_str_hash(key) +#define DN_SIMDHASH_KEY_EQUALS(hash, lhs, rhs) dn_simdhash_str_equal(lhs, rhs) +#define DN_SIMDHASH_ACCESSOR_SUFFIX _raw + +// perfect cache alignment. 32-bit ptrs: 8-byte keys. 64-bit: 16-byte keys. +#if SIZEOF_VOID_P == 8 +#define DN_SIMDHASH_BUCKET_CAPACITY 11 +#else +#define DN_SIMDHASH_BUCKET_CAPACITY 12 +#endif + +#include "dn-simdhash-specialization.h" +#include "dn-simdhash-string-ptr.h" + +static dn_simdhash_str_key +dn_simdhash_make_str_key (const char *text) +{ + dn_simdhash_str_key result = { 0, }; + if (text) { + // FIXME: Select a good seed. + result.hash = MurmurHash3_32_streaming((uint8_t *)text, 0); + result.text = text; + } + return result; +} + +uint8_t +dn_simdhash_string_ptr_try_add (dn_simdhash_string_ptr_t *hash, const char *key, void *value) +{ + return dn_simdhash_string_ptr_try_add_raw(hash, dn_simdhash_make_str_key(key), value); +} + +uint8_t +dn_simdhash_string_ptr_try_get_value (dn_simdhash_string_ptr_t *hash, const char *key, void **result) +{ + return dn_simdhash_string_ptr_try_get_value_raw(hash, dn_simdhash_make_str_key(key), result); +} + +uint8_t +dn_simdhash_string_ptr_try_remove (dn_simdhash_string_ptr_t *hash, const char *key) +{ + return dn_simdhash_string_ptr_try_remove_raw(hash, dn_simdhash_make_str_key(key)); +} + +// FIXME: Find a way to make this easier to define +void +dn_simdhash_string_ptr_foreach (dn_simdhash_string_ptr_t *hash, dn_simdhash_string_ptr_foreach_func func, void *user_data) +{ + assert(hash); + assert(func); + + dn_simdhash_buffers_t buffers = hash->buffers; + BEGIN_SCAN_PAIRS(buffers, key_address, value_address) + func(key_address->text, *value_address, user_data); + END_SCAN_PAIRS(buffers, key_address, value_address) +} diff --git a/src/native/containers/dn-simdhash-string-ptr.h b/src/native/containers/dn-simdhash-string-ptr.h new file mode 100644 index 00000000000000..fc3e80fef5689a --- /dev/null +++ b/src/native/containers/dn-simdhash-string-ptr.h @@ -0,0 +1,13 @@ +uint8_t +dn_simdhash_string_ptr_try_add (dn_simdhash_string_ptr_t *hash, const char *key, void *value); + +uint8_t +dn_simdhash_string_ptr_try_get_value (dn_simdhash_string_ptr_t *hash, const char *key, void **result); + +uint8_t +dn_simdhash_string_ptr_try_remove (dn_simdhash_string_ptr_t *hash, const char *key); + +typedef void (*dn_simdhash_string_ptr_foreach_func) (const char *key, void *value, void *user_data); + +void +dn_simdhash_string_ptr_foreach (dn_simdhash_string_ptr_t *hash, dn_simdhash_string_ptr_foreach_func func, void *user_data); diff --git a/src/native/containers/dn-simdhash-test.c b/src/native/containers/dn-simdhash-test.c new file mode 100644 index 00000000000000..da0e60acb28878 --- /dev/null +++ b/src/native/containers/dn-simdhash-test.c @@ -0,0 +1,262 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#include +#define MTICKS_PER_SEC (10 * 1000 * 1000) +#else +#include +#endif + +#include "dn-vector.h" +#include "dn-simdhash.h" +#include "dn-simdhash-utils.h" + +typedef struct { + int i; + float f; +} instance_data_t; + +void +dn_simdhash_assert_fail (const char *file, int line, const char *condition) { + printf("simdhash assertion failed at %s:%i:\n%s\n", file, line, condition); + fflush(stdout); +} + +static DN_FORCEINLINE(uint8_t) +key_comparer (instance_data_t data, size_t lhs, size_t rhs) { + return ((data.f == 4.20f) || (lhs == rhs)); +} + +#define DN_SIMDHASH_T dn_simdhash_size_t_size_t +#define DN_SIMDHASH_KEY_T size_t +#define DN_SIMDHASH_VALUE_T size_t +#define DN_SIMDHASH_KEY_HASHER(data, key) (uint32_t)(key & 0xFFFFFFFFu) +#define DN_SIMDHASH_KEY_EQUALS key_comparer +#define DN_SIMDHASH_INSTANCE_DATA_T instance_data_t +#define DN_SIMDHASH_ON_REMOVE(data, key, value) // printf("remove [%zd, %zd], f==%f\n", key, value, data.f) +#define DN_SIMDHASH_ON_REPLACE(data, old_key, new_key, old_value, new_value) // printf("replace [%zd, %zd] with [%zd, %zd] i==%i\n", key, old_value, key, new_value, data.i) + +#include "dn-simdhash-specialization.h" + +uint32_t count_cascaded_buckets (dn_simdhash_size_t_size_t_t *hash) { + uint32_t result = 0; + dn_simdhash_buffers_t buffers = hash->buffers; + BEGIN_SCAN_BUCKETS(0, bucket_index, bucket_address) + result += dn_simdhash_bucket_cascaded_count(bucket_address->suffixes); + END_SCAN_BUCKETS(0, bucket_index, bucket_address) + return result; +} + +uint8_t tassert (int b, const char *msg) { + if (b) + return b; + printf("%s\n", msg); + return 0; +} + +uint8_t tassert1 (int b, size_t v, const char *msg) { + if (b) + return b; + printf("%s (%zd)\n", msg, v); + return 0; +} + +uint8_t tasserteq (size_t actual, size_t expected, const char *msg) { + if (actual == expected) + return 1; + printf("%s: expected %zd, got %zd\n", msg, expected, actual); + return 0; +} + +void foreach_callback (size_t key, size_t value, void * user_data) { + // printf("[%zd, %zd]\n", key, value); + (*(uint32_t *)user_data)++; +} + +int64_t get_100ns_ticks () { +#ifdef _MSC_VER + static LARGE_INTEGER freq; + static UINT64 start_time; + UINT64 cur_time; + LARGE_INTEGER value; + + if (!freq.QuadPart) { + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&value); + start_time = value.QuadPart; + } + QueryPerformanceCounter(&value); + cur_time = value.QuadPart; + return (int64_t)((cur_time - start_time) * (double)MTICKS_PER_SEC / freq.QuadPart); +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return ((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) * 10; +#endif +} + +int main () { + // NOTE: High values of C will cause this test to never complete if libc + // rand() is not high quality enough, i.e. MSVC 2022 on x64 + const int c = 32000; + dn_simdhash_size_t_size_t_t *test = dn_simdhash_size_t_size_t_new(0, NULL); + dn_simdhash_instance_data(instance_data_t, test).f = 3.14f; + dn_simdhash_instance_data(instance_data_t, test).i = 42; + + printf("hash(test)=%u\n", MurmurHash3_32_ptr(test, 0)); + + dn_vector_t *keys = dn_vector_alloc(sizeof(DN_SIMDHASH_KEY_T)), + *values = dn_vector_alloc(sizeof(DN_SIMDHASH_VALUE_T)); + // Ensure consistency between runs + srand(1); + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_VALUE_T value = (i * 2) + 1; + DN_SIMDHASH_KEY_T key; + +retry: { + key = rand(); + uint8_t ok = dn_simdhash_size_t_size_t_try_add(test, key, value); + if (!ok) + goto retry; +} + + dn_vector_push_back(keys, key); + dn_vector_push_back(values, value); + } + + int64_t started = get_100ns_ticks(); + for (int iter = 0; iter < 5; iter++) { + if (!tasserteq(dn_simdhash_count(test), c, "count did not match")) + return 1; + + printf("Calling foreach:\n"); + uint32_t foreach_count = 0; + dn_simdhash_size_t_size_t_foreach(test, foreach_callback, &foreach_count); + printf("Foreach iterated %u time(s)\n", foreach_count); + printf("Count: %u, Capacity: %u, Cascaded item count: %u\n", dn_simdhash_count(test), dn_simdhash_capacity(test), count_cascaded_buckets(test)); + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + DN_SIMDHASH_VALUE_T value, expected_value = *dn_vector_index_t(values, DN_SIMDHASH_VALUE_T, i); + + uint8_t ok = dn_simdhash_size_t_size_t_try_get_value(test, key, &value); + if (tassert1(ok, key, "did not find key")) + tasserteq(value, expected_value, "value did not match"); + } + + // NOTE: Adding duplicates could grow the table if we're unlucky, since the add operation + // eagerly grows before doing a table scan if we're at the grow threshold. + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + DN_SIMDHASH_VALUE_T value = *dn_vector_index_t(values, DN_SIMDHASH_VALUE_T, i); + + uint8_t ok = dn_simdhash_size_t_size_t_try_add(test, key, value); + tassert1(!ok, key, "added duplicate key successfully"); + } + + printf("After adding dupes: Count: %u, Capacity: %u, Cascaded item count: %u\n", dn_simdhash_count(test), dn_simdhash_capacity(test), count_cascaded_buckets(test)); + uint32_t final_capacity = dn_simdhash_capacity(test); + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + uint8_t ok = dn_simdhash_size_t_size_t_try_remove(test, key); + tassert1(ok, key, "could not remove key"); + + DN_SIMDHASH_VALUE_T value; + ok = dn_simdhash_size_t_size_t_try_get_value(test, key, &value); + tassert1(!ok, key, "found key after removal"); + } + + if (!tasserteq(dn_simdhash_count(test), 0, "was not empty")) + return 1; + if (!tasserteq(dn_simdhash_capacity(test), final_capacity, "capacity changed by emptying")) + return 1; + + printf ("Calling foreach after emptying:\n"); + foreach_count = 0; + dn_simdhash_size_t_size_t_foreach(test, foreach_callback, &foreach_count); + printf("Foreach iterated %u time(s)\n", foreach_count); + printf("Count: %u, Capacity: %u, Cascaded item count: %u\n", dn_simdhash_count(test), dn_simdhash_capacity(test), count_cascaded_buckets(test)); + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + DN_SIMDHASH_VALUE_T value; + uint8_t ok = dn_simdhash_size_t_size_t_try_get_value(test, key, &value); + tassert1(!ok, key, "found key after removal"); + } + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + DN_SIMDHASH_VALUE_T value = *dn_vector_index_t(values, DN_SIMDHASH_VALUE_T, i); + + uint8_t ok = dn_simdhash_size_t_size_t_try_add(test, key, value); + tassert1(ok, key, "could not re-insert key after emptying"); + } + + if (!tasserteq(dn_simdhash_capacity(test), final_capacity, "expected capacity not to change after refilling")) + return 1; + + for (int i = 0; i < c; i++) { + DN_SIMDHASH_KEY_T key = *dn_vector_index_t(keys, DN_SIMDHASH_KEY_T, i); + DN_SIMDHASH_VALUE_T value, expected_value = *dn_vector_index_t(values, DN_SIMDHASH_VALUE_T, i); + + uint8_t ok = dn_simdhash_size_t_size_t_try_get_value(test, key, &value); + if (tassert1(ok, key, "did not find key after refilling")) + tasserteq(value, expected_value, "value did not match after refilling"); + } + + printf("Calling foreach after refilling:\n"); + foreach_count = 0; + dn_simdhash_size_t_size_t_foreach(test, foreach_callback, &foreach_count); + printf("Foreach iterated %u time(s)\n", foreach_count); + printf("Count: %u, Capacity: %u, Cascaded item count: %u\n", dn_simdhash_count(test), dn_simdhash_capacity(test), count_cascaded_buckets(test)); + } + + int64_t ended = get_100ns_ticks(); + + printf("done. elapsed ticks: %lld\n", (ended - started)); + + return 0; + /* + var test = new SimdDictionary(); + var rng = new Random(1234); + int c = 4096, d = 4096 * 5; + var keys = new List(); + for (int i = 0; i < c; i++) + keys.Add(rng.NextInt64()); + for (int i = 0; i < c; i++) + test.Add(keys[i], i * 2 + 1); + + for (int j = 0; j < d; j++) + for (int i = 0; i < c; i++) + if (!test.TryGetValue(keys[i], out _)) + throw new Exception(); + + var keyList = test.Keys.ToArray(); + var valueList = test.Values.ToArray(); + + var copy = new SimdDictionary(test); + for (int i = 0; i < c; i++) + if (!copy.TryGetValue(keys[i], out _)) + throw new Exception(); + + for (int i = 0; i < c; i++) + if (!test.Remove(keys[i])) + throw new Exception(); + + for (int i = 0; i < c; i++) + if (test.TryGetValue(keys[i], out _)) + throw new Exception(); + + if (test.Count != 0) + throw new Exception(); + */ +} diff --git a/src/native/containers/dn-simdhash-u32-ptr.c b/src/native/containers/dn-simdhash-u32-ptr.c new file mode 100644 index 00000000000000..a3e1a77a92c2a6 --- /dev/null +++ b/src/native/containers/dn-simdhash-u32-ptr.c @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "dn-simdhash.h" +#include "dn-simdhash-utils.h" + +#define DN_SIMDHASH_T dn_simdhash_u32_ptr +#define DN_SIMDHASH_KEY_T uint32_t +#define DN_SIMDHASH_VALUE_T void * +#define DN_SIMDHASH_KEY_HASHER(hash, key) murmur3_fmix32(key) +#define DN_SIMDHASH_KEY_EQUALS(hash, lhs, rhs) (lhs == rhs) + +#include "dn-simdhash-specialization.h" diff --git a/src/native/containers/dn-simdhash-utils.h b/src/native/containers/dn-simdhash-utils.h new file mode 100644 index 00000000000000..889127d4088a8b --- /dev/null +++ b/src/native/containers/dn-simdhash-utils.h @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DN_SIMDHASH_UTILS_H__ +#define __DN_SIMDHASH_UTILS_H__ + +#include + +#if defined(__clang__) || defined (__GNUC__) +static DN_FORCEINLINE(uint32_t) +next_power_of_two (uint32_t value) { + if (value < 2) + return 1; + return 1u << (32 - __builtin_clz (value - 1)); +} +#else // __clang__ || __GNUC__ +static DN_FORCEINLINE(uint32_t) +next_power_of_two (uint32_t value) { + if (value < 2) + return 1; + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value++; + return value; +} +#endif // __clang__ || __GNUC__ + +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +static const uint32_t murmur3_c1 = 0xcc9e2d51, murmur3_c2 = 0x1b873593; + +inline static uint32_t +murmur3_rotl32 (uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + +// Finalization mix - force all bits of a hash block to avalanche +inline static uint32_t +murmur3_fmix32 (uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +inline static uint64_t +murmur3_fmix64(uint64_t k) +{ + k ^= k >> 33; + k *= 0xff51afd7ed558ccdLLU; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53LLU; + k ^= k >> 33; + return k; +} + +// Convenience macro so you can define your own fixed-size MurmurHashes +#define MURMUR3_HASH_BLOCK(block) \ + { \ + uint32_t k1 = block; \ + k1 *= murmur3_c1; \ + k1 = murmur3_rotl32(k1, 15); \ + k1 *= murmur3_c2; \ + h1 ^= k1; \ + h1 = murmur3_rotl32(h1, 13); \ + h1 = h1 * 5 + 0xe6546b64; \ + } + +// Hash a void * (either 4 or 8 bytes) +static inline uint32_t +MurmurHash3_32_ptr (const void *ptr, uint32_t seed) +{ + // mono_aligned_addr_hash shifts all incoming pointers by 3 bits to account + // for a presumed 8-byte alignment of addresses (the dlmalloc default). + const uint32_t alignment_shift = 3; + // Compute this outside of the if to suppress msvc build warning + const uint8_t is_64_bit = sizeof(void*) == sizeof(uint64_t); + union { + uint32_t u32; + uint64_t u64; + const void *ptr; + } u; + u.ptr = ptr; + + // Apply murmurhash3's finalization bit mixer to a pointer to compute a 32-bit hash. + if (is_64_bit) { + // The high bits of a 64-bit pointer are usually low entropy, as are the + // 2-3 lowest bits. We want to capture most of the entropy and mix it into + // a 32-bit hash to reduce the odds of hash collisions for arbitrary 64-bit + // pointers. From my testing, this is a good way to do it. + return murmur3_fmix32((uint32_t)((u.u64 >> alignment_shift) & 0xFFFFFFFFu)); + // return (uint32_t)(murmur3_fmix64(u.u64 >> alignment_shift) & 0xFFFFFFFFu); + } else { + // No need for an alignment shift here, we're mixing the bits and then + // simdhash uses 7 of the top bits and a handful of the low bits. + return murmur3_fmix32(u.u32); + } +} + +// end of murmurhash + +// FNV has bad properties for simdhash even though it's a fairly fast/good hash, +// but the overhead of having to do strlen() first before passing a string key to +// MurmurHash3 is significant and annoying. This is an attempt to reformulate the +// 32-bit version of MurmurHash3 into a 1-pass version for null terminated strings. +// The output of this will probably be different from regular MurmurHash3. I don't +// see that as a problem, since you shouldn't rely on the exact bit patterns of +// a non-cryptographic hash anyway. +typedef struct murmur3_scan_result_t { + union { + uint32_t u32; + uint8_t bytes[4]; + } result; + const uint8_t *next; +} murmur3_scan_result_t; + +static inline murmur3_scan_result_t +murmur3_scan_forward (const uint8_t *ptr) +{ + // TODO: On wasm we could do a single u32 load then scan the bytes, + // as long as we're sure ptr isn't up against the end of memory + murmur3_scan_result_t result = { 0, }; + + // I tried to get a loop to auto-unroll, but GCC only unrolls at O3 and MSVC never does. +#define SCAN_1(i) \ + result.result.bytes[i] = ptr[i]; \ + if (DN_UNLIKELY(!result.result.bytes[i])) \ + return result; + + SCAN_1(0); + SCAN_1(1); + SCAN_1(2); + SCAN_1(3); +#undef SCAN_1 + + // doing ptr[i] 4 times then computing here produces better code than ptr++ especially on wasm + result.next = ptr + 4; + return result; +} + +static inline uint32_t +MurmurHash3_32_streaming (const uint8_t *key, uint32_t seed) +{ + uint32_t h1 = seed, block_count = 0; + + // Scan forward through the buffer collecting up to 4 bytes at a time, then hash + murmur3_scan_result_t block = murmur3_scan_forward(key); + // As long as the scan found at least one nonzero byte, u32 will be != 0 + while (block.result.u32) { + block_count += 1; + + MURMUR3_HASH_BLOCK(block.result.u32); + + // If the scan found a null byte next will be 0, so we stop scanning + if (DN_UNLIKELY(!block.next)) + break; + block = murmur3_scan_forward(block.next); + } + + // finalize. we don't have an exact byte length but we have a block count + // it would be ideal to figure out a cheap way to produce an exact byte count, + // since then we can compute the length and hash in one go and use memcmp later, + // since emscripten/musl strcmp isn't optimized at all + h1 ^= block_count; + h1 = murmur3_fmix32(h1); + return h1; +} + +// end of reformulated murmur3-32 + +void +dn_simdhash_assert_fail (const char *file, int line, const char *condition); + +#define dn_simdhash_assert(expr) \ + if (DN_UNLIKELY(!(expr))) { \ + dn_simdhash_assert_fail(__FILE__, __LINE__, #expr); \ + } + +#endif // __DN_SIMDHASH_UTILS_H__ diff --git a/src/native/containers/dn-simdhash.c b/src/native/containers/dn-simdhash.c new file mode 100644 index 00000000000000..03d4d2bf3951aa --- /dev/null +++ b/src/native/containers/dn-simdhash.c @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "dn-simdhash.h" +#include "dn-simdhash-utils.h" + +static uint32_t +compute_adjusted_capacity (uint32_t requested_capacity) +{ + uint64_t _capacity = requested_capacity; + _capacity *= DN_SIMDHASH_SIZING_PERCENTAGE; + _capacity /= 100; + dn_simdhash_assert(_capacity <= UINT32_MAX); + return (uint32_t)_capacity; +} + +dn_simdhash_t * +dn_simdhash_new_internal (dn_simdhash_meta_t *meta, dn_simdhash_vtable_t vtable, uint32_t capacity, dn_allocator_t *allocator) +{ + const size_t size = sizeof(dn_simdhash_t) + meta->data_size; + dn_simdhash_t *result = (dn_simdhash_t *)dn_allocator_alloc(allocator, size); + memset(result, 0, size); + + dn_simdhash_assert(meta); + dn_simdhash_assert((meta->bucket_capacity > 1) && (meta->bucket_capacity <= DN_SIMDHASH_MAX_BUCKET_CAPACITY)); + dn_simdhash_assert(meta->key_size > 0); + dn_simdhash_assert(meta->bucket_size_bytes >= (DN_SIMDHASH_VECTOR_WIDTH + (meta->bucket_capacity * meta->key_size))); + result->meta = meta; + result->vtable = vtable; + result->buffers.allocator = allocator; + + dn_simdhash_ensure_capacity_internal(result, compute_adjusted_capacity(capacity)); + + return result; +} + +void +dn_simdhash_free (dn_simdhash_t *hash) +{ + dn_simdhash_assert(hash); + if (hash->vtable.destroy_all) + hash->vtable.destroy_all(hash); + dn_simdhash_buffers_t buffers = hash->buffers; + memset(hash, 0, sizeof(dn_simdhash_t)); + dn_simdhash_free_buffers(buffers); + dn_allocator_free(buffers.allocator, (void *)hash); +} + +void +dn_simdhash_free_buffers (dn_simdhash_buffers_t buffers) +{ + if (buffers.buckets) + dn_allocator_free(buffers.allocator, (void *)(((uint8_t *)buffers.buckets) - buffers.buckets_bias)); + if (buffers.values) + dn_allocator_free(buffers.allocator, buffers.values); +} + +dn_simdhash_buffers_t +dn_simdhash_ensure_capacity_internal (dn_simdhash_t *hash, uint32_t capacity) +{ + dn_simdhash_assert(hash); + size_t bucket_count = (capacity + hash->meta->bucket_capacity - 1) / hash->meta->bucket_capacity; + // FIXME: Only apply this when capacity == 0? + if (bucket_count < DN_SIMDHASH_MIN_BUCKET_COUNT) + bucket_count = DN_SIMDHASH_MIN_BUCKET_COUNT; + dn_simdhash_assert(bucket_count < UINT32_MAX); + // Bucket count must be a power of two (this enables more efficient hashcode -> bucket mapping) + bucket_count = next_power_of_two((uint32_t)bucket_count); + size_t value_count = bucket_count * hash->meta->bucket_capacity; + dn_simdhash_assert(value_count <= UINT32_MAX); + + dn_simdhash_buffers_t result = { 0, }; + if (bucket_count <= hash->buffers.buckets_length) { + dn_simdhash_assert(value_count <= hash->buffers.values_length); + return result; + } + + /* + printf ( + "growing from %d bucket(s) to %d bucket(s) for requested capacity %d (actual capacity %d)\n", + hash->buffers.buckets_length, bucket_count, + capacity, value_count + ); + */ + // Store old buffers so caller can rehash and then free them + result = hash->buffers; + + size_t grow_at_count = value_count; + grow_at_count *= 100; + grow_at_count /= DN_SIMDHASH_SIZING_PERCENTAGE; + hash->grow_at_count = (uint32_t)grow_at_count; + hash->buffers.buckets_length = (uint32_t)bucket_count; + hash->buffers.values_length = (uint32_t)value_count; + + // pad buckets allocation by the width of one vector so we can align it + size_t buckets_size_bytes = (bucket_count * hash->meta->bucket_size_bytes) + DN_SIMDHASH_VECTOR_WIDTH, + values_size_bytes = value_count * hash->meta->value_size; + + hash->buffers.buckets = dn_allocator_alloc(hash->buffers.allocator, buckets_size_bytes); + memset(hash->buffers.buckets, 0, buckets_size_bytes); + + // Calculate necessary bias for alignment + hash->buffers.buckets_bias = (uint32_t)(DN_SIMDHASH_VECTOR_WIDTH - (((size_t)hash->buffers.buckets) % DN_SIMDHASH_VECTOR_WIDTH)); + // Apply bias + hash->buffers.buckets = (void *)(((uint8_t *)hash->buffers.buckets) + hash->buffers.buckets_bias); + + // No need to go out of our way to align values + hash->buffers.values = dn_allocator_alloc(hash->buffers.allocator, values_size_bytes); + // Skip this for performance; memset is especially slow in wasm + // memset(hash->buffers.values, 0, values_size_bytes); + + return result; +} + +void +dn_simdhash_clear (dn_simdhash_t *hash) +{ + dn_simdhash_assert(hash); + if (hash->vtable.destroy_all) + hash->vtable.destroy_all(hash); + hash->count = 0; + // TODO: Scan through buckets sequentially and only erase ones with data in them + // Maybe skip erasing the key slots too? + memset(hash->buffers.buckets, 0, hash->buffers.buckets_length * hash->meta->bucket_size_bytes); + // Skip this for performance; memset is especially slow in wasm + // memset(hash->buffers.values, 0, hash->buffers.values_length * hash->meta->value_size); +} + +uint32_t +dn_simdhash_capacity (dn_simdhash_t *hash) +{ + dn_simdhash_assert(hash); + return hash->buffers.buckets_length * hash->meta->bucket_capacity; +} + +uint32_t +dn_simdhash_count (dn_simdhash_t *hash) +{ + dn_simdhash_assert(hash); + return hash->count; +} + +void +dn_simdhash_ensure_capacity (dn_simdhash_t *hash, uint32_t capacity) +{ + dn_simdhash_assert(hash); + capacity = compute_adjusted_capacity(capacity); + dn_simdhash_buffers_t old_buffers = dn_simdhash_ensure_capacity_internal(hash, capacity); + if (old_buffers.buckets) { + hash->vtable.rehash(hash, old_buffers); + dn_simdhash_free_buffers(old_buffers); + } +} diff --git a/src/native/containers/dn-simdhash.h b/src/native/containers/dn-simdhash.h new file mode 100644 index 00000000000000..2a26083ec7df1c --- /dev/null +++ b/src/native/containers/dn-simdhash.h @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DN_SIMDHASH_H__ +#define __DN_SIMDHASH_H__ + +#include +#include "dn-utils.h" +#include "dn-allocator.h" + +// We reserve the last two bytes of each suffix vector to store data +#define DN_SIMDHASH_MAX_BUCKET_CAPACITY 14 +// The ideal capacity depends on the size of your keys. For 4-byte keys, it is 12. +#define DN_SIMDHASH_DEFAULT_BUCKET_CAPACITY 12 +// We use the last two bytes specifically to store item count and cascade flag +#define DN_SIMDHASH_COUNT_SLOT (DN_SIMDHASH_MAX_BUCKET_CAPACITY) +// The cascade flag indicates that an item overflowed from this bucket into the next one +#define DN_SIMDHASH_CASCADED_SLOT (DN_SIMDHASH_MAX_BUCKET_CAPACITY + 1) +// We always use 16-byte-wide vectors (I've tested this, 32-byte vectors are slower) +#define DN_SIMDHASH_VECTOR_WIDTH 16 +// We need to make sure suffixes are never zero. A bad hash is more likely to collide +// at the top bit than at the bottom. +#define DN_SIMDHASH_SUFFIX_SALT 0b10000000 +// Set a minimum number of buckets when created, regardless of requested capacity +#define DN_SIMDHASH_MIN_BUCKET_COUNT 1 +// User-specified capacity values will be increased to this percentage in order +// to maintain an ideal load factor. FIXME: 120 isn't right +#define DN_SIMDHASH_SIZING_PERCENTAGE 120 + +typedef struct dn_simdhash_void_data_t { + // HACK: Empty struct or 0-element array produce a MSVC warning and break the build. + uint8_t data[1]; +} dn_simdhash_void_data_t; + +typedef struct dn_simdhash_buffers_t { + // sizes of current allocations in items (not bytes) + // so values_length should == (buckets_length * bucket_capacity) + uint32_t buckets_length, values_length, + // The number of bytes we pushed the buckets ptr forward after allocating it. + // We'll need to subtract this from the ptr before freeing. + buckets_bias; + void *buckets; + void *values; + dn_allocator_t *allocator; +} dn_simdhash_buffers_t; + +typedef struct dn_simdhash_t dn_simdhash_t; + +typedef struct dn_simdhash_meta_t { + // type metadata for generic implementation + uint32_t bucket_capacity, bucket_size_bytes, key_size, value_size, + // Allocate this many bytes of extra data inside the dn_simdhash_t + data_size; +} dn_simdhash_meta_t; + +typedef enum dn_simdhash_insert_result { + DN_SIMDHASH_INSERT_OK_ADDED_NEW, + DN_SIMDHASH_INSERT_OK_OVERWROTE_EXISTING, + DN_SIMDHASH_INSERT_NEED_TO_GROW, + DN_SIMDHASH_INSERT_KEY_ALREADY_PRESENT, +} dn_simdhash_insert_result; + +typedef struct dn_simdhash_vtable_t { + // Does not free old_buffers, that's your job. Required. + void (*rehash) (dn_simdhash_t *hash, dn_simdhash_buffers_t old_buffers); + // Invokes remove handler for all items, if necessary. Optional. + void (*destroy_all) (dn_simdhash_t *hash); +} dn_simdhash_vtable_t; + +typedef struct dn_simdhash_t { + // internal state + uint32_t count, grow_at_count; + dn_simdhash_buffers_t buffers; + dn_simdhash_vtable_t vtable; + dn_simdhash_meta_t *meta; + // We allocate extra space here based on meta.data_size + // This has one element because 0 elements generates a MSVC warning and breaks the build + uint8_t data[1]; +} dn_simdhash_t; + +#define dn_simdhash_instance_data(type, hash) \ + (*(type *)(&hash->data)) + +// These helpers use .values instead of .vec to avoid generating unnecessary +// vector loads/stores. Operations that touch these values may not need vectorization, +// so it's ideal to just do single-byte memory accesses instead. +// These unfortunately have to be macros because the suffixes type isn't defined yet +#define dn_simdhash_bucket_count(suffixes) \ + (suffixes).values[DN_SIMDHASH_COUNT_SLOT] + +#define dn_simdhash_bucket_cascaded_count(suffixes) \ + (suffixes).values[DN_SIMDHASH_CASCADED_SLOT] + +#define dn_simdhash_bucket_set_suffix(suffixes, slot, value) \ + (suffixes).values[(slot)] = (value) + +#define dn_simdhash_bucket_set_count(suffixes, value) \ + (suffixes).values[DN_SIMDHASH_COUNT_SLOT] = (value) + +#define dn_simdhash_bucket_set_cascaded_count(suffixes, value) \ + (suffixes).values[DN_SIMDHASH_CASCADED_SLOT] = (value) + +static DN_FORCEINLINE(uint8_t) +dn_simdhash_select_suffix (uint32_t key_hash) +{ + // Extract top 8 bits, then trash the highest one. + // The lowest bits of the hash are used to select the bucket index. + return (key_hash >> 24) | DN_SIMDHASH_SUFFIX_SALT; +} + +static DN_FORCEINLINE(uint32_t) +dn_simdhash_select_bucket_index (dn_simdhash_buffers_t buffers, uint32_t key_hash) +{ + // This relies on bucket count being a power of two. + return key_hash & (buffers.buckets_length - 1); +} + + +// Creates a simdhash with the provided configuration metadata, vtable, size, and allocator. +// Be sure you know what you're doing. +dn_simdhash_t * +dn_simdhash_new_internal (dn_simdhash_meta_t *meta, dn_simdhash_vtable_t vtable, uint32_t capacity, dn_allocator_t *allocator); + +// Frees a simdhash and its associated buffers. +void +dn_simdhash_free (dn_simdhash_t *hash); + +// Frees a set of simdhash buffers (returned by ensure_capacity_internal). +void +dn_simdhash_free_buffers (dn_simdhash_buffers_t buffers); + +// If a resize happens, this will allocate new buffers and return the old ones. +// It is your responsibility to rehash and then free the old buffers. +dn_simdhash_buffers_t +dn_simdhash_ensure_capacity_internal (dn_simdhash_t *hash, uint32_t capacity); + +// Erases the contents of the table, but does not shrink it. +void +dn_simdhash_clear (dn_simdhash_t *hash); + +// Returns the actual number of values the table can currently hold. +// It may grow automatically before reaching that point. +uint32_t +dn_simdhash_capacity (dn_simdhash_t *hash); + +// Returns the number of value currently stored in the table. +uint32_t +dn_simdhash_count (dn_simdhash_t *hash); + +// Automatically resizes the table if it is too small to hold the requested number +// of items. Will not shrink the table if it is already bigger. +void +dn_simdhash_ensure_capacity (dn_simdhash_t *hash, uint32_t capacity); + +#endif // __DN_SIMDHASH_H__ diff --git a/src/native/containers/dn-utils.h b/src/native/containers/dn-utils.h index 131889165b2f2d..dab4160852b1fa 100644 --- a/src/native/containers/dn-utils.h +++ b/src/native/containers/dn-utils.h @@ -39,7 +39,7 @@ #define DN_CALLBACK_CALLTYPE #endif -#if defined(__GNUC__) && (__GNUC__ > 2) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 2)) #define DN_LIKELY(expr) (__builtin_expect ((expr) != 0, 1)) #define DN_UNLIKELY(expr) (__builtin_expect ((expr) != 0, 0)) #else @@ -51,6 +51,12 @@ #define _DN_STATIC_ASSERT(expr) static_assert(expr, "") +#ifdef _MSC_VER +#define DN_FORCEINLINE(RET_TYPE) __forceinline RET_TYPE +#else +#define DN_FORCEINLINE(RET_TYPE) inline RET_TYPE __attribute__((always_inline)) +#endif + static inline bool dn_safe_size_t_multiply (size_t lhs, size_t rhs, size_t *result) { @@ -58,7 +64,7 @@ dn_safe_size_t_multiply (size_t lhs, size_t rhs, size_t *result) *result = 0; return true; } - + if (((size_t)(~(size_t)0) / lhs) < rhs) return false; diff --git a/src/native/containers/simdhash-benchmark/Makefile b/src/native/containers/simdhash-benchmark/Makefile new file mode 100644 index 00000000000000..7aa316ba1555b6 --- /dev/null +++ b/src/native/containers/simdhash-benchmark/Makefile @@ -0,0 +1,26 @@ +.DEFAULT_GOAL := default_target + +dn_deps := $(wildcard ../*.c) $(wildcard ../*.h) +benchmark_deps := $(wildcard ./*.c) $(wildcard ./*.h) + +benchmark_sources := ../dn-simdhash.c ../dn-vector.c ./benchmark.c ../dn-simdhash-u32-ptr.c ../dn-simdhash-string-ptr.c ./ghashtable.c ./all-measurements.c +common_options := -g -O3 -DNO_CONFIG_H -lm -DNDEBUG + +benchmark-native: $(dn_deps) $(benchmark_deps) + clang $(benchmark_sources) $(common_options) -DSIZEOF_VOID_P=8 + +benchmark-wasm: $(dn_deps) $(benchmark_deps) + ~/Projects/emscripten/emcc $(benchmark_sources) $(common_options) -DSIZEOF_VOID_P=4 -mbulk-memory -msimd128 + +disassemble-benchmark: benchmark-native benchmark-wasm + objdump -d ./a.out > ./a.dis + ~/wabt/bin/wasm2wat ./a.out.wasm > ./a.wat + +run-native: benchmark-native + ./a.out + +run-wasm: benchmark-wasm + node ./a.out.js + +default_target: disassemble-benchmark + diff --git a/src/native/containers/simdhash-benchmark/all-measurements.c b/src/native/containers/simdhash-benchmark/all-measurements.c new file mode 100644 index 00000000000000..2d04bbd4270120 --- /dev/null +++ b/src/native/containers/simdhash-benchmark/all-measurements.c @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include + +#include "../dn-vector.h" +#include "../dn-simdhash.h" +#include "../dn-simdhash-utils.h" +#include "../dn-simdhash-specializations.h" + +#include "measurement.h" + +#undef MEASUREMENT +#define MEASUREMENT(name, data_type, setup, teardown, body) \ + static void DN_SIMDHASH_GLUE(measurement_, name) (void *_data) { \ + data_type data = (data_type)_data; \ + body; \ + } + +#include "all-measurements.h" + +#undef MEASUREMENT +#define MEASUREMENT(name, data_type, setup, teardown, body) \ + measurement_info DN_SIMDHASH_GLUE(name, _measurement_info) = { \ + #name, \ + setup, \ + DN_SIMDHASH_GLUE(measurement_, name), \ + teardown \ + }; + +#include "all-measurements.h" diff --git a/src/native/containers/simdhash-benchmark/all-measurements.h b/src/native/containers/simdhash-benchmark/all-measurements.h new file mode 100644 index 00000000000000..2e49927337a34f --- /dev/null +++ b/src/native/containers/simdhash-benchmark/all-measurements.h @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "ghashtable.h" + +#ifndef MEASUREMENTS_IMPLEMENTATION +#define MEASUREMENTS_IMPLEMENTATION 1 + +// If this is too large and libc's rand() is low quality (i.e. MSVC), +// initializing the data will take forever +#define INNER_COUNT 1024 * 16 +#define BASELINE_SIZE 20480 + +static dn_simdhash_u32_ptr_t *random_u32s_hash; +static dn_vector_t *sequential_u32s, *random_u32s, *random_unused_u32s; + +static void init_data () { + random_u32s_hash = dn_simdhash_u32_ptr_new(INNER_COUNT, NULL); + sequential_u32s = dn_vector_alloc(sizeof(uint32_t)); + random_u32s = dn_vector_alloc(sizeof(uint32_t)); + random_unused_u32s = dn_vector_alloc(sizeof(uint32_t)); + // For consistent data between runs + srand(1); + + for (uint32_t i = 0; i < INNER_COUNT; i++) { + dn_vector_push_back(sequential_u32s, i); + +retry: { + uint32_t key = (uint32_t)(rand() & 0xFFFFFFFFu); + if (!dn_simdhash_u32_ptr_try_add(random_u32s_hash, key, NULL)) + goto retry; + + dn_vector_push_back(random_u32s, key); +} + } + + for (uint32_t i = 0; i < INNER_COUNT; i++) { +retry2: { + uint32_t key = (uint32_t)(rand() & 0xFFFFFFFFu); + if (!dn_simdhash_u32_ptr_try_add(random_u32s_hash, key, NULL)) + goto retry2; + + dn_vector_push_back(random_unused_u32s, key); +} + } +} + + +static void * create_instance_u32_ptr () { + if (!random_u32s) + init_data(); + + return dn_simdhash_u32_ptr_new(INNER_COUNT, NULL); +} + +static void * create_instance_u32_ptr_random_values () { + if (!random_u32s) + init_data(); + + dn_simdhash_u32_ptr_t *result = dn_simdhash_u32_ptr_new(INNER_COUNT, NULL); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_u32_ptr_try_add(result, key, (void *)(size_t)i); + } + return result; +} + +static void destroy_instance (void *_data) { + dn_simdhash_u32_ptr_t *data = _data; + if (!data) + return; + + dn_simdhash_free(data); +} + + +static void * baseline_init () { + return malloc(BASELINE_SIZE); +} + + +static void * create_instance_ght () { + if (!random_u32s) + init_data(); + + return g_hash_table_new(NULL, NULL); +} + +static void * create_instance_ght_random_values () { + if (!random_u32s) + init_data(); + + GHashTable *result = g_hash_table_new(NULL, NULL); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + g_hash_table_insert(result, (gpointer)(size_t)key, (gpointer)(size_t)i); + } + return result; +} + +static void destroy_instance_ght (void *data) { + g_hash_table_destroy((GHashTable *)data); +} + +#endif // MEASUREMENTS_IMPLEMENTATION + +// These go outside the guard because we include this file multiple times. + +MEASUREMENT(baseline, uint8_t *, baseline_init, free, { + for (int i = 0; i < 256; i++) { + memset(data, i, BASELINE_SIZE); + // Without this the memset gets optimized out + dn_simdhash_assert(data[i] == i); + } +}); + +MEASUREMENT(dn_clear_then_fill_sequential, dn_simdhash_u32_ptr_t *, create_instance_u32_ptr, destroy_instance, { + dn_simdhash_clear(data); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(sequential_u32s, uint32_t, i); + dn_simdhash_assert(dn_simdhash_u32_ptr_try_add(data, key, (void *)(size_t)i)); + } +}) + +MEASUREMENT(dn_clear_then_fill_random, dn_simdhash_u32_ptr_t *, create_instance_u32_ptr, destroy_instance, { + dn_simdhash_clear(data); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_assert(dn_simdhash_u32_ptr_try_add(data, key, (void *)(size_t)i)); + } +}) + +MEASUREMENT(dn_find_random_keys, dn_simdhash_u32_ptr_t *, create_instance_u32_ptr_random_values, destroy_instance, { + void *temp = NULL; + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_assert(dn_simdhash_u32_ptr_try_get_value(data, key, &temp)); + } +}) + +MEASUREMENT(dn_find_missing_key, dn_simdhash_u32_ptr_t *, create_instance_u32_ptr_random_values, destroy_instance, { + void *temp = NULL; + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_unused_u32s, uint32_t, i); + dn_simdhash_assert(!dn_simdhash_u32_ptr_try_get_value(data, key, &temp)); + } +}) + +MEASUREMENT(dn_fill_then_remove_every_item, dn_simdhash_u32_ptr_t *, create_instance_u32_ptr, destroy_instance, { + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_assert(dn_simdhash_u32_ptr_try_add(data, key, (void *)(size_t)i)); + } + + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_assert(dn_simdhash_u32_ptr_try_remove(data, key)); + } +}) + +MEASUREMENT(ght_clear_then_fill_sequential, GHashTable *, create_instance_ght, destroy_instance_ght, { + g_hash_table_remove_all(data); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(sequential_u32s, uint32_t, i); + g_hash_table_insert(data, (gpointer)(size_t)key, (gpointer)(size_t)i); + } +}) + +MEASUREMENT(ght_clear_then_fill_random, GHashTable *, create_instance_ght, destroy_instance_ght, { + g_hash_table_remove_all(data); + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + g_hash_table_insert(data, (gpointer)(size_t)key, (gpointer)(size_t)i); + } +}) + +MEASUREMENT(ght_find_random_keys, GHashTable *, create_instance_ght_random_values, destroy_instance_ght, { + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_u32s, uint32_t, i); + dn_simdhash_assert(g_hash_table_lookup(data, (gpointer)(size_t)key) == (gpointer)(size_t)i); + } +}) + +MEASUREMENT(ght_find_missing_key, GHashTable *, create_instance_ght_random_values, destroy_instance_ght, { + for (int i = 0; i < INNER_COUNT; i++) { + uint32_t key = *dn_vector_index_t(random_unused_u32s, uint32_t, i); + dn_simdhash_assert(g_hash_table_lookup(data, (gpointer)(size_t)key) == NULL); + } +}) diff --git a/src/native/containers/simdhash-benchmark/benchmark.c b/src/native/containers/simdhash-benchmark/benchmark.c new file mode 100644 index 00000000000000..320e43389cbeda --- /dev/null +++ b/src/native/containers/simdhash-benchmark/benchmark.c @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#include +#else +#include +#include +char *strcasestr(const char *haystack, const char *needle); +#endif + +#include "../dn-vector.h" +#include "../dn-simdhash.h" +#include "../dn-simdhash-utils.h" +#include "../dn-simdhash-specializations.h" + +#include "measurement.h" + +dn_simdhash_string_ptr_t *all_measurements; + +#undef MEASUREMENT +#define MEASUREMENT(name, data_type, setup, teardown, body) \ + extern measurement_info DN_SIMDHASH_GLUE(name, _measurement_info); + +// Suppress actual codegen +#define MEASUREMENTS_IMPLEMENTATION 0 + +#include "all-measurements.h" + +void +dn_simdhash_assert_fail (const char *file, int line, const char *condition) { + fprintf(stderr, "simdhash assertion failed at %s:%i:\n%s\n", file, line, condition); + fflush(stderr); + abort(); +} + +#define MTICKS_PER_SEC (10 * 1000 * 1000) + +int64_t get_100ns_ticks () { +#ifdef _MSC_VER + static LARGE_INTEGER freq; + static UINT64 start_time; + UINT64 cur_time; + LARGE_INTEGER value; + + if (!freq.QuadPart) { + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&value); + start_time = value.QuadPart; + } + QueryPerformanceCounter(&value); + cur_time = value.QuadPart; + return (int64_t)((cur_time - start_time) * (double)MTICKS_PER_SEC / freq.QuadPart); +#else + struct timespec ts; + // FIXME: Use clock_monotonic for wall time instead? I think process time is what we want +#ifdef __wasm + dn_simdhash_assert(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); +#else + dn_simdhash_assert(clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0); +#endif + return ((int64_t)ts.tv_sec * MTICKS_PER_SEC + ts.tv_nsec / 100); +#endif +} + +void init_measurements () { + if (!all_measurements) + all_measurements = dn_simdhash_string_ptr_new (0, NULL); + + #undef MEASUREMENT + #define MEASUREMENT(name, data_type, setup, teardown, body) \ + dn_simdhash_string_ptr_try_add(all_measurements, #name, &DN_SIMDHASH_GLUE(name, _measurement_info)); + + #include "all-measurements.h" +} + +int64_t run_measurement (int iteration_count, setup_func setup, measurement_func measurement, measurement_func teardown) { + void *data = NULL; + if (setup) + data = setup(); + + int64_t started = get_100ns_ticks(); + for (int i = 0; i < iteration_count; i++) + measurement(data); + int64_t ended = get_100ns_ticks(); + + if (teardown) + teardown(data); + + return ended - started; +} + +typedef struct { + int argc; + char **argv; + int result; +} main_args; + +void foreach_measurement (const char *name, void *_info, void *_args) { + measurement_info *info = _info; + main_args *args = _args; + + uint8_t match = args->argc <= 1; + for (int i = 1; i < args->argc; i++) { +#ifdef _MSC_VER + if (strstr(name, args->argv[i])) { +#else + if (strcasestr(name, args->argv[i])) { +#endif + match = 1; + break; + } + } + + if (!match) + return; + + printf("%s: ", name); + fflush(stdout); + + run_measurement(100, info->setup, info->func, info->teardown); + + int64_t overhead = run_measurement(1, info->setup, info->func, info->teardown); + + int64_t warmup_duration = 20000000, + target_step_duration = 10000000, + target_duration = warmup_duration * 10, + warmup_iterations = 500, + warmup_until = get_100ns_ticks() + warmup_duration, + warmup_elapsed_total = 0, + warmup_count = 0; + + do { + warmup_elapsed_total += run_measurement(warmup_iterations, info->setup, info->func, info->teardown) - overhead; + warmup_count++; + } while (get_100ns_ticks() < warmup_until); + + int64_t average_warmup_duration = warmup_elapsed_total / warmup_count, + necessary_iterations = (target_step_duration * warmup_iterations / average_warmup_duration), + steps = 0, + run_elapsed_total = 0, + run_elapsed_min = INT64_MAX, + run_elapsed_max = INT64_MIN, + run_until = get_100ns_ticks() + target_duration; + + if (necessary_iterations < 16) + necessary_iterations = 16; + // HACK: Reduce minor variation in iteration count + necessary_iterations = next_power_of_two((uint32_t)necessary_iterations); + + printf( + "Warmed %" PRId64 " time(s). Running %" PRId64 " iterations... ", + warmup_count, necessary_iterations + ); + fflush(stdout); + + do { + int64_t step_duration = run_measurement(necessary_iterations, info->setup, info->func, info->teardown) - overhead; + run_elapsed_total += step_duration; + if (step_duration < run_elapsed_min) + run_elapsed_min = step_duration; + if (step_duration > run_elapsed_max) + run_elapsed_max = step_duration; + steps++; + } while (get_100ns_ticks() < run_until); + + double run_elapsed_average = (double)(run_elapsed_total) / steps / necessary_iterations / 100.0; + + args->result = 0; + printf( + "%" PRId64 " step(s): avg %.3fns min %.3fns max %.3fns\n", + steps, + run_elapsed_average, + (double)run_elapsed_min / necessary_iterations / 100.0, + (double)run_elapsed_max / necessary_iterations / 100.0 + ); + fflush(stdout); +} + +int main (int argc, char* argv[]) { + init_measurements(); + + main_args args = { + argc, argv, 1 + }; + dn_simdhash_string_ptr_foreach(all_measurements, foreach_measurement, &args); + + fflush(stdout); + fflush(stderr); + + switch (args.result) { + case 0: + break; + case 1: + // no benchmarks run + fprintf(stderr, "No benchmarks run. List of all benchmarks follows:\n"); + break; + default: + fprintf(stderr, "Unknown failure!\n"); + break; + } + + return args.result; +} diff --git a/src/native/containers/simdhash-benchmark/ghashtable.c b/src/native/containers/simdhash-benchmark/ghashtable.c new file mode 100644 index 00000000000000..6e3f24eb77e817 --- /dev/null +++ b/src/native/containers/simdhash-benchmark/ghashtable.c @@ -0,0 +1,606 @@ +/* + * ghashtable.c: Hashtable implementation + * Modified for simdhash benchmarking + * + * Author: + * Miguel de Icaza (miguel@novell.com) + * + * (C) 2006 Novell, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ghashtable.h" +#include + +typedef struct _Slot Slot; + +struct _Slot { + gpointer key; + gpointer value; + Slot *next; +}; + +static gpointer KEYMARKER_REMOVED = &KEYMARKER_REMOVED; + +struct _GHashTable { + GHashFunc hash_func; + GEqualFunc key_equal_func; + + Slot **table; + int table_size; + int in_use; + int threshold; + int last_rehash; + GDestroyNotify value_destroy_func, key_destroy_func; +}; + +typedef struct { + GHashTable *ht; + int slot_index; + Slot *slot; +} Iter; + +static const guint prime_tbl[] = { + 11, 19, 37, 73, 109, 163, 251, 367, 557, 823, 1237, + 1861, 2777, 4177, 6247, 9371, 14057, 21089, 31627, + 47431, 71143, 106721, 160073, 240101, 360163, + 540217, 810343, 1215497, 1823231, 2734867, 4102283, + 6153409, 9230113, 13845163 +}; + +static gboolean +test_prime (int x) +{ + if ((x & 1) != 0) { + int n; + for (n = 3; n< (int)sqrt (x); n += 2) { + if ((x % n) == 0) + return FALSE; + } + return TRUE; + } + // There is only one even prime - 2. + return (x == 2); +} + +static int +calc_prime (int x) +{ + int i; + + for (i = (x & (~1))-1; i< G_MAXINT32; i += 2) { + if (test_prime (i)) + return i; + } + return x; +} + +guint +g_spaced_primes_closest (guint x) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (prime_tbl); i++) { + if (x <= prime_tbl [i]) + return prime_tbl [i]; + } + return calc_prime (x); +} + +GHashTable * +g_hash_table_new (GHashFunc hash_func, GEqualFunc key_equal_func) +{ + GHashTable *hash; + + if (hash_func == NULL) + hash_func = g_direct_hash; + if (key_equal_func == NULL) + key_equal_func = g_direct_equal; + hash = g_new0 (GHashTable, 1); + + hash->hash_func = hash_func; + hash->key_equal_func = key_equal_func; + + hash->table_size = g_spaced_primes_closest (1); + hash->table = g_new0 (Slot *, hash->table_size); + hash->last_rehash = hash->table_size; + + return hash; +} + +GHashTable * +g_hash_table_new_full (GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func) +{ + GHashTable *hash = g_hash_table_new (hash_func, key_equal_func); + if (hash == NULL) + return NULL; + + hash->key_destroy_func = key_destroy_func; + hash->value_destroy_func = value_destroy_func; + + return hash; +} + +#define sanity_check(HASH) do {}while(0) + +static void +do_rehash (GHashTable *hash) +{ + int current_size, i; + Slot **table; + + /* printf ("Resizing diff=%d slots=%d\n", hash->in_use - hash->last_rehash, hash->table_size); */ + hash->last_rehash = hash->table_size; + current_size = hash->table_size; + hash->table_size = g_spaced_primes_closest (hash->in_use); + /* printf ("New size: %d\n", hash->table_size); */ + table = hash->table; + hash->table = g_new0 (Slot *, hash->table_size); + + for (i = 0; i < current_size; i++){ + Slot *s, *next; + + for (s = table [i]; s != NULL; s = next){ + guint hashcode = ((*hash->hash_func) (s->key)) % hash->table_size; + next = s->next; + + s->next = hash->table [hashcode]; + hash->table [hashcode] = s; + } + } + g_free (table); +} + +static void +rehash (GHashTable *hash) +{ + int diff = ABS (hash->last_rehash - hash->in_use); + + /* These are the factors to play with to change the rehashing strategy */ + /* I played with them with a large range, and could not really get */ + /* something that was too good, maybe the tests are not that great */ + if (!(diff * 0.75 > hash->table_size * 2)) + return; + do_rehash (hash); + sanity_check (hash); +} + +void +g_hash_table_insert_replace (GHashTable *hash, gpointer key, gpointer value, gboolean replace) +{ + guint hashcode; + Slot *s; + GEqualFunc equal; + + g_return_if_fail (hash != NULL); + sanity_check (hash); + + equal = hash->key_equal_func; + if (hash->in_use >= hash->threshold) + rehash (hash); + + hashcode = ((*hash->hash_func) (key)) % hash->table_size; + for (s = hash->table [hashcode]; s != NULL; s = s->next){ + if ((*equal) (s->key, key)){ + if (replace){ + if (hash->key_destroy_func != NULL) + (*hash->key_destroy_func)(s->key); + s->key = key; + } + if (hash->value_destroy_func != NULL) + (*hash->value_destroy_func) (s->value); + s->value = value; + sanity_check (hash); + return; + } + } + s = g_new (Slot, 1); + s->key = key; + s->value = value; + s->next = hash->table [hashcode]; + hash->table [hashcode] = s; + hash->in_use++; + sanity_check (hash); +} + +guint +g_hash_table_size (GHashTable *hash) +{ + g_return_val_if_fail (hash != NULL, 0); + + return hash->in_use; +} + +gboolean +g_hash_table_contains (GHashTable *hash, gconstpointer key) +{ + g_return_val_if_fail (key != NULL, FALSE); + + return g_hash_table_lookup_extended (hash, key, NULL, NULL); +} + +gpointer +g_hash_table_lookup (GHashTable *hash, gconstpointer key) +{ + gpointer orig_key, value; + + if (g_hash_table_lookup_extended (hash, key, &orig_key, &value)) + return value; + else + return NULL; +} + +gboolean +g_hash_table_lookup_extended (GHashTable *hash, gconstpointer key, gpointer *orig_key, gpointer *value) +{ + GEqualFunc equal; + Slot *s; + guint hashcode; + + g_return_val_if_fail (hash != NULL, FALSE); + sanity_check (hash); + equal = hash->key_equal_func; + + hashcode = ((*hash->hash_func) (key)) % hash->table_size; + + for (s = hash->table [hashcode]; s != NULL; s = s->next){ + if ((*equal)(s->key, key)){ + if (orig_key) + *orig_key = s->key; + if (value) + *value = s->value; + return TRUE; + } + } + return FALSE; +} + +void +g_hash_table_foreach (GHashTable *hash, GHFunc func, gpointer user_data) +{ + int i; + + g_return_if_fail (hash != NULL); + g_return_if_fail (func != NULL); + + for (i = 0; i < hash->table_size; i++){ + Slot *s; + + for (s = hash->table [i]; s != NULL; s = s->next) + (*func)(s->key, s->value, user_data); + } +} + +gpointer +g_hash_table_find (GHashTable *hash, GHRFunc predicate, gpointer user_data) +{ + int i; + + g_return_val_if_fail (hash != NULL, NULL); + g_return_val_if_fail (predicate != NULL, NULL); + + for (i = 0; i < hash->table_size; i++){ + Slot *s; + + for (s = hash->table [i]; s != NULL; s = s->next) + if ((*predicate)(s->key, s->value, user_data)) + return s->value; + } + return NULL; +} + +void +g_hash_table_remove_all (GHashTable *hash) +{ + int i; + + g_return_if_fail (hash != NULL); + + for (i = 0; i < hash->table_size; i++){ + Slot *s; + + while (hash->table [i]) { + s = hash->table [i]; + g_hash_table_remove (hash, s->key); + } + } +} + +gboolean +g_hash_table_remove (GHashTable *hash, gconstpointer key) +{ + GEqualFunc equal; + Slot *s, *last; + guint hashcode; + + g_return_val_if_fail (hash != NULL, FALSE); + sanity_check (hash); + equal = hash->key_equal_func; + + hashcode = ((*hash->hash_func)(key)) % hash->table_size; + last = NULL; + for (s = hash->table [hashcode]; s != NULL; s = s->next){ + if ((*equal)(s->key, key)){ + if (hash->key_destroy_func != NULL) + (*hash->key_destroy_func)(s->key); + if (hash->value_destroy_func != NULL) + (*hash->value_destroy_func)(s->value); + if (last == NULL) + hash->table [hashcode] = s->next; + else + last->next = s->next; + g_free (s); + hash->in_use--; + sanity_check (hash); + return TRUE; + } + last = s; + } + sanity_check (hash); + return FALSE; +} + +guint +g_hash_table_foreach_remove (GHashTable *hash, GHRFunc func, gpointer user_data) +{ + int i; + int count = 0; + + g_return_val_if_fail (hash != NULL, 0); + g_return_val_if_fail (func != NULL, 0); + + sanity_check (hash); + for (i = 0; i < hash->table_size; i++){ + Slot *s, *last; + + last = NULL; + for (s = hash->table [i]; s != NULL; ){ + if ((*func)(s->key, s->value, user_data)){ + Slot *n; + + if (hash->key_destroy_func != NULL) + (*hash->key_destroy_func)(s->key); + if (hash->value_destroy_func != NULL) + (*hash->value_destroy_func)(s->value); + if (last == NULL){ + hash->table [i] = s->next; + n = s->next; + } else { + last->next = s->next; + n = last->next; + } + g_free (s); + hash->in_use--; + count++; + s = n; + } else { + last = s; + s = s->next; + } + } + } + sanity_check (hash); + if (count > 0) + rehash (hash); + return count; +} + +gboolean +g_hash_table_steal (GHashTable *hash, gconstpointer key) +{ + GEqualFunc equal; + Slot *s, *last; + guint hashcode; + + g_return_val_if_fail (hash != NULL, FALSE); + sanity_check (hash); + equal = hash->key_equal_func; + + hashcode = ((*hash->hash_func)(key)) % hash->table_size; + last = NULL; + for (s = hash->table [hashcode]; s != NULL; s = s->next){ + if ((*equal)(s->key, key)) { + if (last == NULL) + hash->table [hashcode] = s->next; + else + last->next = s->next; + g_free (s); + hash->in_use--; + sanity_check (hash); + return TRUE; + } + last = s; + } + sanity_check (hash); + return FALSE; + +} + +guint +g_hash_table_foreach_steal (GHashTable *hash, GHRFunc func, gpointer user_data) +{ + int i; + int count = 0; + + g_return_val_if_fail (hash != NULL, 0); + g_return_val_if_fail (func != NULL, 0); + + sanity_check (hash); + for (i = 0; i < hash->table_size; i++){ + Slot *s, *last; + + last = NULL; + for (s = hash->table [i]; s != NULL; ){ + if ((*func)(s->key, s->value, user_data)){ + Slot *n; + + if (last == NULL){ + hash->table [i] = s->next; + n = s->next; + } else { + last->next = s->next; + n = last->next; + } + g_free (s); + hash->in_use--; + count++; + s = n; + } else { + last = s; + s = s->next; + } + } + } + sanity_check (hash); + if (count > 0) + rehash (hash); + return count; +} + +void +g_hash_table_destroy (GHashTable *hash) +{ + int i; + + if (!hash) + return; + + for (i = 0; i < hash->table_size; i++){ + Slot *s, *next; + + for (s = hash->table [i]; s != NULL; s = next){ + next = s->next; + + if (hash->key_destroy_func != NULL) + (*hash->key_destroy_func)(s->key); + if (hash->value_destroy_func != NULL) + (*hash->value_destroy_func)(s->value); + g_free (s); + } + } + g_free (hash->table); + + g_free (hash); +} + +void +g_hash_table_print_stats (GHashTable *table) +{ + int i, max_chain_index, chain_size, max_chain_size; + Slot *node; + + max_chain_size = 0; + max_chain_index = -1; + for (i = 0; i < table->table_size; i++) { + chain_size = 0; + for (node = table->table [i]; node; node = node->next) + chain_size ++; + if (chain_size > max_chain_size) { + max_chain_size = chain_size; + max_chain_index = i; + } + } + + printf ("Size: %d Table Size: %d Max Chain Length: %d at %d\n", table->in_use, table->table_size, max_chain_size, max_chain_index); +} + +void +g_hash_table_iter_init (GHashTableIter *it, GHashTable *hash_table) +{ + Iter *iter = (Iter*)it; + + memset (iter, 0, sizeof (Iter)); + iter->ht = hash_table; + iter->slot_index = -1; +} + +gboolean g_hash_table_iter_next (GHashTableIter *it, gpointer *key, gpointer *value) +{ + Iter *iter = (Iter*)it; + + GHashTable *hash = iter->ht; + + g_assert (iter->slot_index != -2); + g_assert (sizeof (Iter) <= sizeof (GHashTableIter)); + + if (!iter->slot) { + while (TRUE) { + iter->slot_index ++; + if (iter->slot_index >= hash->table_size) { + iter->slot_index = -2; + return FALSE; + } + if (hash->table [iter->slot_index]) + break; + } + iter->slot = hash->table [iter->slot_index]; + } + + if (key) + *key = iter->slot->key; + if (value) + *value = iter->slot->value; + iter->slot = iter->slot->next; + + return TRUE; +} + +gboolean +g_direct_equal (gconstpointer v1, gconstpointer v2) +{ + return v1 == v2; +} + +guint +g_direct_hash (gconstpointer v1) +{ + return GCONSTPOINTER_TO_UINT (v1); +} + +gboolean +g_int_equal (gconstpointer v1, gconstpointer v2) +{ + return *(gint *)v1 == *(gint *)v2; +} + +guint +g_int_hash (gconstpointer v1) +{ + return *(guint *)v1; +} + +gboolean +g_str_equal (gconstpointer v1, gconstpointer v2) +{ + return v1 == v2 || strcmp ((const char*)v1, (const char*)v2) == 0; +} + +guint +g_str_hash (gconstpointer v1) +{ + guint hash = 0; + unsigned char *p = (unsigned char *) v1; + + while (*p++) + hash = (hash << 5) - (hash + *p); + + return hash; +} diff --git a/src/native/containers/simdhash-benchmark/ghashtable.h b/src/native/containers/simdhash-benchmark/ghashtable.h new file mode 100644 index 00000000000000..076ddfde5e1c8f --- /dev/null +++ b/src/native/containers/simdhash-benchmark/ghashtable.h @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef GHASHTABLE_H +#define GHASHTABLE_H + +#include +#include +#include +#include + +/* + * Basic data types + */ +typedef int gint; +typedef unsigned int guint; +typedef short gshort; +typedef unsigned short gushort; +typedef long glong; +typedef unsigned long gulong; +typedef void * gpointer; +typedef const void * gconstpointer; +typedef char gchar; +typedef unsigned char guchar; + +/* Types defined in terms of the stdint.h */ +typedef int8_t gint8; +typedef uint8_t guint8; +typedef int16_t gint16; +typedef uint16_t guint16; +typedef int32_t gint32; +typedef uint32_t guint32; +typedef int64_t gint64; +typedef uint64_t guint64; +typedef float gfloat; +typedef double gdouble; +typedef int32_t gboolean; +// typedef ptrdiff_t gptrdiff; +typedef intptr_t gintptr; +typedef uintptr_t guintptr; + +#define G_N_ELEMENTS(arr) (sizeof(arr)/sizeof(arr[0])) + +#define FALSE 0 +#define TRUE 1 + +#define G_MINSHORT SHRT_MIN +#define G_MAXSHORT SHRT_MAX +#define G_MAXUSHORT USHRT_MAX +#define G_MAXINT INT_MAX +#define G_MININT INT_MIN +#define G_MAXINT8 INT8_MAX +#define G_MAXUINT8 UINT8_MAX +#define G_MININT8 INT8_MIN +#define G_MAXINT16 INT16_MAX +#define G_MAXUINT16 UINT16_MAX +#define G_MININT16 INT16_MIN +#define G_MAXINT32 INT32_MAX +#define G_MAXUINT32 UINT32_MAX +#define G_MININT32 INT32_MIN +#define G_MININT64 INT64_MIN +#define G_MAXINT64 INT64_MAX +#define G_MAXUINT64 UINT64_MAX + +#define G_LITTLE_ENDIAN 1234 +#define G_BIG_ENDIAN 4321 +#define G_STMT_START do +#define G_STMT_END while (0) + +typedef void (*GFunc) (gpointer data, gpointer user_data); +typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b); +typedef gint (*GCompareDataFunc) (gconstpointer a, gconstpointer b, gpointer user_data); +typedef void (*GHFunc) (gpointer key, gpointer value, gpointer user_data); +typedef gboolean (*GHRFunc) (gpointer key, gpointer value, gpointer user_data); +typedef void (*GDestroyNotify) (gpointer data); +typedef guint (*GHashFunc) (gconstpointer key); +typedef gboolean (*GEqualFunc) (gconstpointer a, gconstpointer b); +typedef void (*GFreeFunc) (gpointer data); + +/* + * Hashtables + */ +typedef struct _GHashTable GHashTable; +typedef struct _GHashTableIter GHashTableIter; + +/* Private, but needed for stack allocation */ +struct _GHashTableIter +{ + gpointer dummy [8]; +}; + +GHashTable *g_hash_table_new (GHashFunc hash_func, GEqualFunc key_equal_func); +GHashTable *g_hash_table_new_full (GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func); +void g_hash_table_insert_replace (GHashTable *hash, gpointer key, gpointer value, gboolean replace); +guint g_hash_table_size (GHashTable *hash); +gboolean g_hash_table_contains (GHashTable *hash, gconstpointer key); +gpointer g_hash_table_lookup (GHashTable *hash, gconstpointer key); +gboolean g_hash_table_lookup_extended (GHashTable *hash, gconstpointer key, gpointer *orig_key, gpointer *value); +void g_hash_table_foreach (GHashTable *hash, GHFunc func, gpointer user_data); +gpointer g_hash_table_find (GHashTable *hash, GHRFunc predicate, gpointer user_data); +gboolean g_hash_table_remove (GHashTable *hash, gconstpointer key); +gboolean g_hash_table_steal (GHashTable *hash, gconstpointer key); +void g_hash_table_remove_all (GHashTable *hash); +guint g_hash_table_foreach_remove (GHashTable *hash, GHRFunc func, gpointer user_data); +guint g_hash_table_foreach_steal (GHashTable *hash, GHRFunc func, gpointer user_data); +void g_hash_table_destroy (GHashTable *hash); +void g_hash_table_print_stats (GHashTable *table); + +void g_hash_table_iter_init (GHashTableIter *iter, GHashTable *hash_table); +gboolean g_hash_table_iter_next (GHashTableIter *iter, gpointer *key, gpointer *value); + +guint g_spaced_primes_closest (guint x); + +#define g_hash_table_insert(h,k,v) g_hash_table_insert_replace ((h),(k),(v),FALSE) +#define g_hash_table_replace(h,k,v) g_hash_table_insert_replace ((h),(k),(v),TRUE) +#define g_hash_table_add(h,k) g_hash_table_insert_replace ((h),(k),(k),TRUE) + +gboolean g_direct_equal (gconstpointer v1, gconstpointer v2); +guint g_direct_hash (gconstpointer v1); +gboolean g_int_equal (gconstpointer v1, gconstpointer v2); +guint g_int_hash (gconstpointer v1); +gboolean g_str_equal (gconstpointer v1, gconstpointer v2); +guint g_str_hash (gconstpointer v1); + +#define GCONSTPOINTER_TO_INT(v) (gint)((ssize_t)v) +#define GCONSTPOINTER_TO_UINT(v) (gint)((size_t)v) + +// FIXME +#define g_assert(expr) (void)(expr) + +#define g_malloc malloc +#define g_free free +#define g_realloc realloc + +#define g_new(type,size) ((type *) g_malloc (sizeof (type) * (size))) +#define g_new0(type,size) ((type *) g_malloc0 (sizeof (type)* (size))) +#define g_newa(type,size) ((type *) alloca (sizeof (type) * (size))) +#define g_newa0(type,size) ((type *) memset (alloca (sizeof (type) * (size)), 0, sizeof (type) * (size))) + +#define g_memmove(dest,src,len) memmove (dest, src, len) +#define g_renew(struct_type, mem, n_structs) ((struct_type*)g_realloc (mem, sizeof (struct_type) * n_structs)) +#define g_alloca(size) (g_cast (alloca (size))) + +#define g_return_if_fail(x) if (!(x)) { return; } +#define g_return_val_if_fail(x,e) if (!(x)) { return (e); } + +static inline void * +g_malloc0 (size_t size) { + void * result = malloc(size); + memset(result, 0, size); + return result; +} + +#define ABS(x) abs(x) + +#endif // GHASHTABLE_H diff --git a/src/native/containers/simdhash-benchmark/measurement.h b/src/native/containers/simdhash-benchmark/measurement.h new file mode 100644 index 00000000000000..368253a357df0e --- /dev/null +++ b/src/native/containers/simdhash-benchmark/measurement.h @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +typedef void * (*setup_func) (void); +typedef void (*measurement_func) (void *data); + +typedef struct { + const char *name; + setup_func setup; + measurement_func func, teardown; +} measurement_info; diff --git a/src/native/containers/simdhash-benchmark/run-benchmark.ps1 b/src/native/containers/simdhash-benchmark/run-benchmark.ps1 new file mode 100755 index 00000000000000..536ba40cd9144f --- /dev/null +++ b/src/native/containers/simdhash-benchmark/run-benchmark.ps1 @@ -0,0 +1,2 @@ +cl /GS- /O2 /std:c17 ./*.c ../dn-simdhash-u32-ptr.c ../dn-simdhash.c ../dn-vector.c ../dn-simdhash-string-ptr.c /DNO_CONFIG_H /DSIZEOF_VOID_P=8 /Fe:all-measurements.exe +./all-measurements.exe diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in index c4843bf8712df7..22b1d5713acf79 100644 --- a/src/native/libs/Common/pal_config.h.in +++ b/src/native/libs/Common/pal_config.h.in @@ -100,6 +100,7 @@ #cmakedefine01 HAVE_IOS_NET_IFMEDIA_H #cmakedefine01 HAVE_LINUX_RTNETLINK_H #cmakedefine01 HAVE_LINUX_CAN_H +#cmakedefine01 HAVE_LINUX_ERRQUEUE_H #cmakedefine01 HAVE_GETDOMAINNAME_SIZET #cmakedefine01 HAVE_INOTIFY #cmakedefine01 HAVE_CLOCK_MONOTONIC diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index ee842ee2b73648..30cc86f2aff976 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -160,6 +160,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_SetSendTimeout) DllImportEntry(SystemNative_Receive) DllImportEntry(SystemNative_ReceiveMessage) + DllImportEntry(SystemNative_ReceiveSocketError) DllImportEntry(SystemNative_Send) DllImportEntry(SystemNative_SendMessage) DllImportEntry(SystemNative_Accept) diff --git a/src/native/libs/System.Native/pal_networking.c b/src/native/libs/System.Native/pal_networking.c index ee8c9a89e28acb..f8916702ef48d5 100644 --- a/src/native/libs/System.Native/pal_networking.c +++ b/src/native/libs/System.Native/pal_networking.c @@ -62,6 +62,10 @@ #if HAVE_SYS_FILIO_H #include #endif +#if HAVE_LINUX_ERRQUEUE_H +#include +#endif + #if HAVE_KQUEUE #if KEVENT_HAS_VOID_UDATA @@ -1325,7 +1329,11 @@ int32_t SystemNative_SetSendTimeout(intptr_t socket, int32_t millisecondsTimeout static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFlags) { - const int32_t SupportedFlagsMask = SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC; + const int32_t SupportedFlagsMask = +#ifdef MSG_ERRQUEUE + SocketFlags_MSG_ERRQUEUE | +#endif + SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC | SocketFlags_MSG_DONTWAIT; if ((palFlags & ~SupportedFlagsMask) != 0) { @@ -1335,9 +1343,15 @@ static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFla *platformFlags = ((palFlags & SocketFlags_MSG_OOB) == 0 ? 0 : MSG_OOB) | ((palFlags & SocketFlags_MSG_PEEK) == 0 ? 0 : MSG_PEEK) | ((palFlags & SocketFlags_MSG_DONTROUTE) == 0 ? 0 : MSG_DONTROUTE) | + ((palFlags & SocketFlags_MSG_DONTWAIT) == 0 ? 0 : MSG_DONTWAIT) | ((palFlags & SocketFlags_MSG_TRUNC) == 0 ? 0 : MSG_TRUNC) | ((palFlags & SocketFlags_MSG_CTRUNC) == 0 ? 0 : MSG_CTRUNC); - +#ifdef MSG_ERRQUEUE + if ((palFlags & SocketFlags_MSG_ERRQUEUE) != 0) + { + *platformFlags |= MSG_ERRQUEUE; + } +#endif return true; } @@ -1381,6 +1395,51 @@ int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bufferLen, i return SystemNative_ConvertErrorPlatformToPal(errno); } +int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader) +{ + int fd = ToFileDescriptor(socket); + ssize_t res; + +#if HAVE_LINUX_ERRQUEUE_H + char buffer[sizeof(struct sock_extended_err) + sizeof(struct sockaddr_storage)]; + messageHeader->ControlBufferLen = sizeof(buffer); + messageHeader->ControlBuffer = (void*)buffer; + + struct msghdr header; + ConvertMessageHeaderToMsghdr(&header, messageHeader, fd); + + while ((res = recvmsg(fd, &header, SocketFlags_MSG_DONTWAIT | SocketFlags_MSG_ERRQUEUE)) < 0 && errno == EINTR); + + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(&header); cmsg; cmsg = GET_CMSG_NXTHDR(&header, cmsg)) + { + if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) + { + struct sock_extended_err *e = (struct sock_extended_err *)CMSG_DATA(cmsg); + if (e->ee_origin == SO_EE_ORIGIN_ICMP) + { + int size = (int)(cmsg->cmsg_len - sizeof(struct sock_extended_err)); + messageHeader->SocketAddressLen = size < messageHeader->SocketAddressLen ? size : messageHeader->SocketAddressLen; + memcpy(messageHeader->SocketAddress, (struct sockaddr_in*)(e+1), (size_t)messageHeader->SocketAddressLen); + return Error_SUCCESS; + } + } + } +#else + res = -1; + errno = ENOTSUP; +#endif + + messageHeader->SocketAddressLen = 0; + + if (res != -1) + { + return Error_SUCCESS; + } + + return SystemNative_ConvertErrorPlatformToPal(errno); +} + int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received) { if (messageHeader == NULL || received == NULL || messageHeader->SocketAddressLen < 0 || diff --git a/src/native/libs/System.Native/pal_networking.h b/src/native/libs/System.Native/pal_networking.h index 0a46f1490aab96..5dfe1c1c54df10 100644 --- a/src/native/libs/System.Native/pal_networking.h +++ b/src/native/libs/System.Native/pal_networking.h @@ -206,6 +206,8 @@ typedef enum SocketFlags_MSG_DONTROUTE = 0x0004, // SocketFlags.DontRoute SocketFlags_MSG_TRUNC = 0x0100, // SocketFlags.Truncated SocketFlags_MSG_CTRUNC = 0x0200, // SocketFlags.ControlDataTruncated + SocketFlags_MSG_DONTWAIT = 0x1000, // used privately by Ping + SocketFlags_MSG_ERRQUEUE = 0x2000, // used privately by Ping } SocketFlags; /* @@ -356,6 +358,8 @@ PALEXPORT int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bu PALEXPORT int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received); +PALEXPORT int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader); + PALEXPORT int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* sent); PALEXPORT int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* sent); diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake index 42502a34b4ef24..55e794b42769be 100644 --- a/src/native/libs/configure.cmake +++ b/src/native/libs/configure.cmake @@ -500,6 +500,10 @@ check_include_files( "sys/proc_info.h" HAVE_SYS_PROCINFO_H) +check_include_files( + "time.h;linux/errqueue.h" + HAVE_LINUX_ERRQUEUE_H) + check_symbol_exists( epoll_create1 sys/epoll.h diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index b6c71b671a6eda..505dab92815db5 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -9,8 +9,20 @@ extern "C" { #endif -int cdac_reader_init(intptr_t descriptor, intptr_t* handle); +// Initialize the cDAC reader +// descriptor: the address of the descriptor in the target process +// read_from_target: a callback that reads memory from the target process +// read_context: a context pointer that will be passed to read_from_target +// handle: returned opaque the handle to the reader. This should be passed to other functions in this API. +int cdac_reader_init(uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), void* read_context, /*out*/ intptr_t* handle); + +// Free the cDAC reader +// handle: handle to the reader int cdac_reader_free(intptr_t handle); + +// Get the SOS interface from the cDAC reader +// handle: handle to the reader +// obj: returned SOS interface that can be QI'd to ISOSDacInterface* int cdac_reader_get_sos_interface(intptr_t handle, IUnknown** obj); #ifdef __cplusplus diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index a65ba9c5fa5ea8..30cab9ec851887 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -12,9 +12,9 @@ internal static class Entrypoints private const string CDAC = "cdac_reader_"; [UnmanagedCallersOnly(EntryPoint = $"{CDAC}init")] - private static unsafe int Init(nint descriptor, IntPtr* handle) + private static unsafe int Init(ulong descriptor, delegate* unmanaged readFromTarget, void* readContext, IntPtr* handle) { - Target target = new(descriptor); + Target target = new(descriptor, readFromTarget, readContext); GCHandle gcHandle = GCHandle.Alloc(target); *handle = GCHandle.ToIntPtr(gcHandle); return 0; @@ -42,7 +42,7 @@ private static unsafe int GetSOSInterface(IntPtr handle, nint* obj) if (target == null) return -1; - SOSDacImpl impl = new(target); + Legacy.SOSDacImpl impl = new(target); nint ptr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); *obj = ptr; return 0; diff --git a/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs new file mode 100644 index 00000000000000..bd0d20e65b77c0 --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/ISOSDacInterface.cs @@ -0,0 +1,293 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +// This file contains managed declarations for the SOS-DAC interfaces. +// See src/coreclr/inc/sospriv.idl + +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +internal struct DacpThreadStoreData +{ + public int threadCount; + public int unstartedThreadCount; + public int backgroundThreadCount; + public int pendingThreadCount; + public int deadThreadCount; + public ulong firstThread; + public ulong finalizerThread; + public ulong gcThread; + public int fHostConfig; // Uses hosting flags defined above +}; + +internal struct DacpThreadData +{ + public int corThreadId; + public int osThreadId; + public int state; + public uint preemptiveGCDisabled; + public ulong allocContextPtr; + public ulong allocContextLimit; + public ulong context; + public ulong domain; + public ulong pFrame; + public int lockCount; + public ulong firstNestedException; // Pass this pointer to DacpNestedExceptionInfo + public ulong teb; + public ulong fiberData; + public ulong lastThrownObjectHandle; + public ulong nextThread; +} +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value + +[GeneratedComInterface] +[Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] +internal unsafe partial interface ISOSDacInterface +{ + // All functions are explicitly PreserveSig so that we can just return E_NOTIMPL instead of throwing + // as the cDAC slowly replaces parts of the DAC. + + // ThreadStore + [PreserveSig] + int GetThreadStoreData(DacpThreadStoreData* data); + + // AppDomains + [PreserveSig] + int GetAppDomainStoreData(/*struct DacpAppDomainStoreData*/ void* data); + [PreserveSig] + int GetAppDomainList(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ulong[] values, uint* pNeeded); + [PreserveSig] + int GetAppDomainData(ulong addr, /*struct DacpAppDomainData*/ void* data); + [PreserveSig] + int GetAppDomainName(ulong addr, uint count, char* name, uint* pNeeded); + [PreserveSig] + int GetDomainFromContext(ulong context, ulong* domain); + + // Assemblies + [PreserveSig] + int GetAssemblyList(ulong appDomain, int count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ulong[] values, int* pNeeded); + [PreserveSig] + int GetAssemblyData(ulong baseDomainPtr, ulong assembly, /*struct DacpAssemblyData*/ void* data); + [PreserveSig] + int GetAssemblyName(ulong assembly, uint count, char* name, uint* pNeeded); + + // Modules + [PreserveSig] + int GetModule(ulong addr, /*IXCLRDataModule*/ void** mod); + [PreserveSig] + int GetModuleData(ulong moduleAddr, /*struct DacpModuleData*/ void* data); + [PreserveSig] + int TraverseModuleMap(/*ModuleMapType*/ int mmt, ulong moduleAddr, /*MODULEMAPTRAVERSE*/ void* pCallback, void* token); + [PreserveSig] + int GetAssemblyModuleList(ulong assembly, uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ulong[] modules, uint* pNeeded); + [PreserveSig] + int GetILForModule(ulong moduleAddr, int rva, ulong* il); + + // Threads + [PreserveSig] + int GetThreadData(ulong thread, DacpThreadData *data); + [PreserveSig] + int GetThreadFromThinlockID(uint thinLockId, ulong* pThread); + [PreserveSig] + int GetStackLimits(ulong threadPtr, ulong* lower, ulong* upper, ulong* fp); + + // MethodDescs + [PreserveSig] + int GetMethodDescData(ulong methodDesc, ulong ip, /*struct DacpMethodDescData*/ void* data, uint cRevertedRejitVersions, /*struct DacpReJitData*/ void* rgRevertedRejitData, uint* pcNeededRevertedRejitData); + [PreserveSig] + int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD); + [PreserveSig] + int GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded); + [PreserveSig] + int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD); + [PreserveSig] + int GetMethodDescFromToken(ulong moduleAddr, /*mdToken*/ uint token, ulong* methodDesc); + [PreserveSig] + int GetMethodDescTransparencyData(ulong methodDesc, /*struct DacpMethodDescTransparencyData*/ void* data); + + // JIT Data + [PreserveSig] + int GetCodeHeaderData(ulong ip, /*struct DacpCodeHeaderData*/ void* data); + [PreserveSig] + int GetJitManagerList(uint count, /*struct DacpJitManagerInfo*/ void* managers, uint* pNeeded); + [PreserveSig] + int GetJitHelperFunctionName(ulong ip, uint count, byte* name, uint* pNeeded); + [PreserveSig] + int GetJumpThunkTarget(/*T_CONTEXT*/void* ctx, ulong* targetIP, ulong* targetMD); + + // ThreadPool + [PreserveSig] + int GetThreadpoolData(/*struct DacpThreadpoolData*/ void* data); + [PreserveSig] + int GetWorkRequestData(ulong addrWorkRequest, /*struct DacpWorkRequestData*/ void* data); + [PreserveSig] + int GetHillClimbingLogEntry(ulong addr, /*struct DacpHillClimbingLogEntry*/ void* data); + + // Objects + [PreserveSig] + int GetObjectData(ulong objAddr, /*struct DacpObjectData*/ void* data); + [PreserveSig] + int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded); + [PreserveSig] + int GetObjectClassName(ulong obj, uint count, char* className, uint* pNeeded); + + // MethodTable + [PreserveSig] + int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded); + [PreserveSig] + int GetMethodTableData(ulong mt, /*struct DacpMethodTableData*/ void* data); + [PreserveSig] + int GetMethodTableSlot(ulong mt, uint slot, ulong* value); + [PreserveSig] + int GetMethodTableFieldData(ulong mt, /*struct DacpMethodTableFieldData*/ void* data); + [PreserveSig] + int GetMethodTableTransparencyData(ulong mt, /*struct DacpMethodTableTransparencyData*/ void* data); + + // EEClass + [PreserveSig] + int GetMethodTableForEEClass(ulong eeClass, ulong* value); + + // FieldDesc + [PreserveSig] + int GetFieldDescData(ulong fieldDesc, /*struct DacpFieldDescData*/ void* data); + + // Frames + [PreserveSig] + int GetFrameName(ulong vtable, uint count, char* frameName, uint* pNeeded); + + // PEFiles + [PreserveSig] + int GetPEFileBase(ulong addr, ulong* peBase); + [PreserveSig] + int GetPEFileName(ulong addr, uint count, char* fileName, uint* pNeeded); + + // GC + [PreserveSig] + int GetGCHeapData(/*struct DacpGcHeapData*/ void* data); + [PreserveSig] + int GetGCHeapList(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ulong[] heaps, uint* pNeeded); // svr only + [PreserveSig] + int GetGCHeapDetails(ulong heap, /*struct DacpGcHeapDetails */ void* details); // wks only + [PreserveSig] + int GetGCHeapStaticData(/*struct DacpGcHeapDetails */ void* data); + [PreserveSig] + int GetHeapSegmentData(ulong seg, /*struct DacpHeapSegmentData */ void* data); + [PreserveSig] + int GetOOMData(ulong oomAddr, /*struct DacpOomData */ void* data); + [PreserveSig] + int GetOOMStaticData(/*struct DacpOomData */ void* data); + [PreserveSig] + int GetHeapAnalyzeData(ulong addr, /*struct DacpGcHeapAnalyzeData */ void* data); + [PreserveSig] + int GetHeapAnalyzeStaticData(/*struct DacpGcHeapAnalyzeData */ void* data); + + // DomainLocal + [PreserveSig] + int GetDomainLocalModuleData(ulong addr, /*struct DacpDomainLocalModuleData */ void* data); + [PreserveSig] + int GetDomainLocalModuleDataFromAppDomain(ulong appDomainAddr, int moduleID, /*struct DacpDomainLocalModuleData */ void* data); + [PreserveSig] + int GetDomainLocalModuleDataFromModule(ulong moduleAddr, /*struct DacpDomainLocalModuleData */ void* data); + + // ThreadLocal + [PreserveSig] + int GetThreadLocalModuleData(ulong thread, uint index, /*struct DacpThreadLocalModuleData */ void* data); + + // SyncBlock + [PreserveSig] + int GetSyncBlockData(uint number, /*struct DacpSyncBlockData */ void* data); + [PreserveSig] + int GetSyncBlockCleanupData(ulong addr, /*struct DacpSyncBlockCleanupData */ void* data); + + // Handles + [PreserveSig] + int GetHandleEnum(/*ISOSHandleEnum*/ void** ppHandleEnum); + [PreserveSig] + int GetHandleEnumForTypes([In, MarshalUsing(CountElementName = nameof(count))] uint[] types, uint count, /*ISOSHandleEnum*/ void** ppHandleEnum); + [PreserveSig] + int GetHandleEnumForGC(uint gen, /*ISOSHandleEnum*/ void** ppHandleEnum); + + // EH + [PreserveSig] + int TraverseEHInfo(ulong ip, /*DUMPEHINFO*/ void* pCallback, void* token); + [PreserveSig] + int GetNestedExceptionData(ulong exception, ulong* exceptionObject, ulong* nextNestedException); + + // StressLog + [PreserveSig] + int GetStressLogAddress(ulong* stressLog); + + // Heaps + [PreserveSig] + int TraverseLoaderHeap(ulong loaderHeapAddr, /*VISITHEAP*/ void* pCallback); + [PreserveSig] + int GetCodeHeapList(ulong jitManager, uint count, /*struct DacpJitCodeHeapInfo*/ void* codeHeaps, uint* pNeeded); + [PreserveSig] + int TraverseVirtCallStubHeap(ulong pAppDomain, /*VCSHeapType*/ int heaptype, /*VISITHEAP*/ void* pCallback); + + // Other + [PreserveSig] + int GetUsefulGlobals(/*struct DacpUsefulGlobalsData */ void* data); + [PreserveSig] + int GetClrWatsonBuckets(ulong thread, void* pGenericModeBlock); + [PreserveSig] + int GetTLSIndex(uint* pIndex); + [PreserveSig] + int GetDacModuleHandle(/*HMODULE*/ void* phModule); + + // COM + [PreserveSig] + int GetRCWData(ulong addr, /*struct DacpRCWData */ void* data); + [PreserveSig] + int GetRCWInterfaces(ulong rcw, uint count, /*struct DacpCOMInterfacePointerData*/ void* interfaces, uint* pNeeded); + [PreserveSig] + int GetCCWData(ulong ccw, /*struct DacpCCWData */ void* data); + [PreserveSig] + int GetCCWInterfaces(ulong ccw, uint count, /*struct DacpCOMInterfacePointerData*/ void* interfaces, uint* pNeeded); + [PreserveSig] + int TraverseRCWCleanupList(ulong cleanupListPtr, /*VISITRCWFORCLEANUP*/ void* pCallback, void* token); + + // GC Reference Functions + + /* GetStackReferences + * Enumerates all references on a given callstack. + */ + [PreserveSig] + int GetStackReferences(int osThreadID, /*ISOSStackRefEnum*/ void** ppEnum); + [PreserveSig] + int GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded); + + [PreserveSig] + int GetThreadAllocData(ulong thread, /*struct DacpAllocData */ void* data); + [PreserveSig] + int GetHeapAllocData(uint count, /*struct DacpGenerationAllocData */ void* data, uint* pNeeded); + + // For BindingDisplay plugin + [PreserveSig] + int GetFailedAssemblyList(ulong appDomain, int count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ulong[] values, uint* pNeeded); + [PreserveSig] + int GetPrivateBinPaths(ulong appDomain, int count, char* paths, uint* pNeeded); + [PreserveSig] + int GetAssemblyLocation(ulong assembly, int count, char* location, uint* pNeeded); + [PreserveSig] + int GetAppDomainConfigFile(ulong appDomain, int count, char* configFile, uint* pNeeded); + [PreserveSig] + int GetApplicationBase(ulong appDomain, int count, char* appBase, uint* pNeeded); + [PreserveSig] + int GetFailedAssemblyData(ulong assembly, uint* pContext, int* pResult); + [PreserveSig] + int GetFailedAssemblyLocation(ulong assesmbly, uint count, char* location, uint* pNeeded); + [PreserveSig] + int GetFailedAssemblyDisplayName(ulong assembly, uint count, char* name, uint* pNeeded); +}; + +[GeneratedComInterface] +[Guid("4eca42d8-7e7b-4c8a-a116-7bfbf6929267")] +internal partial interface ISOSDacInterface9 +{ + int GetBreakingChangeVersion(); +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs new file mode 100644 index 00000000000000..b0640c44efc98d --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +/// +/// Implementation of ISOSDacInterface* interfaces intended to be passed out to consumers +/// interacting with the DAC via those COM interfaces. +/// +[GeneratedComClass] +internal sealed partial class SOSDacImpl : ISOSDacInterface, ISOSDacInterface9 +{ + private readonly Target _target; + + public SOSDacImpl(Target target) + { + _target = target; + } + + public unsafe int GetAppDomainConfigFile(ulong appDomain, int count, char* configFile, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAppDomainData(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetAppDomainList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ulong[] values, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAppDomainName(ulong addr, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAppDomainStoreData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetApplicationBase(ulong appDomain, int count, char* appBase, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAssemblyData(ulong baseDomainPtr, ulong assembly, void* data) => HResults.E_NOTIMPL; + public unsafe int GetAssemblyList(ulong appDomain, int count, [In, MarshalUsing(CountElementName = "count"), Out] ulong[] values, int* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAssemblyLocation(ulong assembly, int count, char* location, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAssemblyModuleList(ulong assembly, uint count, [In, MarshalUsing(CountElementName = "count"), Out] ulong[] modules, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetAssemblyName(ulong assembly, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; + + public int GetBreakingChangeVersion() + { + // TODO: Return non-hard-coded version + return 4; + } + + public unsafe int GetCCWData(ulong ccw, void* data) => HResults.E_NOTIMPL; + public unsafe int GetCCWInterfaces(ulong ccw, uint count, void* interfaces, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetClrWatsonBuckets(ulong thread, void* pGenericModeBlock) => HResults.E_NOTIMPL; + public unsafe int GetCodeHeaderData(ulong ip, void* data) => HResults.E_NOTIMPL; + public unsafe int GetCodeHeapList(ulong jitManager, uint count, void* codeHeaps, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetDacModuleHandle(void* phModule) => HResults.E_NOTIMPL; + public unsafe int GetDomainFromContext(ulong context, ulong* domain) => HResults.E_NOTIMPL; + public unsafe int GetDomainLocalModuleData(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetDomainLocalModuleDataFromAppDomain(ulong appDomainAddr, int moduleID, void* data) => HResults.E_NOTIMPL; + public unsafe int GetDomainLocalModuleDataFromModule(ulong moduleAddr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetFailedAssemblyData(ulong assembly, uint* pContext, int* pResult) => HResults.E_NOTIMPL; + public unsafe int GetFailedAssemblyDisplayName(ulong assembly, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetFailedAssemblyList(ulong appDomain, int count, [In, MarshalUsing(CountElementName = "count"), Out] ulong[] values, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetFailedAssemblyLocation(ulong assesmbly, uint count, char* location, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetFieldDescData(ulong fieldDesc, void* data) => HResults.E_NOTIMPL; + public unsafe int GetFrameName(ulong vtable, uint count, char* frameName, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetGCHeapData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetGCHeapDetails(ulong heap, void* details) => HResults.E_NOTIMPL; + public unsafe int GetGCHeapList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ulong[] heaps, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetGCHeapStaticData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetHandleEnum(void** ppHandleEnum) => HResults.E_NOTIMPL; + public unsafe int GetHandleEnumForGC(uint gen, void** ppHandleEnum) => HResults.E_NOTIMPL; + public unsafe int GetHandleEnumForTypes([In, MarshalUsing(CountElementName = "count")] uint[] types, uint count, void** ppHandleEnum) => HResults.E_NOTIMPL; + public unsafe int GetHeapAllocData(uint count, void* data, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetHeapAnalyzeData(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetHeapAnalyzeStaticData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetHeapSegmentData(ulong seg, void* data) => HResults.E_NOTIMPL; + public unsafe int GetHillClimbingLogEntry(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetILForModule(ulong moduleAddr, int rva, ulong* il) => HResults.E_NOTIMPL; + public unsafe int GetJitHelperFunctionName(ulong ip, uint count, byte* name, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetJitManagerList(uint count, void* managers, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetJumpThunkTarget(void* ctx, ulong* targetIP, ulong* targetMD) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, void* data, uint cRevertedRejitVersions, void* rgRevertedRejitData, uint* pcNeededRevertedRejitData) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescFromToken(ulong moduleAddr, uint token, ulong* methodDesc) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescName(ulong methodDesc, uint count, char* name, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) => HResults.E_NOTIMPL; + public unsafe int GetMethodDescTransparencyData(ulong methodDesc, void* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableData(ulong mt, void* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableFieldData(ulong mt, void* data) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableForEEClass(ulong eeClass, ulong* value) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableName(ulong mt, uint count, char* mtName, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableSlot(ulong mt, uint slot, ulong* value) => HResults.E_NOTIMPL; + public unsafe int GetMethodTableTransparencyData(ulong mt, void* data) => HResults.E_NOTIMPL; + public unsafe int GetModule(ulong addr, void** mod) => HResults.E_NOTIMPL; + public unsafe int GetModuleData(ulong moduleAddr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject, ulong* nextNestedException) => HResults.E_NOTIMPL; + public unsafe int GetObjectClassName(ulong obj, uint count, char* className, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetObjectData(ulong objAddr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetOOMData(ulong oomAddr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetOOMStaticData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetPEFileBase(ulong addr, ulong* peBase) => HResults.E_NOTIMPL; + public unsafe int GetPEFileName(ulong addr, uint count, char* fileName, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetPrivateBinPaths(ulong appDomain, int count, char* paths, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetRCWData(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetRCWInterfaces(ulong rcw, uint count, void* interfaces, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetStackLimits(ulong threadPtr, ulong* lower, ulong* upper, ulong* fp) => HResults.E_NOTIMPL; + public unsafe int GetStackReferences(int osThreadID, void** ppEnum) => HResults.E_NOTIMPL; + public unsafe int GetStressLogAddress(ulong* stressLog) => HResults.E_NOTIMPL; + public unsafe int GetSyncBlockCleanupData(ulong addr, void* data) => HResults.E_NOTIMPL; + public unsafe int GetSyncBlockData(uint number, void* data) => HResults.E_NOTIMPL; + public unsafe int GetThreadAllocData(ulong thread, void* data) => HResults.E_NOTIMPL; + public unsafe int GetThreadData(ulong thread, DacpThreadData* data) => HResults.E_NOTIMPL; + public unsafe int GetThreadFromThinlockID(uint thinLockId, ulong* pThread) => HResults.E_NOTIMPL; + public unsafe int GetThreadLocalModuleData(ulong thread, uint index, void* data) => HResults.E_NOTIMPL; + public unsafe int GetThreadpoolData(void* data) => HResults.E_NOTIMPL; + public unsafe int GetThreadStoreData(DacpThreadStoreData* data) => HResults.E_NOTIMPL; + public unsafe int GetTLSIndex(uint* pIndex) => HResults.E_NOTIMPL; + public unsafe int GetUsefulGlobals(void* data) => HResults.E_NOTIMPL; + public unsafe int GetWorkRequestData(ulong addrWorkRequest, void* data) => HResults.E_NOTIMPL; + public unsafe int TraverseEHInfo(ulong ip, void* pCallback, void* token) => HResults.E_NOTIMPL; + public unsafe int TraverseLoaderHeap(ulong loaderHeapAddr, void* pCallback) => HResults.E_NOTIMPL; + public unsafe int TraverseModuleMap(int mmt, ulong moduleAddr, void* pCallback, void* token) => HResults.E_NOTIMPL; + public unsafe int TraverseRCWCleanupList(ulong cleanupListPtr, void* pCallback, void* token) => HResults.E_NOTIMPL; + public unsafe int TraverseVirtCallStubHeap(ulong pAppDomain, int heaptype, void* pCallback) => HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdacreader/src/SOSDacImpl.cs b/src/native/managed/cdacreader/src/SOSDacImpl.cs deleted file mode 100644 index 893c39bff8830d..00000000000000 --- a/src/native/managed/cdacreader/src/SOSDacImpl.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.Marshalling; - -namespace Microsoft.Diagnostics.DataContractReader; - -[GeneratedComInterface] -[Guid("4eca42d8-7e7b-4c8a-a116-7bfbf6929267")] -internal partial interface ISOSDacInterface9 -{ - int GetBreakingChangeVersion(); -} - -/// -/// Implementation of ISOSDacInterface* interfaces intended to be passed out to consumers -/// interacting with the DAC via those COM interfaces. -/// -[GeneratedComClass] -internal sealed partial class SOSDacImpl : ISOSDacInterface9 -{ - private readonly Target _target; - - public SOSDacImpl(Target target) - { - _target = target; - } - - public int GetBreakingChangeVersion() - { - // TODO: Return non-hard-coded version - return 4; - } -} diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index 1590984f017c30..898cab774bfb56 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -1,11 +1,64 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Buffers.Binary; + namespace Microsoft.Diagnostics.DataContractReader; -internal sealed class Target +public struct TargetPointer +{ + public static TargetPointer Null = new(0); + + public ulong Value; + public TargetPointer(ulong value) => Value = value; +} + +internal sealed unsafe class Target { - public Target(nint _) + private readonly delegate* unmanaged _readFromTarget; + private readonly void* _readContext; + + private bool _isLittleEndian; + private int _pointerSize; + + public Target(ulong _, delegate* unmanaged readFromTarget, void* readContext) { + _readFromTarget = readFromTarget; + _readContext = readContext; + + // TODO: [cdac] Populate from descriptor + _isLittleEndian = BitConverter.IsLittleEndian; + _pointerSize = IntPtr.Size; } + + public bool TryReadPointer(ulong address, out TargetPointer pointer) + { + pointer = TargetPointer.Null; + + byte* buffer = stackalloc byte[_pointerSize]; + ReadOnlySpan span = new ReadOnlySpan(buffer, _pointerSize); + if (ReadFromTarget(address, buffer, (uint)_pointerSize) < 0) + return false; + + if (_pointerSize == sizeof(uint)) + { + pointer = new TargetPointer( + _isLittleEndian + ? BinaryPrimitives.ReadUInt32LittleEndian(span) + : BinaryPrimitives.ReadUInt32BigEndian(span)); + } + else if (_pointerSize == sizeof(ulong)) + { + pointer = new TargetPointer( + _isLittleEndian + ? BinaryPrimitives.ReadUInt64LittleEndian(span) + : BinaryPrimitives.ReadUInt64BigEndian(span)); + } + + return true; + } + + private int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) + => _readFromTarget(address, buffer, bytesToRead, _readContext); } diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 37e77c1221eacb..253bb3c6c27e01 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -1,7 +1,9 @@ + lib$(MSBuildProjectName) $(NetCoreAppToolCurrent) + Microsoft.Diagnostics.DataContractReader enable true @@ -9,6 +11,9 @@ true + + + diff --git a/src/native/managed/compile-native.proj b/src/native/managed/compile-native.proj index 769954456f3466..d227466bfcebd1 100644 --- a/src/native/managed/compile-native.proj +++ b/src/native/managed/compile-native.proj @@ -1,8 +1,6 @@ - - Release - + shared @@ -39,7 +37,7 @@ - + diff --git a/src/tests/Common/wasm-test-runner/index.html b/src/tests/Common/wasm-test-runner/index.html index ff7d959f164d40..716b7c4c11f39f 100644 --- a/src/tests/Common/wasm-test-runner/index.html +++ b/src/tests/Common/wasm-test-runner/index.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/tests/FunctionalTests/WebAssembly/Directory.Build.props b/src/tests/FunctionalTests/WebAssembly/Directory.Build.props index e0eb44a860c46f..f0095527e9c471 100644 --- a/src/tests/FunctionalTests/WebAssembly/Directory.Build.props +++ b/src/tests/FunctionalTests/WebAssembly/Directory.Build.props @@ -5,7 +5,7 @@ Exe true - WasmTestOnBrowser + WasmTestOnChrome $(TestArchiveRoot)browseronly/ $(TestArchiveTestsRoot)$(OSPlatformConfig)/ $(DefineConstants);TARGET_BROWSER diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.cs b/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.cs new file mode 100644 index 00000000000000..31b69aaffaf14f --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Runtime_101028 +{ + public static bool C1Run = false; + public static bool C2Run = false; + + [Fact] + public static int TestEntryPoint() + { + int x = 1234; + Foo(ref x); + if (!C1Run) + { + return 101; + } + + if (!C2Run) + { + return 102; + } + + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Foo(ref int x) + { + int value = x; + int calc = value / (C1.Value | -1); + int calc2 = value / (C2.Value | -1); + Consume(calc + calc2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(int value) + { + } + + static class C1 + { + public static int Value; + static C1() + { + C1Run = true; + } + } + + static class C2 + { + public static int Value; + static C2() + { + C2Run = true; + } + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.csproj new file mode 100644 index 00000000000000..15edd99711a1a4 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_101028/Runtime_101028.csproj @@ -0,0 +1,8 @@ + + + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.cs new file mode 100644 index 00000000000000..ccca99fb956cbb --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Reference source for Runtime_73615.il. + +using InlineIL; +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Runtime_73615 +{ + [Fact] + public static int TestEntryPoint() + { + Foo(new C(101)); + return Result; + } + + public static int Result; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Foo(C arg) + { + IL.Emit.Ldarga(nameof(arg)); + IL.Emit.Ldarga(nameof(arg)); + IL.Emit.Call(new MethodRef(typeof(Runtime_73615), nameof(Bar))); + IL.Emit.Constrained(); + IL.Emit.Callvirt(new MethodRef(typeof(C), nameof(arg.Baz))); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int Bar(ref C o) + { + o = new C(100); + return 0; + } + + public class C + { + public C(int result) => Result = result; + public int Result; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Baz(int arg) + { + Runtime_73615.Result = Result; + } + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.il b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.il new file mode 100644 index 00000000000000..261d8f25f84b35 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.il @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) } +.assembly extern xunit.core { } +.assembly extern System.Console { } + +.assembly Runtime_73615 { } + +.class public auto ansi beforefieldinit Runtime_73615 + extends [System.Runtime]System.Object +{ + .class auto ansi nested public beforefieldinit C + extends [System.Runtime]System.Object + { + .field public int32 Result + .method public hidebysig specialname rtspecialname + instance void .ctor(int32 result) cil managed + { + // Code size 14 (0xe) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld int32 Runtime_73615/C::Result + IL_000d: ret + } // end of method C::.ctor + + .method public hidebysig instance void + Baz(int32 arg) cil managed noinlining + { + // Code size 12 (0xc) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld int32 Runtime_73615/C::Result + IL_0006: stsfld int32 Runtime_73615::Result + IL_000b: ret + } // end of method C::Baz + + } // end of class C + + .field public static int32 Result + .method public hidebysig static int32 Main() cil managed + { + .custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( 01 00 00 00 ) + .entrypoint + // Code size 18 (0x12) + .maxstack 8 + IL_0000: ldc.i4.s 101 + IL_0002: newobj instance void Runtime_73615/C::.ctor(int32) + IL_0007: call void Runtime_73615::Foo(class Runtime_73615/C) + IL_000c: ldsfld int32 Runtime_73615::Result + IL_0011: ret + } // end of method Runtime_73615::TestEntryPoint + + .method private hidebysig static void Foo(class Runtime_73615/C arg) cil managed noinlining + { + // Code size 21 (0x15) + .maxstack 2 + IL_0000: ldarga.s arg + IL_0002: ldarga.s arg + IL_0004: call int32 Runtime_73615::Bar(class Runtime_73615/C&) + IL_0009: constrained. Runtime_73615/C + IL_000f: callvirt instance void Runtime_73615/C::Baz(int32) + IL_0014: ret + } // end of method Runtime_73615::Foo + + .method private hidebysig static int32 + Bar(class Runtime_73615/C& o) cil managed noinlining + { + // Code size 11 (0xb) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 100 + IL_0003: newobj instance void Runtime_73615/C::.ctor(int32) + IL_0008: stind.ref + IL_0009: ldc.i4.0 + IL_000a: ret + } // end of method Runtime_73615::Bar + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ret + } // end of method Runtime_73615::.ctor + +} // end of class Runtime_73615 diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.ilproj b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.ilproj new file mode 100644 index 00000000000000..5fa250452852d2 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73615/Runtime_73615.ilproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/tools/illink/src/linker/CompatibilitySuppressions.xml b/src/tools/illink/src/linker/CompatibilitySuppressions.xml index 7bf0a1e0ce6974..a1c6c7ff82976c 100644 --- a/src/tools/illink/src/linker/CompatibilitySuppressions.xml +++ b/src/tools/illink/src/linker/CompatibilitySuppressions.xml @@ -1,4 +1,4 @@ - + @@ -1497,6 +1497,18 @@ CP0002 M:Mono.Linker.Steps.SubStepsDispatcher.Process(Mono.Linker.LinkContext) + + CP0002 + M:Mono.Linker.LinkContext.get_KeepComInterfaces + ref/net9.0/illink.dll + lib/net9.0/illink.dll + + + CP0002 + M:Mono.Linker.LinkContext.set_KeepComInterfaces(System.Boolean) + ref/net9.0/illink.dll + lib/net9.0/illink.dll + CP0008 T:Mono.Linker.LinkContext @@ -1537,4 +1549,4 @@ CP0017 M:Mono.Linker.LinkContext.Resolve(Mono.Cecil.AssemblyNameReference)$0 - + \ No newline at end of file diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index c2ef7cabae3a88..876ca9da73122b 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2438,7 +2438,7 @@ protected virtual bool ShouldMarkInterfaceImplementationList (TypeDefinition typ // It's hard to know if a com or windows runtime interface will be needed from managed code alone, // so as a precaution we will mark these interfaces once the type is instantiated - if (resolvedInterfaceType.IsImport || resolvedInterfaceType.IsWindowsRuntime) + if (Context.KeepComInterfaces && (resolvedInterfaceType.IsImport || resolvedInterfaceType.IsWindowsRuntime)) return true; return IsFullyPreserved (type); diff --git a/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs b/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs index 5cf6f0e3f1e918..2fa3c03f2b396b 100644 --- a/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/OutputStep.cs @@ -42,17 +42,6 @@ namespace Mono.Linker.Steps public class OutputStep : BaseStep { - private Dictionary? architectureMap; - - private enum NativeOSOverride - { - Apple = 0x4644, - FreeBSD = 0xadc4, - Linux = 0x7b79, - NetBSD = 0x1993, - Default = 0 - } - readonly List assembliesWritten; public OutputStep () @@ -60,25 +49,6 @@ public OutputStep () assembliesWritten = new List (); } - TargetArchitecture CalculateArchitecture (TargetArchitecture readyToRunArch) - { - if (architectureMap == null) { - architectureMap = new Dictionary (); - foreach (var os in Enum.GetValues (typeof (NativeOSOverride))) { - ushort osVal = (ushort) (NativeOSOverride) os; - foreach (var arch in Enum.GetValues (typeof (TargetArchitecture))) { - ushort archVal = (ushort) (TargetArchitecture) arch; - architectureMap.Add ((ushort) (archVal ^ osVal), (TargetArchitecture) arch); - } - } - } - - if (architectureMap.TryGetValue ((ushort) readyToRunArch, out TargetArchitecture pureILArch)) { - return pureILArch; - } - throw new BadImageFormatException ("unrecognized module attributes"); - } - protected override bool ConditionToProcess () { return Context.ErrorsCount == 0; @@ -125,7 +95,7 @@ protected virtual void WriteAssembly (AssemblyDefinition assembly, string direct if (module.IsCrossgened ()) { module.Attributes |= ModuleAttributes.ILOnly; module.Attributes ^= ModuleAttributes.ILLibrary; - module.Architecture = CalculateArchitecture (module.Architecture); + module.Architecture = TargetArchitecture.I386; // I386+ILOnly which ultimately translates to AnyCPU } } diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs index f699127b16c3de..43495d53582040 100644 --- a/src/tools/illink/src/linker/Linker/Driver.cs +++ b/src/tools/illink/src/linker/Linker/Driver.cs @@ -362,6 +362,12 @@ protected int SetupContext (ILogger? customLogger = null) context.SetCustomData (values[0], values[1]); continue; + case "--keep-com-interfaces": + if (!GetBoolParam (token, l => context.KeepComInterfaces = l)) + return -1; + + continue; + case "--keep-compilers-resources": if (!GetBoolParam (token, l => keepCompilersResources = l)) return -1; @@ -1284,6 +1290,7 @@ protected virtual LinkContext GetDefaultContext (Pipeline pipeline, ILogger? log return new LinkContext (pipeline, logger ?? new ConsoleLogger (), "output") { TrimAction = AssemblyAction.Link, DefaultAction = AssemblyAction.Link, + KeepComInterfaces = true, }; } @@ -1369,6 +1376,7 @@ static void Usage () Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed"); Console.WriteLine (" --explicit-reflection Adds to members never used through reflection DisablePrivateReflection attribute. Defaults to false"); Console.WriteLine (" --feature FEATURE VALUE Apply any optimizations defined when this feature setting is a constant known at link time"); + Console.WriteLine (" --keep-com-interfaces Keep COM interfaces implemented by kept types. Defaults to true"); Console.WriteLine (" --keep-compilers-resources Keep assembly resources used for F# compilation resources. Defaults to false"); Console.WriteLine (" --keep-dep-attributes Keep attributes used for manual dependency tracking. Defaults to false"); Console.WriteLine (" --keep-metadata NAME Keep metadata which would otherwise be removed if not used"); diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs index 4af4cdc654ab5f..081deb5c5ccce9 100644 --- a/src/tools/illink/src/linker/Linker/LinkContext.cs +++ b/src/tools/illink/src/linker/Linker/LinkContext.cs @@ -108,6 +108,8 @@ public Pipeline Pipeline { public bool LinkSymbols { get; set; } + public bool KeepComInterfaces { get; set; } + public bool KeepMembersForDebugger { get; set; } = true; public bool IgnoreUnresolved { get; set; } = true; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsKept.cs index 61ebdf89cda824..048dfb63629b6a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsKept.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsKept.cs @@ -21,6 +21,9 @@ public static void Main () [Guid ("D7BB1889-3AB7-4681-A115-60CA9158FECA")] interface IBar { + // Trimming may remove members from COM interfaces + // even when keeping the COM-related attributes. + // https://github.com/dotnet/runtime/issues/101128 void Bar (); } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemoved.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemoved.cs new file mode 100644 index 00000000000000..64612b1b704f4f --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemoved.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType +{ + /// + /// With --keep-com-interfaces false, we apply the unused interface rules also to com interfaces. + /// + [SetupLinkerArgument ("--keep-com-interfaces", "false")] + public class UnusedComInterfaceIsRemoved + { + public static void Main () + { + var i = new A (); + i.Foo (); + } + + [ComImport] + [Guid ("D7BB1889-3AB7-4681-A115-60CA9158FECA")] + interface IBar + { + void Bar (); + } + + [Kept] + [KeptMember (".ctor()")] + class A : IBar + { + [Kept] + public void Foo () + { + } + + public void Bar () + { + } + } + } +}