diff --git a/prereqs/git-info/arcade.props b/prereqs/git-info/arcade.props index aaeae81362c..c0df67974ee 100644 --- a/prereqs/git-info/arcade.props +++ b/prereqs/git-info/arcade.props @@ -1,8 +1,8 @@  - 9bbce22e13f399ad3cb8b4b7e53960b621f92ea1 - 20250515.1 - 10.0.0-beta.25265.1 + 35a34fa5ab9b2f97d3f7bdf48a7c2100727308b3 + 20250518.1 + 10.0.0-beta.25268.1 \ No newline at end of file diff --git a/prereqs/git-info/aspnetcore.props b/prereqs/git-info/aspnetcore.props index 0ffe5901a8e..eb5eeae2b86 100644 --- a/prereqs/git-info/aspnetcore.props +++ b/prereqs/git-info/aspnetcore.props @@ -1,8 +1,8 @@  - f0a43e8c514e5b2f013bdac6f4eca3a107e0438a - 20250515.1 - 10.0.0-preview.5.25265.1 + e26a16c185cf95e3c52b028920aeba6bcea814ce + 20250519.5 + 10.0.0-preview.5.25269.5 \ No newline at end of file diff --git a/prereqs/git-info/cecil.props b/prereqs/git-info/cecil.props index 83a1cbe4fbf..6f30d26a40b 100644 --- a/prereqs/git-info/cecil.props +++ b/prereqs/git-info/cecil.props @@ -1,8 +1,8 @@  - 091a4d494e2575c524c316013a482ec88bba28f2 - 20250511.1 - 0.11.5-alpha.25261.1 + 50f19ccf4b834a6eab54496486a114ba3839f7b9 + 20250516.1 + 0.11.5-alpha.25266.1 \ No newline at end of file diff --git a/prereqs/git-info/diagnostics.props b/prereqs/git-info/diagnostics.props index 82b0400284c..9643287ab4e 100644 --- a/prereqs/git-info/diagnostics.props +++ b/prereqs/git-info/diagnostics.props @@ -1,8 +1,8 @@  - a3c354f4ea00123f55276619455569d53c4c402d - 20250513.2 - 9.0.626302 + ce653def0d1969dca0d518a95559b2acc3c6128c + 20250516.1 + 9.0.626601 \ No newline at end of file diff --git a/prereqs/git-info/efcore.props b/prereqs/git-info/efcore.props index e5d8c1e25ff..67814bc96d2 100644 --- a/prereqs/git-info/efcore.props +++ b/prereqs/git-info/efcore.props @@ -1,8 +1,8 @@  - 059c5c2d125d5fc8a5cce3438c0e38be108f1b6a - 20250515.1 - 10.0.0-preview.4.25265.1 + d0237c7e0c3192b00766d6a163fe2a5def526e03 + 20250516.5 + 10.0.0-preview.4.25266.5 \ No newline at end of file diff --git a/prereqs/git-info/emsdk.props b/prereqs/git-info/emsdk.props index 3f5965d5ee0..e87c9e16b9b 100644 --- a/prereqs/git-info/emsdk.props +++ b/prereqs/git-info/emsdk.props @@ -1,8 +1,8 @@  - b01d7473b8d3088bad808f83bf9ed89b4c341563 - 20250514.1 - 10.0.0-preview.5.25264.1 + 1f91ec07aa2b387b41ec464fd876dca5552150e2 + 20250516.1 + 10.0.0-preview.5.25266.1 \ No newline at end of file diff --git a/prereqs/git-info/fsharp.props b/prereqs/git-info/fsharp.props index 3dea649abdb..f47f8c30581 100644 --- a/prereqs/git-info/fsharp.props +++ b/prereqs/git-info/fsharp.props @@ -1,8 +1,8 @@  - e8bfd3562774b2939c252d907de2f3173a2bd5bd - 20250424.6 - 10.0.100-beta.25224.6 + 665b1f1cdff308582aa1a944aa03c3a62d1fbcd0 + 20250516.3 + 10.0.100-beta.25266.3 \ No newline at end of file diff --git a/prereqs/git-info/msbuild.props b/prereqs/git-info/msbuild.props index f817638d424..e261e69862b 100644 --- a/prereqs/git-info/msbuild.props +++ b/prereqs/git-info/msbuild.props @@ -1,8 +1,8 @@  - b6fb9dd6c70366421497d6621ea966e610784e15 - 20250515.4 - 17.15.0-preview-25265-04 + f5b4822ee5fdbb9001e38fc324c43fd1d1b090c9 + 20250516.1 + 17.15.0-preview-25266-01 \ No newline at end of file diff --git a/prereqs/git-info/razor.props b/prereqs/git-info/razor.props index 8a9bb79c579..82650d1c5e1 100644 --- a/prereqs/git-info/razor.props +++ b/prereqs/git-info/razor.props @@ -1,8 +1,8 @@  - 6352a3201c903ffe907d9c7d2eb3a10507f1bd4e - 20250515.1 - 10.0.0-preview.25265.1 + 013cbf58b149ad9664d2b1252a73ed51f7cdb564 + 20250517.1 + 10.0.0-preview.25267.1 \ No newline at end of file diff --git a/prereqs/git-info/roslyn-analyzers.props b/prereqs/git-info/roslyn-analyzers.props index 6924eef5e39..28774012c51 100644 --- a/prereqs/git-info/roslyn-analyzers.props +++ b/prereqs/git-info/roslyn-analyzers.props @@ -1,8 +1,8 @@  - df7fa025647ab226d55b7de67a72ebbfed60f5a0 - 20250513.1 - 10.0.0-preview.25263.1 + 5db220ec96a406ab3a095bdf73ffa37fd94a8c9b + 20250517.1 + 10.0.0-preview.25267.1 \ No newline at end of file diff --git a/prereqs/git-info/roslyn.props b/prereqs/git-info/roslyn.props index 986dc499579..c5092ee9493 100644 --- a/prereqs/git-info/roslyn.props +++ b/prereqs/git-info/roslyn.props @@ -1,8 +1,8 @@  - 015a854ffc3fc7660eca31b0ffe168ea8d1d65a0 - 20250515.6 - 5.0.0-1.25265.6 + c3c7ad6a866dd0b857ad14ce683987c39d2b8fe0 + 20250516.4 + 5.0.0-1.25266.4 \ No newline at end of file diff --git a/prereqs/git-info/runtime.props b/prereqs/git-info/runtime.props index 8e476a8882e..aa69f9ec9ff 100644 --- a/prereqs/git-info/runtime.props +++ b/prereqs/git-info/runtime.props @@ -1,8 +1,8 @@  - 1307a67de8039d70a9e3d6e74d6651afbd8a0278 - 20250515.21 + 29638e8e84335bc877f064766be3f1b3038da311 + 20250518.1 10.0.0-preview.5.25262.10 \ No newline at end of file diff --git a/prereqs/git-info/scenario-tests.props b/prereqs/git-info/scenario-tests.props index 3c03a4cf4be..fccd88d81ec 100644 --- a/prereqs/git-info/scenario-tests.props +++ b/prereqs/git-info/scenario-tests.props @@ -1,8 +1,8 @@  - 828faff7300aac7fae6f9544393f9cb317baeb6d - 20250512.1 + ba23e62da145ef04187aabe1e1fb64029fe2317d + 20250516.1 10.0.0-preview.25221.1 \ No newline at end of file diff --git a/prereqs/git-info/templating.props b/prereqs/git-info/templating.props index b4f3cf50287..a2179f0338a 100644 --- a/prereqs/git-info/templating.props +++ b/prereqs/git-info/templating.props @@ -1,8 +1,8 @@  - db6256f5f72d0b954273e94f616f1bf0ba9c443c - 20250511.1 - 10.0.100-preview.5.25261.1 + 297983ef280be4b32f0d36b5bb2742b8e6e947b3 + 20250516.4 + 10.0.100-preview.5.25266.4 \ No newline at end of file diff --git a/prereqs/git-info/vstest.props b/prereqs/git-info/vstest.props index cdb08873a3b..65b4952de2e 100644 --- a/prereqs/git-info/vstest.props +++ b/prereqs/git-info/vstest.props @@ -1,8 +1,8 @@  - 2e6d9288d3aa0269ae710844f3aa9e0a3981b26e - 20250424.1 - 17.15.0-preview-25224-01 + f93fcd2943f39c2cbb65d25e3949e21c3190184c + 20250516.1 + 17.15.0-preview-25266-01 \ No newline at end of file diff --git a/prereqs/git-info/windowsdesktop.props b/prereqs/git-info/windowsdesktop.props index 10a371cdf6c..6ec56fa114b 100644 --- a/prereqs/git-info/windowsdesktop.props +++ b/prereqs/git-info/windowsdesktop.props @@ -1,8 +1,8 @@  - b9faa7e590d3e5acc87d0600072167d140b811f1 - 20250515.1 - 10.0.0-preview.5.25265.1 + 66210f6e71c81c26d9c6dd3fef6a6a600528ee9d + 20250517.3 + 10.0.0-preview.5.25267.3 \ No newline at end of file diff --git a/prereqs/git-info/winforms.props b/prereqs/git-info/winforms.props index 4561c672407..d4622afaf12 100644 --- a/prereqs/git-info/winforms.props +++ b/prereqs/git-info/winforms.props @@ -1,8 +1,8 @@  - 05a904f4c1419562b2ae01bad597f9cd3bad5872 - 20250515.1 - 10.0.0-preview.5.25265.1 + 716bc129a4679a98394b2e2d24d1d1d3d4f54ddb + 20250517.1 + 10.0.0-preview.5.25267.1 \ No newline at end of file diff --git a/prereqs/git-info/wpf.props b/prereqs/git-info/wpf.props index c8d852a7438..0d00467c668 100644 --- a/prereqs/git-info/wpf.props +++ b/prereqs/git-info/wpf.props @@ -1,8 +1,8 @@  - 1002fa520a05b240ace9f51a69a60314edc8c762 - 20250515.3 - 10.0.0-preview.5.25265.3 + 57fe8ac611226f89c1a213fea520a7a82000c6a4 + 20250516.3 + 10.0.0-preview.5.25266.3 \ No newline at end of file diff --git a/src/arcade/.config/dotnet-tools.json b/src/arcade/.config/dotnet-tools.json index 58ebc1fb17b..3d2202d837f 100644 --- a/src/arcade/.config/dotnet-tools.json +++ b/src/arcade/.config/dotnet-tools.json @@ -13,6 +13,12 @@ "commands": [ "pat-generator" ] + }, + "microsoft.dotnet.darc": { + "version": "1.1.0-beta.25230.8", + "commands": [ + "darc" + ] } } } diff --git a/src/arcade/Directory.Packages.props b/src/arcade/Directory.Packages.props index 998fec90a2e..1f226a93c68 100644 --- a/src/arcade/Directory.Packages.props +++ b/src/arcade/Directory.Packages.props @@ -14,7 +14,7 @@ 2.9.2 - 2.0.1 + 2.0.2 1.16.0 $(XUnitVersion) 3.0.2 diff --git a/src/arcade/eng/common/templates/steps/vmr-sync.yml b/src/arcade/eng/common/templates/steps/vmr-sync.yml new file mode 100644 index 00000000000..599afb6186b --- /dev/null +++ b/src/arcade/eng/common/templates/steps/vmr-sync.yml @@ -0,0 +1,207 @@ +### These steps synchronize new code from product repositories into the VMR (https://github.com/dotnet/dotnet). +### They initialize the darc CLI and pull the new updates. +### Changes are applied locally onto the already cloned VMR (located in $vmrPath). + +parameters: +- name: targetRef + displayName: Target revision in dotnet/ to synchronize + type: string + default: $(Build.SourceVersion) + +- name: vmrPath + displayName: Path where the dotnet/dotnet is checked out to + type: string + default: $(Agent.BuildDirectory)/vmr + +- name: additionalSyncs + displayName: Optional list of package names whose repo's source will also be synchronized in the local VMR, e.g. NuGet.Protocol + type: object + default: [] + +steps: +- checkout: vmr + displayName: Clone dotnet/dotnet + path: vmr + clean: true + +- checkout: self + displayName: Clone $(Build.Repository.Name) + path: repo + fetchDepth: 0 + +# This step is needed so that when we get a detached HEAD / shallow clone, +# we still pull the commit into the temporary repo clone to use it during the sync. +# Also unshallow the clone so that forwardflow command would work. +- script: | + git branch repo-head + git rev-parse HEAD + displayName: Label PR commit + workingDirectory: $(Agent.BuildDirectory)/repo + +- script: | + vmr_sha=$(grep -oP '(?<=Sha=")[^"]*' $(Agent.BuildDirectory)/repo/eng/Version.Details.xml) + echo "##vso[task.setvariable variable=vmr_sha]$vmr_sha" + displayName: Obtain the vmr sha from Version.Details.xml (Unix) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- powershell: | + [xml]$xml = Get-Content -Path $(Agent.BuildDirectory)/repo/eng/Version.Details.xml + $vmr_sha = $xml.SelectSingleNode("//Source").Sha + Write-Output "##vso[task.setvariable variable=vmr_sha]$vmr_sha" + displayName: Obtain the vmr sha from Version.Details.xml (Windows) + condition: eq(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- script: | + git fetch --all + git checkout $(vmr_sha) + displayName: Checkout VMR at correct sha for repo flow + workingDirectory: ${{ parameters.vmrPath }} + +- script: | + git config --global user.name "dotnet-maestro[bot]" + git config --global user.email "dotnet-maestro[bot]@users.noreply.github.com" + displayName: Set git author to dotnet-maestro[bot] + workingDirectory: ${{ parameters.vmrPath }} + +- script: | + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(dn-bot-all-orgs-code-r)' \ + --ci \ + --debug + + if [ "$?" -ne 0 ]; then + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + fi + displayName: Sync repo into VMR (Unix) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- script: | + git config --global diff.astextplain.textconv echo + git config --system core.longpaths true + displayName: Configure Windows git (longpaths, astextplain) + condition: eq(variables['Agent.OS'], 'Windows_NT') + +- powershell: | + ./eng/common/vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(dn-bot-all-orgs-code-r)' ` + -ci ` + -debugOutput + + if ($LASTEXITCODE -ne 0) { + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + } + displayName: Sync repo into VMR (Windows) + condition: eq(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - task: CopyFiles@2 + displayName: Collect failed patches + condition: failed() + inputs: + SourceFolder: '$(Agent.TempDirectory)' + Contents: '*.patch' + TargetFolder: '$(Build.ArtifactStagingDirectory)/FailedPatches' + + - publish: '$(Build.ArtifactStagingDirectory)/FailedPatches' + artifact: $(System.JobDisplayName)_FailedPatches + displayName: Upload failed patches + condition: failed() + +- ${{ each assetName in parameters.additionalSyncs }}: + # The vmr-sync script ends up staging files in the local VMR so we have to commit those + - script: + git commit --allow-empty -am "Forward-flow $(Build.Repository.Name)" + displayName: Commit local VMR changes + workingDirectory: ${{ parameters.vmrPath }} + + - script: | + set -ex + + echo "Searching for details of asset ${{ assetName }}..." + + # Use darc to get dependencies information + dependencies=$(./.dotnet/dotnet darc get-dependencies --name '${{ assetName }}' --ci) + + # Extract repository URL and commit hash + repository=$(echo "$dependencies" | grep 'Repo:' | sed 's/Repo:[[:space:]]*//' | head -1) + + if [ -z "$repository" ]; then + echo "##vso[task.logissue type=error]Asset ${{ assetName }} not found in the dependency list" + exit 1 + fi + + commit=$(echo "$dependencies" | grep 'Commit:' | sed 's/Commit:[[:space:]]*//' | head -1) + + echo "Updating the VMR from $repository / $commit..." + cd .. + git clone $repository ${{ assetName }} + cd ${{ assetName }} + git checkout $commit + git branch "sync/$commit" + + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(dn-bot-all-orgs-code-r)' \ + --ci \ + --debug + + if [ "$?" -ne 0 ]; then + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + fi + displayName: Sync ${{ assetName }} into (Unix) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + + - powershell: | + $ErrorActionPreference = 'Stop' + + Write-Host "Searching for details of asset ${{ assetName }}..." + + $dependencies = .\.dotnet\dotnet darc get-dependencies --name '${{ assetName }}' --ci + + $repository = $dependencies | Select-String -Pattern 'Repo:\s+([^\s]+)' | Select-Object -First 1 + $repository -match 'Repo:\s+([^\s]+)' | Out-Null + $repository = $matches[1] + + if ($repository -eq $null) { + Write-Error "Asset ${{ assetName }} not found in the dependency list" + exit 1 + } + + $commit = $dependencies | Select-String -Pattern 'Commit:\s+([^\s]+)' | Select-Object -First 1 + $commit -match 'Commit:\s+([^\s]+)' | Out-Null + $commit = $matches[1] + + Write-Host "Updating the VMR from $repository / $commit..." + cd .. + git clone $repository ${{ assetName }} + cd ${{ assetName }} + git checkout $commit + git branch "sync/$commit" + + .\eng\common\vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(dn-bot-all-orgs-code-r)' ` + -ci ` + -debugOutput + + if ($LASTEXITCODE -ne 0) { + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + } + displayName: Sync ${{ assetName }} into (Windows) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo diff --git a/src/arcade/eng/common/templates/vmr-build-pr.yml b/src/arcade/eng/common/templates/vmr-build-pr.yml new file mode 100644 index 00000000000..670cf32c3bd --- /dev/null +++ b/src/arcade/eng/common/templates/vmr-build-pr.yml @@ -0,0 +1,33 @@ +trigger: none +pr: + branches: + include: + - main + - release/* + paths: + exclude: + - documentation/* + - README.md + - CODEOWNERS + +variables: +- template: /eng/common/templates/variables/pool-providers.yml@self + +- name: skipComponentGovernanceDetection # we run CG on internal builds only + value: true + +- name: Codeql.Enabled # we run CodeQL on internal builds only + value: false + +resources: + repositories: + - repository: vmr + type: github + name: dotnet/dotnet + endpoint: dotnet + +stages: +- template: /eng/pipelines/templates/stages/vmr-build.yml@vmr + parameters: + isBuiltFromVmr: false + scope: lite diff --git a/src/arcade/eng/common/vmr-sync.ps1 b/src/arcade/eng/common/vmr-sync.ps1 new file mode 100755 index 00000000000..8c3c91ce8de --- /dev/null +++ b/src/arcade/eng/common/vmr-sync.ps1 @@ -0,0 +1,138 @@ +<# +.SYNOPSIS + +This script is used for synchronizing the current repository into a local VMR. +It pulls the current repository's code into the specified VMR directory for local testing or +Source-Build validation. + +.DESCRIPTION + +The tooling used for synchronization will clone the VMR repository into a temporary folder if +it does not already exist. These clones can be reused in future synchronizations, so it is +recommended to dedicate a folder for this to speed up re-runs. + +.EXAMPLE + Synchronize current repository into a local VMR: + ./vmr-sync.ps1 -vmrDir "$HOME/repos/dotnet" -tmpDir "$HOME/repos/tmp" + +.PARAMETER tmpDir +Required. Path to the temporary folder where repositories will be cloned + +.PARAMETER vmrBranch +Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch + +.PARAMETER azdevPat +Optional. Azure DevOps PAT to use for cloning private repositories. + +.PARAMETER vmrDir +Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder + +.PARAMETER debugOutput +Optional. Enables debug logging in the darc vmr command. + +.PARAMETER ci +Optional. Denotes that the script is running in a CI environment. +#> +param ( + [Parameter(Mandatory=$true, HelpMessage="Path to the temporary folder where repositories will be cloned")] + [string][Alias('t', 'tmp')]$tmpDir, + [string][Alias('b', 'branch')]$vmrBranch, + [string]$remote, + [string]$azdevPat, + [string][Alias('v', 'vmr')]$vmrDir, + [switch]$ci, + [switch]$debugOutput +) + +function Fail { + Write-Host "> $($args[0])" -ForegroundColor 'Red' +} + +function Highlight { + Write-Host "> $($args[0])" -ForegroundColor 'Cyan' +} + +$verbosity = 'verbose' +if ($debugOutput) { + $verbosity = 'debug' +} +# Validation + +if (-not $tmpDir) { + Fail "Missing -tmpDir argument. Please specify the path to the temporary folder where the repositories will be cloned" + exit 1 +} + +# Sanitize the input + +if (-not $vmrDir) { + $vmrDir = Join-Path $tmpDir 'dotnet' +} + +if (-not (Test-Path -Path $tmpDir -PathType Container)) { + New-Item -ItemType Directory -Path $tmpDir | Out-Null +} + +# Prepare the VMR + +if (-not (Test-Path -Path $vmrDir -PathType Container)) { + Highlight "Cloning 'dotnet/dotnet' into $vmrDir.." + git clone https://github.com/dotnet/dotnet $vmrDir + + if ($vmrBranch) { + git -C $vmrDir switch -c $vmrBranch + } +} +else { + if ((git -C $vmrDir diff --quiet) -eq $false) { + Fail "There are changes in the working tree of $vmrDir. Please commit or stash your changes" + exit 1 + } + + if ($vmrBranch) { + Highlight "Preparing $vmrDir" + git -C $vmrDir checkout $vmrBranch + git -C $vmrDir pull + } +} + +Set-StrictMode -Version Latest + +# Prepare darc + +Highlight 'Installing .NET, preparing the tooling..' +. .\eng\common\tools.ps1 +$dotnetRoot = InitializeDotNetCli -install:$true +$dotnet = "$dotnetRoot\dotnet.exe" +& "$dotnet" tool restore + +Highlight "Starting the synchronization of VMR.." + +# Synchronize the VMR +$darcArgs = ( + "darc", "vmr", "forwardflow", + "--tmp", $tmpDir, + "--$verbosity", + $vmrDir +) + +if ($ci) { + $darcArgs += ("--ci") +} + +if ($azdevPat) { + $darcArgs += ("--azdev-pat", $azdevPat) +} + +& "$dotnet" $darcArgs + +if ($LASTEXITCODE -eq 0) { + Highlight "Synchronization succeeded" +} +else { + Fail "Synchronization of repo to VMR failed!" + Fail "'$vmrDir' is left in its last state (re-run of this script will reset it)." + Fail "Please inspect the logs which contain path to the failing patch file (use -debugOutput to get all the details)." + Fail "Once you make changes to the conflicting VMR patch, commit it locally and re-run this script." + exit 1 +} diff --git a/src/arcade/eng/common/vmr-sync.sh b/src/arcade/eng/common/vmr-sync.sh new file mode 100755 index 00000000000..86d77ccf5b4 --- /dev/null +++ b/src/arcade/eng/common/vmr-sync.sh @@ -0,0 +1,205 @@ +#!/bin/bash + +### This script is used for synchronizing the current repository into a local VMR. +### It pulls the current repository's code into the specified VMR directory for local testing or +### Source-Build validation. +### +### The tooling used for synchronization will clone the VMR repository into a temporary folder if +### it does not already exist. These clones can be reused in future synchronizations, so it is +### recommended to dedicate a folder for this to speed up re-runs. +### +### USAGE: +### Synchronize current repository into a local VMR: +### ./vmr-sync.sh --tmp "$HOME/repos/tmp" "$HOME/repos/dotnet" +### +### Options: +### -t, --tmp, --tmp-dir PATH +### Required. Path to the temporary folder where repositories will be cloned +### +### -b, --branch, --vmr-branch BRANCH_NAME +### Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch +### +### --debug +### Optional. Turns on the most verbose logging for the VMR tooling +### +### --remote name:URI +### Optional. Additional remote to use during the synchronization +### This can be used to synchronize to a commit from a fork of the repository +### Example: 'runtime:https://github.com/yourfork/runtime' +### +### --azdev-pat +### Optional. Azure DevOps PAT to use for cloning private repositories. +### +### -v, --vmr, --vmr-dir PATH +### Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +function print_help () { + sed -n '/^### /,/^$/p' "$source" | cut -b 5- +} + +COLOR_RED=$(tput setaf 1 2>/dev/null || true) +COLOR_CYAN=$(tput setaf 6 2>/dev/null || true) +COLOR_CLEAR=$(tput sgr0 2>/dev/null || true) +COLOR_RESET=uniquesearchablestring +FAILURE_PREFIX='> ' + +function fail () { + echo "${COLOR_RED}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_RED}}${COLOR_CLEAR}" >&2 +} + +function highlight () { + echo "${COLOR_CYAN}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_CYAN}}${COLOR_CLEAR}" +} + +tmp_dir='' +vmr_dir='' +vmr_branch='' +additional_remotes='' +verbosity=verbose +azdev_pat='' +ci=false + +while [[ $# -gt 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -t|--tmp|--tmp-dir) + tmp_dir=$2 + shift + ;; + -v|--vmr|--vmr-dir) + vmr_dir=$2 + shift + ;; + -b|--branch|--vmr-branch) + vmr_branch=$2 + shift + ;; + --remote) + additional_remotes="$additional_remotes $2" + shift + ;; + --azdev-pat) + azdev_pat=$2 + shift + ;; + --ci) + ci=true + ;; + -d|--debug) + verbosity=debug + ;; + -h|--help) + print_help + exit 0 + ;; + *) + fail "Invalid argument: $1" + print_help + exit 1 + ;; + esac + + shift +done + +# Validation + +if [[ -z "$tmp_dir" ]]; then + fail "Missing --tmp-dir argument. Please specify the path to the temporary folder where the repositories will be cloned" + exit 1 +fi + +# Sanitize the input + +if [[ -z "$vmr_dir" ]]; then + vmr_dir="$tmp_dir/dotnet" +fi + +if [[ ! -d "$tmp_dir" ]]; then + mkdir -p "$tmp_dir" +fi + +if [[ "$verbosity" == "debug" ]]; then + set -x +fi + +# Prepare the VMR + +if [[ ! -d "$vmr_dir" ]]; then + highlight "Cloning 'dotnet/dotnet' into $vmr_dir.." + git clone https://github.com/dotnet/dotnet "$vmr_dir" + + if [[ -n "$vmr_branch" ]]; then + git -C "$vmr_dir" switch -c "$vmr_branch" + fi +else + if ! git -C "$vmr_dir" diff --quiet; then + fail "There are changes in the working tree of $vmr_dir. Please commit or stash your changes" + exit 1 + fi + + if [[ -n "$vmr_branch" ]]; then + highlight "Preparing $vmr_dir" + git -C "$vmr_dir" checkout "$vmr_branch" + git -C "$vmr_dir" pull + fi +fi + +set -e + +# Prepare darc + +highlight 'Installing .NET, preparing the tooling..' +source "./eng/common/tools.sh" +InitializeDotNetCli true +dotnetDir=$( cd ./.dotnet/; pwd -P ) +dotnet=$dotnetDir/dotnet +"$dotnet" tool restore + +highlight "Starting the synchronization of VMR.." +set +e + +if [[ -n "$additional_remotes" ]]; then + additional_remotes="--additional-remotes $additional_remotes" +fi + +if [[ -n "$azdev_pat" ]]; then + azdev_pat="--azdev-pat $azdev_pat" +fi + +ci_arg='' +if [[ "$ci" == "true" ]]; then + ci_arg="--ci" +fi + +# Synchronize the VMR + +"$dotnet" darc vmr forwardflow \ + --tmp "$tmp_dir" \ + $azdev_pat \ + --$verbosity \ + $ci_arg \ + $additional_remotes \ + "$vmr_dir" + +if [[ $? == 0 ]]; then + highlight "Synchronization succeeded" +else + fail "Synchronization of repo to VMR failed!" + fail "'$vmr_dir' is left in its last state (re-run of this script will reset it)." + fail "Please inspect the logs which contain path to the failing patch file (use --debug to get all the details)." + fail "Once you make changes to the conflicting VMR patch, commit it locally and re-run this script." + exit 1 +fi diff --git a/src/arcade/eng/configure-toolset.ps1 b/src/arcade/eng/configure-toolset.ps1 index 369cfcc1b3f..28bfce2082b 100644 --- a/src/arcade/eng/configure-toolset.ps1 +++ b/src/arcade/eng/configure-toolset.ps1 @@ -4,7 +4,8 @@ function Test-FilesUseTelemetryOutput { "enable-cross-org-publishing.ps1", "performance-setup.ps1", "retain-build.ps1", - "nuget-verification.ps1") + "nuget-verification.ps1", + "vmr-sync.ps1") $filesMissingTelemetry = Get-ChildItem -File -Recurse -Path "$PSScriptRoot\common" -Include "*.ps1" -Exclude $requireTelemetryExcludeFiles | Where-Object { -Not( $_ | Select-String -Pattern "Write-PipelineTelemetryError" )} diff --git a/src/arcade/eng/configure-toolset.sh b/src/arcade/eng/configure-toolset.sh index c0bc3989978..092c75b374d 100644 --- a/src/arcade/eng/configure-toolset.sh +++ b/src/arcade/eng/configure-toolset.sh @@ -15,6 +15,7 @@ function Test-FilesUseTelemetryOutput { 'eng/common/darc-init.sh' 'eng/common/msbuild.sh' 'eng/common/performance/performance-setup.sh' + 'eng/common/vmr-sync.sh' ) local file_list=`grep --files-without-match --recursive --include=*.sh "Write-PipelineTelemetryError" $scriptroot` diff --git a/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props b/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props index b4207716751..30798e1babe 100644 --- a/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props +++ b/src/arcade/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props @@ -89,7 +89,7 @@ $(XUnitVersion) 3.0.2 - 2.0.1 + 2.0.2 1.6.3 3.8.3 diff --git a/src/aspnetcore/.github/workflows/backport.yml b/src/aspnetcore/.github/workflows/backport.yml index 37eb30cf546..8e29f20ada5 100644 --- a/src/aspnetcore/.github/workflows/backport.yml +++ b/src/aspnetcore/.github/workflows/backport.yml @@ -14,7 +14,7 @@ permissions: jobs: backport: - uses: dotnet/arcade/.github/workflows/backport-base.yml@d7540e540636883d3d080d087223d28b6b7395ae # 2025-01-13 + uses: dotnet/arcade/.github/workflows/backport-base.yml@9bbce22e13f399ad3cb8b4b7e53960b621f92ea1 # 2025-01-13 with: pr_description_template: | Backport of #%source_pr_number% to %target_branch% diff --git a/src/aspnetcore/.github/workflows/inter-branch-merge-flow.yml b/src/aspnetcore/.github/workflows/inter-branch-merge-flow.yml index cba6fffbaf4..86a0cdb328f 100644 --- a/src/aspnetcore/.github/workflows/inter-branch-merge-flow.yml +++ b/src/aspnetcore/.github/workflows/inter-branch-merge-flow.yml @@ -10,4 +10,4 @@ permissions: jobs: Merge: - uses: dotnet/arcade/.github/workflows/backport-base.yml@d7540e540636883d3d080d087223d28b6b7395ae # 2024-06-24 + uses: dotnet/arcade/.github/workflows/backport-base.yml@9bbce22e13f399ad3cb8b4b7e53960b621f92ea1 # 2024-06-24 diff --git a/src/aspnetcore/eng/Version.Details.xml b/src/aspnetcore/eng/Version.Details.xml index 6eb2edc8f26..ae10ab52298 100644 --- a/src/aspnetcore/eng/Version.Details.xml +++ b/src/aspnetcore/eng/Version.Details.xml @@ -8,337 +8,337 @@ See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md for instructions on using darc. --> - + - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee @@ -357,52 +357,52 @@ https://github.com/dotnet/roslyn afdd413cee50c16318620252e4e64dc326e2d300 - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5e6dacd4d3debda3266224b2a434811c6fa94987 + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5e6dacd4d3debda3266224b2a434811c6fa94987 + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee https://github.com/dotnet/extensions diff --git a/src/aspnetcore/eng/Versions.props b/src/aspnetcore/eng/Versions.props index ed056a7a6f7..1869f6dca62 100644 --- a/src/aspnetcore/eng/Versions.props +++ b/src/aspnetcore/eng/Versions.props @@ -67,97 +67,97 @@ --> - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 - 10.0.0-preview.5.25260.104 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25265.101 9.6.0-preview.1.25260.2 9.6.0-preview.1.25260.2 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 - 10.0.0-preview.4.25260.104 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 + 10.0.0-preview.4.25265.101 - 10.0.100-preview.5.25258.39 - 10.0.100-preview.5.25258.39 + 10.0.100-preview.5.25265.101 + 10.0.100-preview.5.25265.101 4.13.0-3.24613.7 @@ -170,12 +170,12 @@ 6.2.4 6.2.4 - 10.0.0-beta.25260.104 - 10.0.0-beta.25260.104 - 10.0.0-beta.25260.104 - 10.0.0-beta.25260.104 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 - 10.0.0-preview.25260.104 + 10.0.0-preview.25265.101 1.0.0-prerelease.25217.3 1.0.0-prerelease.25217.3 diff --git a/src/aspnetcore/eng/common/build.sh b/src/aspnetcore/eng/common/build.sh index 36fba82a379..27ae2c85601 100755 --- a/src/aspnetcore/eng/common/build.sh +++ b/src/aspnetcore/eng/common/build.sh @@ -136,7 +136,7 @@ while [[ $# > 0 ]]; do restore=true pack=true ;; - -productBuild|-pb) + -productbuild|-pb) build=true product_build=true restore=true diff --git a/src/aspnetcore/eng/common/core-templates/steps/source-build.yml b/src/aspnetcore/eng/common/core-templates/steps/source-build.yml index f2a0f347fdd..0dde553c3eb 100644 --- a/src/aspnetcore/eng/common/core-templates/steps/source-build.yml +++ b/src/aspnetcore/eng/common/core-templates/steps/source-build.yml @@ -51,13 +51,12 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ + --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $baseRidArgs \ $portableBuildArgs \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml diff --git a/src/aspnetcore/eng/common/darc-init.sh b/src/aspnetcore/eng/common/darc-init.sh index 36dbd45e1ce..e889f439b8d 100755 --- a/src/aspnetcore/eng/common/darc-init.sh +++ b/src/aspnetcore/eng/common/darc-init.sh @@ -68,7 +68,7 @@ function InstallDarcCli { fi fi - local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." diff --git a/src/aspnetcore/eng/common/tools.ps1 b/src/aspnetcore/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/aspnetcore/eng/common/tools.ps1 +++ b/src/aspnetcore/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/aspnetcore/eng/common/tools.sh b/src/aspnetcore/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/aspnetcore/eng/common/tools.sh +++ b/src/aspnetcore/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/aspnetcore/global.json b/src/aspnetcore/global.json index 2445fe6f7b1..fb35fa5b26c 100644 --- a/src/aspnetcore/global.json +++ b/src/aspnetcore/global.json @@ -27,9 +27,9 @@ "jdk": "latest" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25260.104", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25260.104", - "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.25260.104", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25265.101", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25265.101", + "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.25265.101", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.4.0" } diff --git a/src/aspnetcore/src/Components/Components/src/ComponentsActivitySource.cs b/src/aspnetcore/src/Components/Components/src/ComponentsActivitySource.cs new file mode 100644 index 00000000000..7079bf7743a --- /dev/null +++ b/src/aspnetcore/src/Components/Components/src/ComponentsActivitySource.cs @@ -0,0 +1,177 @@ +// 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; + +namespace Microsoft.AspNetCore.Components; + +/// +/// This is instance scoped per renderer +/// +internal class ComponentsActivitySource +{ + internal const string Name = "Microsoft.AspNetCore.Components"; + internal const string OnCircuitName = $"{Name}.CircuitStart"; + internal const string OnRouteName = $"{Name}.RouteChange"; + internal const string OnEventName = $"{Name}.HandleEvent"; + + private ActivityContext _httpContext; + private ActivityContext _circuitContext; + private string? _circuitId; + private ActivityContext _routeContext; + + private ActivitySource ActivitySource { get; } = new ActivitySource(Name); + + public static ActivityContext CaptureHttpContext() + { + var parentActivity = Activity.Current; + if (parentActivity is not null && parentActivity.OperationName == "Microsoft.AspNetCore.Hosting.HttpRequestIn" && parentActivity.Recorded) + { + return parentActivity.Context; + } + return default; + } + + public Activity? StartCircuitActivity(string circuitId, ActivityContext httpContext) + { + _circuitId = circuitId; + + var activity = ActivitySource.CreateActivity(OnCircuitName, ActivityKind.Internal, parentId: null, null, null); + if (activity is not null) + { + if (activity.IsAllDataRequested) + { + if (_circuitId != null) + { + activity.SetTag("aspnetcore.components.circuit.id", _circuitId); + } + if (httpContext != default) + { + activity.AddLink(new ActivityLink(httpContext)); + } + } + activity.DisplayName = $"Circuit {circuitId ?? ""}"; + activity.Start(); + _circuitContext = activity.Context; + } + return activity; + } + + public void FailCircuitActivity(Activity? activity, Exception ex) + { + _circuitContext = default; + if (activity != null && !activity.IsStopped) + { + activity.SetTag("error.type", ex.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error); + activity.Stop(); + } + } + + public Activity? StartRouteActivity(string componentType, string route) + { + if (_httpContext == default) + { + _httpContext = CaptureHttpContext(); + } + + var activity = ActivitySource.CreateActivity(OnRouteName, ActivityKind.Internal, parentId: null, null, null); + if (activity is not null) + { + if (activity.IsAllDataRequested) + { + if (_circuitId != null) + { + activity.SetTag("aspnetcore.components.circuit.id", _circuitId); + } + if (componentType != null) + { + activity.SetTag("aspnetcore.components.type", componentType); + } + if (route != null) + { + activity.SetTag("aspnetcore.components.route", route); + } + if (_httpContext != default) + { + activity.AddLink(new ActivityLink(_httpContext)); + } + if (_circuitContext != default) + { + activity.AddLink(new ActivityLink(_circuitContext)); + } + } + + activity.DisplayName = $"Route {route ?? "[unknown path]"} -> {componentType ?? "[unknown component]"}"; + activity.Start(); + _routeContext = activity.Context; + } + return activity; + } + + public Activity? StartEventActivity(string? componentType, string? methodName, string? attributeName) + { + var activity = ActivitySource.CreateActivity(OnEventName, ActivityKind.Internal, parentId: null, null, null); + if (activity is not null) + { + if (activity.IsAllDataRequested) + { + if (_circuitId != null) + { + activity.SetTag("aspnetcore.components.circuit.id", _circuitId); + } + if (componentType != null) + { + activity.SetTag("aspnetcore.components.type", componentType); + } + if (methodName != null) + { + activity.SetTag("aspnetcore.components.method", methodName); + } + if (attributeName != null) + { + activity.SetTag("aspnetcore.components.attribute.name", attributeName); + } + if (_httpContext != default) + { + activity.AddLink(new ActivityLink(_httpContext)); + } + if (_circuitContext != default) + { + activity.AddLink(new ActivityLink(_circuitContext)); + } + if (_routeContext != default) + { + activity.AddLink(new ActivityLink(_routeContext)); + } + } + + activity.DisplayName = $"Event {attributeName ?? "[unknown attribute]"} -> {componentType ?? "[unknown component]"}.{methodName ?? "[unknown method]"}"; + activity.Start(); + } + return activity; + } + + public static void FailEventActivity(Activity? activity, Exception ex) + { + if (activity != null && !activity.IsStopped) + { + activity.SetTag("error.type", ex.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error); + activity.Stop(); + } + } + + public static async Task CaptureEventStopAsync(Task task, Activity? activity) + { + try + { + await task; + activity?.Stop(); + } + catch (Exception ex) + { + FailEventActivity(activity, ex); + } + } +} diff --git a/src/aspnetcore/src/Components/Components/src/ComponentsMetrics.cs b/src/aspnetcore/src/Components/Components/src/ComponentsMetrics.cs new file mode 100644 index 00000000000..712d9e395b4 --- /dev/null +++ b/src/aspnetcore/src/Components/Components/src/ComponentsMetrics.cs @@ -0,0 +1,191 @@ +// 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.Diagnostics.Metrics; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Components; + +internal sealed class ComponentsMetrics : IDisposable +{ + public const string MeterName = "Microsoft.AspNetCore.Components"; + public const string LifecycleMeterName = "Microsoft.AspNetCore.Components.Lifecycle"; + private readonly Meter _meter; + private readonly Meter _lifeCycleMeter; + + private readonly Counter _navigationCount; + + private readonly Histogram _eventDuration; + private readonly Histogram _parametersDuration; + private readonly Histogram _batchDuration; + + public bool IsNavigationEnabled => _navigationCount.Enabled; + + public bool IsEventEnabled => _eventDuration.Enabled; + + public bool IsParametersEnabled => _parametersDuration.Enabled; + + public bool IsBatchEnabled => _batchDuration.Enabled; + + public ComponentsMetrics(IMeterFactory meterFactory) + { + Debug.Assert(meterFactory != null); + + _meter = meterFactory.Create(MeterName); + _lifeCycleMeter = meterFactory.Create(LifecycleMeterName); + + _navigationCount = _meter.CreateCounter( + "aspnetcore.components.navigation", + unit: "{route}", + description: "Total number of route changes."); + + _eventDuration = _meter.CreateHistogram( + "aspnetcore.components.event_handler", + unit: "s", + description: "Duration of processing browser event. It includes business logic of the component but not affected child components.", + advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); + + _parametersDuration = _lifeCycleMeter.CreateHistogram( + "aspnetcore.components.update_parameters", + unit: "s", + description: "Duration of processing component parameters. It includes business logic of the component.", + advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.BlazorRenderingSecondsBucketBoundaries }); + + _batchDuration = _lifeCycleMeter.CreateHistogram( + "aspnetcore.components.render_diff", + unit: "s", + description: "Duration of rendering component tree and producing HTML diff. It includes business logic of the changed components.", + advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.BlazorRenderingSecondsBucketBoundaries }); + } + + public void Navigation(string componentType, string route) + { + var tags = new TagList + { + { "aspnetcore.components.type", componentType ?? "unknown" }, + { "aspnetcore.components.route", route ?? "unknown" }, + }; + + _navigationCount.Add(1, tags); + } + + public async Task CaptureEventDuration(Task task, long startTimestamp, string? componentType, string? methodName, string? attributeName) + { + var tags = new TagList + { + { "aspnetcore.components.type", componentType ?? "unknown" }, + { "aspnetcore.components.method", methodName ?? "unknown" }, + { "aspnetcore.components.attribute.name", attributeName ?? "unknown" } + }; + + try + { + await task; + } + catch (Exception ex) + { + tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + } + var duration = Stopwatch.GetElapsedTime(startTimestamp); + _eventDuration.Record(duration.TotalSeconds, tags); + } + + public void FailEventSync(Exception ex, long startTimestamp, string? componentType, string? methodName, string? attributeName) + { + var tags = new TagList + { + { "aspnetcore.components.type", componentType ?? "unknown" }, + { "aspnetcore.components.method", methodName ?? "unknown" }, + { "aspnetcore.components.attribute.name", attributeName ?? "unknown" }, + { "error.type", ex.GetType().FullName ?? "unknown" } + }; + var duration = Stopwatch.GetElapsedTime(startTimestamp); + _eventDuration.Record(duration.TotalSeconds, tags); + } + + public async Task CaptureParametersDuration(Task task, long startTimestamp, string? componentType) + { + var tags = new TagList + { + { "aspnetcore.components.type", componentType ?? "unknown" }, + }; + + try + { + await task; + } + catch(Exception ex) + { + tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + } + var duration = Stopwatch.GetElapsedTime(startTimestamp); + _parametersDuration.Record(duration.TotalSeconds, tags); + } + + public void FailParametersSync(Exception ex, long startTimestamp, string? componentType) + { + var duration = Stopwatch.GetElapsedTime(startTimestamp); + var tags = new TagList + { + { "aspnetcore.components.type", componentType ?? "unknown" }, + { "error.type", ex.GetType().FullName ?? "unknown" } + }; + _parametersDuration.Record(duration.TotalSeconds, tags); + } + + public async Task CaptureBatchDuration(Task task, long startTimestamp, int diffLength) + { + var tags = new TagList + { + { "aspnetcore.components.diff.length", BucketDiffLength(diffLength) } + }; + + try + { + await task; + } + catch (Exception ex) + { + tags.Add("error.type", ex.GetType().FullName ?? "unknown"); + } + var duration = Stopwatch.GetElapsedTime(startTimestamp); + _batchDuration.Record(duration.TotalSeconds, tags); + } + + public void FailBatchSync(Exception ex, long startTimestamp) + { + var duration = Stopwatch.GetElapsedTime(startTimestamp); + var tags = new TagList + { + { "aspnetcore.components.diff.length", 0 }, + { "error.type", ex.GetType().FullName ?? "unknown" } + }; + _batchDuration.Record(duration.TotalSeconds, tags); + } + + private static int BucketDiffLength(int diffLength) + { + return diffLength switch + { + <= 1 => 1, + <= 2 => 2, + <= 5 => 5, + <= 10 => 10, + <= 20 => 20, + <= 50 => 50, + <= 100 => 100, + <= 500 => 500, + <= 1000 => 1000, + <= 10000 => 10000, + _ => 10001, + }; + } + + public void Dispose() + { + _meter.Dispose(); + _lifeCycleMeter.Dispose(); + } +} diff --git a/src/aspnetcore/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/aspnetcore/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index ca3286f8b6c..07fcc360fd7 100644 --- a/src/aspnetcore/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/aspnetcore/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -79,6 +79,7 @@ + diff --git a/src/aspnetcore/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs b/src/aspnetcore/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs index 3a91043bb6d..64936326e3e 100644 --- a/src/aspnetcore/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs +++ b/src/aspnetcore/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs @@ -211,8 +211,16 @@ private static string ComputeKey(Type keyType, string propertyName) // This happens once per type and property combo, so allocations are ok. var assemblyName = keyType.Assembly.FullName; var typeName = keyType.FullName; - var input = Encoding.UTF8.GetBytes(string.Join(".", assemblyName, typeName, propertyName)); - return Convert.ToBase64String(SHA256.HashData(input)); + + // Internal classes can be bundled in different assemblies during prerendering and WASM rendering. + bool isTypeInternal = (!keyType.IsPublic && !keyType.IsNested) || keyType.IsNestedAssembly; + var inputString = isTypeInternal + ? string.Join(".", typeName, propertyName) + : string.Join(".", assemblyName, typeName, propertyName); + + var input = Encoding.UTF8.GetBytes(inputString); + var hash = SHA256.HashData(input); + return Convert.ToBase64String(hash); } internal static IEnumerable GetCandidateBindableProperties( diff --git a/src/aspnetcore/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/aspnetcore/src/Components/Components/src/PublicAPI.Unshipped.txt index b99e9fd7f21..ce563e15a7c 100644 --- a/src/aspnetcore/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/aspnetcore/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,7 +1,7 @@ #nullable enable - Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.get -> System.Type! Microsoft.AspNetCore.Components.Routing.Router.NotFoundPage.set -> void +Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler! Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void Microsoft.AspNetCore.Components.Routing.IHostEnvironmentNavigationManager.Initialize(string! baseUri, string! uri, System.Func! onNavigateTo) -> void @@ -14,5 +14,7 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Microsoft.AspNetCore.Components.Infrastructure.ComponentsMetricsServiceCollectionExtensions.AddComponentsTracing(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void \ No newline at end of file diff --git a/src/aspnetcore/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs b/src/aspnetcore/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs new file mode 100644 index 00000000000..e1e224e5f71 --- /dev/null +++ b/src/aspnetcore/src/Components/Components/src/RegisterRenderingMetricsServiceCollectionExtensions.cs @@ -0,0 +1,57 @@ +// 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.Metrics; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.AspNetCore.Components.Infrastructure; + +/// +/// Infrastructure APIs for registering diagnostic metrics. +/// +public static class ComponentsMetricsServiceCollectionExtensions +{ + /// + /// Registers component rendering metrics + /// + /// The . + /// The . + public static IServiceCollection AddComponentsMetrics( + IServiceCollection services) + { + // do not register IConfigureOptions multiple times + if (!IsMeterFactoryRegistered(services)) + { + services.AddMetrics(); + } + services.TryAddSingleton(); + + return services; + } + + /// + /// Registers component rendering traces + /// + /// The . + /// The . + public static IServiceCollection AddComponentsTracing( + IServiceCollection services) + { + services.TryAddScoped(); + + return services; + } + + private static bool IsMeterFactoryRegistered(IServiceCollection services) + { + foreach (var service in services) + { + if (service.ServiceType == typeof(IMeterFactory)) + { + return true; + } + } + return false; + } +} diff --git a/src/aspnetcore/src/Components/Components/src/RenderHandle.cs b/src/aspnetcore/src/Components/Components/src/RenderHandle.cs index 6ac2b7b3cde..edcb644bddf 100644 --- a/src/aspnetcore/src/Components/Components/src/RenderHandle.cs +++ b/src/aspnetcore/src/Components/Components/src/RenderHandle.cs @@ -21,6 +21,9 @@ internal RenderHandle(Renderer renderer, int componentId) _componentId = componentId; } + internal ComponentsMetrics? ComponentMetrics => _renderer?.ComponentMetrics; + internal ComponentsActivitySource? ComponentActivitySource => _renderer?.ComponentActivitySource; + /// /// Gets the associated with the component. /// diff --git a/src/aspnetcore/src/Components/Components/src/RenderTree/Renderer.cs b/src/aspnetcore/src/Components/Components/src/RenderTree/Renderer.cs index 5ba977930a4..177eccf9bf5 100644 --- a/src/aspnetcore/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/aspnetcore/src/Components/Components/src/RenderTree/Renderer.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Metrics; using System.Linq; using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Reflection; @@ -25,16 +24,20 @@ namespace Microsoft.AspNetCore.Components.RenderTree; // dispatching events to them, and notifying when the user interface is being updated. public abstract partial class Renderer : IDisposable, IAsyncDisposable { + internal static readonly Task CanceledRenderTask = Task.FromCanceled(new CancellationToken(canceled: true)); + private readonly object _lockObject = new(); private readonly IServiceProvider _serviceProvider; private readonly Dictionary _componentStateById = new Dictionary(); private readonly Dictionary _componentStateByComponent = new Dictionary(); private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder(); - private readonly Dictionary _eventBindings = new(); + private readonly Dictionary _eventBindings = new(); private readonly Dictionary _eventHandlerIdReplacements = new Dictionary(); private readonly ILogger _logger; private readonly ComponentFactory _componentFactory; - private readonly RenderingMetrics? _renderingMetrics; + private readonly ComponentsMetrics? _componentsMetrics; + private readonly ComponentsActivitySource? _componentsActivitySource; + private Dictionary? _rootComponentsLatestParameters; private Task? _ongoingQuiescenceTask; @@ -92,16 +95,17 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, // logger name in here as a string literal. _logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Components.RenderTree.Renderer"); _componentFactory = new ComponentFactory(componentActivator, this); - - // TODO register RenderingMetrics as singleton in DI - var meterFactory = serviceProvider.GetService(); - _renderingMetrics = meterFactory != null ? new RenderingMetrics(meterFactory) : null; + _componentsMetrics = serviceProvider.GetService(); + _componentsActivitySource = serviceProvider.GetService(); ServiceProviderCascadingValueSuppliers = serviceProvider.GetService() is null ? Array.Empty() : serviceProvider.GetServices().ToArray(); } + internal ComponentsMetrics? ComponentMetrics => _componentsMetrics; + internal ComponentsActivitySource? ComponentActivitySource => _componentsActivitySource; + internal ICascadingValueSupplier[] ServiceProviderCascadingValueSuppliers { get; } internal HotReloadManager HotReloadManager { get; set; } = HotReloadManager.Default; @@ -442,7 +446,20 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie _pendingTasks ??= new(); } - var (renderedByComponentId, callback) = GetRequiredEventBindingEntry(eventHandlerId); + var (renderedByComponentId, callback, attributeName) = GetRequiredEventBindingEntry(eventHandlerId); + + // collect trace + Activity? activity = null; + string receiverName = null; + string methodName = null; + if (ComponentActivitySource != null) + { + receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; + methodName ??= callback.Delegate.Method?.Name; + activity = ComponentActivitySource.StartEventActivity(receiverName, methodName, attributeName); + } + + var eventStartTimestamp = ComponentMetrics != null && ComponentMetrics.IsEventEnabled ? Stopwatch.GetTimestamp() : 0; // If this event attribute was rendered by a component that's since been disposed, don't dispatch the event at all. // This can occur because event handler disposal is deferred, so event handler IDs can outlive their components. @@ -484,9 +501,35 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie _isBatchInProgress = true; task = callback.InvokeAsync(eventArgs); + + // collect metrics + if (ComponentMetrics != null && ComponentMetrics.IsEventEnabled) + { + receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; + methodName ??= callback.Delegate.Method?.Name; + _ = ComponentMetrics.CaptureEventDuration(task, eventStartTimestamp, receiverName, methodName, attributeName); + } + + // stop activity/trace + if (ComponentActivitySource != null && activity != null) + { + _ = ComponentsActivitySource.CaptureEventStopAsync(task, activity); + } } catch (Exception e) { + if (ComponentMetrics != null && ComponentMetrics.IsEventEnabled) + { + receiverName ??= (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName; + methodName ??= callback.Delegate.Method?.Name; + ComponentMetrics.FailEventSync(e, eventStartTimestamp, receiverName, methodName, attributeName); + } + + if (ComponentActivitySource != null && activity != null) + { + ComponentsActivitySource.FailEventActivity(activity, e); + } + HandleExceptionViaErrorBoundary(e, receiverComponentState); return Task.CompletedTask; } @@ -638,7 +681,7 @@ internal void AssignEventHandlerId(int renderedByComponentId, ref RenderTreeFram // // When that happens we intentionally box the EventCallback because we need to hold on to // the receiver. - _eventBindings.Add(id, (renderedByComponentId, callback)); + _eventBindings.Add(id, (renderedByComponentId, callback, frame.AttributeName)); } else if (frame.AttributeValueField is MulticastDelegate @delegate) { @@ -646,7 +689,7 @@ internal void AssignEventHandlerId(int renderedByComponentId, ref RenderTreeFram // is the same as delegate.Target. In this case since the receiver is implicit we can // avoid boxing the EventCallback object and just re-hydrate it on the other side of the // render tree. - _eventBindings.Add(id, (renderedByComponentId, new EventCallback(@delegate.Target as IHandleEvent, @delegate))); + _eventBindings.Add(id, (renderedByComponentId, new EventCallback(@delegate.Target as IHandleEvent, @delegate), frame.AttributeName)); } // NOTE: we do not to handle EventCallback here. EventCallback is only used when passing @@ -696,7 +739,7 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven _eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId); } - private (int RenderedByComponentId, EventCallback Callback) GetRequiredEventBindingEntry(ulong eventHandlerId) + private (int RenderedByComponentId, EventCallback Callback, string? attributeName) GetRequiredEventBindingEntry(ulong eventHandlerId) { if (!_eventBindings.TryGetValue(eventHandlerId, out var entry)) { @@ -770,6 +813,7 @@ private void ProcessRenderQueue() _isBatchInProgress = true; var updateDisplayTask = Task.CompletedTask; + var batchStartTimestamp = ComponentMetrics != null && ComponentMetrics.IsBatchEnabled ? Stopwatch.GetTimestamp() : 0; try { @@ -801,9 +845,19 @@ private void ProcessRenderQueue() // Fire off the execution of OnAfterRenderAsync, but don't wait for it // if there is async work to be done. _ = InvokeRenderCompletedCalls(batch.UpdatedComponents, updateDisplayTask); + + if (ComponentMetrics != null && ComponentMetrics.IsBatchEnabled) + { + _ = ComponentMetrics.CaptureBatchDuration(updateDisplayTask, batchStartTimestamp, batch.UpdatedComponents.Count); + } } catch (Exception e) { + if (ComponentMetrics != null && ComponentMetrics.IsBatchEnabled) + { + ComponentMetrics.FailBatchSync(e, batchStartTimestamp); + } + // Ensure we catch errors while running the render functions of the components. HandleException(e); return; @@ -947,15 +1001,13 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry) { var componentState = renderQueueEntry.ComponentState; Log.RenderingComponent(_logger, componentState); - var startTime = (_renderingMetrics != null && _renderingMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; - _renderingMetrics?.RenderStart(componentState.Component.GetType().FullName); + componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment, out var renderFragmentException); if (renderFragmentException != null) { // If this returns, the error was handled by an error boundary. Otherwise it throws. HandleExceptionViaErrorBoundary(renderFragmentException, componentState); } - _renderingMetrics?.RenderEnd(componentState.Component.GetType().FullName, renderFragmentException, startTime, Stopwatch.GetTimestamp()); // Process disposal queue now in case it causes further component renders to be enqueued ProcessDisposalQueueInExistingBatch(); diff --git a/src/aspnetcore/src/Components/Components/src/Rendering/ComponentState.cs b/src/aspnetcore/src/Components/Components/src/Rendering/ComponentState.cs index c7be2643edd..c1ee3c2f164 100644 --- a/src/aspnetcore/src/Components/Components/src/Rendering/ComponentState.cs +++ b/src/aspnetcore/src/Components/Components/src/Rendering/ComponentState.cs @@ -23,6 +23,7 @@ public class ComponentState : IAsyncDisposable private RenderTreeBuilder _nextRenderTree; private ArrayBuilder? _latestDirectParametersSnapshot; // Lazily instantiated private bool _componentWasDisposed; + private readonly string? _componentTypeName; /// /// Constructs an instance of . @@ -51,6 +52,11 @@ public ComponentState(Renderer renderer, int componentId, IComponent component, _hasCascadingParameters = true; _hasAnyCascadingParameterSubscriptions = AddCascadingParameterSubscriptions(); } + + if (_renderer.ComponentMetrics != null && _renderer.ComponentMetrics.IsParametersEnabled) + { + _componentTypeName = component.GetType().FullName; + } } private static ComponentState? GetSectionOutletLogicalParent(Renderer renderer, SectionOutlet sectionOutlet) @@ -231,14 +237,27 @@ internal void NotifyCascadingValueChanged(in ParameterViewLifetime lifetime) // a consistent set to the recipient. private void SupplyCombinedParameters(ParameterView directAndCascadingParameters) { - // Normalise sync and async exceptions into a Task + var parametersStartTimestamp = _renderer.ComponentMetrics != null && _renderer.ComponentMetrics.IsParametersEnabled ? Stopwatch.GetTimestamp() : 0; + + // Normalize sync and async exceptions into a Task Task setParametersAsyncTask; try { setParametersAsyncTask = Component.SetParametersAsync(directAndCascadingParameters); + + // collect metrics + if (_renderer.ComponentMetrics != null && _renderer.ComponentMetrics.IsParametersEnabled) + { + _ = _renderer.ComponentMetrics.CaptureParametersDuration(setParametersAsyncTask, parametersStartTimestamp, _componentTypeName); + } } catch (Exception ex) { + if (_renderer.ComponentMetrics != null && _renderer.ComponentMetrics.IsParametersEnabled) + { + _renderer.ComponentMetrics.FailParametersSync(ex, parametersStartTimestamp, _componentTypeName); + } + setParametersAsyncTask = Task.FromException(ex); } diff --git a/src/aspnetcore/src/Components/Components/src/Rendering/RenderingMetrics.cs b/src/aspnetcore/src/Components/Components/src/Rendering/RenderingMetrics.cs deleted file mode 100644 index 54b32a793cc..00000000000 --- a/src/aspnetcore/src/Components/Components/src/Rendering/RenderingMetrics.cs +++ /dev/null @@ -1,106 +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.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Components.Rendering; - -internal sealed class RenderingMetrics : IDisposable -{ - public const string MeterName = "Microsoft.AspNetCore.Components.Rendering"; - - private readonly Meter _meter; - private readonly Counter _renderTotalCounter; - private readonly UpDownCounter _renderActiveCounter; - private readonly Histogram _renderDuration; - - public RenderingMetrics(IMeterFactory meterFactory) - { - Debug.Assert(meterFactory != null); - - _meter = meterFactory.Create(MeterName); - - _renderTotalCounter = _meter.CreateCounter( - "aspnetcore.components.rendering.count", - unit: "{renders}", - description: "Number of component renders performed."); - - _renderActiveCounter = _meter.CreateUpDownCounter( - "aspnetcore.components.rendering.active_renders", - unit: "{renders}", - description: "Number of component renders performed."); - - _renderDuration = _meter.CreateHistogram( - "aspnetcore.components.rendering.duration", - unit: "ms", - description: "Duration of component rendering operations per component.", - advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); - } - - public void RenderStart(string componentType) - { - var tags = new TagList(); - tags = InitializeRequestTags(componentType, tags); - - if (_renderActiveCounter.Enabled) - { - _renderActiveCounter.Add(1, tags); - } - if (_renderTotalCounter.Enabled) - { - _renderTotalCounter.Add(1, tags); - } - } - - public void RenderEnd(string componentType, Exception? exception, long startTimestamp, long currentTimestamp) - { - // Tags must match request start. - var tags = new TagList(); - tags = InitializeRequestTags(componentType, tags); - - if (_renderActiveCounter.Enabled) - { - _renderActiveCounter.Add(-1, tags); - } - - if (_renderDuration.Enabled) - { - if (exception != null) - { - TryAddTag(ref tags, "error.type", exception.GetType().FullName); - } - - var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); - _renderDuration.Record(duration.TotalMilliseconds, tags); - } - } - - private static TagList InitializeRequestTags(string componentType, TagList tags) - { - tags.Add("component.type", componentType); - return tags; - } - - public bool IsDurationEnabled() => _renderDuration.Enabled; - - public void Dispose() - { - _meter.Dispose(); - } - - private static bool TryAddTag(ref TagList tags, string name, object? value) - { - for (var i = 0; i < tags.Count; i++) - { - if (tags[i].Key == name) - { - return false; - } - } - - tags.Add(new KeyValuePair(name, value)); - return true; - } -} diff --git a/src/aspnetcore/src/Components/Components/src/Routing/Router.cs b/src/aspnetcore/src/Components/Components/src/Routing/Router.cs index d562b94bb63..2d2afadc3d3 100644 --- a/src/aspnetcore/src/Components/Components/src/Routing/Router.cs +++ b/src/aspnetcore/src/Components/Components/src/Routing/Router.cs @@ -3,6 +3,7 @@ #nullable disable warnings +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Metadata; @@ -10,8 +11,8 @@ using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Internal; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Routing; @@ -222,10 +223,13 @@ internal virtual void Refresh(bool isNavigationIntercepted) var relativePath = NavigationManager.ToBaseRelativePath(_locationAbsolute.AsSpan()); var locationPathSpan = TrimQueryOrHash(relativePath); var locationPath = $"/{locationPathSpan}"; + Activity? activity = null; // In order to avoid routing twice we check for RouteData if (RoutingStateProvider?.RouteData is { } endpointRouteData) { + activity = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); + // Other routers shouldn't provide RouteData, this is specific to our router component // and must abide by our syntax and behaviors. // Other routers must create their own abstractions to flow data from their SSR routing @@ -236,6 +240,8 @@ internal virtual void Refresh(bool isNavigationIntercepted) // - Convert constrained parameters with (int, double, etc) to the target type. endpointRouteData = RouteTable.ProcessParameters(endpointRouteData); _renderHandle.Render(Found(endpointRouteData)); + + activity?.Stop(); return; } @@ -252,6 +258,8 @@ internal virtual void Refresh(bool isNavigationIntercepted) $"does not implement {typeof(IComponent).FullName}."); } + activity = RecordDiagnostics(context.Handler.FullName, context.Entry.RoutePattern.RawText); + Log.NavigatingToComponent(_logger, context.Handler, locationPath, _baseUri); var routeData = new RouteData( @@ -271,6 +279,8 @@ internal virtual void Refresh(bool isNavigationIntercepted) { if (!isNavigationIntercepted) { + activity = RecordDiagnostics("NotFound", "NotFound"); + Log.DisplayingNotFound(_logger, locationPath, _baseUri); // We did not find a Component that matches the route. @@ -280,10 +290,30 @@ internal virtual void Refresh(bool isNavigationIntercepted) } else { + activity = RecordDiagnostics("External", "External"); + Log.NavigatingToExternalUri(_logger, _locationAbsolute, locationPath, _baseUri); NavigationManager.NavigateTo(_locationAbsolute, forceLoad: true); } } + activity?.Stop(); + + } + + private Activity? RecordDiagnostics(string componentType, string template) + { + Activity? activity = null; + if (_renderHandle.ComponentActivitySource != null) + { + activity = _renderHandle.ComponentActivitySource.StartRouteActivity(componentType, template); + } + + if (_renderHandle.ComponentMetrics != null && _renderHandle.ComponentMetrics.IsNavigationEnabled) + { + _renderHandle.ComponentMetrics.Navigation(componentType, template); + } + + return activity; } private static void DefaultNotFoundContent(RenderTreeBuilder builder) diff --git a/src/aspnetcore/src/Components/Components/test/ComponentsActivitySourceTest.cs b/src/aspnetcore/src/Components/Components/test/ComponentsActivitySourceTest.cs new file mode 100644 index 00000000000..660bf942826 --- /dev/null +++ b/src/aspnetcore/src/Components/Components/test/ComponentsActivitySourceTest.cs @@ -0,0 +1,251 @@ +// 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; + +namespace Microsoft.AspNetCore.Components; + +public class ComponentsActivitySourceTest +{ + private readonly ActivityListener _listener; + private readonly List _activities; + + public ComponentsActivitySourceTest() + { + _activities = new List(); + _listener = new ActivityListener + { + ShouldListenTo = source => source.Name == ComponentsActivitySource.Name, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData, + ActivityStarted = activity => _activities.Add(activity), + ActivityStopped = activity => { } + }; + ActivitySource.AddActivityListener(_listener); + } + + [Fact] + public void Constructor_CreatesActivitySourceCorrectly() + { + // Arrange & Act + var componentsActivitySource = new ComponentsActivitySource(); + + // Assert + Assert.NotNull(componentsActivitySource); + } + + [Fact] + public void CaptureHttpContext_ReturnsDefault_WhenNoCurrentActivity() + { + // Arrange + Activity.Current = null; + + // Act + var result = ComponentsActivitySource.CaptureHttpContext(); + + // Assert + Assert.Equal(default, result); + } + + [Fact] + public void CaptureHttpContext_ReturnsDefault_WhenActivityHasWrongName() + { + // Arrange + using var activity = new ActivitySource("Test").StartActivity("WrongName"); + Activity.Current = activity; + + // Act + var result = ComponentsActivitySource.CaptureHttpContext(); + + // Assert + Assert.Equal(default, result); + } + + [Fact] + public void StartCircuitActivity_CreatesAndStartsActivity() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var circuitId = "test-circuit-id"; + var httpContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); + + // Act + var activity = componentsActivitySource.StartCircuitActivity(circuitId, httpContext); + + // Assert + Assert.NotNull(activity); + Assert.Equal(ComponentsActivitySource.OnCircuitName, activity.OperationName); + Assert.Equal($"Circuit {circuitId}", activity.DisplayName); + Assert.Equal(ActivityKind.Internal, activity.Kind); + Assert.True(activity.IsAllDataRequested); + Assert.Equal(circuitId, activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.Contains(activity.Links, link => link.Context == httpContext); + Assert.False(activity.IsStopped); + } + + [Fact] + public void FailCircuitActivity_SetsErrorStatusAndStopsActivity() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var circuitId = "test-circuit-id"; + var httpContext = default(ActivityContext); + var activity = componentsActivitySource.StartCircuitActivity(circuitId, httpContext); + var exception = new InvalidOperationException("Test exception"); + + // Act + componentsActivitySource.FailCircuitActivity(activity, exception); + + // Assert + Assert.True(activity!.IsStopped); + Assert.Equal(ActivityStatusCode.Error, activity.Status); + Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); + } + + [Fact] + public void StartRouteActivity_CreatesAndStartsActivity() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var componentType = "TestComponent"; + var route = "/test-route"; + + // First set up a circuit context + componentsActivitySource.StartCircuitActivity("test-circuit-id", default); + + // Act + var activity = componentsActivitySource.StartRouteActivity(componentType, route); + + // Assert + Assert.NotNull(activity); + Assert.Equal(ComponentsActivitySource.OnRouteName, activity.OperationName); + Assert.Equal($"Route {route} -> {componentType}", activity.DisplayName); + Assert.Equal(ActivityKind.Internal, activity.Kind); + Assert.True(activity.IsAllDataRequested); + Assert.Equal(componentType, activity.GetTagItem("aspnetcore.components.type")); + Assert.Equal(route, activity.GetTagItem("aspnetcore.components.route")); + Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.False(activity.IsStopped); + } + + [Fact] + public void StartEventActivity_CreatesAndStartsActivity() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var componentType = "TestComponent"; + var methodName = "OnClick"; + var attributeName = "onclick"; + + // First set up a circuit and route context + componentsActivitySource.StartCircuitActivity("test-circuit-id", default); + componentsActivitySource.StartRouteActivity("ParentComponent", "/parent"); + + // Act + var activity = componentsActivitySource.StartEventActivity(componentType, methodName, attributeName); + + // Assert + Assert.NotNull(activity); + Assert.Equal(ComponentsActivitySource.OnEventName, activity.OperationName); + Assert.Equal($"Event {attributeName} -> {componentType}.{methodName}", activity.DisplayName); + Assert.Equal(ActivityKind.Internal, activity.Kind); + Assert.True(activity.IsAllDataRequested); + Assert.Equal(componentType, activity.GetTagItem("aspnetcore.components.type")); + Assert.Equal(methodName, activity.GetTagItem("aspnetcore.components.method")); + Assert.Equal(attributeName, activity.GetTagItem("aspnetcore.components.attribute.name")); + Assert.Equal("test-circuit-id", activity.GetTagItem("aspnetcore.components.circuit.id")); + Assert.False(activity.IsStopped); + } + + [Fact] + public void FailEventActivity_SetsErrorStatusAndStopsActivity() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var exception = new InvalidOperationException("Test exception"); + + // Act + ComponentsActivitySource.FailEventActivity(activity, exception); + + // Assert + Assert.True(activity!.IsStopped); + Assert.Equal(ActivityStatusCode.Error, activity.Status); + Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); + } + + [Fact] + public async Task CaptureEventStopAsync_StopsActivityOnSuccessfulTask() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var task = Task.CompletedTask; + + // Act + await ComponentsActivitySource.CaptureEventStopAsync(task, activity); + + // Assert + Assert.True(activity!.IsStopped); + Assert.Equal(ActivityStatusCode.Unset, activity.Status); + } + + [Fact] + public async Task CaptureEventStopAsync_FailsActivityOnException() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + var activity = componentsActivitySource.StartEventActivity("TestComponent", "OnClick", "onclick"); + var exception = new InvalidOperationException("Test exception"); + var task = Task.FromException(exception); + + // Act + await ComponentsActivitySource.CaptureEventStopAsync(task, activity); + + // Assert + Assert.True(activity!.IsStopped); + Assert.Equal(ActivityStatusCode.Error, activity.Status); + Assert.Equal(exception.GetType().FullName, activity.GetTagItem("error.type")); + } + + [Fact] + public void StartCircuitActivity_HandlesNullValues() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + + // Act + var activity = componentsActivitySource.StartCircuitActivity(null, default); + + // Assert + Assert.NotNull(activity); + Assert.Equal("Circuit ", activity.DisplayName); + } + + [Fact] + public void StartRouteActivity_HandlesNullValues() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + + // Act + var activity = componentsActivitySource.StartRouteActivity(null, null); + + // Assert + Assert.NotNull(activity); + Assert.Equal("Route [unknown path] -> [unknown component]", activity.DisplayName); + } + + [Fact] + public void StartEventActivity_HandlesNullValues() + { + // Arrange + var componentsActivitySource = new ComponentsActivitySource(); + + // Act + var activity = componentsActivitySource.StartEventActivity(null, null, null); + + // Assert + Assert.NotNull(activity); + Assert.Equal("Event [unknown attribute] -> [unknown component].[unknown method]", activity.DisplayName); + } +} diff --git a/src/aspnetcore/src/Components/Components/test/ComponentsMetricsTest.cs b/src/aspnetcore/src/Components/Components/test/ComponentsMetricsTest.cs new file mode 100644 index 00000000000..e8b466d5e45 --- /dev/null +++ b/src/aspnetcore/src/Components/Components/test/ComponentsMetricsTest.cs @@ -0,0 +1,402 @@ +// 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.Diagnostics.Metrics; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.InternalTesting; +using Moq; + +namespace Microsoft.AspNetCore.Components; + +public class ComponentsMetricsTest +{ + private readonly TestMeterFactory _meterFactory; + + public ComponentsMetricsTest() + { + _meterFactory = new TestMeterFactory(); + } + + [Fact] + public void Constructor_CreatesMetersCorrectly() + { + // Arrange & Act + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Assert + Assert.Equal(2, _meterFactory.Meters.Count); + Assert.Contains(_meterFactory.Meters, m => m.Name == ComponentsMetrics.MeterName); + Assert.Contains(_meterFactory.Meters, m => m.Name == ComponentsMetrics.LifecycleMeterName); + } + + [Fact] + public void Navigation_RecordsMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var navigationCounter = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.navigation"); + + // Act + componentsMetrics.Navigation("TestComponent", "/test-route"); + + // Assert + var measurements = navigationCounter.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.Equal(1, measurements[0].Value); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("/test-route", Assert.Contains("aspnetcore.components.route", measurements[0].Tags)); + } + + [Fact] + public void IsNavigationEnabled_ReturnsCorrectState() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Create a collector to ensure the meter is enabled + using var navigationCounter = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.navigation"); + + // Act & Assert + Assert.True(componentsMetrics.IsNavigationEnabled); + } + + [Fact] + public async Task CaptureEventDuration_RecordsSuccessMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.event_handler"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureEventDuration(Task.CompletedTask, startTimestamp, + "TestComponent", "OnClick", "onclick"); + + // Assert + var measurements = eventDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("OnClick", Assert.Contains("aspnetcore.components.method", measurements[0].Tags)); + Assert.Equal("onclick", Assert.Contains("aspnetcore.components.attribute.name", measurements[0].Tags)); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } + + [Fact] + public async Task CaptureEventDuration_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.event_handler"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureEventDuration(Task.FromException(new InvalidOperationException()), + startTimestamp, "TestComponent", "OnClick", "onclick"); + + // Assert + var measurements = eventDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("OnClick", Assert.Contains("aspnetcore.components.method", measurements[0].Tags)); + Assert.Equal("onclick", Assert.Contains("aspnetcore.components.attribute.name", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void FailEventSync_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.event_handler"); + var exception = new InvalidOperationException(); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + componentsMetrics.FailEventSync(exception, startTimestamp, + "TestComponent", "OnClick", "onclick"); + + // Assert + var measurements = eventDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("OnClick", Assert.Contains("aspnetcore.components.method", measurements[0].Tags)); + Assert.Equal("onclick", Assert.Contains("aspnetcore.components.attribute.name", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void IsEventEnabled_ReturnsCorrectState() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Create a collector to ensure the meter is enabled + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.event_handler"); + + // Act & Assert + Assert.True(componentsMetrics.IsEventEnabled); + } + + [Fact] + public async Task CaptureParametersDuration_RecordsSuccessMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureParametersDuration(Task.CompletedTask, startTimestamp, "TestComponent"); + + // Assert + var measurements = parametersDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } + + [Fact] + public async Task CaptureParametersDuration_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureParametersDuration(Task.FromException(new InvalidOperationException()), + startTimestamp, "TestComponent"); + + // Assert + var measurements = parametersDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void FailParametersSync_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters"); + var exception = new InvalidOperationException(); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + componentsMetrics.FailParametersSync(exception, startTimestamp, "TestComponent"); + + // Assert + var measurements = parametersDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void IsParametersEnabled_ReturnsCorrectState() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Create a collector to ensure the meter is enabled + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters"); + + // Act & Assert + Assert.True(componentsMetrics.IsParametersEnabled); + } + + [Fact] + public async Task CaptureBatchDuration_RecordsSuccessMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureBatchDuration(Task.CompletedTask, startTimestamp, 25); + + // Assert + var measurements = batchDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal(50, Assert.Contains("aspnetcore.components.diff.length", measurements[0].Tags)); + Assert.DoesNotContain("error.type", measurements[0].Tags); + } + + [Fact] + public async Task CaptureBatchDuration_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff"); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + await Task.Delay(10); // Small delay to ensure measureable duration + await componentsMetrics.CaptureBatchDuration(Task.FromException(new InvalidOperationException()), + startTimestamp, 25); + + // Assert + var measurements = batchDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal(50, Assert.Contains("aspnetcore.components.diff.length", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void FailBatchSync_RecordsErrorMetric() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff"); + var exception = new InvalidOperationException(); + + // Act + var startTimestamp = Stopwatch.GetTimestamp(); + componentsMetrics.FailBatchSync(exception, startTimestamp); + + // Assert + var measurements = batchDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(measurements); + Assert.True(measurements[0].Value > 0); + Assert.Equal(0, Assert.Contains("aspnetcore.components.diff.length", measurements[0].Tags)); + Assert.Equal("System.InvalidOperationException", Assert.Contains("error.type", measurements[0].Tags)); + } + + [Fact] + public void IsBatchEnabled_ReturnsCorrectState() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Create a collector to ensure the meter is enabled + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff"); + + // Act & Assert + Assert.True(componentsMetrics.IsBatchEnabled); + } + + [Fact] + public async Task ComponentLifecycle_RecordsAllMetricsCorrectly() + { + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + using var navigationCounter = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.navigation"); + using var eventDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.MeterName, "aspnetcore.components.event_handler"); + using var parametersDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.update_parameters"); + using var batchDurationHistogram = new MetricCollector(_meterFactory, + ComponentsMetrics.LifecycleMeterName, "aspnetcore.components.render_diff"); + + // Act - Simulate a component lifecycle + // 1. Navigation + componentsMetrics.Navigation("TestComponent", "/test-route"); + + // 2. Parameters update + var startTimestamp1 = Stopwatch.GetTimestamp(); + await Task.Delay(10); + await componentsMetrics.CaptureParametersDuration(Task.CompletedTask, startTimestamp1, "TestComponent"); + + // 3. Event handler + var startTimestamp2 = Stopwatch.GetTimestamp(); + await Task.Delay(10); + await componentsMetrics.CaptureEventDuration(Task.CompletedTask, startTimestamp2, + "TestComponent", "OnClick", "onclick"); + + // 4. Rendering batch + var startTimestamp3 = Stopwatch.GetTimestamp(); + await Task.Delay(10); + await componentsMetrics.CaptureBatchDuration(Task.CompletedTask, startTimestamp3, 15); + + // Assert + var navigationMeasurements = navigationCounter.GetMeasurementSnapshot(); + var eventMeasurements = eventDurationHistogram.GetMeasurementSnapshot(); + var parametersMeasurements = parametersDurationHistogram.GetMeasurementSnapshot(); + var batchMeasurements = batchDurationHistogram.GetMeasurementSnapshot(); + + Assert.Single(navigationMeasurements); + Assert.Single(eventMeasurements); + Assert.Single(parametersMeasurements); + Assert.Single(batchMeasurements); + + // Check navigation + Assert.Equal(1, navigationMeasurements[0].Value); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", navigationMeasurements[0].Tags)); + Assert.Equal("/test-route", Assert.Contains("aspnetcore.components.route", navigationMeasurements[0].Tags)); + + // Check event duration + Assert.True(eventMeasurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", eventMeasurements[0].Tags)); + Assert.Equal("OnClick", Assert.Contains("aspnetcore.components.method", eventMeasurements[0].Tags)); + Assert.Equal("onclick", Assert.Contains("aspnetcore.components.attribute.name", eventMeasurements[0].Tags)); + + // Check parameters duration + Assert.True(parametersMeasurements[0].Value > 0); + Assert.Equal("TestComponent", Assert.Contains("aspnetcore.components.type", parametersMeasurements[0].Tags)); + + // Check batch duration + Assert.True(batchMeasurements[0].Value > 0); + Assert.Equal(20, Assert.Contains("aspnetcore.components.diff.length", batchMeasurements[0].Tags)); + } + + [Fact] + public void Dispose_DisposesAllMeters() + { + // This test verifies that disposing ComponentsMetrics properly disposes its meters + // Since we can't easily test disposal directly, we'll verify meters are created and assume + // the dispose method works as expected + + // Arrange + var componentsMetrics = new ComponentsMetrics(_meterFactory); + + // Act - We're not actually asserting anything here, just ensuring no exceptions are thrown + componentsMetrics.Dispose(); + + // Assert - MeterFactory.Create was called twice in constructor + Assert.Equal(2, _meterFactory.Meters.Count); + } +} diff --git a/src/aspnetcore/src/Components/Components/test/Rendering/RenderingMetricsTest.cs b/src/aspnetcore/src/Components/Components/test/Rendering/RenderingMetricsTest.cs deleted file mode 100644 index 7339ebbf5de..00000000000 --- a/src/aspnetcore/src/Components/Components/test/Rendering/RenderingMetricsTest.cs +++ /dev/null @@ -1,238 +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.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.Extensions.Diagnostics.Metrics.Testing; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.InternalTesting; -using Moq; - -namespace Microsoft.AspNetCore.Components.Rendering; - -public class RenderingMetricsTest -{ - private readonly TestMeterFactory _meterFactory; - - public RenderingMetricsTest() - { - _meterFactory = new TestMeterFactory(); - } - - [Fact] - public void Constructor_CreatesMetersCorrectly() - { - // Arrange & Act - var renderingMetrics = new RenderingMetrics(_meterFactory); - - // Assert - Assert.Single(_meterFactory.Meters); - Assert.Equal(RenderingMetrics.MeterName, _meterFactory.Meters[0].Name); - } - - [Fact] - public void RenderStart_IncreasesCounters() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - using var totalCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.count"); - using var activeCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders"); - - var componentType = "TestComponent"; - - // Act - renderingMetrics.RenderStart(componentType); - - // Assert - var totalMeasurements = totalCounter.GetMeasurementSnapshot(); - var activeMeasurements = activeCounter.GetMeasurementSnapshot(); - - Assert.Single(totalMeasurements); - Assert.Equal(1, totalMeasurements[0].Value); - Assert.Equal(componentType, totalMeasurements[0].Tags["component.type"]); - - Assert.Single(activeMeasurements); - Assert.Equal(1, activeMeasurements[0].Value); - Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]); - } - - [Fact] - public void RenderEnd_DecreasesActiveCounterAndRecordsDuration() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - using var activeCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders"); - using var durationCollector = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration"); - - var componentType = "TestComponent"; - - // Act - var startTime = Stopwatch.GetTimestamp(); - Thread.Sleep(10); // Add a small delay to ensure a measurable duration - var endTime = Stopwatch.GetTimestamp(); - renderingMetrics.RenderEnd(componentType, null, startTime, endTime); - - // Assert - var activeMeasurements = activeCounter.GetMeasurementSnapshot(); - var durationMeasurements = durationCollector.GetMeasurementSnapshot(); - - Assert.Single(activeMeasurements); - Assert.Equal(-1, activeMeasurements[0].Value); - Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]); - - Assert.Single(durationMeasurements); - Assert.True(durationMeasurements[0].Value > 0); - Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]); - } - - [Fact] - public void RenderEnd_AddsErrorTypeTag_WhenExceptionIsProvided() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - using var durationCollector = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration"); - - var componentType = "TestComponent"; - var exception = new InvalidOperationException("Test exception"); - - // Act - var startTime = Stopwatch.GetTimestamp(); - Thread.Sleep(10); - var endTime = Stopwatch.GetTimestamp(); - renderingMetrics.RenderEnd(componentType, exception, startTime, endTime); - - // Assert - var durationMeasurements = durationCollector.GetMeasurementSnapshot(); - - Assert.Single(durationMeasurements); - Assert.True(durationMeasurements[0].Value > 0); - Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]); - Assert.Equal(exception.GetType().FullName, durationMeasurements[0].Tags["error.type"]); - } - - [Fact] - public void IsDurationEnabled_ReturnsMeterEnabledState() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - - // Create a collector to ensure the meter is enabled - using var durationCollector = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration"); - - // Act & Assert - Assert.True(renderingMetrics.IsDurationEnabled()); - } - - [Fact] - public void FullRenderingLifecycle_RecordsAllMetricsCorrectly() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - using var totalCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.count"); - using var activeCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders"); - using var durationCollector = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration"); - - var componentType = "TestComponent"; - - // Act - Simulating a full rendering lifecycle - var startTime = Stopwatch.GetTimestamp(); - - // 1. Component render starts - renderingMetrics.RenderStart(componentType); - - // 2. Component render ends - Thread.Sleep(10); // Add a small delay to ensure a measurable duration - var endTime = Stopwatch.GetTimestamp(); - renderingMetrics.RenderEnd(componentType, null, startTime, endTime); - - // Assert - var totalMeasurements = totalCounter.GetMeasurementSnapshot(); - var activeMeasurements = activeCounter.GetMeasurementSnapshot(); - var durationMeasurements = durationCollector.GetMeasurementSnapshot(); - - // Total render count should have 1 measurement with value 1 - Assert.Single(totalMeasurements); - Assert.Equal(1, totalMeasurements[0].Value); - Assert.Equal(componentType, totalMeasurements[0].Tags["component.type"]); - - // Active render count should have 2 measurements (1 for start, -1 for end) - Assert.Equal(2, activeMeasurements.Count); - Assert.Equal(1, activeMeasurements[0].Value); - Assert.Equal(-1, activeMeasurements[1].Value); - Assert.Equal(componentType, activeMeasurements[0].Tags["component.type"]); - Assert.Equal(componentType, activeMeasurements[1].Tags["component.type"]); - - // Duration should have 1 measurement with a positive value - Assert.Single(durationMeasurements); - Assert.True(durationMeasurements[0].Value > 0); - Assert.Equal(componentType, durationMeasurements[0].Tags["component.type"]); - } - - [Fact] - public void MultipleRenders_TracksMetricsIndependently() - { - // Arrange - var renderingMetrics = new RenderingMetrics(_meterFactory); - using var totalCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.count"); - using var activeCounter = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.active_renders"); - using var durationCollector = new MetricCollector(_meterFactory, - RenderingMetrics.MeterName, "aspnetcore.components.rendering.duration"); - - var componentType1 = "TestComponent1"; - var componentType2 = "TestComponent2"; - - // Act - // First component render - var startTime1 = Stopwatch.GetTimestamp(); - renderingMetrics.RenderStart(componentType1); - - // Second component render starts while first is still rendering - var startTime2 = Stopwatch.GetTimestamp(); - renderingMetrics.RenderStart(componentType2); - - // First component render ends - Thread.Sleep(5); - var endTime1 = Stopwatch.GetTimestamp(); - renderingMetrics.RenderEnd(componentType1, null, startTime1, endTime1); - - // Second component render ends - Thread.Sleep(5); - var endTime2 = Stopwatch.GetTimestamp(); - renderingMetrics.RenderEnd(componentType2, null, startTime2, endTime2); - - // Assert - var totalMeasurements = totalCounter.GetMeasurementSnapshot(); - var activeMeasurements = activeCounter.GetMeasurementSnapshot(); - var durationMeasurements = durationCollector.GetMeasurementSnapshot(); - - // Should have 2 total render counts (one for each component) - Assert.Equal(2, totalMeasurements.Count); - Assert.Contains(totalMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType1); - Assert.Contains(totalMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType2); - - // Should have 4 active render counts (start and end for each component) - Assert.Equal(4, activeMeasurements.Count); - Assert.Contains(activeMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType1); - Assert.Contains(activeMeasurements, m => m.Value == 1 && m.Tags["component.type"] as string == componentType2); - Assert.Contains(activeMeasurements, m => m.Value == -1 && m.Tags["component.type"] as string == componentType1); - Assert.Contains(activeMeasurements, m => m.Value == -1 && m.Tags["component.type"] as string == componentType2); - - // Should have 2 duration measurements (one for each component) - Assert.Equal(2, durationMeasurements.Count); - Assert.Contains(durationMeasurements, m => m.Value > 0 && m.Tags["component.type"] as string == componentType1); - Assert.Contains(durationMeasurements, m => m.Value > 0 && m.Tags["component.type"] as string == componentType2); - } -} diff --git a/src/aspnetcore/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs b/src/aspnetcore/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs index 302dec7dcb1..219b2e33b9d 100644 --- a/src/aspnetcore/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs +++ b/src/aspnetcore/src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs @@ -75,6 +75,10 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection services.TryAddScoped(); services.TryAddScoped(); + RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(services, RenderMode.InteractiveWebAssembly); + + ComponentsMetricsServiceCollectionExtensions.AddComponentsMetrics(services); + ComponentsMetricsServiceCollectionExtensions.AddComponentsTracing(services); // Form handling services.AddSupplyValueFromFormProvider(); diff --git a/src/aspnetcore/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/aspnetcore/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index e99574aa881..d7e6a20d698 100644 --- a/src/aspnetcore/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/aspnetcore/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -149,7 +149,7 @@ private static void InitializeResourceCollection(HttpContext httpContext) var resourceCollectionProvider = resourceCollectionUrl != null ? httpContext.RequestServices.GetService() : null; if (resourceCollectionUrl != null && resourceCollectionProvider != null) { - resourceCollectionProvider.SetResourceCollectionUrl(resourceCollectionUrl.Url); + resourceCollectionProvider.ResourceCollectionUrl = resourceCollectionUrl.Url; resourceCollectionProvider.SetResourceCollection(resourceCollection ?? ResourceAssetCollection.Empty); } } diff --git a/src/aspnetcore/src/Components/Endpoints/test/RazorComponentsServiceCollectionExtensionsTest.cs b/src/aspnetcore/src/Components/Endpoints/test/RazorComponentsServiceCollectionExtensionsTest.cs index 09dfd3a9040..40efccecb1b 100644 --- a/src/aspnetcore/src/Components/Endpoints/test/RazorComponentsServiceCollectionExtensionsTest.cs +++ b/src/aspnetcore/src/Components/Endpoints/test/RazorComponentsServiceCollectionExtensionsTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms.Mapping; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Hosting; @@ -92,7 +93,12 @@ private Dictionary MultiRegistrationServiceTypes { typeof(SupplyParameterFromFormValueProvider), typeof(SupplyParameterFromQueryValueProvider), - } + }, + [typeof(IPersistentServiceRegistration)] = new[] + { + typeof(ResourceCollectionProvider), + typeof(AntiforgeryStateProvider), + }, }; } } diff --git a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitFactory.cs b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitFactory.cs index cb8573bd81b..6683c2e20d7 100644 --- a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitFactory.cs +++ b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitFactory.cs @@ -66,6 +66,7 @@ public async ValueTask CreateCircuitHostAsync( { navigationManager.Initialize(baseUri, uri); } + var componentsActivitySource = scope.ServiceProvider.GetService(); if (components.Count > 0) { @@ -109,6 +110,7 @@ public async ValueTask CreateCircuitHostAsync( navigationManager, circuitHandlers, _circuitMetrics, + componentsActivitySource, _loggerFactory.CreateLogger()); Log.CreatedCircuit(_logger, circuitHost); diff --git a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs index b8bc2b05e15..38b50461ce3 100644 --- a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitHost.cs @@ -25,6 +25,7 @@ internal partial class CircuitHost : IAsyncDisposable private readonly RemoteNavigationManager _navigationManager; private readonly ILogger _logger; private readonly CircuitMetrics? _circuitMetrics; + private readonly ComponentsActivitySource? _componentsActivitySource; private Func, Task> _dispatchInboundActivity; private CircuitHandler[] _circuitHandlers; private bool _initialized; @@ -51,6 +52,7 @@ public CircuitHost( RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, CircuitMetrics? circuitMetrics, + ComponentsActivitySource? componentsActivitySource, ILogger logger) { CircuitId = circuitId; @@ -69,6 +71,7 @@ public CircuitHost( _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _circuitHandlers = circuitHandlers ?? throw new ArgumentNullException(nameof(circuitHandlers)); _circuitMetrics = circuitMetrics; + _componentsActivitySource = componentsActivitySource; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); Services = scope.ServiceProvider; @@ -105,7 +108,7 @@ public CircuitHost( // InitializeAsync is used in a fire-and-forget context, so it's responsible for its own // error handling. - public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, CancellationToken cancellationToken) + public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, ActivityContext httpContext, CancellationToken cancellationToken) { Log.InitializationStarted(_logger); @@ -115,13 +118,17 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, C { throw new InvalidOperationException("The circuit host is already initialized."); } + Activity? activity = null; try { _initialized = true; // We're ready to accept incoming JSInterop calls from here on + activity = _componentsActivitySource?.StartCircuitActivity(CircuitId.Id, httpContext); + _startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; + // We only run the handlers in case we are in a Blazor Server scenario, which renders - // the components inmediately during start. + // the components immediately during start. // On Blazor Web scenarios we delay running these handlers until the first UpdateRootComponents call // We do this so that the handlers can have access to the restored application state. if (Descriptors.Count > 0) @@ -162,9 +169,13 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, C _isFirstUpdate = Descriptors.Count == 0; Log.InitializationSucceeded(_logger); + + activity?.Stop(); } catch (Exception ex) { + _componentsActivitySource?.FailCircuitActivity(activity, ex); + // Report errors asynchronously. InitializeAsync is designed not to throw. Log.InitializationFailed(_logger, ex); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); @@ -235,7 +246,6 @@ await Renderer.Dispatcher.InvokeAsync(async () => private async Task OnCircuitOpenedAsync(CancellationToken cancellationToken) { Log.CircuitOpened(_logger, CircuitId); - _startTime = (_circuitMetrics != null && _circuitMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0; _circuitMetrics?.OnCircuitOpened(); Renderer.Dispatcher.AssertAccess(); diff --git a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitMetrics.cs b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitMetrics.cs index fb772c119f5..9432c6d1867 100644 --- a/src/aspnetcore/src/Components/Server/src/Circuits/CircuitMetrics.cs +++ b/src/aspnetcore/src/Components/Server/src/Circuits/CircuitMetrics.cs @@ -12,7 +12,6 @@ internal sealed class CircuitMetrics : IDisposable public const string MeterName = "Microsoft.AspNetCore.Components.Server.Circuits"; private readonly Meter _meter; - private readonly Counter _circuitTotalCounter; private readonly UpDownCounter _circuitActiveCounter; private readonly UpDownCounter _circuitConnectedCounter; private readonly Histogram _circuitDuration; @@ -23,81 +22,63 @@ public CircuitMetrics(IMeterFactory meterFactory) _meter = meterFactory.Create(MeterName); - _circuitTotalCounter = _meter.CreateCounter( - "aspnetcore.components.circuits.count", - unit: "{circuits}", - description: "Number of active circuits."); - _circuitActiveCounter = _meter.CreateUpDownCounter( - "aspnetcore.components.circuits.active_circuits", - unit: "{circuits}", - description: "Number of active circuits."); + "aspnetcore.components.circuit.active", + unit: "{circuit}", + description: "Number of active circuits in memory."); _circuitConnectedCounter = _meter.CreateUpDownCounter( - "aspnetcore.components.circuits.connected_circuits", - unit: "{circuits}", - description: "Number of disconnected circuits."); + "aspnetcore.components.circuit.connected", + unit: "{circuit}", + description: "Number of circuits connected to client."); _circuitDuration = _meter.CreateHistogram( - "aspnetcore.components.circuits.duration", + "aspnetcore.components.circuit.duration", unit: "s", - description: "Duration of circuit.", - advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.VeryLongSecondsBucketBoundaries }); + description: "Duration of circuit lifetime and their total count.", + advice: new InstrumentAdvice { HistogramBucketBoundaries = MetricsConstants.BlazorCircuitSecondsBucketBoundaries }); } public void OnCircuitOpened() { - var tags = new TagList(); - if (_circuitActiveCounter.Enabled) { - _circuitActiveCounter.Add(1, tags); - } - if (_circuitTotalCounter.Enabled) - { - _circuitTotalCounter.Add(1, tags); + _circuitActiveCounter.Add(1); } } public void OnConnectionUp() { - var tags = new TagList(); - if (_circuitConnectedCounter.Enabled) { - _circuitConnectedCounter.Add(1, tags); + _circuitConnectedCounter.Add(1); } } public void OnConnectionDown() { - var tags = new TagList(); - if (_circuitConnectedCounter.Enabled) { - _circuitConnectedCounter.Add(-1, tags); + _circuitConnectedCounter.Add(-1); } } public void OnCircuitDown(long startTimestamp, long currentTimestamp) { - // Tags must match request start. - var tags = new TagList(); - if (_circuitActiveCounter.Enabled) { - _circuitActiveCounter.Add(-1, tags); + _circuitActiveCounter.Add(-1); } if (_circuitConnectedCounter.Enabled) { - _circuitConnectedCounter.Add(-1, tags); + _circuitConnectedCounter.Add(-1); } if (_circuitDuration.Enabled) { var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); - _circuitDuration.Record(duration.TotalSeconds, tags); + _circuitDuration.Record(duration.TotalSeconds); } } diff --git a/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs index 31b29206212..7f2345bff74 100644 --- a/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/aspnetcore/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -60,11 +60,11 @@ public RemoteRenderer( public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); - protected override ResourceAssetCollection Assets => _resourceCollection ?? base.Assets; + protected internal override ResourceAssetCollection Assets => _resourceCollection ?? base.Assets; - protected override RendererInfo RendererInfo => _componentPlatform; + protected internal override RendererInfo RendererInfo => _componentPlatform; - protected override IComponentRenderMode? GetComponentRenderMode(IComponent component) => RenderMode.InteractiveServer; + protected internal override IComponentRenderMode? GetComponentRenderMode(IComponent component) => RenderMode.InteractiveServer; public Task AddComponentAsync(Type componentType, ParameterView parameters, string domElementSelector) { @@ -306,7 +306,7 @@ public Task OnRenderCompletedAsync(long incomingBatchId, string? errorMessageOrN } } - protected override IComponent ResolveComponentForRenderMode([DynamicallyAccessedMembers(Component)] Type componentType, int? parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode) + protected internal override IComponent ResolveComponentForRenderMode([DynamicallyAccessedMembers(Component)] Type componentType, int? parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode) => renderMode switch { InteractiveServerRenderMode or InteractiveAutoRenderMode => componentActivator.CreateInstance(componentType), @@ -369,7 +369,7 @@ private async Task CaptureAsyncExceptions(Task task) } } - private static partial class Log + private static new partial class Log { [LoggerMessage(100, LogLevel.Warning, "Unhandled exception rendering component: {Message}", EventName = "ExceptionRenderingComponent")] private static partial void UnhandledExceptionRenderingComponent(ILogger logger, string message, Exception exception); diff --git a/src/aspnetcore/src/Components/Server/src/ComponentHub.cs b/src/aspnetcore/src/Components/Server/src/ComponentHub.cs index b3308f17bfd..84561349ee4 100644 --- a/src/aspnetcore/src/Components/Server/src/ComponentHub.cs +++ b/src/aspnetcore/src/Components/Server/src/ComponentHub.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.AspNetCore.DataProtection; @@ -43,6 +44,7 @@ internal sealed partial class ComponentHub : Hub private readonly CircuitRegistry _circuitRegistry; private readonly ICircuitHandleRegistry _circuitHandleRegistry; private readonly ILogger _logger; + private readonly ActivityContext _httpContext; public ComponentHub( IServerComponentDeserializer serializer, @@ -60,6 +62,7 @@ public ComponentHub( _circuitRegistry = circuitRegistry; _circuitHandleRegistry = circuitHandleRegistry; _logger = logger; + _httpContext = ComponentsActivitySource.CaptureHttpContext(); } /// @@ -137,7 +140,7 @@ public async ValueTask StartCircuit(string baseUri, string uri, string s // SignalR message loop (we'd get a deadlock if any of the initialization // logic relied on receiving a subsequent message from SignalR), and it will // take care of its own errors anyway. - _ = circuitHost.InitializeAsync(store, Context.ConnectionAborted); + _ = circuitHost.InitializeAsync(store, _httpContext, Context.ConnectionAborted); // It's safe to *publish* the circuit now because nothing will be able // to run inside it until after InitializeAsync completes. diff --git a/src/aspnetcore/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/aspnetcore/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs index 08195b0218c..4c6eb34d27f 100644 --- a/src/aspnetcore/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs +++ b/src/aspnetcore/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Metrics; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Forms; @@ -62,11 +61,7 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti // user's configuration. So even if the user has multiple independent server-side // Components entrypoints, this lot is the same and repeated registrations are a no-op. - services.TryAddSingleton(s => - { - var meterFactory = s.GetService(); - return meterFactory != null ? new CircuitMetrics(meterFactory) : null; - }); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs index 47d7e7cf5a7..b68bdf8286f 100644 --- a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -193,7 +193,7 @@ public async Task InitializeAsync_InvokesHandlers() var circuitHost = TestCircuitHost.Create(handlers: new[] { handler1.Object, handler2.Object }); // Act - await circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), cancellationToken); + await circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), default, cancellationToken); // Assert handler1.VerifyAll(); @@ -236,7 +236,7 @@ public async Task InitializeAsync_RendersRootComponentsInParallel() // Act object initializeException = null; circuitHost.UnhandledException += (sender, eventArgs) => initializeException = eventArgs.ExceptionObject; - var initializeTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), cancellationToken); + var initializeTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), default, cancellationToken); await initializeTask.WaitAsync(initializeTimeout); // Assert: This was not reached only because an exception was thrown in InitializeAsync() @@ -266,7 +266,7 @@ public async Task InitializeAsync_ReportsOwnAsyncExceptions() }; // Act - var initializeAsyncTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), new CancellationToken()); + var initializeAsyncTask = circuitHost.InitializeAsync(new ProtectedPrerenderComponentApplicationStore(Mock.Of()), default, new CancellationToken()); // Assert: No synchronous exceptions handler.VerifyAll(); diff --git a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs index fd8c950dc6f..4c89b7ec182 100644 --- a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs +++ b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitIdFactoryTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits; -public class circuitIdFactoryTest +public class CircuitIdFactoryTest { [Fact] public void CreateCircuitId_Generates_NewRandomId() diff --git a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitMetricsTest.cs b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitMetricsTest.cs index 77012599663..8394b01036d 100644 --- a/src/aspnetcore/src/Components/Server/test/Circuits/CircuitMetricsTest.cs +++ b/src/aspnetcore/src/Components/Server/test/Circuits/CircuitMetricsTest.cs @@ -39,21 +39,15 @@ public void OnCircuitOpened_IncreasesCounters() { // Arrange var circuitMetrics = new CircuitMetrics(_meterFactory); - using var activeTotalCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.count"); using var activeCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.active_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.active"); // Act circuitMetrics.OnCircuitOpened(); // Assert - var totalMeasurements = activeTotalCounter.GetMeasurementSnapshot(); var activeMeasurements = activeCircuitCounter.GetMeasurementSnapshot(); - Assert.Single(totalMeasurements); - Assert.Equal(1, totalMeasurements[0].Value); - Assert.Single(activeMeasurements); Assert.Equal(1, activeMeasurements[0].Value); } @@ -64,7 +58,7 @@ public void OnConnectionUp_IncreasesConnectedCounter() // Arrange var circuitMetrics = new CircuitMetrics(_meterFactory); using var connectedCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.connected_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.connected"); // Act circuitMetrics.OnConnectionUp(); @@ -82,7 +76,7 @@ public void OnConnectionDown_DecreasesConnectedCounter() // Arrange var circuitMetrics = new CircuitMetrics(_meterFactory); using var connectedCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.connected_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.connected"); // Act circuitMetrics.OnConnectionDown(); @@ -95,20 +89,20 @@ public void OnConnectionDown_DecreasesConnectedCounter() } [Fact] - public void OnCircuitDown_UpdatesCountersAndRecordsDuration() + public async Task OnCircuitDown_UpdatesCountersAndRecordsDuration() { // Arrange var circuitMetrics = new CircuitMetrics(_meterFactory); using var activeCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.active_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.active"); using var connectedCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.connected_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.connected"); using var circuitDurationCollector = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.duration"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.duration"); // Act var startTime = Stopwatch.GetTimestamp(); - Thread.Sleep(10); // Add a small delay to ensure a measurable duration + await Task.Delay(10); // Add a small delay to ensure a measurable duration var endTime = Stopwatch.GetTimestamp(); circuitMetrics.OnCircuitDown(startTime, endTime); @@ -135,7 +129,7 @@ public void IsDurationEnabled_ReturnsMeterEnabledState() // Create a collector to ensure the meter is enabled using var circuitDurationCollector = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.duration"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.duration"); // Act & Assert Assert.True(circuitMetrics.IsDurationEnabled()); @@ -146,14 +140,12 @@ public void FullCircuitLifecycle_RecordsAllMetricsCorrectly() { // Arrange var circuitMetrics = new CircuitMetrics(_meterFactory); - using var totalCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.count"); using var activeCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.active_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.active"); using var connectedCircuitCounter = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.connected_circuits"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.connected"); using var circuitDurationCollector = new MetricCollector(_meterFactory, - CircuitMetrics.MeterName, "aspnetcore.components.circuits.duration"); + CircuitMetrics.MeterName, "aspnetcore.components.circuit.duration"); // Act - Simulating a full circuit lifecycle var startTime = Stopwatch.GetTimestamp(); @@ -176,15 +168,10 @@ public void FullCircuitLifecycle_RecordsAllMetricsCorrectly() circuitMetrics.OnCircuitDown(startTime, endTime); // Assert - var totalMeasurements = totalCounter.GetMeasurementSnapshot(); var activeMeasurements = activeCircuitCounter.GetMeasurementSnapshot(); var connectedMeasurements = connectedCircuitCounter.GetMeasurementSnapshot(); var durationMeasurements = circuitDurationCollector.GetMeasurementSnapshot(); - // Total circuit count should have 1 measurement with value 1 - Assert.Single(totalMeasurements); - Assert.Equal(1, totalMeasurements[0].Value); - // Active circuit count should have 2 measurements (1 for open, -1 for close) Assert.Equal(2, activeMeasurements.Count); Assert.Equal(1, activeMeasurements[0].Value); diff --git a/src/aspnetcore/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/aspnetcore/src/Components/Server/test/Circuits/TestCircuitHost.cs index eeb86ad3a63..11e9f7ed4d6 100644 --- a/src/aspnetcore/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/aspnetcore/src/Components/Server/test/Circuits/TestCircuitHost.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.Diagnostics; using System.Diagnostics.Metrics; using Microsoft.AspNetCore.InternalTesting; using Microsoft.AspNetCore.SignalR; @@ -15,8 +16,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits; internal class TestCircuitHost : CircuitHost { - private TestCircuitHost(CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, CircuitMetrics circuitMetrics, ILogger logger) - : base(circuitId, scope, options, client, renderer, descriptors, jsRuntime, navigationManager, circuitHandlers, circuitMetrics, logger) + private TestCircuitHost(CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, CircuitMetrics circuitMetrics, ComponentsActivitySource componentsActivitySource, ILogger logger) + : base(circuitId, scope, options, client, renderer, descriptors, jsRuntime, navigationManager, circuitHandlers, circuitMetrics, componentsActivitySource, logger) { } @@ -38,6 +39,7 @@ public static CircuitHost Create( .Returns(jsRuntime); var serverComponentDeserializer = Mock.Of(); var circuitMetrics = new CircuitMetrics(new TestMeterFactory()); + var componentsActivitySource = new ComponentsActivitySource(); if (remoteRenderer == null) { @@ -64,6 +66,7 @@ public static CircuitHost Create( navigationManager, handlers, circuitMetrics, + componentsActivitySource, NullLogger.Instance); } } diff --git a/src/aspnetcore/src/Components/Shared/src/ResourceCollectionProvider.cs b/src/aspnetcore/src/Components/Shared/src/ResourceCollectionProvider.cs index 78ebef9d13d..030d4b0e684 100644 --- a/src/aspnetcore/src/Components/Shared/src/ResourceCollectionProvider.cs +++ b/src/aspnetcore/src/Components/Shared/src/ResourceCollectionProvider.cs @@ -11,36 +11,27 @@ namespace Microsoft.AspNetCore.Components; internal class ResourceCollectionProvider { - private const string ResourceCollectionUrlKey = "__ResourceCollectionUrl"; private string? _url; - private ResourceAssetCollection? _resourceCollection; - private readonly PersistentComponentState _state; - private readonly IJSRuntime _jsRuntime; - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Strings are not trimmed")] - public ResourceCollectionProvider(PersistentComponentState state, IJSRuntime jsRuntime) + [SupplyParameterFromPersistentComponentState] + public string? ResourceCollectionUrl { - _state = state; - _jsRuntime = jsRuntime; - _ = _state.TryTakeFromJson(ResourceCollectionUrlKey, out _url); + get => _url; + set + { + if (_url != null) + { + throw new InvalidOperationException("The resource collection URL has already been set."); + } + _url = value; + } } - [MemberNotNull(nameof(_url))] - [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Strings are not trimmed")] - internal void SetResourceCollectionUrl(string url) + private ResourceAssetCollection? _resourceCollection; + private readonly IJSRuntime _jsRuntime; + public ResourceCollectionProvider(IJSRuntime jsRuntime) { - if (_url != null) - { - throw new InvalidOperationException("The resource collection URL has already been set."); - } - _url = url; - PersistingComponentStateSubscription registration = default; - registration = _state.RegisterOnPersisting(() => - { - _state.PersistAsJson(ResourceCollectionUrlKey, _url); - registration.Dispose(); - return Task.CompletedTask; - }, RenderMode.InteractiveWebAssembly); + _jsRuntime = jsRuntime; } internal async Task GetResourceCollection() diff --git a/src/aspnetcore/src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.cs b/src/aspnetcore/src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.cs index 104dcb930d6..a77b3e98c4e 100644 --- a/src/aspnetcore/src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.cs +++ b/src/aspnetcore/src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.cs @@ -21,7 +21,6 @@ public partial class StaticHtmlRenderer : Renderer { private static readonly RendererInfo _componentPlatform = new RendererInfo("Static", isInteractive: false); - private static readonly Task CanceledRenderTask = Task.FromCanceled(new CancellationToken(canceled: true)); private readonly NavigationManager? _navigationManager; /// diff --git a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index 218936a9c1d..e90fdab63e7 100644 --- a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -308,6 +308,7 @@ internal void InitializeDefaultServices() Services.AddSupplyValueFromPersistentComponentStateProvider(); Services.AddSingleton(); Services.AddSingleton(); + RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Services, RenderMode.InteractiveWebAssembly); Services.AddLogging(builder => { builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance)); diff --git a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj index 90c9b18180d..957925b0327 100644 --- a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj +++ b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -21,8 +21,8 @@ - - + + diff --git a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/targets/Microsoft.AspNetCore.Components.WebAssembly.props b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/targets/Microsoft.AspNetCore.Components.WebAssembly.props index f8387e44dbd..6eb5c1ba4fe 100644 --- a/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/targets/Microsoft.AspNetCore.Components.WebAssembly.props +++ b/src/aspnetcore/src/Components/WebAssembly/WebAssembly/src/targets/Microsoft.AspNetCore.Components.WebAssembly.props @@ -2,6 +2,7 @@ $(MSBuildThisFileDirectory)blazor.webassembly.js false + Baseline;AddMethodToExistingType;AddStaticFieldToExistingType;NewTypeDefinition;ChangeCustomAttributes;AddInstanceFieldToExistingType;GenericAddMethodToExistingType;GenericUpdateMethod;UpdateParameters;GenericAddFieldToExistingType diff --git a/src/aspnetcore/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Emitters/ValidationsGenerator.Emitter.cs b/src/aspnetcore/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Emitters/ValidationsGenerator.Emitter.cs index 5d4e9fdaba5..4d90dfbb5b1 100644 --- a/src/aspnetcore/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Emitters/ValidationsGenerator.Emitter.cs +++ b/src/aspnetcore/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Emitters/ValidationsGenerator.Emitter.cs @@ -7,6 +7,7 @@ using System.Text; using Microsoft.CodeAnalysis.CSharp; using System.IO; +using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.Http.ValidationsGenerator; @@ -14,6 +15,7 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator { public static string GeneratedCodeConstructor => $@"global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(ValidationsGenerator).Assembly.FullName}"", ""{typeof(ValidationsGenerator).Assembly.GetName().Version}"")"; public static string GeneratedCodeAttribute => $"[{GeneratedCodeConstructor}]"; + private static readonly Regex InvalidNameCharsRegex = new("[^0-9A-Za-z_]", RegexOptions.Compiled); internal static void Emit(SourceProductionContext context, (InterceptableLocation? AddValidation, ImmutableArray ValidatableTypes) emitInputs) { @@ -238,12 +240,7 @@ private static void EmitValidatableMemberForCreate(ValidatableProperty member, C private static string SanitizeTypeName(string typeName) { - // Replace invalid characters with underscores and remove generic notation - return typeName - .Replace(".", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace(",", "_") - .Replace(" ", "_"); + // Replace invalid characters with underscores + return InvalidNameCharsRegex.Replace(typeName, "_"); } } diff --git a/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parameters.cs b/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parameters.cs index 7494e27efa2..f05f8099914 100644 --- a/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parameters.cs +++ b/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parameters.cs @@ -28,7 +28,7 @@ public async Task CanValidateParameters() var app = builder.Build(); -app.MapGet("/params", ( +app.MapPost("/params", ( // Skipped from validation because it is resolved as a service by IServiceProviderIsService TestService testService, // Skipped from validation because it is marked as a [FromKeyedService] parameter @@ -39,7 +39,8 @@ public async Task CanValidateParameters() [CustomValidation(ErrorMessage = "Value must be an even number")] int value4 = 4, [CustomValidation, Range(10, 100)] int value5 = 10, // Skipped from validation because it is marked as a [FromService] parameter - [FromServices] [Range(10, 100)] int? value6 = 4) => "OK"); + [FromServices] [Range(10, 100)] int? value6 = 4, + Dictionary? testDict = null) => "OK"); app.Run(); diff --git a/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableInfoResolver.g.verified.cs b/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableInfoResolver.g.verified.cs index 2b7c2a0c272..97eeacbed29 100644 --- a/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableInfoResolver.g.verified.cs +++ b/src/aspnetcore/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableInfoResolver.g.verified.cs @@ -67,6 +67,11 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System. validatableInfo = CreateTestService(); return true; } + if (type == typeof(global::System.Collections.Generic.Dictionary)) + { + validatableInfo = CreateDictionary_2(); + return true; + } return false; } @@ -92,6 +97,38 @@ private ValidatableTypeInfo CreateTestService() ] ); } + private ValidatableTypeInfo CreateDictionary_2() + { + return new GeneratedValidatableTypeInfo( + type: typeof(global::System.Collections.Generic.Dictionary), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::System.Collections.Generic.Dictionary), + propertyType: typeof(global::System.Collections.Generic.ICollection), + name: "System.Collections.Generic.IDictionary.Values", + displayName: "System.Collections.Generic.IDictionary.Values" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::System.Collections.Generic.Dictionary), + propertyType: typeof(global::System.Collections.Generic.IEnumerable), + name: "System.Collections.Generic.IReadOnlyDictionary.Values", + displayName: "System.Collections.Generic.IReadOnlyDictionary.Values" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::System.Collections.Generic.Dictionary), + propertyType: typeof(global::TestService), + name: "this[]", + displayName: "this[]" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::System.Collections.Generic.Dictionary), + propertyType: typeof(global::System.Collections.ICollection), + name: "System.Collections.IDictionary.Values", + displayName: "System.Collections.IDictionary.Values" + ), + ] + ); + } } diff --git a/src/aspnetcore/src/Shared/Metrics/MetricsConstants.cs b/src/aspnetcore/src/Shared/Metrics/MetricsConstants.cs index ff64c6fefca..cebbaba0e6a 100644 --- a/src/aspnetcore/src/Shared/Metrics/MetricsConstants.cs +++ b/src/aspnetcore/src/Shared/Metrics/MetricsConstants.cs @@ -11,6 +11,9 @@ internal static class MetricsConstants // Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration. See https://github.com/open-telemetry/semantic-conventions/issues/336 public static readonly IReadOnlyList LongSecondsBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300]; - // For Blazor/signalR sessions, which can last a long time. - public static readonly IReadOnlyList VeryLongSecondsBucketBoundaries = [0.5, 1, 2, 5, 10, 30, 60, 120, 300, 600, 1500, 60*60, 2 * 60 * 60, 4 * 60 * 60]; + // For blazor rendering, which should be very fast. + public static readonly IReadOnlyList BlazorRenderingSecondsBucketBoundaries = [0.000001, 0.00001, 0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]; + + // For blazor circuit sessions, which can last a long time. + public static readonly IReadOnlyList BlazorCircuitSecondsBucketBoundaries = [1, 3, 10, 30, 1 * 60, 3 * 60, 10 * 60, 30 * 60, 1 * 60 * 60, 3 * 60 * 60, 10 * 60 * 60, 24 * 60 * 60]; } diff --git a/src/aspnetcore/src/submodules/googletest/MODULE.bazel b/src/aspnetcore/src/submodules/googletest/MODULE.bazel index 44dce2ff2de..c2fb850a593 100644 --- a/src/aspnetcore/src/submodules/googletest/MODULE.bazel +++ b/src/aspnetcore/src/submodules/googletest/MODULE.bazel @@ -41,7 +41,7 @@ module( bazel_dep( name = "abseil-cpp", - version = "20250127.1", + version = "20250512.0", ) bazel_dep( name = "platforms", diff --git a/src/aspnetcore/src/submodules/googletest/ci/linux-presubmit.sh b/src/aspnetcore/src/submodules/googletest/ci/linux-presubmit.sh index 6491e79844b..c4ad5791461 100644 --- a/src/aspnetcore/src/submodules/googletest/ci/linux-presubmit.sh +++ b/src/aspnetcore/src/submodules/googletest/ci/linux-presubmit.sh @@ -31,15 +31,23 @@ set -euox pipefail -readonly LINUX_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20241218" -readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20250205" +readonly LINUX_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250430" +readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20250430" if [[ -z ${GTEST_ROOT:-} ]]; then GTEST_ROOT="$(realpath $(dirname ${0})/..)" fi +# Use Bazel Vendor mode to reduce reliance on external dependencies. +# See https://bazel.build/external/vendor and the Dockerfile for +# an explaination of how this works. +if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f "${KOKORO_GFILE_DIR}/distdir/googletest_vendor.tar.gz" ]]; then + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly --env=BAZEL_VENDOR_ARCHIVE=/distdir/googletest_vendor.tar.gz ${DOCKER_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--vendor_dir=/googletest_vendor ${BAZEL_EXTRA_ARGS:-}" +fi + if [[ -z ${STD:-} ]]; then - STD="c++17 c++20" + STD="c++17 c++20 c++23" fi # Test CMake + GCC @@ -93,18 +101,21 @@ time docker run \ --rm \ --env="CC=/usr/local/bin/gcc" \ --env="BAZEL_CXXOPTS=-std=c++17" \ + ${DOCKER_EXTRA_ARGS:-} \ ${LINUX_GCC_FLOOR_CONTAINER} \ + /bin/bash --login -c " /usr/local/bin/bazel test ... \ - --copt="-Wall" \ - --copt="-Werror" \ - --copt="-Wuninitialized" \ - --copt="-Wundef" \ - --copt="-Wno-error=pragmas" \ + --copt=\"-Wall\" \ + --copt=\"-Werror\" \ + --copt=\"-Wuninitialized\" \ + --copt=\"-Wundef\" \ + --copt=\"-Wno-error=pragmas\" \ --enable_bzlmod=false \ --features=external_include_paths \ --keep_going \ --show_timestamps \ - --test_output=errors + --test_output=errors \ + ${BAZEL_EXTRA_ARGS:-}" # Test GCC for std in ${STD}; do @@ -115,18 +126,21 @@ for std in ${STD}; do --rm \ --env="CC=/usr/local/bin/gcc" \ --env="BAZEL_CXXOPTS=-std=${std}" \ + ${DOCKER_EXTRA_ARGS:-} \ ${LINUX_LATEST_CONTAINER} \ - /usr/local/bin/bazel test ... \ - --copt="-Wall" \ - --copt="-Werror" \ - --copt="-Wuninitialized" \ - --copt="-Wundef" \ - --define="absl=${absl}" \ - --enable_bzlmod=true \ - --features=external_include_paths \ - --keep_going \ - --show_timestamps \ - --test_output=errors + /bin/bash --login -c " + /usr/local/bin/bazel test ... \ + --copt=\"-Wall\" \ + --copt=\"-Werror\" \ + --copt=\"-Wuninitialized\" \ + --copt=\"-Wundef\" \ + --define=\"absl=${absl}\" \ + --enable_bzlmod=true \ + --features=external_include_paths \ + --keep_going \ + --show_timestamps \ + --test_output=errors \ + ${BAZEL_EXTRA_ARGS:-}" done done @@ -139,19 +153,22 @@ for std in ${STD}; do --rm \ --env="CC=/opt/llvm/clang/bin/clang" \ --env="BAZEL_CXXOPTS=-std=${std}" \ + ${DOCKER_EXTRA_ARGS:-} \ ${LINUX_LATEST_CONTAINER} \ - /usr/local/bin/bazel test ... \ - --copt="--gcc-toolchain=/usr/local" \ - --copt="-Wall" \ - --copt="-Werror" \ - --copt="-Wuninitialized" \ - --copt="-Wundef" \ - --define="absl=${absl}" \ - --enable_bzlmod=true \ - --features=external_include_paths \ - --keep_going \ - --linkopt="--gcc-toolchain=/usr/local" \ - --show_timestamps \ - --test_output=errors + /bin/bash --login -c " + /usr/local/bin/bazel test ... \ + --copt=\"--gcc-toolchain=/usr/local\" \ + --copt=\"-Wall\" \ + --copt=\"-Werror\" \ + --copt=\"-Wuninitialized\" \ + --copt=\"-Wundef\" \ + --define=\"absl=${absl}\" \ + --enable_bzlmod=true \ + --features=external_include_paths \ + --keep_going \ + --linkopt=\"--gcc-toolchain=/usr/local\" \ + --show_timestamps \ + --test_output=errors \ + ${BAZEL_EXTRA_ARGS:-}" done done diff --git a/src/aspnetcore/src/submodules/googletest/ci/macos-presubmit.sh b/src/aspnetcore/src/submodules/googletest/ci/macos-presubmit.sh index 5370ed60d3d..63cf8148452 100644 --- a/src/aspnetcore/src/submodules/googletest/ci/macos-presubmit.sh +++ b/src/aspnetcore/src/submodules/googletest/ci/macos-presubmit.sh @@ -56,7 +56,7 @@ done # Test the Bazel build # If we are running on Kokoro, check for a versioned Bazel binary. -KOKORO_GFILE_BAZEL_BIN="bazel-8.0.0-darwin-x86_64" +KOKORO_GFILE_BAZEL_BIN="bazel-8.2.1-darwin-x86_64" if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}" chmod +x ${BAZEL_BIN} @@ -64,6 +64,12 @@ else BAZEL_BIN="bazel" fi +# Use Bazel Vendor mode to reduce reliance on external dependencies. +if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f "${KOKORO_GFILE_DIR}/distdir/googletest_vendor.tar.gz" ]]; then + tar -xf "${KOKORO_GFILE_DIR}/distdir/googletest_vendor.tar.gz" -C "${TMP}/" + BAZEL_EXTRA_ARGS="--vendor_dir=\"${TMP}/googletest_vendor\" ${BAZEL_EXTRA_ARGS:-}" +fi + cd ${GTEST_ROOT} for absl in 0 1; do ${BAZEL_BIN} test ... \ @@ -76,5 +82,6 @@ for absl in 0 1; do --features=external_include_paths \ --keep_going \ --show_timestamps \ - --test_output=errors + --test_output=errors \ + ${BAZEL_EXTRA_ARGS:-} done diff --git a/src/aspnetcore/src/submodules/googletest/ci/windows-presubmit.bat b/src/aspnetcore/src/submodules/googletest/ci/windows-presubmit.bat index e2664c538da..267e2e97f44 100644 --- a/src/aspnetcore/src/submodules/googletest/ci/windows-presubmit.bat +++ b/src/aspnetcore/src/submodules/googletest/ci/windows-presubmit.bat @@ -1,6 +1,6 @@ SETLOCAL ENABLEDELAYEDEXPANSION -SET BAZEL_EXE=%KOKORO_GFILE_DIR%\bazel-8.0.0-windows-x86_64.exe +SET BAZEL_EXE=%KOKORO_GFILE_DIR%\bazel-8.2.1-windows-x86_64.exe SET PATH=C:\Python34;%PATH% SET BAZEL_PYTHON=C:\python34\python.exe @@ -48,6 +48,14 @@ RMDIR /S /Q %CMAKE_BUILD_PATH% :: --output_user_root=C:\tmp causes Bazel to use a shorter path. SET BAZEL_VS=C:\Program Files\Microsoft Visual Studio\2022\Community +:: Use Bazel Vendor mode to reduce reliance on external dependencies. +IF EXIST "%KOKORO_GFILE_DIR%\distdir\googletest_vendor.tar.gz" ( + tar --force-local -xf "%KOKORO_GFILE_DIR%\distdir\googletest_vendor.tar.gz" -C c: + SET VENDOR_FLAG=--vendor_dir=c:\googletest_vendor +) ELSE ( + SET VENDOR_FLAG= +) + :: C++17 %BAZEL_EXE% ^ --output_user_root=C:\tmp ^ @@ -58,7 +66,8 @@ SET BAZEL_VS=C:\Program Files\Microsoft Visual Studio\2022\Community --enable_bzlmod=true ^ --keep_going ^ --test_output=errors ^ - --test_tag_filters=-no_test_msvc2017 + --test_tag_filters=-no_test_msvc2017 ^ + %VENDOR_FLAG% IF %errorlevel% neq 0 EXIT /B 1 :: C++20 @@ -71,5 +80,6 @@ IF %errorlevel% neq 0 EXIT /B 1 --enable_bzlmod=true ^ --keep_going ^ --test_output=errors ^ - --test_tag_filters=-no_test_msvc2017 + --test_tag_filters=-no_test_msvc2017 ^ + %VENDOR_FLAG% IF %errorlevel% neq 0 EXIT /B 1 diff --git a/src/aspnetcore/src/submodules/googletest/docs/reference/actions.md b/src/aspnetcore/src/submodules/googletest/docs/reference/actions.md index 0ebdc1e8433..2ca3a9fd078 100644 --- a/src/aspnetcore/src/submodules/googletest/docs/reference/actions.md +++ b/src/aspnetcore/src/submodules/googletest/docs/reference/actions.md @@ -48,8 +48,8 @@ functor, or lambda. | `InvokeWithoutArgs(object_pointer, &class::method)` | Invoke the method on the object, which takes no arguments. | | `InvokeArgument(arg1, arg2, ..., argk)` | Invoke the mock function's `N`-th (0-based) argument, which must be a function or a functor, with the `k` arguments. | -The return value of the invoked function is used as the return value of the -action. +The return value of the invoked function (except `InvokeArgument`) is used as +the return value of the action. When defining a callable to be used with `Invoke*()`, you can declare any unused parameters as `Unused`: diff --git a/src/aspnetcore/src/submodules/googletest/googletest/include/gtest/gtest.h b/src/aspnetcore/src/submodules/googletest/googletest/include/gtest/gtest.h index 7be0caaf515..cbe680c1adb 100644 --- a/src/aspnetcore/src/submodules/googletest/googletest/include/gtest/gtest.h +++ b/src/aspnetcore/src/submodules/googletest/googletest/include/gtest/gtest.h @@ -1693,7 +1693,7 @@ class WithParamInterface { // The current parameter value. Is also available in the test fixture's // constructor. - static const ParamType& GetParam() { + [[nodiscard]] static const ParamType& GetParam() { GTEST_CHECK_(parameter_ != nullptr) << "GetParam() can only be called inside a value-parameterized test " << "-- did you intend to write TEST_P instead of TEST_F?"; diff --git a/src/aspnetcore/src/submodules/googletest/googletest/test/googletest-param-test-test.cc b/src/aspnetcore/src/submodules/googletest/googletest/test/googletest-param-test-test.cc index bc130601978..fab977e078b 100644 --- a/src/aspnetcore/src/submodules/googletest/googletest/test/googletest-param-test-test.cc +++ b/src/aspnetcore/src/submodules/googletest/googletest/test/googletest-param-test-test.cc @@ -1174,7 +1174,7 @@ TEST_P(ParameterizedDerivedTest, SeesSequence) { class ParameterizedDeathTest : public ::testing::TestWithParam {}; TEST_F(ParameterizedDeathTest, GetParamDiesFromTestF) { - EXPECT_DEATH_IF_SUPPORTED(GetParam(), ".* value-parameterized test .*"); + EXPECT_DEATH_IF_SUPPORTED((void)GetParam(), ".* value-parameterized test .*"); } INSTANTIATE_TEST_SUITE_P(RangeZeroToFive, ParameterizedDerivedTest, diff --git a/src/aspnetcore/src/submodules/googletest/googletest_deps.bzl b/src/aspnetcore/src/submodules/googletest/googletest_deps.bzl index cadc244e85d..9eb7270bd57 100644 --- a/src/aspnetcore/src/submodules/googletest/googletest_deps.bzl +++ b/src/aspnetcore/src/submodules/googletest/googletest_deps.bzl @@ -17,9 +17,9 @@ def googletest_deps(): if not native.existing_rule("abseil-cpp"): http_archive( name = "abseil-cpp", - sha256 = "b396401fd29e2e679cace77867481d388c807671dc2acc602a0259eeb79b7811", - strip_prefix = "abseil-cpp-20250127.1", - urls = ["https://github.com/abseil/abseil-cpp/releases/download/20250127.1/abseil-cpp-20250127.1.tar.gz"], + sha256 = "7262daa7c1711406248c10f41026d685e88223bc92817d16fb93c19adb57f669", + strip_prefix = "abseil-cpp-20250512.0", + urls = ["https://github.com/abseil/abseil-cpp/releases/download/20250512.0/abseil-cpp-20250512.0.tar.gz"], ) if not native.existing_rule("fuchsia_sdk"): diff --git a/src/cecil/eng/Version.Details.xml b/src/cecil/eng/Version.Details.xml index 29f8b749402..2e677a36e50 100644 --- a/src/cecil/eng/Version.Details.xml +++ b/src/cecil/eng/Version.Details.xml @@ -1,12 +1,12 @@ - + - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + 094631c46bc1d3cf5f3749e57bb584671f9e10f3 diff --git a/src/cecil/eng/common/build.sh b/src/cecil/eng/common/build.sh index 36fba82a379..08f99154b7d 100755 --- a/src/cecil/eng/common/build.sh +++ b/src/cecil/eng/common/build.sh @@ -129,14 +129,14 @@ while [[ $# > 0 ]]; do -pack) pack=true ;; - -sourcebuild|-sb) + -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; - -productBuild|-pb) + -productbuild|-product-build|-pb) build=true product_build=true restore=true diff --git a/src/cecil/eng/common/core-templates/steps/source-build.yml b/src/cecil/eng/common/core-templates/steps/source-build.yml index f2a0f347fdd..0dde553c3eb 100644 --- a/src/cecil/eng/common/core-templates/steps/source-build.yml +++ b/src/cecil/eng/common/core-templates/steps/source-build.yml @@ -51,13 +51,12 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ + --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $baseRidArgs \ $portableBuildArgs \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml diff --git a/src/cecil/eng/common/darc-init.sh b/src/cecil/eng/common/darc-init.sh index 36dbd45e1ce..e889f439b8d 100755 --- a/src/cecil/eng/common/darc-init.sh +++ b/src/cecil/eng/common/darc-init.sh @@ -68,7 +68,7 @@ function InstallDarcCli { fi fi - local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." diff --git a/src/cecil/eng/common/tools.ps1 b/src/cecil/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/cecil/eng/common/tools.ps1 +++ b/src/cecil/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/cecil/eng/common/tools.sh b/src/cecil/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/cecil/eng/common/tools.sh +++ b/src/cecil/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/cecil/global.json b/src/cecil/global.json index 18ddac93379..bb8d1ea6767 100644 --- a/src/cecil/global.json +++ b/src/cecil/global.json @@ -1,9 +1,9 @@ { "tools": { - "dotnet": "10.0.100-preview.3.25201.16" + "dotnet": "10.0.100-preview.5.25230.108" }, "msbuild-sdks": { "Microsoft.Build.NoTargets": "3.7.0", - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25260.104" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25265.103" } } diff --git a/src/diagnostics/eng/InstallRuntimes.proj b/src/diagnostics/eng/InstallRuntimes.proj index b5ddc047d98..9dbfd87967d 100644 --- a/src/diagnostics/eng/InstallRuntimes.proj +++ b/src/diagnostics/eng/InstallRuntimes.proj @@ -35,7 +35,7 @@ -NoPath -SkipNonVersionedFiles -Architecture $(BuildArch) -InstallDir $(DotNetInstallRoot) - $([MSBuild]::NormalizeDirectory('$(DotNetInstallRoot)', 'shared', 'Microsoft.NETCore.App', '$(MicrosoftNETCoreAppRuntimewinx64Version)')) + $([MSBuild]::NormalizeDirectory('$(DotNetInstallRoot)', 'shared', 'Microsoft.NETCore.App', '$(MicrosoftNETCoreAppRefVersion)')) $(DotNetInstallRoot)Debugger.Tests.Versions.txt @@ -103,7 +103,7 @@ Condition="'$(LiveRuntimeDir)' != ''" Inputs="$(VersionsPropsPath)" Outputs="$(TestConfigFileName)"> - + <_LiveRuntimeFiles Include="$(LiveRuntimeDir)\**\*.*" /> diff --git a/src/diagnostics/eng/Version.Details.xml b/src/diagnostics/eng/Version.Details.xml index a5ecb2bfc1f..7a24e9a6c21 100644 --- a/src/diagnostics/eng/Version.Details.xml +++ b/src/diagnostics/eng/Version.Details.xml @@ -35,11 +35,7 @@ https://github.com/dotnet/dotnet 78c5fa9a48d469a19ab5a61c16c955c1f370b5be - - https://github.com/dotnet/dotnet - 78c5fa9a48d469a19ab5a61c16c955c1f370b5be - - + https://github.com/dotnet/dotnet 78c5fa9a48d469a19ab5a61c16c955c1f370b5be diff --git a/src/diagnostics/eng/Versions.props b/src/diagnostics/eng/Versions.props index 2d6fa857101..2768cb29591 100644 --- a/src/diagnostics/eng/Versions.props +++ b/src/diagnostics/eng/Versions.props @@ -16,8 +16,8 @@ - 10.0.0-preview.5.25229.109 - 10.0.0-preview.5.25229.109 + 10.0.0-preview.5.25229.109 + 10.0.0-preview.5.25229.109 10.0.0-preview.5.25229.109 10.0.0-preview.5.25229.109 @@ -114,8 +114,8 @@ - $(VSRedistCommonNetCoreSharedFrameworkx64100Version) - $(MicrosoftNETCoreAppRuntimewinx64Version) + $(MicrosoftNETCorePlatformsVersion) + $(MicrosoftNETCoreAppRefVersion) $(MicrosoftAspNetCoreAppRefInternalVersion) $(MicrosoftAspNetCoreAppRefVersion) net10.0 @@ -138,8 +138,8 @@ - $(VSRedistCommonNetCoreSharedFrameworkx64100Version) - $(MicrosoftNETCoreAppRuntimewinx64Version) + $(MicrosoftNETCorePlatformsVersion) + $(MicrosoftNETCoreAppRefVersion) $(MicrosoftAspNetCoreAppRefInternalVersion) $(MicrosoftAspNetCoreAppRefVersion) net10.0 diff --git a/src/diagnostics/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj b/src/diagnostics/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj index 0cf20b754fd..c0ab49ec2d5 100644 --- a/src/diagnostics/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj +++ b/src/diagnostics/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj @@ -61,14 +61,4 @@ - - - - - -trait Category=CDACCompatible - - - diff --git a/src/efcore/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/efcore/src/EFCore.Cosmos/EFCore.Cosmos.csproj index f690652f7fd..63d9e9b17e5 100644 --- a/src/efcore/src/EFCore.Cosmos/EFCore.Cosmos.csproj +++ b/src/efcore/src/EFCore.Cosmos/EFCore.Cosmos.csproj @@ -1,4 +1,4 @@ - + Azure Cosmos provider for Entity Framework Core. @@ -12,6 +12,7 @@ $(NoWarn);EF9101 $(NoWarn);EF9102 $(NoWarn);EF9103 + $(NoWarn);EF9104 @@ -49,7 +50,8 @@ - + + diff --git a/src/efcore/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs b/src/efcore/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs index ce37b16ece4..c34b68cd07b 100644 --- a/src/efcore/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs +++ b/src/efcore/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs @@ -59,6 +59,7 @@ public static T CoalesceUndefined( /// The property to search. /// The keyword to search for. /// if the property contains the keyword; otherwise, . + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public static bool FullTextContains(this DbFunctions _, string property, string keyword) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContains))); @@ -69,6 +70,7 @@ public static bool FullTextContains(this DbFunctions _, string property, string /// The property to search. /// The keywords to search for. /// if the property contains all the keywords; otherwise, . + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public static bool FullTextContainsAll(this DbFunctions _, string property, params string[] keywords) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAll))); @@ -79,6 +81,7 @@ public static bool FullTextContainsAll(this DbFunctions _, string property, para /// The property to search. /// The keywords to search for. /// if the property contains any of the keywords; otherwise, . + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public static bool FullTextContainsAny(this DbFunctions _, string property, params string[] keywords) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAny))); @@ -89,6 +92,7 @@ public static bool FullTextContainsAny(this DbFunctions _, string property, para /// The property to score. /// The keywords to score by. /// The full-text search score. + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public static double FullTextScore(this DbFunctions _, string property, params string[] keywords) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextScore))); @@ -98,6 +102,7 @@ public static double FullTextScore(this DbFunctions _, string property, params s /// The instance. /// The functions to compute the score for. /// The combined score. + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public static double Rrf(this DbFunctions _, params double[] functions) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Rrf))); diff --git a/src/efcore/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs b/src/efcore/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs index 50068ca74ba..2d8f631c1b0 100644 --- a/src/efcore/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs +++ b/src/efcore/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs @@ -79,7 +79,7 @@ protected override Expression VisitExtension(Expression expression) return base.VisitExtension(expression); } - // Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2)) + // Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2)) case SqlFunctionExpression { Name: "FullTextContainsAny" or "FullTextContainsAll", @@ -100,7 +100,7 @@ protected override Expression VisitExtension(Expression expression) fullTextContainsAllAnyFunction.TypeMapping); } - // Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, [keyword1, keyword2])) + // Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, [keyword1, keyword2])) case SqlFunctionExpression { Name: "FullTextScore", @@ -116,7 +116,7 @@ protected override Expression VisitExtension(Expression expression) return new SqlFunctionExpression( fullTextScoreFunction.Name, - scoringFunction: true, + isScoringFunction: true, [property, sqlExpressionFactory.Constant(keywordValues, typeMapping)], fullTextScoreFunction.Type, fullTextScoreFunction.TypeMapping); diff --git a/src/efcore/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs b/src/efcore/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs index f222dbe918c..9d05d774ebc 100644 --- a/src/efcore/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs +++ b/src/efcore/src/EFCore.Cosmos/Query/Internal/Expressions/SqlFunctionExpression.cs @@ -26,7 +26,7 @@ public SqlFunctionExpression( IEnumerable arguments, Type type, CoreTypeMapping? typeMapping) - : this(name, scoringFunction: false, arguments, type, typeMapping) + : this(name, isScoringFunction: false, arguments, type, typeMapping) { } @@ -36,17 +36,18 @@ public SqlFunctionExpression( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public SqlFunctionExpression( string name, - bool scoringFunction, + bool isScoringFunction, IEnumerable arguments, Type type, CoreTypeMapping? typeMapping) : base(type, typeMapping) { Name = name; - Arguments = arguments.ToList(); - IsScoringFunction = scoringFunction; + Arguments = [.. arguments]; + IsScoringFunction = isScoringFunction; } /// @@ -63,6 +64,7 @@ public SqlFunctionExpression( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// + [Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)] public virtual bool IsScoringFunction { get; } /// diff --git a/src/efcore/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs b/src/efcore/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs index b7de73b0257..4b7aaf371a6 100644 --- a/src/efcore/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs +++ b/src/efcore/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs @@ -36,22 +36,42 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF nameof(CosmosDbFunctionsExtensions.FullTextContains) when arguments is [_, var property, var keyword] => sqlExpressionFactory.Function( "FullTextContains", - [property, keyword], + [ + property, + keyword, + ], typeof(bool), typeMappingSource.FindMapping(typeof(bool))), nameof(CosmosDbFunctionsExtensions.FullTextScore) - when arguments is [_, var property, var keywords] => sqlExpressionFactory.ScoringFunction( + when arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords] + && keywordClrType == typeof(string[]) => BuildScoringFunction( + sqlExpressionFactory, + "FullTextScore", + [property, .. values.Select(x => sqlExpressionFactory.Constant(x))], + typeof(double), + typeMappingSource.FindMapping(typeof(double))), + + nameof(CosmosDbFunctionsExtensions.FullTextScore) + when arguments is [_, SqlExpression property, SqlParameterExpression { Type: var keywordClrType } keywords] + && keywordClrType == typeof(string[]) => BuildScoringFunction( + sqlExpressionFactory, + "FullTextScore", + [property, keywords], + typeof(double), + typeMappingSource.FindMapping(typeof(double))), + + nameof(CosmosDbFunctionsExtensions.FullTextScore) + when arguments is [_, SqlExpression property, ArrayConstantExpression keywords] => BuildScoringFunction( + sqlExpressionFactory, "FullTextScore", - [ - property, - keywords, - ], + [property, .. keywords.Items], typeof(double), typeMappingSource.FindMapping(typeof(double))), nameof(CosmosDbFunctionsExtensions.Rrf) - when arguments is [_, ArrayConstantExpression functions] => sqlExpressionFactory.ScoringFunction( + when arguments is [_, ArrayConstantExpression functions] => BuildScoringFunction( + sqlExpressionFactory, "RRF", functions.Items, typeof(double), @@ -61,7 +81,7 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF when arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords] && keywordClrType == typeof(string[]) => sqlExpressionFactory.Function( method.Name == nameof(CosmosDbFunctionsExtensions.FullTextContainsAny) ? "FullTextContainsAny" : "FullTextContainsAll", - [property, ..values.Select(x => sqlExpressionFactory.Constant(x))], + [property, .. values.Select(x => sqlExpressionFactory.Constant(x))], typeof(bool), typeMappingSource.FindMapping(typeof(bool))), @@ -83,4 +103,26 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF _ => null }; } + + private SqlExpression BuildScoringFunction( + ISqlExpressionFactory sqlExpressionFactory, + string functionName, + IEnumerable arguments, + Type returnType, + CoreTypeMapping? typeMapping = null) + { + var typeMappedArguments = new List(); + + foreach (var argument in arguments) + { + typeMappedArguments.Add(argument is SqlExpression sqlArgument ? sqlExpressionFactory.ApplyDefaultTypeMapping(sqlArgument) : argument); + } + + return new SqlFunctionExpression( + functionName, + isScoringFunction: true, + typeMappedArguments, + returnType, + typeMapping); + } } diff --git a/src/efcore/src/Shared/EFDiagnostics.cs b/src/efcore/src/Shared/EFDiagnostics.cs index 42476e39280..35e3e754126 100644 --- a/src/efcore/src/Shared/EFDiagnostics.cs +++ b/src/efcore/src/Shared/EFDiagnostics.cs @@ -19,4 +19,5 @@ internal static class EFDiagnostics public const string MetricsExperimental = "EF9101"; public const string PagingExperimental = "EF9102"; public const string CosmosVectorSearchExperimental = "EF9103"; + public const string CosmosFullTextSearchExperimental = "EF9104"; } diff --git a/src/efcore/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs b/src/efcore/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs index b74d601792b..712bd2c284c 100644 --- a/src/efcore/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs +++ b/src/efcore/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs @@ -7,7 +7,8 @@ namespace Microsoft.EntityFrameworkCore; -[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)] +#pragma warning disable EF9104 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential | CosmosCondition.IsNotEmulator)] public class FullTextSearchCosmosTest : IClassFixture { public FullTextSearchCosmosTest(FullTextSearchFixture fixture, ITestOutputHelper testOutputHelper) diff --git a/src/efcore/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs b/src/efcore/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs index ed87b74e00a..9be808ad8ff 100644 --- a/src/efcore/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs +++ b/src/efcore/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs @@ -6,8 +6,9 @@ namespace Microsoft.EntityFrameworkCore; +#pragma warning disable EF9104 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning disable EF9103 -[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential)] +[CosmosCondition(CosmosCondition.DoesNotUseTokenCredential | CosmosCondition.IsNotEmulator)] public class HybridSearchCosmosTest : IClassFixture { public HybridSearchCosmosTest(HybridSearchFixture fixture, ITestOutputHelper testOutputHelper) diff --git a/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosCondition.cs b/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosCondition.cs index 6a6de38641e..d3ab1d3120c 100644 --- a/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosCondition.cs +++ b/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosCondition.cs @@ -8,4 +8,6 @@ public enum CosmosCondition { UsesTokenCredential = 1 << 0, DoesNotUseTokenCredential = 1 << 1, + IsEmulator = 1 << 2, + IsNotEmulator = 1 << 3, } diff --git a/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosConditionAttribute.cs b/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosConditionAttribute.cs index e1448e20ee2..3a80eb3317f 100644 --- a/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosConditionAttribute.cs +++ b/src/efcore/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosConditionAttribute.cs @@ -24,6 +24,16 @@ public ValueTask IsMetAsync() isMet &= !TestEnvironment.UseTokenCredential; } + if (Conditions.HasFlag(CosmosCondition.IsEmulator)) + { + isMet &= TestEnvironment.IsEmulator; + } + + if (Conditions.HasFlag(CosmosCondition.IsNotEmulator)) + { + isMet &= !TestEnvironment.IsEmulator; + } + return ValueTask.FromResult(isMet); } diff --git a/src/efcore/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs b/src/efcore/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs index 99e471e70aa..044d075a5fe 100644 --- a/src/efcore/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs +++ b/src/efcore/test/EFCore.Cosmos.FunctionalTests/VectorSearchCosmosTest.cs @@ -257,12 +257,14 @@ public virtual async Task RRF_with_two_Vector_distance_functions_in_OrderBy() var inputVector1 = new byte[] { 2, 1, 4, 6, 5, 2, 5, 7, 3, 1 }; var inputVector2 = new[] { 0.33f, -0.52f, 0.45f, -0.67f, 0.89f, -0.34f, 0.86f, -0.78f }; +#pragma warning disable EF9104 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var booksFromStore = await context .Set() .OrderBy(e => EF.Functions.Rrf( EF.Functions.VectorDistance(e.BytesArray, inputVector1), EF.Functions.VectorDistance(e.SinglesArray, inputVector2))) .ToListAsync(); +#pragma warning restore EF9104 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. Assert.Equal(3, booksFromStore.Count); diff --git a/src/emsdk/eng/Version.Details.xml b/src/emsdk/eng/Version.Details.xml index cf34ee98deb..7e6c235ecb5 100644 --- a/src/emsdk/eng/Version.Details.xml +++ b/src/emsdk/eng/Version.Details.xml @@ -1,38 +1,38 @@ - + - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 - + https://github.com/dotnet/binaryen - 4ff6f136fb2207432bb68c4c7350bc72989331ee + 230fc28af46874544263444e85f7c38d3a86d864 https://github.com/dotnet/cpython @@ -236,33 +236,33 @@ https://github.com/dotnet/llvm-project 2402c9bc329d9bc19c786ae1c5bd608cf2e0d326 - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee diff --git a/src/emsdk/eng/Versions.props b/src/emsdk/eng/Versions.props index 9ec663e5c2f..fdc0a2d1659 100644 --- a/src/emsdk/eng/Versions.props +++ b/src/emsdk/eng/Versions.props @@ -18,14 +18,14 @@ 7.0.20 6.0.36 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 - 10.0.0-alpha.1.25261.2 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 + 10.0.0-alpha.1.25265.1 10.0.0-alpha.1.25261.1 10.0.0-alpha.1.25261.1 @@ -57,11 +57,11 @@ release - 10.0.0-beta.25263.104 - 10.0.0-beta.25263.104 - 10.0.0-beta.25263.104 - 10.0.0-beta.25263.104 - 10.0.0-beta.25263.104 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 + 10.0.0-beta.25265.101 1.1.87-gba258badda diff --git a/src/emsdk/eng/common/core-templates/steps/source-build.yml b/src/emsdk/eng/common/core-templates/steps/source-build.yml index f2a0f347fdd..0dde553c3eb 100644 --- a/src/emsdk/eng/common/core-templates/steps/source-build.yml +++ b/src/emsdk/eng/common/core-templates/steps/source-build.yml @@ -51,13 +51,12 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ + --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $baseRidArgs \ $portableBuildArgs \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml diff --git a/src/emsdk/eng/common/tools.ps1 b/src/emsdk/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/emsdk/eng/common/tools.ps1 +++ b/src/emsdk/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/emsdk/eng/common/tools.sh b/src/emsdk/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/emsdk/eng/common/tools.sh +++ b/src/emsdk/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/emsdk/global.json b/src/emsdk/global.json index eb8a5954da0..be0108e6c2e 100644 --- a/src/emsdk/global.json +++ b/src/emsdk/global.json @@ -3,8 +3,8 @@ "dotnet": "10.0.100-preview.3.25201.16" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25263.104", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25263.104", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25265.101", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25265.101", "Microsoft.Build.Traversal": "3.4.0" } } diff --git a/src/fsharp/azure-pipelines.yml b/src/fsharp/azure-pipelines.yml index 712dc877a9e..a8e15eae559 100644 --- a/src/fsharp/azure-pipelines.yml +++ b/src/fsharp/azure-pipelines.yml @@ -35,7 +35,7 @@ variables: # (since for all *new* release branches we insert into VS main and for all *previous* releases we insert into corresponding VS release), # i.e. 'rel/d17.9' *or* 'main' in dotnet/fsharp/refs/heads/main and 'main' in F# dotnet/fsharp/refs/heads/release/dev17.10 (latest release branch) - name: VSInsertionTargetBranchName - value: feature/d18initial + value: main - name: _TeamName value: FSharp - name: TeamName diff --git a/src/fsharp/eng/Version.Details.xml b/src/fsharp/eng/Version.Details.xml index 0f64f53dbf3..612b8e5a32f 100644 --- a/src/fsharp/eng/Version.Details.xml +++ b/src/fsharp/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + https://github.com/dotnet/source-build-reference-packages diff --git a/src/msbuild/eng/Version.Details.xml b/src/msbuild/eng/Version.Details.xml index 33ea01f34bc..53547add86e 100644 --- a/src/msbuild/eng/Version.Details.xml +++ b/src/msbuild/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs index 6552d391309..2f3583d83e9 100644 --- a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs @@ -9483,6 +9483,70 @@ @typeparam TItem CompileToAssembly(generated); } + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/11718")] + public void GenericInference_DynamicallyAccessedMembers_01() + { + var generated = CompileToCSharp(""" + @using Microsoft.AspNetCore.Components.Forms + + + + + + + @code { + private string value1 = "true"; + } + """); + + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + + Assert.Contains("DynamicallyAccessedMembers", generated.Code); + } + + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/11718")] + public void GenericInference_DynamicallyAccessedMembers_02() + { + AdditionalSyntaxTrees.Add(Parse(""" + using Microsoft.AspNetCore.Components; + using System; + using System.Diagnostics.CodeAnalysis; + using DAM = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute; + using DAMT = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + namespace Test; + public class MyComponent + : ComponentBase + { + [Parameter] public required T1 P1 { get; set; } + [Parameter] public required T2 P2 { get; set; } + [Parameter] public required T3 P3 { get; set; } + } + class Attr : Attribute; + """)); + + var expectedDiagnostics = new[] + { + // (9,23): warning CS0658: 'x' is not a recognized attribute location. Valid attribute locations for this declaration are 'typevar'. All attributes in this block will be ignored. + // [DAM(DAMT.None)] [x: DAM(DAMT.All)] T3> + Diagnostic(ErrorCode.WRN_InvalidAttributeLocation, "x").WithArguments("x", "typevar").WithLocation(9, 23) + }; + + var generated = CompileToCSharp(""" + + @code { + private string s = "x"; + } + """, expectedDiagnostics); + + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated, expectedDiagnostics); + + Assert.Contains("DynamicallyAccessedMembers", generated.Code); + } + #endregion #region Key diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs new file mode 100644 index 00000000000..066fea07787 --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs @@ -0,0 +1,159 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components.Forms; + +#line default +#line hidden +#nullable disable + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + var __typeInference_CreateInputRadioGroup_0 = global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadioGroup_0(__builder, -1, -1, +#nullable restore +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + value1 + +#line default +#line hidden +#nullable disable + , -1, global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, + global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => value1 = __value, value1)), -1, () => value1, -1, (__builder2) => { + var __typeInference_CreateInputRadio_1 = global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadio_1(__builder2, -1, -1, +#nullable restore +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" + "false" + +#line default +#line hidden +#nullable disable + ); + #pragma warning disable BL0005 + __typeInference_CreateInputRadio_1. +#nullable restore +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" + Value + +#line default +#line hidden +#nullable disable + = default; + #pragma warning restore BL0005 +#nullable restore +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(global::Microsoft.AspNetCore.Components.Forms.InputRadio<>); + +#line default +#line hidden +#nullable disable + var __typeInference_CreateInputRadio_2 = global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadio_2(__builder2, -1, -1, +#nullable restore +#line 5 "x:\dir\subdir\Test\TestComponent.cshtml" + "true" + +#line default +#line hidden +#nullable disable + ); + #pragma warning disable BL0005 + __typeInference_CreateInputRadio_2. +#nullable restore +#line 5 "x:\dir\subdir\Test\TestComponent.cshtml" + Value + +#line default +#line hidden +#nullable disable + = default; + #pragma warning restore BL0005 +#nullable restore +#line 5 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(global::Microsoft.AspNetCore.Components.Forms.InputRadio<>); + +#line default +#line hidden +#nullable disable + } + ); + #pragma warning disable BL0005 + __typeInference_CreateInputRadioGroup_0. +#nullable restore +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + Value + +#line default +#line hidden +#nullable disable + = default; + #pragma warning restore BL0005 +#nullable restore +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(global::Microsoft.AspNetCore.Components.Forms.InputRadioGroup<>); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 +#nullable restore +#line 8 "x:\dir\subdir\Test\TestComponent.cshtml" + + private string value1 = "true"; + +#line default +#line hidden +#nullable disable + } +} +namespace __Blazor.Test.TestComponent +{ + #line hidden + internal static class TypeInference + { + public static global::Microsoft.AspNetCore.Components.Forms.InputRadioGroup CreateInputRadioGroup_0<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0, int __seq1, global::Microsoft.AspNetCore.Components.EventCallback __arg1, int __seq2, global::System.Linq.Expressions.Expression> __arg2, int __seq3, global::Microsoft.AspNetCore.Components.RenderFragment __arg3) + { + __builder.OpenComponent>(seq); + __builder.AddAttribute(__seq0, "Value", (object)__arg0); + __builder.AddAttribute(__seq1, "ValueChanged", (object)__arg1); + __builder.AddAttribute(__seq2, "ValueExpression", (object)__arg2); + __builder.AddAttribute(__seq3, "ChildContent", (object)__arg3); + __builder.CloseComponent(); + return default; + } + public static global::Microsoft.AspNetCore.Components.Forms.InputRadio CreateInputRadio_1<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0) + { + __builder.OpenComponent>(seq); + __builder.AddAttribute(__seq0, "Value", (object)__arg0); + __builder.CloseComponent(); + return default; + } + public static global::Microsoft.AspNetCore.Components.Forms.InputRadio CreateInputRadio_2<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0) + { + __builder.OpenComponent>(seq); + __builder.AddAttribute(__seq0, "Value", (object)__arg0); + __builder.CloseComponent(); + return default; + } + } +} +#pragma warning restore 1591 diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt new file mode 100644 index 00000000000..6d1f1cc4d72 --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt @@ -0,0 +1,44 @@ +Source Location: (1:0,1 [43] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components.Forms| +Generated Location: (361:12,0 [43] ) +|using Microsoft.AspNetCore.Components.Forms| + +Source Location: (78:2,30 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|value1| +Generated Location: (1297:34,30 [6] ) +|value1| + +Source Location: (113:3,25 [7] x:\dir\subdir\Test\TestComponent.cshtml) +|"false"| +Generated Location: (1891:44,25 [7] ) +|"false"| + +Source Location: (104:3,16 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (2157:54,16 [5] ) +|Value| + +Source Location: (152:4,25 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|"true"| +Generated Location: (2729:71,25 [6] ) +|"true"| + +Source Location: (143:4,16 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (2994:81,16 [5] ) +|Value| + +Source Location: (71:2,23 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (3545:101,23 [5] ) +|Value| + +Source Location: (194:7,7 [39] x:\dir\subdir\Test\TestComponent.cshtml) +| + private string value1 = "true"; +| +Generated Location: (3997:119,7 [39] ) +| + private string value1 = "true"; +| + diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs new file mode 100644 index 00000000000..248ac4bd3be --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs @@ -0,0 +1,116 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + var __typeInference_CreateMyComponent_0 = global::__Blazor.Test.TestComponent.TypeInference.CreateMyComponent_0(__builder, -1, -1, +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + s + +#line default +#line hidden +#nullable disable + , -1, +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + 2 + +#line default +#line hidden +#nullable disable + , -1, +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + s + +#line default +#line hidden +#nullable disable + ); + #pragma warning disable BL0005 + __typeInference_CreateMyComponent_0. +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + P1 + +#line default +#line hidden +#nullable disable + = default; + __typeInference_CreateMyComponent_0. +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + P2 + +#line default +#line hidden +#nullable disable + = default; + __typeInference_CreateMyComponent_0. +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" + P3 + +#line default +#line hidden +#nullable disable + = default; + #pragma warning restore BL0005 +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(global::Test.MyComponent<,,>); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 +#nullable restore +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + + private string s = "x"; + +#line default +#line hidden +#nullable disable + } +} +namespace __Blazor.Test.TestComponent +{ + #line hidden + internal static class TypeInference + { + public static global::Test.MyComponent CreateMyComponent_0(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, T1 __arg0, int __seq1, T2 __arg1, int __seq2, T3 __arg2) + { + __builder.OpenComponent>(seq); + __builder.AddAttribute(__seq0, "P1", (object)__arg0); + __builder.AddAttribute(__seq1, "P2", (object)__arg1); + __builder.AddAttribute(__seq2, "P3", (object)__arg2); + __builder.CloseComponent(); + return default; + } + } +} +#pragma warning restore 1591 diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt new file mode 100644 index 00000000000..7d6403a4fe6 --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt @@ -0,0 +1,39 @@ +Source Location: (17:0,17 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|s| +Generated Location: (1147:29,17 [1] ) +|s| + +Source Location: (24:0,24 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|2| +Generated Location: (1314:37,24 [1] ) +|2| + +Source Location: (31:0,31 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|s| +Generated Location: (1488:45,31 [1] ) +|s| + +Source Location: (13:0,13 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P1| +Generated Location: (1734:55,13 [2] ) +|P1| + +Source Location: (20:0,20 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P2| +Generated Location: (1953:64,20 [2] ) +|P2| + +Source Location: (27:0,27 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P3| +Generated Location: (2179:73,27 [2] ) +|P3| + +Source Location: (45:1,7 [31] x:\dir\subdir\Test\TestComponent.cshtml) +| + private string s = "x"; +| +Generated Location: (2593:91,7 [31] ) +| + private string s = "x"; +| + diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs new file mode 100644 index 00000000000..bf124352035 --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.codegen.cs @@ -0,0 +1,121 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; +#nullable restore +#line (1,2)-(1,45) "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Components.Forms + +#nullable disable + ; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadioGroup_0(__builder, 0, 1, +#nullable restore +#line (3,31)-(3,37) "x:\dir\subdir\Test\TestComponent.cshtml" +value1 + +#line default +#line hidden +#nullable disable + , 2, global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => value1 = __value, value1)), 3, () => value1, 4, (__builder2) => { + global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadio_1(__builder2, 5, 6, +#nullable restore +#line (4,26)-(4,33) "x:\dir\subdir\Test\TestComponent.cshtml" +"false" + +#line default +#line hidden +#nullable disable + ); + __builder2.AddMarkupContent(7, "\r\n "); + global::__Blazor.Test.TestComponent.TypeInference.CreateInputRadio_2(__builder2, 8, 9, +#nullable restore +#line (5,26)-(5,32) "x:\dir\subdir\Test\TestComponent.cshtml" +"true" + +#line default +#line hidden +#nullable disable + ); + } + ); + } + #pragma warning restore 1998 +#nullable restore +#line (8,8)-(10,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + private string value1 = "true"; + +#line default +#line hidden +#nullable disable + + } +} +namespace __Blazor.Test.TestComponent +{ + #line hidden + internal static class TypeInference + { + public static void CreateInputRadioGroup_0<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0, int __seq1, global::Microsoft.AspNetCore.Components.EventCallback __arg1, int __seq2, global::System.Linq.Expressions.Expression> __arg2, int __seq3, global::Microsoft.AspNetCore.Components.RenderFragment __arg3) + { + __builder.OpenComponent>(seq); + __builder.AddComponentParameter(__seq0, nameof(global::Microsoft.AspNetCore.Components.Forms.InputRadioGroup. +#nullable restore +#line (3,24)-(3,29) "x:\dir\subdir\Test\TestComponent.cshtml" +Value + +#line default +#line hidden +#nullable disable + ), __arg0); + __builder.AddComponentParameter(__seq1, nameof(global::Microsoft.AspNetCore.Components.Forms.InputRadioGroup.ValueChanged), __arg1); + __builder.AddComponentParameter(__seq2, nameof(global::Microsoft.AspNetCore.Components.Forms.InputRadioGroup.ValueExpression), __arg2); + __builder.AddComponentParameter(__seq3, "ChildContent", __arg3); + __builder.CloseComponent(); + } + public static void CreateInputRadio_1<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0) + { + __builder.OpenComponent>(seq); + __builder.AddComponentParameter(__seq0, nameof(global::Microsoft.AspNetCore.Components.Forms.InputRadio. +#nullable restore +#line (4,17)-(4,22) "x:\dir\subdir\Test\TestComponent.cshtml" +Value + +#line default +#line hidden +#nullable disable + ), __arg0); + __builder.CloseComponent(); + } + public static void CreateInputRadio_2<[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(unchecked((global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)0xffffffff))] TValue>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, TValue __arg0) + { + __builder.OpenComponent>(seq); + __builder.AddComponentParameter(__seq0, nameof(global::Microsoft.AspNetCore.Components.Forms.InputRadio. +#nullable restore +#line (5,17)-(5,22) "x:\dir\subdir\Test\TestComponent.cshtml" +Value + +#line default +#line hidden +#nullable disable + ), __arg0); + __builder.CloseComponent(); + } + } +} +#pragma warning restore 1591 diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt new file mode 100644 index 00000000000..acddc46cbae --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_01/TestComponent.mappings.txt @@ -0,0 +1,44 @@ +Source Location: (1:0,1 [43] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Components.Forms| +Generated Location: (372:12,0 [43] ) +|using Microsoft.AspNetCore.Components.Forms| + +Source Location: (78:2,30 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|value1| +Generated Location: (995:28,0 [6] ) +|value1| + +Source Location: (113:3,25 [7] x:\dir\subdir\Test\TestComponent.cshtml) +|"false"| +Generated Location: (1516:37,0 [7] ) +|"false"| + +Source Location: (152:4,25 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|"true"| +Generated Location: (1843:47,0 [6] ) +|"true"| + +Source Location: (194:7,7 [39] x:\dir\subdir\Test\TestComponent.cshtml) +| + private string value1 = "true"; +| +Generated Location: (2082:59,0 [39] ) +| + private string value1 = "true"; +| + +Source Location: (71:2,23 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (3218:79,0 [5] ) +|Value| + +Source Location: (104:3,16 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (4384:96,0 [5] ) +|Value| + +Source Location: (143:4,16 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (5173:110,0 [5] ) +|Value| + diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs new file mode 100644 index 00000000000..79b517bb297 --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.codegen.cs @@ -0,0 +1,97 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + global::__Blazor.Test.TestComponent.TypeInference.CreateMyComponent_0(__builder, 0, 1, +#nullable restore +#line (1,18)-(1,19) "x:\dir\subdir\Test\TestComponent.cshtml" +s + +#line default +#line hidden +#nullable disable + , 2, +#nullable restore +#line (1,25)-(1,26) "x:\dir\subdir\Test\TestComponent.cshtml" +2 + +#line default +#line hidden +#nullable disable + , 3, +#nullable restore +#line (1,32)-(1,33) "x:\dir\subdir\Test\TestComponent.cshtml" +s + +#line default +#line hidden +#nullable disable + ); + } + #pragma warning restore 1998 +#nullable restore +#line (2,8)-(4,1) "x:\dir\subdir\Test\TestComponent.cshtml" + + private string s = "x"; + +#line default +#line hidden +#nullable disable + + } +} +namespace __Blazor.Test.TestComponent +{ + #line hidden + internal static class TypeInference + { + public static void CreateMyComponent_0(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, T1 __arg0, int __seq1, T2 __arg1, int __seq2, T3 __arg2) + { + __builder.OpenComponent>(seq); + __builder.AddComponentParameter(__seq0, nameof(global::Test.MyComponent. +#nullable restore +#line (1,14)-(1,16) "x:\dir\subdir\Test\TestComponent.cshtml" +P1 + +#line default +#line hidden +#nullable disable + ), __arg0); + __builder.AddComponentParameter(__seq1, nameof(global::Test.MyComponent. +#nullable restore +#line (1,21)-(1,23) "x:\dir\subdir\Test\TestComponent.cshtml" +P2 + +#line default +#line hidden +#nullable disable + ), __arg1); + __builder.AddComponentParameter(__seq2, nameof(global::Test.MyComponent. +#nullable restore +#line (1,28)-(1,30) "x:\dir\subdir\Test\TestComponent.cshtml" +P3 + +#line default +#line hidden +#nullable disable + ), __arg2); + __builder.CloseComponent(); + } + } +} +#pragma warning restore 1591 diff --git a/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt new file mode 100644 index 00000000000..e47bea3a94d --- /dev/null +++ b/src/razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/GenericInference_DynamicallyAccessedMembers_02/TestComponent.mappings.txt @@ -0,0 +1,39 @@ +Source Location: (17:0,17 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|s| +Generated Location: (837:22,0 [1] ) +|s| + +Source Location: (24:0,24 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|2| +Generated Location: (991:30,0 [1] ) +|2| + +Source Location: (31:0,31 [1] x:\dir\subdir\Test\TestComponent.cshtml) +|s| +Generated Location: (1145:38,0 [1] ) +|s| + +Source Location: (45:1,7 [31] x:\dir\subdir\Test\TestComponent.cshtml) +| + private string s = "x"; +| +Generated Location: (1343:48,0 [31] ) +| + private string s = "x"; +| + +Source Location: (13:0,13 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P1| +Generated Location: (2371:68,0 [2] ) +|P1| + +Source Location: (20:0,20 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P2| +Generated Location: (2622:77,0 [2] ) +|P2| + +Source Location: (27:0,27 [2] x:\dir\subdir\Test\TestComponent.cshtml) +|P3| +Generated Location: (2873:86,0 [2] ) +|P3| + diff --git a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs index 7edbd71fb75..ca1f542ca66 100644 --- a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs +++ b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.AspNetCore.Razor.PooledObjects; +using Microsoft.CodeAnalysis.CSharp; using static Microsoft.AspNetCore.Razor.Language.CommonMetadata; namespace Microsoft.CodeAnalysis.Razor; @@ -445,6 +447,63 @@ private static void CreateTypeParameterProperty(TagHelperDescriptorBuilder build metadataPairs.Add(new(ComponentMetadata.Component.TypeParameterConstraintsKey, whereClauseText)); } + // Collect attributes that should be propagated to the type inference method. + using var _2 = StringBuilderPool.GetPooledObject(out var withAttributes); + foreach (var attribute in typeParameter.GetAttributes()) + { + if (attribute.HasFullName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")) + { + Debug.Assert(attribute.AttributeClass != null); + + if (withAttributes.Length > 0) + { + withAttributes.Append(", "); + } + else + { + withAttributes.Append('['); + } + + withAttributes.Append(attribute.AttributeClass.ToDisplayString(GloballyQualifiedFullNameTypeDisplayFormat)); + withAttributes.Append('('); + + var first = true; + foreach (var arg in attribute.ConstructorArguments) + { + if (first) + { + first = false; + } + else + { + withAttributes.Append(", "); + } + + if (arg.Kind == TypedConstantKind.Enum) + { + withAttributes.Append("unchecked(("); + withAttributes.Append(arg.Type!.ToDisplayString(GloballyQualifiedFullNameTypeDisplayFormat)); + withAttributes.Append(')'); + withAttributes.Append(CSharp.SymbolDisplay.FormatPrimitive(arg.Value!, quoteStrings: true, useHexadecimalNumbers: true)); + withAttributes.Append(')'); + } + else + { + Debug.Assert(false, $"Need to add support for '{arg.Kind}' and make sure the output is 'global::' prefixed."); + withAttributes.Append(arg.ToCSharpString()); + } + } + + withAttributes.Append(')'); + } + } + if (withAttributes.Length > 0) + { + withAttributes.Append("] "); + withAttributes.Append(typeParameter.Name); + metadataPairs.Add(new(ComponentMetadata.Component.TypeParameterWithAttributesKey, withAttributes.ToString())); + } + pb.SetMetadata(MetadataCollection.Create(metadataPairs)); pb.SetDocumentation( diff --git a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMetadata.cs b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMetadata.cs index 3c2450c82a1..b8845e82007 100644 --- a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMetadata.cs +++ b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentMetadata.cs @@ -137,6 +137,12 @@ public static class Component public const string TypeParameterConstraintsKey = "Component.TypeParameterConstraints"; + /// + /// If there are attributes that should be propagated into type inference method, the value of this metadata is the corresponding code for the type parameter such as + /// [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T. + /// + public const string TypeParameterWithAttributesKey = "Component.TypeParameterWithAttributes"; + public const string NameMatchKey = "Components.NameMatch"; public const string HasRenderModeDirectiveKey = "Components.HasRenderModeDirective"; diff --git a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentNodeWriter.cs b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentNodeWriter.cs index 29362cc1f91..034855aa124 100644 --- a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentNodeWriter.cs +++ b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentNodeWriter.cs @@ -116,7 +116,7 @@ protected void WriteComponentTypeInferenceMethod(CodeRenderingContext context, C writer.Write(" "); writer.Write(node.MethodName); writer.Write("<"); - writer.Write(string.Join(", ", node.Component.Component.GetTypeParameters().Select(a => a.Name))); + writer.Write(string.Join(", ", node.Component.Component.GetTypeParameters().Select(serializeTypeParameter))); writer.Write(">"); writer.Write("("); @@ -338,6 +338,16 @@ static void writeConstraints(CodeWriter writer, ComponentTypeInferenceMethodInte writer.WriteLine(); } + + static string serializeTypeParameter(BoundAttributeDescriptor attribute) + { + if (attribute.Metadata.TryGetValue(ComponentMetadata.Component.TypeParameterWithAttributesKey, out var withAttributes)) + { + return withAttributes; + } + + return attribute.Name; + } } protected static void WriteComponentAttributeName(CodeRenderingContext context, ComponentAttributeIntermediateNode attribute, bool allowNameof = true) diff --git a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorProjectFileSystem.cs b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorProjectFileSystem.cs index 6f33bdec79b..04b59fdd763 100644 --- a/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorProjectFileSystem.cs +++ b/src/razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorProjectFileSystem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using Microsoft.AspNetCore.Razor.Utilities; namespace Microsoft.AspNetCore.Razor.Language; @@ -93,6 +94,23 @@ protected override string NormalizeAndEnsureValidPath(string path) var normalizedPath = path.Replace('\\', '/'); + // On Windows, check to see if this is a rooted file path. If it is, just return it. + // This covers the following cases: + // + // 1. It is rooted within the project root. That's valid and we would have checked + // specifically for that case below. + // 2. It is rooted outside of the project root. That's invalid, and we don't want to + // concatenate it with the project root. That would potentially produce an invalid + // Windows path like 'C:/project/C:/other-project/some-file.cshtml'. + // + // Note that returning a path that is rooted outside of the project root will cause + // the GetItem(...) method to throw, but it could be overridden by a descendant file + // system. + if (PlatformInformation.IsWindows && PathUtilities.IsPathFullyQualified(path)) + { + return normalizedPath; + } + // Check if the given path is an absolute path. It is absolute if... // // 1. It is a network share path and starts with a '//' (e.g. //server/some/network/folder) or... diff --git a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/CompilationHelpers.cs b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/CompilationHelpers.cs index db960f2d816..17dbc60999d 100644 --- a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/CompilationHelpers.cs +++ b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/CompilationHelpers.cs @@ -20,7 +20,7 @@ internal static async Task GenerateCodeDocumentAsync( { var importSources = await GetImportSourcesAsync(document, projectEngine, cancellationToken).ConfigureAwait(false); var tagHelpers = await document.Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false); - var source = await document.GetSourceAsync(projectEngine, cancellationToken).ConfigureAwait(false); + var source = await document.GetSourceAsync(cancellationToken).ConfigureAwait(false); var generator = new CodeDocumentGenerator(projectEngine, compilerOptions); return generator.Generate(source, document.FileKind, importSources, tagHelpers, cancellationToken); @@ -33,7 +33,7 @@ internal static async Task GenerateDesignTimeCodeDocumentAsyn { var importSources = await GetImportSourcesAsync(document, projectEngine, cancellationToken).ConfigureAwait(false); var tagHelpers = await document.Project.GetTagHelpersAsync(cancellationToken).ConfigureAwait(false); - var source = await document.GetSourceAsync(projectEngine, cancellationToken).ConfigureAwait(false); + var source = await document.GetSourceAsync(cancellationToken).ConfigureAwait(false); var generator = new CodeDocumentGenerator(projectEngine, RazorCompilerOptions.None); return generator.GenerateDesignTime(source, document.FileKind, importSources, tagHelpers, cancellationToken); @@ -41,7 +41,10 @@ internal static async Task GenerateDesignTimeCodeDocumentAsyn private static async Task> GetImportSourcesAsync(IDocumentSnapshot document, RazorProjectEngine projectEngine, CancellationToken cancellationToken) { - var projectItem = projectEngine.FileSystem.GetItem(document.FilePath, document.FileKind); + // We don't use document.FilePath when calling into GetItem(...) because + // it could be rooted outside of the project root. document.TargetPath should + // represent the logical relative path within the project root. + var projectItem = projectEngine.FileSystem.GetItem(document.TargetPath, document.FileKind); using var importProjectItems = new PooledArrayBuilder(); projectEngine.CollectImports(projectItem, ref importProjectItems.AsRef()); diff --git a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs index 4d3a574b86b..39f36cbc2eb 100644 --- a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs +++ b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/IDocumentSnapshotExtensions.cs @@ -11,14 +11,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal static class IDocumentSnapshotExtensions { - public static async Task GetSourceAsync(this IDocumentSnapshot document, RazorProjectEngine projectEngine, CancellationToken cancellationToken) + public static async Task GetSourceAsync( + this IDocumentSnapshot document, + CancellationToken cancellationToken) { - var projectItem = document is { FilePath: string filePath, FileKind: var fileKind } - ? projectEngine.FileSystem.GetItem(filePath, fileKind) - : null; - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var properties = RazorSourceDocumentProperties.Create(document.FilePath, projectItem?.RelativePhysicalPath); + var properties = RazorSourceDocumentProperties.Create(document.FilePath, document.TargetPath); return RazorSourceDocument.Create(text, properties); } diff --git a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs index 0420c28a8a1..c754ea209f8 100644 --- a/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs +++ b/src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/Legacy/ILegacyDocumentSnapshot.cs @@ -13,5 +13,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Legacy; /// internal interface ILegacyDocumentSnapshot { + string TargetPath { get; } RazorFileKind FileKind { get; } } diff --git a/src/razor/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Discovery/ProjectStateUpdater.cs b/src/razor/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Discovery/ProjectStateUpdater.cs index 97c22b038b8..f0e45022077 100644 --- a/src/razor/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Discovery/ProjectStateUpdater.cs +++ b/src/razor/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Discovery/ProjectStateUpdater.cs @@ -311,9 +311,26 @@ private void ReleaseSemaphore(ProjectKey projectKey) .ConfigureAwait(false); watch.Stop(); - // don't report success if the work was cancelled + // Don't report success if the work was cancelled cancellationToken.ThrowIfCancellationRequested(); + // Don't report success if the call failed. + // If the ImmutableArray that was returned is default, then the call failed. + + if (tagHelpers.IsDefault) + { + _telemetryReporter.ReportEvent("taghelperresolve/end", Severity.Normal, + new("id", telemetryId), + new("result", "error")); + + _logger.LogError($""" + Tag helper discovery failed. + Project: {projectSnapshot.FilePath} + """); + + return (null, configuration); + } + _telemetryReporter.ReportEvent("taghelperresolve/end", Severity.Normal, new("id", telemetryId), new("ellapsedms", watch.ElapsedMilliseconds), diff --git a/src/razor/src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/ImportDocumentManager.cs b/src/razor/src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/ImportDocumentManager.cs index f0828246900..a8f635f6709 100644 --- a/src/razor/src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/ImportDocumentManager.cs +++ b/src/razor/src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/ImportDocumentManager.cs @@ -97,10 +97,16 @@ public void OnUnsubscribed(IVisualStudioDocumentTracker documentTracker) private static ImmutableArray GetPhysicalImportItems(string filePath, ILegacyProjectSnapshot projectSnapshot) { var projectEngine = projectSnapshot.GetProjectEngine(); - var documentSnapshot = projectSnapshot.GetDocument(filePath); - var projectItem = projectEngine.FileSystem.GetItem(filePath, documentSnapshot?.FileKind); - return projectEngine.GetImports(projectItem, static i => i.PhysicalPath is not null); + // If we can get the document, use it's target path to find the project item + // to avoid GetItem(...) throwing an exception if the file path is rooted outside + // of the project. If we can't get the document, we'll just go ahead and use + // the file path, since it's probably OK. + var projectItem = projectSnapshot.GetDocument(filePath) is { } document + ? projectEngine.FileSystem.GetItem(document.TargetPath, document.FileKind) + : projectEngine.FileSystem.GetItem(filePath, fileKind: null); + + return projectEngine.GetImports(projectItem, static i => i is not DefaultImportProjectItem); } private void FileChangeTracker_Changed(object sender, FileChangeEventArgs args) diff --git a/src/razor/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestRazorProjectService.cs b/src/razor/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestRazorProjectService.cs index 29682f2c686..5dcad1a850c 100644 --- a/src/razor/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestRazorProjectService.cs +++ b/src/razor/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/TestRazorProjectService.cs @@ -6,9 +6,11 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.Test.Common; +using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.Serialization; +using Microsoft.CodeAnalysis.Razor.Utilities; using Moq; namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem; @@ -37,13 +39,19 @@ private static IRazorProjectInfoDriver CreateProjectInfoDriver() return mock.Object; } - public async Task AddDocumentToPotentialProjectsAsync(string textDocumentPath, CancellationToken cancellationToken) + public async Task AddDocumentToPotentialProjectsAsync(string filePath, CancellationToken cancellationToken) { - var document = new DocumentSnapshotHandle( - textDocumentPath, textDocumentPath, FileKinds.GetFileKindFromPath(textDocumentPath)); - - foreach (var projectSnapshot in _projectManager.FindPotentialProjects(textDocumentPath)) + foreach (var projectSnapshot in _projectManager.FindPotentialProjects(filePath)) { + var projectDirectory = FilePathNormalizer.GetNormalizedDirectoryName(projectSnapshot.FilePath); + var normalizedFilePath = FilePathNormalizer.Normalize(filePath); + + var targetPath = normalizedFilePath.StartsWith(projectDirectory, FilePathComparison.Instance) + ? normalizedFilePath[projectDirectory.Length..] + : normalizedFilePath; + + var document = new DocumentSnapshotHandle(filePath, targetPath, FileKinds.GetFileKindFromPath(filePath)); + var projectInfo = projectSnapshot.ToRazorProjectInfo(); projectInfo = projectInfo with diff --git a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ConditionalFactAttribute.cs b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ConditionalFactAttribute.cs index c025e3b34fa..9e6170c1fa8 100644 --- a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ConditionalFactAttribute.cs +++ b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ConditionalFactAttribute.cs @@ -121,6 +121,11 @@ public static class Is /// public const string FreeBSD = nameof(FreeBSD); + /// + /// Only execute if the current operating system platform is Unix-based. + /// + public const string AnyUnix = nameof(AnyUnix); + public static class Not { /// @@ -142,6 +147,12 @@ public static class Not /// Only execute if the current operating system platform is not FreeBSD. /// public const string FreeBSD = $"!{nameof(FreeBSD)}"; + + /// + /// Only execute if the current operating system platform is not Unix-based. + /// + public const string AnyUnix = $"!{nameof(AnyUnix)}"; + } } @@ -157,6 +168,9 @@ private static FrozenDictionary> CreateConditionMap() Add(Is.Linux, static () => PlatformInformation.IsLinux); Add(Is.MacOS, static () => PlatformInformation.IsMacOS); Add(Is.FreeBSD, static () => PlatformInformation.IsFreeBSD); + Add(Is.AnyUnix, static () => PlatformInformation.IsLinux || + PlatformInformation.IsMacOS || + PlatformInformation.IsFreeBSD); return map.ToFrozenDictionary(); diff --git a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ErrorCode.cs b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ErrorCode.cs index 8d155dcd923..94231654484 100644 --- a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ErrorCode.cs +++ b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/ErrorCode.cs @@ -20,6 +20,7 @@ public enum ErrorCode ERR_SingleTypeNameNotFound = 246, ERR_CantInferMethTypeArgs = 411, WRN_UnreferencedFieldAssg = 414, + WRN_InvalidAttributeLocation = 658, ERR_VariableUsedBeforeDeclaration = 841, ERR_SemicolonExpected = 1002, ERR_SyntaxError = 1003, diff --git a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PathUtilitiesTests.cs b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PathUtilitiesTests.cs index 0d0be00e06d..26bd1cc32fe 100644 --- a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PathUtilitiesTests.cs +++ b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PathUtilitiesTests.cs @@ -42,6 +42,84 @@ public void GetExtension_Span(string path, string expected) Assert.Equal(!string.IsNullOrEmpty(expected), PathUtilities.HasExtension(path.AsSpan())); } + // The tests below are derived from the .NET Runtime: + // - https://github.com/dotnet/runtime/blob/91195a7948a16c769ccaf7fd8ca84b1d210f6841/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/IO/Path.IsPathFullyQualified.cs + + [Fact] + public static void IsPathFullyQualified_NullArgument() + { + Assert.Throws(() => PathUtilities.IsPathFullyQualified(null!)); + } + + [Fact] + public static void IsPathFullyQualified_Empty() + { + Assert.False(PathUtilities.IsPathFullyQualified("")); + Assert.False(PathUtilities.IsPathFullyQualified(ReadOnlySpan.Empty)); + } + + [ConditionalTheory(Is.Windows)] + [InlineData("/")] + [InlineData(@"\")] + [InlineData(".")] + [InlineData("C:")] + [InlineData("C:foo.txt")] + public static void IsPathFullyQualified_Windows_Invalid(string path) + { + Assert.False(PathUtilities.IsPathFullyQualified(path)); + Assert.False(PathUtilities.IsPathFullyQualified(path.AsSpan())); + } + + [ConditionalTheory(Is.Windows)] + [InlineData(@"\\")] + [InlineData(@"\\\")] + [InlineData(@"\\Server")] + [InlineData(@"\\Server\Foo.txt")] + [InlineData(@"\\Server\Share\Foo.txt")] + [InlineData(@"\\Server\Share\Test\Foo.txt")] + [InlineData(@"C:\")] + [InlineData(@"C:\foo1")] + [InlineData(@"C:\\")] + [InlineData(@"C:\\foo2")] + [InlineData(@"C:/")] + [InlineData(@"C:/foo1")] + [InlineData(@"C://")] + [InlineData(@"C://foo2")] + public static void IsPathFullyQualified_Windows_Valid(string path) + { + Assert.True(PathUtilities.IsPathFullyQualified(path)); + Assert.True(PathUtilities.IsPathFullyQualified(path.AsSpan())); + } + + [ConditionalTheory(Is.AnyUnix)] + [InlineData(@"\")] + [InlineData(@"\\")] + [InlineData(".")] + [InlineData("./foo.txt")] + [InlineData("..")] + [InlineData("../foo.txt")] + [InlineData(@"C:")] + [InlineData(@"C:/")] + [InlineData(@"C://")] + public static void IsPathFullyQualified_Unix_Invalid(string path) + { + Assert.False(PathUtilities.IsPathFullyQualified(path)); + Assert.False(PathUtilities.IsPathFullyQualified(path.AsSpan())); + } + + [ConditionalTheory(Is.AnyUnix)] + [InlineData("/")] + [InlineData("/foo.txt")] + [InlineData("/..")] + [InlineData("//")] + [InlineData("//foo.txt")] + [InlineData("//..")] + public static void IsPathFullyQualified_Unix_Valid(string path) + { + Assert.True(PathUtilities.IsPathFullyQualified(path)); + Assert.True(PathUtilities.IsPathFullyQualified(path.AsSpan())); + } + private static void AssertEqual(ReadOnlySpan expected, ReadOnlySpan actual) { if (!actual.SequenceEqual(expected)) diff --git a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PathUtilities.cs b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PathUtilities.cs index fd042160f5b..65c1c5ce396 100644 --- a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PathUtilities.cs +++ b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PathUtilities.cs @@ -36,7 +36,7 @@ public static ReadOnlySpan GetExtension(ReadOnlySpan path) { return i != length - 1 ? path[i..length] - : []; + : []; } if (IsDirectorySeparator(ch)) @@ -70,5 +70,95 @@ public static bool HasExtension(ReadOnlySpan path) private static bool IsDirectorySeparator(char ch) => ch == Path.DirectorySeparatorChar || (PlatformInformation.IsWindows && ch == Path.AltDirectorySeparatorChar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidDriveChar(char value) + => (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a'); +#endif + + public static bool IsPathFullyQualified(string path) + { + ArgHelper.ThrowIfNull(path); + + return IsPathFullyQualified(path.AsSpan()); + } + + public static bool IsPathFullyQualified(ReadOnlySpan path) + { +#if NET + return Path.IsPathFullyQualified(path); +#else + if (PlatformInformation.IsWindows) + { + // Derived from .NET Runtime: + // - https://github.com/dotnet/runtime/blob/c7c961a330395152e5ec4000032cd3204ceb4a10/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L250-L274 + + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return false; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return path[1] == '?' || IsDirectorySeparator(path[1]); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return (path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0]); + } + else + { + // Derived from .NET Runtime: + // - https://github.com/dotnet/runtime/blob/c7c961a330395152e5ec4000032cd3204ceb4a10/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L77-L82 + + // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative) + // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified. + return IsPathRooted(path); + } +#endif + } + + public static bool IsPathRooted(string path) + { +#if NET + return Path.IsPathRooted(path); +#else + return IsPathRooted(path.AsSpan()); +#endif + } + + public static bool IsPathRooted(ReadOnlySpan path) + { +#if NET + return Path.IsPathRooted(path); + +#else + if (PlatformInformation.IsWindows) + { + // Derived from .NET Runtime + // - https://github.com/dotnet/runtime/blob/850c0ab4519b904a28f2d67abdaba1ac78c955ff/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs#L271-L276 + + var length = path.Length; + return (length >= 1 && IsDirectorySeparator(path[0])) + || (length >= 2 && IsValidDriveChar(path[0]) && path[1] == Path.VolumeSeparatorChar); + } + else + { + // Derived from .NET Runtime + // - https://github.com/dotnet/runtime/blob/850c0ab4519b904a28f2d67abdaba1ac78c955ff/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs#L132-L135 + + return path.StartsWith(Path.DirectorySeparatorChar); + } #endif } +} diff --git a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/SpanExtensions.cs b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/SpanExtensions.cs index f61e91c7206..1748316a359 100644 --- a/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/SpanExtensions.cs +++ b/src/razor/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/SpanExtensions.cs @@ -2,10 +2,11 @@ // Licensed under the MIT license. See License.txt in the project root for license information. #if !NET8_0_OR_GREATER -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #endif +using System.Runtime.CompilerServices; + namespace System; internal static class SpanExtensions @@ -61,4 +62,32 @@ public static unsafe void Replace(this Span span, char oldValue, char newV } #endif } + + /// + /// Determines whether the specified value appears at the start of the span. + /// + /// The span to search. + /// The value to compare. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan span, T value) + where T : IEquatable? => +#if NET9_0_OR_GREATER + MemoryExtensions.StartsWith(span, value); +#else + span.Length != 0 && (span[0]?.Equals(value) ?? (object?)value is null); +#endif + + /// + /// Determines whether the specified value appears at the end of the span. + /// + /// The span to search. + /// The value to compare. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlySpan span, T value) + where T : IEquatable? => +#if NET9_0_OR_GREATER + MemoryExtensions.EndsWith(span, value); +#else + span.Length != 0 && (span[^1]?.Equals(value) ?? (object?)value is null); +#endif } diff --git a/src/roslyn-analyzers/eng/Version.Details.xml b/src/roslyn-analyzers/eng/Version.Details.xml index 89ed9a47655..b9de13d8487 100644 --- a/src/roslyn-analyzers/eng/Version.Details.xml +++ b/src/roslyn-analyzers/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + https://github.com/dotnet/roslyn @@ -8,13 +8,13 @@ - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 5d437e96e412b189af05abb84753996de9aa698f + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 https://github.com/dotnet/roslyn diff --git a/src/roslyn-analyzers/eng/Versions.props b/src/roslyn-analyzers/eng/Versions.props index e0b611bc49f..9bf4ef1f14e 100644 --- a/src/roslyn-analyzers/eng/Versions.props +++ b/src/roslyn-analyzers/eng/Versions.props @@ -75,7 +75,7 @@ 1.1.2-beta1.24314.1 - 10.0.0-beta.25263.104 + 10.0.0-beta.25267.102 0.13.0 2.14.1 diff --git a/src/roslyn-analyzers/eng/common/build.ps1 b/src/roslyn-analyzers/eng/common/build.ps1 index 6b3be1916fc..ae2309e312d 100644 --- a/src/roslyn-analyzers/eng/common/build.ps1 +++ b/src/roslyn-analyzers/eng/common/build.ps1 @@ -127,7 +127,7 @@ function Build { /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` - /p:DotNetBuildRepo=$productBuild ` + /p:DotNetBuild=$productBuild ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` diff --git a/src/roslyn-analyzers/eng/common/build.sh b/src/roslyn-analyzers/eng/common/build.sh index 27ae2c85601..da906da2026 100755 --- a/src/roslyn-analyzers/eng/common/build.sh +++ b/src/roslyn-analyzers/eng/common/build.sh @@ -129,14 +129,14 @@ while [[ $# > 0 ]]; do -pack) pack=true ;; - -sourcebuild|-sb) + -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; - -productbuild|-pb) + -productbuild|-product-build|-pb) build=true product_build=true restore=true @@ -241,7 +241,7 @@ function Build { /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ - /p:DotNetBuildRepo=$product_build \ + /p:DotNetBuild=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ /p:Rebuild=$rebuild \ /p:Test=$test \ diff --git a/src/roslyn-analyzers/eng/common/core-templates/steps/source-build.yml b/src/roslyn-analyzers/eng/common/core-templates/steps/source-build.yml index f2a0f347fdd..0dde553c3eb 100644 --- a/src/roslyn-analyzers/eng/common/core-templates/steps/source-build.yml +++ b/src/roslyn-analyzers/eng/common/core-templates/steps/source-build.yml @@ -51,13 +51,12 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ + --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $baseRidArgs \ $portableBuildArgs \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml diff --git a/src/roslyn-analyzers/eng/common/tools.ps1 b/src/roslyn-analyzers/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/roslyn-analyzers/eng/common/tools.ps1 +++ b/src/roslyn-analyzers/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/roslyn-analyzers/eng/common/tools.sh b/src/roslyn-analyzers/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/roslyn-analyzers/eng/common/tools.sh +++ b/src/roslyn-analyzers/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/roslyn-analyzers/global.json b/src/roslyn-analyzers/global.json index c840c4569a7..f3c6fbd0abb 100644 --- a/src/roslyn-analyzers/global.json +++ b/src/roslyn-analyzers/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "10.0.100-preview.3.25201.16", + "dotnet": "10.0.100-preview.5.25265.106", "runtimes": { "dotnet": [ "3.1.7", @@ -13,11 +13,11 @@ "xcopy-msbuild": "17.13.0" }, "sdk": { - "version": "10.0.100-preview.3.25201.16", + "version": "10.0.100-preview.5.25265.106", "allowPrerelease": true, "rollForward": "patch" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25263.104" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25267.102" } } diff --git a/src/roslyn/Compilers.slnf b/src/roslyn/Compilers.slnf index e73b1562e7b..49acd2063d5 100644 --- a/src/roslyn/Compilers.slnf +++ b/src/roslyn/Compilers.slnf @@ -20,7 +20,9 @@ "src\\Compilers\\Core\\AnalyzerDriver\\AnalyzerDriver.shproj", "src\\Compilers\\Core\\CodeAnalysisTest\\Microsoft.CodeAnalysis.UnitTests.csproj", "src\\Compilers\\Core\\MSBuildTaskTests\\Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj", - "src\\Compilers\\Core\\MSBuildTask\\Microsoft.Build.Tasks.CodeAnalysis.csproj", + "src\\Compilers\\Core\\SdkTaskTests\\Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests.csproj", + "src\\Compilers\\Core\\MSBuildTask\\MSBuild\\Microsoft.Build.Tasks.CodeAnalysis.csproj", + "src\\Compilers\\Core\\MSBuildTask\\Sdk\\Microsoft.Build.Tasks.CodeAnalysis.Sdk.csproj", "src\\Compilers\\Core\\Portable\\Microsoft.CodeAnalysis.csproj", "src\\Compilers\\Core\\RebuildTest\\Microsoft.CodeAnalysis.Rebuild.UnitTests.csproj", "src\\Compilers\\Core\\Rebuild\\Microsoft.CodeAnalysis.Rebuild.csproj", diff --git a/src/roslyn/Roslyn.sln b/src/roslyn/Roslyn.sln index 58cfacb5e57..701f568ef6a 100644 --- a/src/roslyn/Roslyn.sln +++ b/src/roslyn/Roslyn.sln @@ -263,8 +263,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Remo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Remote.ServiceHub", "src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj", "{80FDDD00-9393-47F7-8BAF-7E87CE011068}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeAnalysis", "src\Compilers\Core\MSBuildTask\Microsoft.Build.Tasks.CodeAnalysis.csproj", "{7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.VisualStudio.Next.UnitTests", "src\VisualStudio\Core\Test.Next\Roslyn.VisualStudio.Next.UnitTests.csproj", "{2E1658E2-5045-4F85-A64C-C0ECCD39F719}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildBoss", "src\Tools\BuildBoss\BuildBoss.csproj", "{9C0660D9-48CA-40E1-BABA-8F6A1F11FE10}" @@ -729,6 +727,12 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Exte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Extensions.Package", "src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Extensions\Microsoft.CodeAnalysis.Extensions.Package.csproj", "{EEFAB994-3778-9C0D-1E88-C0ABB1D3DE43}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeAnalysis", "src\Compilers\Core\MSBuildTask\MSBuild\Microsoft.Build.Tasks.CodeAnalysis.csproj", "{1B4AC233-B345-123F-E004-DAA28DE1CB08}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeAnalysis.Sdk", "src\Compilers\Core\MSBuildTask\Sdk\Microsoft.Build.Tasks.CodeAnalysis.Sdk.csproj", "{91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests", "src\Compilers\Core\SdkTaskTests\Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests.csproj", "{5399BBCC-417F-C710-46DE-EB0C0074C34D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1119,10 +1123,6 @@ Global {80FDDD00-9393-47F7-8BAF-7E87CE011068}.Debug|Any CPU.Build.0 = Debug|Any CPU {80FDDD00-9393-47F7-8BAF-7E87CE011068}.Release|Any CPU.ActiveCfg = Release|Any CPU {80FDDD00-9393-47F7-8BAF-7E87CE011068}.Release|Any CPU.Build.0 = Release|Any CPU - {7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3}.Release|Any CPU.Build.0 = Release|Any CPU {2E1658E2-5045-4F85-A64C-C0ECCD39F719}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E1658E2-5045-4F85-A64C-C0ECCD39F719}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E1658E2-5045-4F85-A64C-C0ECCD39F719}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1795,6 +1795,18 @@ Global {EEFAB994-3778-9C0D-1E88-C0ABB1D3DE43}.Debug|Any CPU.Build.0 = Debug|Any CPU {EEFAB994-3778-9C0D-1E88-C0ABB1D3DE43}.Release|Any CPU.ActiveCfg = Release|Any CPU {EEFAB994-3778-9C0D-1E88-C0ABB1D3DE43}.Release|Any CPU.Build.0 = Release|Any CPU + {1B4AC233-B345-123F-E004-DAA28DE1CB08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B4AC233-B345-123F-E004-DAA28DE1CB08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B4AC233-B345-123F-E004-DAA28DE1CB08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B4AC233-B345-123F-E004-DAA28DE1CB08}.Release|Any CPU.Build.0 = Release|Any CPU + {91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11}.Release|Any CPU.Build.0 = Release|Any CPU + {5399BBCC-417F-C710-46DE-EB0C0074C34D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5399BBCC-417F-C710-46DE-EB0C0074C34D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5399BBCC-417F-C710-46DE-EB0C0074C34D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5399BBCC-417F-C710-46DE-EB0C0074C34D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1915,7 +1927,6 @@ Global {C1930979-C824-496B-A630-70F5369A636F} = {C2D1346B-9665-4150-B644-075CF1636BAA} {F822F72A-CC87-4E31-B57D-853F65CBEBF3} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} {80FDDD00-9393-47F7-8BAF-7E87CE011068} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} - {7AD4FE65-9A30-41A6-8004-AA8F89BCB7F3} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} {2E1658E2-5045-4F85-A64C-C0ECCD39F719} = {8DBA5174-B0AA-4561-82B1-A46607697753} {9C0660D9-48CA-40E1-BABA-8F6A1F11FE10} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {21A01C2D-2501-4619-8144-48977DD22D9C} = {38940C5F-97FD-4B2A-B2CD-C4E4EF601B05} @@ -2136,6 +2147,9 @@ Global {6C816C16-D563-884A-D65B-5E68C6FB6659} = {8977A560-45C2-4EC2-A849-97335B382C74} {02BCC112-0A29-43AA-84FA-C71C18A9486C} = {7A69EA65-4411-4CD0-B439-035E720C1BD3} {EEFAB994-3778-9C0D-1E88-C0ABB1D3DE43} = {7A69EA65-4411-4CD0-B439-035E720C1BD3} + {1B4AC233-B345-123F-E004-DAA28DE1CB08} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} + {91F9EAA4-ACA2-87EE-868E-6CC3B73D6A11} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} + {5399BBCC-417F-C710-46DE-EB0C0074C34D} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} @@ -2150,6 +2164,7 @@ Global src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis.Utilities.projitems*{0a1267e9-52ff-b8de-8522-802be55f41da}*SharedItemsImports = 5 src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{0c2e1633-1462-4712-88f4-a0c945bad3a8}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{0c2e1633-1462-4712-88f4-a0c945bad3a8}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{1b4ac233-b345-123f-e004-daa28de1cb08}*SharedItemsImports = 5 src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{1b6c4a1a-413b-41fb-9f85-5c09118e541b}*SharedItemsImports = 13 src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 @@ -2211,7 +2226,6 @@ Global src\RoslynAnalyzers\Utilities\Workspaces\Workspaces.Utilities.projitems*{7005dd7b-d3b6-1360-313b-975974aa6254}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 - src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{7ad4fe65-9a30-41a6-8004-aa8f89bcb7f3}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 src\RoslynAnalyzers\Utilities\Compiler\Analyzer.Utilities.projitems*{8087bde4-6707-05a5-5f84-dfe6628e8ec8}*SharedItemsImports = 5 src\RoslynAnalyzers\Utilities\Workspaces\Workspaces.Utilities.projitems*{8087bde4-6707-05a5-5f84-dfe6628e8ec8}*SharedItemsImports = 5 @@ -2223,6 +2237,7 @@ Global src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 src\Dependencies\Threading\Microsoft.CodeAnalysis.Threading.projitems*{8e2a252e-a140-45a6-a81a-2652996ea589}*SharedItemsImports = 5 + src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.projitems*{91f9eaa4-aca2-87ee-868e-6cc3b73d6a11}*SharedItemsImports = 5 src\RoslynAnalyzers\Utilities\Compiler\Analyzer.Utilities.projitems*{923e34ba-ca8a-971e-7ff7-51dd346394a1}*SharedItemsImports = 5 src\RoslynAnalyzers\Utilities\Workspaces\Workspaces.Utilities.projitems*{923e34ba-ca8a-971e-7ff7-51dd346394a1}*SharedItemsImports = 5 src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{94faf461-2e74-4dbb-9813-6b2cde6f1880}*SharedItemsImports = 13 diff --git a/src/roslyn/SpellingExclusions.dic b/src/roslyn/SpellingExclusions.dic index 3d49d6e1f80..6bcf960f73f 100644 --- a/src/roslyn/SpellingExclusions.dic +++ b/src/roslyn/SpellingExclusions.dic @@ -1,7 +1,6 @@ -stackalloc +stackalloc awaitable -Refactorings -Infos -cref -binlog -Namer +Refactorings +Infos +cref +binlog \ No newline at end of file diff --git a/src/roslyn/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/roslyn/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index c1191eba65e..f54ef8c0535 100644 --- a/src/roslyn/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/roslyn/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -3487,13 +3487,6 @@ private void EmitConstantExpression(TypeSymbol type, ConstantValue constantValue } else { - // TODO: use-site dependencies are not reported to UsedAssemblyReferences https://github.com/dotnet/roslyn/issues/78172 - if (constantValue.IsString && constantValue.StringValue.Length > _module.Compilation.DataSectionStringLiteralThreshold) - { - _ = Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_Text_Encoding__get_UTF8, _diagnostics, syntax: syntaxNode); - _ = Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_Text_Encoding__GetString, _diagnostics, syntax: syntaxNode); - } - _builder.EmitConstantValue(constantValue, syntaxNode); } } diff --git a/src/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs b/src/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs index 1018ea9ba53..0dcce524cce 100644 --- a/src/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs +++ b/src/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs @@ -244,6 +244,14 @@ internal override void ForceComplete(SourceLocation? locationOpt, Predicate "abc";""")] + [InlineData("""public static void M(string s) { switch (s) { case "abc": break; } }""")] + public void DataSectionStringLiterals_UsedAssemblyReferences(string code) + { + var source1 = """ + namespace System + { + public class Object; + public class String + { + public static bool op_Equality(string a, string b) => false; + } + public class ValueType; + public struct Void; + public struct Byte; + public struct Int16; + public struct Int32; + public struct Int64; + public struct Boolean; + public class Attribute; + public class Enum; + public enum AttributeTargets; + public class AttributeUsageAttribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + } + """; + var ref1 = CreateEmptyCompilation(source1, assemblyName: "MinimalCoreLib").VerifyDiagnostics().EmitToImageReference(); + + var source2 = """ + namespace System.Text + { + public class Encoding + { + public static Encoding UTF8 => null; + public unsafe string GetString(byte* bytes, int byteCount) => null; + } + } + """; + var ref2 = CreateEmptyCompilation(source2, [ref1], options: TestOptions.UnsafeDebugDll, assemblyName: "Encoding") + .VerifyDiagnostics().EmitToImageReference(); + + var source3 = $$""" + public static class C + { + {{code}} + } + """; + var comp = CreateEmptyCompilation(source3, [ref1, ref2], assemblyName: "Lib1"); + AssertEx.SetEqual([ref1], comp.GetUsedAssemblyReferences()); + comp.VerifyEmitDiagnostics(); + + comp = CreateEmptyCompilation(source3, [ref1, ref2], assemblyName: "Lib2", + parseOptions: TestOptions.Regular.WithFeature("experimental-data-section-string-literals", "0")); + AssertEx.SetEqual([ref1, ref2], comp.GetUsedAssemblyReferences()); + comp.VerifyEmitDiagnostics(); + } + [Fact] public void DataSectionStringLiterals_InvalidUtf8() { @@ -3561,8 +3591,7 @@ public class InvalidOperationException(); """; var parseOptions = TestOptions.RegularPreview - .WithNoRefSafetyRulesAttribute() - .WithFeature("experimental-data-section-string-literals", "0"); + .WithNoRefSafetyRulesAttribute(); CompileAndVerify(CreateEmptyCompilation(source, parseOptions: parseOptions), verify: Verification.Skipped, @@ -3587,6 +3616,16 @@ public class InvalidOperationException(); .VerifyDiagnostics( // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1)); + + // NOTE: If the feature is enabled by default in the future, it should not fail in case of missing Encoding members + // (it should be automatically disabled instead and could warn) to avoid regressing the scenario above. + CreateEmptyCompilation(source, + parseOptions: parseOptions.WithFeature("experimental-data-section-string-literals", "0")) + .VerifyDiagnostics( + // error CS0656: Missing compiler required member 'System.Text.Encoding.get_UTF8' + Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.Text.Encoding", "get_UTF8").WithLocation(1, 1), + // error CS0656: Missing compiler required member 'System.Text.Encoding.GetString' + Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.Text.Encoding", "GetString").WithLocation(1, 1)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/76707")] diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.props b/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.props new file mode 100644 index 00000000000..6c4c27e3282 --- /dev/null +++ b/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.props @@ -0,0 +1,71 @@ + + + + + + en-US + true + true + + + false + + + $(NoWarn);CA1819 + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + true + None + true + contentFiles\any\any + + + diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.targets b/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.targets new file mode 100644 index 00000000000..5579a307979 --- /dev/null +++ b/src/roslyn/src/Compilers/Core/MSBuildTask/Directory.Build.targets @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + PreserveNewest + true + None + true + contentFiles\any\any + + + + + + <_CompilerApiVersion>$([System.Version]::Parse($(VersionPrefix)).Major).$([System.Version]::Parse($(VersionPrefix)).Minor) + <_CompilerVersionTargetsFileContent> + + roslyn$(_CompilerApiVersion) + +]]> + + + + + \ No newline at end of file diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/MSBuild/Microsoft.Build.Tasks.CodeAnalysis.csproj b/src/roslyn/src/Compilers/Core/MSBuildTask/MSBuild/Microsoft.Build.Tasks.CodeAnalysis.csproj new file mode 100644 index 00000000000..56fe58e6caa --- /dev/null +++ b/src/roslyn/src/Compilers/Core/MSBuildTask/MSBuild/Microsoft.Build.Tasks.CodeAnalysis.csproj @@ -0,0 +1,15 @@ + + + + + Library + Microsoft.CodeAnalysis.BuildTasks + $(NetRoslynSourceBuild);net472 + + + + + + + + diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs b/src/roslyn/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs index e292ca94505..356ba7a0719 100644 --- a/src/roslyn/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs +++ b/src/roslyn/src/Compilers/Core/MSBuildTask/ManagedToolTask.cs @@ -16,7 +16,22 @@ namespace Microsoft.CodeAnalysis.BuildTasks { public abstract class ManagedToolTask : ToolTask { - private static bool DefaultIsSdkFrameworkToCoreBridgeTask { get; } = CalculateIsSdkFrameworkToCoreBridgeTask(); + /// + /// A copy of this task, compiled for .NET Framework, is deployed into the .NET SDK. It is a bridge task + /// that is loaded into .NET Framework MSBuild but launches the .NET Core compiler. This task necessarily + /// has different behaviors than the standard build task compiled for .NET Framework and loaded into the + /// .NET Framework MSBuild. + /// + /// + /// The reason this task is a different assembly is to allow both the MSBuild and .NET SDK copy to be loaded + /// into the same MSBuild process. + /// + internal static bool IsSdkFrameworkToCoreBridgeTask { get; } = +#if NETFRAMEWORK && SDK_TASK + true; +#else + false; +#endif /// /// Is the builtin tool being used here? When false the developer has specified a custom tool @@ -31,21 +46,10 @@ public abstract class ManagedToolTask : ToolTask /// protected bool UsingBuiltinTool => string.IsNullOrEmpty(ToolPath) && ToolExe == ToolName; - /// - /// A copy of this task, compiled for .NET Framework, is deployed into the .NET SDK. It is a bridge task - /// that is loaded into .NET Framework MSBuild but launches the .NET Core compiler. This task necessarily - /// has different behaviors than the standard build task compiled for .NET Framework and loaded into the - /// .NET Framework MSBuild. - /// - /// - /// This is mutable to facilitate testing - /// - internal bool IsSdkFrameworkToCoreBridgeTask { get; init; } = DefaultIsSdkFrameworkToCoreBridgeTask; - /// /// Is the builtin tool executed by this task running on .NET Core? /// - internal bool IsBuiltinToolRunningOnCoreClr => RuntimeHostInfo.IsCoreClrRuntime || IsSdkFrameworkToCoreBridgeTask; + internal static bool IsBuiltinToolRunningOnCoreClr => RuntimeHostInfo.IsCoreClrRuntime || IsSdkFrameworkToCoreBridgeTask; internal string PathToBuiltInTool => Path.Combine(GetToolDirectory(), ToolName); @@ -176,7 +180,7 @@ protected static ITaskItem[] GenerateCommandLineArgsTaskItems(List comma return items; } - private string GetToolDirectory() + private static string GetToolDirectory() { var buildTask = typeof(ManagedToolTask).Assembly; var buildTaskDirectory = GetBuildTaskDirectory(); @@ -189,33 +193,6 @@ private string GetToolDirectory() #endif } - /// - /// - /// - /// - /// Using the file system as a way to differentiate between the two tasks is not ideal, but it is effective - /// and allows us to avoid significantly complicating the build process. The alternative is another parameter - /// to the Csc / Vbc / etc ... tasks that all invocations would need to pass along. - /// - internal static bool CalculateIsSdkFrameworkToCoreBridgeTask() - { -#if NET - return false; -#else - // This logic needs to be updated when this issue is fixed. That moves csc.exe out to a subdirectory - // and hence the check below will need to change - // - // https://github.com/dotnet/roslyn/issues/78001 - - var buildTaskDirectory = GetBuildTaskDirectory(); - var buildTaskDirectoryName = Path.GetFileName(buildTaskDirectory); - return - string.Equals(buildTaskDirectoryName, "binfx", StringComparison.OrdinalIgnoreCase) && - !File.Exists(Path.Combine(buildTaskDirectory, "csc.exe")) && - Directory.Exists(Path.Combine(buildTaskDirectory, "..", "bincore")); -#endif - } - internal static string GetBuildTaskDirectory() { var buildTask = typeof(ManagedToolTask).Assembly; diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj b/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj deleted file mode 100644 index 1cc48b02c01..00000000000 --- a/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Build.Tasks.CodeAnalysis.csproj +++ /dev/null @@ -1,108 +0,0 @@ - - - - - 14.0 - Library - Microsoft.CodeAnalysis.BuildTasks - en-US - $(NetRoslynSourceBuild);net472 - true - - - false - - - $(NoWarn);CA1819 - - - true - Microsoft.CodeAnalysis.Build.Tasks - - - The build task and targets used by MSBuild to run the C# and VB compilers. - Supports using VBCSCompiler on Windows. - - true - - - - - PreserveNewest - true - None - true - contentFiles\any\any - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - true - None - true - contentFiles\any\any - - - - - - - - <_CompilerApiVersion>$([System.Version]::Parse($(VersionPrefix)).Major).$([System.Version]::Parse($(VersionPrefix)).Minor) - <_CompilerVersionTargetsFileContent> - - - roslyn$(_CompilerApiVersion) - -]]> - - - - - - - - diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets b/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets index b40b3f07468..3f73efa8f77 100644 --- a/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets +++ b/src/roslyn/src/Compilers/Core/MSBuildTask/Microsoft.Managed.Core.targets @@ -4,12 +4,14 @@ <_BuildTasksDirectory>$(MSBuildThisFileDirectory) <_BuildTasksDirectory Condition="Exists('$(RoslynTargetsPath)')">$(RoslynTargetsPath)\ + <_BuildTasksAssemblyName>Microsoft.Build.Tasks.CodeAnalysis + <_BuildTasksAssemblyName Condition="!Exists('$(_BuildTasksDirectory)$(_BuildTasksAssemblyName)') and Exists('$(_BuildTasksDirectory)Microsoft.Build.Tasks.CodeAnalysis.Sdk.dll')">Microsoft.Build.Tasks.CodeAnalysis.Sdk - + @@ -63,7 +65,7 @@ ======================== --> - + @@ -156,7 +158,7 @@ --> - + + + + + Library + Microsoft.CodeAnalysis.BuildTasks + net472 + SDK_TASK;$(DefineConstants) + + + true + + + + + + + + diff --git a/src/roslyn/src/Compilers/Core/MSBuildTask/TaskTestHost.cs b/src/roslyn/src/Compilers/Core/MSBuildTask/TaskTestHost.cs deleted file mode 100644 index 98f0c83b02a..00000000000 --- a/src/roslyn/src/Compilers/Core/MSBuildTask/TaskTestHost.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace Microsoft.CodeAnalysis.BuildTasks -{ -#if NETFRAMEWORK && DEBUG - - /// - /// This class is used in to allow us to test custom - /// disk layouts and its behavior in the context of the managed tool task. - /// - internal sealed class TaskTestHost : MarshalByRefObject - { - internal bool IsSdkFrameworkToCoreBridgeTask => ManagedToolTask.CalculateIsSdkFrameworkToCoreBridgeTask(); - } - -#endif -} diff --git a/src/roslyn/src/Compilers/Core/MSBuildTaskTests/CscTests.cs b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/CscTests.cs index 02007237d16..e7303f02c4a 100644 --- a/src/roslyn/src/Compilers/Core/MSBuildTaskTests/CscTests.cs +++ b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/CscTests.cs @@ -649,107 +649,5 @@ void parseRef(string refText, string alias) Assert.Throws(() => csc.GenerateResponseFileContents()); } } - - [Fact] - public void PathToManagedTool_Normal() - { - var taskPath = Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!; - var relativePath = RuntimeHostInfo.IsCoreClrRuntime - ? Path.Combine("bincore", "csc.dll") - : "csc.exe"; - var task = new Csc(); - Assert.Equal(Path.Combine(taskPath, relativePath), task.PathToBuiltInTool); - } - -#if NETFRAMEWORK - - [Fact] - public void PathToManagedTool_Bridge() - { - var taskPath = Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!; - var task = new Csc() - { - IsSdkFrameworkToCoreBridgeTask = true - }; - Assert.Equal(Path.Combine(taskPath, "..", "bincore", "csc.dll"), task.PathToBuiltInTool); - } - -#endif - - [Fact] - public void IsManagedToolRunningOnCoreClr_Normal() - { - var task = new Csc(); - Assert.Equal(RuntimeHostInfo.IsCoreClrRuntime, task.IsBuiltinToolRunningOnCoreClr); - } - - [Fact] - public void IsManagedToolRunningOnCoreClr_Bridge() - { -#if NET - Assert.False(ManagedToolTask.CalculateIsSdkFrameworkToCoreBridgeTask()); -#else - var task = new Csc(); - Assert.False(task.IsBuiltinToolRunningOnCoreClr); -#endif - } - -#if NETFRAMEWORK && DEBUG - - [Theory] - [InlineData("binfx", true, true)] - [InlineData("binfx", false, false)] - [InlineData("other", true, false)] - [InlineData("other", false, false)] - public void CalculateIsSdkFrameworkToCoreBridgeTask_DirectoryName(string dirName, bool makeBincore, bool expected) - { - LoadInAppDomain(dirName, (appDomain, taskAssemblyName, dirPath) => - { - if (makeBincore) - { - _ = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(dirPath), "bincore")); - } - - var testHost = (TaskTestHost)appDomain.CreateInstanceAndUnwrap(taskAssemblyName, typeof(TaskTestHost).FullName); - Assert.Equal(expected, testHost.IsSdkFrameworkToCoreBridgeTask); - }); - } - - [Fact] - public void CalculateIsSdkFrameworkToCoreBridgeTask_CscPresent() - { - LoadInAppDomain("binfx", (appDomain, taskAssemblyName, dirPath) => - { - File.WriteAllText(Path.Combine(dirPath, "csc.exe"), "real code"); - var testHost = (TaskTestHost)appDomain.CreateInstanceAndUnwrap(taskAssemblyName, typeof(TaskTestHost).FullName); - Assert.False(testHost.IsSdkFrameworkToCoreBridgeTask); - }); - } - - private static void LoadInAppDomain(string dirName, Action action) - { - using var tempRoot = new TempRoot(); - var dirPath = tempRoot.CreateDirectory().CreateDirectory(dirName).Path; - var taskAssembly = typeof(ManagedCompiler).Assembly; - var taskFilePath = taskAssembly.Location!; - var taskPath = Path.GetDirectoryName(taskFilePath); - foreach (var dllPath in Directory.EnumerateFiles(taskPath, "*.dll")) - { - File.Copy(dllPath, Path.Combine(dirPath, Path.GetFileName(dllPath))); - } - - var appDomain = Roslyn.Test.Utilities.Desktop.AppDomainUtils.Create("TestAppDomain", dirPath); - try - { - action(appDomain, taskAssembly.FullName, dirPath); - } - finally - { - AppDomain.Unload(appDomain); - } - } - -#endif - } } diff --git a/src/roslyn/src/Compilers/Core/MSBuildTaskTests/MSBuildManagedToolTests.cs b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/MSBuildManagedToolTests.cs new file mode 100644 index 00000000000..4e69936b4e6 --- /dev/null +++ b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/MSBuildManagedToolTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.BuildTasks.UnitTests; + +public sealed class MSBuildManagedToolTests +{ + [Fact] + public void PathToBuiltinTool() + { + var taskPath = Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!; + var relativePath = RuntimeHostInfo.IsCoreClrRuntime + ? Path.Combine("bincore", "csc.dll") + : "csc.exe"; + var task = new Csc(); + Assert.Equal(Path.Combine(taskPath, relativePath), task.PathToBuiltInTool); + } + + [Fact] + public void IsSdkFrameworkToCoreBridgeTask() + { + Assert.False(ManagedToolTask.IsSdkFrameworkToCoreBridgeTask); + } + + [Fact] + public void IsBuiltinToolRunningOnCoreClr() + { + Assert.Equal(RuntimeHostInfo.IsCoreClrRuntime, ManagedToolTask.IsBuiltinToolRunningOnCoreClr); + } +} diff --git a/src/roslyn/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj index 7f3264bb5f7..59ae5eb2b95 100644 --- a/src/roslyn/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj +++ b/src/roslyn/src/Compilers/Core/MSBuildTaskTests/Microsoft.Build.Tasks.CodeAnalysis.UnitTests.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/roslyn/src/Compilers/Core/SdkTaskTests/Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests.csproj b/src/roslyn/src/Compilers/Core/SdkTaskTests/Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests.csproj new file mode 100644 index 00000000000..b0f7e319cef --- /dev/null +++ b/src/roslyn/src/Compilers/Core/SdkTaskTests/Microsoft.Build.Tasks.CodeAnalysis.Sdk.UnitTests.csproj @@ -0,0 +1,25 @@ + + + + + Library + Microsoft.CodeAnalysis.BuildTasks.Sdk.UnitTests + true + net472 + + + + + + + + + + + + + + + + + diff --git a/src/roslyn/src/Compilers/Core/SdkTaskTests/SdkManagedToolTests.cs b/src/roslyn/src/Compilers/Core/SdkTaskTests/SdkManagedToolTests.cs new file mode 100644 index 00000000000..9c235ed8f8f --- /dev/null +++ b/src/roslyn/src/Compilers/Core/SdkTaskTests/SdkManagedToolTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Roslyn.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit.Abstractions; +using Xunit; + +namespace Microsoft.CodeAnalysis.BuildTasks.Sdk.UnitTests; + +public sealed class SdkManagedToolTests +{ + public ITestOutputHelper TestOutputHelper { get; } + + public SdkManagedToolTests(ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + } + + [Fact] + public void PathToBuiltinTool() + { + var taskPath = Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!; + var task = new Csc(); + Assert.Equal(Path.Combine(taskPath, "..", "bincore", "csc.dll"), task.PathToBuiltInTool); + } + + [Fact] + public void IsSdkFrameworkToCoreBridgeTask() + { + Assert.True(ManagedToolTask.IsSdkFrameworkToCoreBridgeTask); + } + + [Fact] + public void IsBuiltinToolRunningOnCoreClr() + { + Assert.True(ManagedToolTask.IsBuiltinToolRunningOnCoreClr); + } +} diff --git a/src/roslyn/src/Compilers/Extension/Roslyn.Compilers.Extension.csproj b/src/roslyn/src/Compilers/Extension/Roslyn.Compilers.Extension.csproj index d2c5a638009..9c84fa22c52 100644 --- a/src/roslyn/src/Compilers/Extension/Roslyn.Compilers.Extension.csproj +++ b/src/roslyn/src/Compilers/Extension/Roslyn.Compilers.Extension.csproj @@ -59,7 +59,7 @@ DebugSymbolsProjectOutputGroup%3b true - + BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems DebugSymbolsProjectOutputGroup%3b true diff --git a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinlogNamer.cs b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinLogPathProvider.cs similarity index 83% rename from src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinlogNamer.cs rename to src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinLogPathProvider.cs index c2535e2923d..05958b1a0a7 100644 --- a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinlogNamer.cs +++ b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/BinLogPathProvider.cs @@ -4,14 +4,15 @@ using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Composition; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; -[Export(typeof(BinlogNamer)), Shared] -internal sealed class BinlogNamer +[Export(typeof(IBinLogPathProvider)), Shared] +internal sealed class BinLogPathProvider : IBinLogPathProvider { /// /// The suffix to use for the binary log name; incremented each time we have a new build. Should be incremented with . @@ -28,13 +29,13 @@ internal sealed class BinlogNamer [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public BinlogNamer(IGlobalOptionService globalOptionService, ILoggerFactory loggerFactory) + public BinLogPathProvider(IGlobalOptionService globalOptionService, ILoggerFactory loggerFactory) { _globalOptionService = globalOptionService; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); } - internal string? GetMSBuildBinaryLogPath() + public string? GetNewLogPath() { if (_globalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.BinaryLogPath) is not string binaryLogDirectory) return null; diff --git a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsProjectSystem.cs b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsProjectSystem.cs index 4b229d8497a..f60b31557ee 100644 --- a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsProjectSystem.cs +++ b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsProjectSystem.cs @@ -41,7 +41,7 @@ public FileBasedProgramsProjectSystem( IAsynchronousOperationListenerProvider listenerProvider, ProjectLoadTelemetryReporter projectLoadTelemetry, ServerConfigurationFactory serverConfigurationFactory, - BinlogNamer binlogNamer) + IBinLogPathProvider binLogPathProvider) : base( workspaceFactory.FileBasedProgramsProjectFactory, workspaceFactory.TargetFrameworkManager, @@ -52,7 +52,7 @@ public FileBasedProgramsProjectSystem( listenerProvider, projectLoadTelemetry, serverConfigurationFactory, - binlogNamer) + binLogPathProvider) { _lspServices = lspServices; _logger = loggerFactory.CreateLogger(); diff --git a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsWorkspaceProviderFactory.cs b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsWorkspaceProviderFactory.cs index d7c63e9f2ae..de90d3175e2 100644 --- a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsWorkspaceProviderFactory.cs +++ b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileBasedProgramsWorkspaceProviderFactory.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.ProjectTelemetry; using Microsoft.CodeAnalysis.MetadataAsSource; +using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ProjectSystem; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -33,10 +34,10 @@ internal sealed class FileBasedProgramsWorkspaceProviderFactory( IAsynchronousOperationListenerProvider listenerProvider, ProjectLoadTelemetryReporter projectLoadTelemetry, ServerConfigurationFactory serverConfigurationFactory, - BinlogNamer binlogNamer) : ILspMiscellaneousFilesWorkspaceProviderFactory + IBinLogPathProvider binLogPathProvider) : ILspMiscellaneousFilesWorkspaceProviderFactory { public ILspMiscellaneousFilesWorkspaceProvider CreateLspMiscellaneousFilesWorkspaceProvider(ILspServices lspServices, HostServices hostServices) { - return new FileBasedProgramsProjectSystem(lspServices, metadataAsSourceFileService, workspaceFactory, fileChangeWatcher, globalOptionService, loggerFactory, listenerProvider, projectLoadTelemetry, serverConfigurationFactory, binlogNamer); + return new FileBasedProgramsProjectSystem(lspServices, metadataAsSourceFileService, workspaceFactory, fileChangeWatcher, globalOptionService, loggerFactory, listenerProvider, projectLoadTelemetry, serverConfigurationFactory, binLogPathProvider); } } diff --git a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs index d97745c2edb..b16e6c6ccf0 100644 --- a/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs +++ b/src/roslyn/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs @@ -43,7 +43,7 @@ internal abstract class LanguageServerProjectLoader protected readonly ILoggerFactory LoggerFactory; private readonly ILogger _logger; private readonly ProjectLoadTelemetryReporter _projectLoadTelemetryReporter; - private readonly BinlogNamer _binlogNamer; + private readonly IBinLogPathProvider _binLogPathProvider; protected readonly ImmutableDictionary AdditionalProperties; /// @@ -98,7 +98,7 @@ protected LanguageServerProjectLoader( IAsynchronousOperationListenerProvider listenerProvider, ProjectLoadTelemetryReporter projectLoadTelemetry, ServerConfigurationFactory serverConfigurationFactory, - BinlogNamer binlogNamer) + IBinLogPathProvider binLogPathProvider) { ProjectFactory = projectFactory; _targetFrameworkManager = targetFrameworkManager; @@ -108,7 +108,7 @@ protected LanguageServerProjectLoader( LoggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(nameof(LanguageServerProjectLoader)); _projectLoadTelemetryReporter = projectLoadTelemetry; - _binlogNamer = binlogNamer; + _binLogPathProvider = binLogPathProvider; var workspace = projectFactory.Workspace; var razorDesignTimePath = serverConfigurationFactory.ServerConfiguration?.RazorDesignTimePath; @@ -145,9 +145,7 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList Path.ChangeExtension(documentFilePath, ".csproj"); + #region TODO: Copy-pasted from dotnet run-api. Delete when run-api is adopted. + // See https://github.com/dotnet/sdk/blob/b5dbc69cc28676ac6ea615654c8016a11b75e747/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs#L10 + private static class Sha256Hasher + { + public static string Hash(string text) + { + byte[] bytes = Encoding.UTF8.GetBytes(text); + byte[] hash = SHA256.HashData(bytes); +#if NET9_0_OR_GREATER + return Convert.ToHexStringLower(hash); +#else + return Convert.ToHexString(hash).ToLowerInvariant(); +#endif + } + + public static string HashWithNormalizedCasing(string text) + { + return Hash(text.ToUpperInvariant()); + } + } + + // TODO: this is a copy of SDK run-api code. Must delete when adopting run-api. + // See https://github.com/dotnet/sdk/blob/5a4292947487a9d34f4256c1d17fb3dc26859174/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs#L449 + internal static string GetArtifactsPath(string entryPointFileFullPath) + { + // We want a location where permissions are expected to be restricted to the current user. + string directory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.GetTempPath() + : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + // Include entry point file name so the directory name is not completely opaque. + string fileName = Path.GetFileNameWithoutExtension(entryPointFileFullPath); + string hash = Sha256Hasher.HashWithNormalizedCasing(entryPointFileFullPath); + string directoryName = $"{fileName}-{hash}"; + + return Path.Join(directory, "dotnet", "runfile", directoryName); + } + #endregion + internal static (string virtualProjectXml, bool isFileBasedProgram) MakeVirtualProjectContent(string documentFilePath, SourceText text) { Contract.ThrowIfFalse(PathUtilities.IsAbsolute(documentFilePath)); @@ -30,48 +72,74 @@ internal static (string virtualProjectXml, bool isFileBasedProgram) MakeVirtualP var root = tree.GetRoot(); var isFileBasedProgram = root.GetLeadingTrivia().Any(SyntaxKind.IgnoredDirectiveTrivia) || root.ChildNodes().Any(node => node.IsKind(SyntaxKind.GlobalStatement)); + var artifactsPath = GetArtifactsPath(documentFilePath); + + var targetFramework = Environment.GetEnvironmentVariable("DOTNET_RUN_FILE_TFM") ?? "net10.0"; + var virtualProjectXml = $""" - + + + + false + {SecurityElement.Escape(artifactsPath)} + - - Exe - net8.0 - enable - enable - $(Features);FileBasedProgram - false - + + + + Exe + {SecurityElement.Escape(targetFramework)} + enable + enable + + + + false + + + + preview + + + + $(Features);FileBasedProgram + + + + + + + + + + + + + + + + + + - + <_RestoreProjectPathItems Include="@(FilteredRestoreGraphProjectInputItems)" /> + + + + + - - - - - - - - - - - <_RestoreProjectPathItems Include="@(FilteredRestoreGraphProjectInputItems)" /> - - - - - - """; diff --git a/src/roslyn/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/roslyn/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index 187cd244bdf..f3c9f824904 100644 --- a/src/roslyn/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/roslyn/src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -41,7 +41,7 @@ internal static partial class ProtocolConversions private static readonly char[] s_dirSeparators = [PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar]; - private static readonly Regex s_markdownEscapeRegex = new(@"([\\`\*_\{\}\[\]\(\)#+\-\.!])", RegexOptions.Compiled); + private static readonly Regex s_markdownEscapeRegex = new(@"([\\`\*_\{\}\[\]\(\)#+\-\.!<>])", RegexOptions.Compiled); // NOTE: While the spec allows it, don't use Function and Method, as both VS and VS Code display them the same // way which can confuse users diff --git a/src/roslyn/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/roslyn/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index d272e37c274..af452f1de5f 100644 --- a/src/roslyn/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/roslyn/src/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -527,6 +527,7 @@ public async Task DoAsync() expectedLocation).ConfigureAwait(false); Assert.Equal(expectedMarkdown, results.Contents.Fourth.Value); } + [Theory, CombinatorialData] public async Task TestGetHoverAsync_UsesNonBreakingSpaceForSupportedPlatforms(bool mutatingLspWorkspace) { @@ -586,6 +587,47 @@ static void Main(string[] args) Assert.Equal(expectedMarkdown, result.Contents.Fourth.Value); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/vscode-csharp/issues/6577")] + public async Task TestGetHoverAsync_EscapesAngleBracketsInGenerics(bool mutatingLspWorkspace) + { + var markup = + """ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Threading.Tasks; + class C + { + private async Task>> GetData() + { + {|caret:var|} d = await GetData(); + return null; + } + } + """; + var clientCapabilities = new LSP.ClientCapabilities + { + TextDocument = new LSP.TextDocumentClientCapabilities { Hover = new LSP.HoverSetting { ContentFormat = [LSP.MarkupKind.Markdown] } } + }; + await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); + var expectedLocation = testLspServer.GetLocations("caret").Single(); + + var expectedMarkdown = """ + ```csharp + interface System.Collections.Generic.IDictionary + ``` + + + TKey is string + TValue is ImmutableArray\ + + """; + + var results = await RunGetHoverAsync( + testLspServer, + expectedLocation).ConfigureAwait(false); + Assert.Equal(expectedMarkdown, results.Contents.Fourth.Value); + } + private static async Task RunGetHoverAsync( TestLspServer testLspServer, LSP.Location caret, diff --git a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj index 2456300ab12..54016ae2414 100644 --- a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj +++ b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/Microsoft.Net.Compilers.Toolset.Package.csproj @@ -31,7 +31,8 @@ - + + <_File Include="@(CoreClrCompilerBinArtifact)" TargetDir="tasks/netcore/bincore"/> <_File Include="@(CoreClrCompilerBinRuntimesArtifact)" TargetDir="tasks/netcore/bincore/runtimes"/> - <_File Include="@(DesktopCompilerArtifact)" Condition="'%(DesktopCompilerArtifact.NeededForBuildTask)' == 'true'" TargetDir="tasks/netcore/binfx"/> - <_File Include="@(DesktopCompilerResourceArtifact)" Condition="'%(DesktopCompilerResourceArtifact.NeededForBuildTask)' == 'true'" TargetDir="tasks/netcore/binfx"/> + <_File Include="@(BridgeCompilerArtifact)" TargetDir="tasks/netcore/binfx"/> + <_File Include="@(BridgeCompilerResourceArtifact)" TargetDir="tasks/netcore/binfx"/> <_FileWithPath Include="@(_File)" TargetPath="%(_File.TargetDir)/%(_File.RecursiveDir)%(_File.FileName)%(_File.Extension)" /> diff --git a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props index 8dc5f46a1ec..710e106bed4 100644 --- a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props +++ b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/AnyCpu/build/Microsoft.Net.Compilers.Toolset.props @@ -5,6 +5,7 @@ <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' == 'Core'">netcore <_RoslynTargetDirectoryName Condition="'$(MSBuildRuntimeType)' != 'Core'">net472 <_RoslynTasksDirectory>$(MSBuildThisFileDirectory)..\tasks\$(_RoslynTargetDirectoryName)\ + Custom $(_RoslynTasksDirectory)Microsoft.Build.Tasks.CodeAnalysis.dll true $(_RoslynTasksDirectory)Microsoft.CSharp.Core.targets diff --git a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets index 5e5207a85e1..747d3732e84 100644 --- a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets +++ b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/DesktopCompilerArtifacts.targets @@ -48,7 +48,7 @@ - + @@ -62,19 +62,19 @@ We don't currently collect optimization data for the following assemblies. --> - <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Buffers.dll" NeededForBuildTask="true"/> + <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Buffers.dll" /> <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Collections.Immutable.dll"/> - <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Memory.dll" NeededForBuildTask="true"/> + <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Memory.dll" /> <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Numerics.Vectors.dll"/> <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Reflection.Metadata.dll"/> - <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Runtime.CompilerServices.Unsafe.dll" NeededForBuildTask="true"/> + <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Runtime.CompilerServices.Unsafe.dll" /> <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Text.Encoding.CodePages.dll"/> <_NoOptimizationData Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Threading.Tasks.Extensions.dll"/> - <_NoNGen Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Numerics.Vectors.dll" NeededForBuildTask="true"/> + <_NoNGen Include="$(ArtifactsBinDir)csi\$(Configuration)\net472\System.Numerics.Vectors.dll" /> @@ -86,8 +86,15 @@ - + + + + + + + + diff --git a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/Framework/Microsoft.Net.Compilers.Toolset.Framework.Package.csproj b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/Framework/Microsoft.Net.Compilers.Toolset.Framework.Package.csproj index a907edcd537..da4ffd5050f 100644 --- a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/Framework/Microsoft.Net.Compilers.Toolset.Framework.Package.csproj +++ b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/Framework/Microsoft.Net.Compilers.Toolset.Framework.Package.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/Microsoft.Net.Compilers.Toolset.Arm64.Package.csproj b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/Microsoft.Net.Compilers.Toolset.Arm64.Package.csproj index 51c21a5b9d6..05b8b035813 100644 --- a/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/Microsoft.Net.Compilers.Toolset.Arm64.Package.csproj +++ b/src/roslyn/src/NuGet/Microsoft.Net.Compilers.Toolset/arm64/Microsoft.Net.Compilers.Toolset.Arm64.Package.csproj @@ -32,7 +32,7 @@ - + - + diff --git a/src/roslyn/src/Setup/DevDivVsix/CompilersPackage/CompilersPackage.targets b/src/roslyn/src/Setup/DevDivVsix/CompilersPackage/CompilersPackage.targets index b398c100c00..e3ebb3a6ad2 100644 --- a/src/roslyn/src/Setup/DevDivVsix/CompilersPackage/CompilersPackage.targets +++ b/src/roslyn/src/Setup/DevDivVsix/CompilersPackage/CompilersPackage.targets @@ -8,7 +8,7 @@ - + diff --git a/src/roslyn/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/roslyn/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs index 4720b750bfc..28e24e4ea2c 100644 --- a/src/roslyn/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs +++ b/src/roslyn/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs @@ -191,7 +191,7 @@ private bool CheckPackages(TextWriter textWriter) (@"tasks\netcore\bincore", GetProjectPublishDirectory("vbc", "net9.0")), (@"tasks\netcore\bincore", GetProjectPublishDirectory("VBCSCompiler", "net9.0")), (@"tasks\netcore", GetProjectPublishDirectory("Microsoft.Build.Tasks.CodeAnalysis", "net9.0")), - (@"tasks\netcore\binfx", GetProjectOutputDirectory("Microsoft.Build.Tasks.CodeAnalysis", "net472"))); + (@"tasks\netcore\binfx", GetProjectOutputDirectory("Microsoft.Build.Tasks.CodeAnalysis.Sdk", "net472"))); foreach (var arch in new[] { "x86", "x64", "arm64" }) { diff --git a/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs b/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs index 057a90fafd4..5600ee46d9a 100644 --- a/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs +++ b/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/BuildHostProcessManager.cs @@ -25,15 +25,15 @@ internal sealed class BuildHostProcessManager : IAsyncDisposable private readonly ImmutableDictionary _globalMSBuildProperties; private readonly ILoggerFactory? _loggerFactory; private readonly ILogger? _logger; - private readonly string? _binaryLogPath; + private readonly IBinLogPathProvider? _binaryLogPathProvider; private readonly SemaphoreSlim _gate = new(initialCount: 1); private readonly Dictionary _processes = []; - public BuildHostProcessManager(ImmutableDictionary? globalMSBuildProperties = null, string? binaryLogPath = null, ILoggerFactory? loggerFactory = null) + public BuildHostProcessManager(ImmutableDictionary? globalMSBuildProperties = null, IBinLogPathProvider? binaryLogPathProvider = null, ILoggerFactory? loggerFactory = null) { _globalMSBuildProperties = globalMSBuildProperties ?? ImmutableDictionary.Empty; - _binaryLogPath = binaryLogPath; + _binaryLogPathProvider = binaryLogPathProvider; _loggerFactory = loggerFactory; _logger = loggerFactory?.CreateLogger(); } @@ -244,10 +244,10 @@ private void AppendBuildHostCommandLineArgumentsConfigureProcess(ProcessStartInf AddArgument(processStartInfo, globalMSBuildProperty.Key + '=' + globalMSBuildProperty.Value); } - if (_binaryLogPath is not null) + if (_binaryLogPathProvider?.GetNewLogPath() is string binaryLogPath) { AddArgument(processStartInfo, "--binlog"); - AddArgument(processStartInfo, _binaryLogPath); + AddArgument(processStartInfo, binaryLogPath); } AddArgument(processStartInfo, "--locale"); diff --git a/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/IBinLogPathProvider.cs b/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/IBinLogPathProvider.cs new file mode 100644 index 00000000000..d2a47962f4d --- /dev/null +++ b/src/roslyn/src/Workspaces/MSBuild/Core/MSBuild/IBinLogPathProvider.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. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.MSBuild; + +internal interface IBinLogPathProvider +{ + /// + /// Returns a new log path. Each call will return a new name, so that way we don't have collisions if multiple processes are writing to different logs. + /// + /// A new path, or null if no logging is currently wanted. An instance is allowed to switch between return null and returning non-null if + /// the user changes configuration. + string? GetNewLogPath(); +} diff --git a/src/roslyn/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs b/src/roslyn/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs index 8db19e288e2..c89105505db 100644 --- a/src/roslyn/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs +++ b/src/roslyn/src/Workspaces/MSBuild/Test/BuildHostProcessManagerTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Moq; using Roslyn.Test.Utilities; using Xunit; @@ -65,7 +66,10 @@ internal void ProcessStartInfo_PassesBinLogPath(BuildHostProcessKind buildHostKi { const string BinaryLogPath = "test.binlog"; - var processStartInfo = new BuildHostProcessManager(binaryLogPath: BinaryLogPath) + var binLogPathProviderMock = new Mock(MockBehavior.Strict); + binLogPathProviderMock.Setup(m => m.GetNewLogPath()).Returns(BinaryLogPath); + + var processStartInfo = new BuildHostProcessManager(binaryLogPathProvider: binLogPathProviderMock.Object) .CreateBuildHostStartInfo(buildHostKind, pipeName: ""); #if NET diff --git a/src/runtime/NuGet.config b/src/runtime/NuGet.config index b8539443065..240b4807a1a 100644 --- a/src/runtime/NuGet.config +++ b/src/runtime/NuGet.config @@ -19,6 +19,8 @@ + + diff --git a/src/runtime/docs/design/coreclr/jit/GC-write-barriers.md b/src/runtime/docs/design/coreclr/jit/GC-write-barriers.md new file mode 100644 index 00000000000..c0b58e572d5 --- /dev/null +++ b/src/runtime/docs/design/coreclr/jit/GC-write-barriers.md @@ -0,0 +1,101 @@ +# GC write barriers + +The GC write barrier function (JIT_WriteBarrier) is generally the hottest function in CoreCLR and is written in assembly. The full pseudo code for the function is as follows: + + +```` +JIT_WriteBarrier(Object **dst, Object *ref) + Set *dst = ref + + // Shadow Heap update + ifdef WRITE_BARRIER_CHECK: // Only set in DEBUG mode + if g_GCShadow != 0: + long *shadow_dst = g_GCShadow + (dst - g_lowest_address) + // Check shadow heap location is within shadow heap + if shadow_dst < g_GCShadowEnd: + *shadow_dst = ref + atomic: wait for stores to complete + if *dst != ref: + *shadow_dst = INVALIDGCVALUE + + // Update the write watch table, if it's in use + ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP: + if g_sw_ww_table != 0: + char *ww_table_dst = g_sw_ww_table + (dst>>11) + if *ww_table_dst != 0: + *ww_table_dst = 0xff + + // Return if the reference is not in ephemeral generations + if ref < g_ephemeral_low || ref >= g_ephemeral_high: + return + + // Region Checks + if g_region_to_generation_table != 0: + + // Calculate region generations + char reg_loc_dst = *((dst >> g_region_shr) + g_region_to_generation_table) + char reg_loc_ref = *((ref >> g_region_shr) + g_region_to_generation_table) + + // Return if the region we're storing into is Gen 0 + if reg_loc_dst == 0: + return + + // Return if the new reference is not from old to young + if reg_loc_ref >= reg_loc_dst: + return + + // Bitwise write barriers only + if g_region_use_bitwise_write_barrier: + + char *card_table_dst = (dst >> 11) + g_card_table + char dst_bit = 1 << (dst >> 8 && 7) + + // Check if we need to update the card table + if *card_table_dst & dst_bit == 0: + return + + // Atomically update the card table + lock: *card_table_dst |= dst_bit + + goto CardBundle + + // Check if we need to update the card table + char *card_table_dst = (dst >> 11) + g_card_table + if *card_table_dst == 0xff: + return + + // Update the card table + *card_table_dst = 0xff + +CardBundle: + + // Mark the card bundle table as dirty + Ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES: + char card_bundle_dst = (dst >> 21) + g_card_bundle_table + if *card_bundle_dst != 0xff: + *card_bundle_dst = 0xff + +```` + +The Checked Write Barrier has additional checks: + +```` +JIT_CheckedWriteBarrier(Object **dst, Object *ref) + + // Return if the destination is not on the heap + if ref < g_lowest_address || ref >= g_highest_address: + return + + return JIT_WriteBarrier(dst, ref) +```` + +## WriteBarrierManager + +On AMD64 and Arm64, there several different implementations of the write barrier function. Each version is a subset of the `JIT_WriteBarrier` above, assuming different state, meaning most `if` checks can be skipped. The actual write barrier that is called is a copy of one of these implementations. + +The WriteBarrierManager keeps track of which implementation is currently being used. As internal state changes, the WriteBarrierManager updates the copy to the correct implementation. In practice, most of the internal state is fixed on startup, with only changes to/from use of write watch barriers changing during runtime. + +`WRITE_BARRIER_CHECK` is only set in `DEBUG` mode. On Arm64 `WRITE_BARRIER_CHECK` checks exist at the top of each version of the function when `DEBUG` mode is enabled. On `Amd64` these checks do not exist. Instead, a special `JIT_WriteBarrier_Debug` version of the function exists, which contains most of the functionality of `JIT_WriteBarrier` pseudo code and is used exclusively when `DEBUG` mode is enabled. + +On Arm64, `g_region_use_bitwise_write_barrier` is only set if LSE atomics are present on the hardware, as only LSE provides a single instruction to atomically update a byte via a bitwise OR. + diff --git a/src/runtime/eng/testing/tests.browser.targets b/src/runtime/eng/testing/tests.browser.targets index d591b8a608b..4264bde6d01 100644 --- a/src/runtime/eng/testing/tests.browser.targets +++ b/src/runtime/eng/testing/tests.browser.targets @@ -13,9 +13,10 @@ - false + true + false true <_WasmMainJSFileName Condition="'$(WasmMainJSPath)' != ''">$([System.IO.Path]::GetFileName('$(WasmMainJSPath)')) diff --git a/src/runtime/eng/testing/tests.wasm.targets b/src/runtime/eng/testing/tests.wasm.targets index 10148f9c8ef..a5aed85a7fc 100644 --- a/src/runtime/eng/testing/tests.wasm.targets +++ b/src/runtime/eng/testing/tests.wasm.targets @@ -133,6 +133,7 @@ <_WasmPropertyNames Include="WasmDedup" /> <_WasmPropertyNames Include="WasmLinkIcalls" /> <_WasmPropertyNames Include="WasmNativeStrip" /> + <_WasmPropertyNames Include="WasmNativeDebugSymbols" /> <_WasmPropertyNames Include="_WasmDevel" /> <_WasmPropertyNames Include="_WasmStrictVersionMatch" /> <_WasmPropertyNames Include="WasmEmitSymbolMap" /> diff --git a/src/runtime/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs b/src/runtime/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs index e1bc5e6a094..df3da7a876b 100644 --- a/src/runtime/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs +++ b/src/runtime/src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs @@ -68,9 +68,9 @@ class AsmOffsets public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x132; #elif TARGET_X86 public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4; - public const int SIZEOF__StackFrameIterator = 0x3cc; - public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3ba; - public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c8; + public const int SIZEOF__StackFrameIterator = 0x3d4; + public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3c2; + public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3d0; #else // TARGET_64BIT public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4; public const int SIZEOF__StackFrameIterator = 0xcc; @@ -134,9 +134,9 @@ class AsmOffsets public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x12a; #elif TARGET_X86 public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4; - public const int SIZEOF__StackFrameIterator = 0x3c4; - public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3b2; - public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c0; + public const int SIZEOF__StackFrameIterator = 0x3cc; + public const int OFFSETOF__StackFrameIterator__m_isRuntimeWrappedExceptions = 0x3ba; + public const int OFFSETOF__StackFrameIterator__m_AdjustedControlPC = 0x3c8; #else // TARGET_64BIT public const int OFFSETOF__REGDISPLAY__m_pCurrentContext = 0x4; public const int SIZEOF__StackFrameIterator = 0xc4; diff --git a/src/runtime/src/coreclr/clrdefinitions.cmake b/src/runtime/src/coreclr/clrdefinitions.cmake index cb3b645ff0e..efb6ab0738a 100644 --- a/src/runtime/src/coreclr/clrdefinitions.cmake +++ b/src/runtime/src/coreclr/clrdefinitions.cmake @@ -218,10 +218,6 @@ if (FEATURE_STUBPRECODE_DYNAMIC_HELPERS) add_definitions(-DFEATURE_STUBPRECODE_DYNAMIC_HELPERS) endif() -if (CLR_CMAKE_TARGET_APPLE) - add_definitions(-DFEATURE_MAP_THUNKS_FROM_IMAGE) -endif() - # Use this function to enable building with a specific target OS and architecture set of defines # This is known to work for the set of defines used by the JIT and gcinfo, it is not likely correct for # other components of the runtime diff --git a/src/runtime/src/coreclr/debug/daccess/enummem.cpp b/src/runtime/src/coreclr/debug/daccess/enummem.cpp index 95d401caa0d..0f5d418d648 100644 --- a/src/runtime/src/coreclr/debug/daccess/enummem.cpp +++ b/src/runtime/src/coreclr/debug/daccess/enummem.cpp @@ -591,8 +591,6 @@ HRESULT ClrDataAccess::DumpManagedExcepObject(CLRDataEnumMemoryFlags flags, OBJE // Pulls in data to translate from token to MethodDesc FindLoadedMethodRefOrDef(pMD->GetMethodTable()->GetModule(), pMD->GetMemberDef()); - // Pulls in sequence points. - DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMD); PCODE addr = pMD->GetNativeCode(); if (addr != (PCODE)NULL) { @@ -970,9 +968,6 @@ HRESULT ClrDataAccess::EnumMemWalkStackHelper(CLRDataEnumMemoryFlags flags, // back to source lines for functions on stacks is very useful and we don't // want to allow the function to fail for all targets. - // Pulls in sequence points and local variable info - DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMethodDesc); - #if defined(FEATURE_EH_FUNCLETS) && defined(USE_GC_INFO_DECODER) if (addr != (PCODE)NULL) diff --git a/src/runtime/src/coreclr/gcdump/i386/gcdumpx86.cpp b/src/runtime/src/coreclr/gcdump/i386/gcdumpx86.cpp index c035f8ac64b..bb85705b45c 100644 --- a/src/runtime/src/coreclr/gcdump/i386/gcdumpx86.cpp +++ b/src/runtime/src/coreclr/gcdump/i386/gcdumpx86.cpp @@ -114,6 +114,17 @@ size_t GCDump::DumpInfoHdr (PTR_CBYTE gcInfoBlock, header->revPInvokeOffset = count; } + if (header->noGCRegionCnt == HAS_NOGCREGIONS) + { + hasArgTabOffset = TRUE; + table += decodeUnsigned(table, &count); + header->noGCRegionCnt = count; + } + else if (header->noGCRegionCnt > 0) + { + hasArgTabOffset = TRUE; + } + // // First print out all the basic information // @@ -156,6 +167,8 @@ size_t GCDump::DumpInfoHdr (PTR_CBYTE gcInfoBlock, gcPrintf(" Sync region = [%u,%u] ([0x%x,0x%x])\n", header->syncStartOffset, header->syncEndOffset, header->syncStartOffset, header->syncEndOffset); + if (header->noGCRegionCnt > 0) + gcPrintf(" no GC region count = %2u \n", header->noGCRegionCnt); if (header->epilogCount > 1 || (header->epilogCount != 0 && header->epilogAtEnd == 0)) @@ -232,6 +245,23 @@ size_t GCDump::DumpGCTable(PTR_CBYTE table, if (header.ebxSaved) calleeSavedRegs++; } + /* Dump the no GC region table */ + + if (header.noGCRegionCnt > 0) + { + count = header.noGCRegionCnt; + while (count-- > 0) + { + unsigned regionOffset; + unsigned regionSize; + + table += decodeUnsigned(table, ®ionOffset); + table += decodeUnsigned(table, ®ionSize); + + gcPrintf("[%04X-%04X) no GC region\n", regionOffset, regionOffset + regionSize); + } + } + /* Dump the untracked frame variable table */ count = header.untrackedCnt; @@ -995,6 +1025,12 @@ void GCDump::DumpPtrsInFrame(PTR_CBYTE gcInfoBlock, header.revPInvokeOffset = offset; _ASSERTE(offset != INVALID_REV_PINVOKE_OFFSET); } + if (header.noGCRegionCnt == HAS_NOGCREGIONS) + { + unsigned count; + table += decodeUnsigned(table, &count); + header.noGCRegionCnt = count; + } prologSize = header.prologSize; epilogSize = header.epilogSize; diff --git a/src/runtime/src/coreclr/inc/executableallocator.h b/src/runtime/src/coreclr/inc/executableallocator.h index 973b950ad36..11caf3a6857 100644 --- a/src/runtime/src/coreclr/inc/executableallocator.h +++ b/src/runtime/src/coreclr/inc/executableallocator.h @@ -182,9 +182,6 @@ class ExecutableAllocator // Return true if double mapping is enabled. static bool IsDoubleMappingEnabled(); - // Release memory allocated via DoubleMapping for either templates or normal double mapped data - void ReleaseWorker(void* pRX, bool releaseTemplate); - // Initialize the allocator instance bool Initialize(); @@ -265,18 +262,6 @@ class ExecutableAllocator // Unmap the RW mapping at the specified address void UnmapRW(void* pRW); - - // Allocate thunks from a template. pTemplate is the return value from CreateTemplate - void* AllocateThunksFromTemplate(void *pTemplate, size_t templateSize); - - // Free a set of thunks allocated from templates. pThunks must have been returned from AllocateThunksFromTemplate - void FreeThunksFromTemplate(void *pThunks, size_t templateSize); - - // Create a template - // If templateInImage is not null, it will attempt to use it as the template, otherwise it will create an temporary in memory file to serve as the template - // Some OS/Architectures may/may not be able to work with this, so this api is permitted to return NULL, and callers should have an alternate approach using - // the codePageGenerator directly. - void* CreateTemplate(void* templateInImage, size_t templateSize, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)); }; #define ExecutableWriterHolder ExecutableWriterHolderNoLog diff --git a/src/runtime/src/coreclr/inc/gc_unwind_x86.h b/src/runtime/src/coreclr/inc/gc_unwind_x86.h index 311c032c9cc..46d44afec4f 100644 --- a/src/runtime/src/coreclr/inc/gc_unwind_x86.h +++ b/src/runtime/src/coreclr/inc/gc_unwind_x86.h @@ -367,6 +367,8 @@ struct hdrInfo unsigned int syncEpilogStart; // The start of the epilog. Synchronized methods are guaranteed to have no more than one epilog. unsigned int revPInvokeOffset; // INVALID_REV_PINVOKE_OFFSET if there is no Reverse PInvoke frame + unsigned int noGCRegionCnt; + enum { NOT_IN_PROLOG = -1, NOT_IN_EPILOG = -1 }; int prologOffs; // NOT_IN_PROLOG if not in prolog @@ -403,4 +405,8 @@ size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken, unsigned curOffset, hdrInfo * infoPtr); +bool IsInNoGCRegion(hdrInfo * infoPtr, + PTR_CBYTE table, + unsigned curOffset); + #endif // _UNWIND_X86_H diff --git a/src/runtime/src/coreclr/inc/gcdecoder.cpp b/src/runtime/src/coreclr/inc/gcdecoder.cpp index d4a3c4c3a6f..26001f58894 100644 --- a/src/runtime/src/coreclr/inc/gcdecoder.cpp +++ b/src/runtime/src/coreclr/inc/gcdecoder.cpp @@ -205,9 +205,22 @@ PTR_CBYTE FASTCALL decodeHeader(PTR_CBYTE table, UINT32 version, InfoHdr* header nextByte = *table++; encoding = nextByte & ADJ_ENCODING_MAX; // encoding here always corresponds to codes in InfoHdrAdjust2 set - - _ASSERTE(encoding < SET_RET_KIND_MAX); - header->returnKind = (ReturnKind)encoding; + if (encoding <= SET_RET_KIND_MAX) + { + header->returnKind = (ReturnKind)encoding; + } + else if (encoding < FFFF_NOGCREGION_CNT) + { + header->noGCRegionCnt = encoding - SET_NOGCREGIONS_CNT; + } + else if (encoding == FFFF_NOGCREGION_CNT) + { + header->noGCRegionCnt = HAS_NOGCREGIONS; + } + else + { + _ASSERTE(!"Unexpected encoding"); + } break; } } @@ -470,7 +483,8 @@ bool InfoHdrSmall::isHeaderMatch(const InfoHdr& target) const target.varPtrTableSize != HAS_VARPTR && target.gsCookieOffset != HAS_GS_COOKIE_OFFSET && target.syncStartOffset != HAS_SYNC_OFFSET && - target.revPInvokeOffset != HAS_REV_PINVOKE_FRAME_OFFSET); + target.revPInvokeOffset != HAS_REV_PINVOKE_FRAME_OFFSET && + target.noGCRegionCnt != HAS_NOGCREGIONS); #endif // compare two InfoHdr's up to but not including the untrackCnt field @@ -495,7 +509,10 @@ bool InfoHdrSmall::isHeaderMatch(const InfoHdr& target) const if (target.syncStartOffset != INVALID_SYNC_OFFSET) return false; - if (target.revPInvokeOffset!= INVALID_REV_PINVOKE_OFFSET) + if (target.revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET) + return false; + + if (target.noGCRegionCnt > 0) return false; return true; diff --git a/src/runtime/src/coreclr/inc/gcinfotypes.h b/src/runtime/src/coreclr/inc/gcinfotypes.h index 0218a49853a..10d22d67094 100644 --- a/src/runtime/src/coreclr/inc/gcinfotypes.h +++ b/src/runtime/src/coreclr/inc/gcinfotypes.h @@ -320,7 +320,8 @@ enum infoHdrAdjustConstants { SET_EPILOGSIZE_MAX = 10, // Change to 6 SET_EPILOGCNT_MAX = 4, SET_UNTRACKED_MAX = 3, - SET_RET_KIND_MAX = 4, // 2 bits for ReturnKind + SET_RET_KIND_MAX = 3, // 2 bits for ReturnKind + SET_NOGCREGIONS_MAX = 4, ADJ_ENCODING_MAX = 0x7f, // Maximum valid encoding in a byte // Also used to mask off next bit from each encoding byte. MORE_BYTES_TO_FOLLOW = 0x80 // If the High-bit of a header or adjustment byte @@ -372,10 +373,13 @@ enum infoHdrAdjust { // Second set of opcodes, when first code is 0x4F enum infoHdrAdjust2 { SET_RETURNKIND = 0, // 0x00-SET_RET_KIND_MAX Set ReturnKind to value + SET_NOGCREGIONS_CNT = SET_RETURNKIND + SET_RET_KIND_MAX + 1, // 0x04 + FFFF_NOGCREGION_CNT = SET_NOGCREGIONS_CNT + SET_NOGCREGIONS_MAX + 1 // 0x09 There is a count (>SET_NOGCREGIONS_MAX) after the header encoding }; #define HAS_UNTRACKED ((unsigned int) -1) #define HAS_VARPTR ((unsigned int) -1) +#define HAS_NOGCREGIONS ((unsigned int) -1) // 0 is a valid offset for the Reverse P/Invoke block // So use -1 as the sentinel for invalid and -2 as the sentinel for present. @@ -424,7 +428,7 @@ struct InfoHdrSmall { unsigned short argCount; // 5,6 in bytes unsigned int frameSize; // 7,8,9,10 in bytes unsigned int untrackedCnt; // 11,12,13,14 - unsigned int varPtrTableSize; // 15.16,17,18 + unsigned int varPtrTableSize; // 15,16,17,18 // Checks whether "this" is compatible with "target". // It is not an exact bit match as "this" could have some @@ -442,7 +446,8 @@ struct InfoHdr : public InfoHdrSmall { unsigned int syncStartOffset; // 23,24,25,26 unsigned int syncEndOffset; // 27,28,29,30 unsigned int revPInvokeOffset; // 31,32,33,34 Available GcInfo v2 onwards, previously undefined - // 35 bytes total + unsigned int noGCRegionCnt; // 35,36,37,38 + // 39 bytes total // Checks whether "this" is compatible with "target". // It is not an exact bit match as "this" could have some @@ -457,7 +462,8 @@ struct InfoHdr : public InfoHdrSmall { target.varPtrTableSize != HAS_VARPTR && target.gsCookieOffset != HAS_GS_COOKIE_OFFSET && target.syncStartOffset != HAS_SYNC_OFFSET && - target.revPInvokeOffset != HAS_REV_PINVOKE_FRAME_OFFSET); + target.revPInvokeOffset != HAS_REV_PINVOKE_FRAME_OFFSET && + target.noGCRegionCnt != HAS_NOGCREGIONS); #endif // compare two InfoHdr's up to but not including the untrackCnt field @@ -488,6 +494,13 @@ struct InfoHdr : public InfoHdrSmall { (target.revPInvokeOffset == INVALID_REV_PINVOKE_OFFSET)) return false; + if (noGCRegionCnt != target.noGCRegionCnt) { + if (target.noGCRegionCnt <= SET_NOGCREGIONS_MAX) + return false; + else if (noGCRegionCnt != HAS_UNTRACKED) + return false; + } + return true; } }; @@ -518,6 +531,7 @@ inline void GetInfoHdr(int index, InfoHdr * header) header->syncStartOffset = INVALID_SYNC_OFFSET; header->syncEndOffset = INVALID_SYNC_OFFSET; header->revPInvokeOffset = INVALID_REV_PINVOKE_OFFSET; + header->noGCRegionCnt = 0; } PTR_CBYTE FASTCALL decodeHeader(PTR_CBYTE table, UINT32 version, InfoHdr* header); diff --git a/src/runtime/src/coreclr/inc/loaderheap.h b/src/runtime/src/coreclr/inc/loaderheap.h index d3040e0b4aa..782f93cedc6 100644 --- a/src/runtime/src/coreclr/inc/loaderheap.h +++ b/src/runtime/src/coreclr/inc/loaderheap.h @@ -455,19 +455,10 @@ class UnlockedLoaderHeap : public UnlockedLoaderHeapBase static void WeGotAFaultNowWhat(UnlockedLoaderHeap *pHeap); }; -struct InterleavedLoaderHeapConfig -{ - uint32_t StubSize; - void* Template; - void (*CodePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size); -}; - -void InitializeLoaderHeapConfig(InterleavedLoaderHeapConfig *pConfig, size_t stubSize, void* templateInImage, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)); - //=============================================================================== // This is the base class for InterleavedLoaderHeap It's used as a simple // allocator for stubs in a scheme where each stub is a small fixed size, and is paired -// with memory which is GetStubCodePageSize() bytes away. In addition there is an +// with memory which is GetOSStubPageSize() bytes away. In addition there is an // ability to free is via a "backout" mechanism that is not considered to have good performance. // //=============================================================================== @@ -501,13 +492,16 @@ class UnlockedInterleavedLoaderHeap : public UnlockedLoaderHeapBase InterleavedStubFreeListNode *m_pFreeListHead; - const InterleavedLoaderHeapConfig *m_pConfig; +public: +public: + void (*m_codePageGenerator)(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size); #ifndef DACCESS_COMPILE protected: UnlockedInterleavedLoaderHeap( RangeList *pRangeList, - const InterleavedLoaderHeapConfig *pConfig); + void (*codePageGenerator)(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size), + DWORD dwGranularity); virtual ~UnlockedInterleavedLoaderHeap(); #endif @@ -1045,11 +1039,13 @@ class InterleavedLoaderHeap : public UnlockedInterleavedLoaderHeap public: InterleavedLoaderHeap(RangeList *pRangeList, BOOL fUnlocked, - const InterleavedLoaderHeapConfig *pConfig + void (*codePageGenerator)(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size), + DWORD dwGranularity ) : UnlockedInterleavedLoaderHeap( pRangeList, - pConfig), + codePageGenerator, + dwGranularity), m_CriticalSection(fUnlocked ? NULL : CreateLoaderHeapLock()) { WRAPPER_NO_CONTRACT; diff --git a/src/runtime/src/coreclr/interpreter/compiler.cpp b/src/runtime/src/coreclr/interpreter/compiler.cpp index cf584149c87..9073f7a4d02 100644 --- a/src/runtime/src/coreclr/interpreter/compiler.cpp +++ b/src/runtime/src/coreclr/interpreter/compiler.cpp @@ -1418,6 +1418,10 @@ void InterpCompiler::EmitBranch(InterpOpcode opcode, int32_t ilOffset) if (target < 0 || target >= m_ILCodeSize) assert(0); + // Backwards branch, emit safepoint + if (ilOffset < 0) + AddIns(INTOP_SAFEPOINT); + InterpBasicBlock *pTargetBB = m_ppOffsetToBB[target]; assert(pTargetBB != NULL); @@ -2123,6 +2127,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) codeEnd = m_ip + m_ILCodeSize; + // Safepoint at each method entry. This could be done as part of a call, rather than + // adding an opcode. + AddIns(INTOP_SAFEPOINT); + linkBBlocks = true; needsRetryEmit = false; retry_emit: diff --git a/src/runtime/src/coreclr/interpreter/intops.def b/src/runtime/src/coreclr/interpreter/intops.def index ab9caede9b0..ee27ab90024 100644 --- a/src/runtime/src/coreclr/interpreter/intops.def +++ b/src/runtime/src/coreclr/interpreter/intops.def @@ -35,6 +35,7 @@ OPDEF(INTOP_LDLOCA, "ldloca", 3, 1, 0, InterpOpInt) OPDEF(INTOP_SWITCH, "switch", 0, 0, 1, InterpOpSwitch) +OPDEF(INTOP_SAFEPOINT, "safepoint", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_BR, "br", 2, 0, 0, InterpOpBranch) OPDEF(INTOP_BRFALSE_I4, "brfalse.i4", 3, 0, 1, InterpOpBranch) diff --git a/src/runtime/src/coreclr/jit/async.cpp b/src/runtime/src/coreclr/jit/async.cpp index d4818b87074..33ec19ce75d 100644 --- a/src/runtime/src/coreclr/jit/async.cpp +++ b/src/runtime/src/coreclr/jit/async.cpp @@ -1,6 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// +// This file implements the transformation of C# async methods into state +// machines. The transformation takes place late in the JIT pipeline, when most +// optimizations have already been performed, right before lowering. +// +// The transformation performs the following key operations: +// +// 1. Each async call becomes a suspension point where execution can pause and +// return to the caller, accompanied by a resumption point where execution can +// continue when the awaited operation completes. +// +// 2. When suspending at a suspension point a continuation object is created that contains: +// - All live local variables +// - State number to identify which await is being resumed +// - Return value from the awaited operation (filled in by the callee later) +// - Exception information if an exception occurred +// - Resumption function pointer +// - Flags containing additional information +// +// 3. The method entry is modified to include dispatch logic that checks for an +// incoming continuation and jumps to the appropriate resumption point. +// +// 4. Special handling is included for: +// - Exception propagation across await boundaries +// - Return value management for different types (primitives, references, structs) +// - Tiered compilation and On-Stack Replacement (OSR) +// - Optimized state capture based on variable liveness analysis +// +// The transformation ensures that the semantics of the original async method are +// preserved while enabling efficient suspension and resumption of execution. +// + #include "jitpch.h" #include "jitstd/algorithm.h" #include "async.h" diff --git a/src/runtime/src/coreclr/jit/codegenxarch.cpp b/src/runtime/src/coreclr/jit/codegenxarch.cpp index 1df74199425..01f639dc7dd 100644 --- a/src/runtime/src/coreclr/jit/codegenxarch.cpp +++ b/src/runtime/src/coreclr/jit/codegenxarch.cpp @@ -202,14 +202,10 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block) } else { -// TODO-Linux-x86: Do we need to handle the GC information for this NOP or JMP specially, as is done for other -// architectures? -#ifndef JIT32_GCENCODER // Because of the way the flowgraph is connected, the liveness info for this one instruction // after the call is not (can not be) correct in cases where a variable has a last use in the // handler. So turn off GC reporting once we execute the call and reenable after the jmp/nop GetEmitter()->emitDisableGC(); -#endif // JIT32_GCENCODER GetEmitter()->emitIns_J(INS_call, block->GetTarget()); @@ -229,9 +225,7 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block) inst_JMP(EJ_jmp, finallyContinuation); } -#ifndef JIT32_GCENCODER GetEmitter()->emitEnableGC(); -#endif // JIT32_GCENCODER } } #if defined(FEATURE_EH_WINDOWS_X86) @@ -1879,11 +1873,9 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) switch (treeNode->gtOper) { -#ifndef JIT32_GCENCODER case GT_START_NONGC: GetEmitter()->emitDisableGC(); break; -#endif // !defined(JIT32_GCENCODER) case GT_START_PREEMPTGC: // Kill callee saves GC registers, and create a label @@ -3160,9 +3152,7 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode) { case GenTreeBlk::BlkOpKindCpObjRepInstr: case GenTreeBlk::BlkOpKindCpObjUnroll: -#ifndef JIT32_GCENCODER assert(!storeBlkNode->gtBlkOpGcUnsafe); -#endif genCodeForCpObj(storeBlkNode->AsBlk()); break; @@ -3172,9 +3162,7 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode) break; case GenTreeBlk::BlkOpKindRepInstr: -#ifndef JIT32_GCENCODER assert(!storeBlkNode->gtBlkOpGcUnsafe); -#endif if (isCopyBlk) { genCodeForCpBlkRepMovs(storeBlkNode); @@ -3188,12 +3176,10 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode) case GenTreeBlk::BlkOpKindUnroll: if (isCopyBlk) { -#ifndef JIT32_GCENCODER if (storeBlkNode->gtBlkOpGcUnsafe) { GetEmitter()->emitDisableGC(); } -#endif if (storeBlkNode->gtBlkOpKind == GenTreeBlk::BlkOpKindUnroll) { genCodeForCpBlkUnroll(storeBlkNode); @@ -3203,18 +3189,14 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* storeBlkNode) assert(storeBlkNode->gtBlkOpKind == GenTreeBlk::BlkOpKindUnrollMemmove); genCodeForMemmove(storeBlkNode); } -#ifndef JIT32_GCENCODER if (storeBlkNode->gtBlkOpGcUnsafe) { GetEmitter()->emitEnableGC(); } -#endif } else { -#ifndef JIT32_GCENCODER assert(!storeBlkNode->gtBlkOpGcUnsafe); -#endif genCodeForInitBlkUnroll(storeBlkNode); } break; diff --git a/src/runtime/src/coreclr/jit/compiler.h b/src/runtime/src/coreclr/jit/compiler.h index de0f6afbdc8..daf898ff372 100644 --- a/src/runtime/src/coreclr/jit/compiler.h +++ b/src/runtime/src/coreclr/jit/compiler.h @@ -72,22 +72,22 @@ inline var_types genActualType(T value); * Forward declarations */ -struct InfoHdr; // defined in GCInfo.h -struct escapeMapping_t; // defined in fgdiagnostic.cpp -class emitter; // defined in emit.h -struct ShadowParamVarInfo; // defined in GSChecks.cpp -struct InitVarDscInfo; // defined in registerargconvention.h -class FgStack; // defined in fgbasic.cpp -class Instrumentor; // defined in fgprofile.cpp -class SpanningTreeVisitor; // defined in fgprofile.cpp -class CSE_DataFlow; // defined in optcse.cpp -struct CSEdsc; // defined in optcse.h -class CSE_HeuristicCommon; // defined in optcse.h -class OptBoolsDsc; // defined in optimizer.cpp -struct JumpThreadInfo; // defined in redundantbranchopts.cpp -class ProfileSynthesis; // defined in profilesynthesis.h -class LoopLocalOccurrences; // defined in inductionvariableopts.cpp -class RangeCheck; // defined in rangecheck.h +struct InfoHdr; // defined in GCInfo.h +struct escapeMapping_t; // defined in fgdiagnostic.cpp +class emitter; // defined in emit.h +struct ShadowParamVarInfo; // defined in GSChecks.cpp +struct InitVarDscInfo; // defined in registerargconvention.h +class FgStack; // defined in fgbasic.cpp +class Instrumentor; // defined in fgprofile.cpp +class SpanningTreeVisitor; // defined in fgprofile.cpp +class CSE_DataFlow; // defined in optcse.cpp +struct CSEdsc; // defined in optcse.h +class CSE_HeuristicCommon; // defined in optcse.h +class OptBoolsDsc; // defined in optimizer.cpp +struct JumpThreadInfo; // defined in redundantbranchopts.cpp +class ProfileSynthesis; // defined in profilesynthesis.h +class PerLoopInfo; // defined in inductionvariableopts.cpp +class RangeCheck; // defined in rangecheck.h #ifdef DEBUG struct IndentStack; #endif @@ -7654,33 +7654,30 @@ class Compiler void optVisitBoundingExitingCondBlocks(FlowGraphNaturalLoop* loop, TFunctor func); bool optMakeLoopDownwardsCounted(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, - LoopLocalOccurrences* loopLocals); + PerLoopInfo* loopLocals); bool optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, BasicBlock* exiting, - LoopLocalOccurrences* loopLocals); + PerLoopInfo* loopLocals); bool optCanAndShouldChangeExitTest(GenTree* cond, bool dump); - bool optLocalHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); + bool optLocalHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, PerLoopInfo* loopLocals); bool optLocalIsLiveIntoBlock(unsigned lclNum, BasicBlock* block); - bool optWidenIVs(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); - bool optWidenPrimaryIV(FlowGraphNaturalLoop* loop, - unsigned lclNum, - ScevAddRec* addRec, - LoopLocalOccurrences* loopLocals); + bool optWidenIVs(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, PerLoopInfo* loopLocals); + bool optWidenPrimaryIV(FlowGraphNaturalLoop* loop, unsigned lclNum, ScevAddRec* addRec, PerLoopInfo* loopLocals); bool optCanSinkWidenedIV(unsigned lclNum, FlowGraphNaturalLoop* loop); bool optIsIVWideningProfitable(unsigned lclNum, BasicBlock* initBlock, bool initedToConstant, FlowGraphNaturalLoop* loop, - LoopLocalOccurrences* loopLocals); + PerLoopInfo* loopLocals); void optBestEffortReplaceNarrowIVUses( unsigned lclNum, unsigned ssaNum, unsigned newLclNum, BasicBlock* block, Statement* firstStmt); void optReplaceWidenedIV(unsigned lclNum, unsigned ssaNum, unsigned newLclNum, Statement* stmt); void optSinkWidenedIV(unsigned lclNum, unsigned newLclNum, FlowGraphNaturalLoop* loop); - bool optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); + bool optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, PerLoopInfo* loopLocals); bool optIsUpdateOfIVWithoutSideEffects(GenTree* tree, unsigned lclNum); // Redundant branch opts @@ -7708,6 +7705,7 @@ class Compiler BitVecTraits* apTraits; ASSERT_TP apFull; ASSERT_TP apLocal; + ASSERT_TP apLocalPostorder; ASSERT_TP apLocalIfTrue; enum optAssertionKind : uint8_t diff --git a/src/runtime/src/coreclr/jit/emit.cpp b/src/runtime/src/coreclr/jit/emit.cpp index 40264818097..9e7c3cefeaa 100644 --- a/src/runtime/src/coreclr/jit/emit.cpp +++ b/src/runtime/src/coreclr/jit/emit.cpp @@ -10597,7 +10597,6 @@ regMaskTP emitter::emitGetGCRegsKilledByNoGCCall(CorInfoHelpFunc helper) return result; } -#if !defined(JIT32_GCENCODER) //------------------------------------------------------------------------ // emitDisableGC: Requests that the following instruction groups are not GC-interruptible. // @@ -10689,4 +10688,3 @@ void emitter::emitEnableGC() JITDUMP("Enable GC: still %u no-gc requests\n", emitNoGCRequestCount); } } -#endif // !defined(JIT32_GCENCODER) diff --git a/src/runtime/src/coreclr/jit/emit.h b/src/runtime/src/coreclr/jit/emit.h index f9c37456e24..0f11db154ed 100644 --- a/src/runtime/src/coreclr/jit/emit.h +++ b/src/runtime/src/coreclr/jit/emit.h @@ -2891,11 +2891,9 @@ class emitter void emitNewIG(); -#if !defined(JIT32_GCENCODER) void emitDisableGC(); void emitEnableGC(); bool emitGCDisabled(); -#endif // !defined(JIT32_GCENCODER) #if defined(TARGET_XARCH) static bool emitAlignInstHasNoCode(instrDesc* id); diff --git a/src/runtime/src/coreclr/jit/emitinl.h b/src/runtime/src/coreclr/jit/emitinl.h index a586193dd5b..02aab9d4ed9 100644 --- a/src/runtime/src/coreclr/jit/emitinl.h +++ b/src/runtime/src/coreclr/jit/emitinl.h @@ -585,10 +585,17 @@ inline bool insIsCMOV(instruction ins) * false. Returns the final result of the callback. */ template -bool emitter::emitGenNoGCLst(Callback& cb) +bool emitter::emitGenNoGCLst(Callback& cb, bool skipAllPrologsAndEpilogs /* = false */) { for (insGroup* ig = emitIGlist; ig; ig = ig->igNext) { + if (skipAllPrologsAndEpilogs) + { + if (ig == emitPrologIG) + continue; + if (ig->igFlags & (IGF_EPILOG | IGF_FUNCLET_PROLOG | IGF_FUNCLET_EPILOG)) + continue; + } if ((ig->igFlags & IGF_NOGCINTERRUPT) && ig->igSize > 0) { emitter::instrDesc* id = emitFirstInstrDesc(ig->igData); diff --git a/src/runtime/src/coreclr/jit/emitpub.h b/src/runtime/src/coreclr/jit/emitpub.h index bf15ba33667..5725c04e6f6 100644 --- a/src/runtime/src/coreclr/jit/emitpub.h +++ b/src/runtime/src/coreclr/jit/emitpub.h @@ -43,7 +43,7 @@ unsigned emitEndCodeGen(Compiler* comp, unsigned emitGetEpilogCnt(); template -bool emitGenNoGCLst(Callback& cb); +bool emitGenNoGCLst(Callback& cb, bool skipAllPrologsAndEpilogs = false); void emitBegProlog(); unsigned emitGetPrologOffsetEstimate(); diff --git a/src/runtime/src/coreclr/jit/gcencode.cpp b/src/runtime/src/coreclr/jit/gcencode.cpp index a8ba3bf4b23..b473b6ff0db 100644 --- a/src/runtime/src/coreclr/jit/gcencode.cpp +++ b/src/runtime/src/coreclr/jit/gcencode.cpp @@ -886,7 +886,7 @@ BYTE FASTCALL encodeHeaderNext(const InfoHdr& header, InfoHdr* state, BYTE& code state->returnKind = header.returnKind; codeSet = 2; // Two byte encoding encoding = header.returnKind; - _ASSERTE(encoding < SET_RET_KIND_MAX); + _ASSERTE(encoding <= SET_RET_KIND_MAX); goto DO_RETURN; } @@ -950,6 +950,27 @@ BYTE FASTCALL encodeHeaderNext(const InfoHdr& header, InfoHdr* state, BYTE& code } } + if (state->noGCRegionCnt != header.noGCRegionCnt) + { + assert(state->noGCRegionCnt <= SET_NOGCREGIONS_MAX || state->noGCRegionCnt == HAS_NOGCREGIONS); + + // We have two-byte encodings for 0..4 + if (header.noGCRegionCnt <= SET_NOGCREGIONS_MAX) + { + state->noGCRegionCnt = header.noGCRegionCnt; + codeSet = 2; + encoding = (BYTE)(SET_NOGCREGIONS_CNT + header.noGCRegionCnt); + goto DO_RETURN; + } + else if (state->noGCRegionCnt != HAS_NOGCREGIONS) + { + state->noGCRegionCnt = HAS_NOGCREGIONS; + codeSet = 2; + encoding = FFFF_NOGCREGION_CNT; + goto DO_RETURN; + } + } + DO_RETURN: _ASSERTE(encoding < MORE_BYTES_TO_FOLLOW); if (!state->isHeaderMatch(header)) @@ -964,7 +985,7 @@ static int measureDistance(const InfoHdr& header, const InfoHdrSmall* p, int clo if (p->untrackedCnt != header.untrackedCnt) { - if (header.untrackedCnt > 3) + if (header.untrackedCnt > SET_UNTRACKED_MAX) { if (p->untrackedCnt != HAS_UNTRACKED) distance += 1; @@ -1199,6 +1220,13 @@ static int measureDistance(const InfoHdr& header, const InfoHdrSmall* p, int clo return distance; } + if (header.noGCRegionCnt > 0) + { + distance += 2; + if (distance >= closeness) + return distance; + } + return distance; } @@ -1546,7 +1574,7 @@ size_t GCInfo::gcInfoBlockHdrSave( ReturnKind returnKind = getReturnKind(); _ASSERTE(IsValidReturnKind(returnKind) && "Return Kind must be valid"); _ASSERTE(!IsStructReturnKind(returnKind) && "Struct Return Kinds Unexpected for JIT32"); - _ASSERTE(((int)returnKind < (int)SET_RET_KIND_MAX) && "ReturnKind has no legal encoding"); + _ASSERTE(((int)returnKind <= (int)SET_RET_KIND_MAX) && "ReturnKind has no legal encoding"); header->returnKind = returnKind; header->gsCookieOffset = INVALID_GS_COOKIE_OFFSET; @@ -1599,7 +1627,8 @@ size_t GCInfo::gcInfoBlockHdrSave( if (mask == 0) { gcCountForHeader((UNALIGNED unsigned int*)&header->untrackedCnt, - (UNALIGNED unsigned int*)&header->varPtrTableSize); + (UNALIGNED unsigned int*)&header->varPtrTableSize, + (UNALIGNED unsigned int*)&header->noGCRegionCnt); } // @@ -1696,6 +1725,14 @@ size_t GCInfo::gcInfoBlockHdrSave( dest += (sz & mask); } + if (header->noGCRegionCnt > SET_NOGCREGIONS_MAX) + { + unsigned count = header->noGCRegionCnt; + unsigned sz = encodeUnsigned(mask ? dest : NULL, count); + size += sz; + dest += (sz & mask); + } + if (header->epilogCount) { /* Generate table unless one epilog at the end of the method */ @@ -2088,6 +2125,29 @@ unsigned PendingArgsStack::pasEnumGCoffs(unsigned iter, unsigned* offs) return pasENUM_END; } +// Small helper class to handle the No-GC-Interrupt callbacks +// when reporting interruptible ranges. +class NoGCRegionEncoder +{ + BYTE* dest; +public: + size_t totalSize; + + NoGCRegionEncoder(BYTE* dest) + : dest(dest) + , totalSize(0) + { + } + + // This callback is called for each insGroup marked with IGF_NOGCINTERRUPT. + bool operator()(unsigned igFuncIdx, unsigned igOffs, unsigned igSize, unsigned firstInstrSize, bool isInProlog) + { + totalSize += encodeUnsigned(dest == NULL ? NULL : dest + totalSize, igOffs); + totalSize += encodeUnsigned(dest == NULL ? NULL : dest + totalSize, igSize); + return true; + } +}; + /***************************************************************************** * * Generate the register pointer map, and return its total size in bytes. If @@ -2109,7 +2169,8 @@ size_t GCInfo::gcMakeRegPtrTable(BYTE* dest, int mask, const InfoHdr& header, un /* Start computing the total size of the table */ - bool emitArgTabOffset = (header.varPtrTableSize != 0 || header.untrackedCnt > SET_UNTRACKED_MAX); + bool emitArgTabOffset = + (header.varPtrTableSize != 0 || header.untrackedCnt > SET_UNTRACKED_MAX || header.noGCRegionCnt != 0); if (mask != 0 && emitArgTabOffset) { assert(*pArgTabOffset <= MAX_UNSIGNED_SIZE_T); @@ -2129,18 +2190,29 @@ size_t GCInfo::gcMakeRegPtrTable(BYTE* dest, int mask, const InfoHdr& header, un /************************************************************************** * - * Untracked ptr variables + * Untracked ptr variables and no GC regions * ************************************************************************** */ #if DEBUG unsigned untrackedCount = 0; unsigned varPtrTableSize = 0; - gcCountForHeader(&untrackedCount, &varPtrTableSize); + unsigned noGCRegionCount = 0; + gcCountForHeader(&untrackedCount, &varPtrTableSize, &noGCRegionCount); assert(untrackedCount == header.untrackedCnt); assert(varPtrTableSize == header.varPtrTableSize); + assert(noGCRegionCount == header.noGCRegionCnt); #endif // DEBUG + if (header.noGCRegionCnt != 0) + { + NoGCRegionEncoder encoder(mask != 0 ? dest : NULL); + compiler->GetEmitter()->emitGenNoGCLst(encoder, /* skipAllPrologsAndEpilogs = */ true); + totalSize += encoder.totalSize; + if (mask != 0) + dest += encoder.totalSize; + } + if (header.untrackedCnt != 0) { // Write the table of untracked pointer variables. @@ -3582,7 +3654,14 @@ size_t GCInfo::gcInfoBlockHdrDump(const BYTE* table, InfoHdr* header, unsigned* size_t GCInfo::gcDumpPtrTable(const BYTE* table, const InfoHdr& header, unsigned methodSize) { - printf("Pointer table:\n"); + if (header.noGCRegionCnt > 0) + { + printf("No GC regions and pointer table:\n"); + } + else + { + printf("Pointer table:\n"); + } GCDump gcDump(GCINFO_VERSION); diff --git a/src/runtime/src/coreclr/jit/gcinfo.cpp b/src/runtime/src/coreclr/jit/gcinfo.cpp index ff534a0afcb..3f6f5169d39 100644 --- a/src/runtime/src/coreclr/jit/gcinfo.cpp +++ b/src/runtime/src/coreclr/jit/gcinfo.cpp @@ -419,12 +419,33 @@ GCInfo::regPtrDsc* GCInfo::gcRegPtrAllocDsc() #ifdef JIT32_GCENCODER +// Small helper class to handle the No-GC-Interrupt callbacks +// when reporting interruptible ranges. +struct NoGCRegionCounter +{ + unsigned noGCRegionCount; + + NoGCRegionCounter() + : noGCRegionCount(0) + { + } + + // This callback is called for each insGroup marked with IGF_NOGCINTERRUPT. + bool operator()(unsigned igFuncIdx, unsigned igOffs, unsigned igSize, unsigned firstInstrSize, bool isInProlog) + { + noGCRegionCount++; + return true; + } +}; + /***************************************************************************** * * Compute the various counts that get stored in the info block header. */ -void GCInfo::gcCountForHeader(UNALIGNED unsigned int* pUntrackedCount, UNALIGNED unsigned int* pVarPtrTableSize) +void GCInfo::gcCountForHeader(UNALIGNED unsigned int* pUntrackedCount, + UNALIGNED unsigned int* pVarPtrTableSize, + UNALIGNED unsigned int* pNoGCRegionCount) { unsigned varNum; LclVarDsc* varDsc; @@ -556,6 +577,19 @@ void GCInfo::gcCountForHeader(UNALIGNED unsigned int* pUntrackedCount, UNALIGNED #endif *pVarPtrTableSize = varPtrTableSize; + + // Count the number of no GC regions + + unsigned int noGCRegionCount = 0; + + if (compiler->codeGen->GetInterruptible()) + { + NoGCRegionCounter counter; + compiler->GetEmitter()->emitGenNoGCLst(counter, /* skipAllPrologsAndEpilogs = */ true); + noGCRegionCount = counter.noGCRegionCount; + } + + *pNoGCRegionCount = noGCRegionCount; } //------------------------------------------------------------------------ diff --git a/src/runtime/src/coreclr/jit/gentree.cpp b/src/runtime/src/coreclr/jit/gentree.cpp index 4993bf556c1..46d8517f5a9 100644 --- a/src/runtime/src/coreclr/jit/gentree.cpp +++ b/src/runtime/src/coreclr/jit/gentree.cpp @@ -27867,8 +27867,14 @@ GenTree* Compiler::gtNewSimdWithElementNode( var_types simdBaseType = JitType2PreciseVarType(simdBaseJitType); assert(varTypeIsArithmetic(simdBaseType)); + assert(op2->IsCnsIntOrI()); assert(varTypeIsArithmetic(op3)); + ssize_t imm8 = op2->AsIntCon()->IconValue(); + ssize_t count = simdSize / genTypeSize(simdBaseType); + + assert((0 <= imm8) && (imm8 < count)); + #if defined(TARGET_XARCH) switch (simdBaseType) { @@ -27934,20 +27940,6 @@ GenTree* Compiler::gtNewSimdWithElementNode( #error Unsupported platform #endif // !TARGET_XARCH && !TARGET_ARM64 - int immUpperBound = getSIMDVectorLength(simdSize, simdBaseType) - 1; - bool rangeCheckNeeded = !op2->OperIsConst(); - - if (!rangeCheckNeeded) - { - ssize_t imm8 = op2->AsIntCon()->IconValue(); - rangeCheckNeeded = (imm8 < 0) || (imm8 > immUpperBound); - } - - if (rangeCheckNeeded) - { - op2 = addRangeCheckForHWIntrinsic(op2, 0, immUpperBound); - } - return gtNewSimdHWIntrinsicNode(type, op1, op2, op3, hwIntrinsicID, simdBaseJitType, simdSize); } diff --git a/src/runtime/src/coreclr/jit/gentree.h b/src/runtime/src/coreclr/jit/gentree.h index e57d334a280..5f2e8d1eb2b 100644 --- a/src/runtime/src/coreclr/jit/gentree.h +++ b/src/runtime/src/coreclr/jit/gentree.h @@ -7900,9 +7900,7 @@ struct GenTreeBlk : public GenTreeIndir BlkOpKindUnrollMemmove, } gtBlkOpKind; -#ifndef JIT32_GCENCODER bool gtBlkOpGcUnsafe; -#endif bool ContainsReferences() { @@ -7940,11 +7938,9 @@ struct GenTreeBlk : public GenTreeIndir assert(layout != nullptr); assert(layout->GetSize() != 0); - m_layout = layout; - gtBlkOpKind = BlkOpKindInvalid; -#ifndef JIT32_GCENCODER + m_layout = layout; + gtBlkOpKind = BlkOpKindInvalid; gtBlkOpGcUnsafe = false; -#endif } #if DEBUGGABLE_GENTREE diff --git a/src/runtime/src/coreclr/jit/hwintrinsiccodegenxarch.cpp b/src/runtime/src/coreclr/jit/hwintrinsiccodegenxarch.cpp index a4f22c16ec2..ea114113412 100644 --- a/src/runtime/src/coreclr/jit/hwintrinsiccodegenxarch.cpp +++ b/src/runtime/src/coreclr/jit/hwintrinsiccodegenxarch.cpp @@ -1832,7 +1832,6 @@ void CodeGen::genBaseIntrinsic(GenTreeHWIntrinsic* node, insOpts instOptions) GenTree* op1 = (node->GetOperandCount() >= 1) ? node->Op(1) : nullptr; GenTree* op2 = (node->GetOperandCount() >= 2) ? node->Op(2) : nullptr; - GenTree* op3 = (node->GetOperandCount() >= 3) ? node->Op(3) : nullptr; genConsumeMultiOpOperands(node); regNumber op1Reg = (op1 == nullptr) ? REG_NA : op1->GetRegNum(); @@ -1969,56 +1968,6 @@ void CodeGen::genBaseIntrinsic(GenTreeHWIntrinsic* node, insOpts instOptions) break; } - case NI_Vector128_WithElement: - case NI_Vector256_WithElement: - case NI_Vector512_WithElement: - { - // Optimize the case where op2 is not a constant. - assert(!op2->OperIsConst()); - - // We don't have an instruction to implement this intrinsic if the index is not a constant. - // So we will use the SIMD temp location to store the vector, set the value and then reload it. - // The range check will already have been performed, so at this point we know we have an index - // within the bounds of the vector. - - unsigned simdInitTempVarNum = compiler->lvaSIMDInitTempVarNum; - noway_assert(simdInitTempVarNum != BAD_VAR_NUM); - - bool isEBPbased; - unsigned offs = compiler->lvaFrameAddress(simdInitTempVarNum, &isEBPbased); - -#if !FEATURE_FIXED_OUT_ARGS - if (!isEBPbased) - { - // Adjust the offset by the amount currently pushed on the CPU stack - offs += genStackLevel; - } -#else - assert(genStackLevel == 0); -#endif // !FEATURE_FIXED_OUT_ARGS - - regNumber indexReg = op2->GetRegNum(); - regNumber valueReg = op3->GetRegNum(); // New element value to be stored - - // Store the vector to the temp location. - GetEmitter()->emitIns_S_R(ins_Store(simdType, compiler->isSIMDTypeLocalAligned(simdInitTempVarNum)), - emitTypeSize(simdType), op1Reg, simdInitTempVarNum, 0); - - // Set the desired element. - GetEmitter()->emitIns_ARX_R(ins_Move_Extend(op3->TypeGet(), false), // Store - emitTypeSize(baseType), // Of the vector baseType - valueReg, // From valueReg - (isEBPbased) ? REG_EBP : REG_ESP, // Stack-based - indexReg, // Indexed - genTypeSize(baseType), // by the size of the baseType - offs); // Offset - - // Write back the modified vector to the original location. - GetEmitter()->emitIns_R_S(ins_Load(simdType, compiler->isSIMDTypeLocalAligned(simdInitTempVarNum)), - emitTypeSize(simdType), targetReg, simdInitTempVarNum, 0); - break; - } - case NI_Vector128_GetElement: case NI_Vector256_GetElement: case NI_Vector512_GetElement: diff --git a/src/runtime/src/coreclr/jit/hwintrinsicxarch.cpp b/src/runtime/src/coreclr/jit/hwintrinsicxarch.cpp index 29fe70b74b6..72328592e3a 100644 --- a/src/runtime/src/coreclr/jit/hwintrinsicxarch.cpp +++ b/src/runtime/src/coreclr/jit/hwintrinsicxarch.cpp @@ -4273,6 +4273,34 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, assert(sig->numArgs == 3); GenTree* indexOp = impStackTop(1).val; + if (!indexOp->OperIsConst()) + { + if (!opts.OptimizationEnabled()) + { + // Only enable late stage rewriting if optimizations are enabled + // as we won't otherwise encounter a constant at the later point + return nullptr; + } + + op3 = impPopStack().val; + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + retNode = gtNewSimdHWIntrinsicNode(retType, op1, op2, op3, intrinsic, simdBaseJitType, simdSize); + + retNode->AsHWIntrinsic()->SetMethodHandle(this, method R2RARG(*entryPoint)); + break; + } + + ssize_t imm8 = indexOp->AsIntCon()->IconValue(); + ssize_t count = simdSize / genTypeSize(simdBaseType); + + if ((imm8 >= count) || (imm8 < 0)) + { + // Using software fallback if index is out of range (throw exception) + return nullptr; + } + switch (simdBaseType) { // Using software fallback if simdBaseType is not supported by hardware diff --git a/src/runtime/src/coreclr/jit/importer.cpp b/src/runtime/src/coreclr/jit/importer.cpp index 075e0e8d540..af6fa9769a4 100644 --- a/src/runtime/src/coreclr/jit/importer.cpp +++ b/src/runtime/src/coreclr/jit/importer.cpp @@ -9936,9 +9936,10 @@ void Compiler::impImportBlockCode(BasicBlock* block) impPushOnStack(gtNewLclvNode(lclNum, TYP_REF), tiRetVal); #ifdef DEBUG - // Under SPMI, look up info we might ask for if we stack allocate this array + // Under SPMI, look up info we might ask for if we stack allocate this array, + // but only if we know the precise type // - if (JitConfig.EnableExtraSuperPmiQueries()) + if (JitConfig.EnableExtraSuperPmiQueries() && !eeIsSharedInst(resolvedToken.hClass)) { void* pEmbedClsHnd; info.compCompHnd->embedClassHandle(resolvedToken.hClass, &pEmbedClsHnd); diff --git a/src/runtime/src/coreclr/jit/inductionvariableopts.cpp b/src/runtime/src/coreclr/jit/inductionvariableopts.cpp index 77f15adfb07..56818096912 100644 --- a/src/runtime/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/runtime/src/coreclr/jit/inductionvariableopts.cpp @@ -1,11 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// // This file contains code to optimize induction variables in loops based on // scalar evolution analysis (see scev.h and scev.cpp for more information // about the scalar evolution analysis). // -// Currently the following optimizations are done: +// Currently the following optimizations are implemented: // // IV widening: // This widens primary induction variables from 32 bits into 64 bits. This is @@ -37,21 +38,26 @@ // single instruction, bypassing the need to do a separate comparison with a // bound. // -// Strength reduction (disabled): -// This changes the stride of primary IVs in a loop to avoid more expensive -// multiplications inside the loop. Commonly the primary IVs are only used -// for indexing memory at some element size, which can end up with these -// multiplications. +// Strength reduction: +// Strength reduction identifies cases where all uses of a primary IV compute +// a common derived value. Commonly this happens when indexing memory at some +// element size, resulting in multiplications. It introduces a new primary IV +// that directly computes this derived value, avoiding the need for the +// original primary IV and its associated calculations. The optimization +// handles GC pointers carefully, ensuring all accesses remain within managed +// objects. // -// Strength reduction frequently relies on reversing the loop to remove the -// last non-multiplied use of the primary IV. +// Unused IV removal: +// This removes induction variables that are only used for self-updates with +// no external uses. This commonly happens after other IV optimizations have +// replaced all meaningful uses of an IV with a different, more efficient IV. // #include "jitpch.h" #include "scev.h" -// Data structure that keeps track of local occurrences inside loops. -class LoopLocalOccurrences +// Data structure that keeps track of per-loop info, like occurrences and suspension-points inside loops. +class PerLoopInfo { struct Occurrence { @@ -63,19 +69,26 @@ class LoopLocalOccurrences typedef JitHashTable, Occurrence*> LocalToOccurrenceMap; + struct LoopInfo + { + LocalToOccurrenceMap* LocalToOccurrences = nullptr; + bool HasSuspensionPoint = false; + }; + FlowGraphNaturalLoops* m_loops; - // For every loop, we track all occurrences exclusive to that loop. - // Occurrences in descendant loops are not kept in their ancestor's maps. - LocalToOccurrenceMap** m_maps; + // For every loop, we track all occurrences exclusive to that loop, and + // whether or not the loop has a suspension point. + // Occurrences/suspensions in descendant loops are not kept in their ancestor's maps. + LoopInfo* m_info; // Blocks whose IR we have visited to find local occurrences in. BitVec m_visitedBlocks; - LocalToOccurrenceMap* GetOrCreateMap(FlowGraphNaturalLoop* loop); + LoopInfo* GetOrCreateInfo(FlowGraphNaturalLoop* loop); template - bool VisitLoopNestMaps(FlowGraphNaturalLoop* loop, TFunc& func); + bool VisitLoopNestInfo(FlowGraphNaturalLoop* loop, TFunc& func); public: - LoopLocalOccurrences(FlowGraphNaturalLoops* loops); + PerLoopInfo(FlowGraphNaturalLoops* loops); template bool VisitOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func); @@ -85,38 +98,40 @@ class LoopLocalOccurrences template bool VisitStatementsWithOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func); + bool HasSuspensionPoint(FlowGraphNaturalLoop* loop); + void Invalidate(FlowGraphNaturalLoop* loop); }; -LoopLocalOccurrences::LoopLocalOccurrences(FlowGraphNaturalLoops* loops) +PerLoopInfo::PerLoopInfo(FlowGraphNaturalLoops* loops) : m_loops(loops) { - Compiler* comp = loops->GetDfsTree()->GetCompiler(); - m_maps = loops->NumLoops() == 0 ? nullptr : new (comp, CMK_LoopOpt) LocalToOccurrenceMap* [loops->NumLoops()] {}; + Compiler* comp = loops->GetDfsTree()->GetCompiler(); + m_info = loops->NumLoops() == 0 ? nullptr : new (comp, CMK_LoopOpt) LoopInfo[loops->NumLoops()]; BitVecTraits poTraits = loops->GetDfsTree()->PostOrderTraits(); m_visitedBlocks = BitVecOps::MakeEmpty(&poTraits); } //------------------------------------------------------------------------------ -// LoopLocalOccurrences:GetOrCreateMap: -// Get or create the map of occurrences exclusive to a single loop. +// PerLoopInfo:GetOrCreateInfo: +// Get or create the info exclusive to a single loop. // // Parameters: // loop - The loop // // Returns: -// Map of occurrences. +// Loop information. // // Remarks: // As a precondition occurrences of all descendant loops must already have // been found. // -LoopLocalOccurrences::LocalToOccurrenceMap* LoopLocalOccurrences::GetOrCreateMap(FlowGraphNaturalLoop* loop) +PerLoopInfo::LoopInfo* PerLoopInfo::GetOrCreateInfo(FlowGraphNaturalLoop* loop) { - LocalToOccurrenceMap* map = m_maps[loop->GetIndex()]; - if (map != nullptr) + LoopInfo& info = m_info[loop->GetIndex()]; + if (info.LocalToOccurrences != nullptr) { - return map; + return &info; } BitVecTraits poTraits = m_loops->GetDfsTree()->PostOrderTraits(); @@ -132,11 +147,10 @@ LoopLocalOccurrences::LocalToOccurrenceMap* LoopLocalOccurrences::GetOrCreateMap } #endif - Compiler* comp = m_loops->GetDfsTree()->GetCompiler(); - map = new (comp, CMK_LoopOpt) LocalToOccurrenceMap(comp->getAllocator(CMK_LoopOpt)); - m_maps[loop->GetIndex()] = map; + Compiler* comp = m_loops->GetDfsTree()->GetCompiler(); + info.LocalToOccurrences = new (comp, CMK_LoopOpt) LocalToOccurrenceMap(comp->getAllocator(CMK_LoopOpt)); - loop->VisitLoopBlocksReversePostOrder([=, &poTraits](BasicBlock* block) { + loop->VisitLoopBlocksReversePostOrder([=, &poTraits, &info](BasicBlock* block) { if (!BitVecOps::TryAddElemD(&poTraits, m_visitedBlocks, block->bbPostorderNum)) { return BasicBlockVisit::Continue; @@ -146,13 +160,15 @@ LoopLocalOccurrences::LocalToOccurrenceMap* LoopLocalOccurrences::GetOrCreateMap { for (GenTree* node : stmt->TreeList()) { + info.HasSuspensionPoint |= node->IsCall() && node->AsCall()->IsAsync(); + if (!node->OperIsAnyLocal()) { continue; } - GenTreeLclVarCommon* lcl = node->AsLclVarCommon(); - Occurrence** occurrence = map->LookupPointerOrAdd(lcl->GetLclNum(), nullptr); + GenTreeLclVarCommon* lcl = node->AsLclVarCommon(); + Occurrence** occurrence = info.LocalToOccurrences->LookupPointerOrAdd(lcl->GetLclNum(), nullptr); Occurrence* newOccurrence = new (comp, CMK_LoopOpt) Occurrence; newOccurrence->Block = block; @@ -166,15 +182,15 @@ LoopLocalOccurrences::LocalToOccurrenceMap* LoopLocalOccurrences::GetOrCreateMap return BasicBlockVisit::Continue; }); - return map; + return &info; } //------------------------------------------------------------------------------ -// LoopLocalOccurrences:VisitLoopNestMaps: -// Visit all occurrence maps of the specified loop nest. +// PerLoopInfo:VisitLoopNestInfo: +// Visit all info of the specified loop nest. // // Type parameters: -// TFunc - bool(LocalToOccurrenceMap*) functor that returns true to continue +// TFunc - bool(LoopInfo*) functor that returns true to continue // the visit and false to abort. // // Parameters: @@ -185,21 +201,21 @@ LoopLocalOccurrences::LocalToOccurrenceMap* LoopLocalOccurrences::GetOrCreateMap // True if the visit completed; false if "func" returned false for any map. // template -bool LoopLocalOccurrences::VisitLoopNestMaps(FlowGraphNaturalLoop* loop, TFunc& func) +bool PerLoopInfo::VisitLoopNestInfo(FlowGraphNaturalLoop* loop, TFunc& func) { for (FlowGraphNaturalLoop* child = loop->GetChild(); child != nullptr; child = child->GetSibling()) { - if (!VisitLoopNestMaps(child, func)) + if (!VisitLoopNestInfo(child, func)) { return false; } } - return func(GetOrCreateMap(loop)); + return func(GetOrCreateInfo(loop)); } //------------------------------------------------------------------------------ -// LoopLocalOccurrences:VisitOccurrences: +// PerLoopInfo:VisitOccurrences: // Visit all occurrences of the specified local inside the loop. // // Type parameters: @@ -216,11 +232,11 @@ bool LoopLocalOccurrences::VisitLoopNestMaps(FlowGraphNaturalLoop* loop, TFunc& // returning false. // template -bool LoopLocalOccurrences::VisitOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func) +bool PerLoopInfo::VisitOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func) { - auto visitor = [=, &func](LocalToOccurrenceMap* map) { + auto visitor = [=, &func](LoopInfo* info) { Occurrence* occurrence; - if (!map->Lookup(lclNum, &occurrence)) + if (!info->LocalToOccurrences->Lookup(lclNum, &occurrence)) { return true; } @@ -240,11 +256,11 @@ bool LoopLocalOccurrences::VisitOccurrences(FlowGraphNaturalLoop* loop, unsigned return true; }; - return VisitLoopNestMaps(loop, visitor); + return VisitLoopNestInfo(loop, visitor); } //------------------------------------------------------------------------------ -// LoopLocalOccurrences:HasAnyOccurrences: +// PerLoopInfo:HasAnyOccurrences: // Check if this loop has any occurrences of the specified local. // // Parameters: @@ -257,7 +273,7 @@ bool LoopLocalOccurrences::VisitOccurrences(FlowGraphNaturalLoop* loop, unsigned // Remarks: // Does not take promotion into account. // -bool LoopLocalOccurrences::HasAnyOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum) +bool PerLoopInfo::HasAnyOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum) { if (!VisitOccurrences(loop, lclNum, [](BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree) { return false; @@ -270,7 +286,7 @@ bool LoopLocalOccurrences::HasAnyOccurrences(FlowGraphNaturalLoop* loop, unsigne } //------------------------------------------------------------------------------ -// LoopLocalOccurrences:VisitStatementsWithOccurrences: +// PerLoopInfo:VisitStatementsWithOccurrences: // Visit all statements with occurrences of the specified local inside // the loop. // @@ -292,11 +308,11 @@ bool LoopLocalOccurrences::HasAnyOccurrences(FlowGraphNaturalLoop* loop, unsigne // once. // template -bool LoopLocalOccurrences::VisitStatementsWithOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func) +bool PerLoopInfo::VisitStatementsWithOccurrences(FlowGraphNaturalLoop* loop, unsigned lclNum, TFunc func) { - auto visitor = [=, &func](LocalToOccurrenceMap* map) { + auto visitor = [=, &func](LoopInfo* info) { Occurrence* occurrence; - if (!map->Lookup(lclNum, &occurrence)) + if (!info->LocalToOccurrences->Lookup(lclNum, &occurrence)) { return true; } @@ -330,7 +346,43 @@ bool LoopLocalOccurrences::VisitStatementsWithOccurrences(FlowGraphNaturalLoop* return true; }; - return VisitLoopNestMaps(loop, visitor); + return VisitLoopNestInfo(loop, visitor); +} + +//------------------------------------------------------------------------------ +// PerLoopInfo:HasSuspensionPoint: +// Check if a loop has a suspension point. +// +// Parameters: +// loop - The loop +// +// Returns: +// True if so. +// +bool PerLoopInfo::HasSuspensionPoint(FlowGraphNaturalLoop* loop) +{ + if (!loop->GetDfsTree()->GetCompiler()->compIsAsync()) + { + return false; + } + + auto visitor = [](LoopInfo* info) { + if (info->HasSuspensionPoint) + { + // Abort now that we've found a suspension point + return false; + } + + return true; + }; + + if (!VisitLoopNestInfo(loop, visitor)) + { + // Aborted, so has a suspension point + return true; + } + + return false; } //------------------------------------------------------------------------ @@ -340,16 +392,18 @@ bool LoopLocalOccurrences::VisitStatementsWithOccurrences(FlowGraphNaturalLoop* // Parameters: // loop - The loop // -void LoopLocalOccurrences::Invalidate(FlowGraphNaturalLoop* loop) +void PerLoopInfo::Invalidate(FlowGraphNaturalLoop* loop) { for (FlowGraphNaturalLoop* child = loop->GetChild(); child != nullptr; child = child->GetSibling()) { Invalidate(child); } - if (m_maps[loop->GetIndex()] != nullptr) + LoopInfo& info = m_info[loop->GetIndex()]; + if (info.LocalToOccurrences != nullptr) { - m_maps[loop->GetIndex()] = nullptr; + info.LocalToOccurrences = nullptr; + info.HasSuspensionPoint = false; BitVecTraits poTraits = m_loops->GetDfsTree()->PostOrderTraits(); loop->VisitLoopBlocks([=, &poTraits](BasicBlock* block) { @@ -446,7 +500,7 @@ bool Compiler::optCanSinkWidenedIV(unsigned lclNum, FlowGraphNaturalLoop* loop) // initBlock - The block in where the new IV would be initialized // initedToConstant - Whether or not the new IV will be initialized to a constant // loop - The loop -// loopLocals - Data structure tracking local uses inside the loop +// loopInfo - Data structure tracking loop info, like local occurrences // // // Returns: @@ -462,11 +516,8 @@ bool Compiler::optCanSinkWidenedIV(unsigned lclNum, FlowGraphNaturalLoop* loop) // 2. We need to store the wide IV back into the narrow one in each of // the exits where the narrow IV is live-in. // -bool Compiler::optIsIVWideningProfitable(unsigned lclNum, - BasicBlock* initBlock, - bool initedToConstant, - FlowGraphNaturalLoop* loop, - LoopLocalOccurrences* loopLocals) +bool Compiler::optIsIVWideningProfitable( + unsigned lclNum, BasicBlock* initBlock, bool initedToConstant, FlowGraphNaturalLoop* loop, PerLoopInfo* loopInfo) { for (FlowGraphNaturalLoop* otherLoop : m_loops->InReversePostOrder()) { @@ -522,7 +573,7 @@ bool Compiler::optIsIVWideningProfitable(unsigned lclNum, return true; }; - loopLocals->VisitOccurrences(loop, lclNum, measure); + loopInfo->VisitOccurrences(loop, lclNum, measure); if (!initedToConstant) { @@ -763,14 +814,12 @@ void Compiler::optBestEffortReplaceNarrowIVUses( // Parameters: // scevContext - Context for scalar evolution // loop - The loop -// loopLocals - Data structure for locals occurrences +// loopInfo - Data structure for tracking loop info, like locals occurrences // // Returns: // True if any primary IV was widened. // -bool Compiler::optWidenIVs(ScalarEvolutionContext& scevContext, - FlowGraphNaturalLoop* loop, - LoopLocalOccurrences* loopLocals) +bool Compiler::optWidenIVs(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, PerLoopInfo* loopInfo) { JITDUMP("Considering primary IVs of " FMT_LP " for widening\n", loop->GetIndex()); @@ -811,14 +860,14 @@ bool Compiler::optWidenIVs(ScalarEvolutionContext& scevContext, // For a struct field with occurrences of the parent local we won't // be able to do much. - if (lclDsc->lvIsStructField && loopLocals->HasAnyOccurrences(loop, lclDsc->lvParentLcl)) + if (lclDsc->lvIsStructField && loopInfo->HasAnyOccurrences(loop, lclDsc->lvParentLcl)) { JITDUMP(" V%02u is a struct field whose parent local V%02u has occurrences inside the loop\n", lclNum, lclDsc->lvParentLcl); continue; } - if (optWidenPrimaryIV(loop, lclNum, addRec, loopLocals)) + if (optWidenPrimaryIV(loop, lclNum, addRec, loopInfo)) { numWidened++; } @@ -835,12 +884,9 @@ bool Compiler::optWidenIVs(ScalarEvolutionContext& scevContext, // loop - The loop // lclNum - The primary IV // addRec - The add recurrence for the primary IV -// loopLocals - Data structure for locals occurrences +// loopInfo - Data structure for tracking loop info like locals occurrences // -bool Compiler::optWidenPrimaryIV(FlowGraphNaturalLoop* loop, - unsigned lclNum, - ScevAddRec* addRec, - LoopLocalOccurrences* loopLocals) +bool Compiler::optWidenPrimaryIV(FlowGraphNaturalLoop* loop, unsigned lclNum, ScevAddRec* addRec, PerLoopInfo* loopInfo) { LclVarDsc* lclDsc = lvaGetDesc(lclNum); if (lclDsc->TypeGet() != TYP_INT) @@ -882,7 +928,7 @@ bool Compiler::optWidenPrimaryIV(FlowGraphNaturalLoop* loop, initBlock = startSsaDsc->GetBlock(); } - if (!optIsIVWideningProfitable(lclNum, initBlock, initToConstant, loop, loopLocals)) + if (!optIsIVWideningProfitable(lclNum, initBlock, initToConstant, loop, loopInfo)) { return false; } @@ -977,10 +1023,10 @@ bool Compiler::optWidenPrimaryIV(FlowGraphNaturalLoop* loop, return true; }; - loopLocals->VisitStatementsWithOccurrences(loop, lclNum, replace); + loopInfo->VisitStatementsWithOccurrences(loop, lclNum, replace); optSinkWidenedIV(lclNum, newLclNum, loop); - loopLocals->Invalidate(loop); + loopInfo->Invalidate(loop); return true; } @@ -1031,21 +1077,21 @@ void Compiler::optVisitBoundingExitingCondBlocks(FlowGraphNaturalLoop* loop, TFu // Parameters: // scevContext - Context for scalar evolution // loop - Loop to transform -// loopLocals - Data structure that tracks occurrences of locals in the loop +// loopInfo - Data structure that tracks occurrences of locals in the loop // // Returns: // True if the loop was made downwards counted; otherwise false. // bool Compiler::optMakeLoopDownwardsCounted(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, - LoopLocalOccurrences* loopLocals) + PerLoopInfo* loopInfo) { JITDUMP("Checking if we should make " FMT_LP " downwards counted\n", loop->GetIndex()); bool changed = false; optVisitBoundingExitingCondBlocks(loop, [=, &scevContext, &changed](BasicBlock* exiting) { JITDUMP(" Considering exiting block " FMT_BB "\n", exiting->bbNum); - changed |= optMakeExitTestDownwardsCounted(scevContext, loop, exiting, loopLocals); + changed |= optMakeExitTestDownwardsCounted(scevContext, loop, exiting, loopInfo); }); return changed; @@ -1060,7 +1106,7 @@ bool Compiler::optMakeLoopDownwardsCounted(ScalarEvolutionContext& scevContext, // scevContext - SCEV context // loop - The specific loop // exiting - Exiting block -// loopLocals - Data structure tracking local uses +// loopInfo - Data structure tracking local uses // // Returns: // True if any modification was made. @@ -1068,7 +1114,7 @@ bool Compiler::optMakeLoopDownwardsCounted(ScalarEvolutionContext& scevContext, bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, BasicBlock* exiting, - LoopLocalOccurrences* loopLocals) + PerLoopInfo* loopInfo) { // Note: keep the heuristics here in sync with // `StrengthReductionContext::IsUseExpectedToBeRemoved`. @@ -1099,7 +1145,7 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte unsigned candidateLclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); - if (optLocalHasNonLoopUses(candidateLclNum, loop, loopLocals)) + if (optLocalHasNonLoopUses(candidateLclNum, loop, loopInfo)) { continue; } @@ -1122,7 +1168,7 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte return false; }; - if (!loopLocals->VisitStatementsWithOccurrences(loop, candidateLclNum, checkRemovableUse)) + if (!loopInfo->VisitStatementsWithOccurrences(loop, candidateLclNum, checkRemovableUse)) { // Aborted means we found a non-removable use continue; @@ -1215,7 +1261,7 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte DISPSTMT(jtrueStmt); JITDUMP("\n"); - loopLocals->Invalidate(loop); + loopInfo->Invalidate(loop); return true; } @@ -1270,16 +1316,16 @@ bool Compiler::optCanAndShouldChangeExitTest(GenTree* cond, bool dump) // Parameters: // lclNum - The local // loop - The loop -// loopLocals - Data structure tracking local uses +// loopInfo - Data structure tracking local uses // // Returns: // True if the local may have non-loop uses (or if it is a field with uses of // the parent struct). // -bool Compiler::optLocalHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals) +bool Compiler::optLocalHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, PerLoopInfo* loopInfo) { LclVarDsc* varDsc = lvaGetDesc(lclNum); - if (varDsc->lvIsStructField && loopLocals->HasAnyOccurrences(loop, varDsc->lvParentLcl)) + if (varDsc->lvIsStructField && loopInfo->HasAnyOccurrences(loop, varDsc->lvParentLcl)) { return true; } @@ -1362,7 +1408,7 @@ class StrengthReductionContext Compiler* m_comp; ScalarEvolutionContext& m_scevContext; FlowGraphNaturalLoop* m_loop; - LoopLocalOccurrences& m_loopLocals; + PerLoopInfo& m_loopInfo; ArrayStack m_backEdgeBounds; SimplificationAssumptions m_simplAssumptions; @@ -1403,11 +1449,11 @@ class StrengthReductionContext StrengthReductionContext(Compiler* comp, ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, - LoopLocalOccurrences& loopLocals) + PerLoopInfo& loopInfo) : m_comp(comp) , m_scevContext(scevContext) , m_loop(loop) - , m_loopLocals(loopLocals) + , m_loopInfo(loopInfo) , m_backEdgeBounds(comp->getAllocator(CMK_LoopIVOpts)) , m_cursors1(comp->getAllocator(CMK_LoopIVOpts)) , m_cursors2(comp->getAllocator(CMK_LoopIVOpts)) @@ -1484,7 +1530,7 @@ bool StrengthReductionContext::TryStrengthReduce() continue; } - if (m_comp->optLocalHasNonLoopUses(primaryIVLcl->GetLclNum(), m_loop, &m_loopLocals)) + if (m_comp->optLocalHasNonLoopUses(primaryIVLcl->GetLclNum(), m_loop, &m_loopInfo)) { // We won't be able to remove this primary IV JITDUMP(" Has non-loop uses\n"); @@ -1524,12 +1570,20 @@ bool StrengthReductionContext::TryStrengthReduce() assert(nextIV != nullptr); - if (varTypeIsGC(nextIV->Type) && !StaysWithinManagedObject(nextCursors, nextIV)) + if (varTypeIsGC(nextIV->Type)) { - JITDUMP( - " Next IV computes a GC pointer that we cannot prove to be inside a managed object. Bailing.\n", - varTypeName(nextIV->Type)); - break; + if (m_loopInfo.HasSuspensionPoint(m_loop)) + { + JITDUMP(" Next IV computes a GC pointer in a loop with a suspension point. Bailing.\n"); + break; + } + + if (!StaysWithinManagedObject(nextCursors, nextIV)) + { + JITDUMP( + " Next IV computes a GC pointer that we cannot prove to be inside a managed object. Bailing.\n"); + break; + } } ExpandStoredCursors(nextCursors, cursors); @@ -1573,7 +1627,7 @@ bool StrengthReductionContext::TryStrengthReduce() if (TryReplaceUsesWithNewPrimaryIV(cursors, currentIV)) { strengthReducedAny = true; - m_loopLocals.Invalidate(m_loop); + m_loopInfo.Invalidate(m_loop); } } @@ -1686,7 +1740,7 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL return true; }; - if (!m_loopLocals.VisitOccurrences(m_loop, primaryIVLcl->GetLclNum(), visitor) || (m_cursors1.Height() <= 0)) + if (!m_loopInfo.VisitOccurrences(m_loop, primaryIVLcl->GetLclNum(), visitor) || (m_cursors1.Height() <= 0)) { JITDUMP(" Could not create cursors for all loop uses of primary IV\n"); return false; @@ -1882,7 +1936,7 @@ void StrengthReductionContext::ExpandStoredCursors(ArrayStack* curso GenTreeLclVarCommon* storedLcl = parent->AsLclVarCommon(); if ((storedLcl->Data() == cur) && ((cur->gtFlags & GTF_SIDE_EFFECT) == 0) && storedLcl->HasSsaIdentity() && - !m_comp->optLocalHasNonLoopUses(storedLcl->GetLclNum(), m_loop, &m_loopLocals)) + !m_comp->optLocalHasNonLoopUses(storedLcl->GetLclNum(), m_loop, &m_loopInfo)) { int numCreated = 0; ScevAddRec* cursorIV = cursor->IV; @@ -1922,7 +1976,7 @@ void StrengthReductionContext::ExpandStoredCursors(ArrayStack* curso return true; }; - if (m_loopLocals.VisitOccurrences(m_loop, storedLcl->GetLclNum(), createExtraCursor)) + if (m_loopInfo.VisitOccurrences(m_loop, storedLcl->GetLclNum(), createExtraCursor)) { JITDUMP( " [%06u] was the data of store [%06u]; expanded to %d new cursors, and will replace with a store of 0\n", @@ -2678,12 +2732,12 @@ bool StrengthReductionContext::InsertionPointPostDominatesUses(BasicBlock* // // Parameters: // loop - The loop -// loopLocals - Locals of the loop +// loopInfo - Locals of the loop // // Returns: // True if any primary IV was removed. // -bool Compiler::optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals) +bool Compiler::optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, PerLoopInfo* loopInfo) { JITDUMP(" Now looking for unnecessary primary IVs\n"); @@ -2697,7 +2751,7 @@ bool Compiler::optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrenc unsigned lclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); JITDUMP(" V%02u", lclNum); - if (optLocalHasNonLoopUses(lclNum, loop, loopLocals)) + if (optLocalHasNonLoopUses(lclNum, loop, loopInfo)) { JITDUMP(" has non-loop uses, cannot remove\n"); continue; @@ -2707,7 +2761,7 @@ bool Compiler::optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrenc return optIsUpdateOfIVWithoutSideEffects(stmt->GetRootNode(), lclNum); }; - if (!loopLocals->VisitStatementsWithOccurrences(loop, lclNum, visit)) + if (!loopInfo->VisitStatementsWithOccurrences(loop, lclNum, visit)) { JITDUMP(" has essential uses, cannot remove\n"); continue; @@ -2720,9 +2774,9 @@ bool Compiler::optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrenc return true; }; - loopLocals->VisitStatementsWithOccurrences(loop, lclNum, remove); + loopInfo->VisitStatementsWithOccurrences(loop, lclNum, remove); numRemoved++; - loopLocals->Invalidate(loop); + loopInfo->Invalidate(loop); } Metrics.UnusedIVsRemoved += numRemoved; @@ -2810,7 +2864,7 @@ PhaseStatus Compiler::optInductionVariables() m_loops = FlowGraphNaturalLoops::Find(m_dfsTree); } - LoopLocalOccurrences loopLocals(m_loops); + PerLoopInfo loopInfo(m_loops); ScalarEvolutionContext scevContext(this); JITDUMP("Optimizing induction variables:\n"); @@ -2830,14 +2884,14 @@ PhaseStatus Compiler::optInductionVariables() continue; } - StrengthReductionContext strengthReductionContext(this, scevContext, loop, loopLocals); + StrengthReductionContext strengthReductionContext(this, scevContext, loop, loopInfo); if (strengthReductionContext.TryStrengthReduce()) { Metrics.LoopsStrengthReduced++; changed = true; } - if (optMakeLoopDownwardsCounted(scevContext, loop, &loopLocals)) + if (optMakeLoopDownwardsCounted(scevContext, loop, &loopInfo)) { Metrics.LoopsMadeDownwardsCounted++; changed = true; @@ -2847,14 +2901,14 @@ PhaseStatus Compiler::optInductionVariables() // addressing modes can include the zero/sign-extension of the index // for free. #if defined(TARGET_XARCH) && defined(TARGET_64BIT) - if (optWidenIVs(scevContext, loop, &loopLocals)) + if (optWidenIVs(scevContext, loop, &loopInfo)) { Metrics.LoopsIVWidened++; changed = true; } #endif - if (optRemoveUnusedIVs(loop, &loopLocals)) + if (optRemoveUnusedIVs(loop, &loopInfo)) { changed = true; } diff --git a/src/runtime/src/coreclr/jit/jitconfigvalues.h b/src/runtime/src/coreclr/jit/jitconfigvalues.h index 92a1cb5bf8f..c42560d33bd 100644 --- a/src/runtime/src/coreclr/jit/jitconfigvalues.h +++ b/src/runtime/src/coreclr/jit/jitconfigvalues.h @@ -808,6 +808,9 @@ RELEASE_CONFIG_INTEGER(JitEnablePhysicalPromotion, "JitEnablePhysicalPromotion", // Enable cross-block local assertion prop RELEASE_CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, "JitEnableCrossBlockLocalAssertionProp", 1) +// Enable postorder local assertion prop +RELEASE_CONFIG_INTEGER(JitEnablePostorderLocalAssertionProp, "JitEnablePostorderLocalAssertionProp", 1) + // Enable strength reduction RELEASE_CONFIG_INTEGER(JitEnableStrengthReduction, "JitEnableStrengthReduction", 1) diff --git a/src/runtime/src/coreclr/jit/jitgcinfo.h b/src/runtime/src/coreclr/jit/jitgcinfo.h index 6d04e3b2273..ae9e4852a5d 100644 --- a/src/runtime/src/coreclr/jit/jitgcinfo.h +++ b/src/runtime/src/coreclr/jit/jitgcinfo.h @@ -289,7 +289,9 @@ class GCInfo //------------------------------------------------------------------------- #ifdef JIT32_GCENCODER - void gcCountForHeader(UNALIGNED unsigned int* pUntrackedCount, UNALIGNED unsigned int* pVarPtrTableSize); + void gcCountForHeader(UNALIGNED unsigned int* pUntrackedCount, + UNALIGNED unsigned int* pVarPtrTableSize, + UNALIGNED unsigned int* pNoGCRegionCount); bool gcIsUntrackedLocalOrNonEnregisteredArg(unsigned varNum, bool* pThisKeptAliveIsInUntracked = nullptr); diff --git a/src/runtime/src/coreclr/jit/lowerxarch.cpp b/src/runtime/src/coreclr/jit/lowerxarch.cpp index 17baca1ea00..e81f0ea6014 100644 --- a/src/runtime/src/coreclr/jit/lowerxarch.cpp +++ b/src/runtime/src/coreclr/jit/lowerxarch.cpp @@ -5658,14 +5658,7 @@ GenTree* Lowering::LowerHWIntrinsicWithElement(GenTreeHWIntrinsic* node) GenTree* op2 = node->Op(2); GenTree* op3 = node->Op(3); - if (!op2->OperIsConst()) - { - comp->getSIMDInitTempVarNum(simdType); - - // We will specially handle WithElement in codegen when op2 isn't a constant - ContainCheckHWIntrinsic(node); - return node->gtNext; - } + assert(op2->OperIsConst()); ssize_t count = simdSize / genTypeSize(simdBaseType); ssize_t imm8 = op2->AsIntCon()->IconValue(); diff --git a/src/runtime/src/coreclr/jit/morph.cpp b/src/runtime/src/coreclr/jit/morph.cpp index 3705bc6faa2..99c18f19b58 100644 --- a/src/runtime/src/coreclr/jit/morph.cpp +++ b/src/runtime/src/coreclr/jit/morph.cpp @@ -7104,6 +7104,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA if (optLocalAssertionProp) { isQmarkColon = true; + BitVecOps::ClearD(apTraits, apLocalPostorder); } break; @@ -7744,6 +7745,40 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA qmarkOp2 = oldTree->AsOp()->gtOp2->AsOp()->gtOp2; } + // During global morph, give assertion prop another shot at this tree. + // + // We need to use the "postorder" assertion set here, because apLocal + // may reflect results from subtrees that have since been reordered. + // + // apLocalPostorder only includes live assertions from prior statements. + // + if (fgGlobalMorph && optLocalAssertionProp && (optAssertionCount > 0)) + { + GenTree* optimizedTree = tree; + bool again = JitConfig.JitEnablePostorderLocalAssertionProp() > 0; + bool didOptimize = false; + + if (!again) + { + JITDUMP("*** Postorder assertion prop disabled by config\n"); + } + + while (again) + { + tree = optimizedTree; + optimizedTree = optAssertionProp(apLocalPostorder, tree, nullptr, nullptr); + again = (optimizedTree != nullptr); + didOptimize |= again; + } + + assert(tree != nullptr); + + if (didOptimize) + { + gtUpdateNodeSideEffects(tree); + } + } + // Try to fold it, maybe we get lucky, tree = gtFoldExpr(tree); @@ -11716,13 +11751,12 @@ void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTree* { // Active dependent assertions are killed here // - ASSERT_TP killed = BitVecOps::MakeCopy(apTraits, GetAssertionDep(lclNum)); - BitVecOps::IntersectionD(apTraits, killed, apLocal); - - if (killed) - { + ASSERT_TP killed = GetAssertionDep(lclNum); #ifdef DEBUG + bool hasKills = !BitVecOps::IsEmptyIntersection(apTraits, apLocal, killed); + if (hasKills) + { AssertionIndex index = optAssertionCount; while (killed && (index > 0)) { @@ -11742,10 +11776,11 @@ void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTree* index--; } + } #endif - BitVecOps::DiffD(apTraits, apLocal, killed); - } + BitVecOps::DiffD(apTraits, apLocal, killed); + BitVecOps::DiffD(apTraits, apLocalPostorder, killed); } //------------------------------------------------------------------------ @@ -11762,7 +11797,7 @@ void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTree* // void Compiler::fgKillDependentAssertions(unsigned lclNum DEBUGARG(GenTree* tree)) { - if (BitVecOps::IsEmpty(apTraits, apLocal)) + if (BitVecOps::IsEmpty(apTraits, apLocal) && BitVecOps::IsEmpty(apTraits, apLocalPostorder)) { return; } @@ -12448,6 +12483,11 @@ void Compiler::fgMorphStmts(BasicBlock* block) compCurStmt = stmt; GenTree* oldTree = stmt->GetRootNode(); + if (optLocalAssertionProp) + { + BitVecOps::Assign(apTraits, apLocalPostorder, apLocal); + } + #ifdef DEBUG unsigned oldHash = verbose ? gtHashValue(oldTree) : DUMMY_INIT(~0); @@ -12668,7 +12708,8 @@ void Compiler::fgMorphBlock(BasicBlock* block, MorphUnreachableInfo* unreachable // Each block starts with an empty table, and no available assertions // optAssertionReset(0); - apLocal = BitVecOps::MakeEmpty(apTraits); + BitVecOps::ClearD(apTraits, apLocal); + BitVecOps::ClearD(apTraits, apLocalPostorder); } else { @@ -12806,6 +12847,8 @@ void Compiler::fgMorphBlock(BasicBlock* block, MorphUnreachableInfo* unreachable apLocal = BitVecOps::MakeEmpty(apTraits); } + BitVecOps::Assign(apTraits, apLocalPostorder, apLocal); + JITDUMPEXEC(optDumpAssertionIndices("Assertions in: ", apLocal)); } } @@ -12874,6 +12917,8 @@ PhaseStatus Compiler::fgMorphBlocks() // Local assertion prop is enabled if we are optimizing. // optAssertionInit(/* isLocalProp*/ true); + apLocal = BitVecOps::MakeEmpty(apTraits); + apLocalPostorder = BitVecOps::MakeEmpty(apTraits); } else { @@ -13009,10 +13054,12 @@ PhaseStatus Compiler::fgMorphBlocks() if (optLocalAssertionProp) { - Metrics.LocalAssertionCount = optAssertionCount; - Metrics.LocalAssertionOverflow = optAssertionOverflow; - Metrics.MorphTrackedLocals = lvaTrackedCount; - Metrics.MorphLocals = lvaCount; + Metrics.LocalAssertionCount = optAssertionCount; + Metrics.LocalAssertionOverflow = optAssertionOverflow; + Metrics.MorphTrackedLocals = lvaTrackedCount; + Metrics.MorphLocals = lvaCount; + optLocalAssertionProp = false; + optCrossBlockLocalAssertionProp = false; } // We may have converted a tailcall into a loop, in which case the first BB diff --git a/src/runtime/src/coreclr/jit/promotion.cpp b/src/runtime/src/coreclr/jit/promotion.cpp index b1e3af0f917..b6f55fd0525 100644 --- a/src/runtime/src/coreclr/jit/promotion.cpp +++ b/src/runtime/src/coreclr/jit/promotion.cpp @@ -1,6 +1,37 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// +// Physical promotion is an optimization where struct fields accessed as +// LCL_FLD nodes are promoted to individual primitive-typed local variables +// accessed as LCL_VAR, allowing register allocation and removing unnecessary +// memory operations. +// +// Key components: +// +// 1. Candidate Identification: +// - Identifies struct locals that aren't already promoted and aren't address-exposed +// - Analyzes access patterns to determine which fields are good promotion candidates +// - Uses weighted cost models to balance performance and code size and to take PGO +// data into account +// +// 2. Field Promotion: +// - Creates primitive-typed replacement locals for selected fields +// - Records which parts of the struct remains unpromoted +// +// 3. Access Transformation: +// - Transforms local field accesses to use promoted field variables +// - Decomposes struct stores and copies to operate on the primitive fields +// - Handles call argument passing and returns with field lists where appropriate +// - Tracks when values in promoted fields vs. original struct are fresher +// - Inserts read-backs when the struct field is fresher than the promoted local +// - Inserts write-backs when the promoted local is fresher than the struct field +// - Ensures proper state across basic block boundaries and exception flow +// +// The transformation carefully handles OSR locals, parameters, and call arguments, +// while maintaining correct behavior for exception handling and control flow. +// + #include "jitpch.h" #include "promotion.h" #include "jitstd/algorithm.h" diff --git a/src/runtime/src/coreclr/jit/promotiondecomposition.cpp b/src/runtime/src/coreclr/jit/promotiondecomposition.cpp index a8c6a00ce50..7f2dbf0257f 100644 --- a/src/runtime/src/coreclr/jit/promotiondecomposition.cpp +++ b/src/runtime/src/coreclr/jit/promotiondecomposition.cpp @@ -1,6 +1,33 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// +// This file provides the machinery to decompose stores and initializations +// involving physically promoted structs into stores/initialization involving +// individual fields. +// +// Key components include: +// +// 1. DecompositionStatementList +// - Collects statement trees during decomposition +// - Converts them to a single comma tree at the end +// +// 2. DecompositionPlan +// - Plans the decomposition of block operations +// - Manages mappings between source and destination replacements +// - Supports both copies between structs and initializations +// - Creates specialized access plans for remainders (unpromoted parts) +// +// 3. Field-by-field copying and initialization +// - Determines optimal order and strategy for field operations +// - Handles cases where replacements partially overlap +// - Optimizes GC pointer handling to minimize write barriers +// - Special cases primitive fields when possible +// +// This works in coordination with the ReplaceVisitor from promotion.cpp to +// transform IR after physical promotion decisions have been made. +// + #include "jitpch.h" #include "promotion.h" #include "jitstd/algorithm.h" diff --git a/src/runtime/src/coreclr/jit/promotionliveness.cpp b/src/runtime/src/coreclr/jit/promotionliveness.cpp index 93ac0c1eaf6..e137caab827 100644 --- a/src/runtime/src/coreclr/jit/promotionliveness.cpp +++ b/src/runtime/src/coreclr/jit/promotionliveness.cpp @@ -4,6 +4,42 @@ #include "jitpch.h" #include "promotion.h" +// +// This file implements a specialized liveness analysis for physically promoted struct fields +// and remainders. Unlike standard JIT liveness analysis, it focuses on accurately tracking +// which fields are live at specific program points to optimize physically promoted struct operations. +// +// Key characteristics: +// +// 1. Separate Bit Vectors: +// - Maintains its own liveness bit vectors separate from the main compiler's bbLiveIn/bbLiveOut +// - Uses "dense" indices: bit vectors only contain entries for the remainder and replacement +// fields of physically promoted structs (allocating 1 + num_fields indices per local) +// - Does not update BasicBlock::bbLiveIn or other standard liveness storage, as this would +// require allocating regular tracked indices (lvVarIndex) for all new fields +// +// 2. Liveness Representation: +// - Writes liveness into IR using normal GTF_VAR_DEATH flags +// - Important: After liveness is computed but before replacement phase completes, +// GTF_VAR_DEATH semantics temporarily differ from the rest of the JIT +// (e.g., "LCL_FLD int V16 [+8] (last use)" indicates that specific field is dying, +// not the whole variable) +// - For struct uses that can indicate deaths of multiple fields or remainder parts, +// maintains side information accessed via GetDeathsForStructLocal() +// +// 3. Analysis Process: +// - Single-pass dataflow computation (no DCE iterations, unlike other liveness passes) +// - Handles QMark nodes specially for conditional execution +// - Accounts for implicit exception flow +// - Distinguishes between full definitions and partial definitions +// +// The liveness information is critical for: +// - Avoiding creation of dead stores (especially to remainders, which the SSA liveness +// pass handles very conservatively as partial definitions) +// - Marking replacement fields with proper liveness flags for subsequent compiler phases +// - Optimizing read-back operations by determining when they're unnecessary +// + struct BasicBlockLiveness { // Variables used before a full definition. @@ -22,54 +58,6 @@ struct BasicBlockLiveness // Run: // Compute liveness information pertaining the promoted structs. // -// Remarks: -// For each promoted aggregate we compute the liveness for its remainder and -// all of its fields. Unlike regular liveness we currently do not do any DCE -// here and so only do the dataflow computation once. -// -// The liveness information is written into the IR using the normal -// GTF_VAR_DEATH flag. Note that the semantics of GTF_VAR_DEATH differs from -// the rest of the JIT for a short while between the liveness is computed and -// the replacement phase has run: in particular, after this liveness pass you -// may see a node like: -// -// LCL_FLD int V16 tmp9 [+8] (last use) -// -// that indicates that this particular field (or the remainder if it wasn't -// promoted) is dying, not that V16 itself is dying. After replacement has -// run the semantics align with the rest of the JIT: in the promoted case V16 -// [+8] would be replaced by its promoted field local, and in the remainder -// case all non-remainder uses of V16 would also be. -// -// There is one catch which is struct uses of the local. These can indicate -// deaths of multiple fields and also the remainder, so this information is -// stored on the side. PromotionLiveness::GetDeathsForStructLocal is used to -// query this information. -// -// The liveness information is used by decomposition to avoid creating dead -// stores, and also to mark the replacement field uses/defs with proper -// up-to-date liveness information to be used by future phases (forward sub -// and morph, as of writing this). It is also used to avoid creating -// unnecessary read-backs; this is mostly just a TP optimization as future -// liveness passes would be expected to DCE these anyway. -// -// Avoiding the creation of dead stores to the remainder is especially -// important as these otherwise would often end up looking like partial -// definitions, and the other liveness passes handle partial definitions very -// conservatively and are not able to DCE them. -// -// Unlike the other liveness passes we keep the per-block liveness -// information on the side and we do not update BasicBlock::bbLiveIn et al. -// This relies on downstream phases not requiring/wanting to use per-basic -// block live-in/live-out/var-use/var-def sets. To be able to update these we -// would need to give the new locals "regular" tracked indices (i.e. allocate -// a lvVarIndex). -// -// The indices allocated and used internally within the liveness computation -// are "dense" in the sense that the bit vectors only have indices for -// remainders and the replacement fields introduced by this pass. In other -// words, we allocate 1 + num_fields indices for each promoted struct local). -// void PromotionLiveness::Run() { m_structLclToTrackedIndex = new (m_compiler, CMK_Promotion) unsigned[m_compiler->lvaCount]{}; diff --git a/src/runtime/src/coreclr/jit/rationalize.cpp b/src/runtime/src/coreclr/jit/rationalize.cpp index 1c7d31f9eb4..be2e6f61944 100644 --- a/src/runtime/src/coreclr/jit/rationalize.cpp +++ b/src/runtime/src/coreclr/jit/rationalize.cpp @@ -385,6 +385,57 @@ void Rationalizer::RewriteHWIntrinsicAsUserCall(GenTree** use, ArrayStackOperIsConst()) + { + ssize_t imm8 = op2->AsIntCon()->IconValue(); + ssize_t count = simdSize / genTypeSize(simdBaseType); + + if ((imm8 >= count) || (imm8 < 0)) + { + // Using software fallback if index is out of range (throw exception) + break; + } + +#if defined(TARGET_XARCH) + if (varTypeIsIntegral(simdBaseType)) + { + if (varTypeIsLong(simdBaseType)) + { + if (!comp->compOpportunisticallyDependsOn(InstructionSet_SSE41_X64)) + { + break; + } + } + else if (!varTypeIsShort(simdBaseType)) + { + if (!comp->compOpportunisticallyDependsOn(InstructionSet_SSE41)) + { + break; + } + } + } +#endif // TARGET_XARCH + + result = comp->gtNewSimdWithElementNode(retType, op1, op2, op3, simdBaseJitType, simdSize); + break; + } + break; + } + default: { if (sigInfo.numArgs == 0) diff --git a/src/runtime/src/coreclr/jit/scev.cpp b/src/runtime/src/coreclr/jit/scev.cpp index e1140e48660..5a11e81e85a 100644 --- a/src/runtime/src/coreclr/jit/scev.cpp +++ b/src/runtime/src/coreclr/jit/scev.cpp @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// // This file contains code to analyze how the value of induction variables // evolve (scalar evolution analysis), and to turn them into the SCEV IR // defined in scev.h. The analysis is inspired by "Michael Wolfe. 1992. Beyond // induction variables." and also by LLVM's scalar evolution analysis. // -// The main idea of scalar evolution nalysis is to give a closed form +// The main idea of scalar evolution analysis is to give a closed form // describing the value of tree nodes inside loops even when taking into // account that they are changing on each loop iteration. This is useful for // optimizations that want to reason about values of IR nodes inside loops, @@ -28,34 +29,19 @@ // describes its value (possibly taking its evolution into account). Note that // SCEV nodes are immutable and the values they represent are _not_ // flow-dependent; that is, they don't exist at a specific location inside the -// loop, even though some particular tree node gave rise to that SCEV node. The -// analysis itself _is_ flow-dependent and guarantees that the Scev* returned -// describes the value that corresponds to what the tree node computes at its -// specific location. However, it would be perfectly legal for two trees at -// different locations in the loop to analyze to the same SCEV node (even -// potentially returning the same pointer). For example, in theory "i" and "j" -// in the following loop would both be represented by the same add recurrence -// , and the analysis could even return the same Scev* for both of -// them, even if it does not today: -// -// int i = 0; -// while (true) -// { -// i++; -// ... -// int j = i - 1; -// } -// -// Actually materializing the value of a SCEV node back into tree IR is not -// implemented yet, but generally would depend on the availability of tree -// nodes that compute the dependent values at the point where the IR is to be -// materialized. -// -// Besides the add recurrences the analysis itself is generally a -// straightforward translation from JIT IR into the SCEV IR. Creating the add -// recurrences requires paying attention to the structure of PHIs, and -// disambiguating the values coming from outside the loop and the values coming -// from the backedges. +// loop, even though some particular tree node gave rise to that SCEV node. +// +// The SCEV analysis is capable of: +// +// 1. Identifying both direct and indirect induction variables +// 2. Simplifying complex expressions involving induction variables +// 3. Determining when recurrences won't overflow during loop execution +// 4. Computing exact trip counts for countable loops +// 5. Converting SCEV expressions back to JIT IR and value numbers +// +// Understanding the relationship between values across iterations enables +// many loop optimizations, including strength reduction, loop reversal, +// and IV widening, which are implemented in inductionvariableopts.cpp. // #include "jitpch.h" diff --git a/src/runtime/src/coreclr/minipal/Unix/doublemapping.cpp b/src/runtime/src/coreclr/minipal/Unix/doublemapping.cpp index 4a2516bea58..b866da9f93e 100644 --- a/src/runtime/src/coreclr/minipal/Unix/doublemapping.cpp +++ b/src/runtime/src/coreclr/minipal/Unix/doublemapping.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -26,11 +25,6 @@ #include "minipal.h" #include "minipal/cpufeatures.h" -#ifndef TARGET_APPLE -#include -#include -#endif // TARGET_APPLE - #ifdef TARGET_APPLE #include @@ -259,320 +253,3 @@ bool VMToOSInterface::ReleaseRWMapping(void* pStart, size_t size) { return munmap(pStart, size) != -1; } - -#ifndef TARGET_APPLE -#define MAX_TEMPLATE_THUNK_TYPES 3 // Maximum number of times the CreateTemplate api can be called -struct TemplateThunkMappingData -{ - int fdImage; - off_t offsetInFileOfStartOfSection; - void* addrOfStartOfSection; // Always NULL if the template mapping data could not be initialized - void* addrOfEndOfSection; - bool imageTemplates; - int templatesCreated; - off_t nonImageTemplateCurrent; -}; - -struct InitializeTemplateThunkLocals -{ - void* pTemplate; - Dl_info info; - TemplateThunkMappingData data; -}; - -static TemplateThunkMappingData *s_pThunkData = NULL; - -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE - -static Elf32_Word Elf32_WordMin(Elf32_Word left, Elf32_Word right) -{ - return left < right ? left : right; -} - -static int InitializeTemplateThunkMappingDataPhdrCallback(struct dl_phdr_info *info, size_t size, void *dataPtr) -{ - InitializeTemplateThunkLocals *locals = (InitializeTemplateThunkLocals*)dataPtr; - - if ((void*)info->dlpi_addr == locals->info.dli_fbase) - { - for (size_t j = 0; j < info->dlpi_phnum; j++) - { - uint8_t* baseSectionAddr = (uint8_t*)locals->info.dli_fbase + info->dlpi_phdr[j].p_vaddr; - if (locals->pTemplate < baseSectionAddr) - { - // Address is before the virtual address of this section begins - continue; - } - - // Since this is all in support of mapping code from the file, we need to ensure that the region we find - // is actually present in the file. - Elf32_Word sizeOfSectionWhichCanBeMapped = Elf32_WordMin(info->dlpi_phdr[j].p_filesz, info->dlpi_phdr[j].p_memsz); - - uint8_t* endAddressAllowedForTemplate = baseSectionAddr + sizeOfSectionWhichCanBeMapped; - if (locals->pTemplate >= endAddressAllowedForTemplate) - { - // Template is after the virtual address of this section ends (or the mappable region of the file) - continue; - } - - // At this point, we have found the template section. Attempt to open the file, and record the various offsets for future use - - if (strlen(info->dlpi_name) == 0) - { - // This image cannot be directly referenced without capturing the argv[0] parameter - return -1; - } - - int fdImage = open(info->dlpi_name, O_RDONLY); - if (fdImage == -1) - { - return -1; // Opening the image didn't work - } - - locals->data.fdImage = fdImage; - locals->data.offsetInFileOfStartOfSection = info->dlpi_phdr[j].p_offset; - locals->data.addrOfStartOfSection = baseSectionAddr; - locals->data.addrOfEndOfSection = baseSectionAddr + sizeOfSectionWhichCanBeMapped; - locals->data.imageTemplates = true; - return 1; // We have found the result. Abort further processing. - } - } - - // This isn't the interesting .so - return 0; -} -#endif // FEATURE_MAP_THUNKS_FROM_IMAGE - -TemplateThunkMappingData *InitializeTemplateThunkMappingData(void* pTemplate) -{ - InitializeTemplateThunkLocals locals; - locals.pTemplate = pTemplate; - locals.data.fdImage = 0; - locals.data.offsetInFileOfStartOfSection = 0; - locals.data.addrOfStartOfSection = NULL; - locals.data.addrOfEndOfSection = NULL; - locals.data.imageTemplates = false; - locals.data.nonImageTemplateCurrent = 0; - locals.data.templatesCreated = 0; - -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE - if (dladdr(pTemplate, &locals.info) != 0) - { - dl_iterate_phdr(InitializeTemplateThunkMappingDataPhdrCallback, &locals); - } -#endif // FEATURE_MAP_THUNKS_FROM_IMAGE - - if (locals.data.addrOfStartOfSection == NULL) - { - // This is the detail of thunk data which indicates if we were able to compute the template mapping data from the image. - -#ifdef TARGET_FREEBSD - int fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, S_IRWXU); -#elif defined(TARGET_LINUX) || defined(TARGET_ANDROID) - int fd = memfd_create("doublemapper-template", MFD_CLOEXEC); -#else - int fd = -1; - -#ifndef TARGET_ANDROID - // Bionic doesn't have shm_{open,unlink} - // POSIX fallback - if (fd == -1) - { - char name[24]; - sprintf(name, "/shm-dotnet-template-%d", getpid()); - name[sizeof(name) - 1] = '\0'; - shm_unlink(name); - fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); - shm_unlink(name); - } -#endif // !TARGET_ANDROID -#endif - if (fd != -1) - { - off_t maxFileSize = MAX_TEMPLATE_THUNK_TYPES * 0x10000; // The largest page size we support currently is 64KB. - if (ftruncate(fd, maxFileSize) == -1) // Reserve a decent size chunk of logical memory for these things. - { - close(fd); - } - else - { - locals.data.fdImage = fd; - locals.data.offsetInFileOfStartOfSection = 0; - // We simulate the template thunk mapping data existing in mapped ram, by declaring that it exists at at - // an address which is not NULL, and which is naturally aligned on the largest page size supported by any - // architecture we support (0x10000). We do this, as the generalized logic here is designed around remapping - // already mapped memory, and by doing this we are able to share that logic. - locals.data.addrOfStartOfSection = (void*)0x10000; - locals.data.addrOfEndOfSection = ((uint8_t*)locals.data.addrOfStartOfSection) + maxFileSize; - locals.data.imageTemplates = false; - } - } - } - - - TemplateThunkMappingData *pAllocatedData = (TemplateThunkMappingData*)malloc(sizeof(TemplateThunkMappingData)); - *pAllocatedData = locals.data; - TemplateThunkMappingData *pExpectedNull = NULL; - if (__atomic_compare_exchange_n (&s_pThunkData, &pExpectedNull, pAllocatedData, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) - { - return pAllocatedData; - } - else - { - free(pAllocatedData); - return __atomic_load_n(&s_pThunkData, __ATOMIC_ACQUIRE); - } -} -#endif - -bool VMToOSInterface::AllocateThunksFromTemplateRespectsStartAddress() -{ -#ifdef TARGET_APPLE - return false; -#else - return true; -#endif -} - -void* VMToOSInterface::CreateTemplate(void* pImageTemplate, size_t templateSize, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)) -{ -#ifdef TARGET_APPLE - return pImageTemplate; -#elif defined(TARGET_X86) - return NULL; // X86 doesn't support high performance relative addressing, which makes the template system not work -#else - if (pImageTemplate == NULL) - return NULL; - - TemplateThunkMappingData* pThunkData = __atomic_load_n(&s_pThunkData, __ATOMIC_ACQUIRE); - if (s_pThunkData == NULL) - { - pThunkData = InitializeTemplateThunkMappingData(pImageTemplate); - } - - // Unable to create template mapping region - if (pThunkData->addrOfStartOfSection == NULL) - { - return NULL; - } - - int templatesCreated = __atomic_add_fetch(&pThunkData->templatesCreated, 1, __ATOMIC_SEQ_CST); - assert(templatesCreated <= MAX_TEMPLATE_THUNK_TYPES); - - if (!pThunkData->imageTemplates) - { - // Need to allocate a memory mapped region to fill in the data - off_t locationInFileToStoreGeneratedCode = __atomic_fetch_add((off_t*)&pThunkData->nonImageTemplateCurrent, (off_t)templateSize, __ATOMIC_SEQ_CST); - void* mappedMemory = mmap(NULL, templateSize, PROT_READ | PROT_WRITE, MAP_SHARED, pThunkData->fdImage, locationInFileToStoreGeneratedCode); - if (mappedMemory != MAP_FAILED) - { - codePageGenerator((uint8_t*)mappedMemory, (uint8_t*)mappedMemory, templateSize); - munmap(mappedMemory, templateSize); - return ((uint8_t*)pThunkData->addrOfStartOfSection) + locationInFileToStoreGeneratedCode; - } - else - { - return NULL; - } - } - else - { - return pImageTemplate; - } -#endif -} - -void* VMToOSInterface::AllocateThunksFromTemplate(void* pTemplate, size_t templateSize, void* pStartSpecification) -{ -#ifdef TARGET_APPLE - vm_address_t addr, taddr; - vm_prot_t prot, max_prot; - kern_return_t ret; - - // Allocate two contiguous ranges of memory: the first range will contain the stubs - // and the second range will contain their data. - do - { - ret = vm_allocate(mach_task_self(), &addr, templateSize * 2, VM_FLAGS_ANYWHERE); - } while (ret == KERN_ABORTED); - - if (ret != KERN_SUCCESS) - { - return NULL; - } - - do - { - ret = vm_remap( - mach_task_self(), &addr, templateSize, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, - mach_task_self(), (vm_address_t)pTemplate, FALSE, &prot, &max_prot, VM_INHERIT_SHARE); - } while (ret == KERN_ABORTED); - - if (ret != KERN_SUCCESS) - { - do - { - ret = vm_deallocate(mach_task_self(), addr, templateSize * 2); - } while (ret == KERN_ABORTED); - - return NULL; - } - return (void*)addr; -#else - TemplateThunkMappingData* pThunkData = __atomic_load_n(&s_pThunkData, __ATOMIC_ACQUIRE); - if (s_pThunkData == NULL) - { - pThunkData = InitializeTemplateThunkMappingData(pTemplate); - } - - if (pThunkData->addrOfStartOfSection == NULL) - { - // This is the detail of thunk data which indicates if we were able to compute the template mapping data - return NULL; - } - - if (pTemplate < pThunkData->addrOfStartOfSection) - { - return NULL; - } - - uint8_t* endOfTemplate = ((uint8_t*)pTemplate + templateSize); - if (endOfTemplate > pThunkData->addrOfEndOfSection) - return NULL; - - size_t sectionOffset = (uint8_t*)pTemplate - (uint8_t*)pThunkData->addrOfStartOfSection; - off_t fileOffset = pThunkData->offsetInFileOfStartOfSection + sectionOffset; - - void *pStart = mmap(pStartSpecification, templateSize * 2, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | (pStartSpecification != NULL ? MAP_FIXED : 0), -1, 0); - if (pStart == MAP_FAILED) - { - return NULL; - } - - void *pStartCode = mmap(pStart, templateSize, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_FIXED, pThunkData->fdImage, fileOffset); - if (pStart != pStartCode) - { - munmap(pStart, templateSize * 2); - return NULL; - } - - return pStart; -#endif -} - -bool VMToOSInterface::FreeThunksFromTemplate(void* thunks, size_t templateSize) -{ -#ifdef TARGET_APPLE - kern_return_t ret; - - do - { - ret = vm_deallocate(mach_task_self(), (vm_address_t)thunks, templateSize * 2); - } while (ret == KERN_ABORTED); - - return ret == KERN_SUCCESS ? true : false; -#else - munmap(thunks, templateSize * 2); - return true; -#endif -} diff --git a/src/runtime/src/coreclr/minipal/Windows/doublemapping.cpp b/src/runtime/src/coreclr/minipal/Windows/doublemapping.cpp index f5f25f2bec9..9e8ddfed8e9 100644 --- a/src/runtime/src/coreclr/minipal/Windows/doublemapping.cpp +++ b/src/runtime/src/coreclr/minipal/Windows/doublemapping.cpp @@ -210,23 +210,3 @@ bool VMToOSInterface::ReleaseRWMapping(void* pStart, size_t size) { return UnmapViewOfFile(pStart); } - -void* VMToOSInterface::CreateTemplate(void* pImageTemplate, size_t templateSize, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)) -{ - return NULL; -} - -bool VMToOSInterface::AllocateThunksFromTemplateRespectsStartAddress() -{ - return false; -} - -void* VMToOSInterface::AllocateThunksFromTemplate(void* pTemplate, size_t templateSize, void* pStart) -{ - return NULL; -} - -bool VMToOSInterface::FreeThunksFromTemplate(void* thunks, size_t templateSize) -{ - return false; -} diff --git a/src/runtime/src/coreclr/minipal/minipal.h b/src/runtime/src/coreclr/minipal/minipal.h index 01f497e60e6..afecd9ce74d 100644 --- a/src/runtime/src/coreclr/minipal/minipal.h +++ b/src/runtime/src/coreclr/minipal/minipal.h @@ -75,41 +75,6 @@ class VMToOSInterface // Return: // true if it succeeded, false if it failed static bool ReleaseRWMapping(void* pStart, size_t size); - - // Create a template for use by AllocateThunksFromTemplate - // Parameters: - // pImageTemplate - Address of start of template in the image for coreclr. (All addresses passed to the api in a process must be from the same module, if any call uses a pImageTemplate, all calls MUST) - // templateSize - Size of the template - // codePageGenerator - If the system is unable to use pImageTemplate, use this parameter to generate the code page instead - // - // Return: - // NULL if creating the template fails - // Non-NULL, a pointer to the template - static void* CreateTemplate(void* pImageTemplate, size_t templateSize, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)); - - // Indicate if the AllocateThunksFromTemplate function respects the pStart address passed to AllocateThunksFromTemplate on this platform - // Return: - // true if the parameter is respected, false if not - static bool AllocateThunksFromTemplateRespectsStartAddress(); - - // Allocate thunks from template - // Parameters: - // pTemplate - Value returned from CreateTemplate - // templateSize - Size of the templates block in the image - // pStart - Where to allocate (Specify NULL if no particular address is required). If non-null, this must be an address returned by ReserveDoubleMappedMemory - // - // Return: - // NULL if the allocation fails - // Non-NULL, a pointer to the allocated region. - static void* AllocateThunksFromTemplate(void* pTemplate, size_t templateSize, void* pStart); - - // Free thunks allocated from template - // Parameters: - // pThunks - Address previously returned by AllocateThunksFromTemplate - // templateSize - Size of the templates block in the image - // Return: - // true if it succeeded, false if it failed - static bool FreeThunksFromTemplate(void* thunks, size_t templateSize); }; #if defined(HOST_64BIT) && defined(FEATURE_CACHED_INTERFACE_DISPATCH) diff --git a/src/runtime/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/runtime/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 94ad25ceab8..a928e7018da 100644 --- a/src/runtime/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/runtime/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -526,7 +526,7 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDL vm_prot_t prot, max_prot; kern_return_t ret; - // Allocate two contiguous ranges of memory: the first range will contain the stubs + // Allocate two contiguous ranges of memory: the first range will contain the trampolines // and the second range will contain their data. do { diff --git a/src/runtime/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/runtime/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index a2debab5499..26df766b18b 100644 --- a/src/runtime/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/runtime/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -425,9 +425,16 @@ bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress) #else // Extract the necessary information from the info block header hdrInfo info; - DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &info); + size_t infoSize = DecodeGCHdrInfo(GCInfoToken(gcInfo), codeOffset, &info); + PTR_CBYTE table = gcInfo + infoSize; + + if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG) + return false; + + if (!info.interruptible) + return false; - return info.interruptible && info.prologOffs == hdrInfo::NOT_IN_PROLOG && info.epilogOffs == hdrInfo::NOT_IN_EPILOG; + return !IsInNoGCRegion(&info, table, codeOffset); #endif } diff --git a/src/runtime/src/coreclr/pal/inc/unixasmmacrosarm64.inc b/src/runtime/src/coreclr/pal/inc/unixasmmacrosarm64.inc index 640716f8058..746e48321db 100644 --- a/src/runtime/src/coreclr/pal/inc/unixasmmacrosarm64.inc +++ b/src/runtime/src/coreclr/pal/inc/unixasmmacrosarm64.inc @@ -37,6 +37,13 @@ C_FUNC(\Name): C_FUNC(\Name): .endm +// On MacOS, local labels cannot be used in arithmetic expressions. +#if defined(__APPLE__) +#define FIXUP_LABEL(name) name +#else +#define FIXUP_LABEL(name) .L##name +#endif + .macro LEAF_ENTRY Name, Section .global C_FUNC(\Name) #if defined(__APPLE__) diff --git a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs index 653425781a7..83ac2ef3747 100644 --- a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs +++ b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs @@ -19,7 +19,8 @@ enum InfoHdrAdjustConstants SET_EPILOGSIZE_MAX = 10, SET_EPILOGCNT_MAX = 4, SET_UNTRACKED_MAX = 3, - SET_RET_KIND_MAX = 4, + SET_RET_KIND_MAX = 3, + SET_NOGCREGIONS_MAX = 4, ADJ_ENCODING_MAX = 0x7f, MORE_BYTES_TO_FOLLOW = 0x80 }; @@ -67,6 +68,16 @@ enum InfoHdrAdjust NEXT_THREE_EPILOGSIZE = 0x78 }; + /// + /// Second set of opcodes, when first code is 0x4F + /// + enum InfoHdrAdjust2 + { + SET_RETURNKIND = 0, // 0x00-SET_RET_KIND_MAX Set ReturnKind to value + SET_NOGCREGIONS_CNT = SET_RETURNKIND + InfoHdrAdjustConstants.SET_RET_KIND_MAX + 1, // 0x04 + FFFF_NOGCREGION_CNT = SET_NOGCREGIONS_CNT + InfoHdrAdjustConstants.SET_NOGCREGIONS_MAX + 1 // 0x09 There is a count (>SET_NOGCREGIONS_MAX) after the header encoding + }; + /// /// based on macros defined in src\inc\gcinfotypes.h /// diff --git a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs index ddb44a1d312..7b6f466c2f1 100644 --- a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs +++ b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs @@ -13,6 +13,7 @@ public class GcInfo : BaseGcInfo const uint byref_OFFSET_FLAG = 0x1; public InfoHdrSmall Header { get; set; } + public NoGcRegionTable NoGCRegions { get; set; } public GcSlotTable SlotTable { get; set; } public GcInfo() { } @@ -28,6 +29,8 @@ public GcInfo(byte[] image, int offset) Header = InfoHdrDecoder.DecodeHeader(image, ref offset, CodeLength); + NoGCRegions = new NoGcRegionTable(image, Header, ref offset); + SlotTable = new GcSlotTable(image, Header, ref offset); Transitions = new Dictionary>(); @@ -54,6 +57,7 @@ public override string ToString() sb.AppendLine($" CodeLength: {CodeLength} bytes"); sb.AppendLine($" InfoHdr:"); sb.AppendLine($"{Header}"); + sb.AppendLine($"{NoGCRegions}"); sb.AppendLine($"{SlotTable}"); sb.AppendLine($" Size: {Size} bytes"); diff --git a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs index 14148a9ddc2..7852ebf962d 100644 --- a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs +++ b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/InfoHdr.cs @@ -44,6 +44,7 @@ public struct InfoHdrSmall public uint SyncStartOffset { get; set; } public uint SyncEndOffset { get; set; } public uint RevPInvokeOffset { get; set; } + public uint NoGCRegionCnt { get; set; } public bool HasArgTabOffset { get; set; } public uint ArgTabOffset { get; set; } @@ -82,6 +83,7 @@ public InfoHdrSmall(uint prologSize, uint epilogSize, byte epilogCount, byte epi SyncStartOffset = 0; SyncEndOffset = 0; RevPInvokeOffset = 0; + NoGCRegionCnt = 0; HasArgTabOffset = false; ArgTabOffset = 0; @@ -138,6 +140,10 @@ public override string ToString() { sb.AppendLine($" Sync region = [{SyncStartOffset},{SyncEndOffset}]"); } + if (NoGCRegionCnt > 0) + { + sb.AppendLine($" No GC region count = {NoGCRegionCnt}"); + } sb.Append($" Epilogs:"); foreach (int epilog in Epilogs) @@ -158,6 +164,7 @@ public class InfoHdrDecoder { private const uint HAS_GS_COOKIE_OFFSET = 0xFFFFFFFF; private const uint HAS_SYNC_OFFSET = 0xFFFFFFFF; private const uint HAS_REV_PINVOKE_FRAME_OFFSET = 0xFFFFFFFF; + private const uint HAS_NOGCREGIONS = 0xFFFFFFFF; private const uint YES = HAS_VARPTR; /// @@ -278,11 +285,18 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe nextByte = image[offset++]; encoding = (byte)(nextByte & (int)InfoHdrAdjustConstants.ADJ_ENCODING_MAX); // encoding here always corresponds to codes in InfoHdrAdjust2 set - - if (encoding < (int)InfoHdrAdjustConstants.SET_RET_KIND_MAX) + if (encoding <= (int)InfoHdrAdjustConstants.SET_RET_KIND_MAX) { header.ReturnKind = (ReturnKinds)encoding; } + else if (encoding < (int)InfoHdrAdjust2.FFFF_NOGCREGION_CNT) + { + header.NoGCRegionCnt = (uint)encoding - (uint)InfoHdrAdjust2.SET_NOGCREGIONS_CNT; + } + else if (encoding == (int)InfoHdrAdjust2.FFFF_NOGCREGION_CNT) + { + header.NoGCRegionCnt = HAS_NOGCREGIONS; + } else { throw new BadImageFormatException("Unexpected gcinfo header encoding"); @@ -351,6 +365,10 @@ public static InfoHdrSmall DecodeHeader(byte[] image, ref int offset, int codeLe { header.RevPInvokeOffset = NativeReader.DecodeUnsignedGc(image, ref offset); } + if (header.NoGCRegionCnt == HAS_NOGCREGIONS) + { + header.NoGCRegionCnt = NativeReader.DecodeUnsignedGc(image, ref offset); + } header.Epilogs = new List(); if (header.EpilogCount > 1 || (header.EpilogCount != 0 && !header.EpilogAtEnd)) diff --git a/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs new file mode 100644 index 00000000000..4dcc332cb7c --- /dev/null +++ b/src/runtime/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/NoGcRegionTable.cs @@ -0,0 +1,65 @@ +// 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.Generic; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace ILCompiler.Reflection.ReadyToRun.x86 +{ + public class NoGcRegionTable + { + public class NoGcRegion + { + public uint Offset { get; set; } + public uint Size { get; set; } + + public NoGcRegion(uint offset, uint size) + { + Offset = offset; + Size = size; + } + + public override string ToString() + { + return $" [{Offset:04X}-{Offset+Size:04X})\n"; + } + } + + public List Regions { get; set; } + + public NoGcRegionTable() { } + + public NoGcRegionTable(byte[] image, InfoHdrSmall header, ref int offset) + { + Regions = new List((int)header.NoGCRegionCnt); + + uint count = header.NoGCRegionCnt; + while (count-- > 0) + { + uint regionOffset = NativeReader.DecodeUnsignedGc(image, ref offset); + uint regionSize = NativeReader.DecodeUnsignedGc(image, ref offset); + Regions.Add(new NoGcRegion(regionOffset, regionSize)); + } + } + + public override string ToString() + { + if (Regions.Count > 0) + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($" No GC regions:"); + foreach (NoGcRegion region in Regions) + { + sb.Append(region.ToString()); + } + + return sb.ToString(); + } + + return string.Empty; + } + } +} diff --git a/src/runtime/src/coreclr/utilcode/executableallocator.cpp b/src/runtime/src/coreclr/utilcode/executableallocator.cpp index 02423770722..d145ab03987 100644 --- a/src/runtime/src/coreclr/utilcode/executableallocator.cpp +++ b/src/runtime/src/coreclr/utilcode/executableallocator.cpp @@ -503,11 +503,6 @@ void* ExecutableAllocator::Commit(void* pStart, size_t size, bool isExecutable) } void ExecutableAllocator::Release(void* pRX) -{ - ReleaseWorker(pRX, false /* this is the standard Release of normally allocated memory */); -} - -void ExecutableAllocator::ReleaseWorker(void* pRX, bool releaseTemplate) { LIMITED_METHOD_CONTRACT; @@ -553,19 +548,9 @@ void ExecutableAllocator::ReleaseWorker(void* pRX, bool releaseTemplate) cachedMappingThatOverlaps = FindOverlappingCachedMapping(pBlock); } - if (releaseTemplate) + if (!VMToOSInterface::ReleaseDoubleMappedMemory(m_doubleMemoryMapperHandle, pRX, pBlock->offset, pBlock->size)) { - if (!VMToOSInterface::FreeThunksFromTemplate(pRX, pBlock->size / 2)) - { - g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the template mapped memory failed")); - } - } - else - { - if (!VMToOSInterface::ReleaseDoubleMappedMemory(m_doubleMemoryMapperHandle, pRX, pBlock->offset, pBlock->size)) - { - g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the double mapped memory failed")); - } + g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the double mapped memory failed")); } // Put the released block into the free block list pBlock->baseRX = NULL; @@ -977,60 +962,3 @@ void ExecutableAllocator::UnmapRW(void* pRW) g_fatalErrorHandler(COR_E_EXECUTIONENGINE, W("Releasing the RW mapping failed")); } } - -void* ExecutableAllocator::AllocateThunksFromTemplate(void *pTemplate, size_t templateSize) -{ - if (IsDoubleMappingEnabled() && VMToOSInterface::AllocateThunksFromTemplateRespectsStartAddress()) - { - CRITSEC_Holder csh(m_CriticalSection); - - bool isFreeBlock; - BlockRX* block = AllocateBlock(templateSize * 2, &isFreeBlock); - if (block == NULL) - { - return NULL; - } - - void* result = VMToOSInterface::ReserveDoubleMappedMemory(m_doubleMemoryMapperHandle, block->offset, templateSize * 2, 0, 0); - - if (result != NULL) - { - block->baseRX = result; - AddRXBlock(block); - } - else - { - BackoutBlock(block, isFreeBlock); - } - - void *pTemplateAddressAllocated = VMToOSInterface::AllocateThunksFromTemplate(pTemplate, templateSize, block->baseRX); - - if (pTemplateAddressAllocated == NULL) - { - ReleaseWorker(block->baseRX, false); - } - - return pTemplateAddressAllocated; - } - else - { - return VMToOSInterface::AllocateThunksFromTemplate(pTemplate, templateSize, NULL); - } -} - -void ExecutableAllocator::FreeThunksFromTemplate(void *pThunks, size_t templateSize) -{ - if (IsDoubleMappingEnabled() && VMToOSInterface::AllocateThunksFromTemplateRespectsStartAddress()) - { - ReleaseWorker(pThunks, true /* This is a release of template allocated memory */); - } - else - { - VMToOSInterface::FreeThunksFromTemplate(pThunks, templateSize); - } -} - -void* ExecutableAllocator::CreateTemplate(void* templateInImage, size_t templateSize, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)) -{ - return VMToOSInterface::CreateTemplate(templateInImage, templateSize, codePageGenerator); -} diff --git a/src/runtime/src/coreclr/utilcode/interleavedloaderheap.cpp b/src/runtime/src/coreclr/utilcode/interleavedloaderheap.cpp index 082e337caeb..d908ea20c19 100644 --- a/src/runtime/src/coreclr/utilcode/interleavedloaderheap.cpp +++ b/src/runtime/src/coreclr/utilcode/interleavedloaderheap.cpp @@ -33,13 +33,10 @@ namespace UnlockedInterleavedLoaderHeap::UnlockedInterleavedLoaderHeap( RangeList *pRangeList, - const InterleavedLoaderHeapConfig *pConfig) : + void (*codePageGenerator)(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size), + DWORD dwGranularity) : UnlockedLoaderHeapBase(LoaderHeapImplementationKind::Interleaved), - m_pEndReservedRegion(NULL), - m_dwGranularity(pConfig->StubSize), - m_pRangeList(pRangeList), - m_pFreeListHead(NULL), - m_pConfig(pConfig) + m_pFreeListHead(NULL) { CONTRACTL { @@ -49,7 +46,15 @@ UnlockedInterleavedLoaderHeap::UnlockedInterleavedLoaderHeap( } CONTRACTL_END; + m_pEndReservedRegion = NULL; + + m_pRangeList = pRangeList; + _ASSERTE((GetStubCodePageSize() % GetOsPageSize()) == 0); // Stub code page size MUST be in increments of the page size. (Really it must be a power of 2 as well, but this is good enough) + m_dwGranularity = dwGranularity; + + _ASSERTE(codePageGenerator != NULL); + m_codePageGenerator = codePageGenerator; } // ~LoaderHeap is not synchronised (obviously) @@ -75,14 +80,7 @@ UnlockedInterleavedLoaderHeap::~UnlockedInterleavedLoaderHeap() pVirtualAddress = pSearch->pVirtualAddress; pNext = pSearch->pNext; - if (m_pConfig->Template != NULL) - { - ExecutableAllocator::Instance()->FreeThunksFromTemplate(pVirtualAddress, GetStubCodePageSize()); - } - else - { - ExecutableAllocator::Instance()->Release(pVirtualAddress); - } + ExecutableAllocator::Instance()->Release(pVirtualAddress); delete pSearch; } @@ -103,7 +101,6 @@ size_t UnlockedInterleavedLoaderHeap::GetBytesAvailReservedRegion() BOOL UnlockedInterleavedLoaderHeap::CommitPages(void* pData, size_t dwSizeToCommitPart) { - _ASSERTE(m_pConfig->Template == NULL); // This path should only be used for LoaderHeaps which use the standard ExecutableAllocator functions // Commit first set of pages, since it will contain the LoaderHeapBlock { void *pTemp = ExecutableAllocator::Instance()->Commit(pData, dwSizeToCommitPart, IsExecutable()); @@ -124,7 +121,7 @@ BOOL UnlockedInterleavedLoaderHeap::CommitPages(void* pData, size_t dwSizeToComm } ExecutableWriterHolder codePageWriterHolder((BYTE*)pData, dwSizeToCommitPart, ExecutableAllocator::DoNotAddToCache); - m_pConfig->CodePageGenerator(codePageWriterHolder.GetRW(), (BYTE*)pData, dwSizeToCommitPart); + m_codePageGenerator(codePageWriterHolder.GetRW(), (BYTE*)pData, dwSizeToCommitPart); FlushInstructionCache(GetCurrentProcess(), pData, dwSizeToCommitPart); return TRUE; @@ -140,8 +137,6 @@ BOOL UnlockedInterleavedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit) } CONTRACTL_END; - _ASSERTE(m_pConfig->Template == NULL); // This path should only be used for LoaderHeaps which use the standard ExecutableAllocator functions - size_t dwSizeToReserve; // Round to page size again @@ -227,14 +222,6 @@ BOOL UnlockedInterleavedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit) return TRUE; } -void ReleaseAllocatedThunks(BYTE* thunks) -{ - ExecutableAllocator::Instance()->FreeThunksFromTemplate(thunks, GetStubCodePageSize()); -} - -using ThunkMemoryHolder = SpecializedWrapper; - - // Get some more committed pages - either commit some more in the current reserved region, or, if it // has run out, reserve another set of pages. // Returns: FALSE if we can't get any more memory @@ -250,57 +237,6 @@ BOOL UnlockedInterleavedLoaderHeap::GetMoreCommittedPages(size_t dwMinSize) } CONTRACTL_END; - if (m_pConfig->Template != NULL) - { - ThunkMemoryHolder newAllocatedThunks = (BYTE*)ExecutableAllocator::Instance()->AllocateThunksFromTemplate(m_pConfig->Template, GetStubCodePageSize()); - if (newAllocatedThunks == NULL) - { - return FALSE; - } - - NewHolder pNewBlock = new (nothrow) LoaderHeapBlock; - if (pNewBlock == NULL) - { - return FALSE; - } - - size_t dwSizeToReserve = GetStubCodePageSize() * 2; - - // Record reserved range in range list, if one is specified - // Do this AFTER the commit - otherwise we'll have bogus ranges included. - if (m_pRangeList != NULL) - { - if (!m_pRangeList->AddRange((const BYTE *) newAllocatedThunks, - ((const BYTE *) newAllocatedThunks) + dwSizeToReserve, - (void *) this)) - { - return FALSE; - } - } - - m_dwTotalAlloc += dwSizeToReserve; - - pNewBlock.SuppressRelease(); - newAllocatedThunks.SuppressRelease(); - - pNewBlock->dwVirtualSize = dwSizeToReserve; - pNewBlock->pVirtualAddress = newAllocatedThunks; - pNewBlock->pNext = m_pFirstBlock; - pNewBlock->m_fReleaseMemory = TRUE; - - // Add to the linked list - m_pFirstBlock = pNewBlock; - - m_pAllocPtr = (BYTE*)newAllocatedThunks; - m_pPtrToEndOfCommittedRegion = m_pAllocPtr + GetStubCodePageSize(); - m_pEndReservedRegion = m_pAllocPtr + dwSizeToReserve; // For consistency with the non-template path m_pEndReservedRegion is after the end of the data area - m_dwTotalAlloc += GetStubCodePageSize(); - - return TRUE; - } - - // From here, all work is only for the dynamically allocated InterleavedLoaderHeap path - // If we have memory we can use, what are you doing here! _ASSERTE(dwMinSize > (SIZE_T)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr)); @@ -538,13 +474,5 @@ void *UnlockedInterleavedLoaderHeap::UnlockedAllocStub( return pResult; } - -void InitializeLoaderHeapConfig(InterleavedLoaderHeapConfig *pConfig, size_t stubSize, void* templateInImage, void (*codePageGenerator)(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size)) -{ - pConfig->StubSize = (uint32_t)stubSize; - pConfig->Template = ExecutableAllocator::Instance()->CreateTemplate(templateInImage, GetStubCodePageSize(), codePageGenerator); - pConfig->CodePageGenerator = codePageGenerator; -} - #endif // #ifndef DACCESS_COMPILE diff --git a/src/runtime/src/coreclr/vm/CMakeLists.txt b/src/runtime/src/coreclr/vm/CMakeLists.txt index e910f0579ff..32e932906ab 100644 --- a/src/runtime/src/coreclr/vm/CMakeLists.txt +++ b/src/runtime/src/coreclr/vm/CMakeLists.txt @@ -785,11 +785,11 @@ if(CLR_CMAKE_TARGET_ARCH_AMD64) ) set(VM_SOURCES_WKS_ARCH - ${ARCH_SOURCES_DIR}/jitinterfaceamd64.cpp ${ARCH_SOURCES_DIR}/profiler.cpp exceptionhandling.cpp gcinfodecoder.cpp jitinterfacegen.cpp + writebarriermanager.cpp ) set(VM_HEADERS_WKS_ARCH @@ -856,6 +856,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM64) set(VM_SOURCES_WKS_ARCH ${ARCH_SOURCES_DIR}/profiler.cpp gcinfodecoder.cpp + writebarriermanager.cpp ) if(CLR_CMAKE_HOST_UNIX) diff --git a/src/runtime/src/coreclr/vm/amd64/thunktemplates.S b/src/runtime/src/coreclr/vm/amd64/thunktemplates.S index 611556da202..ebb0f6f67f1 100644 --- a/src/runtime/src/coreclr/vm/amd64/thunktemplates.S +++ b/src/runtime/src/coreclr/vm/amd64/thunktemplates.S @@ -5,155 +5,9 @@ #include "unixasmmacros.inc" #include "asmconstants.h" -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE - -#define POINTER_SIZE 0x08 - -#define THUNKS_MAP_SIZE 0x4000 - -#define PAGE_SIZE 0x4000 -#define PAGE_SIZE_LOG2 14 - - -#define DATA_SLOT(stub, field, thunkSize, thunkTemplateName) C_FUNC(thunkTemplateName) + THUNKS_MAP_SIZE + stub##Data__##field + IN_PAGE_INDEX * thunkSize - -// ---------- -// StubPrecode -// ---------- - -#define STUB_PRECODE_CODESIZE 0x18 // 3 instructions, 13 bytes encoded + 11 bytes of padding -#define STUB_PRECODE_DATASIZE 0x18 // 2 qwords + a BYTE -.set STUB_PRECODE_NUM_THUNKS_PER_MAPPING,(THUNKS_MAP_SIZE / STUB_PRECODE_CODESIZE) - -.macro THUNKS_BLOCK_STUB_PRECODE - IN_PAGE_INDEX = 0 - .rept STUB_PRECODE_NUM_THUNKS_PER_MAPPING - - mov r10, [rip + DATA_SLOT(StubPrecode, SecretParam, STUB_PRECODE_CODESIZE, StubPrecodeCodeTemplate)] - jmp [rip + DATA_SLOT(StubPrecode, Target, STUB_PRECODE_CODESIZE, StubPrecodeCodeTemplate)] - // The above is 13 bytes - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY StubPrecodeCodeTemplate - THUNKS_BLOCK_STUB_PRECODE -LEAF_END_MARKED StubPrecodeCodeTemplate, _TEXT - -// ---------- -// FixupPrecode -// ---------- - -#define FIXUP_PRECODE_CODESIZE 0x18 -#define FIXUP_PRECODE_DATASIZE 0x18 // 3 qwords -.set FIXUP_PRECODE_NUM_THUNKS_PER_MAPPING,(THUNKS_MAP_SIZE / FIXUP_PRECODE_CODESIZE) - -.macro THUNKS_BLOCK_FIXUP_PRECODE - IN_PAGE_INDEX = 0 - .rept FIXUP_PRECODE_NUM_THUNKS_PER_MAPPING - - jmp [rip + DATA_SLOT(FixupPrecode, Target, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate)] - mov r10, [rip + DATA_SLOT(FixupPrecode, MethodDesc, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate)] - jmp [rip + DATA_SLOT(FixupPrecode, PrecodeFixupThunk, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate)] - // The above is 19 bytes - int 3 - int 3 - int 3 - int 3 - int 3 - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY FixupPrecodeCodeTemplate - THUNKS_BLOCK_FIXUP_PRECODE - // We need 16 bytes of padding to pad this out - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 -LEAF_END_MARKED FixupPrecodeCodeTemplate, _TEXT - -// ---------- -// CallCountingStub -// ---------- - -#define CALLCOUNTING_CODESIZE 0x18 -#define CALLCOUNTING_DATASIZE 0x18 // 3 qwords -.set CALLCOUNTING_NUM_THUNKS_PER_MAPPING, (THUNKS_MAP_SIZE / CALLCOUNTING_CODESIZE) -.macro THUNKS_BLOCK_CALLCOUNTING - IN_PAGE_INDEX = 0 - .rept CALLCOUNTING_NUM_THUNKS_PER_MAPPING - - mov rax,QWORD PTR [rip + DATA_SLOT(CallCountingStub, RemainingCallCountCell, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate)] - dec WORD PTR [rax] - je 0f - jmp QWORD PTR [rip + DATA_SLOT(CallCountingStub, TargetForMethod, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate)] - 0: - jmp QWORD PTR [rip + DATA_SLOT(CallCountingStub, TargetForThresholdReached, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate)] - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY CallCountingStubCodeTemplate - THUNKS_BLOCK_CALLCOUNTING - // We need 16 bytes of padding to pad this out - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 - int 3 -LEAF_END_MARKED CallCountingStubCodeTemplate, _TEXT - -#endif - // STUB_PAGE_SIZE must match the behavior of GetStubCodePageSize() on this architecture/os STUB_PAGE_SIZE = 16384 -#ifdef DATA_SLOT -#undef DATA_SLOT -#endif - #define DATA_SLOT(stub, field) C_FUNC(stub##Code) + STUB_PAGE_SIZE + stub##Data__##field LEAF_ENTRY StubPrecodeCode, _TEXT diff --git a/src/runtime/src/coreclr/vm/arm64/asmhelpers.S b/src/runtime/src/coreclr/vm/arm64/asmhelpers.S index 03e495606fa..bdb9d5d4280 100644 --- a/src/runtime/src/coreclr/vm/arm64/asmhelpers.S +++ b/src/runtime/src/coreclr/vm/arm64/asmhelpers.S @@ -180,83 +180,6 @@ PATCH_LABEL ThePreStubPatchLabel ret lr LEAF_END ThePreStubPatch, _TEXT -// void JIT_UpdateWriteBarrierState(bool skipEphemeralCheck, size_t writeableOffset) -// -// Update shadow copies of the various state info required for barrier -// -// State info is contained in a literal pool at the end of the function -// Placed in text section so that it is close enough to use ldr literal and still -// be relocatable. Eliminates need for PREPARE_EXTERNAL_VAR in hot code. -// -// Align and group state info together so it fits in a single cache line -// and each entry can be written atomically -// -LEAF_ENTRY JIT_UpdateWriteBarrierState, _TEXT - PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -16 - - // x0-x7, x10 will contain intended new state - // x8 will preserve skipEphemeralCheck - // x12 will be used for pointers - - mov x8, x0 - mov x9, x1 - - PREPARE_EXTERNAL_VAR g_card_table, x12 - ldr x0, [x12] - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - PREPARE_EXTERNAL_VAR g_card_bundle_table, x12 - ldr x1, [x12] -#endif - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - PREPARE_EXTERNAL_VAR g_write_watch_table, x12 - ldr x2, [x12] -#endif - - PREPARE_EXTERNAL_VAR g_ephemeral_low, x12 - ldr x3, [x12] - - PREPARE_EXTERNAL_VAR g_ephemeral_high, x12 - ldr x4, [x12] - - cbz x8, LOCAL_LABEL(EphemeralCheckEnabled) - movz x3, #0 - movn x4, #0 -LOCAL_LABEL(EphemeralCheckEnabled): - - PREPARE_EXTERNAL_VAR g_lowest_address, x12 - ldr x5, [x12] - - PREPARE_EXTERNAL_VAR g_highest_address, x12 - ldr x6, [x12] - -#ifdef WRITE_BARRIER_CHECK - PREPARE_EXTERNAL_VAR g_GCShadow, x12 - ldr x7, [x12] - - PREPARE_EXTERNAL_VAR g_GCShadowEnd, x12 - ldr x10, [x12] -#endif - - // Update wbs state - PREPARE_EXTERNAL_VAR JIT_WriteBarrier_Table_Loc, x12 - ldr x12, [x12] - add x12, x12, x9 - - stp x0, x1, [x12], 16 - stp x2, x3, [x12], 16 - stp x4, x5, [x12], 16 - str x6, [x12], 8 -#ifdef WRITE_BARRIER_CHECK - stp x7, x10, [x12], 16 -#endif - - - EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 16 - EPILOG_RETURN -LEAF_END JIT_UpdateWriteBarrierState - // ------------------------// ------------------------------------------------------------------ // __declspec(naked) void F_CALL_CONV JIT_WriteBarrier_Callable(Object **dst, Object* val) LEAF_ENTRY JIT_WriteBarrier_Callable, _TEXT diff --git a/src/runtime/src/coreclr/vm/arm64/asmhelpers.asm b/src/runtime/src/coreclr/vm/arm64/asmhelpers.asm index ce63e41a084..a5864d120da 100644 --- a/src/runtime/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/runtime/src/coreclr/vm/arm64/asmhelpers.asm @@ -248,84 +248,6 @@ ThePreStubPatchLabel ret lr LEAF_END -;----------------------------------------------------------------------------- -; void JIT_UpdateWriteBarrierState(bool skipEphemeralCheck, size_t writeableOffset) -; -; Update shadow copies of the various state info required for barrier -; -; State info is contained in a literal pool at the end of the function -; Placed in text section so that it is close enough to use ldr literal and still -; be relocatable. Eliminates need for PREPARE_EXTERNAL_VAR in hot code. -; -; Align and group state info together so it fits in a single cache line -; and each entry can be written atomically -; - LEAF_ENTRY JIT_UpdateWriteBarrierState - PROLOG_SAVE_REG_PAIR fp, lr, #-16! - - ; x0-x7, x10 will contain intended new state - ; x8 will preserve skipEphemeralCheck - ; x12 will be used for pointers - - mov x8, x0 - mov x9, x1 - - adrp x12, g_card_table - ldr x0, [x12, g_card_table] - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - adrp x12, g_card_bundle_table - ldr x1, [x12, g_card_bundle_table] -#endif - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - adrp x12, g_write_watch_table - ldr x2, [x12, g_write_watch_table] -#endif - - adrp x12, g_ephemeral_low - ldr x3, [x12, g_ephemeral_low] - - adrp x12, g_ephemeral_high - ldr x4, [x12, g_ephemeral_high] - - ; Check skipEphemeralCheck - cbz x8, EphemeralCheckEnabled - movz x3, #0 - movn x4, #0 - -EphemeralCheckEnabled - adrp x12, g_lowest_address - ldr x5, [x12, g_lowest_address] - - adrp x12, g_highest_address - ldr x6, [x12, g_highest_address] - -#ifdef WRITE_BARRIER_CHECK - adrp x12, $g_GCShadow - ldr x7, [x12, $g_GCShadow] - - adrp x12, $g_GCShadowEnd - ldr x10, [x12, $g_GCShadowEnd] -#endif - - ; Update wbs state - adrp x12, JIT_WriteBarrier_Table_Loc - ldr x12, [x12, JIT_WriteBarrier_Table_Loc] - add x12, x12, x9 - stp x0, x1, [x12], 16 - stp x2, x3, [x12], 16 - stp x4, x5, [x12], 16 - str x6, [x12], 8 -#ifdef WRITE_BARRIER_CHECK - stp x7, x10, [x12], 16 -#endif - - EPILOG_RESTORE_REG_PAIR fp, lr, #16! - EPILOG_RETURN - - LEAF_END JIT_UpdateWriteBarrierState - #ifdef FEATURE_COMINTEROP ; ------------------------------------------------------------------ diff --git a/src/runtime/src/coreclr/vm/arm64/patchedcode.S b/src/runtime/src/coreclr/vm/arm64/patchedcode.S index 0e223cbc1d3..2bea90942e6 100644 --- a/src/runtime/src/coreclr/vm/arm64/patchedcode.S +++ b/src/runtime/src/coreclr/vm/arm64/patchedcode.S @@ -3,6 +3,7 @@ #include "asmconstants.h" #include "unixasmmacros.inc" +#include "patchedcodeconstants.h" //----------------------------------------------------------------------------- // The following Macros help in WRITE_BARRIER Implementations @@ -85,6 +86,10 @@ WRITE_BARRIER_END JIT_CheckedWriteBarrier //----------------------------------------------------------------------------- // void JIT_WriteBarrier(Object** dst, Object* src) +// +// Empty function which at runtime is patched with one of the JIT_WriteBarrier_ +// functions below. +// // On entry: // x14 : the destination address (LHS of the assignment) // x15 : the object reference (RHS of the assignment) @@ -99,25 +104,82 @@ WRITE_BARRIER_END JIT_CheckedWriteBarrier // if you add more trashed registers. // WRITE_BARRIER_ENTRY JIT_WriteBarrier - stlr x15, [x14] + // This must be greater than the largest JIT_WriteBarrier_ function. + .space JIT_WriteBarrier_Size, 0 +WRITE_BARRIER_END JIT_WriteBarrier +//----------------------------------------------------------------------------- +// JIT_WriteBarrier_Table +// +// Patchable literal pool +// + .balign 64 // Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line +WRITE_BARRIER_ENTRY JIT_WriteBarrier_Table +PATCH_LABEL JIT_WriteBarrier_Patch_Label_CardTable + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_CardBundleTable + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_WriteWatchTable + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_Lower + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_Upper + .quad 0 +LOCAL_LABEL(wbs_lowest_address): +PATCH_LABEL JIT_WriteBarrier_Patch_Label_LowestAddress + .quad 0 +LOCAL_LABEL(wbs_highest_address): +PATCH_LABEL JIT_WriteBarrier_Patch_Label_HighestAddress + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_RegionToGeneration + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_RegionShr + .quad 0 #ifdef WRITE_BARRIER_CHECK +PATCH_LABEL JIT_WriteBarrier_Patch_Label_GCShadow + .quad 0 +PATCH_LABEL JIT_WriteBarrier_Patch_Label_GCShadowEnd + .quad 0 +#endif +WRITE_BARRIER_END JIT_WriteBarrier_Table + +// ------------------------------------------------------------------ +// End of the writeable code region +LEAF_ENTRY JIT_PatchedCodeLast, _TEXT + ret lr +LEAF_END JIT_PatchedCodeLast, _TEXT + + + +//----------------------------------------------------------------------------- +// The following Macros are used by the different JIT_WriteBarrier_ functions. +// +// + +.macro WRITE_BARRIER_ENTRY_STUB start +FIXUP_LABEL(\start): + stlr x15, [x14] +.endm + + +.macro WRITE_BARRIER_SHADOW_UPDATE_STUB start + #ifdef WRITE_BARRIER_CHECK // Update GC Shadow Heap // Do not perform the work if g_GCShadow is 0 - ldr x12, LOCAL_LABEL(wbs_GCShadow) - cbz x12, LOCAL_LABEL(ShadowUpdateEnd) + ldr x12, JIT_WriteBarrier_Offset_GCShadow + FIXUP_LABEL(\start) + cbz x12, LOCAL_LABEL(ShadowUpdateEnd\@) // Compute address of shadow heap location: // pShadow = g_GCShadow + (x14 - g_lowest_address) - ldr x17, LOCAL_LABEL(wbs_lowest_address) + ldr x17, JIT_WriteBarrier_Offset_LowestAddress + FIXUP_LABEL(\start) sub x17, x14, x17 add x12, x17, x12 // if (pShadow >= g_GCShadowEnd) goto end - ldr x17, LOCAL_LABEL(wbs_GCShadowEnd) + ldr x17, JIT_WriteBarrier_Offset_GCShadowEnd + FIXUP_LABEL(\start) cmp x12, x17 - bhs LOCAL_LABEL(ShadowUpdateEnd) + bhs LOCAL_LABEL(ShadowUpdateEnd\@) // *pShadow = x15 str x15, [x12] @@ -129,96 +191,305 @@ WRITE_BARRIER_ENTRY JIT_WriteBarrier // if ([x14] == x15) goto end ldr x17, [x14] cmp x17, x15 - beq LOCAL_LABEL(ShadowUpdateEnd) + beq LOCAL_LABEL(ShadowUpdateEnd\@) // *pShadow = INVALIDGCVALUE (0xcccccccd) movz x17, #0xcccd movk x17, #0xcccc, LSL #16 str x17, [x12] - -LOCAL_LABEL(ShadowUpdateEnd): #endif +LOCAL_LABEL(ShadowUpdateEnd\@): +.endm + +.macro WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB start, exit #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP // Update the write watch table if necessary - ldr x12, LOCAL_LABEL(wbs_sw_ww_table) - cbz x12, LOCAL_LABEL(CheckCardTable) + ldr x12, JIT_WriteBarrier_Offset_WriteWatchTable + FIXUP_LABEL(\start) add x12, x12, x14, lsr #0xc // SoftwareWriteWatch::AddressToTableByteIndexShift ldrb w17, [x12] - cbnz x17, LOCAL_LABEL(CheckCardTable) + cbnz x17, LOCAL_LABEL(WriteWatchForGCHeapEnd\@) mov w17, #0xFF strb w17, [x12] +LOCAL_LABEL(WriteWatchForGCHeapEnd\@): #endif +.endm -LOCAL_LABEL(CheckCardTable): - // Branch to Exit if the reference is not in the Gen0 heap - ldr x12, LOCAL_LABEL(wbs_ephemeral_low) - ldr x17, LOCAL_LABEL(wbs_ephemeral_high) + +.macro WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB start, exit + // Branch to Exit if the reference is not in the ephemeral generations. + ldr x12, JIT_WriteBarrier_Offset_Lower + FIXUP_LABEL(\start) + cmp x15, x12 + blo LOCAL_LABEL(\exit) +.endm + + +.macro WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB start, exit + // Branch to Exit if the reference is not in the ephemeral generations. + ldr x12, JIT_WriteBarrier_Offset_Lower + FIXUP_LABEL(\start) + ldr x17, JIT_WriteBarrier_Offset_Upper + FIXUP_LABEL(\start) cmp x15, x12 ccmp x15, x17, #0x2, hs - bhs LOCAL_LABEL(Exit) + bhs LOCAL_LABEL(\exit) +.endm + + +.macro WRITE_BARRIER_REGION_CHECK_STUB start, exit + // Calculate region generations + ldr x17, JIT_WriteBarrier_Offset_RegionToGeneration + FIXUP_LABEL(\start) + ldr w12, JIT_WriteBarrier_Offset_RegionShr + FIXUP_LABEL(\start) + lsr x15, x15, x12 + add x15, x15, x17 // x15 = (RHS >> wbs_region_shr) + wbs_region_to_generation_table + lsr x12, x14, x12 + add x12, x12, x17 // x12 = (LHS >> wbs_region_shr) + wbs_region_to_generation_table + // Check whether the region we are storing into is gen 0 - nothing to do in this case + ldrb w12, [x12] + cbz w12, LOCAL_LABEL(\exit) + + // Return if the new reference is not from old to young + ldrb w15, [x15] + cmp w15, w12 + bhs LOCAL_LABEL(\exit) +.endm + + +.macro WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB start, exit // Check if we need to update the card table - ldr x12, LOCAL_LABEL(wbs_card_table) + lsr w17, w14, 8 + and w17, w17, 7 + movz w15, 1 + lsl w17, w15, w17 // w17 = 1 << (LHS >> 8 && 7) + ldr x12, JIT_WriteBarrier_Offset_CardTable + FIXUP_LABEL(\start) add x15, x12, x14, lsr #11 - ldrb w12, [x15] + ldrb w12, [x15] // w12 = [(LHS >> 11) + g_card_table] + tst w12, w17 + bne LOCAL_LABEL(\exit) + + // Atomically update the card table + // Requires LSE, but the code is only compiled for 8.0 + .word 0x383131FF // stsetb w17, [x15] +.endm + + +.macro WRITE_BARRIER_CHECK_CARD_TABLE_STUB start, exit + // Check if we need to update the card table + ldr x12, JIT_WriteBarrier_Offset_CardTable + FIXUP_LABEL(\start) + add x15, x12, x14, lsr #11 + ldrb w12, [x15] // w12 = [(RHS >> 11) + g_card_table] cmp x12, 0xFF - beq LOCAL_LABEL(Exit) + beq LOCAL_LABEL(\exit) // Update the card table mov x12, 0xFF strb w12, [x15] +.endm + +.macro WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB start, exit #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES // Check if we need to update the card bundle table - ldr x12, LOCAL_LABEL(wbs_card_bundle_table) + ldr x12, JIT_WriteBarrier_Offset_CardBundleTable + FIXUP_LABEL(\start) add x15, x12, x14, lsr #21 ldrb w12, [x15] cmp x12, 0xFF - beq LOCAL_LABEL(Exit) + beq LOCAL_LABEL(\exit) // Update the card bundle mov x12, 0xFF strb w12, [x15] #endif +.endm + -LOCAL_LABEL(Exit): +.macro WRITE_BARRIER_RETURN_STUB exit +LOCAL_LABEL(\exit): // Increment by 8 to implement JIT_ByRefWriteBarrier contract. // TODO: Consider duplicating the logic to get rid of this redundant 'add' // for JIT_WriteBarrier/JIT_CheckedWriteBarrier add x14, x14, 8 ret lr -WRITE_BARRIER_END JIT_WriteBarrier +.endm - // Begin patchable literal pool - .balign 64 // Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line -WRITE_BARRIER_ENTRY JIT_WriteBarrier_Table -LOCAL_LABEL(wbs_begin): -LOCAL_LABEL(wbs_card_table): - .quad 0 -LOCAL_LABEL(wbs_card_bundle_table): - .quad 0 -LOCAL_LABEL(wbs_sw_ww_table): - .quad 0 -LOCAL_LABEL(wbs_ephemeral_low): - .quad 0 -LOCAL_LABEL(wbs_ephemeral_high): - .quad 0 -LOCAL_LABEL(wbs_lowest_address): - .quad 0 -LOCAL_LABEL(wbs_highest_address): - .quad 0 -#ifdef WRITE_BARRIER_CHECK -LOCAL_LABEL(wbs_GCShadow): - .quad 0 -LOCAL_LABEL(wbs_GCShadowEnd): - .quad 0 -#endif -WRITE_BARRIER_END JIT_WriteBarrier_Table +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_PreGrow64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not update the write watch table +// Does not check wbs_ephemeral_high +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_PreGrow64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_PreGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_PreGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB Start_PreGrow64, Exit_PreGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_PreGrow64, Exit_PreGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_PreGrow64, Exit_PreGrow64 + WRITE_BARRIER_RETURN_STUB Exit_PreGrow64 +LEAF_END_MARKED JIT_WriteBarrier_PreGrow64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_PostGrow64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not update the write watch table +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_PostGrow64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_PostGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_PostGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_PostGrow64, Exit_PostGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_PostGrow64, Exit_PostGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_PostGrow64, Exit_PostGrow64 + WRITE_BARRIER_RETURN_STUB Exit_PostGrow64 +LEAF_END_MARKED JIT_WriteBarrier_PostGrow64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_SVR64(Object** dst, Object* src) +// +// SVR GC has multiple heaps, so it cannot provide one single ephemeral region to bounds check +// against, so we just skip the bounds checking all together and do our card table update unconditionally. +// +// Skipped functionality: +// Does not update the write watch table +// Does not check wbs_ephemeral_high or wbs_ephemeral_low +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_SVR64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_SVR64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_SVR64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_SVR64, Exit_SVR64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_SVR64, Exit_SVR64 + WRITE_BARRIER_RETURN_STUB Exit_SVR64 +LEAF_END_MARKED JIT_WriteBarrier_SVR64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_Byte_Region64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not update the write watch table +// Bitwise updates for region checks +// +LEAF_ENTRY JIT_WriteBarrier_Byte_Region64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_Byte_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_Byte_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_Byte_Region64, Exit_Byte_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Start_Byte_Region64, Exit_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_Byte_Region64, Exit_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_Byte_Region64, Exit_Byte_Region64 + WRITE_BARRIER_RETURN_STUB Exit_Byte_Region64 +LEAF_END_MARKED JIT_WriteBarrier_Byte_Region64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_Bit_Region64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not update the write watch table +// Does not call check card table stub +// +LEAF_ENTRY JIT_WriteBarrier_Bit_Region64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_Bit_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_Bit_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_Bit_Region64, Exit_Bit_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Start_Bit_Region64, Exit_Bit_Region64 + WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB Start_Bit_Region64, Exit_Bit_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_Bit_Region64, Exit_Bit_Region64 + WRITE_BARRIER_RETURN_STUB Exit_Bit_Region64 +LEAF_END_MARKED JIT_WriteBarrier_Bit_Region64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_WriteWatch_PreGrow64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not check wbs_ephemeral_high +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_WriteWatch_PreGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_WriteWatch_PreGrow64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB Start_WriteWatch_PreGrow64, Exit_WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB Start_WriteWatch_PreGrow64, Exit_WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_WriteWatch_PreGrow64, Exit_WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_WriteWatch_PreGrow64, Exit_WriteWatch_PreGrow64 + WRITE_BARRIER_RETURN_STUB Exit_WriteWatch_PreGrow64 +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_WriteWatch_PostGrow64(Object** dst, Object* src) +// +// Skipped functionality: +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_WriteWatch_PostGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_WriteWatch_PostGrow64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB Start_WriteWatch_PostGrow64, Exit_WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_WriteWatch_PostGrow64, Exit_WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_WriteWatch_PostGrow64, Exit_WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_WriteWatch_PostGrow64, Exit_WriteWatch_PostGrow64 + WRITE_BARRIER_RETURN_STUB Exit_WriteWatch_PostGrow64 +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_WriteWatch_SVR64(Object** dst, Object* src) +// +// SVR GC has multiple heaps, so it cannot provide one single ephemeral region to bounds check +// against, so we just skip the bounds checking all together and do our card table update unconditionally. +// +// Skipped functionality: +// Does not check wbs_ephemeral_high or wbs_ephemeral_low +// No region checks +// +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_WriteWatch_SVR64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_WriteWatch_SVR64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB Start_WriteWatch_SVR64, Exit_WriteWatch_SVR64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_WriteWatch_SVR64, Exit_WriteWatch_SVR64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_WriteWatch_SVR64, Exit_WriteWatch_SVR64 + WRITE_BARRIER_RETURN_STUB Exit_WriteWatch_SVR64 +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_SVR64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_WriteWatch_Byte_Region64(Object** dst, Object* src) +// +// Skipped functionality: +// Bitwise updates for region checks +// +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_WriteWatch_Byte_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_WriteWatch_Byte_Region64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB Start_WriteWatch_Byte_Region64, Exit_WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_WriteWatch_Byte_Region64, Exit_WriteWatch_Byte_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Start_WriteWatch_Byte_Region64, Exit_WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Start_WriteWatch_Byte_Region64, Exit_WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_WriteWatch_Byte_Region64, Exit_WriteWatch_Byte_Region64 + WRITE_BARRIER_RETURN_STUB Exit_WriteWatch_Byte_Region64 +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + + +//----------------------------------------------------------------------------- +// void JIT_WriteBarrier_WriteWatch_Bit_Region64(Object** dst, Object* src) +// +// Skipped functionality: +// Does not call check card table stub +// +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT + WRITE_BARRIER_ENTRY_STUB Start_WriteWatch_Bit_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Start_WriteWatch_Bit_Region64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB Start_WriteWatch_Bit_Region64, Exit_WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Start_WriteWatch_Bit_Region64, Exit_WriteWatch_Bit_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Start_WriteWatch_Bit_Region64, Exit_WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB Start_WriteWatch_Bit_Region64, Exit_WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Start_WriteWatch_Bit_Region64, Exit_WriteWatch_Bit_Region64 + WRITE_BARRIER_RETURN_STUB Exit_WriteWatch_Bit_Region64 +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT -// ------------------------------------------------------------------ -// End of the writeable code region -LEAF_ENTRY JIT_PatchedCodeLast, _TEXT - ret lr -LEAF_END JIT_PatchedCodeLast, _TEXT diff --git a/src/runtime/src/coreclr/vm/arm64/patchedcode.asm b/src/runtime/src/coreclr/vm/arm64/patchedcode.asm index 454b8cac0c4..500f1044d48 100644 --- a/src/runtime/src/coreclr/vm/arm64/patchedcode.asm +++ b/src/runtime/src/coreclr/vm/arm64/patchedcode.asm @@ -4,6 +4,7 @@ #include "ksarm64.h" #include "asmconstants.h" #include "asmmacros.h" +#include "patchedcodeconstants.h" ;;like TEXTAREA, but with 64 byte alignment so that we can align the patchable pool below to 64 without warning AREA |.text|,ALIGN=6,CODE,READONLY @@ -38,32 +39,6 @@ ret lr LEAF_END - ; Begin patchable literal pool - ALIGN 64 ; Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line - WRITE_BARRIER_ENTRY JIT_WriteBarrier_Table -wbs_begin -wbs_card_table - DCQ 0 -wbs_card_bundle_table - DCQ 0 -wbs_sw_ww_table - DCQ 0 -wbs_ephemeral_low - DCQ 0 -wbs_ephemeral_high - DCQ 0 -wbs_lowest_address - DCQ 0 -wbs_highest_address - DCQ 0 -#ifdef WRITE_BARRIER_CHECK -wbs_GCShadow - DCQ 0 -wbs_GCShadowEnd - DCQ 0 -#endif - WRITE_BARRIER_END JIT_WriteBarrier_Table - ;----------------------------------------------------------------------------- ; void JIT_ByRefWriteBarrier ; On entry: @@ -117,6 +92,9 @@ NotInHeap ;----------------------------------------------------------------------------- ; void JIT_WriteBarrier(Object** dst, Object* src) +; +; Empty function which at runtime is patched with one of the JIT_WriteBarrier_ +; functions below. ; On entry: ; x14 : the destination address (LHS of the assignment) ; x15 : the object reference (RHS of the assignment) @@ -131,107 +109,395 @@ NotInHeap ; if you add more trashed registers. ; WRITE_BARRIER_ENTRY JIT_WriteBarrier - stlr x15, [x14] +; This must be greater than the largest JIT_WriteBarrier_ function. + space (232*4), 0 + WRITE_BARRIER_END JIT_WriteBarrier + ; Begin patchable literal pool + ALIGN 64 ; Align to power of two at least as big as patchable literal pool so that it fits optimally in cache line + WRITE_BARRIER_ENTRY JIT_WriteBarrier_Table + PATCH_LABEL JIT_WriteBarrier_Patch_Label_CardTable + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_CardBundleTable + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_WriteWatchTable + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_Lower + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_Upper + DCQ 0 +wbs_lowest_address + PATCH_LABEL JIT_WriteBarrier_Patch_Label_LowestAddress + DCQ 0 +wbs_highest_address + PATCH_LABEL JIT_WriteBarrier_Patch_Label_HighestAddress + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_RegionToGeneration + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_RegionShr + DCQ 0 #ifdef WRITE_BARRIER_CHECK + PATCH_LABEL JIT_WriteBarrier_Patch_Label_GCShadow + DCQ 0 + PATCH_LABEL JIT_WriteBarrier_Patch_Label_GCShadowEnd + DCQ 0 +#endif + WRITE_BARRIER_END JIT_WriteBarrier_Table + +; ------------------------------------------------------------------ +; End of the writeable code region + LEAF_ENTRY JIT_PatchedCodeLast + ret lr + LEAF_END + + + +;----------------------------------------------------------------------------- +; The following Macros are used by the different JIT_WriteBarrier_ functions. +; +; + + MACRO + WRITE_BARRIER_ENTRY_STUB $name +start$name + stlr x15, [x14] + MEND + + + MACRO + WRITE_BARRIER_SHADOW_UPDATE_STUB $name + #ifdef WRITE_BARRIER_CHECK ; Update GC Shadow Heap ; Do not perform the work if g_GCShadow is 0 - ldr x12, wbs_GCShadow - cbz x12, ShadowUpdateEnd + ldr x12, JIT_WriteBarrier_Offset_GCShadow + start$name + cbz x12, ShadowUpdateEnd$name ; Compute address of shadow heap location: - ; pShadow = $g_GCShadow + (x14 - g_lowest_address) - ldr x17, wbs_lowest_address - sub x17, x14, x17 - add x12, x17, x12 + ; pShadow = g_GCShadow + (x14 - g_lowest_address) + ldr x17, JIT_WriteBarrier_Offset_LowestAddress + start$name + sub x17, x14, x17 + add x12, x17, x12 - ; if (pShadow >= $g_GCShadowEnd) goto end - ldr x17, wbs_GCShadowEnd - cmp x12, x17 - bhs ShadowUpdateEnd + ; if (pShadow >= g_GCShadowEnd) goto end + ldr x17, JIT_WriteBarrier_Offset_GCShadowEnd + start$name + cmp x12, x17 + bhs ShadowUpdateEnd$name ; *pShadow = x15 - str x15, [x12] + str x15, [x12] ; Ensure that the write to the shadow heap occurs before the read from the GC heap so that race ; conditions are caught by INVALIDGCVALUE. - dmb ish + dmb ish ; if ([x14] == x15) goto end - ldr x17, [x14] - cmp x17, x15 - beq ShadowUpdateEnd + ldr x17, [x14] + cmp x17, x15 + beq ShadowUpdateEnd$name ; *pShadow = INVALIDGCVALUE (0xcccccccd) - movz x17, #0xcccd - movk x17, #0xcccc, LSL #16 - str x17, [x12] - -ShadowUpdateEnd -#endif + movz x17, #0xcccd + movk x17, #0xcccc, LSL #16 + str x17, [x12] + #endif +ShadowUpdateEnd$name + MEND -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + MACRO + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB $name + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP ; Update the write watch table if necessary - ldr x12, wbs_sw_ww_table - cbz x12, CheckCardTable - add x12, x12, x14, LSR #0xC // SoftwareWriteWatch::AddressToTableByteIndexShift - ldrb w17, [x12] - cbnz x17, CheckCardTable - mov w17, 0xFF - strb w17, [x12] -#endif + ldr x12, JIT_WriteBarrier_Offset_WriteWatchTable + start$name + ; SoftwareWriteWatch::AddressToTableByteIndexShift + add x12, x12, x14, lsr #0xc + ldrb w17, [x12] + cbnz x17, WriteWatchForGCHeapEnd$name + mov w17, #0xFF + strb w17, [x12] +WriteWatchForGCHeapEnd$name + #endif + MEND -CheckCardTable - ; Branch to Exit if the reference is not in the Gen0 heap - ldr x12, wbs_ephemeral_low - ldr x17, wbs_ephemeral_high - cmp x15, x12 - ccmp x15, x17, #0x2, hs - bhs Exit + MACRO + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB $name + ; Branch to Exit if the reference is not in the ephemeral generations. + ldr x12, JIT_WriteBarrier_Offset_Lower + start$name + cmp x15, x12 + blo exit$name + MEND - ; Check if we need to update the card table - ldr x12, wbs_card_table + MACRO + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB $name + ; Branch to Exit if the reference is not in the ephemeral generations. + ldr x12, JIT_WriteBarrier_Offset_Lower + start$name + ldr x17, JIT_WriteBarrier_Offset_Upper + start$name + cmp x15, x12 + ccmp x15, x17, #0x2, hs + bhs exit$name + MEND + + MACRO + WRITE_BARRIER_REGION_CHECK_STUB $name + ; Calculate region generations + ldr x17, JIT_WriteBarrier_Offset_RegionToGeneration + start$name + ldr w12, JIT_WriteBarrier_Offset_RegionShr + start$name + lsr x15, x15, x12 + add x15, x15, x17 ; x15 = (RHS >> wbs_region_shr) + wbs_region_to_generation_table + lsr x12, x14, x12 + add x12, x12, x17 ; x12 = (LHS >> wbs_region_shr) + wbs_region_to_generation_table + + ; Check whether the region we are storing into is gen 0 - nothing to do in this case + ldrb w12, [x12] + cbz w12, exit$name + + ; Return if the new reference is not from old to young + ldrb w15, [x15] + cmp w15, w12 + bhs exit$name + MEND - ; x15 := pointer into card table - add x15, x12, x14, lsr #11 + MACRO + WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB $name + ; Check if we need to update the card table + lsr w17, w14, 8 + and w17, w17, 7 + movz w15, 1 + lsl w17, w15, w17 ; w17 = 1 << (LHS >> 8 && 7) + ldr x12, JIT_WriteBarrier_Offset_CardTable + start$name + add x15, x12, x14, lsr #11 + ldrb w12, [x15] ; w12 = [(LHS >> 11) + g_card_table] + tst w12, w17 + bne exit$name + + ; Atomically update the card table + ; Requires LSE, but the code is only compiled for 8.0 + ; stsetb w17, [x15] + DCD 0x383131FF + MEND - ldrb w12, [x15] - cmp x12, 0xFF - beq Exit + MACRO + WRITE_BARRIER_CHECK_CARD_TABLE_STUB $name + ; Check if we need to update the card table + ldr x12, JIT_WriteBarrier_Offset_CardTable + start$name + add x15, x12, x14, lsr #11 + ; w12 = [(RHS >> 11) + g_card_table] + ldrb w12, [x15] + cmp x12, 0xFF + beq exit$name ; Update the card table - mov x12, 0xFF - strb w12, [x15] + mov x12, 0xFF + strb w12, [x15] + MEND -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + MACRO + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB $name + #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES ; Check if we need to update the card bundle table - ldr x12, wbs_card_bundle_table - - ; x15 := pointer into card bundle table - add x15, x12, x14, lsr #21 - - ldrb w12, [x15] - cmp x12, 0xFF - beq Exit - - mov x12, 0xFF - strb w12, [x15] -#endif + ldr x12, JIT_WriteBarrier_Offset_CardBundleTable + start$name + add x15, x12, x14, lsr #21 + ldrb w12, [x15] + cmp x12, 0xFF + beq exit$name + + ; Update the card bundle + mov x12, 0xFF + strb w12, [x15] + #endif + MEND -Exit + MACRO + WRITE_BARRIER_RETURN_STUB $name +exit$name ; Increment by 8 to implement JIT_ByRefWriteBarrier contract. ; TODO: Consider duplicating the logic to get rid of this redundant 'add' ; for JIT_WriteBarrier/JIT_CheckedWriteBarrier - add x14, x14, 8 - ret lr - WRITE_BARRIER_END JIT_WriteBarrier + add x14, x14, 8 + ret lr + MEND + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_PreGrow64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not update the write watch table + ; Does not check wbs_ephemeral_high + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_PreGrow64 + WRITE_BARRIER_ENTRY_STUB PreGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB PreGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB PreGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB PreGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB PreGrow64 + WRITE_BARRIER_RETURN_STUB PreGrow64 + WRITE_BARRIER_END JIT_WriteBarrier_PreGrow64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_PostGrow64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not update the write watch table + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_PostGrow64 + WRITE_BARRIER_ENTRY_STUB PostGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB PostGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB PostGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB PostGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB PostGrow64 + WRITE_BARRIER_RETURN_STUB PostGrow64 + WRITE_BARRIER_END JIT_WriteBarrier_PostGrow64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_SVR64(Object** dst, Object* src) + ; + ; SVR GC has multiple heaps, so it cannot provide one single ephemeral region to bounds check + ; against, so we just skip the bounds checking all together and do our card table update unconditionally. + ; + ; Skipped functionality: + ; Does not update the write watch table + ; Does not check wbs_ephemeral_high or wbs_ephemeral_low + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_SVR64 + WRITE_BARRIER_ENTRY_STUB SVR64 + WRITE_BARRIER_SHADOW_UPDATE_STUB SVR64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB SVR64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB SVR64 + WRITE_BARRIER_RETURN_STUB SVR64 + WRITE_BARRIER_END JIT_WriteBarrier_SVR64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_Byte_Region64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not update the write watch table + ; Bitwise updates for region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_Byte_Region64 + WRITE_BARRIER_ENTRY_STUB Byte_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Byte_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Byte_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Byte_Region64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB Byte_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Byte_Region64 + WRITE_BARRIER_RETURN_STUB Byte_Region64 + WRITE_BARRIER_END JIT_WriteBarrier_Byte_Region64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_Bit_Region64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not update the write watch table + ; Does not call check card table stub + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_Bit_Region64 + WRITE_BARRIER_ENTRY_STUB Bit_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB Bit_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB Bit_Region64 + WRITE_BARRIER_REGION_CHECK_STUB Bit_Region64 + WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB Bit_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB Bit_Region64 + WRITE_BARRIER_RETURN_STUB Bit_Region64 + WRITE_BARRIER_END JIT_WriteBarrier_Bit_Region64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_WriteWatch_PreGrow64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not check wbs_ephemeral_high + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_WriteWatch_PreGrow64 + WRITE_BARRIER_ENTRY_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_RETURN_STUB WriteWatch_PreGrow64 + WRITE_BARRIER_END JIT_WriteBarrier_WriteWatch_PreGrow64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_WriteWatch_PostGrow64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_WriteWatch_PostGrow64 + WRITE_BARRIER_ENTRY_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_SHADOW_UPDATE_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_RETURN_STUB WriteWatch_PostGrow64 + WRITE_BARRIER_END JIT_WriteBarrier_WriteWatch_PostGrow64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_WriteWatch_SVR64(Object** dst, Object* src) + ; + ; SVR GC has multiple heaps, so it cannot provide one single ephemeral region to bounds check + ; against, so we just skip the bounds checking all together and do our card table update unconditionally. + ; + ; Skipped functionality: + ; Does not check wbs_ephemeral_high or wbs_ephemeral_low + ; No region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_WriteWatch_SVR64 + WRITE_BARRIER_ENTRY_STUB WriteWatch_SVR64 + WRITE_BARRIER_SHADOW_UPDATE_STUB WriteWatch_SVR64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB WriteWatch_SVR64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB WriteWatch_SVR64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB WriteWatch_SVR64 + WRITE_BARRIER_RETURN_STUB WriteWatch_SVR64 + WRITE_BARRIER_END JIT_WriteBarrier_WriteWatch_SVR64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_WriteWatch_Byte_Region64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Bitwise updates for region checks + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_WriteWatch_Byte_Region64 + WRITE_BARRIER_ENTRY_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_REGION_CHECK_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_TABLE_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_RETURN_STUB WriteWatch_Byte_Region64 + WRITE_BARRIER_END JIT_WriteBarrier_WriteWatch_Byte_Region64 + + + ;----------------------------------------------------------------------------- + ; void JIT_WriteBarrier_WriteWatch_Bit_Region64(Object** dst, Object* src) + ; + ; Skipped functionality: + ; Does not call check card table stub + ; + WRITE_BARRIER_ENTRY JIT_WriteBarrier_WriteWatch_Bit_Region64 + WRITE_BARRIER_ENTRY_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_SHADOW_UPDATE_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_WRITE_WATCH_FOR_GC_HEAP_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_EPHEMERAL_LOW_AND_HIGH_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_REGION_CHECK_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_BIT_REGIONS_CARD_TABLE_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_CHECK_CARD_BUNDLE_TABLE_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_RETURN_STUB WriteWatch_Bit_Region64 + WRITE_BARRIER_END JIT_WriteBarrier_WriteWatch_Bit_Region64 -; ------------------------------------------------------------------ -; End of the writeable code region - LEAF_ENTRY JIT_PatchedCodeLast - ret lr - LEAF_END ; Must be at very end of file END diff --git a/src/runtime/src/coreclr/vm/arm64/patchedcodeconstants.h b/src/runtime/src/coreclr/vm/arm64/patchedcodeconstants.h new file mode 100644 index 00000000000..1b4a32d210b --- /dev/null +++ b/src/runtime/src/coreclr/vm/arm64/patchedcodeconstants.h @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// =========================================================================== +// File: patchedcodeconstants.h +// =========================================================================== + +#ifndef PATCHEDCODECONSTANTS_H +#define PATCHEDCODECONSTANTS_H + +// These are fixed constants becuase MacOS doesn't allow label arithmetic in +// LDR instructions. Asserts in writebarriermanager CALC_TABLE_LOCATION ensure +// the values are correct. + +#define JIT_WriteBarrier_Size 0x3a0 + +#ifdef TARGET_WINDOWS +#define JIT_WriteBarrier_Table_Offset (0x30 + JIT_WriteBarrier_Size) +#else +#define JIT_WriteBarrier_Table_Offset (0x2c + JIT_WriteBarrier_Size) +#endif + +#define JIT_WriteBarrier_Offset_CardTable (0x0 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_CardBundleTable (0x8 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_WriteWatchTable (0x10 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_Lower (0x18 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_Upper (0x20 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_LowestAddress (0x28 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_HighestAddress (0x30 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_RegionToGeneration (0x38 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_RegionShr (0x40 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_GCShadow (0x48 + JIT_WriteBarrier_Table_Offset) +#define JIT_WriteBarrier_Offset_GCShadowEnd (0x50 + JIT_WriteBarrier_Table_Offset) + +#endif // PATCHEDCODECONSTANTS_H \ No newline at end of file diff --git a/src/runtime/src/coreclr/vm/arm64/stubs.cpp b/src/runtime/src/coreclr/vm/arm64/stubs.cpp index bc2ae8a0607..43986504527 100644 --- a/src/runtime/src/coreclr/vm/arm64/stubs.cpp +++ b/src/runtime/src/coreclr/vm/arm64/stubs.cpp @@ -13,6 +13,7 @@ #include "virtualcallstub.h" #include "jitinterface.h" #include "ecall.h" +#include "writebarriermanager.h" #ifdef FEATURE_PERFMAP #include "perfmap.h" @@ -840,25 +841,6 @@ void emitCOMStubCall (ComCallMethodDesc *pCOMMethodRX, ComCallMethodDesc *pCOMMe #endif // FEATURE_COMINTEROP #if !defined(DACCESS_COMPILE) -EXTERN_C void JIT_UpdateWriteBarrierState(bool skipEphemeralCheck, size_t writeableOffset); - -extern "C" void STDCALL JIT_PatchedCodeStart(); -extern "C" void STDCALL JIT_PatchedCodeLast(); - -static void UpdateWriteBarrierState(bool skipEphemeralCheck) -{ - if (IsWriteBarrierCopyEnabled()) - { - BYTE *writeBarrierCodeStart = GetWriteBarrierCodeLocation((void*)JIT_PatchedCodeStart); - BYTE *writeBarrierCodeStartRW = writeBarrierCodeStart; - ExecutableWriterHolderNoLog writeBarrierWriterHolder; - { - writeBarrierWriterHolder.AssignExecutableWriterHolder(writeBarrierCodeStart, (BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart); - writeBarrierCodeStartRW = writeBarrierWriterHolder.GetRW(); - } - JIT_UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap(), writeBarrierCodeStartRW - writeBarrierCodeStart); - } -} void InitJITHelpers1() { @@ -866,6 +848,8 @@ void InitJITHelpers1() _ASSERTE(g_SystemInfo.dwNumberOfProcessors != 0); + g_WriteBarrierManager.Initialize(); + // Allocation helpers, faster but non-logging if (!((TrackAllocationsEnabled()) || (LoggingOn(LF_GCALLOC, LL_INFO10)) @@ -884,13 +868,8 @@ void InitJITHelpers1() ECall::DynamicallyAssignFCallImpl(GetEEFuncEntryPoint(AllocateString_MP_FastPortable), ECall::FastAllocateString); } } - - UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap()); } - -#else -void UpdateWriteBarrierState(bool) {} #endif // !defined(DACCESS_COMPILE) #ifdef TARGET_WINDOWS @@ -1011,37 +990,6 @@ LONG CLRNoCatchHandler(EXCEPTION_POINTERS* pExceptionInfo, PVOID pv) return EXCEPTION_CONTINUE_SEARCH; } -void FlushWriteBarrierInstructionCache() -{ - // this wouldn't be called in arm64, just to comply with gchelpers.h -} - -int StompWriteBarrierEphemeral(bool isRuntimeSuspended) -{ - UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap()); - return SWB_PASS; -} - -int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) -{ - UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap()); - return SWB_PASS; -} - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -int SwitchToWriteWatchBarrier(bool isRuntimeSuspended) -{ - UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap()); - return SWB_PASS; -} - -int SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended) -{ - UpdateWriteBarrierState(GCHeapUtilities::IsServerHeap()); - return SWB_PASS; -} -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - #ifdef DACCESS_COMPILE BOOL GetAnyThunkTarget (T_CONTEXT *pctx, TADDR *pTarget, TADDR *pTargetMethodDesc) { diff --git a/src/runtime/src/coreclr/vm/arm64/thunktemplates.S b/src/runtime/src/coreclr/vm/arm64/thunktemplates.S index bbbc4908547..df2abf7c29e 100644 --- a/src/runtime/src/coreclr/vm/arm64/thunktemplates.S +++ b/src/runtime/src/coreclr/vm/arm64/thunktemplates.S @@ -4,117 +4,6 @@ #include "unixasmmacros.inc" #include "asmconstants.h" -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE -#define POINTER_SIZE 0x08 -// Since Arm64 supports 4KB, 16KB and 64KB page sizes, as the templates is only defined for 16KB page size, this cannot be used -// in a general purpose Linux environment. However it CAN be used on Apple platforms, which specify that 16KB is the system standard -// page size. - -#define THUNKS_MAP_SIZE 0x4000 - -#define PAGE_SIZE 0x4000 -#define PAGE_SIZE_LOG2 14 - - -#define DATA_SLOT(stub, field, thunkSize, thunkTemplateName) C_FUNC(thunkTemplateName) + THUNKS_MAP_SIZE + stub##Data__##field + IN_PAGE_INDEX * thunkSize - -// ---------- -// StubPrecode -// ---------- - -#define STUB_PRECODE_CODESIZE 0x18 // 3 instructions, 4 bytes each (and we also have 12 bytes of padding) -#define STUB_PRECODE_DATASIZE 0x18 // 2 qwords + 1 byte -.set STUB_PRECODE_NUM_THUNKS_PER_MAPPING, (THUNKS_MAP_SIZE / STUB_PRECODE_CODESIZE) - -.macro THUNKS_BLOCK_STUB_PRECODE - IN_PAGE_INDEX = 0 - .rept STUB_PRECODE_NUM_THUNKS_PER_MAPPING - - ldr x10, DATA_SLOT(StubPrecode, Target, STUB_PRECODE_CODESIZE, StubPrecodeCodeTemplate) - ldr x12, DATA_SLOT(StubPrecode, SecretParam, STUB_PRECODE_CODESIZE, StubPrecodeCodeTemplate) - br x10 - - brk 0xf000 // Stubs need to be 24-byte in size to allow for the data to be 2 pointers + 1 byte - brk 0xf000 // Stubs need to be 24-byte in size to allow for the data to be 2 pointers + 1 byte - brk 0xf000 // Stubs need to be 24-byte in size to allow for the data to be 2 pointers + 1 byte - - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY StubPrecodeCodeTemplate - THUNKS_BLOCK_STUB_PRECODE -LEAF_END_MARKED StubPrecodeCodeTemplate, _TEXT - -// ---------- -// FixupPrecode -// ---------- - -#define FIXUP_PRECODE_CODESIZE 0x18 // 5 instructions, 4 bytes each (and we also have 4 bytes of padding) -#define FIXUP_PRECODE_DATASIZE 0x18 // 3 qwords -.set FIXUP_PRECODE_NUM_THUNKS_PER_MAPPING,(THUNKS_MAP_SIZE / FIXUP_PRECODE_CODESIZE) - -.macro THUNKS_BLOCK_FIXUP_PRECODE - IN_PAGE_INDEX = 0 - .rept FIXUP_PRECODE_NUM_THUNKS_PER_MAPPING - - ldr x11, DATA_SLOT(FixupPrecode, Target, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate) - br x11 - ldr x12, DATA_SLOT(FixupPrecode, MethodDesc, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate) - ldr x11, DATA_SLOT(FixupPrecode, PrecodeFixupThunk, FIXUP_PRECODE_CODESIZE, FixupPrecodeCodeTemplate) - br x11 - brk 0xf000 // Stubs need to be 24-byte in size to allow for the data to be 3 pointers - - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY FixupPrecodeCodeTemplate - THUNKS_BLOCK_FIXUP_PRECODE -LEAF_END_MARKED FixupPrecodeCodeTemplate, _TEXT - -// ---------- -// CallCountingStub -// ---------- - -#define CALLCOUNTING_CODESIZE 0x28 // 5 instructions, 4 bytes each (and we also have 4 bytes of padding) -#define CALLCOUNTING_DATASIZE 0x18 // 3 qwords -.set CALLCOUNTING_NUM_THUNKS_PER_MAPPING, (THUNKS_MAP_SIZE / CALLCOUNTING_CODESIZE) - -.macro THUNKS_BLOCK_CALLCOUNTING - IN_PAGE_INDEX = 0 - .rept CALLCOUNTING_NUM_THUNKS_PER_MAPPING - - ldr x9, DATA_SLOT(CallCountingStub, RemainingCallCountCell, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate) - ldrh w10, [x9] - subs w10, w10, #1 - strh w10, [x9] - beq 0f - ldr x9, DATA_SLOT(CallCountingStub, TargetForMethod, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate) - br x9 -0: - ldr x10, DATA_SLOT(CallCountingStub, TargetForThresholdReached, CALLCOUNTING_CODESIZE, CallCountingStubCodeTemplate) - br x10 - brk 0xf000 // Stubs need to be 40-byte in size to allow for the data to be pointer aligned - - IN_PAGE_INDEX = IN_PAGE_INDEX + 1 - .endr -.endm - - .text - .p2align PAGE_SIZE_LOG2 -LEAF_ENTRY CallCountingStubCodeTemplate - THUNKS_BLOCK_CALLCOUNTING -LEAF_END_MARKED CallCountingStubCodeTemplate, _TEXT -#endif - -#ifdef DATA_SLOT -#undef DATA_SLOT -#endif #define DATA_SLOT(stub, field) . - (. - C_FUNC(stub##Code\STUB_PAGE_SIZE)) + \STUB_PAGE_SIZE + stub##Data__##field .irp STUB_PAGE_SIZE, 16384, 32768, 65536 diff --git a/src/runtime/src/coreclr/vm/callcounting.cpp b/src/runtime/src/coreclr/vm/callcounting.cpp index f5168fc0f79..0f26b7d4090 100644 --- a/src/runtime/src/coreclr/vm/callcounting.cpp +++ b/src/runtime/src/coreclr/vm/callcounting.cpp @@ -293,14 +293,6 @@ void (*CallCountingStub::CallCountingStubCode)(); #ifndef DACCESS_COMPILE -static InterleavedLoaderHeapConfig s_callCountingHeapConfig; - -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE -extern "C" void CallCountingStubCodeTemplate(); -#else -#define CallCountingStubCodeTemplate NULL -#endif - void CallCountingStub::StaticInitialize() { #if defined(TARGET_ARM64) && defined(TARGET_UNIX) @@ -318,22 +310,14 @@ void CallCountingStub::StaticInitialize() EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("Unsupported OS page size")); } #undef ENUM_PAGE_SIZE - - if (CallCountingStubCodeTemplate != NULL && pageSize != 0x4000) - { - // This should fail if the template is used on a platform which doesn't support the supported page size for templates - ThrowHR(COR_E_EXECUTIONENGINE); - } #else _ASSERTE((SIZE_T)((BYTE*)CallCountingStubCode_End - (BYTE*)CallCountingStubCode) <= CallCountingStub::CodeSize); #endif - - InitializeLoaderHeapConfig(&s_callCountingHeapConfig, CallCountingStub::CodeSize, (void*)CallCountingStubCodeTemplate, CallCountingStub::GenerateCodePage); } #endif // DACCESS_COMPILE -void CallCountingStub::GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t pageSize) +void CallCountingStub::GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T pageSize) { #ifdef TARGET_X86 int totalCodeSize = (pageSize / CallCountingStub::CodeSize) * CallCountingStub::CodeSize; @@ -344,13 +328,13 @@ void CallCountingStub::GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, // Set absolute addresses of the slots in the stub BYTE* pCounterSlot = pageBaseRX + i + pageSize + offsetof(CallCountingStubData, RemainingCallCountCell); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_RemainingCallCountCell_Offset)) = pCounterSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_RemainingCallCountCell_Offset)) = pCounterSlot; BYTE* pTargetSlot = pageBaseRX + i + pageSize + offsetof(CallCountingStubData, TargetForMethod); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_TargetForMethod_Offset)) = pTargetSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_TargetForMethod_Offset)) = pTargetSlot; BYTE* pCountReachedZeroSlot = pageBaseRX + i + pageSize + offsetof(CallCountingStubData, TargetForThresholdReached); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_TargetForThresholdReached_Offset)) = pCountReachedZeroSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(CallCountingStubCode_TargetForThresholdReached_Offset)) = pCountReachedZeroSlot; } #else // TARGET_X86 FillStubCodePage(pageBase, (const void*)PCODEToPINSTR((PCODE)CallCountingStubCode), CallCountingStub::CodeSize, pageSize); @@ -370,7 +354,7 @@ NOINLINE InterleavedLoaderHeap *CallCountingManager::CallCountingStubAllocator:: _ASSERTE(m_heap == nullptr); - InterleavedLoaderHeap *heap = new InterleavedLoaderHeap(&m_heapRangeList, true /* fUnlocked */, &s_callCountingHeapConfig); + InterleavedLoaderHeap *heap = new InterleavedLoaderHeap(&m_heapRangeList, true /* fUnlocked */, CallCountingStub::GenerateCodePage, CallCountingStub::CodeSize); m_heap = heap; return heap; } @@ -491,7 +475,6 @@ CallCountingManager::~CallCountingManager() } #ifndef DACCESS_COMPILE - void CallCountingManager::StaticInitialize() { WRAPPER_NO_CONTRACT; diff --git a/src/runtime/src/coreclr/vm/callcounting.h b/src/runtime/src/coreclr/vm/callcounting.h index 59071aa51f1..75a907f4d6e 100644 --- a/src/runtime/src/coreclr/vm/callcounting.h +++ b/src/runtime/src/coreclr/vm/callcounting.h @@ -150,7 +150,7 @@ class CallCountingStub static void StaticInitialize(); #endif // !DACCESS_COMPILE - static void GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size); + static void GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size); PTR_CallCount GetRemainingCallCountCell() const; PCODE GetTargetForMethod() const; diff --git a/src/runtime/src/coreclr/vm/codeman.cpp b/src/runtime/src/coreclr/vm/codeman.cpp index 35d3f0165ab..06ab31bc0e4 100644 --- a/src/runtime/src/coreclr/vm/codeman.cpp +++ b/src/runtime/src/coreclr/vm/codeman.cpp @@ -4148,6 +4148,14 @@ void CodeHeader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, IJitManager* pJ this->pRealCodeHeader.EnumMem(); +#ifdef FEATURE_EH_FUNCLETS + // UnwindInfos are stored in an array immediately following the RealCodeHeader structure in memory. + if (this->pRealCodeHeader->nUnwindInfos) + { + DacEnumMemoryRegion(PTR_TO_MEMBER_TADDR(RealCodeHeader, pRealCodeHeader, unwindInfos), this->pRealCodeHeader->nUnwindInfos * sizeof(T_RUNTIME_FUNCTION)); + } +#endif // FEATURE_EH_FUNCLETS + #ifdef FEATURE_ON_STACK_REPLACEMENT BOOL hasFlagByte = TRUE; #else @@ -4164,7 +4172,7 @@ void CodeHeader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, IJitManager* pJ // Enumerate for minidumps. //----------------------------------------------------------------------------- template -void EECodeGenManager::EnumMemoryRegionsForMethodDebugInfoWorker(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) +void EECodeGenManager::EnumMemoryRegionsForMethodDebugInfoWorker(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { CONTRACTL { @@ -4175,15 +4183,14 @@ void EECodeGenManager::EnumMemoryRegionsForMethodDebugInfoWorker(CLRDataEnumMemo CONTRACTL_END; DebugInfoRequest request; - PCODE addrCode = pMD->GetNativeCode(); - request.InitFromStartingAddr(pMD, addrCode); + request.InitFromStartingAddr(pCodeInfo->GetMethodDesc(), pCodeInfo->GetStartAddress()); TCodeHeader * pHeader = GetCodeHeaderFromDebugInfoRequest(request); pHeader->EnumMemoryRegions(flags, NULL); } -void EEJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) +void EEJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { CONTRACTL { @@ -4193,7 +4200,7 @@ void EEJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags fl } CONTRACTL_END; - EnumMemoryRegionsForMethodDebugInfoWorker(flags, pMD); + EnumMemoryRegionsForMethodDebugInfoWorker(flags, pCodeInfo); } #ifdef FEATURE_INTERPRETER @@ -4217,7 +4224,7 @@ void InterpreterCodeHeader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, IJit } } -void InterpreterJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) +void InterpreterJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { CONTRACTL { @@ -4227,7 +4234,7 @@ void InterpreterJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemor } CONTRACTL_END; - EnumMemoryRegionsForMethodDebugInfoWorker(flags, pMD); + EnumMemoryRegionsForMethodDebugInfoWorker(flags, pCodeInfo); } #endif // FEATURE_INTERPRETER @@ -6314,16 +6321,15 @@ BOOL ReadyToRunJitManager::GetRichDebugInfo( // // Need to write out debug info // -void ReadyToRunJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) +void ReadyToRunJitManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { SUPPORTS_DAC; - EECodeInfo codeInfo(pMD->GetNativeCode()); - if (!codeInfo.IsValid()) + if (!pCodeInfo->IsValid()) return; - ReadyToRunInfo * pReadyToRunInfo = JitTokenToReadyToRunInfo(codeInfo.GetMethodToken()); - PTR_RUNTIME_FUNCTION pRuntimeFunction = JitTokenToRuntimeFunction(codeInfo.GetMethodToken()); + ReadyToRunInfo * pReadyToRunInfo = JitTokenToReadyToRunInfo(pCodeInfo->GetMethodToken()); + PTR_RUNTIME_FUNCTION pRuntimeFunction = JitTokenToRuntimeFunction(pCodeInfo->GetMethodToken()); PTR_BYTE pDebugInfo = pReadyToRunInfo->GetDebugInfo(pRuntimeFunction); if (pDebugInfo == NULL) diff --git a/src/runtime/src/coreclr/vm/codeman.h b/src/runtime/src/coreclr/vm/codeman.h index 9e2eabdeecc..edad07c4341 100644 --- a/src/runtime/src/coreclr/vm/codeman.h +++ b/src/runtime/src/coreclr/vm/codeman.h @@ -1781,7 +1781,7 @@ class IJitManager // DAC builds is compatible with the non-DAC one so that DAC virtual dispatch will work correctly. #if defined(DACCESS_COMPILE) virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); - virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) = 0; + virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) = 0; #if defined(FEATURE_EH_FUNCLETS) // Enumerate the memory necessary to retrieve the unwind info for a specific method virtual void EnumMemoryRegionsForMethodUnwindInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) = 0; @@ -1924,7 +1924,7 @@ class EECodeGenManager : public IJitManager virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); protected: template - void EnumMemoryRegionsForMethodDebugInfoWorker(CLRDataEnumMemoryFlags flags, MethodDesc * pMD); + void EnumMemoryRegionsForMethodDebugInfoWorker(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #endif // !DACCESS_COMPILE private: @@ -2085,7 +2085,7 @@ class EEJitManager final : public EECodeGenManager GCInfoToken GetGCInfoToken(const METHODTOKEN& MethodToken); #ifdef DACCESS_COMPILE - virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD); + virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #endif // DACCESS_COMPILE #if !defined DACCESS_COMPILE @@ -2671,7 +2671,7 @@ class ReadyToRunJitManager final: public IJitManager #if defined(DACCESS_COMPILE) virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); - virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD); + virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #if defined(FEATURE_EH_FUNCLETS) // Enumerate the memory necessary to retrieve the unwind info for a specific method virtual void EnumMemoryRegionsForMethodUnwindInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); @@ -2791,7 +2791,7 @@ class InterpreterJitManager final : public EECodeGenManager #if defined(DACCESS_COMPILE) - virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD); + virtual void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #if defined(FEATURE_EH_FUNCLETS) virtual void EnumMemoryRegionsForMethodUnwindInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) @@ -2941,7 +2941,7 @@ class EECodeInfo { return DecodeGCHdrInfoHelper(infoPtr); } - + *infoPtr = &m_hdrInfoBody; return m_hdrInfoTable; } diff --git a/src/runtime/src/coreclr/vm/debuginfostore.cpp b/src/runtime/src/coreclr/vm/debuginfostore.cpp index 789693f33b6..e3ef94a1960 100644 --- a/src/runtime/src/coreclr/vm/debuginfostore.cpp +++ b/src/runtime/src/coreclr/vm/debuginfostore.cpp @@ -1025,7 +1025,7 @@ BOOL DebugInfoManager::GetRichDebugInfo( } #ifdef DACCESS_COMPILE -void DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD) +void DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo) { CONTRACTL { @@ -1035,18 +1035,23 @@ void DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlag } CONTRACTL_END; - PCODE addrCode = pMD->GetNativeCode(); + if (!pCodeInfo->IsValid()) + { + return; + } + + PCODE addrCode = pCodeInfo->GetStartAddress(); if (addrCode == (PCODE)NULL) { return; } - IJitManager* pJitMan = ExecutionManager::FindJitMan(addrCode); + IJitManager* pJitMan = pCodeInfo->GetJitManager(); if (pJitMan == NULL) { return; // no info available. } - pJitMan->EnumMemoryRegionsForMethodDebugInfo(flags, pMD); + pJitMan->EnumMemoryRegionsForMethodDebugInfo(flags, pCodeInfo); } #endif diff --git a/src/runtime/src/coreclr/vm/debuginfostore.h b/src/runtime/src/coreclr/vm/debuginfostore.h index 1df939432ce..699940486e2 100644 --- a/src/runtime/src/coreclr/vm/debuginfostore.h +++ b/src/runtime/src/coreclr/vm/debuginfostore.h @@ -154,7 +154,7 @@ class DebugInfoManager OUT ULONG32* pNumRichMappings); #ifdef DACCESS_COMPILE - static void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD); + static void EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, EECodeInfo * pCodeInfo); #endif }; diff --git a/src/runtime/src/coreclr/vm/eetwain.cpp b/src/runtime/src/coreclr/vm/eetwain.cpp index 9290e3f15a9..7db5fdc7f27 100644 --- a/src/runtime/src/coreclr/vm/eetwain.cpp +++ b/src/runtime/src/coreclr/vm/eetwain.cpp @@ -851,18 +851,21 @@ bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo, SUPPORTS_DAC; } CONTRACTL_END; - hdrInfo info = { 0 }; + hdrInfo info = { 0 }; /* Extract the necessary information from the info block header */ - pCodeInfo->DecodeGCHdrInfo(&info, dwRelOffset); + PTR_CBYTE table = pCodeInfo->DecodeGCHdrInfo(&info, dwRelOffset); /* workaround: prevent interruption within prolog/epilog */ if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG) return false; - return (info.interruptible); + if (!info.interruptible) + return false; + + return !::IsInNoGCRegion(&info, table, dwRelOffset); } #endif // !USE_GC_INFO_DECODER @@ -1494,13 +1497,20 @@ OBJECTREF EECodeManager::GetInstance( PREGDISPLAY pContext, _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF); #endif + /* Skip over no-GC region table */ + unsigned count = hdrInfoBody->noGCRegionCnt; + while (count-- > 0) + { + fastSkipUnsigned(table); fastSkipUnsigned(table); + } + #ifndef FEATURE_EH_FUNCLETS /* Parse the untracked frame variable table */ /* The 'this' pointer can never be located in the untracked table */ /* as we only allow pinned and byrefs in the untracked table */ - unsigned count = hdrInfoBody->untrackedCnt; + count = hdrInfoBody->untrackedCnt; while (count-- > 0) { fastSkipSigned(table); diff --git a/src/runtime/src/coreclr/vm/finalizerthread.cpp b/src/runtime/src/coreclr/vm/finalizerthread.cpp index db45c4c0d8d..21bd8e58416 100644 --- a/src/runtime/src/coreclr/vm/finalizerthread.cpp +++ b/src/runtime/src/coreclr/vm/finalizerthread.cpp @@ -125,13 +125,8 @@ void FinalizerThread::FinalizeAllObjects() void FinalizerThread::WaitForFinalizerEvent (CLREvent *event) { - // Non-host environment - // We don't want kLowMemoryNotification to starve out kFinalizer - // (as the latter may help correct the former), and we don't want either - // to starve out kProfilingAPIAttach, as we want decent responsiveness - // to a user trying to attach a profiler. So check in this order: - // kProfilingAPIAttach alone (0 wait) + // (as the latter may help correct the former). So check in this order: // kFinalizer alone (2s wait) // all events together (infinite wait) @@ -162,7 +157,6 @@ void FinalizerThread::WaitForFinalizerEvent (CLREvent *event) // // * kLowMemoryNotification (if it's non-NULL && g_fEEStarted) // * kFinalizer (always) - // * kProfilingAPIAttach (if it's non-NULL) // // The enum code:MHandleType values become important here, as // WaitForMultipleObjects needs to wait on a contiguous set of non-NULL diff --git a/src/runtime/src/coreclr/vm/gc_unwind_x86.inl b/src/runtime/src/coreclr/vm/gc_unwind_x86.inl index e1502ec6049..eb08dff6cf1 100644 --- a/src/runtime/src/coreclr/vm/gc_unwind_x86.inl +++ b/src/runtime/src/coreclr/vm/gc_unwind_x86.inl @@ -197,6 +197,17 @@ size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken, header.revPInvokeOffset = fastDecodeUnsigned(table); } + if (header.noGCRegionCnt == HAS_NOGCREGIONS) + { + hasArgTabOffset = TRUE; + header.noGCRegionCnt = fastDecodeUnsigned(table); + } + else if (header.noGCRegionCnt > 0) + { + hasArgTabOffset = TRUE; + } + + /* Some sanity checks on header */ _ASSERTE( header.prologSize + @@ -231,6 +242,7 @@ size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken, infoPtr->syncStartOffset = header.syncStartOffset; infoPtr->syncEndOffset = header.syncEndOffset; infoPtr->revPInvokeOffset = header.revPInvokeOffset; + infoPtr->noGCRegionCnt = header.noGCRegionCnt; infoPtr->doubleAlign = header.doubleAlign; infoPtr->handlers = header.handlers; @@ -667,6 +679,37 @@ inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info) } #endif // FEATURE_NATIVEAOT +/*****************************************************************************/ +bool IsInNoGCRegion(hdrInfo * infoPtr, + PTR_CBYTE table, + unsigned curOffset) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + if (infoPtr->noGCRegionCnt == 0) + return false; + +#if VERIFY_GC_TABLES + _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF); +#endif + + unsigned count = infoPtr->noGCRegionCnt; + while (count-- > 0) { + unsigned regionOffset = fastDecodeUnsigned(table); + if (curOffset < regionOffset) + return false; + unsigned regionSize = fastDecodeUnsigned(table); + if (curOffset - regionOffset < regionSize) + return true; + } + + return false; +} + /*****************************************************************************/ static PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table) @@ -692,6 +735,13 @@ PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table) _ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF); #endif + /* Skip over the no GC regions table */ + + count = info.noGCRegionCnt; + while (count-- > 0) { + fastSkipUnsigned(table); fastSkipUnsigned(table); + } + /* Skip over the untracked frame variable table */ count = info.untrackedCnt; @@ -2393,7 +2443,7 @@ void UnwindEspFrameEpilog( // We have already popped off the frame (excluding the callee-saved registers) - if (epilogBase[0] == X86_INSTR_POP_ECX) + if (epilogBase[offset] == X86_INSTR_POP_ECX) { // We may use "POP ecx" for doing "ADD ESP, 4", // or we may not (in the case of JMP epilogs) @@ -2510,8 +2560,11 @@ void UnwindEbpDoubleAlignFrameEpilog( { // do nothing before popping the callee-saved registers } - else if (info->rawStkSize == sizeof(void*)) + else if (info->rawStkSize == sizeof(void*) && epilogBase[offset] == X86_INSTR_POP_ECX) { + // We may use "POP ecx" for doing "ADD ESP, 4", + // or we may not (in the case of JMP epilogs) + // "pop ecx" will make ESP point to the callee-saved registers if (!InstructionAlreadyExecuted(offset, info->epilogOffs)) ESP += sizeof(void*); @@ -3583,6 +3636,12 @@ bool EnumGcRefsX86(PREGDISPLAY pContext, unsigned ptrAddr; unsigned lowBits; + /* Skip over no-GC region table */ + count = info.noGCRegionCnt; + while (count-- > 0) + { + fastSkipUnsigned(table); fastSkipUnsigned(table); + } /* Process the untracked frame variable table */ diff --git a/src/runtime/src/coreclr/vm/gcenv.ee.cpp b/src/runtime/src/coreclr/vm/gcenv.ee.cpp index b62915d6c76..9c4dccec648 100644 --- a/src/runtime/src/coreclr/vm/gcenv.ee.cpp +++ b/src/runtime/src/coreclr/vm/gcenv.ee.cpp @@ -1055,6 +1055,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) ThreadSuspend::RestartEE(FALSE, TRUE); } return; // unlike other branches we have already done cleanup so bailing out here + case WriteBarrierOp::StompEphemeral: assert(args->is_runtime_suspended && "the runtime must be suspended here!"); // StompEphemeral requires a new ephemeral low and a new ephemeral high @@ -1065,8 +1066,16 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) g_region_to_generation_table = args->region_to_generation_table; g_region_shr = args->region_shr; g_region_use_bitwise_write_barrier = args->region_use_bitwise_write_barrier; +#if defined(HOST_ARM64) + // Only allow bitwise write barriers if LSE atomics are present + if (!g_arm64_atomics_present) + { + g_region_use_bitwise_write_barrier = false; + } +#endif stompWBCompleteActions |= ::StompWriteBarrierEphemeral(args->is_runtime_suspended); break; + case WriteBarrierOp::Initialize: assert(args->is_runtime_suspended && "the runtime must be suspended here!"); // This operation should only be invoked once, upon initialization. @@ -1092,16 +1101,24 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) g_region_to_generation_table = args->region_to_generation_table; g_region_shr = args->region_shr; g_region_use_bitwise_write_barrier = args->region_use_bitwise_write_barrier; + g_ephemeral_low = args->ephemeral_low; + g_ephemeral_high = args->ephemeral_high; +#if defined(HOST_ARM64) + // Only allow bitwise write barriers if LSE atomics are present + if (!g_arm64_atomics_present) + { + g_region_use_bitwise_write_barrier = false; + } +#endif stompWBCompleteActions |= ::StompWriteBarrierResize(true, false); // StompWriteBarrierResize does not necessarily bash g_ephemeral_low - // usages, so we must do so here. This is particularly true on x86, + // usages, so we must do so here. This is particularly true on x86/Arm64, // where StompWriteBarrierResize will not bash g_ephemeral_low when // called with the parameters (true, false), as it is above. - g_ephemeral_low = args->ephemeral_low; - g_ephemeral_high = args->ephemeral_high; stompWBCompleteActions |= ::StompWriteBarrierEphemeral(true); break; + case WriteBarrierOp::SwitchToWriteWatch: #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP assert(args->is_runtime_suspended && "the runtime must be suspended here!"); @@ -1113,6 +1130,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) assert(!"should never be called without FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP"); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP break; + case WriteBarrierOp::SwitchToNonWriteWatch: #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP assert(args->is_runtime_suspended && "the runtime must be suspended here!"); @@ -1123,6 +1141,7 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) assert(!"should never be called without FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP"); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP break; + default: assert(!"unknown WriteBarrierOp enum"); } diff --git a/src/runtime/src/coreclr/vm/interpexec.cpp b/src/runtime/src/coreclr/vm/interpexec.cpp index c90aaf377d4..d907176a227 100644 --- a/src/runtime/src/coreclr/vm/interpexec.cpp +++ b/src/runtime/src/coreclr/vm/interpexec.cpp @@ -36,6 +36,13 @@ static void InterpBreakpoint() void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext) { + CONTRACTL + { + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + #if defined(HOST_AMD64) && defined(HOST_WINDOWS) pInterpreterFrame->SetInterpExecMethodSSP((TADDR)_rdsspq()); #endif // HOST_AMD64 && HOST_WINDOWS @@ -293,6 +300,12 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr break; } + case INTOP_SAFEPOINT: + if (g_TrapReturningThreads) + JIT_PollGC(); + ip++; + break; + case INTOP_BR: ip += ip[1]; break; diff --git a/src/runtime/src/coreclr/vm/jitinterface.h b/src/runtime/src/coreclr/vm/jitinterface.h index 6639751c92b..293f5241bf3 100644 --- a/src/runtime/src/coreclr/vm/jitinterface.h +++ b/src/runtime/src/coreclr/vm/jitinterface.h @@ -197,70 +197,6 @@ extern "C" FCDECL2(VOID, JIT_WriteBarrier_Callable, Object **dst, Object *ref); #define WriteBarrier_Helper JIT_WriteBarrier_Callable -#ifdef TARGET_AMD64 - - -class WriteBarrierManager -{ -public: - enum WriteBarrierType - { - WRITE_BARRIER_UNINITIALIZED, - WRITE_BARRIER_PREGROW64, - WRITE_BARRIER_POSTGROW64, -#ifdef FEATURE_SVR_GC - WRITE_BARRIER_SVR64, -#endif // FEATURE_SVR_GC - WRITE_BARRIER_BYTE_REGIONS64, - WRITE_BARRIER_BIT_REGIONS64, -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - WRITE_BARRIER_WRITE_WATCH_PREGROW64, - WRITE_BARRIER_WRITE_WATCH_POSTGROW64, -#ifdef FEATURE_SVR_GC - WRITE_BARRIER_WRITE_WATCH_SVR64, -#endif // FEATURE_SVR_GC - WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64, - WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64, -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - WRITE_BARRIER_BUFFER - }; - - WriteBarrierManager(); - void Initialize(); - - int UpdateEphemeralBounds(bool isRuntimeSuspended); - int UpdateWriteWatchAndCardTableLocations(bool isRuntimeSuspended, bool bReqUpperBoundsCheck); - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - int SwitchToWriteWatchBarrier(bool isRuntimeSuspended); - int SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended); -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - size_t GetCurrentWriteBarrierSize(); - -protected: - size_t GetSpecificWriteBarrierSize(WriteBarrierType writeBarrier); - PBYTE CalculatePatchLocation(LPVOID base, LPVOID label, int offset); - PCODE GetCurrentWriteBarrierCode(); - int ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended); - bool NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, bool bUseBitwiseWriteBarrier, WriteBarrierType* pNewWriteBarrierType); - -private: - void Validate(); - - WriteBarrierType m_currentWriteBarrier; - - PBYTE m_pWriteWatchTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION - PBYTE m_pLowerBoundImmediate; // PREGROW | POSTGROW | | WRITE_WATCH | REGION - PBYTE m_pCardTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION - PBYTE m_pCardBundleTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION - PBYTE m_pUpperBoundImmediate; // | POSTGROW | | WRITE_WATCH | REGION - PBYTE m_pRegionToGenTableImmediate; // | | | WRITE_WATCH | REGION - PBYTE m_pRegionShrDest; // | | | WRITE_WATCH | REGION - PBYTE m_pRegionShrSrc; // | | | WRITE_WATCH | RETION -}; - -#endif // TARGET_AMD64 - EXTERN_C FCDECL2_VV(INT64, JIT_LMul, INT64 val1, INT64 val2); #ifndef HOST_64BIT diff --git a/src/runtime/src/coreclr/vm/jitinterfacegen.cpp b/src/runtime/src/coreclr/vm/jitinterfacegen.cpp index 2190f295b2a..cae7b5b9f66 100644 --- a/src/runtime/src/coreclr/vm/jitinterfacegen.cpp +++ b/src/runtime/src/coreclr/vm/jitinterfacegen.cpp @@ -17,6 +17,7 @@ #include "comdelegate.h" #include "field.h" #include "ecall.h" +#include "writebarriermanager.h" #ifdef HOST_64BIT @@ -27,10 +28,6 @@ EXTERN_C Object* AllocateStringFastUP (CLR_I4 cch); EXTERN_C Object* JIT_NewArr1OBJ_UP (CORINFO_CLASS_HANDLE arrayMT, INT_PTR size); EXTERN_C Object* JIT_NewArr1VC_UP (CORINFO_CLASS_HANDLE arrayMT, INT_PTR size); -#ifdef TARGET_AMD64 -extern WriteBarrierManager g_WriteBarrierManager; -#endif // TARGET_AMD64 - #endif // HOST_64BIT /*********************************************************************/ diff --git a/src/runtime/src/coreclr/vm/loaderallocator.cpp b/src/runtime/src/coreclr/vm/loaderallocator.cpp index f31d2d068bb..5fe3bb2faf2 100644 --- a/src/runtime/src/coreclr/vm/loaderallocator.cpp +++ b/src/runtime/src/coreclr/vm/loaderallocator.cpp @@ -1208,7 +1208,8 @@ void LoaderAllocator::Init(BYTE *pExecutableHeapMemory) m_pNewStubPrecodeHeap = new (&m_NewStubPrecodeHeapInstance) InterleavedLoaderHeap( &m_stubPrecodeRangeList, false /* fUnlocked */, - &s_stubPrecodeHeapConfig); + StubPrecode::GenerateCodePage, + StubPrecode::CodeSize); #if defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) && defined(FEATURE_READYTORUN) if (IsCollectible()) @@ -1218,12 +1219,14 @@ void LoaderAllocator::Init(BYTE *pExecutableHeapMemory) m_pDynamicHelpersStubHeap = new (&m_DynamicHelpersHeapInstance) InterleavedLoaderHeap( &m_dynamicHelpersRangeList, false /* fUnlocked */, - &s_stubPrecodeHeapConfig); + StubPrecode::GenerateCodePage, + StubPrecode::CodeSize); #endif // defined(FEATURE_STUBPRECODE_DYNAMIC_HELPERS) && defined(FEATURE_READYTORUN) m_pFixupPrecodeHeap = new (&m_FixupPrecodeHeapInstance) InterleavedLoaderHeap(&m_fixupPrecodeRangeList, false /* fUnlocked */, - &s_fixupStubPrecodeHeapConfig); + FixupPrecode::GenerateCodePage, + FixupPrecode::CodeSize); // Initialize the EE marshaling data to NULL. m_pMarshalingData = NULL; diff --git a/src/runtime/src/coreclr/vm/method.cpp b/src/runtime/src/coreclr/vm/method.cpp index 6384d8aa2e9..3b4314dce1d 100644 --- a/src/runtime/src/coreclr/vm/method.cpp +++ b/src/runtime/src/coreclr/vm/method.cpp @@ -3673,7 +3673,24 @@ MethodDesc::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) } // Need to save the Debug-Info for this method so that we can see it in a debugger later. - DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, this); +#ifdef FEATURE_CODE_VERSIONING + { + CodeVersionManager::LockHolder codeVersioningLockHolder; + + CodeVersionManager* pCodeVersionManager = GetCodeVersionManager(); + NativeCodeVersionCollection nativeCodeVersions = pCodeVersionManager->GetNativeCodeVersions(dac_cast(this)); + for (NativeCodeVersionIterator iter = nativeCodeVersions.Begin(); iter != nativeCodeVersions.End(); iter++) + { + PCODE addrCode = iter->GetNativeCode(); + EECodeInfo codeInfo(addrCode); + DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, &codeInfo); + } + } +#else + PCODE entryPoint = GetNativeCode(); + EECodeInfo codeInfo(entryPoint); + DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, &codeInfo); +#endif // FEATURE_CODE_VERSIONING if (!IsNoMetadata() ||IsILStub()) { diff --git a/src/runtime/src/coreclr/vm/precode.cpp b/src/runtime/src/coreclr/vm/precode.cpp index 798e9849de3..e3e3983e871 100644 --- a/src/runtime/src/coreclr/vm/precode.cpp +++ b/src/runtime/src/coreclr/vm/precode.cpp @@ -15,11 +15,6 @@ #include "perfmap.h" #endif -InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; -#ifdef HAS_FIXUP_PRECODE -InterleavedLoaderHeapConfig s_fixupStubPrecodeHeapConfig; -#endif - //========================================================================================== // class Precode //========================================================================================== @@ -500,12 +495,6 @@ void (*StubPrecode::StubPrecodeCode)(); void (*StubPrecode::StubPrecodeCode_End)(); #endif -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE -extern "C" void StubPrecodeCodeTemplate(); -#else -#define StubPrecodeCodeTemplate NULL -#endif - void StubPrecode::StaticInitialize() { #if defined(TARGET_ARM64) && defined(TARGET_UNIX) @@ -523,13 +512,6 @@ void StubPrecode::StaticInitialize() default: EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("Unsupported OS page size")); } - - if (StubPrecodeCodeTemplate != NULL && pageSize != 0x4000) - { - // This should fail if the template is used on a platform which doesn't support the supported page size for templates - ThrowHR(COR_E_EXECUTIONENGINE); - } - #undef ENUM_PAGE_SIZE #else _ASSERTE((SIZE_T)((BYTE*)StubPrecodeCode_End - (BYTE*)StubPrecodeCode) <= StubPrecode::CodeSize); @@ -542,22 +524,21 @@ void StubPrecode::StaticInitialize() _ASSERTE((*((BYTE*)PCODEToPINSTR((PCODE)StubPrecodeCode) + OFFSETOF_PRECODE_TYPE)) == StubPrecode::Type); #endif - InitializeLoaderHeapConfig(&s_stubPrecodeHeapConfig, StubPrecode::CodeSize, (void*)StubPrecodeCodeTemplate, StubPrecode::GenerateCodePage); } -void StubPrecode::GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t pageSize) +void StubPrecode::GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T pageSize) { #ifdef TARGET_X86 int totalCodeSize = (pageSize / StubPrecode::CodeSize) * StubPrecode::CodeSize; for (int i = 0; i < totalCodeSize; i += StubPrecode::CodeSize) { - memcpy(pageBase + i, (const void*)StubPrecodeCode, (uint8_t*)StubPrecodeCode_End - (uint8_t*)StubPrecodeCode); + memcpy(pageBase + i, (const void*)StubPrecodeCode, (BYTE*)StubPrecodeCode_End - (BYTE*)StubPrecodeCode); - uint8_t* pTargetSlot = pageBaseRX + i + pageSize + offsetof(StubPrecodeData, Target); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(StubPrecodeCode_Target_Offset)) = pTargetSlot; + BYTE* pTargetSlot = pageBaseRX + i + pageSize + offsetof(StubPrecodeData, Target); + *(BYTE**)(pageBase + i + SYMBOL_VALUE(StubPrecodeCode_Target_Offset)) = pTargetSlot; BYTE* pMethodDescSlot = pageBaseRX + i + pageSize + offsetof(StubPrecodeData, SecretParam); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(StubPrecodeCode_MethodDesc_Offset)) = pMethodDescSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(StubPrecodeCode_MethodDesc_Offset)) = pMethodDescSlot; } #else // TARGET_X86 FillStubCodePage(pageBase, (const void*)PCODEToPINSTR((PCODE)StubPrecodeCode), StubPrecode::CodeSize, pageSize); @@ -645,12 +626,6 @@ void (*FixupPrecode::FixupPrecodeCode)(); void (*FixupPrecode::FixupPrecodeCode_End)(); #endif -#ifdef FEATURE_MAP_THUNKS_FROM_IMAGE -extern "C" void FixupPrecodeCodeTemplate(); -#else -#define FixupPrecodeCodeTemplate NULL -#endif - void FixupPrecode::StaticInitialize() { #if defined(TARGET_ARM64) && defined(TARGET_UNIX) @@ -670,12 +645,6 @@ void FixupPrecode::StaticInitialize() EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("Unsupported OS page size")); } #undef ENUM_PAGE_SIZE - - if (FixupPrecodeCodeTemplate != NULL && pageSize != 0x4000) - { - // This should fail if the template is used on a platform which doesn't support the supported page size for templates - ThrowHR(COR_E_EXECUTIONENGINE); - } #else _ASSERTE((SIZE_T)((BYTE*)FixupPrecodeCode_End - (BYTE*)FixupPrecodeCode) <= FixupPrecode::CodeSize); #endif @@ -686,11 +655,9 @@ void FixupPrecode::StaticInitialize() #else _ASSERTE(*((BYTE*)PCODEToPINSTR((PCODE)FixupPrecodeCode) + OFFSETOF_PRECODE_TYPE) == FixupPrecode::Type); #endif - - InitializeLoaderHeapConfig(&s_fixupStubPrecodeHeapConfig, FixupPrecode::CodeSize, (void*)FixupPrecodeCodeTemplate, FixupPrecode::GenerateCodePage); } -void FixupPrecode::GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t pageSize) +void FixupPrecode::GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T pageSize) { #ifdef TARGET_X86 int totalCodeSize = (pageSize / FixupPrecode::CodeSize) * FixupPrecode::CodeSize; @@ -698,14 +665,14 @@ void FixupPrecode::GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size for (int i = 0; i < totalCodeSize; i += FixupPrecode::CodeSize) { memcpy(pageBase + i, (const void*)FixupPrecodeCode, FixupPrecode::CodeSize); - uint8_t* pTargetSlot = pageBaseRX + i + pageSize + offsetof(FixupPrecodeData, Target); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_Target_Offset)) = pTargetSlot; + BYTE* pTargetSlot = pageBaseRX + i + pageSize + offsetof(FixupPrecodeData, Target); + *(BYTE**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_Target_Offset)) = pTargetSlot; BYTE* pMethodDescSlot = pageBaseRX + i + pageSize + offsetof(FixupPrecodeData, MethodDesc); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_MethodDesc_Offset)) = pMethodDescSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_MethodDesc_Offset)) = pMethodDescSlot; BYTE* pPrecodeFixupThunkSlot = pageBaseRX + i + pageSize + offsetof(FixupPrecodeData, PrecodeFixupThunk); - *(uint8_t**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_PrecodeFixupThunk_Offset)) = pPrecodeFixupThunkSlot; + *(BYTE**)(pageBase + i + SYMBOL_VALUE(FixupPrecodeCode_PrecodeFixupThunk_Offset)) = pPrecodeFixupThunkSlot; } #else // TARGET_X86 FillStubCodePage(pageBase, (const void*)PCODEToPINSTR((PCODE)FixupPrecodeCode), FixupPrecode::CodeSize, pageSize); diff --git a/src/runtime/src/coreclr/vm/precode.h b/src/runtime/src/coreclr/vm/precode.h index 64394d259e9..87570f21729 100644 --- a/src/runtime/src/coreclr/vm/precode.h +++ b/src/runtime/src/coreclr/vm/precode.h @@ -225,7 +225,7 @@ struct StubPrecode pData->Target = (PCODE)target; } - static void GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size); + static void GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size); #endif // !DACCESS_COMPILE }; @@ -428,7 +428,7 @@ struct FixupPrecode static void StaticInitialize(); - static void GenerateCodePage(uint8_t* pageBase, uint8_t* pageBaseRX, size_t size); + static void GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size); PTR_FixupPrecodeData GetData() const { @@ -861,9 +861,4 @@ struct PrecodeMachineDescriptor }; #endif //DACCESS_COMPILE -extern InterleavedLoaderHeapConfig s_stubPrecodeHeapConfig; -#ifdef HAS_FIXUP_PRECODE -extern InterleavedLoaderHeapConfig s_fixupStubPrecodeHeapConfig; -#endif - #endif // __PRECODE_H__ diff --git a/src/runtime/src/coreclr/vm/profdetach.cpp b/src/runtime/src/coreclr/vm/profdetach.cpp index 7bfcba8ed2c..28b8442454c 100644 --- a/src/runtime/src/coreclr/vm/profdetach.cpp +++ b/src/runtime/src/coreclr/vm/profdetach.cpp @@ -55,9 +55,6 @@ void ProfilerDetachInfo::Init() } -// ---------------------------------------------------------------------------- -// Implementation of ProfilingAPIAttachDetach statics - // ---------------------------------------------------------------------------- // ProfilingAPIDetach::Initialize diff --git a/src/runtime/src/coreclr/vm/profilinghelper.cpp b/src/runtime/src/coreclr/vm/profilinghelper.cpp index d94dce9d89c..99eb7eb0921 100644 --- a/src/runtime/src/coreclr/vm/profilinghelper.cpp +++ b/src/runtime/src/coreclr/vm/profilinghelper.cpp @@ -411,13 +411,6 @@ EXTERN_C void STDMETHODCALLTYPE ProfileTailcallNaked(UINT_PTR clientData); // Notes: // This function (or one of its callees) will log an error to the event log // if there is a failure -// -// Assumptions: -// InitializeProfiling is called during startup, AFTER the host has initialized its -// settings and the config variables have been read, but BEFORE the finalizer thread -// has entered its first wait state. ASSERTs are placed in -// code:ProfilingAPIAttachDetach::Initialize (which is called by this function, and -// which depends on these assumptions) to verify. // static HRESULT ProfilingAPIUtility::InitializeProfiling() diff --git a/src/runtime/src/coreclr/vm/amd64/jitinterfaceamd64.cpp b/src/runtime/src/coreclr/vm/writebarriermanager.cpp similarity index 87% rename from src/runtime/src/coreclr/vm/amd64/jitinterfaceamd64.cpp rename to src/runtime/src/coreclr/vm/writebarriermanager.cpp index 3df45e888b5..c9aac96ada3 100644 --- a/src/runtime/src/coreclr/vm/amd64/jitinterfaceamd64.cpp +++ b/src/runtime/src/coreclr/vm/writebarriermanager.cpp @@ -2,19 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // =========================================================================== -// File: JITinterfaceCpu.CPP +// File: writebariermanager.cpp // =========================================================================== -// This contains JITinterface routines that are specific to the -// AMD64 platform. They are modeled after the X86 specific routines -// found in JITinterfaceX86.cpp or JIThelp.asm - +// This contains JITinterface routines for managing which write barrier function +// is currently in use, and patching all related constants. #include "common.h" #include "jitinterface.h" #include "eeconfig.h" #include "excep.h" #include "threadsuspend.h" +#include "writebarriermanager.h" +#if !defined(WRITE_BARRIER_VARS_INLINE) +#include "patchedcodeconstants.h" +#endif extern uint8_t* g_ephemeral_low; extern uint8_t* g_ephemeral_high; @@ -22,35 +24,58 @@ extern uint32_t* g_card_table; extern uint32_t* g_card_bundle_table; // Patch Labels for the various write barriers -EXTERN_C void JIT_WriteBarrier_End(); +EXTERN_C void JIT_WriteBarrier_End(); EXTERN_C void JIT_WriteBarrier_PreGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_PreGrow64_End(); +EXTERN_C void JIT_WriteBarrier_PostGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_PostGrow64_End(); +#ifdef FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_SVR64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_SVR64_End(); +#endif // FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_Byte_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_End(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_End(); +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_End(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_End(); +#ifdef FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_End(); +#endif // FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_End(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_End(); +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + +#if defined(WRITE_BARRIER_VARS_INLINE) + EXTERN_C void JIT_WriteBarrier_PreGrow64_Patch_Label_Lower(); EXTERN_C void JIT_WriteBarrier_PreGrow64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_PreGrow64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_PreGrow64_End(); -EXTERN_C void JIT_WriteBarrier_PostGrow64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_Lower(); EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_Upper(); EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_PostGrow64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_PostGrow64_End(); #ifdef FEATURE_SVR_GC -EXTERN_C void JIT_WriteBarrier_SVR64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_SVR64_PatchLabel_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_SVR64_PatchLabel_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_SVR64_End(); #endif // FEATURE_SVR_GC -EXTERN_C void JIT_WriteBarrier_Byte_Region64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionToGeneration(); EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrDest(); EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_Lower(); @@ -60,9 +85,7 @@ EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_Byte_Region64_End(); -EXTERN_C void JIT_WriteBarrier_Bit_Region64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionToGeneration(); EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrDest(); EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_Lower(); @@ -72,20 +95,15 @@ EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_Bit_Region64_End(); - #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_WriteWatchTable(); EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_Lower(); EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_End(); -EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_WriteWatchTable(); EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Lower(); EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Upper(); @@ -93,19 +111,15 @@ EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_WriteWatch_PostGrow64_End(); #ifdef FEATURE_SVR_GC -EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_WriteWatchTable(); EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_End(); #endif // FEATURE_SVR_GC -EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_WriteWatchTable(); EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionToGeneration(); EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrDest(); @@ -116,9 +130,7 @@ EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_End(); -EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_WriteWatchTable(); EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionToGeneration(); EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrDest(); @@ -129,16 +141,33 @@ EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardTable(); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardBundleTable(); #endif -EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_End(); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -WriteBarrierManager g_WriteBarrierManager; +#else // WRITE_BARRIER_VARS_INLINE -// Use this somewhat hokey macro to concatenate the function start with the patch -// label. This allows the code below to look relatively nice, but relies on the -// naming convention which we have established for these helpers. -#define CALC_PATCH_LOCATION(func,label,offset) CalculatePatchLocation((PVOID)func, (PVOID)func##_##label, offset) +EXTERN_C void JIT_WriteBarrier_Table(); +EXTERN_C void JIT_WriteBarrier_Table_End(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_RegionToGeneration(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_RegionShr(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_CardTable(); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +EXTERN_C void JIT_WriteBarrier_Patch_Label_CardBundleTable(); +#endif +#if defined(TARGET_ARM64) +EXTERN_C void JIT_WriteBarrier_Patch_Label_LowestAddress(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_HighestAddress(); +#if defined(WRITE_BARRIER_CHECK) +EXTERN_C void JIT_WriteBarrier_Patch_Label_GCShadow(); +EXTERN_C void JIT_WriteBarrier_Patch_Label_GCShadowEnd(); +#endif // WRITE_BARRIER_CHECK +#endif // TARGET_ARM64 +#endif // WRITE_BARRIER_VARS_INLINE + +WriteBarrierManager g_WriteBarrierManager; WriteBarrierManager::WriteBarrierManager() : m_currentWriteBarrier(WRITE_BARRIER_UNINITIALIZED) @@ -146,173 +175,6 @@ WriteBarrierManager::WriteBarrierManager() : LIMITED_METHOD_CONTRACT; } -#ifndef CODECOVERAGE // Deactivate alignment validation for code coverage builds - // because the instrumentation tool will not preserve alignment - // constraints and we will fail. - -void WriteBarrierManager::Validate() -{ - CONTRACTL - { - MODE_ANY; - GC_NOTRIGGER; - NOTHROW; - } - CONTRACTL_END; - - // we have an invariant that the addresses of all the values that we update in our write barrier - // helpers must be naturally aligned, this is so that the update can happen atomically since there - // are places where these values are updated while the EE is running - // NOTE: we can't call this from the ctor since our infrastructure isn't ready for assert dialogs - - PBYTE pLowerBoundImmediate, pUpperBoundImmediate, pCardTableImmediate; - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - PBYTE pCardBundleTableImmediate; -#endif - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); - - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - -#ifdef FEATURE_SVR_GC - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES -#endif // FEATURE_SVR_GC - - PBYTE pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - - pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - PBYTE pWriteWatchTableImmediate; - - pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); - - _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - - pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); - - _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - -#ifdef FEATURE_SVR_GC - pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES -#endif // FEATURE_SVR_GC - - pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - - pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); - - pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); - pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); - pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); -#endif - -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -} - -#endif // CODECOVERAGE - - PCODE WriteBarrierManager::GetCurrentWriteBarrierCode() { LIMITED_METHOD_CONTRACT; @@ -386,7 +248,11 @@ size_t WriteBarrierManager::GetSpecificWriteBarrierSize(WriteBarrierType writeBa return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_Bit_Region64); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_BUFFER: +#if defined(WRITE_BARRIER_VARS_INLINE) return MARKED_FUNCTION_SIZE(JIT_WriteBarrier); +#else + return (size_t)((LPBYTE)GetEEFuncEntryPoint(JIT_WriteBarrier_Table_End) - (LPBYTE)GetEEFuncEntryPoint(JIT_WriteBarrier)); +#endif default: UNREACHABLE_MSG("unexpected m_currentWriteBarrier!"); }; @@ -398,14 +264,6 @@ size_t WriteBarrierManager::GetCurrentWriteBarrierSize() return GetSpecificWriteBarrierSize(m_currentWriteBarrier); } -PBYTE WriteBarrierManager::CalculatePatchLocation(LPVOID base, LPVOID label, int offset) -{ - // the label should always come after the entrypoint for this funtion - _ASSERTE_ALL_BUILDS((LPBYTE)label > (LPBYTE)base); - - return (GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier) + ((LPBYTE)GetEEFuncEntryPoint(label) - (LPBYTE)GetEEFuncEntryPoint(base) + offset)); -} - int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended) { @@ -428,222 +286,16 @@ int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, stompWBCompleteActions |= SWB_ICACHE_FLUSH; } - switch (newWriteBarrier) - { - case WRITE_BARRIER_PREGROW64: - { - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#if defined(WRITE_BARRIER_VARS_INLINE) + UpdatePatchLocations(newWriteBarrier); #endif - break; - } - case WRITE_BARRIER_POSTGROW64: - { - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - } - -#ifdef FEATURE_SVR_GC - case WRITE_BARRIER_SVR64: - { - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - } -#endif // FEATURE_SVR_GC - - case WRITE_BARRIER_BYTE_REGIONS64: - m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); - m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrDest, 3); - m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrSrc, 3); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - - case WRITE_BARRIER_BIT_REGIONS64: - m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); - m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrDest, 3); - m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrSrc, 3); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - -#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - case WRITE_BARRIER_WRITE_WATCH_PREGROW64: - { - m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - } - - case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: - { - m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - } - -#ifdef FEATURE_SVR_GC - case WRITE_BARRIER_WRITE_WATCH_SVR64: - { - m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - } -#endif // FEATURE_SVR_GC - - case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: - m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_WriteWatchTable, 2); - m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); - m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrDest, 3); - m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrSrc, 3); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - - case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: - m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_WriteWatchTable, 2); - m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); - m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrDest, 3); - m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrSrc, 3); - m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); - m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); - m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); - - // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); - _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); - -#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); - _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); -#endif - break; - - -#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - - default: - UNREACHABLE_MSG("unexpected write barrier type!"); - } - - stompWBCompleteActions |= UpdateEphemeralBounds(true); - stompWBCompleteActions |= UpdateWriteWatchAndCardTableLocations(true, false); + stompWBCompleteActions |= UpdateEphemeralBounds(true); + stompWBCompleteActions |= UpdateWriteWatchAndCardTableLocations(true, false); return stompWBCompleteActions; } -#undef CALC_PATCH_LOCATION - void WriteBarrierManager::Initialize() { CONTRACTL @@ -654,7 +306,6 @@ void WriteBarrierManager::Initialize() } CONTRACTL_END; - // Ensure that the generic JIT_WriteBarrier function buffer is large enough to hold any of the more specific // write barrier implementations. size_t cbWriteBarrierBuffer = GetSpecificWriteBarrierSize(WRITE_BARRIER_BUFFER); @@ -676,11 +327,50 @@ void WriteBarrierManager::Initialize() _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64)); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -#if !defined(CODECOVERAGE) + +#if !defined(WRITE_BARRIER_VARS_INLINE) + + #define CALC_TABLE_LOCATION(var, offset) \ + assert(JIT_WriteBarrier_Offset_##offset == (PBYTE)JIT_WriteBarrier_Patch_Label_##offset - (PBYTE)JIT_WriteBarrier); \ + var = ((PBYTE)GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier) + JIT_WriteBarrier_Offset_##offset); + + CALC_TABLE_LOCATION(m_pWriteWatchTableImmediate, WriteWatchTable); + CALC_TABLE_LOCATION(m_pRegionToGenTableImmediate, RegionToGeneration); + CALC_TABLE_LOCATION(m_pRegionShrDest, RegionShr); + CALC_TABLE_LOCATION(m_pLowerBoundImmediate, Lower); + CALC_TABLE_LOCATION(m_pUpperBoundImmediate, Upper); + CALC_TABLE_LOCATION(m_pCardTableImmediate, CardTable); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + CALC_TABLE_LOCATION(m_pCardBundleTableImmediate, CardBundleTable); +#endif + +#if defined(TARGET_ARM64) + CALC_TABLE_LOCATION(m_lowestAddress, LowestAddress); + CALC_TABLE_LOCATION(m_highestAddress, HighestAddress); +#if defined(WRITE_BARRIER_CHECK) + CALC_TABLE_LOCATION(m_pGCShadow, GCShadow); + CALC_TABLE_LOCATION(m_pGCShadowEnd, GCShadowEnd); +#endif // WRITE_BARRIER_CHECK +#endif // TARGET_AMD64 + +#endif // !WRITE_BARRIER_VARS_INLINE + +#if !defined(CODECOVERAGE) && defined(WRITE_BARRIER_VARS_INLINE) Validate(); #endif } +template int updateVariable(PBYTE loc, T value) +{ + if (*(T*)loc != value) + { + ExecutableWriterHolder varWriterHolder((T*)loc, sizeof(T)); + *varWriterHolder.GetRW() = value; + return SWB_ICACHE_FLUSH; + } + return SWB_PASS; +} + bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, bool bUseBitwiseWriteBarrier, WriteBarrierType* pNewWriteBarrierType) { // Init code for the JIT_WriteBarrier assembly routine. Since it will be bashed everytime the GC Heap @@ -782,6 +472,8 @@ int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) return stompWBCompleteActions; #endif +#if defined(WRITE_BARRIER_VARS_INLINE) + switch (m_currentWriteBarrier) { case WRITE_BARRIER_POSTGROW64: @@ -792,45 +484,44 @@ int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - { - // Change immediate if different from new g_ephermeral_high. - if (*(UINT64*)m_pUpperBoundImmediate != (size_t)g_ephemeral_high) - { - ExecutableWriterHolder upperBoundWriterHolder((UINT64*)m_pUpperBoundImmediate, sizeof(UINT64)); - *upperBoundWriterHolder.GetRW() = (size_t)g_ephemeral_high; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } - } + stompWBCompleteActions |= updateVariable(m_pUpperBoundImmediate, (size_t)g_ephemeral_high); FALLTHROUGH; + case WRITE_BARRIER_PREGROW64: #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_PREGROW64: #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - { - // Change immediate if different from new g_ephermeral_low. - if (*(UINT64*)m_pLowerBoundImmediate != (size_t)g_ephemeral_low) - { - ExecutableWriterHolder lowerBoundImmediateWriterHolder((UINT64*)m_pLowerBoundImmediate, sizeof(UINT64)); - *lowerBoundImmediateWriterHolder.GetRW() = (size_t)g_ephemeral_low; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } + stompWBCompleteActions |= updateVariable(m_pLowerBoundImmediate, (size_t)g_ephemeral_low); break; - } #ifdef FEATURE_SVR_GC case WRITE_BARRIER_SVR64: #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_SVR64: #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - { break; - } #endif // FEATURE_SVR_GC default: UNREACHABLE_MSG("unexpected m_currentWriteBarrier in UpdateEphemeralBounds"); } +#else + + stompWBCompleteActions |= updateVariable(m_pUpperBoundImmediate, (size_t)g_ephemeral_high); + stompWBCompleteActions |= updateVariable(m_pLowerBoundImmediate, (size_t)g_ephemeral_low); +#endif //WRITE_BARRIER_VARS_INLINE + + +#if defined(TARGET_ARM64) + stompWBCompleteActions |= updateVariable(m_lowestAddress, (size_t)g_lowest_address); + stompWBCompleteActions |= updateVariable(m_highestAddress, (size_t)g_highest_address); +#if defined(WRITE_BARRIER_CHECK) + stompWBCompleteActions |= updateVariable(m_pGCShadow, (size_t)g_GCShadow); + stompWBCompleteActions |= updateVariable(m_pGCShadowEnd, (size_t)g_GCShadowEnd); +#endif // WRITE_BARRIER_CHECK +#endif // TARGET_AMD64 + return stompWBCompleteActions; } @@ -854,6 +545,7 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus #endif #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +#if defined(WRITE_BARRIER_VARS_INLINE) switch (m_currentWriteBarrier) { case WRITE_BARRIER_WRITE_WATCH_PREGROW64: @@ -863,65 +555,52 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus #endif // FEATURE_SVR_GC case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: - if (*(UINT64*)m_pWriteWatchTableImmediate != (size_t)g_write_watch_table) - { - ExecutableWriterHolder writeWatchTableImmediateWriterHolder((UINT64*)m_pWriteWatchTableImmediate, sizeof(UINT64)); - *writeWatchTableImmediateWriterHolder.GetRW() = (size_t)g_write_watch_table; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } + stompWBCompleteActions |= updateVariable(m_pWriteWatchTableImmediate, (size_t)g_write_watch_table); break; default: - break; // clang seems to require all enum values to be covered for some reason + break; } +#else + stompWBCompleteActions |= updateVariable(m_pWriteWatchTableImmediate, (size_t)g_write_watch_table); +#endif #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + +#if defined(WRITE_BARRIER_VARS_INLINE) switch (m_currentWriteBarrier) { case WRITE_BARRIER_BYTE_REGIONS64: case WRITE_BARRIER_BIT_REGIONS64: case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: - if (*(UINT64*)m_pRegionToGenTableImmediate != (size_t)g_region_to_generation_table) - { - ExecutableWriterHolder writeWatchTableImmediateWriterHolder((UINT64*)m_pRegionToGenTableImmediate, sizeof(UINT64)); - *writeWatchTableImmediateWriterHolder.GetRW() = (size_t)g_region_to_generation_table; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } - if (*m_pRegionShrDest != g_region_shr) - { - ExecutableWriterHolder writeWatchTableImmediateWriterHolder(m_pRegionShrDest, sizeof(UINT8)); - *writeWatchTableImmediateWriterHolder.GetRW() = g_region_shr; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } - if (*m_pRegionShrSrc != g_region_shr) - { - ExecutableWriterHolder writeWatchTableImmediateWriterHolder(m_pRegionShrSrc, sizeof(UINT8)); - *writeWatchTableImmediateWriterHolder.GetRW() = g_region_shr; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } + stompWBCompleteActions |= updateVariable(m_pRegionToGenTableImmediate, (size_t)g_region_to_generation_table); + stompWBCompleteActions |= updateVariable(m_pRegionShrDest, (size_t)g_region_shr); + stompWBCompleteActions |= updateVariable(m_pRegionShrSrc, (size_t)g_region_shr); break; default: - break; // clang seems to require all enum values to be covered for some reason - } - - if (*(UINT64*)m_pCardTableImmediate != (size_t)g_card_table) - { - ExecutableWriterHolder cardTableImmediateWriterHolder((UINT64*)m_pCardTableImmediate, sizeof(UINT64)); - *cardTableImmediateWriterHolder.GetRW() = (size_t)g_card_table; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; + break; } +#else + stompWBCompleteActions |= updateVariable(m_pRegionToGenTableImmediate, (size_t)g_region_to_generation_table); + stompWBCompleteActions |= updateVariable(m_pRegionShrDest, g_region_shr); +#endif //WRITE_BARRIER_VARS_INLINE + stompWBCompleteActions |= updateVariable(m_pCardTableImmediate, (size_t)g_card_table); #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - if (*(UINT64*)m_pCardBundleTableImmediate != (size_t)g_card_bundle_table) - { - ExecutableWriterHolder cardBundleTableImmediateWriterHolder((UINT64*)m_pCardBundleTableImmediate, sizeof(UINT64)); - *cardBundleTableImmediateWriterHolder.GetRW() = (size_t)g_card_bundle_table; - stompWBCompleteActions |= SWB_ICACHE_FLUSH; - } + stompWBCompleteActions |= updateVariable(m_pCardBundleTableImmediate, (size_t)g_card_bundle_table); #endif +#if defined(TARGET_ARM64) + stompWBCompleteActions |= updateVariable(m_lowestAddress, (size_t)g_lowest_address); + stompWBCompleteActions |= updateVariable(m_highestAddress, (size_t)g_highest_address); +#if defined(WRITE_BARRIER_CHECK) + stompWBCompleteActions |= updateVariable(m_pGCShadow, (size_t)g_GCShadow); + stompWBCompleteActions |= updateVariable(m_pGCShadowEnd, (size_t)g_GCShadowEnd); +#endif // WRITE_BARRIER_CHECK +#endif // TARGET_AMD64 + return stompWBCompleteActions; } @@ -1003,7 +682,406 @@ int WriteBarrierManager::SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended) } #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -// This function bashes the super fast amd64 version of the JIT_WriteBarrier + +#if defined(WRITE_BARRIER_VARS_INLINE) + + +// Use this somewhat hokey macro to concatenate the function start with the patch +// label. This allows the code below to look relatively nice, but relies on the +// naming convention which we have established for these helpers. +#define CALC_PATCH_LOCATION(func,label,offset) CalculatePatchLocation((PVOID)func, (PVOID)func##_##label, offset) + +PBYTE WriteBarrierManager::CalculatePatchLocation(LPVOID base, LPVOID label, int inlineOffset) +{ + // the label should always come after or at the entrypoint for this funtion + _ASSERTE_ALL_BUILDS((LPBYTE)label >= (LPBYTE)base); + + BYTE* patchBase = GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier); + return (patchBase + ((LPBYTE)GetEEFuncEntryPoint(label) - (LPBYTE)GetEEFuncEntryPoint(base))) + inlineOffset; +} + +// Deactivate alignment validation for code coverage builds +// because the instrumentation tool will not preserve alignment +// constraints and we will fail. +#if !defined(CODECOVERAGE) + +void WriteBarrierManager::Validate() +{ + CONTRACTL + { + MODE_ANY; + GC_NOTRIGGER; + NOTHROW; + } + CONTRACTL_END; + + // we have an invariant that the addresses of all the values that we update in our write barrier + // helpers must be naturally aligned, this is so that the update can happen atomically since there + // are places where these values are updated while the EE is running + // NOTE: we can't call this from the ctor since our infrastructure isn't ready for assert dialogs + + PBYTE pLowerBoundImmediate, pUpperBoundImmediate, pCardTableImmediate; + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + PBYTE pCardBundleTableImmediate; +#endif + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); + + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + +#ifdef FEATURE_SVR_GC + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +#endif // FEATURE_SVR_GC + + PBYTE pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + PBYTE pWriteWatchTableImmediate; + + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); + + _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); + + _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + +#ifdef FEATURE_SVR_GC + pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pWriteWatchTableImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +#endif // FEATURE_SVR_GC + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +} + +#endif // CODECOVERAGE + +void WriteBarrierManager::UpdatePatchLocations(WriteBarrierType newWriteBarrier) +{ + switch (newWriteBarrier) + { + case WRITE_BARRIER_PREGROW64: + { + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_Lower, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PreGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } + + case WRITE_BARRIER_POSTGROW64: + { + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_PostGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_SVR64: + { + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_SVR64, PatchLabel_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } +#endif // FEATURE_SVR_GC + + case WRITE_BARRIER_BYTE_REGIONS64: + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + case WRITE_BARRIER_BIT_REGIONS64: + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + case WRITE_BARRIER_WRITE_WATCH_PREGROW64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_WriteWatchTable, 2); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_Lower, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PreGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } + + case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_WriteWatchTable, 2); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_PostGrow64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } + +#ifdef FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_SVR64: + { + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_WriteWatchTable, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_SVR64, PatchLabel_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + } +#endif // FEATURE_SVR_GC + + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_WriteWatchTable, 2); + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_WriteWatchTable, 2); + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + + default: + UNREACHABLE_MSG("unexpected write barrier type!"); + } +} + +#endif // WRITE_BARRIER_VARS_INLINE + + +// This function bashes the super fast version of the JIT_WriteBarrier // helper. It should be called by the GC whenever the ephermeral region // bounds get changed, but still remain on the top of the GC Heap. int StompWriteBarrierEphemeral(bool isRuntimeSuspended) @@ -1016,7 +1094,7 @@ int StompWriteBarrierEphemeral(bool isRuntimeSuspended) return SWB_PASS; } -// This function bashes the super fast amd64 versions of the JIT_WriteBarrier +// This function bashes the super fast versions of the JIT_WriteBarrier // helpers. It should be called by the GC whenever the ephermeral region gets moved // from being at the top of the GC Heap, and/or when the cards table gets moved. int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck) diff --git a/src/runtime/src/coreclr/vm/writebarriermanager.h b/src/runtime/src/coreclr/vm/writebarriermanager.h new file mode 100644 index 00000000000..c8b8bc697b3 --- /dev/null +++ b/src/runtime/src/coreclr/vm/writebarriermanager.h @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// =========================================================================== +// File: writebarriermanager.h +// =========================================================================== + + +#ifndef WRITEBARRIERMANAGER_H +#define WRITEBARRIERMANAGER_H + +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + + +#if defined(TARGET_AMD64) +// Write barrier variables are inlined into the assembly code +#define WRITE_BARRIER_VARS_INLINE +// Else: Write barrier variables are in a table separate to the asm code +#endif + +class WriteBarrierManager +{ +public: + enum WriteBarrierType + { + WRITE_BARRIER_UNINITIALIZED, + WRITE_BARRIER_PREGROW64, + WRITE_BARRIER_POSTGROW64, +#ifdef FEATURE_SVR_GC + WRITE_BARRIER_SVR64, +#endif // FEATURE_SVR_GC + WRITE_BARRIER_BYTE_REGIONS64, + WRITE_BARRIER_BIT_REGIONS64, +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + WRITE_BARRIER_WRITE_WATCH_PREGROW64, + WRITE_BARRIER_WRITE_WATCH_POSTGROW64, +#ifdef FEATURE_SVR_GC + WRITE_BARRIER_WRITE_WATCH_SVR64, +#endif // FEATURE_SVR_GC + WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64, + WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64, +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + WRITE_BARRIER_BUFFER + }; + + WriteBarrierManager(); + void Initialize(); + + int UpdateEphemeralBounds(bool isRuntimeSuspended); + int UpdateWriteWatchAndCardTableLocations(bool isRuntimeSuspended, bool bReqUpperBoundsCheck); + +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + int SwitchToWriteWatchBarrier(bool isRuntimeSuspended); + int SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended); +#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + size_t GetCurrentWriteBarrierSize(); + +private: + size_t GetSpecificWriteBarrierSize(WriteBarrierType writeBarrier); + PCODE GetCurrentWriteBarrierCode(); + int ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended); + bool NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, bool bUseBitwiseWriteBarrier, WriteBarrierType* pNewWriteBarrierType); + + +#if defined(WRITE_BARRIER_VARS_INLINE) + PBYTE CalculatePatchLocation(LPVOID base, LPVOID label, int offset); + void Validate(); + void UpdatePatchLocations(WriteBarrierType newWriteBarrier); +#endif // WRITE_BARRIER_VARS_INLINE + + + WriteBarrierType m_currentWriteBarrier; + + PBYTE m_pWriteWatchTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pLowerBoundImmediate; // PREGROW | POSTGROW | | WRITE_WATCH | REGION + PBYTE m_pCardTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pCardBundleTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pUpperBoundImmediate; // | POSTGROW | | WRITE_WATCH | REGION + PBYTE m_pRegionToGenTableImmediate; // | | | WRITE_WATCH | REGION + PBYTE m_pRegionShrDest; // | | | WRITE_WATCH | REGION + PBYTE m_pRegionShrSrc; // | | | WRITE_WATCH | RETION + +#if defined(TARGET_ARM64) + PBYTE m_lowestAddress; + PBYTE m_highestAddress; +#if defined(WRITE_BARRIER_CHECK) + PBYTE m_pGCShadow; + PBYTE m_pGCShadowEnd; +#endif // WRITE_BARRIER_CHECK +#endif // TARGET_AMD64 + +}; + +extern WriteBarrierManager g_WriteBarrierManager; + +#endif // TARGET_AMD64 || TARGET_ARM64 + +#endif // WRITEBARRIERMANAGER_H diff --git a/src/runtime/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs b/src/runtime/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs index e4b71ded239..7db07c0a176 100644 --- a/src/runtime/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs +++ b/src/runtime/src/libraries/Common/src/System/Security/Cryptography/CryptoPool.cs @@ -12,12 +12,12 @@ internal static class CryptoPool internal static byte[] Rent(int minimumLength) => ArrayPool.Shared.Rent(minimumLength); - internal static void Return(ArraySegment arraySegment) + internal static void Return(ArraySegment arraySegment, int clearSize = ClearAll) { Debug.Assert(arraySegment.Array != null); Debug.Assert(arraySegment.Offset == 0); - Return(arraySegment.Array, arraySegment.Count); + Return(arraySegment.Array, clearSize == ClearAll ? arraySegment.Count : clearSize); } internal static void Return(byte[] array, int clearSize = ClearAll) diff --git a/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs b/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs index 1f49d6af945..5e3131573b5 100644 --- a/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs +++ b/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.Shared.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Security.Cryptography.Tests; using System.Security.Cryptography.SLHDsa.Tests; using Xunit; using Xunit.Sdk; @@ -10,6 +11,9 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio { public static partial class PrivateKeyAssociationTests { + private static partial Func CopyWithPrivateKey_MLKem { get; } + private static partial Func GetMLKemPublicKey { get; } + private static partial Func GetMLKemPrivateKey { get; } private static partial Func CopyWithPrivateKey_SlhDsa { get; } private static partial Func GetSlhDsaPublicKey { get; } private static partial Func GetSlhDsaPrivateKey { get; } @@ -143,6 +147,153 @@ public static void CheckCopyWithPrivateKey_SlhDsa_OtherSlhDsa() } } + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void GetMLKemPublicKey_WithoutPrivateKey() + { + using (X509Certificate2 cert = MLKemCertTests.LoadCertificateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) + using (MLKem certKey = GetMLKemPublicKey(cert)) + { + Assert.NotNull(certKey); + AssertExtensions.SequenceEqual(MLKemTestData.IetfMlKem512Spki, certKey.ExportSubjectPublicKeyInfo()); + + certKey.Encapsulate(out byte[] ciphertext, out _); + Assert.ThrowsAny(() => certKey.Decapsulate(ciphertext)); + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void GetMLKemPublicKey_WithPrivateKey() + { + using (X509Certificate2 cert = X509CertificateLoader.LoadPkcs12( + MLKemTestData.IetfMlKem512PrivateKeySeedPfx, + MLKemTestData.EncryptedPrivateKeyPassword)) + using (MLKem certKey = GetMLKemPublicKey(cert)) + { + AssertExtensions.TrueExpression(cert.HasPrivateKey); + Assert.NotNull(certKey); + AssertExtensions.SequenceEqual(MLKemTestData.IetfMlKem512Spki, certKey.ExportSubjectPublicKeyInfo()); + + certKey.Encapsulate(out byte[] ciphertext, out _); + Assert.ThrowsAny(() => certKey.Decapsulate(ciphertext)); + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void GetMLKemPrivateKey_NoPrivateKey() + { + using (X509Certificate2 cert = MLKemCertTests.LoadCertificateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) + using (MLKem certKey = GetMLKemPrivateKey(cert)) + { + Assert.Null(certKey); + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void GetMLKemPrivateKey_WithPrivateKey() + { + using (X509Certificate2 cert = X509CertificateLoader.LoadPkcs12( + MLKemTestData.IetfMlKem512PrivateKeySeedPfx, + MLKemTestData.EncryptedPrivateKeyPassword)) + using (MLKem certKey = GetMLKemPrivateKey(cert)) + { + AssertExtensions.TrueExpression(cert.HasPrivateKey); + AssertExtensions.SequenceEqual(MLKemTestData.IncrementalSeed, certKey.ExportPrivateSeed()); + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void CheckCopyWithPrivateKey_MLKem() + { + using (X509Certificate2 pubOnly = MLKemCertTests.LoadCertificateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) + using (MLKem privKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem512PrivateKeySeed)) + using (X509Certificate2 wrongAlg = X509CertificateLoader.LoadCertificate(TestData.CertWithEnhancedKeyUsage)) + { + CheckCopyWithPrivateKey( + pubOnly, + wrongAlg, + privKey, + [ + () => MLKem.GenerateKey(MLKemAlgorithm.MLKem512), + () => MLKem.GenerateKey(MLKemAlgorithm.MLKem768), + () => MLKem.GenerateKey(MLKemAlgorithm.MLKem1024), + ], + CopyWithPrivateKey_MLKem, + GetMLKemPublicKey, + GetMLKemPrivateKey, + (priv, pub) => + { + pub.Encapsulate(out byte[] ciphertext, out byte[] pubSharedSecret); + byte[] privSharedSecret = priv.Decapsulate(ciphertext); + AssertExtensions.SequenceEqual(pubSharedSecret, privSharedSecret); + }); + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void CheckCopyWithPrivateKey_MLKem_OtherMLKem_Seed() + { + using (X509Certificate2 pubOnly = MLKemCertTests.LoadCertificateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) + using (MLKemContract contract = new(MLKemAlgorithm.MLKem512)) + { + contract.OnExportPrivateSeedCore = (Span destination) => + { + MLKemTestData.IncrementalSeed.CopyTo(destination); + }; + + contract.OnExportEncapsulationKeyCore = (Span destination) => + { + using MLKem publicKem = MLKem.ImportSubjectPublicKeyInfo(MLKemTestData.IetfMlKem512Spki); + publicKem.ExportEncapsulationKey(destination); + }; + + using (X509Certificate2 cert = CopyWithPrivateKey_MLKem(pubOnly, contract)) + { + AssertExtensions.TrueExpression(cert.HasPrivateKey); + + using (MLKem kem = GetMLKemPrivateKey(cert)) + { + AssertExtensions.SequenceEqual(MLKemTestData.IncrementalSeed, kem.ExportPrivateSeed()); + } + } + } + } + + [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] + public static void CheckCopyWithPrivateKey_MLKem_OtherMLKem_DecapsulationKey() + { + using (X509Certificate2 pubOnly = MLKemCertTests.LoadCertificateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) + using (MLKemContract contract = new(MLKemAlgorithm.MLKem512)) + { + contract.OnExportPrivateSeedCore = (Span destination) => + { + throw new CryptographicException("Should signal to try decaps key"); + }; + + contract.OnExportDecapsulationKeyCore = (Span destination) => + { + MLKemTestData.IetfMlKem512PrivateKeyDecapsulationKey.AsSpan().CopyTo(destination); + }; + + contract.OnExportEncapsulationKeyCore = (Span destination) => + { + using MLKem publicKem = MLKem.ImportSubjectPublicKeyInfo(MLKemTestData.IetfMlKem512Spki); + publicKem.ExportEncapsulationKey(destination); + }; + + using (X509Certificate2 cert = CopyWithPrivateKey_MLKem(pubOnly, contract)) + { + AssertExtensions.TrueExpression(cert.HasPrivateKey); + + using (MLKem kem = GetMLKemPrivateKey(cert)) + { + AssertExtensions.SequenceEqual( + MLKemTestData.IetfMlKem512PrivateKeyDecapsulationKey, + kem.ExportDecapsulationKey()); + } + } + } + } + private static partial void CheckCopyWithPrivateKey( X509Certificate2 cert, X509Certificate2 wrongAlgorithmCert, diff --git a/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/MLKemCertTests.cs b/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/MLKemCertTests.cs index eb3c0f056fb..42e94421a67 100644 --- a/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/MLKemCertTests.cs +++ b/src/runtime/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/MLKemCertTests.cs @@ -126,7 +126,7 @@ public static IEnumerable MLKemCertificatePublicKeys } } - private static X509Certificate2 LoadCertificateFromPem(string pem) + internal static X509Certificate2 LoadCertificateFromPem(string pem) { #if NET return X509Certificate2.CreateFromPem(pem); diff --git a/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateKeyAccessors.cs b/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateKeyAccessors.cs index 07bfeddc386..cdf40e81ee4 100644 --- a/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateKeyAccessors.cs +++ b/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateKeyAccessors.cs @@ -1,12 +1,14 @@ // 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.Generic; using System.Diagnostics.CodeAnalysis; +using System.Formats.Asn1; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; + +#if !NET10_0_OR_GREATER +using System.Security.Cryptography.Asn1; +#endif namespace System.Security.Cryptography.X509Certificates { @@ -15,6 +17,100 @@ namespace System.Security.Cryptography.X509Certificates /// public static class X509CertificateKeyAccessors { + /// + /// Gets the public key from this certificate. + /// + /// + /// The X.509 certificate that contains the public key. + /// + /// + /// The public key, or if this certificate does not have an ML-KEM public key. + /// + /// + /// is . + /// + /// + /// The certificate has an ML-KEM public key, but the platform does not support ML-KEM. + /// + /// + /// The public key was invalid, or otherwise could not be imported. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static MLKem? GetMLKemPublicKey(this X509Certificate2 certificate) + { +#if NET10_0_OR_GREATER + return certificate.GetMLKemPublicKey(); +#else + if (MLKemAlgorithm.FromOid(certificate.GetKeyAlgorithm()) is null) + { + return null; + } + + ArraySegment encoded = GetCertificateSubjectPublicKeyInfo(certificate); + + try + { + return MLKem.ImportSubjectPublicKeyInfo(encoded); + } + finally + { + // SubjectPublicKeyInfo does not need to clear since it's public + CryptoPool.Return(encoded, clearSize: 0); + } +#endif + } + + /// + /// Gets the private key from this certificate. + /// + /// + /// The X.509 certificate that contains the private key. + /// + /// + /// The private key, or if this certificate does not have an ML-KEM private key. + /// + /// + /// An error occurred accessing the private key. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static MLKem? GetMLKemPrivateKey(this X509Certificate2 certificate) => +#if NET10_0_OR_GREATER + certificate.GetMLKemPrivateKey(); +#else + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLKem))); +#endif + + /// + /// Combines a private key with a certificate containing the associated public key into a + /// new instance that can access the private key. + /// + /// + /// The X.509 certificate that contains the public key. + /// + /// + /// The ML-KEM private key that corresponds to the ML-KEM public key in this certificate. + /// + /// + /// A new certificate with the property set to . + /// The current certificate isn't modified. + /// + /// + /// or is . + /// + /// + /// The specified private key doesn't match the public key for this certificate. + /// + /// + /// The certificate already has an associated private key. + /// + [ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, MLKem privateKey) => +#if NET10_0_OR_GREATER + certificate.CopyWithPrivateKey(privateKey); +#else + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLKem))); +#endif + /// /// Gets the public key from this certificate. /// @@ -94,5 +190,36 @@ public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certific #else throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa))); #endif + +#if !NET10_0_OR_GREATER + private static ArraySegment GetCertificateSubjectPublicKeyInfo(X509Certificate2 certificate) + { + // We construct the SubjectPublicKeyInfo from the certificate as-is, parameters and all. Consumers + // decide if the parameters are good or not. + SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn + { + Algorithm = new AlgorithmIdentifierAsn + { + Algorithm = certificate.GetKeyAlgorithm(), + + // .NET Framework uses "empty" to indicate no value, not null, so normalize empty to null since + // the Asn types expect the parameters to be an ASN.1 ANY. + Parameters = certificate.GetKeyAlgorithmParameters() switch + { + null or { Length: 0 } => default(ReadOnlyMemory?), + byte[] array => array, + }, + }, + SubjectPublicKey = certificate.GetPublicKey(), + }; + + AsnWriter writer = new(AsnEncodingRules.DER); + spki.Encode(writer); + + byte[] rented = CryptoPool.Rent(writer.GetEncodedLength()); + int written = writer.Encode(rented); + return new ArraySegment(rented, offset: 0, count: written); + } +#endif } } diff --git a/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj index 94c7dc3e0ac..171882094f5 100644 --- a/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj +++ b/src/runtime/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj @@ -148,6 +148,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\SlhDsa\SlhDsaTestHelpers.cs" /> + CopyWithPrivateKey_MLKem => + X509CertificateKeyAccessors.CopyWithPrivateKey; + + private static partial Func GetMLKemPublicKey => + X509CertificateKeyAccessors.GetMLKemPublicKey; + + private static partial Func GetMLKemPrivateKey => + X509CertificateKeyAccessors.GetMLKemPrivateKey; + private static partial Func CopyWithPrivateKey_SlhDsa => X509CertificateKeyAccessors.CopyWithPrivateKey; diff --git a/src/runtime/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs b/src/runtime/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs index 1317ed240c0..28c0996bd2e 100644 --- a/src/runtime/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs +++ b/src/runtime/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs @@ -62,7 +62,7 @@ public Stack(IEnumerable collection) /// public int Capacity => _array.Length; - /// + /// bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj b/src/runtime/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj index 004fbbadd9a..189adf6b6c1 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj +++ b/src/runtime/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj @@ -47,6 +47,7 @@ + diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/BufferBuilder.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/BufferBuilder.cs index 2ca6684a405..bbb1afb25fb 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/BufferBuilder.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/BufferBuilder.cs @@ -34,6 +34,13 @@ internal void Append(byte value) _buffer[_offset++] = value; } + internal void Append(ReadOnlyMemory value) + { + EnsureBuffer(value.Length); + value.Span.CopyTo(_buffer.AsSpan(_offset)); + _offset += value.Length; + } + internal void Append(ReadOnlySpan value) { EnsureBuffer(value.Length); diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs index c414b047f11..6caf97c5d02 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs @@ -3,10 +3,13 @@ using System; using System.Collections.Specialized; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Mime; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { @@ -429,22 +432,28 @@ private void SetContent(bool allowUnicode) } } - internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode) + internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default) { - SetContent(allowUnicode); - _message.Send(writer, sendEnvelope, allowUnicode); + Task task = SendAsync(writer, sendEnvelope, allowUnicode, cancellationToken); + Debug.Assert(task.IsCompleted, "SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); } - internal IAsyncResult BeginSend(BaseWriter writer, bool allowUnicode, - AsyncCallback? callback, object? state) + internal IAsyncResult BeginSend(BaseWriter writer, bool sendEnvelope, bool allowUnicode, AsyncCallback callback, object? state) { - SetContent(allowUnicode); - return _message.BeginSend(writer, allowUnicode, callback, state); + return TaskToAsyncResult.Begin(SendAsync(writer, sendEnvelope, allowUnicode), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); } - internal void EndSend(IAsyncResult asyncResult) + internal async Task SendAsync(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { - _message.EndSend(asyncResult); + SetContent(allowUnicode); + await _message.SendAsync(writer, sendEnvelope, allowUnicode, cancellationToken).ConfigureAwait(false); } internal string BuildDeliveryStatusNotificationString() diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs index db52511ec6e..f45299e7115 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs @@ -5,6 +5,8 @@ using System.Net.Mime; using System.Runtime.ExceptionServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { @@ -139,7 +141,9 @@ internal string? Subject // extract the encoding from =?encoding?BorQ?blablalba?= inputEncoding = MimeBasePart.DecodeEncoding(value); } - catch (ArgumentException) { }; + catch (ArgumentException) + { + } if (inputEncoding != null && value != null) { @@ -240,94 +244,8 @@ internal MimeBasePart? Content #region Sending - internal void EmptySendCallback(IAsyncResult result) - { - Exception? e = null; - - if (result.CompletedSynchronously) - { - return; - } - - EmptySendContext context = (EmptySendContext)result.AsyncState!; - try - { - BaseWriter.EndGetContentStream(result).Close(); - } - catch (Exception ex) - { - e = ex; - } - context._result.InvokeCallback(e); - } - - internal sealed class EmptySendContext - { - internal EmptySendContext(BaseWriter writer, LazyAsyncResult result) - { - _writer = writer; - _result = result; - } - - internal LazyAsyncResult _result; - internal BaseWriter _writer; - } - - internal IAsyncResult BeginSend(BaseWriter writer, bool allowUnicode, - AsyncCallback? callback, object? state) - { - PrepareHeaders(allowUnicode); - writer.WriteHeaders(Headers, allowUnicode); - - if (Content != null) - { - return Content.BeginSend(writer, callback, allowUnicode, state); - } - else - { - LazyAsyncResult result = new LazyAsyncResult(this, state, callback); - IAsyncResult newResult = writer.BeginGetContentStream(EmptySendCallback, new EmptySendContext(writer, result)); - if (newResult.CompletedSynchronously) - { - BaseWriter.EndGetContentStream(newResult).Close(); - result.InvokeCallback(); - } - return result; - } - } - - internal void EndSend(IAsyncResult asyncResult) - { - ArgumentNullException.ThrowIfNull(asyncResult); - - if (Content != null) - { - Content.EndSend(asyncResult); - } - else - { - LazyAsyncResult? castedAsyncResult = asyncResult as LazyAsyncResult; - - if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this) - { - throw new ArgumentException(SR.net_io_invalidasyncresult); - } - - if (castedAsyncResult.EndCalled) - { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndSend))); - } - - castedAsyncResult.InternalWaitForCompletion(); - castedAsyncResult.EndCalled = true; - if (castedAsyncResult.Result is Exception e) - { - ExceptionDispatchInfo.Throw(e); - } - } - } - - internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode) + internal async Task SendAsync(BaseWriter writer, bool sendEnvelope, bool allowUnicode, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { if (sendEnvelope) { @@ -340,10 +258,11 @@ internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode) if (Content != null) { - Content.Send(writer, allowUnicode); + await Content.SendAsync(writer, allowUnicode, cancellationToken).ConfigureAwait(false); } else { + // No content to write, just close the stream writer.GetContentStream().Close(); } } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailWriter.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailWriter.cs index c643885d53a..9b8ea2ead14 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailWriter.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/MailWriter.cs @@ -40,7 +40,7 @@ internal override void WriteHeaders(NameValueCollection headers, bool allowUnico internal override void Close() { _bufferBuilder.Append("\r\n"u8); - Flush(null); + Flush(); _stream.Close(); } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/ReadWriteAdapter.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/ReadWriteAdapter.cs new file mode 100644 index 00000000000..a5fbdb6469d --- /dev/null +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/ReadWriteAdapter.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Mail +{ + internal interface IReadWriteAdapter + { + static abstract ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken); + static abstract ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken); + static abstract ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken); + static abstract Task FlushAsync(Stream stream, CancellationToken cancellationToken); + static abstract Task WaitAsync(TaskCompletionSource waiter); + } + + internal readonly struct AsyncReadWriteAdapter : IReadWriteAdapter + { + public static ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken) => + stream.ReadAsync(buffer, cancellationToken); + + public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => + stream.ReadAtLeastAsync(buffer, minimumBytes, throwOnEndOfStream, cancellationToken); + + public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) => + stream.WriteAsync(buffer, cancellationToken); + + public static Task FlushAsync(Stream stream, CancellationToken cancellationToken) => stream.FlushAsync(cancellationToken); + + public static Task WaitAsync(TaskCompletionSource waiter) => waiter.Task; + } + + internal readonly struct SyncReadWriteAdapter : IReadWriteAdapter + { + public static ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken) => + new ValueTask(stream.Read(buffer.Span)); + + public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => + new ValueTask(stream.ReadAtLeast(buffer.Span, minimumBytes, throwOnEndOfStream)); + + public static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + stream.Write(buffer.Span); + return default; + } + + public static Task FlushAsync(Stream stream, CancellationToken cancellationToken) + { + stream.Flush(); + return Task.CompletedTask; + } + + public static Task WaitAsync(TaskCompletionSource waiter) + { + waiter.Task.GetAwaiter().GetResult(); + return Task.CompletedTask; + } + } +} diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs index 884d3b18a38..5daccb14d32 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs @@ -892,7 +892,7 @@ private void SendMessageCallback(IAsyncResult result) { try { - _message!.EndSend(result); + MailMessage.EndSend(result); // If some recipients failed but not others, throw AFTER sending the message. Complete(_failedRecipientException, result.AsyncState!); } @@ -929,8 +929,7 @@ private void SendMailCallback(IAsyncResult result) } else { - _message!.BeginSend(_writer, - IsUnicodeSupported(), new AsyncCallback(SendMessageCallback), result.AsyncState!); + _message!.BeginSend(_writer, DeliveryMethod != SmtpDeliveryMethod.Network, IsUnicodeSupported(), new AsyncCallback(SendMessageCallback), result.AsyncState!); } } catch (Exception e) diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs index 60bab14537f..5c116f57960 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs @@ -2,204 +2,107 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Collections; using System.Globalization; using System.IO; using System.Net.Mime; using System.Runtime.ExceptionServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { internal static class CheckCommand { - private static readonly AsyncCallback s_onReadLine = new AsyncCallback(OnReadLine); - private static readonly AsyncCallback s_onWrite = new AsyncCallback(OnWrite); - - internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state) + internal static LineInfo Send(SmtpConnection conn) { - MultiAsyncResult multiResult = new MultiAsyncResult(conn, callback, state); - multiResult.Enter(); - IAsyncResult writeResult = conn.BeginFlush(s_onWrite, multiResult); - if (writeResult.CompletedSynchronously) - { - conn.EndFlush(writeResult); - multiResult.Leave(); - } - SmtpReplyReader reader = conn.Reader!.GetNextReplyReader(); - multiResult.Enter(); - - //this actually does a read on the stream. - IAsyncResult result = reader.BeginReadLine(s_onReadLine, multiResult); - if (result.CompletedSynchronously) - { - LineInfo info = SmtpReplyReader.EndReadLine(result); - if (!(multiResult.Result is Exception)) - multiResult.Result = info; - multiResult.Leave(); - } - multiResult.CompleteSequence(); - return multiResult; + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "CheckCommand.SendAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - - internal static object EndSend(IAsyncResult result, out string response) + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { - object commandResult = MultiAsyncResult.End(result)!; - if (commandResult is Exception e) - { - ExceptionDispatchInfo.Throw(e); - } + await conn.FlushAsync(cancellationToken).ConfigureAwait(false); + return await conn.Reader!.GetNextReplyReader().ReadLineAsync(cancellationToken).ConfigureAwait(false); + } + } - LineInfo info = (LineInfo)commandResult; - response = info.Line; - return info.StatusCode; + internal static class ReadLinesCommand + { + internal static LineInfo[] Send(SmtpConnection conn) + { + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "ReadLinesCommand.SendAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - private static void OnReadLine(IAsyncResult result) + internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback callback, object? state) { - if (!result.CompletedSynchronously) - { - MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!; - try - { - SmtpConnection conn = (SmtpConnection)multiResult.Context; - LineInfo info = SmtpReplyReader.EndReadLine(result); - if (!(multiResult.Result is Exception)) - multiResult.Result = info; - multiResult.Leave(); - } - catch (Exception e) - { - multiResult.Leave(e); - } - } + return TaskToAsyncResult.Begin(SendAsync(conn), callback, state); } - private static void OnWrite(IAsyncResult result) + internal static LineInfo[] EndSend(IAsyncResult asyncResult) { - if (!result.CompletedSynchronously) - { - MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!; - try - { - SmtpConnection conn = (SmtpConnection)multiResult.Context; - conn.EndFlush(result); - multiResult.Leave(); - } - catch (Exception e) - { - multiResult.Leave(e); - } - } + return TaskToAsyncResult.End(asyncResult); } - internal static SmtpStatusCode Send(SmtpConnection conn, out string response) + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { - conn.Flush(); - SmtpReplyReader reader = conn.Reader!.GetNextReplyReader(); - LineInfo info = reader.ReadLine(); - response = info.Line; - reader.Close(); - return info.StatusCode; + await conn.FlushAsync(cancellationToken).ConfigureAwait(false); + return await conn.Reader!.GetNextReplyReader().ReadLinesAsync(cancellationToken).ConfigureAwait(false); } } - internal static class ReadLinesCommand + internal static class AuthCommand { - private static readonly AsyncCallback s_onReadLines = new AsyncCallback(OnReadLines); - private static readonly AsyncCallback s_onWrite = new AsyncCallback(OnWrite); - - internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state) + internal static LineInfo Send(SmtpConnection conn, string type, string message) { - MultiAsyncResult multiResult = new MultiAsyncResult(conn, callback, state); - multiResult.Enter(); - IAsyncResult writeResult = conn.BeginFlush(s_onWrite, multiResult); - if (writeResult.CompletedSynchronously) - { - conn.EndFlush(writeResult); - multiResult.Leave(); - } - SmtpReplyReader reader = conn.Reader!.GetNextReplyReader(); - multiResult.Enter(); - IAsyncResult readLinesResult = reader.BeginReadLines(s_onReadLines, multiResult); - if (readLinesResult.CompletedSynchronously) - { - LineInfo[] lines = SmtpReplyReader.EndReadLines(readLinesResult); - if (!(multiResult.Result is Exception)) - multiResult.Result = lines; - multiResult.Leave(); - } - multiResult.CompleteSequence(); - return multiResult; + Task task = SendAsync(conn, type, message); + Debug.Assert(task.IsCompleted, "AuthCommand.SendAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - internal static LineInfo[] EndSend(IAsyncResult result) + internal static LineInfo Send(SmtpConnection conn, string? message) { - object commandResult = MultiAsyncResult.End(result)!; - if (commandResult is Exception e) - { - ExceptionDispatchInfo.Throw(e); - } - return (LineInfo[])commandResult; + Task task = SendAsync(conn, message); + Debug.Assert(task.IsCompleted, "AuthCommand.SendAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - private static void OnReadLines(IAsyncResult result) + internal static IAsyncResult BeginSend(SmtpConnection conn, string type, string message, AsyncCallback callback, object? state) { - if (!result.CompletedSynchronously) - { - MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!; - try - { - SmtpConnection conn = (SmtpConnection)multiResult.Context; - LineInfo[] lines = SmtpReplyReader.EndReadLines(result); - if (!(multiResult.Result is Exception)) - multiResult.Result = lines; - multiResult.Leave(); - } - catch (Exception e) - { - multiResult.Leave(e); - } - } + return TaskToAsyncResult.Begin(SendAsync(conn, type, message), callback, state); } - private static void OnWrite(IAsyncResult result) + internal static IAsyncResult BeginSend(SmtpConnection conn, string? message, AsyncCallback callback, object? state) { - if (!result.CompletedSynchronously) - { - MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!; - try - { - SmtpConnection conn = (SmtpConnection)multiResult.Context; - conn.EndFlush(result); - multiResult.Leave(); - } - catch (Exception e) - { - multiResult.Leave(e); - } - } + return TaskToAsyncResult.Begin(SendAsync(conn, message), callback, state); } - internal static LineInfo[] Send(SmtpConnection conn) + + internal static LineInfo EndSend(IAsyncResult asyncResult) { - conn.Flush(); - return conn.Reader!.GetNextReplyReader().ReadLines(); + return TaskToAsyncResult.End(asyncResult); } - } - internal static class AuthCommand - { - internal static IAsyncResult BeginSend(SmtpConnection conn, string type, string message, AsyncCallback? callback, object? state) + internal static async Task SendAsync(SmtpConnection conn, string type, string message, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, type, message); - return ReadLinesCommand.BeginSend(conn, callback, state); + LineInfo[] lines = await ReadLinesCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + return CheckResponse(lines); } - internal static IAsyncResult BeginSend(SmtpConnection conn, string? message, AsyncCallback? callback, object? state) + internal static async Task SendAsync(SmtpConnection conn, string? message, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, message); - return ReadLinesCommand.BeginSend(conn, callback, state); + LineInfo[] lines = await ReadLinesCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + return CheckResponse(lines); } private static LineInfo CheckResponse(LineInfo[] lines) @@ -212,10 +115,6 @@ private static LineInfo CheckResponse(LineInfo[] lines) return lines[0]; } - internal static LineInfo EndSend(IAsyncResult result) - { - return CheckResponse(ReadLinesCommand.EndSend(result)); - } private static void PrepareCommand(SmtpConnection conn, string type, string message) { conn.BufferBuilder.Append(SmtpCommands.Auth); @@ -230,26 +129,33 @@ private static void PrepareCommand(SmtpConnection conn, string? message) conn.BufferBuilder.Append(message); conn.BufferBuilder.Append(SmtpCommands.CRLF); } + } - internal static LineInfo Send(SmtpConnection conn, string type, string message) + internal static class DataCommand + { + internal static void Send(SmtpConnection conn) { - PrepareCommand(conn, type, message); - return CheckResponse(ReadLinesCommand.Send(conn)); + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "DataCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); } - internal static LineInfo Send(SmtpConnection conn, string? message) + internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback callback, object? state) { - PrepareCommand(conn, message); - return CheckResponse(ReadLinesCommand.Send(conn)); + return TaskToAsyncResult.Begin(SendAsync(conn), callback, state); } - } - internal static class DataCommand - { - internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state) + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn); - return CheckCommand.BeginSend(conn, callback, state); + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + CheckResponse(info.StatusCode, info.Line); } private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse) @@ -274,13 +180,6 @@ private static void CheckResponse(SmtpStatusCode statusCode, string serverRespon } } - internal static void EndSend(IAsyncResult result) - { - string response; - SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response); - CheckResponse(statusCode, response); - } - private static void PrepareCommand(SmtpConnection conn) { if (conn.IsStreamOpen) @@ -290,18 +189,35 @@ private static void PrepareCommand(SmtpConnection conn) conn.BufferBuilder.Append(SmtpCommands.Data); } + } + internal static class DataStopCommand + { internal static void Send(SmtpConnection conn) + { + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "DataStopCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn); - string response; - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - CheckResponse(statusCode, response); + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + CheckResponse(info.StatusCode, info.Line); } - } - internal static class DataStopCommand - { private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse) { switch (statusCode) @@ -335,21 +251,33 @@ private static void PrepareCommand(SmtpConnection conn) conn.BufferBuilder.Append(SmtpCommands.DataStop); } - internal static void Send(SmtpConnection conn) - { - PrepareCommand(conn); - string response; - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - CheckResponse(statusCode, response); - } } internal static class EHelloCommand { - internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback? callback, object? state) + internal static string[] Send(SmtpConnection conn, string domain) + { + Task task = SendAsync(conn, domain); + Debug.Assert(task.IsCompleted, "EHelloCommand.SendAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn, domain), callback, state); + } + + internal static string[] EndSend(IAsyncResult asyncResult) + { + return TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, string domain, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, domain); - return ReadLinesCommand.BeginSend(conn, callback, state); + LineInfo[] lines = await ReadLinesCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + return CheckResponse(lines); } private static string[] CheckResponse(LineInfo[] lines) @@ -375,10 +303,6 @@ private static string[] CheckResponse(LineInfo[] lines) return extensions; } - internal static string[] EndSend(IAsyncResult result) - { - return CheckResponse(ReadLinesCommand.EndSend(result)); - } private static void PrepareCommand(SmtpConnection conn, string domain) { if (conn.IsStreamOpen) @@ -390,20 +314,33 @@ private static void PrepareCommand(SmtpConnection conn, string domain) conn.BufferBuilder.Append(domain); conn.BufferBuilder.Append(SmtpCommands.CRLF); } - - internal static string[] Send(SmtpConnection conn, string domain) - { - PrepareCommand(conn, domain); - return CheckResponse(ReadLinesCommand.Send(conn)); - } } internal static class HelloCommand { - internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback? callback, object? state) + internal static void Send(SmtpConnection conn, string domain) + { + Task task = SendAsync(conn, domain); + Debug.Assert(task.IsCompleted, "HelloCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn, domain), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, string domain, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, domain); - return CheckCommand.BeginSend(conn, callback, state); + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + CheckResponse(info.StatusCode, info.Line); } private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse) @@ -426,13 +363,6 @@ private static void CheckResponse(SmtpStatusCode statusCode, string serverRespon } } - internal static void EndSend(IAsyncResult result) - { - string response; - SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response); - CheckResponse(statusCode, response); - } - private static void PrepareCommand(SmtpConnection conn, string domain) { if (conn.IsStreamOpen) @@ -444,22 +374,33 @@ private static void PrepareCommand(SmtpConnection conn, string domain) conn.BufferBuilder.Append(domain); conn.BufferBuilder.Append(SmtpCommands.CRLF); } - - internal static void Send(SmtpConnection conn, string domain) - { - PrepareCommand(conn, domain); - string response; - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - CheckResponse(statusCode, response); - } } internal static class StartTlsCommand { - internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state) + internal static void Send(SmtpConnection conn) + { + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "StartTlsCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn); - return CheckCommand.BeginSend(conn, callback, state); + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + CheckResponse(info.StatusCode, info.Line); } private static void CheckResponse(SmtpStatusCode statusCode, string response) @@ -484,13 +425,6 @@ private static void CheckResponse(SmtpStatusCode statusCode, string response) } } - internal static void EndSend(IAsyncResult result) - { - string response; - SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response); - CheckResponse(statusCode, response); - } - private static void PrepareCommand(SmtpConnection conn) { if (conn.IsStreamOpen) @@ -501,23 +435,39 @@ private static void PrepareCommand(SmtpConnection conn) conn.BufferBuilder.Append(SmtpCommands.StartTls); conn.BufferBuilder.Append(SmtpCommands.CRLF); } - - internal static void Send(SmtpConnection conn) - { - PrepareCommand(conn); - string response; - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - CheckResponse(statusCode, response); - } } internal static class MailCommand { - internal static IAsyncResult BeginSend(SmtpConnection conn, ReadOnlySpan command, MailAddress from, - bool allowUnicode, AsyncCallback? callback, object? state) + internal static void Send(SmtpConnection conn, ReadOnlySpan command, MailAddress from, bool allowUnicode) + { + Task task = SendAsync(conn, command, from, allowUnicode); + Debug.Assert(task.IsCompleted, "MailCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, ReadOnlySpan command, MailAddress from, bool allowUnicode, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn, command, from, allowUnicode), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static Task SendAsync(SmtpConnection conn, ReadOnlySpan command, MailAddress from, bool allowUnicode, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, command, from, allowUnicode); - return CheckCommand.BeginSend(conn, callback, state); + return SendAndCheck(conn, cancellationToken); + + static async Task SendAndCheck(SmtpConnection conn, CancellationToken cancellationToken) + { + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + CheckResponse(info.StatusCode, info.Line); + return info; + } } private static void CheckResponse(SmtpStatusCode statusCode, string response) @@ -543,13 +493,6 @@ private static void CheckResponse(SmtpStatusCode statusCode, string response) } } - internal static void EndSend(IAsyncResult result) - { - string response; - SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response); - CheckResponse(statusCode, response); - } - private static void PrepareCommand(SmtpConnection conn, ReadOnlySpan command, MailAddress from, bool allowUnicode) { if (conn.IsStreamOpen) @@ -565,22 +508,37 @@ private static void PrepareCommand(SmtpConnection conn, ReadOnlySpan comma } conn.BufferBuilder.Append(SmtpCommands.CRLF); } - - internal static void Send(SmtpConnection conn, ReadOnlySpan command, MailAddress from, bool allowUnicode) - { - PrepareCommand(conn, command, from, allowUnicode); - string response; - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - CheckResponse(statusCode, response); - } } internal static class RecipientCommand { - internal static IAsyncResult BeginSend(SmtpConnection conn, string to, AsyncCallback? callback, object? state) + internal static bool Send(SmtpConnection conn, string to, out string response) + { + Task<(bool success, string response)> task = SendAsync(conn, to); + Debug.Assert(task.IsCompleted, "RecipientCommand.SendAsync should be completed synchronously."); + (bool success, string r) = task.GetAwaiter().GetResult(); + response = r; + return success; + } + + internal static IAsyncResult BeginSend(SmtpConnection conn, string to, AsyncCallback callback, object? state) + { + return TaskToAsyncResult.Begin(SendAsync(conn, to), callback, state); + } + + internal static bool EndSend(IAsyncResult asyncResult, out string response) + { + (bool success, string r) = TaskToAsyncResult.End<(bool success, string response)>(asyncResult); + response = r; + return success; + } + + internal static async Task<(bool success, string response)> SendAsync(SmtpConnection conn, string to, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter { PrepareCommand(conn, to); - return CheckCommand.BeginSend(conn, callback, state); + LineInfo info = await CheckCommand.SendAsync(conn, cancellationToken).ConfigureAwait(false); + return (CheckResponse(info.StatusCode, info.Line), info.Line); } private static bool CheckResponse(SmtpStatusCode statusCode, string response) @@ -613,12 +571,6 @@ private static bool CheckResponse(SmtpStatusCode statusCode, string response) } } - internal static bool EndSend(IAsyncResult result, out string response) - { - SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response); - return CheckResponse(statusCode, response); - } - private static void PrepareCommand(SmtpConnection conn, string to) { if (conn.IsStreamOpen) @@ -630,18 +582,35 @@ private static void PrepareCommand(SmtpConnection conn, string to) conn.BufferBuilder.Append(to, true); // Unicode validation was done prior conn.BufferBuilder.Append(SmtpCommands.CRLF); } + } + internal static class QuitCommand + { + internal static void Send(SmtpConnection conn) + { + Task task = SendAsync(conn); + Debug.Assert(task.IsCompleted, "QuitCommand.SendAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); + } - internal static bool Send(SmtpConnection conn, string to, out string response) + internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback callback, object? state) { - PrepareCommand(conn, to); - SmtpStatusCode statusCode = CheckCommand.Send(conn, out response); - return CheckResponse(statusCode, response); + return TaskToAsyncResult.Begin(SendAsync(conn), callback, state); + } + + internal static void EndSend(IAsyncResult asyncResult) + { + TaskToAsyncResult.End(asyncResult); + } + + internal static async Task SendAsync(SmtpConnection conn, CancellationToken cancellationToken = default) + where TIOAdapter : IReadWriteAdapter + { + PrepareCommand(conn); + await conn.FlushAsync(cancellationToken).ConfigureAwait(false); + // We don't read any response to match the synchronous behavior } - } - internal static class QuitCommand - { private static void PrepareCommand(SmtpConnection conn) { if (conn.IsStreamOpen) @@ -651,17 +620,6 @@ private static void PrepareCommand(SmtpConnection conn) conn.BufferBuilder.Append(SmtpCommands.Quit); } - - internal static void Send(SmtpConnection conn) - { - PrepareCommand(conn); - - // We simply flush and don't read the response - // to avoid blocking call that will impact users - // that are using async api, since this code - // will run on Dispose() - conn.Flush(); - } } internal static class SmtpCommands diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs index 3dfc0d9214d..e33e3da411a 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs @@ -14,6 +14,7 @@ using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { @@ -105,19 +106,26 @@ internal IAsyncResult BeginGetConnection(ContextAwareResult outerResult, AsyncCa internal IAsyncResult BeginFlush(AsyncCallback? callback, object? state) { - return _stream!.BeginWrite(_bufferBuilder.GetBuffer(), 0, _bufferBuilder.Length, callback, state); + return TaskToAsyncResult.Begin(FlushAsync(CancellationToken.None), callback, state); } - internal void EndFlush(IAsyncResult result) + internal static void EndFlush(IAsyncResult result) { - _stream!.EndWrite(result); + TaskToAsyncResult.End(result); + } + + internal async Task FlushAsync(CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter + { + await TIOAdapter.WriteAsync(_stream!, _bufferBuilder.GetBuffer().AsMemory(0, _bufferBuilder.Length), cancellationToken).ConfigureAwait(false); _bufferBuilder.Reset(); } + internal void Flush() { - _stream!.Write(_bufferBuilder.GetBuffer(), 0, _bufferBuilder.Length); - _bufferBuilder.Reset(); + Task task = FlushAsync(CancellationToken.None); + Debug.Assert(task.IsCompleted, "FlushAsync should be completed synchronously."); + task.GetAwaiter().GetResult(); } private void ShutdownConnection(bool isAbort) diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReader.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReader.cs index 7887ac3c6d7..a2de4cd060e 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReader.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReader.cs @@ -2,13 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { //streams are read only; return of 0 means end of server's reply - internal sealed class SmtpReplyReader + internal sealed class SmtpReplyReader : IDisposable { + public void Dispose() + { + Close(); + } + private readonly SmtpReplyReaderFactory _reader; internal SmtpReplyReader(SmtpReplyReaderFactory reader) @@ -16,39 +24,65 @@ internal SmtpReplyReader(SmtpReplyReaderFactory reader) _reader = reader; } - internal IAsyncResult BeginReadLines(AsyncCallback? callback, object? state) + public void Close() { - return _reader.BeginReadLines(this, callback, state); + _reader.Close(this); } - internal IAsyncResult BeginReadLine(AsyncCallback? callback, object? state) + internal LineInfo[] ReadLines() { - return _reader.BeginReadLine(this, callback, state); + Task task = ReadLinesAsync(); + + Debug.Assert(task.IsCompleted, "ReadLinesAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - public void Close() + internal IAsyncResult BeginReadLines(AsyncCallback callback, object? state) { - _reader.Close(this); + return TaskToAsyncResult.Begin(ReadLinesAsync(), callback, state); } - internal static LineInfo[] EndReadLines(IAsyncResult result) + internal static LineInfo[] EndReadLines(IAsyncResult asyncResult) { - return SmtpReplyReaderFactory.EndReadLines(result); + return TaskToAsyncResult.End(asyncResult); } - internal static LineInfo EndReadLine(IAsyncResult result) + internal LineInfo ReadLine() { - return SmtpReplyReaderFactory.EndReadLine(result); + Task task = ReadLineAsync(); + + Debug.Assert(task.IsCompleted, "ReadLineAsync should be completed synchronously."); + return task.GetAwaiter().GetResult(); } - internal LineInfo[] ReadLines() + internal IAsyncResult BeginReadLine(AsyncCallback callback, object? state) { - return _reader.ReadLines(this); + return TaskToAsyncResult.Begin(ReadLineAsync(), callback, state); } - internal LineInfo ReadLine() + internal static LineInfo EndReadLine(IAsyncResult asyncResult) + { + return TaskToAsyncResult.End(asyncResult); + } + + internal Task ReadLinesAsync() + { + return ReadLinesAsync(); + } + + internal Task ReadLineAsync() + { + return ReadLineAsync(); + } + + internal Task ReadLinesAsync(CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter + { + return _reader.ReadLinesAsync(this, false, cancellationToken); + } + + internal Task ReadLineAsync(CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter { - return _reader.ReadLine(this); + return _reader.ReadLineAsync(this, cancellationToken); } } } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs index 03d32a7e44d..66be00167e3 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mail { @@ -53,20 +56,6 @@ internal SmtpStatusCode StatusCode } } - internal IAsyncResult BeginReadLines(SmtpReplyReader caller, AsyncCallback? callback, object? state) - { - ReadLinesAsyncResult result = new ReadLinesAsyncResult(this, callback, state); - result.Read(caller); - return result; - } - - internal IAsyncResult BeginReadLine(SmtpReplyReader caller, AsyncCallback? callback, object? state) - { - ReadLinesAsyncResult result = new ReadLinesAsyncResult(this, callback, state, true); - result.Read(caller); - return result; - } - internal void Close(SmtpReplyReader caller) { if (_currentReader == caller) @@ -82,21 +71,6 @@ internal void Close(SmtpReplyReader caller) } } - internal static LineInfo[] EndReadLines(IAsyncResult result) - { - return ReadLinesAsyncResult.End(result); - } - - internal static LineInfo EndReadLine(IAsyncResult result) - { - LineInfo[] info = ReadLinesAsyncResult.End(result); - if (info != null && info.Length > 0) - { - return info[0]; - } - return default; - } - internal SmtpReplyReader GetNextReplyReader() { _currentReader?.Close(); @@ -298,29 +272,48 @@ internal LineInfo[] ReadLines(SmtpReplyReader caller) } internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine) + { + Task task = ReadLinesAsync(caller, oneLine); + Debug.Assert(task.IsCompleted, "ReadLinesAsync should complete synchronously for SyncReadWriteAdapter"); + + return task.GetAwaiter().GetResult(); + } + + internal Task ReadLinesAsync(SmtpReplyReader caller, bool oneLine = false, CancellationToken cancellationToken = default) + { + return ReadLinesAsync(caller, oneLine, cancellationToken); + } + + internal async Task ReadLinesAsync(SmtpReplyReader caller, bool oneLine = false, CancellationToken cancellationToken = default) where TIOAdapter : IReadWriteAdapter { if (caller != _currentReader || _readState == ReadState.Done) { return Array.Empty(); } - _byteBuffer ??= new byte[SmtpReplyReaderFactory.DefaultBufferSize]; - + _byteBuffer ??= new byte[DefaultBufferSize]; System.Diagnostics.Debug.Assert(_readState == ReadState.Status0); var builder = new StringBuilder(); var lines = new List(); int statusRead = 0; - for (int start = 0, read = 0; ;) + int start = 0; + int read = 0; + + while (true) { if (start == read) { - read = _bufferedStream.Read(_byteBuffer); start = 0; + read = await TIOAdapter.ReadAsync(_bufferedStream, _byteBuffer, cancellationToken).ConfigureAwait(false); + if (read == 0) + { + throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); + } } - int actual = ProcessRead(_byteBuffer.AsSpan(start, read), true); + int actual = ProcessRead(_byteBuffer!.AsSpan(start, read - start), true); if (statusRead < 4) { @@ -340,160 +333,36 @@ internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine) if (_readState == ReadState.Status0) { statusRead = 0; - lines.Add(new LineInfo(_statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF + lines.Add(new LineInfo(_statusCode, builder.ToString(0, builder.Length - 2))); // Exclude CRLF if (oneLine) { - _bufferedStream.Push(_byteBuffer.AsSpan(start, read - start)); + _bufferedStream.Push(_byteBuffer!.AsSpan(start, read - start)); return lines.ToArray(); } - builder = new StringBuilder(); + + builder.Clear(); } else if (_readState == ReadState.Done) { - lines.Add(new LineInfo(_statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF - _bufferedStream.Push(_byteBuffer.AsSpan(start, read - start)); + lines!.Add(new LineInfo(_statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF + _bufferedStream.Push(_byteBuffer!.AsSpan(start, read - start)); return lines.ToArray(); } } + } - private sealed class ReadLinesAsyncResult : LazyAsyncResult + internal async Task ReadLineAsync(SmtpReplyReader caller) { - private StringBuilder? _builder; - private List? _lines; - private readonly SmtpReplyReaderFactory _parent; - private static readonly AsyncCallback s_readCallback = new AsyncCallback(ReadCallback); - private int _read; - private int _statusRead; - private readonly bool _oneLine; - - internal ReadLinesAsyncResult(SmtpReplyReaderFactory parent, AsyncCallback? callback, object? state) : base(null, state, callback) - { - _parent = parent; - } - - internal ReadLinesAsyncResult(SmtpReplyReaderFactory parent, AsyncCallback? callback, object? state, bool oneLine) : base(null, state, callback) - { - _oneLine = oneLine; - _parent = parent; - } - - internal void Read(SmtpReplyReader caller) - { - // if we've already found the delimitter, then return 0 indicating - // end of stream. - if (_parent._currentReader != caller || _parent._readState == ReadState.Done) - { - InvokeCallback(); - return; - } - - _parent._byteBuffer ??= new byte[SmtpReplyReaderFactory.DefaultBufferSize]; - - System.Diagnostics.Debug.Assert(_parent._readState == ReadState.Status0); - - _builder = new StringBuilder(); - _lines = new List(); - - Read(); - } - - internal static LineInfo[] End(IAsyncResult result) - { - ReadLinesAsyncResult thisPtr = (ReadLinesAsyncResult)result; - thisPtr.InternalWaitForCompletion(); - return thisPtr._lines!.ToArray(); - } - - private void Read() - { - do - { - IAsyncResult result = _parent._bufferedStream.BeginRead(_parent._byteBuffer!, 0, _parent._byteBuffer!.Length, s_readCallback, this); - if (!result.CompletedSynchronously) - { - return; - } - _read = _parent._bufferedStream.EndRead(result); - } while (ProcessRead()); - } - - private static void ReadCallback(IAsyncResult result) - { - if (!result.CompletedSynchronously) - { - Exception? exception = null; - ReadLinesAsyncResult thisPtr = (ReadLinesAsyncResult)result.AsyncState!; - try - { - thisPtr._read = thisPtr._parent._bufferedStream.EndRead(result); - if (thisPtr.ProcessRead()) - { - thisPtr.Read(); - } - } - catch (Exception e) - { - exception = e; - } - - if (exception != null) - { - thisPtr.InvokeCallback(exception); - } - } - } - - private bool ProcessRead() - { - if (_read == 0) - { - throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); - } - - for (int start = 0; start != _read;) - { - int actual = _parent.ProcessRead(_parent._byteBuffer!.AsSpan(start, _read - start), true); - - if (_statusRead < 4) - { - int left = Math.Min(4 - _statusRead, actual); - _statusRead += left; - start += left; - actual -= left; - if (actual == 0) - { - continue; - } - } - - _builder!.Append(Encoding.UTF8.GetString(_parent._byteBuffer!, start, actual)); - start += actual; + LineInfo[] lines = await ReadLinesAsync(caller, oneLine: true).ConfigureAwait(false); + return lines.Length > 0 ? lines[0] : default; + } - if (_parent._readState == ReadState.Status0) - { - _lines!.Add(new LineInfo(_parent._statusCode, _builder.ToString(0, _builder.Length - 2))); // return everything except CRLF - _builder = new StringBuilder(); - _statusRead = 0; - - if (_oneLine) - { - _parent._bufferedStream.Push(_parent._byteBuffer!.AsSpan(start, _read - start)); - InvokeCallback(); - return false; - } - } - else if (_parent._readState == ReadState.Done) - { - _lines!.Add(new LineInfo(_parent._statusCode, _builder.ToString(0, _builder.Length - 2))); // return everything except CRLF - _parent._bufferedStream.Push(_parent._byteBuffer!.AsSpan(start, _read - start)); - InvokeCallback(); - return false; - } - } - return true; - } + internal async Task ReadLineAsync(SmtpReplyReader caller, CancellationToken cancellationToken) where TIOAdapter : IReadWriteAdapter + { + LineInfo[] lines = await ReadLinesAsync(caller, oneLine: true, cancellationToken).ConfigureAwait(false); + return lines.Length > 0 ? lines[0] : default; } } } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs index e06de7affa2..8370bf8a26c 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs @@ -316,7 +316,7 @@ private void SendToCollection() { while (_toIndex < _toCollection.Count) { - MultiAsyncResult result = (MultiAsyncResult)RecipientCommand.BeginSend(_connection, + IAsyncResult result = RecipientCommand.BeginSend(_connection, _toCollection[_toIndex++].GetSmtpAddress(_allowUnicode) + _deliveryNotify, s_sendToCollectionCompleted, this); if (!result.CompletedSynchronously) diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/BaseWriter.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/BaseWriter.cs index 3b59251a0cc..8c0286cf160 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/BaseWriter.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/BaseWriter.cs @@ -15,7 +15,6 @@ internal abstract class BaseWriter // In MailWriter, all encoding has already been done so this will only fold lines // that are NOT encoded already, which means being less conservative is ok. private const int DefaultLineLength = 76; - private static readonly AsyncCallback s_onWrite = OnWrite; protected readonly BufferBuilder _bufferBuilder; protected readonly Stream _stream; @@ -97,9 +96,7 @@ private void WriteAndFold(string value, int charsAlreadyOnLine, bool allowUnicod #region Content - internal Stream GetContentStream() => GetContentStream(null); - - private ClosableStream GetContentStream(MultiAsyncResult? multiResult) + internal Stream GetContentStream() { if (_isInContent) { @@ -111,84 +108,26 @@ private ClosableStream GetContentStream(MultiAsyncResult? multiResult) CheckBoundary(); _bufferBuilder.Append("\r\n"u8); - Flush(multiResult); + Flush(); ClosableStream cs = new ClosableStream(new EightBitStream(_stream, _shouldEncodeLeadingDots), _onCloseHandler); _contentStream = cs; return cs; } - internal IAsyncResult BeginGetContentStream(AsyncCallback? callback, object? state) - { - MultiAsyncResult multiResult = new MultiAsyncResult(this, callback, state); - - Stream s = GetContentStream(multiResult); - - if (!(multiResult.Result is Exception)) - { - multiResult.Result = s; - } - - multiResult.CompleteSequence(); - - return multiResult; - } - - internal static Stream EndGetContentStream(IAsyncResult result) - { - object o = MultiAsyncResult.End(result)!; - if (o is Exception e) - { - ExceptionDispatchInfo.Throw(e); - } - return (Stream)o; - } - #endregion Content #region Cleanup - protected void Flush(MultiAsyncResult? multiResult) + protected void Flush() { if (_bufferBuilder.Length > 0) { - if (multiResult != null) - { - multiResult.Enter(); - IAsyncResult result = _stream.BeginWrite(_bufferBuilder.GetBuffer(), 0, - _bufferBuilder.Length, s_onWrite, multiResult); - if (result.CompletedSynchronously) - { - _stream.EndWrite(result); - multiResult.Leave(); - } - } - else - { - _stream.Write(_bufferBuilder.GetBuffer(), 0, _bufferBuilder.Length); - } + _stream.Write(_bufferBuilder.GetBuffer(), 0, _bufferBuilder.Length); _bufferBuilder.Reset(); } } - protected static void OnWrite(IAsyncResult result) - { - if (!result.CompletedSynchronously) - { - MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!; - BaseWriter thisPtr = (BaseWriter)multiResult.Context; - try - { - thisPtr._stream.EndWrite(result); - multiResult.Leave(); - } - catch (Exception e) - { - multiResult.Leave(e); - } - } - } - internal abstract void Close(); protected abstract void OnClose(object? sender, EventArgs args); diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs index 804c067eb20..a8d2e0436fa 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs @@ -4,10 +4,12 @@ using System.Collections.Specialized; using System.Net.Mail; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mime { - internal class MimeBasePart + internal abstract class MimeBasePart { internal const string DefaultCharSet = "utf-8"; @@ -186,46 +188,6 @@ internal void PrepareHeaders(bool allowUnicode) } } - internal virtual void Send(BaseWriter writer, bool allowUnicode) - { - throw new NotImplementedException(); - } - - internal virtual IAsyncResult BeginSend(BaseWriter writer, AsyncCallback? callback, - bool allowUnicode, object? state) - { - throw new NotImplementedException(); - } - - internal void EndSend(IAsyncResult asyncResult) - { - ArgumentNullException.ThrowIfNull(asyncResult); - - LazyAsyncResult? castedAsyncResult = asyncResult as MimePartAsyncResult; - - if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this) - { - throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult)); - } - - if (castedAsyncResult.EndCalled) - { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndSend))); - } - - castedAsyncResult.InternalWaitForCompletion(); - castedAsyncResult.EndCalled = true; - if (castedAsyncResult.Result is Exception) - { - throw (Exception)castedAsyncResult.Result; - } - } - - internal sealed class MimePartAsyncResult : LazyAsyncResult - { - internal MimePartAsyncResult(MimeBasePart part, object? state, AsyncCallback? callback) : base(part, state, callback) - { - } - } + internal abstract Task SendAsync(BaseWriter writer, bool allowUnicode, CancellationToken cancellationToken) where TIOAdapter : IReadWriteAdapter; } } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs index b9b2038c507..e0ffef86590 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeMultiPart.cs @@ -7,6 +7,7 @@ using System.IO; using System.Runtime.ExceptionServices; using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mime { @@ -14,8 +15,6 @@ internal sealed class MimeMultiPart : MimeBasePart { private Collection? _parts; private static int s_boundary; - private AsyncCallback? _mimePartSentCallback; - private bool _allowUnicode; internal MimeMultiPart(MimeMultiPartType type) { @@ -42,182 +41,7 @@ private void SetType(MimeMultiPartType type) internal Collection Parts => _parts ??= new Collection(); - internal static void Complete(IAsyncResult result, Exception? e) - { - //if we already completed and we got called again, - //it mean's that there was an exception in the callback and we - //should just rethrow it. - - MimePartContext context = (MimePartContext)result.AsyncState!; - - if (context._completed) - { - ExceptionDispatchInfo.Throw(e!); - } - - try - { - context._outputStream!.Close(); - } - catch (Exception ex) - { - e ??= ex; - } - context._completed = true; - context._result.InvokeCallback(e); - } - - internal void MimeWriterCloseCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - MimeWriterCloseCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - private static void MimeWriterCloseCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - ((MimeWriter)context._writer).EndClose(result); - Complete(result, null); - } - - internal void MimePartSentCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - MimePartSentCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - private void MimePartSentCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - MimeBasePart part = (MimeBasePart)context._partsEnumerator.Current; - part.EndSend(result); - - if (context._partsEnumerator.MoveNext()) - { - part = (MimeBasePart)context._partsEnumerator.Current; - IAsyncResult sendResult = part.BeginSend(context._writer, _mimePartSentCallback!, _allowUnicode, context); - if (sendResult.CompletedSynchronously) - { - MimePartSentCallbackHandler(sendResult); - } - return; - } - else - { - IAsyncResult closeResult = ((MimeWriter)context._writer).BeginClose(new AsyncCallback(MimeWriterCloseCallback), context); - if (closeResult.CompletedSynchronously) - { - MimeWriterCloseCallbackHandler(closeResult); - } - } - } - - internal void ContentStreamCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - ContentStreamCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - private void ContentStreamCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - context._outputStream = BaseWriter.EndGetContentStream(result); - context._writer = new MimeWriter(context._outputStream!, ContentType.Boundary!); - if (context._partsEnumerator.MoveNext()) - { - MimeBasePart part = (MimeBasePart)context._partsEnumerator.Current; - - _mimePartSentCallback = new AsyncCallback(MimePartSentCallback); - IAsyncResult sendResult = part.BeginSend(context._writer, _mimePartSentCallback, _allowUnicode, context); - if (sendResult.CompletedSynchronously) - { - MimePartSentCallbackHandler(sendResult); - } - return; - } - else - { - IAsyncResult closeResult = ((MimeWriter)context._writer).BeginClose(new AsyncCallback(MimeWriterCloseCallback), context); - if (closeResult.CompletedSynchronously) - { - MimeWriterCloseCallbackHandler(closeResult); - } - } - } - - internal override IAsyncResult BeginSend(BaseWriter writer, AsyncCallback? callback, bool allowUnicode, - object? state) - { - _allowUnicode = allowUnicode; - PrepareHeaders(allowUnicode); - writer.WriteHeaders(Headers, allowUnicode); - MimePartAsyncResult result = new MimePartAsyncResult(this, state, callback); - MimePartContext context = new MimePartContext(writer, result, Parts.GetEnumerator()); - IAsyncResult contentResult = writer.BeginGetContentStream(new AsyncCallback(ContentStreamCallback), context); - if (contentResult.CompletedSynchronously) - { - ContentStreamCallbackHandler(contentResult); - } - return result; - } - - internal sealed class MimePartContext - { - internal MimePartContext(BaseWriter writer, LazyAsyncResult result, IEnumerator partsEnumerator) - { - _writer = writer; - _result = result; - _partsEnumerator = partsEnumerator; - } - - internal IEnumerator _partsEnumerator; - internal Stream? _outputStream; - internal LazyAsyncResult _result; - internal BaseWriter _writer; - internal bool _completed; - internal bool _completedSynchronously = true; - } - - internal override void Send(BaseWriter writer, bool allowUnicode) + internal override async Task SendAsync(BaseWriter writer, bool allowUnicode, CancellationToken cancellationToken = default) { PrepareHeaders(allowUnicode); writer.WriteHeaders(Headers, allowUnicode); @@ -226,7 +50,7 @@ internal override void Send(BaseWriter writer, bool allowUnicode) foreach (MimeBasePart part in Parts) { - part.Send(mimeWriter, allowUnicode); + await part.SendAsync(mimeWriter, allowUnicode, cancellationToken).ConfigureAwait(false); } mimeWriter.Close(); diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimePart.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimePart.cs index 85d20815d71..c95cf4f0346 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimePart.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimePart.cs @@ -8,6 +8,8 @@ using System.Net.Mail; using System.Runtime.ExceptionServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Mime { @@ -19,8 +21,6 @@ internal sealed class MimePart : MimeBasePart, IDisposable private Stream? _stream; private bool _streamSet; private bool _streamUsedOnce; - private AsyncCallback? _readCallback; - private AsyncCallback? _writeCallback; private const int maxBufferSize = 0x4400; //seems optimal for send based on perf analysis internal MimePart() { } @@ -139,98 +139,6 @@ internal void SetContent(Stream stream, ContentType? contentType) SetContent(stream); } - internal static void Complete(IAsyncResult result, Exception? e) - { - //if we already completed and we got called again, - //it mean's that there was an exception in the callback and we - //should just rethrow it. - - MimePartContext context = (MimePartContext)result.AsyncState!; - if (context._completed) - { - ExceptionDispatchInfo.Throw(e!); - } - - try - { - context._outputStream?.Close(); - } - catch (Exception ex) - { - e ??= ex; - } - context._completed = true; - context._result.InvokeCallback(e); - } - - - internal void ReadCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - ReadCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - internal void ReadCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - context._bytesLeft = Stream!.EndRead(result); - if (context._bytesLeft > 0) - { - IAsyncResult writeResult = context._outputStream!.BeginWrite(context._buffer, 0, context._bytesLeft, _writeCallback, context); - if (writeResult.CompletedSynchronously) - { - WriteCallbackHandler(writeResult); - } - } - else - { - Complete(result, null); - } - } - - internal void WriteCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - WriteCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - internal void WriteCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - context._outputStream!.EndWrite(result); - IAsyncResult readResult = Stream!.BeginRead(context._buffer, 0, context._buffer.Length, _readCallback, context); - if (readResult.CompletedSynchronously) - { - ReadCallbackHandler(readResult); - } - } - internal Stream GetEncodedStream(Stream stream) { Stream outputStream = stream; @@ -251,76 +159,7 @@ internal Stream GetEncodedStream(Stream stream) return outputStream; } - internal void ContentStreamCallbackHandler(IAsyncResult result) - { - MimePartContext context = (MimePartContext)result.AsyncState!; - Stream outputStream = BaseWriter.EndGetContentStream(result); - context._outputStream = GetEncodedStream(outputStream); - - _readCallback = new AsyncCallback(ReadCallback); - _writeCallback = new AsyncCallback(WriteCallback); - IAsyncResult readResult = Stream!.BeginRead(context._buffer, 0, context._buffer.Length, _readCallback, context); - if (readResult.CompletedSynchronously) - { - ReadCallbackHandler(readResult); - } - } - - internal void ContentStreamCallback(IAsyncResult result) - { - if (result.CompletedSynchronously) - { - return; - } - - ((MimePartContext)result.AsyncState!)._completedSynchronously = false; - - try - { - ContentStreamCallbackHandler(result); - } - catch (Exception e) - { - Complete(result, e); - } - } - - internal sealed class MimePartContext - { - internal MimePartContext(BaseWriter writer, LazyAsyncResult result) - { - _writer = writer; - _result = result; - _buffer = new byte[maxBufferSize]; - } - - internal Stream? _outputStream; - internal LazyAsyncResult _result; - internal int _bytesLeft; - internal BaseWriter _writer; - internal byte[] _buffer; - internal bool _completed; - internal bool _completedSynchronously = true; - } - - internal override IAsyncResult BeginSend(BaseWriter writer, AsyncCallback? callback, bool allowUnicode, object? state) - { - PrepareHeaders(allowUnicode); - writer.WriteHeaders(Headers, allowUnicode); - MimePartAsyncResult result = new MimePartAsyncResult(this, state, callback); - MimePartContext context = new MimePartContext(writer, result); - - ResetStream(); - _streamUsedOnce = true; - IAsyncResult contentResult = writer.BeginGetContentStream(new AsyncCallback(ContentStreamCallback), context); - if (contentResult.CompletedSynchronously) - { - ContentStreamCallbackHandler(contentResult); - } - return result; - } - - internal override void Send(BaseWriter writer, bool allowUnicode) + internal override async Task SendAsync(BaseWriter writer, bool allowUnicode, CancellationToken cancellationToken = default) { if (Stream != null) { @@ -332,15 +171,15 @@ internal override void Send(BaseWriter writer, bool allowUnicode) Stream outputStream = writer.GetContentStream(); outputStream = GetEncodedStream(outputStream); - int read; - ResetStream(); _streamUsedOnce = true; - while ((read = Stream.Read(buffer, 0, maxBufferSize)) > 0) + int read; + while ((read = await TIOAdapter.ReadAsync(Stream, buffer.AsMemory(0, maxBufferSize), cancellationToken).ConfigureAwait(false)) > 0) { - outputStream.Write(buffer, 0, read); + await TIOAdapter.WriteAsync(outputStream, buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); } + outputStream.Close(); } } diff --git a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeWriter.cs b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeWriter.cs index 8d9482b8ee1..bb8fd314104 100644 --- a/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeWriter.cs +++ b/src/runtime/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeWriter.cs @@ -35,37 +35,13 @@ internal override void WriteHeaders(NameValueCollection headers, bool allowUnico #region Cleanup - internal IAsyncResult BeginClose(AsyncCallback? callback, object? state) - { - MultiAsyncResult multiResult = new MultiAsyncResult(this, callback, state); - - Close(multiResult); - - multiResult.CompleteSequence(); - - return multiResult; - } - - internal void EndClose(IAsyncResult result) - { - MultiAsyncResult.End(result); - - _stream.Close(); - } - internal override void Close() - { - Close(null); - - _stream.Close(); - } - - private void Close(MultiAsyncResult? multiResult) { _bufferBuilder.Append("\r\n--"u8); _bufferBuilder.Append(_boundaryBytes); _bufferBuilder.Append("--\r\n"u8); - Flush(multiResult); + Flush(); + _stream.Close(); } /// diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoggingTest.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoggingTest.cs index 188dd1711f5..31225bbb60b 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoggingTest.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoggingTest.cs @@ -36,7 +36,7 @@ await RemoteExecutor.Invoke(() => listener.RunWithCallback(events.Enqueue, () => { // Invoke a test that'll cause some events to be generated - new SmtpClientTest().TestMailDelivery(); + new SmtpClientTest(null!).TestMailDelivery(); }); Assert.DoesNotContain(events, ev => ev.EventId == 0); // errors from the EventSource itself Assert.InRange(events.Count, 1, int.MaxValue); diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackServerTestBase.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackServerTestBase.cs index 0d00087e614..7c3c170de14 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackServerTestBase.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackServerTestBase.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.Diagnostics; using System.Net.NetworkInformation; using System.Net.Security; using System.Security.Authentication; @@ -45,6 +46,7 @@ public struct SendMailAsyncMethod : ISendMethodProvider public abstract class LoopbackServerTestBase : IDisposable where T : ISendMethodProvider { + private static TimeSpan s_PassingTestTimeout = Debugger.IsAttached ? TimeSpan.FromSeconds(10000) : TimeSpan.FromSeconds(30); protected LoopbackSmtpServer Server { get; private set; } protected ITestOutputHelper Output { get; private set; } @@ -152,13 +154,13 @@ public LoopbackServerTestBase(ITestOutputHelper output) protected async Task SendMail(MailMessage msg, CancellationToken cancellationToken = default) { - Exception? ex = await SendMailInternal(msg, cancellationToken, null); + Exception? ex = await SendMailInternal(msg, cancellationToken, null).WaitAsync(s_PassingTestTimeout); Assert.Null(ex); } protected async Task SendMail(MailMessage msg, CancellationToken cancellationToken = default, bool unwrapException = true, bool asyncDirectException = false) where TException : Exception { - Exception? ex = await SendMailInternal(msg, cancellationToken, asyncDirectException); + Exception? ex = await SendMailInternal(msg, cancellationToken, asyncDirectException).WaitAsync(s_PassingTestTimeout); if (unwrapException && T.SendMethod != SendMethod.Send && typeof(TException) != typeof(SmtpException)) { diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs index 29d9a978890..5cd541638b2 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs @@ -63,10 +63,11 @@ public LoopbackSmtpServer(ITestOutputHelper? output = null) { _output = output; _socketsToDispose = new ConcurrentBag(); - _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socketsToDispose.Add(_listenSocket); - _listenSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); + // if dual socket supported, bind to Ipv6Any, otherwise Any + _listenSocket.Bind(new IPEndPoint(_listenSocket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0)); Port = ((IPEndPoint)_listenSocket.LocalEndPoint).Port; _listenSocket.Listen(1); diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/MailMessageTest.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/MailMessageTest.cs index c73daa15422..b42390d49ad 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/MailMessageTest.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/MailMessageTest.cs @@ -11,8 +11,10 @@ using System.IO; using System.Reflection; +using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using Xunit; namespace System.Net.Mail.Tests @@ -259,13 +261,14 @@ private static (string Raw, string Attachment) DecodeSentMailMessage(MailMessage culture: null, activationAttributes: null); + var syncSendAdapterType = typeof(MailMessage).Assembly.GetTypes() + .FirstOrDefault(t => t.Name == "SyncReadWriteAdapter"); + // Send the message. - typeof(MailMessage).InvokeMember( - name: "Send", - invokeAttr: BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, - binder: null, - target: mail, - args: new object[] { mailWriter, true, true }); + typeof(MailMessage) + .GetMethod("SendAsync", BindingFlags.Instance | BindingFlags.NonPublic) + .MakeGenericMethod(syncSendAdapterType) + .Invoke(mail, new object[] { mailWriter, true, true, CancellationToken.None }); // Decode contents. string result = Encoding.UTF8.GetString(stream.ToArray()); diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientConnectionTest.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientConnectionTest.cs index 4ae8ccf4d69..7c52ba6d72c 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientConnectionTest.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientConnectionTest.cs @@ -22,6 +22,17 @@ public async Task SocketClosed() await SendMail(new MailMessage("mono@novell.com", "everyone@novell.com", "introduction", "hello")); } + [Fact] + public async Task UnrecognizedReply_Throws() + { + Server.OnCommandReceived = (command, arg) => + { + return "Go away"; + }; + + await SendMail(new MailMessage("mono@novell.com", "everyone@novell.com", "introduction", "hello")); + } + [Fact] public async Task EHelloNotRecognized_RestartWithHello() { diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs index 332b14b3bae..a3b25b27d79 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs @@ -20,6 +20,7 @@ using Microsoft.DotNet.RemoteExecutor; using System.Net.Test.Common; using Xunit; +using Xunit.Abstractions; namespace System.Net.Mail.Tests { @@ -53,6 +54,13 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + ITestOutputHelper _output; + + public SmtpClientTest(ITestOutputHelper output) + { + _output = output; + } + [Theory] [InlineData(SmtpDeliveryMethod.SpecifiedPickupDirectory)] [InlineData(SmtpDeliveryMethod.PickupDirectoryFromIis)] @@ -237,7 +245,7 @@ public async Task SendAsync_ServerDoesntExist_Throws() [Fact] public void TestMailDelivery() { - using var server = new LoopbackSmtpServer(); + using var server = new LoopbackSmtpServer(_output); using SmtpClient client = server.CreateClient(); client.Credentials = new NetworkCredential("foo", "bar"); MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", "hello", "howdydoo"); @@ -282,7 +290,7 @@ public void TestZeroTimeout() [Fact] public void SendMailAsync_CanBeCanceled_CancellationToken_SetAlready() { - using var server = new LoopbackSmtpServer(); + using var server = new LoopbackSmtpServer(_output); using SmtpClient client = server.CreateClient(); CancellationTokenSource cts = new CancellationTokenSource(); @@ -299,7 +307,7 @@ public void SendMailAsync_CanBeCanceled_CancellationToken_SetAlready() [Fact] public async Task SendMailAsync_CanBeCanceled_CancellationToken() { - using var server = new LoopbackSmtpServer(); + using var server = new LoopbackSmtpServer(_output); using SmtpClient client = server.CreateClient(); server.ReceiveMultipleConnections = true; diff --git a/src/runtime/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj b/src/runtime/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj index 03708443b2e..c59d1ab087d 100644 --- a/src/runtime/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj +++ b/src/runtime/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj @@ -110,6 +110,8 @@ Link="ProductionCode\MailHeaderInfo.cs" /> + collection) /// public int Capacity => _array.Length; - /// + /// bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Double.cs index fe89ca49146..5609339bea4 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -1944,7 +1944,7 @@ public static double RadiansToDegrees(double radians) /// public static (double Sin, double Cos) SinCos(double x) => Math.SinCos(x); - /// + /// public static (double SinPi, double CosPi) SinCosPi(double x) { // This code is based on `cospi` and `sinpi` from amd/aocl-libm-ose diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs index b4c2b88a428..e9e7274565b 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs @@ -715,11 +715,11 @@ public static Vector2 LoadUnsafe(ref readonly float source, nuint elementOffset) return Unsafe.ReadUnaligned(in address); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Log(Vector2 vector) => Vector128.Log(Vector4.Create(vector, 1.0f, 1.0f).AsVector128()).AsVector2(); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Log2(Vector2 vector) => Vector128.Log2(Vector4.Create(vector, 1.0f, 1.0f).AsVector128()).AsVector2(); diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs index dd92866c930..8a3633ccc42 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs @@ -748,11 +748,11 @@ public static Vector3 LoadUnsafe(ref readonly float source, nuint elementOffset) return Unsafe.ReadUnaligned(in address); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Log(Vector3 vector) => Vector128.Log(Vector4.Create(vector, 1.0f).AsVector128()).AsVector3(); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Log2(Vector3 vector) => Vector128.Log2(Vector4.Create(vector, 1.0f).AsVector128()).AsVector3(); diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs index 238b7b42c06..83c3cd0698b 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,7 @@ public static partial class AsyncHelpers // It will not capture/restore any local state that is live across it. [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion { ref AsyncHelpers.RuntimeAsyncAwaitState state = ref AsyncHelpers.t_runtimeAsyncAwaitState; @@ -34,6 +36,7 @@ public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INo // It will not capture/restore any local state that is live across it. [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { ref AsyncHelpers.RuntimeAsyncAwaitState state = ref AsyncHelpers.t_runtimeAsyncAwaitState; @@ -48,6 +51,7 @@ public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static T Await(Task task) { TaskAwaiter awaiter = task.GetAwaiter(); @@ -62,6 +66,7 @@ public static T Await(Task task) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void Await(Task task) { TaskAwaiter awaiter = task.GetAwaiter(); @@ -76,6 +81,7 @@ public static void Await(Task task) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static T Await(ValueTask task) { ValueTaskAwaiter awaiter = task.GetAwaiter(); @@ -90,6 +96,7 @@ public static T Await(ValueTask task) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void Await(ValueTask task) { ValueTaskAwaiter awaiter = task.GetAwaiter(); @@ -104,6 +111,7 @@ public static void Await(ValueTask task) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void Await(ConfiguredTaskAwaitable configuredAwaitable) { ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); @@ -118,6 +126,7 @@ public static void Await(ConfiguredTaskAwaitable configuredAwaitable) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static void Await(ConfiguredValueTaskAwaitable configuredAwaitable) { ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); @@ -132,6 +141,7 @@ public static void Await(ConfiguredValueTaskAwaitable configuredAwaitable) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static T Await(ConfiguredTaskAwaitable configuredAwaitable) { ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); @@ -146,6 +156,7 @@ public static T Await(ConfiguredTaskAwaitable configuredAwaitable) [Intrinsic] [BypassReadyToRun] [MethodImpl(MethodImplOptions.Async)] + [RequiresPreviewFeatures] public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) { ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); @@ -157,15 +168,25 @@ public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) return awaiter.GetResult(); } #else + [RequiresPreviewFeatures] public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static void Await(System.Threading.Tasks.Task task) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static T Await(System.Threading.Tasks.Task task) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static void Await(System.Threading.Tasks.ValueTask task) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static T Await(System.Threading.Tasks.ValueTask task) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static T Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + [RequiresPreviewFeatures] public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } #endif } diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs index c3c9e8a8fe1..eb6afdf66d1 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.cs @@ -54,7 +54,15 @@ public static bool IsSupported(string feature) { return feature switch { - PortablePdb or CovariantReturnsOfClasses or ByRefFields or ByRefLikeGenerics or UnmanagedSignatureCallingConvention or DefaultImplementationsOfInterfaces or VirtualStaticsInInterfaces or NumericIntPtr => true, + PortablePdb or + CovariantReturnsOfClasses or + ByRefFields or + ByRefLikeGenerics or + UnmanagedSignatureCallingConvention or + DefaultImplementationsOfInterfaces or + VirtualStaticsInInterfaces or + NumericIntPtr => true, + nameof(IsDynamicCodeSupported) => IsDynamicCodeSupported, nameof(IsDynamicCodeCompiled) => IsDynamicCodeCompiled, _ => false, diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index 5e2fb905df1..d542d13b39f 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs @@ -1247,7 +1247,7 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destinati /// public static NFloat Clamp(NFloat value, NFloat min, NFloat max) => new NFloat(NativeType.Clamp(value._value, min._value, max._value)); - /// + /// public static NFloat ClampNative(NFloat value, NFloat min, NFloat max) => new NFloat(NativeType.ClampNative(value._value, min._value, max._value)); /// @@ -1889,7 +1889,7 @@ public static (NFloat Sin, NFloat Cos) SinCos(NFloat x) return (new NFloat(sin), new NFloat(cos)); } - /// + /// public static (NFloat SinPi, NFloat CosPi) SinCosPi(NFloat x) { var (sinPi, cosPi) = NativeType.SinCosPi(x._value); diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index e8a8abe86ed..c5d877a3be0 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -3743,7 +3743,7 @@ public static Vector128 Sin(Vector128 vector) } } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector128 Sin, Vector128 Cos) SinCos(Vector128 vector) { @@ -3763,7 +3763,7 @@ public static (Vector128 Sin, Vector128 Cos) SinCos(Vector128 + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector128 Sin, Vector128 Cos) SinCos(Vector128 vector) { diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index 5387d209433..8f8580a1fb1 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -3735,7 +3735,7 @@ public static Vector256 Sin(Vector256 vector) } } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector256 Sin, Vector256 Cos) SinCos(Vector256 vector) { @@ -3755,7 +3755,7 @@ public static (Vector256 Sin, Vector256 Cos) SinCos(Vector256 + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector256 Sin, Vector256 Cos) SinCos(Vector256 vector) { diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index c2811af8249..29b10dba031 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -3753,7 +3753,7 @@ public static Vector512 Sin(Vector512 vector) } } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector512 Sin, Vector512 Cos) SinCos(Vector512 vector) { @@ -3773,7 +3773,7 @@ public static (Vector512 Sin, Vector512 Cos) SinCos(Vector512 + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static (Vector512 Sin, Vector512 Cos) SinCos(Vector512 vector) { diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64_1.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64_1.cs index e3e4d8c1f55..68e99650be0 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64_1.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64_1.cs @@ -528,7 +528,7 @@ private string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] stri /// static int ISimdVector, T>.Alignment => Vector64.Alignment; - /// + /// static int ISimdVector, T>.ElementCount => Vector64.Count; /// diff --git a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Single.cs index ce37952076f..400ec426cb9 100644 --- a/src/runtime/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/runtime/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -1860,7 +1860,7 @@ public static float RadiansToDegrees(float radians) /// public static (float Sin, float Cos) SinCos(float x) => MathF.SinCos(x); - /// + /// public static (float SinPi, float CosPi) SinCosPi(float x) { // This code is based on `cospif` and `sinpif` from amd/aocl-libm-ose diff --git a/src/runtime/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/runtime/src/libraries/System.Runtime/ref/System.Runtime.cs index ddbfd6ae568..89a5c81e868 100644 --- a/src/runtime/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/runtime/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13833,15 +13833,25 @@ public static void RunModuleConstructor(System.ModuleHandle module) { } [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5007", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public static partial class AsyncHelpers { + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion { } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void Await(System.Threading.Tasks.Task task) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static T Await(System.Threading.Tasks.Task task) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void Await(System.Threading.Tasks.ValueTask task) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static T Await(System.Threading.Tasks.ValueTask task) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static T Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + [System.Runtime.Versioning.RequiresPreviewFeaturesAttribute] public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } } public sealed partial class RuntimeWrappedException : System.Exception diff --git a/src/runtime/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs b/src/runtime/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs index 43c94066ee6..6ef1d73f8e1 100644 --- a/src/runtime/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs +++ b/src/runtime/src/libraries/System.Security.Cryptography/tests/X509Certificates/CertificateCreation/PrivateKeyAssociationTests.cs @@ -751,98 +751,14 @@ public static void CheckCopyWithPrivateKey_MLDSA() } } - [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] - public static void CheckCopyWithPrivateKey_MLKem() - { - using (X509Certificate2 pubOnly = X509Certificate2.CreateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) - using (MLKem privKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem512PrivateKeySeed)) - using (X509Certificate2 wrongAlg = X509CertificateLoader.LoadCertificate(TestData.CertWithEnhancedKeyUsage)) - { - CheckCopyWithPrivateKey( - pubOnly, - wrongAlg, - privKey, - [ - () => MLKem.GenerateKey(MLKemAlgorithm.MLKem512), - () => MLKem.GenerateKey(MLKemAlgorithm.MLKem768), - () => MLKem.GenerateKey(MLKemAlgorithm.MLKem1024), - ], - (cert, key) => cert.CopyWithPrivateKey(key), - cert => cert.GetMLKemPublicKey(), - cert => cert.GetMLKemPrivateKey(), - (priv, pub) => - { - pub.Encapsulate(out byte[] ciphertext, out byte[] pubSharedSecret); - byte[] privSharedSecret = priv.Decapsulate(ciphertext); - AssertExtensions.SequenceEqual(pubSharedSecret, privSharedSecret); - }); - } - } - - [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] - public static void CheckCopyWithPrivateKey_MLKem_OtherMLKem_Seed() - { - using (X509Certificate2 pubOnly = X509Certificate2.CreateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) - using (MLKemContract contract = new(MLKemAlgorithm.MLKem512)) - { - contract.OnExportPrivateSeedCore = (Span source) => - { - MLKemTestData.IncrementalSeed.CopyTo(source); - }; - - contract.OnExportEncapsulationKeyCore = (Span source) => - { - PublicKey publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(MLKemTestData.IetfMlKem512Spki, out _); - publicKey.EncodedKeyValue.RawData.AsSpan().CopyTo(source); - }; - - using (X509Certificate2 cert = pubOnly.CopyWithPrivateKey(contract)) - { - AssertExtensions.TrueExpression(cert.HasPrivateKey); - - using (MLKem kem = cert.GetMLKemPrivateKey()) - { - AssertExtensions.SequenceEqual(MLKemTestData.IncrementalSeed, kem.ExportPrivateSeed()); - } - } - } - } - - [ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))] - public static void CheckCopyWithPrivateKey_MLKem_OtherMLKem_DecapsulationKey() - { - using (X509Certificate2 pubOnly = X509Certificate2.CreateFromPem(MLKemTestData.IetfMlKem512CertificatePem)) - using (MLKemContract contract = new(MLKemAlgorithm.MLKem512)) - { - contract.OnExportPrivateSeedCore = (Span source) => - { - throw new CryptographicException("Should signal to try decaps key"); - }; - - contract.OnExportDecapsulationKeyCore = (Span source) => - { - MLKemTestData.IetfMlKem512PrivateKeyDecapsulationKey.AsSpan().CopyTo(source); - }; - - contract.OnExportEncapsulationKeyCore = (Span source) => - { - PublicKey publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(MLKemTestData.IetfMlKem512Spki, out _); - publicKey.EncodedKeyValue.RawData.AsSpan().CopyTo(source); - }; + private static partial Func CopyWithPrivateKey_MLKem => + (cert, key) => cert.CopyWithPrivateKey(key); - using (X509Certificate2 cert = pubOnly.CopyWithPrivateKey(contract)) - { - AssertExtensions.TrueExpression(cert.HasPrivateKey); + private static partial Func GetMLKemPublicKey => + cert => cert.GetMLKemPublicKey(); - using (MLKem kem = cert.GetMLKemPrivateKey()) - { - AssertExtensions.SequenceEqual( - MLKemTestData.IetfMlKem512PrivateKeyDecapsulationKey, - kem.ExportDecapsulationKey()); - } - } - } - } + private static partial Func GetMLKemPrivateKey => + cert => cert.GetMLKemPrivateKey(); private static partial Func CopyWithPrivateKey_SlhDsa => (cert, key) => cert.CopyWithPrivateKey(key); diff --git a/src/runtime/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/runtime/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs index dea63b89318..931b7976199 100644 --- a/src/runtime/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs +++ b/src/runtime/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs @@ -1817,8 +1817,6 @@ void EmitSwitchedBranches() writer.Indent++; // Emit the code for the branch, without the first character that was already matched in the switch. - RegexNode? remainder = null; - HandleChild: switch (child.Kind) { case RegexNodeKind.One: @@ -1830,9 +1828,7 @@ void EmitSwitchedBranches() case RegexNodeKind.Multi: // First character was handled by the switch. Emit matching code for the remainder of the multi string. sliceStaticPos++; - EmitNode(child.Str!.Length == 2 ? - new RegexNode(RegexNodeKind.One, child.Options, child.Str![1]) : - new RegexNode(RegexNodeKind.Multi, child.Options, child.Str!.Substring(1))); + EmitNode(SliceOffMultiFirstChar(child)); writer.WriteLine(); break; @@ -1840,25 +1836,31 @@ void EmitSwitchedBranches() // This is a concatenation where its first node is the starting literal we found and that starting literal // is one of the nodes above that we know how to handle completely. This is a common // enough case that we want to special-case it to avoid duplicating the processing for that character - // unnecessarily. So, we'll shave off that first node from the concatenation and then handle the remainder. - // Note that it's critical startingLiteralNode is something we can fully handle above: if it's not, - // we'll end up losing some of the pattern due to overwriting `remainder`. - remainder = child; - child = child.Child(0); - remainder.ReplaceChild(0, new RegexNode(RegexNodeKind.Empty, remainder.Options)); - goto HandleChild; // reprocess just the first node that was saved; the remainder will then be processed below + // unnecessarily. First slice off the first character that was already handled. If that child is a multi, temporarily + // replace it with a node that doesn't have the already-matched first character; otherwise, replace it with an empty node + // that'll be ignored when rendered. Then emit the new tree, and subsequently restore the original child. + sliceStaticPos++; + RegexNode originalFirst = child.Child(0); + child.ReplaceChild(0, + child.Child(0).Kind is RegexNodeKind.Multi ? + SliceOffMultiFirstChar(child.Child(0)) : // multi, so slice off the first character + new RegexNode(RegexNodeKind.Empty, child.Options)); // single, so removing it yields empty + try + { - default: - Debug.Assert(remainder is null); - remainder = child; + EmitNode(child); + } + finally + { + child.ReplaceChild(0, originalFirst); + } + writer.WriteLine(); break; - } - if (remainder is not null) - { - // Emit a full match for whatever part of the child we haven't yet handled. - EmitNode(remainder); - writer.WriteLine(); + default: + EmitNode(child); + writer.WriteLine(); + break; } // This is only ever used for atomic alternations, so we can simply reset the doneLabel @@ -1882,6 +1884,14 @@ void EmitSwitchedBranches() } } + static RegexNode SliceOffMultiFirstChar(RegexNode multi) + { + Debug.Assert(multi.Kind is RegexNodeKind.Multi, $"Expected a Multi node, got {multi.Kind}"); + return multi.Str!.Length == 2 ? + new(RegexNodeKind.One, multi.Options, multi.Str[1]) : + new(RegexNodeKind.Multi, multi.Options, multi.Str.Substring(1)); + } + void EmitAllBranches() { // Label to jump to when any branch completes successfully. diff --git a/src/runtime/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorOutputTests.cs b/src/runtime/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorOutputTests.cs index 57d4232ee1e..e8896708277 100644 --- a/src/runtime/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorOutputTests.cs +++ b/src/runtime/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorOutputTests.cs @@ -754,7 +754,7 @@ void UncaptureUntil(int capturePosition) } /// Helper methods used by generated -derived implementations. - [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "42.42.42.42")] + [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "%VERSION%")] file static class Utilities { /// Supports searching for the string "href". @@ -930,6 +930,206 @@ file static class Utilities } """ }; + + yield return new object[] + { + """ + using System.Text.RegularExpressions; + partial class C + { + [GeneratedRegex(@"abcd*e|f")] + public static partial Regex Valid(); + } + """, + + """ + // + #nullable enable + #pragma warning disable CS0162 // Unreachable code + #pragma warning disable CS0164 // Unreferenced label + #pragma warning disable CS0219 // Variable assigned but never used + + partial class C + { + /// + /// Pattern:
+ /// abcd*e|f
+ /// Explanation:
+ /// + /// ○ Match with 2 alternative expressions, atomically.
+ /// ○ Match a sequence of expressions.
+ /// ○ Match the string "abc".
+ /// ○ Match 'd' atomically any number of times.
+ /// ○ Match 'e'.
+ /// ○ Match 'f'.
+ ///
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "%VERSION%")] + public static partial global::System.Text.RegularExpressions.Regex Valid() => global::System.Text.RegularExpressions.Generated.Valid_0.Instance; + } + + namespace System.Text.RegularExpressions.Generated + { + using System; + using System.Buffers; + using System.CodeDom.Compiler; + using System.Collections; + using System.ComponentModel; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Text.RegularExpressions; + using System.Threading; + + /// Custom -derived type for the Valid method. + [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "%VERSION%")] + [SkipLocalsInit] + file sealed class Valid_0 : Regex + { + /// Cached, thread-safe singleton instance. + internal static readonly Valid_0 Instance = new(); + + /// Initializes the instance. + private Valid_0() + { + base.pattern = "abcd*e|f"; + base.roptions = RegexOptions.None; + ValidateMatchTimeout(Utilities.s_defaultTimeout); + base.internalMatchTimeout = Utilities.s_defaultTimeout; + base.factory = new RunnerFactory(); + base.capsize = 1; + } + + /// Provides a factory for creating instances to be used by methods on . + private sealed class RunnerFactory : RegexRunnerFactory + { + /// Creates an instance of a used by methods on . + protected override RegexRunner CreateInstance() => new Runner(); + + /// Provides the runner that contains the custom logic implementing the specified regular expression. + private sealed class Runner : RegexRunner + { + /// Scan the starting from base.runtextstart for the next match. + /// The text being scanned by the regular expression. + protected override void Scan(ReadOnlySpan inputSpan) + { + // Search until we can't find a valid starting position, we find a match, or we reach the end of the input. + while (TryFindNextPossibleStartingPosition(inputSpan) && + !TryMatchAtCurrentPosition(inputSpan) && + base.runtextpos != inputSpan.Length) + { + base.runtextpos++; + if (Utilities.s_hasTimeout) + { + base.CheckTimeout(); + } + } + } + + /// Search starting from base.runtextpos for the next location a match could possibly start. + /// The text being scanned by the regular expression. + /// true if a possible match was found; false if no more matches are possible. + private bool TryFindNextPossibleStartingPosition(ReadOnlySpan inputSpan) + { + int pos = base.runtextpos; + + // Empty matches aren't possible. + if ((uint)pos < (uint)inputSpan.Length) + { + // The pattern begins with a character in the set [af]. + // Find the next occurrence. If it can't be found, there's no match. + int i = inputSpan.Slice(pos).IndexOfAny('a', 'f'); + if (i >= 0) + { + base.runtextpos = pos + i; + return true; + } + } + + // No match found. + base.runtextpos = inputSpan.Length; + return false; + } + + /// Determine whether at base.runtextpos is a match for the regular expression. + /// The text being scanned by the regular expression. + /// true if the regular expression matches at the current position; otherwise, false. + private bool TryMatchAtCurrentPosition(ReadOnlySpan inputSpan) + { + int pos = base.runtextpos; + int matchStart = pos; + ReadOnlySpan slice = inputSpan.Slice(pos); + + // Match with 2 alternative expressions, atomically. + { + if (slice.IsEmpty) + { + return false; // The input didn't match. + } + + switch (slice[0]) + { + case 'a': + // Match the string "bc". + if (!slice.Slice(1).StartsWith("bc")) + { + return false; // The input didn't match. + } + + // Match 'd' atomically any number of times. + { + int iteration = slice.Slice(3).IndexOfAnyExcept('d'); + if (iteration < 0) + { + iteration = slice.Length - 3; + } + + slice = slice.Slice(iteration); + pos += iteration; + } + + // Match 'e'. + if ((uint)slice.Length < 4 || slice[3] != 'e') + { + return false; // The input didn't match. + } + + pos += 4; + slice = inputSpan.Slice(pos); + break; + + case 'f': + pos++; + slice = inputSpan.Slice(pos); + break; + + default: + return false; // The input didn't match. + } + } + + // The input matched. + base.runtextpos = pos; + base.Capture(0, matchStart, pos); + return true; + } + } + } + + } + + /// Helper methods used by generated -derived implementations. + [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "%VERSION%")] + file static class Utilities + { + /// Default timeout value set in , or if none was set. + internal static readonly TimeSpan s_defaultTimeout = AppContext.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") is TimeSpan timeout ? timeout : Regex.InfiniteMatchTimeout; + + /// Whether is non-infinite. + internal static readonly bool s_hasTimeout = s_defaultTimeout != Regex.InfiniteMatchTimeout; + } + } + """ + }; } } } diff --git a/src/runtime/src/mono/browser/build/BrowserWasmApp.targets b/src/runtime/src/mono/browser/build/BrowserWasmApp.targets index acf09e80e69..208ce20563d 100644 --- a/src/runtime/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/runtime/src/mono/browser/build/BrowserWasmApp.targets @@ -321,7 +321,6 @@ <_EmccCommonFlags Include="$(_DefaultEmccFlags)" /> <_EmccCommonFlags Include="$(EmccFlags)" /> - <_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" /> <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" /> <_EmccCommonFlags Include="-fwasm-exceptions" Condition="'$(WasmEnableExceptionHandling)' == 'true'" /> diff --git a/src/runtime/src/mono/browser/runtime/jiterpreter-opcodes.ts b/src/runtime/src/mono/browser/runtime/jiterpreter-opcodes.ts index 974bbaf5233..4d3ab5c3930 100644 --- a/src/runtime/src/mono/browser/runtime/jiterpreter-opcodes.ts +++ b/src/runtime/src/mono/browser/runtime/jiterpreter-opcodes.ts @@ -289,6 +289,7 @@ export const enum WasmSimdOpcode { v128_const = 0x0c, i8x16_shuffle = 0x0d, i8x16_swizzle = 0x0e, + i8x16_relaxed_swizzle = 0x100, i8x16_splat = 0x0f, i16x8_splat = 0x10, i32x4_splat = 0x11, @@ -466,6 +467,8 @@ export const enum WasmSimdOpcode { f64x2_pmax = 0xf7, i32x4_trunc_sat_f32x4_s = 0xf8, i32x4_trunc_sat_f32x4_u = 0xf9, + i32x4_relaxed_trunc_f32x4_s = 0x101, + i32x4_relaxed_trunc_f32x4_u = 0x102, f32x4_convert_i32x4_s = 0xfa, f32x4_convert_i32x4_u = 0xfb, v128_load32_zero = 0x5c, @@ -483,6 +486,7 @@ export const enum WasmSimdOpcode { i64x2_extmul_low_i32x4_u = 0xde, i64x2_extmul_high_i32x4_u = 0xdf, i16x8_q15mulr_sat_s = 0x82, + i16x8_relaxed_q15mulr_sat_s = 0x111, v128_any_true = 0x53, v128_load8_lane = 0x54, v128_load16_lane = 0x55, @@ -503,6 +507,8 @@ export const enum WasmSimdOpcode { f64x2_convert_low_i32x4_u = 0xff, i32x4_trunc_sat_f64x2_s_zero = 0xfc, i32x4_trunc_sat_f64x2_u_zero = 0xfd, + i32x4_relaxed_trunc_f64_s_zero = 0x103, + i32x4_relaxed_trunc_f64_u_zero = 0x104, f32x4_demote_f64x2_zero = 0x5e, f64x2_promote_low_f32x4 = 0x5f, i8x16_popcnt = 0x62, @@ -510,6 +516,21 @@ export const enum WasmSimdOpcode { i16x8_extadd_pairwise_i8x16_u = 0x7d, i32x4_extadd_pairwise_i16x8_s = 0x7e, i32x4_extadd_pairwise_i16x8_u = 0x7f, + f32x4_relaxed_madd = 0x105, + f32x4_relaxed_mnadd = 0x106, + f64x2_relaxed_madd = 0x107, + f64x2_relaxed_mnadd = 0x108, + i8x16_relaxed_lane_select = 0x109, + i16x8_relaxed_lane_select = 0x10a, + i32x4_relaxed_lane_select = 0x10b, + i64x2_relaxed_lane_select = 0x10c, + f32x4_relaxed_min = 0x10d, + f32x4_relaxed_max = 0x10e, + f64x2_relaxed_min = 0x10f, + f64x2_relaxed_max = 0x110, + i16x8_relaxed_dot_i8x16_i7x16_s = 0x112, + i16x8_relaxed_dot_i8x16_i7x16_u = 0x113, + i32x4_relaxed_dot_i8x16_i7x16_s = 0x114, } export const enum WasmAtomicOpcode { diff --git a/src/runtime/src/mono/browser/runtime/jiterpreter-trace-generator.ts b/src/runtime/src/mono/browser/runtime/jiterpreter-trace-generator.ts index 8da5b65fad7..0d619286bea 100644 --- a/src/runtime/src/mono/browser/runtime/jiterpreter-trace-generator.ts +++ b/src/runtime/src/mono/browser/runtime/jiterpreter-trace-generator.ts @@ -3919,7 +3919,11 @@ function emit_shuffle (builder: WasmBuilder, ip: MintOpcodePtr, elementCount: nu for (let j = 0; j < elementSize; j++) builder.appendU8(i); } - builder.appendSimd(WasmSimdOpcode.i8x16_swizzle); + if (runtimeHelpers.featureWasmRelaxedSimd) { + builder.appendSimd(WasmSimdOpcode.i8x16_relaxed_swizzle); + } else { + builder.appendSimd(WasmSimdOpcode.i8x16_swizzle); + } // multiply indices by 2 or 4 to scale from elt indices to byte indices builder.i32_const(elementCount === 4 ? 2 : 1); builder.appendSimd(WasmSimdOpcode.i8x16_shl); diff --git a/src/runtime/src/mono/browser/runtime/loader/globals.ts b/src/runtime/src/mono/browser/runtime/loader/globals.ts index 962d8631cd7..249af3ae843 100644 --- a/src/runtime/src/mono/browser/runtime/loader/globals.ts +++ b/src/runtime/src/mono/browser/runtime/loader/globals.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/triple-slash-reference */ /// -import { exceptions, simd } from "wasm-feature-detect"; +import { exceptions, simd, relaxedSimd } from "wasm-feature-detect"; import gitHash from "consts:gitHash"; @@ -133,6 +133,7 @@ export function setLoaderGlobals ( // from wasm-feature-detect npm package exceptions, simd, + relaxedSimd }; Object.assign(runtimeHelpers, rh); Object.assign(loaderHelpers, lh); diff --git a/src/runtime/src/mono/browser/runtime/startup.ts b/src/runtime/src/mono/browser/runtime/startup.ts index 325caeb4cd1..be98bde435d 100644 --- a/src/runtime/src/mono/browser/runtime/startup.ts +++ b/src/runtime/src/mono/browser/runtime/startup.ts @@ -519,8 +519,12 @@ async function instantiate_wasm_module ( } async function ensureUsedWasmFeatures () { - runtimeHelpers.featureWasmSimd = await loaderHelpers.simd(); - runtimeHelpers.featureWasmEh = await loaderHelpers.exceptions(); + const simd = loaderHelpers.simd(); + const relaxedSimd = loaderHelpers.relaxedSimd(); + const exceptions = loaderHelpers.exceptions(); + runtimeHelpers.featureWasmSimd = await simd; + runtimeHelpers.featureWasmRelaxedSimd = await relaxedSimd; + runtimeHelpers.featureWasmEh = await exceptions; if (runtimeHelpers.emscriptenBuildOptions.wasmEnableSIMD) { mono_assert(runtimeHelpers.featureWasmSimd, "This browser/engine doesn't support WASM SIMD. Please use a modern version. See also https://aka.ms/dotnet-wasm-features"); } diff --git a/src/runtime/src/mono/browser/runtime/types/internal.ts b/src/runtime/src/mono/browser/runtime/types/internal.ts index 5e132209563..9d5bdc73978 100644 --- a/src/runtime/src/mono/browser/runtime/types/internal.ts +++ b/src/runtime/src/mono/browser/runtime/types/internal.ts @@ -179,6 +179,7 @@ export type LoaderHelpers = { // from wasm-feature-detect npm package exceptions: () => Promise, simd: () => Promise, + relaxedSimd: () => Promise, } export type RuntimeHelpers = { emscriptenBuildOptions: EmscriptenBuildOptions, @@ -232,6 +233,7 @@ export type RuntimeHelpers = { featureWasmEh: boolean, featureWasmSimd: boolean, + featureWasmRelaxedSimd: boolean, //core stringify_as_error_with_stack?: (error: any) => string, diff --git a/src/runtime/src/native/libs/System.Globalization.Native/CMakeLists.txt b/src/runtime/src/native/libs/System.Globalization.Native/CMakeLists.txt index 496bdb0d33e..e3fa46ac9bc 100644 --- a/src/runtime/src/native/libs/System.Globalization.Native/CMakeLists.txt +++ b/src/runtime/src/native/libs/System.Globalization.Native/CMakeLists.txt @@ -127,7 +127,7 @@ if (CLR_CMAKE_TARGET_APPLE) endif() # time zone names are filtered out of icu data for the browser and associated functionality is disabled -if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI AND NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) +if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI AND NOT CLR_CMAKE_TARGET_MACCATALYST AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS) set(NATIVEGLOBALIZATION_SOURCES ${NATIVEGLOBALIZATION_SOURCES} pal_timeZoneInfo.c) endif() @@ -192,6 +192,9 @@ if(CLR_CMAKE_TARGET_UNIX OR CLR_CMAKE_TARGET_WASI) endif() install (TARGETS System.Globalization.Native-Static DESTINATION ${STATIC_LIB_DESTINATION} COMPONENT libs) +if(CLR_CMAKE_HOST_ANDROID) + install (TARGETS System.Globalization.Native-Static DESTINATION sharedFramework COMPONENT runtime) +endif() if(NOT CLR_CMAKE_TARGET_WIN32 AND NOT CLR_CMAKE_TARGET_APPLE AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_LINUX_MUSL AND NOT CLR_CMAKE_TARGET_HAIKU) if (GEN_SHARED_LIB) diff --git a/src/runtime/src/native/libs/System.IO.Compression.Native/CMakeLists.txt b/src/runtime/src/native/libs/System.IO.Compression.Native/CMakeLists.txt index 51320228df5..e7cf0355e36 100644 --- a/src/runtime/src/native/libs/System.IO.Compression.Native/CMakeLists.txt +++ b/src/runtime/src/native/libs/System.IO.Compression.Native/CMakeLists.txt @@ -210,3 +210,7 @@ else () endif () install (TARGETS System.IO.Compression.Native-Static DESTINATION ${STATIC_LIB_DESTINATION} COMPONENT libs) + +if(CLR_CMAKE_HOST_ANDROID) + install (TARGETS System.IO.Compression.Native-Static DESTINATION sharedFramework COMPONENT runtime) +endif() diff --git a/src/runtime/src/native/libs/System.Native/CMakeLists.txt b/src/runtime/src/native/libs/System.Native/CMakeLists.txt index 7712f255602..1407d50e04d 100644 --- a/src/runtime/src/native/libs/System.Native/CMakeLists.txt +++ b/src/runtime/src/native/libs/System.Native/CMakeLists.txt @@ -138,3 +138,7 @@ add_library(System.Native-Static set_target_properties(System.Native-Static PROPERTIES OUTPUT_NAME System.Native CLEAN_DIRECT_OUTPUT 1) install (TARGETS System.Native-Static DESTINATION ${STATIC_LIB_DESTINATION} COMPONENT libs) + +if(CLR_CMAKE_HOST_ANDROID) + install (TARGETS System.Native-Static DESTINATION sharedFramework COMPONENT runtime) +endif() diff --git a/src/runtime/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt b/src/runtime/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt index 033378d2ee0..f860bc26f81 100644 --- a/src/runtime/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt +++ b/src/runtime/src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt @@ -57,3 +57,7 @@ if (GEN_SHARED_LIB) endif() install (TARGETS System.Security.Cryptography.Native.Android-Static DESTINATION ${STATIC_LIB_DESTINATION} COMPONENT libs) + +if(CLR_CMAKE_HOST_ANDROID) + install (TARGETS System.Security.Cryptography.Native.Android-Static DESTINATION sharedFramework COMPONENT runtime) +endif() diff --git a/src/runtime/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs b/src/runtime/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs index 925d8d75d8b..617c02af49b 100644 --- a/src/runtime/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs +++ b/src/runtime/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs @@ -146,11 +146,16 @@ private static unsafe void GetStackWalkInfo(ulong controlPC, void* pUnwindInfoBa { if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer unwindInfoBase = eman.GetUnwindInfoBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); - if ((nuint)pUnwindInfoBase != 0) *(nuint*)pUnwindInfoBase = (nuint)unwindInfoBase.Value; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + if ((nuint)pUnwindInfoBase != 0) + { + TargetPointer unwindInfoBase = eman.GetUnwindInfoBaseAddress(cbh); + *(nuint*)pUnwindInfoBase = (nuint)unwindInfoBase.Value; + } + if ((nuint)pFuncEntry != 0) + { + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } } } catch (System.Exception ex) diff --git a/src/runtime/src/tasks/AndroidAppBuilder/ApkBuilder.cs b/src/runtime/src/tasks/AndroidAppBuilder/ApkBuilder.cs index 9dc90501b0f..9944e69d31e 100644 --- a/src/runtime/src/tasks/AndroidAppBuilder/ApkBuilder.cs +++ b/src/runtime/src/tasks/AndroidAppBuilder/ApkBuilder.cs @@ -310,21 +310,23 @@ public ApkBuilder(TaskLoggingHelper logger) // due to circular dependency. nativeLibraries += $" {runtimeLib}{Environment.NewLine}"; } - } - if (StaticLinkedRuntime && IsCoreCLR) - { - string[] staticLibs = Directory.GetFiles(AppDir, "*.a") - .Where(lib => !Path.GetFileName(lib).Equals("libcoreclr_static.a", StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - foreach (string lib in staticLibs) + if (StaticLinkedRuntime && IsCoreCLR) { - nativeLibraries += $" {lib}{Environment.NewLine}"; - } + string[] staticMonoStubs = Directory.GetFiles(AppDir, "libmono*.a"); + string[] staticLibs = Directory.GetFiles(AppDir, "*.a") + .Where(lib => !Path.GetFileName(lib).Equals("libcoreclr_static.a", StringComparison.OrdinalIgnoreCase) && + !staticMonoStubs.Contains(lib, StringComparer.OrdinalIgnoreCase)) + .ToArray(); - nativeLibraries += $" libc++abi.a{Environment.NewLine}"; - nativeLibraries += $" libc++_static.a{Environment.NewLine}"; + foreach (string lib in staticLibs) + { + nativeLibraries += $" {lib}{Environment.NewLine}"; + } + + nativeLibraries += $" libc++abi.a{Environment.NewLine}"; + nativeLibraries += $" libc++_static.a{Environment.NewLine}"; + } } StringBuilder extraLinkerArgs = new StringBuilder(); diff --git a/src/runtime/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs b/src/runtime/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs index 47a0dcad269..951c557df71 100644 --- a/src/runtime/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs +++ b/src/runtime/src/tests/Common/Coreclr.TestWrapper/CoreclrTestWrapperLib.cs @@ -681,15 +681,25 @@ public static bool TryPrintStackTraceFromDmp(string dmpFile, TextWriter outputWr // The children are sorted in the order they should be dumped static unsafe IEnumerable FindChildProcessesByName(Process process, string childName) { - Console.WriteLine($"Finding all child processes of '{process.ProcessName}' (ID: {process.Id}) with name '{childName}'"); + process.TryGetProcessName(out string parentProcessName); + process.TryGetProcessId(out int parentProcessId); + Console.WriteLine($"Finding all child processes of '{parentProcessName}' (ID: {parentProcessId}) with name '{childName}'"); var children = new Stack(); Queue childrenToCheck = new Queue(); HashSet seen = new HashSet(); - seen.Add(process.Id); - foreach (var child in process.GetChildren()) - childrenToCheck.Enqueue(child); + seen.Add(parentProcessId); + + try + { + foreach (var child in process.GetChildren()) + childrenToCheck.Enqueue(child); + } + catch + { + // Process exited + } while (childrenToCheck.Count != 0) { @@ -707,8 +717,15 @@ static unsafe IEnumerable FindChildProcessesByName(Process process, str Console.WriteLine($"Checking child process: '{processName}' (ID: {processId})"); seen.Add(processId); - foreach (var grandchild in child.GetChildren()) - childrenToCheck.Enqueue(grandchild); + try + { + foreach (var grandchild in child.GetChildren()) + childrenToCheck.Enqueue(grandchild); + } + catch + { + // Process exited + } if (processName.Equals(childName, StringComparison.OrdinalIgnoreCase)) { @@ -844,7 +861,9 @@ public int RunTest(string executable, string outputFile, string errorFile, strin Console.WriteLine($"\t{"ID",-6} ProcessName"); foreach (var activeProcess in Process.GetProcesses()) { - Console.WriteLine($"\t{activeProcess.Id,-6} {activeProcess.ProcessName}"); + activeProcess.TryGetProcessName(out string activeProcessName); + activeProcess.TryGetProcessId(out int activeProcessId); + Console.WriteLine($"\t{activeProcessId,-6} {activeProcessName}"); } if (OperatingSystem.IsWindows()) diff --git a/src/runtime/src/tests/Directory.Build.props b/src/runtime/src/tests/Directory.Build.props index 136b5db5ae4..fc6ebdb5ed7 100644 --- a/src/runtime/src/tests/Directory.Build.props +++ b/src/runtime/src/tests/Directory.Build.props @@ -8,6 +8,11 @@ + + + 5.0.0-1.25259.6 + + true diff --git a/src/runtime/src/tests/async/Directory.Build.props b/src/runtime/src/tests/async/Directory.Build.props index 17e80030ca0..dda1142e724 100644 --- a/src/runtime/src/tests/async/Directory.Build.props +++ b/src/runtime/src/tests/async/Directory.Build.props @@ -7,5 +7,9 @@ true $(NoWarn);xUnit1013 false + $(Features);runtime-async=on + + + diff --git a/src/runtime/src/tests/async/RuntimeAsyncMethodGenerationAttribute.cs b/src/runtime/src/tests/async/RuntimeAsyncMethodGenerationAttribute.cs new file mode 100644 index 00000000000..d12d961ebcf --- /dev/null +++ b/src/runtime/src/tests/async/RuntimeAsyncMethodGenerationAttribute.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Method)] +public class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute +{ + public bool RuntimeAsync { get; } = runtimeAsync; +} \ No newline at end of file diff --git a/src/runtime/src/tests/async/awaitingnotasync/awaitingnotasync.cs b/src/runtime/src/tests/async/awaitingnotasync/awaitingnotasync.cs index 9f4b1732313..bf8ad8c9cc1 100644 --- a/src/runtime/src/tests/async/awaitingnotasync/awaitingnotasync.cs +++ b/src/runtime/src/tests/async/awaitingnotasync/awaitingnotasync.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; @@ -14,6 +13,7 @@ public static void TestEntryPoint() AsyncEntryPoint().Wait(); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task GetTask(T arg) { await Task.Yield(); @@ -21,6 +21,7 @@ private static async Task GetTask(T arg) } // TODO: switch every other scenario to use ValueTask + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async ValueTask GetValueTask(T arg) { await Task.Yield(); @@ -33,13 +34,13 @@ private static async ValueTask GetValueTask(T arg) private static T sIdentity(T arg) => arg; - private static async2 Task AsyncEntryPoint() + private static async Task AsyncEntryPoint() { // static field sField = GetTask(5); Assert.Equal(5, await sField); - // property + // property Assert.Equal(6, await sProp); // generic identity diff --git a/src/runtime/src/tests/async/collectible-alc/collectible-alc.cs b/src/runtime/src/tests/async/collectible-alc/collectible-alc.cs index eb7e43309db..0feb988d4ac 100644 --- a/src/runtime/src/tests/async/collectible-alc/collectible-alc.cs +++ b/src/runtime/src/tests/async/collectible-alc/collectible-alc.cs @@ -17,7 +17,7 @@ public static void TestEntryPoint() AsyncEntryPoint().Wait(); } - private static async2 Task AsyncEntryPoint() + private static async Task AsyncEntryPoint() { WeakReference wr = await CallFooAsyncAndUnload(); @@ -31,7 +31,7 @@ private static async2 Task AsyncEntryPoint() } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task CallFooAsyncAndUnload() + private static async Task CallFooAsyncAndUnload() { TaskCompletionSource tcs = new(); (Task task, WeakReference wr) = CallFooAsyncInCollectibleALC(tcs.Task); @@ -63,7 +63,7 @@ private static (Task, WeakReference) CallFooAsyncInCollectibleALC(Task t } // Task[] to work around a compiler bug - private static async2 Task FooAsync(Task[] t) + private static async Task FooAsync(Task[] t) { await t[0]; return "done"; diff --git a/src/runtime/src/tests/async/cse-array-index-byref/cse-array-index-byref.cs b/src/runtime/src/tests/async/cse-array-index-byref/cse-array-index-byref.cs index be99722a054..d208c4e7a12 100644 --- a/src/runtime/src/tests/async/cse-array-index-byref/cse-array-index-byref.cs +++ b/src/runtime/src/tests/async/cse-array-index-byref/cse-array-index-byref.cs @@ -16,13 +16,14 @@ public static void TestEntryPoint() Assert.Equal(199_990_000, arr[0]); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncTestEntryPoint(int[] arr, int index) { await HoistedByref(arr, index); } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task HoistedByref(int[] arr, int index) + private static async Task HoistedByref(int[] arr, int index) { for (int i = 0; i < 20000; i++) { diff --git a/src/runtime/src/tests/async/eh-microbench/eh-microbench.cs b/src/runtime/src/tests/async/eh-microbench/eh-microbench.cs index 994e443cf9e..2eefa61570a 100644 --- a/src/runtime/src/tests/async/eh-microbench/eh-microbench.cs +++ b/src/runtime/src/tests/async/eh-microbench/eh-microbench.cs @@ -24,6 +24,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { if (!GCSettings.IsServerGC) @@ -68,6 +69,7 @@ public static async Task AsyncEntry() await RunBench(yieldFrequency, depth, finallyRate, throwOrReturn, "ValueTask"); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task RunBench(int yieldCount, int depth, int finallyRate, bool throwOrReturn, string type) { @@ -104,7 +106,7 @@ public Benchmark(int yieldCount, int depth, int finallyRate, bool throwOrReturn) _throwOrReturn = throwOrReturn; } - public async2 Task Run(string type) + public async Task Run(string type) { if (type == "Async2") return await RunAsync2(_depth); @@ -117,6 +119,7 @@ public async2 Task Run(string type) return 0; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public async Task RunTask(int depth) { int liveState1 = depth * 3 + _yieldCount; @@ -182,6 +185,7 @@ public async Task RunTask(int depth) return result; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public async ValueTask RunValueTask(int depth) { int liveState1 = depth * 3 + _yieldCount; @@ -267,7 +271,7 @@ public class FakeThread // This case is used to test the impact of save/restore of the sync and execution context on performance // The intent here is to measure what the performance impact of maintaining the current async semantics with // the new implementation. - public async2 Task RunAsync2WithContextSaveRestore(int depth) + public async Task RunAsync2WithContextSaveRestore(int depth) { FakeThread thread = CurrentThread; if (thread == null) @@ -361,7 +365,7 @@ public async2 Task RunAsync2WithContextSaveRestore(int depth) } } - public async2 Task RunAsync2(int depth) + public async Task RunAsync2(int depth) { int liveState1 = depth * 3 + _yieldCount; int liveState2 = depth; diff --git a/src/runtime/src/tests/async/fibonacci-with-yields/fibonacci-with-yields.cs b/src/runtime/src/tests/async/fibonacci-with-yields/fibonacci-with-yields.cs index a51a834fbdd..814b96eda0b 100644 --- a/src/runtime/src/tests/async/fibonacci-with-yields/fibonacci-with-yields.cs +++ b/src/runtime/src/tests/async/fibonacci-with-yields/fibonacci-with-yields.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Diagnostics; using Xunit; @@ -22,7 +23,7 @@ public static void Test() System.Console.WriteLine("allocated: " + allocated); } - public static async2 Task AsyncEntry() + public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) { @@ -34,7 +35,7 @@ public static async2 Task AsyncEntry() } } - static async2 Task Fib(int i) + static async Task Fib(int i) { if (i <= 1) { diff --git a/src/runtime/src/tests/async/fibonacci-with-yields_struct_return/fibonacci-with-yields_struct_return.cs b/src/runtime/src/tests/async/fibonacci-with-yields_struct_return/fibonacci-with-yields_struct_return.cs index eee23a2c162..c77caf9d912 100644 --- a/src/runtime/src/tests/async/fibonacci-with-yields_struct_return/fibonacci-with-yields_struct_return.cs +++ b/src/runtime/src/tests/async/fibonacci-with-yields_struct_return/fibonacci-with-yields_struct_return.cs @@ -6,6 +6,7 @@ using System; using System.Threading.Tasks; using System.Diagnostics; +using System.Runtime.CompilerServices; using Xunit; public class Async2FibonacceWithYields @@ -34,7 +35,7 @@ public struct MyInt public MyInt(int i) => this.i = i; } - public static async2 Task AsyncEntry() + public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) { @@ -46,7 +47,7 @@ public static async2 Task AsyncEntry() } } - static async2 Task Fib(MyInt n) + static async Task Fib(MyInt n) { int i = n.i; if (i <= 1) diff --git a/src/runtime/src/tests/async/fibonacci-without-yields-config-await/fibonacci-without-yields-config-await.cs b/src/runtime/src/tests/async/fibonacci-without-yields-config-await/fibonacci-without-yields-config-await.cs index 7abf3bac07d..e3b411b5c33 100644 --- a/src/runtime/src/tests/async/fibonacci-without-yields-config-await/fibonacci-without-yields-config-await.cs +++ b/src/runtime/src/tests/async/fibonacci-without-yields-config-await/fibonacci-without-yields-config-await.cs @@ -26,7 +26,7 @@ public static void Test() System.Console.WriteLine("allocated: " + allocated); } - public static async2 Task AsyncEntry() + public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) { @@ -38,7 +38,7 @@ public static async2 Task AsyncEntry() } } - static async2 Task Fib(int i) + static async Task Fib(int i) { if (i <= 1) { diff --git a/src/runtime/src/tests/async/fibonacci-without-yields/fibonacci-without-yields.cs b/src/runtime/src/tests/async/fibonacci-without-yields/fibonacci-without-yields.cs index 77ff71e8d1e..11dd3db5168 100644 --- a/src/runtime/src/tests/async/fibonacci-without-yields/fibonacci-without-yields.cs +++ b/src/runtime/src/tests/async/fibonacci-without-yields/fibonacci-without-yields.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using System.Diagnostics; +using System.Runtime.CompilerServices; using Xunit; public class Async2FibonacciWithoutYields @@ -22,7 +23,7 @@ public static void Test() System.Console.WriteLine("allocated: " + allocated); } - public static async2 Task AsyncEntry() + public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) { @@ -34,7 +35,7 @@ public static async2 Task AsyncEntry() } } - static async2 Task Fib(int i) + static async Task Fib(int i) { if (i <= 1) { diff --git a/src/runtime/src/tests/async/gc-roots-scan/gc-roots-scan.cs b/src/runtime/src/tests/async/gc-roots-scan/gc-roots-scan.cs index fffe874af38..76d0dfb629f 100644 --- a/src/runtime/src/tests/async/gc-roots-scan/gc-roots-scan.cs +++ b/src/runtime/src/tests/async/gc-roots-scan/gc-roots-scan.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using System.Diagnostics; +using System.Runtime.CompilerServices; using Xunit; public class Async2RootReporting @@ -11,6 +12,7 @@ public class Async2RootReporting private static TaskCompletionSource cs; + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] static async Task Recursive1(int n) { Task cTask = cs.Task; @@ -50,7 +52,7 @@ static async Task Recursive1(int n) return result; } - static async2 Task Recursive2(int n) + static async Task Recursive2(int n) { Task cTask = cs.Task; diff --git a/src/runtime/src/tests/async/implement/implement.cs b/src/runtime/src/tests/async/implement/implement.cs index bb8607541d1..ddd20d0570c 100644 --- a/src/runtime/src/tests/async/implement/implement.cs +++ b/src/runtime/src/tests/async/implement/implement.cs @@ -10,11 +10,12 @@ public class Async2Implement { interface IBase1 { - public async2 Task M1(); + public Task M1(); } class Derived1 : IBase1 { + [RuntimeAsyncMethodGeneration(false)] public async Task M1() { await Task.Yield(); @@ -24,7 +25,7 @@ public async Task M1() class Derived1a : IBase1 { - public async2 Task M1() + public async Task M1() { await Task.Yield(); return 3; @@ -38,7 +39,7 @@ interface IBase2 class Derived2 : IBase2 { - public async2 Task M1() + public async Task M1() { await Task.Yield(); return 12; @@ -47,6 +48,7 @@ public async2 Task M1() class Derived2a : IBase2 { + [RuntimeAsyncMethodGeneration(false)] public async Task M1() { await Task.Yield(); diff --git a/src/runtime/src/tests/async/mincallcost-microbench/mincallcost-microbench.cs b/src/runtime/src/tests/async/mincallcost-microbench/mincallcost-microbench.cs index 122994d8061..900aad4ba45 100644 --- a/src/runtime/src/tests/async/mincallcost-microbench/mincallcost-microbench.cs +++ b/src/runtime/src/tests/async/mincallcost-microbench/mincallcost-microbench.cs @@ -24,6 +24,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { if (!GCSettings.IsServerGC) @@ -66,6 +67,7 @@ public static async Task AsyncEntry() static double time = 10.0; static bool printResult = false; + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task RunBench(string type) { if (printResult) @@ -85,6 +87,7 @@ private static async Task RunBench(string type) Console.WriteLine("Result = {0}", (long)avg); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task Run(string type) { if (type == "AsyncCallingAsync") @@ -114,6 +117,7 @@ private static async Task Run(string type) } #pragma warning disable CS1998 + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncCallingAsync() { Stopwatch timer = Stopwatch.StartNew(); @@ -131,6 +135,7 @@ private static async Task AsyncCallingAsync() return numIters * 10; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncCallingValueTaskAsync() { Stopwatch timer = Stopwatch.StartNew(); @@ -148,6 +153,7 @@ private static async Task AsyncCallingValueTaskAsync() return numIters * 10; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncCallingAsync2() { Stopwatch timer = Stopwatch.StartNew(); @@ -165,7 +171,7 @@ private static async Task AsyncCallingAsync2() return numIters * 10; } - private static async2 Task Async2CallingAsync() + private static async Task Async2CallingAsync() { Stopwatch timer = Stopwatch.StartNew(); @@ -182,7 +188,7 @@ private static async2 Task Async2CallingAsync() return numIters * 10; } - private static async2 Task Async2CallingValueTaskAsync() + private static async Task Async2CallingValueTaskAsync() { Stopwatch timer = Stopwatch.StartNew(); @@ -199,7 +205,7 @@ private static async2 Task Async2CallingValueTaskAsync() return numIters * 10; } - private static async2 Task Async2CallingAsync2() + private static async Task Async2CallingAsync2() { Stopwatch timer = Stopwatch.StartNew(); @@ -216,7 +222,7 @@ private static async2 Task Async2CallingAsync2() return numIters * 10; } - private static async2 Task Async2CallingAsync2NoInlining() + private static async Task Async2CallingAsync2NoInlining() { Stopwatch timer = Stopwatch.StartNew(); @@ -233,7 +239,7 @@ private static async2 Task Async2CallingAsync2NoInlining() return numIters * 10; } - private static async2 Task Async2CallingAsync2WithContextSave() + private static async Task Async2CallingAsync2WithContextSave() { FakeThread thread = CurrentThread; if (thread == null) @@ -274,6 +280,7 @@ public class FakeThread public static FakeThread CurrentThread; + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncCallingYield() { Stopwatch timer = Stopwatch.StartNew(); @@ -291,7 +298,7 @@ private static async Task AsyncCallingYield() return numIters * 10; } - private static async2 Task Async2CallingYield() + private static async Task Async2CallingYield() { Stopwatch timer = Stopwatch.StartNew(); @@ -325,6 +332,7 @@ private static long Sync2CallingSync() return numIters * 10; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task EmptyAsync() { // Add some work that forces the method to be a real async method @@ -333,6 +341,7 @@ private static async Task EmptyAsync() return; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async ValueTask EmptyValueTaskAsync() { // Add some work that forces the method to be a real async method @@ -341,7 +350,7 @@ private static async ValueTask EmptyValueTaskAsync() return; } - private static async2 Task EmptyAsync2() + private static async Task EmptyAsync2() { // Add some work that forces the method to be a real async method if (time == 0) @@ -350,7 +359,7 @@ private static async2 Task EmptyAsync2() } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task EmptyAsync2NoInlining() + private static async Task EmptyAsync2NoInlining() { // Add some work that forces the method to be a real async method if (time == 0) @@ -358,8 +367,8 @@ private static async2 Task EmptyAsync2NoInlining() return; } - // This simulates async2 capturing the same amount of state that existing async needs to capture to handle the current semantics around async locals and synchronizationcontext - private static async2 Task EmptyAsync2WithContextSave() + // This simulates async capturing the same amount of state that existing async needs to capture to handle the current semantics around async locals and synchronizationcontext + private static async Task EmptyAsync2WithContextSave() { FakeThread thread = CurrentThread; FakeExecContext? previousExecutionCtx = thread._execContext; diff --git a/src/runtime/src/tests/async/object/object.cs b/src/runtime/src/tests/async/object/object.cs index fb62588652a..7ebec6207ed 100644 --- a/src/runtime/src/tests/async/object/object.cs +++ b/src/runtime/src/tests/async/object/object.cs @@ -14,13 +14,14 @@ public static int TestEntryPoint() return (int)AsyncTestEntryPoint(100).Result; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncTestEntryPoint(int arg) { return await ObjMethod(arg); } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task ObjMethod(int arg) + private static async Task ObjMethod(int arg) { await Task.Yield(); return arg; diff --git a/src/runtime/src/tests/async/objects-captured/objects-captured.cs b/src/runtime/src/tests/async/objects-captured/objects-captured.cs index 197d29f0383..07c502b12ee 100644 --- a/src/runtime/src/tests/async/objects-captured/objects-captured.cs +++ b/src/runtime/src/tests/async/objects-captured/objects-captured.cs @@ -4,11 +4,12 @@ using System; using System.Threading.Tasks; using System.Diagnostics; +using System.Runtime.CompilerServices; using Xunit; public class Async2ObjectsWithYields { - internal static async2 Task A(object n) + internal static async Task A(object n) { // use string equality so that JIT would not think of hoisting "(int)n" // also to produce some amout of garbage @@ -21,6 +22,7 @@ internal static async2 Task A(object n) return 0; } + [RuntimeAsyncMethodGeneration(false)] private static async Task AsyncEntry() { object result = 0; diff --git a/src/runtime/src/tests/async/override/override.cs b/src/runtime/src/tests/async/override/override.cs index 4ee5d1446b8..d55d961880a 100644 --- a/src/runtime/src/tests/async/override/override.cs +++ b/src/runtime/src/tests/async/override/override.cs @@ -10,7 +10,7 @@ public class Async2Override { class Base { - public virtual async2 Task M1() + public virtual async Task M1() { await Task.Yield(); return 1; @@ -19,6 +19,7 @@ public virtual async2 Task M1() class Derived1 : Base { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public override async Task M1() { await Task.Yield(); @@ -28,7 +29,7 @@ public override async Task M1() class Derived2 : Derived1 { - public override async2 Task M1() + public override async Task M1() { await Task.Yield(); return 3; @@ -38,6 +39,7 @@ public override async2 Task M1() class Base1 { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public virtual async Task M1() { await Task.Yield(); @@ -47,7 +49,7 @@ public virtual async Task M1() class Derived11 : Base1 { - public override async2 Task M1() + public override async Task M1() { await Task.Yield(); return 12; @@ -56,6 +58,7 @@ public override async2 Task M1() class Derived12 : Derived11 { + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public override async Task M1() { await Task.Yield(); diff --git a/src/runtime/src/tests/async/pgo/pgo.cs b/src/runtime/src/tests/async/pgo/pgo.cs index 6e3f14caa7b..ba5589c07d9 100644 --- a/src/runtime/src/tests/async/pgo/pgo.cs +++ b/src/runtime/src/tests/async/pgo/pgo.cs @@ -17,7 +17,7 @@ public static void EntryPoint() AsyncEntryPoint().Wait(); } - internal static async2 Task AsyncEntryPoint() + internal static async Task AsyncEntryPoint() { int[] arr = Enumerable.Range(0, 100_000).ToArray(); @@ -36,20 +36,20 @@ internal static async2 Task AsyncEntryPoint() private class AggregateSum : I { #pragma warning disable CS1998 - public async2 Task Aggregate(int a, int b) => a + b; + public async Task Aggregate(int a, int b) => a + b; } - + public interface I { public Task Aggregate(T seed, T val); } - + [MethodImpl(MethodImplOptions.NoInlining)] - public static async2 Task AggregateDelegateAsync(T[] arr, I aggregate, T seed) + public static async Task AggregateDelegateAsync(T[] arr, I aggregate, T seed) { foreach (T val in arr) seed = await aggregate.Aggregate(seed, val); - + return seed; } } diff --git a/src/runtime/src/tests/async/pinvoke/pinvoke.cs b/src/runtime/src/tests/async/pinvoke/pinvoke.cs index df04091cbaf..bbe9e939ad7 100644 --- a/src/runtime/src/tests/async/pinvoke/pinvoke.cs +++ b/src/runtime/src/tests/async/pinvoke/pinvoke.cs @@ -14,7 +14,7 @@ public static void TestEntryPoint() AsyncEntryPoint().Wait(); } - private static async2 Task AsyncEntryPoint() + private static async Task AsyncEntryPoint() { unsafe { diff --git a/src/runtime/src/tests/async/returns/returns.cs b/src/runtime/src/tests/async/returns/returns.cs index 174a66c479a..3df7a8a6589 100644 --- a/src/runtime/src/tests/async/returns/returns.cs +++ b/src/runtime/src/tests/async/returns/returns.cs @@ -15,7 +15,7 @@ public static void TestEntryPoint() } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task Returns(C c) + private static async Task Returns(C c) { for (int i = 0; i < 20000; i++) { @@ -58,28 +58,28 @@ private static void AssertEqual(T expected, T actual) } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task> ReturnsStruct() + private static async Task> ReturnsStruct() { await Task.Yield(); return new S { A = 42, B = 4242, C = 424242, D = 42424242 }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task> ReturnsStructGC() + private static async Task> ReturnsStructGC() { await Task.Yield(); return new S { A = "A", B = "B", C = "C", D = "D" }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task> ReturnsBytes() + private static async Task> ReturnsBytes() { await Task.Yield(); return new S { A = 4, B = 40, C = 42, D = 45 }; } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task ReturnsString() + private static async Task ReturnsString() { await Task.Yield(); return "a string!"; diff --git a/src/runtime/src/tests/async/shared-generic/shared-generic.cs b/src/runtime/src/tests/async/shared-generic/shared-generic.cs index e53ce961680..c75abab84bb 100644 --- a/src/runtime/src/tests/async/shared-generic/shared-generic.cs +++ b/src/runtime/src/tests/async/shared-generic/shared-generic.cs @@ -51,6 +51,7 @@ public static void TestEntryPoint() Async2EntryPoint?>(typeof(S1?), null).Wait(); } + [RuntimeAsyncMethodGeneration(false)] private static async Task Async1EntryPoint(Type t, T value) { await new GenericClass().InstanceMethod(t); @@ -65,7 +66,7 @@ private static async Task Async1EntryPoint(Type t, T value) Assert.Equal(value, await GenericClass.StaticReturnMethodTypeAsync1(value)); } - private static async2 Task Async2EntryPoint(Type t, T value) + private static async Task Async2EntryPoint(Type t, T value) { await new GenericClass().InstanceMethod(t); await GenericClass.StaticMethod(t); @@ -84,7 +85,7 @@ public class GenericClass { // 'this' is context [MethodImpl(MethodImplOptions.NoInlining)] - public async2 Task InstanceMethod(Type t) + public async Task InstanceMethod(Type t) { Assert.Equal(typeof(T), t); await Task.Yield(); @@ -93,7 +94,7 @@ public async2 Task InstanceMethod(Type t) // Class context [MethodImpl(MethodImplOptions.NoInlining)] - public static async2 Task StaticMethod(Type t) + public static async Task StaticMethod(Type t) { Assert.Equal(typeof(T), t); await Task.Yield(); @@ -102,7 +103,7 @@ public static async2 Task StaticMethod(Type t) // Method context [MethodImpl(MethodImplOptions.NoInlining)] - public static async2 Task StaticMethod(Type t, Type tm) + public static async Task StaticMethod(Type t, Type tm) { Assert.Equal(typeof(T), t); Assert.Equal(typeof(TM), tm); @@ -113,6 +114,7 @@ public static async2 Task StaticMethod(Type t, Type tm) // Class context [MethodImpl(MethodImplOptions.NoInlining)] + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task StaticMethodAsync1(Type t) { Assert.Equal(typeof(T), t); @@ -122,6 +124,7 @@ public static async Task StaticMethodAsync1(Type t) // Method context [MethodImpl(MethodImplOptions.NoInlining)] + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task StaticMethodAsync1(Type t, Type tm) { Assert.Equal(typeof(T), t); @@ -131,24 +134,26 @@ public static async Task StaticMethodAsync1(Type t, Type tm) Assert.Equal(typeof(TM), tm); } - public static async2 Task StaticReturnClassType(T value) + public static async Task StaticReturnClassType(T value) { await Task.Yield(); return value; } - public static async2 Task StaticReturnMethodType(TM value) + public static async Task StaticReturnMethodType(TM value) { await Task.Yield(); return value; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task StaticReturnClassTypeAsync1(T value) { await Task.Yield(); return value; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task StaticReturnMethodTypeAsync1(TM value) { await Task.Yield(); diff --git a/src/runtime/src/tests/async/simple-eh/simple-eh.cs b/src/runtime/src/tests/async/simple-eh/simple-eh.cs index 8d70af66efc..04b19f132c4 100644 --- a/src/runtime/src/tests/async/simple-eh/simple-eh.cs +++ b/src/runtime/src/tests/async/simple-eh/simple-eh.cs @@ -18,13 +18,14 @@ public static void Test() Task.Run(AsyncEntry).Wait(); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { int result = await Handler(); Assert.Equal(42, result); } - public static async2 Task Handler() + public static async Task Handler() { try { @@ -37,7 +38,7 @@ public static async2 Task Handler() } [MethodImpl(MethodImplOptions.NoInlining)] - public static async2 Task Throw(int value) + public static async Task Throw(int value) { await Task.Yield(); throw new IntegerException(value); diff --git a/src/runtime/src/tests/async/simple-eh/simple-eh.csproj b/src/runtime/src/tests/async/simple-eh/simple-eh.csproj index de6d5e08882..7df4a08ec02 100644 --- a/src/runtime/src/tests/async/simple-eh/simple-eh.csproj +++ b/src/runtime/src/tests/async/simple-eh/simple-eh.csproj @@ -1,6 +1,7 @@ True + true diff --git a/src/runtime/src/tests/async/small/small.cs b/src/runtime/src/tests/async/small/small.cs index 5afbbe7bc31..bdce432c39b 100644 --- a/src/runtime/src/tests/async/small/small.cs +++ b/src/runtime/src/tests/async/small/small.cs @@ -15,7 +15,7 @@ public static void TestEntryPoint() } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task SmallType(byte arg) + private static async Task SmallType(byte arg) { await Task.Yield(); Assert.Equal(123, arg); diff --git a/src/runtime/src/tests/async/strength-reduction/strength-reduction.cs b/src/runtime/src/tests/async/strength-reduction/strength-reduction.cs new file mode 100644 index 00000000000..175a01f09a5 --- /dev/null +++ b/src/runtime/src/tests/async/strength-reduction/strength-reduction.cs @@ -0,0 +1,29 @@ +// 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.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class StrengthReductionTest +{ + [Fact] + public static int TestEntryPoint() + { + return StrengthReduction(Enumerable.Range(0, 1000).ToArray()).Result - 499400; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task StrengthReduction(int[] arr) + { + int sum = 0; + foreach (int x in arr) + { + sum += x; + await Task.Yield(); + } + return sum; + } +} diff --git a/src/runtime/src/tests/async/strength-reduction/strength-reduction.csproj b/src/runtime/src/tests/async/strength-reduction/strength-reduction.csproj new file mode 100644 index 00000000000..de6d5e08882 --- /dev/null +++ b/src/runtime/src/tests/async/strength-reduction/strength-reduction.csproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/runtime/src/tests/async/struct/struct.cs b/src/runtime/src/tests/async/struct/struct.cs index 76bd0fd35e8..7c5062d499c 100644 --- a/src/runtime/src/tests/async/struct/struct.cs +++ b/src/runtime/src/tests/async/struct/struct.cs @@ -17,6 +17,7 @@ public static void TestEntryPoint() Async2().Wait(); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task Async() { S s = new S(100); @@ -24,13 +25,14 @@ private static async Task Async() AssertEqual(100, s.Value); } - private static async2 Task Async2() + private static async Task Async2() { S s = new S(100); await s.Test(); AssertEqual(100, s.Value); } + [MethodImpl(MethodImplOptions.NoInlining)] private static void AssertEqual(int expected, int val) { @@ -43,7 +45,7 @@ private struct S public S(int value) => Value = value; - public async2 Task Test() + public async Task Test() { // TODO: C# compiler is expected to do this, but not in the prototype. S @this = this; @@ -57,7 +59,7 @@ public async2 Task Test() AssertEqual(102, @this.Value); } - private async2 Task InstanceCall() + private async Task InstanceCall() { // TODO: C# compiler is expected to do this, but not in the prototype. S @this = this; diff --git a/src/runtime/src/tests/async/taskbased-asyncfibonacci-with-yields/taskbased-asyncfibonacci-with-yields.cs b/src/runtime/src/tests/async/taskbased-asyncfibonacci-with-yields/taskbased-asyncfibonacci-with-yields.cs index 4829e029636..2465903d710 100644 --- a/src/runtime/src/tests/async/taskbased-asyncfibonacci-with-yields/taskbased-asyncfibonacci-with-yields.cs +++ b/src/runtime/src/tests/async/taskbased-asyncfibonacci-with-yields/taskbased-asyncfibonacci-with-yields.cs @@ -23,6 +23,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) @@ -35,6 +36,7 @@ public static async Task AsyncEntry() } } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] static async Task Fib(int i) { if (i <= 1) diff --git a/src/runtime/src/tests/async/taskbased-asyncfibonacci-without-yields/taskbased-asyncfibonacci-without-yields.cs b/src/runtime/src/tests/async/taskbased-asyncfibonacci-without-yields/taskbased-asyncfibonacci-without-yields.cs index e3e9c7ff21f..2f8ddb5f066 100644 --- a/src/runtime/src/tests/async/taskbased-asyncfibonacci-without-yields/taskbased-asyncfibonacci-without-yields.cs +++ b/src/runtime/src/tests/async/taskbased-asyncfibonacci-without-yields/taskbased-asyncfibonacci-without-yields.cs @@ -23,6 +23,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { for (int i = 0; i < iterations; i++) @@ -35,6 +36,7 @@ public static async Task AsyncEntry() } } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] static async Task Fib(int i) { if (i <= 1) diff --git a/src/runtime/src/tests/async/valuetask/valuetask.cs b/src/runtime/src/tests/async/valuetask/valuetask.cs index deff530e9f5..4740181e273 100644 --- a/src/runtime/src/tests/async/valuetask/valuetask.cs +++ b/src/runtime/src/tests/async/valuetask/valuetask.cs @@ -19,7 +19,7 @@ private static ValueTask AsyncTestEntryPoint(int arg) return M1(arg); } - private static async2 ValueTask M1(int arg) + private static async ValueTask M1(int arg) { await Task.Yield(); return arg; diff --git a/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-with-yields/valuetaskbased-asyncfibonacci-with-yields.cs b/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-with-yields/valuetaskbased-asyncfibonacci-with-yields.cs index ac48372814e..f34cc93b1d6 100644 --- a/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-with-yields/valuetaskbased-asyncfibonacci-with-yields.cs +++ b/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-with-yields/valuetaskbased-asyncfibonacci-with-yields.cs @@ -23,6 +23,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async ValueTask AsyncEntry() { for (int i = 0; i < iterations; i++) @@ -35,6 +36,7 @@ public static async ValueTask AsyncEntry() } } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] static async ValueTask Fib(int i) { if (i <= 1) diff --git a/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-without-yields/valuetaskbased-asyncfibonacci-without-yields.cs b/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-without-yields/valuetaskbased-asyncfibonacci-without-yields.cs index 1e3aab57ec7..64d57c46f8e 100644 --- a/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-without-yields/valuetaskbased-asyncfibonacci-without-yields.cs +++ b/src/runtime/src/tests/async/valuetaskbased-asyncfibonacci-without-yields/valuetaskbased-asyncfibonacci-without-yields.cs @@ -23,6 +23,7 @@ public static int Main() return 100; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async ValueTask AsyncEntry() { for (int i = 0; i < iterations; i++) @@ -35,6 +36,7 @@ public static async ValueTask AsyncEntry() } } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] static async ValueTask Fib(int i) { if (i <= 1) diff --git a/src/runtime/src/tests/async/varying-yields/varying-yields.cs b/src/runtime/src/tests/async/varying-yields/varying-yields.cs index 6edc30e8118..6194d1890d8 100644 --- a/src/runtime/src/tests/async/varying-yields/varying-yields.cs +++ b/src/runtime/src/tests/async/varying-yields/varying-yields.cs @@ -22,6 +22,7 @@ public static void TestEntryPoint() Task.Run(AsyncEntry).Wait(); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public static async Task AsyncEntry() { if (!GCSettings.IsServerGC) @@ -76,13 +77,14 @@ private class Benchmark public Benchmark(double yieldProbability) => _yieldProbability = yieldProbability; -public #if ASYNC1_TASK - async Task + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + public async Task #elif ASYNC1_VALUETASK - async ValueTask + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + public async ValueTask #else - async2 Task + public async Task #endif Run(int depth) { @@ -98,13 +100,14 @@ async2 Task return result; } -private #if ASYNC1_TASK - async Task + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + private async Task #elif ASYNC1_VALUETASK - async ValueTask + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + private async ValueTask #else - async2 Task + private async Task #endif Loop() { @@ -124,13 +127,14 @@ async2 Task return numIters; } -private #if ASYNC1_TASK - async Task + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + private async Task #elif ASYNC1_VALUETASK - async ValueTask + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] + private async ValueTask #else - async2 Task + private async Task #endif DoYields() { diff --git a/src/runtime/src/tests/async/void/void.cs b/src/runtime/src/tests/async/void/void.cs index 8271887731a..566dbd8e7cc 100644 --- a/src/runtime/src/tests/async/void/void.cs +++ b/src/runtime/src/tests/async/void/void.cs @@ -16,13 +16,14 @@ public static void TestEntryPoint() Assert.Equal(199_990_000, arr[0]); } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncTestEntryPoint(int[] arr, int index) { await HoistedByref(arr, index); } [MethodImpl(MethodImplOptions.NoInlining)] - private static async2 Task HoistedByref(int[] arr, int index) + private static async Task HoistedByref(int[] arr, int index) { for (int i = 0; i < 20000; i++) { diff --git a/src/runtime/src/tests/async/with-yields/with-yields.cs b/src/runtime/src/tests/async/with-yields/with-yields.cs index 2f53a60f7c4..7c382b67109 100644 --- a/src/runtime/src/tests/async/with-yields/with-yields.cs +++ b/src/runtime/src/tests/async/with-yields/with-yields.cs @@ -8,7 +8,7 @@ public class Async2FibonacceWithYields { - internal static async2 Task B(int n) + internal static async Task B(int n) { int num = 1; await Task.Yield(); @@ -22,7 +22,7 @@ internal static async2 Task B(int n) return num; } - internal static async2 Task A(int n) + internal static async Task A(int n) { int num = n; for (int num2 = 0; num2 < n; num2++) @@ -33,6 +33,7 @@ internal static async2 Task A(int n) return num; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncEntry() { int result = 0; diff --git a/src/runtime/src/tests/async/without-yields/without-yields.cs b/src/runtime/src/tests/async/without-yields/without-yields.cs index ba1f2aeff0d..b0866fda6a7 100644 --- a/src/runtime/src/tests/async/without-yields/without-yields.cs +++ b/src/runtime/src/tests/async/without-yields/without-yields.cs @@ -11,12 +11,12 @@ public class Async2FibonacceWithoutYields //This async method lacks 'await' #pragma warning disable 1998 - internal static async2 Task B(int n) + internal static async Task B(int n) { return 100; } - internal static async2 Task A(int n) + internal static async Task A(int n) { int num = n; for (int num2 = 0; num2 < n; num2++) @@ -27,6 +27,7 @@ internal static async2 Task A(int n) return num; } + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private static async Task AsyncEntry() { int result = 0; diff --git a/src/runtime/src/tests/build.proj b/src/runtime/src/tests/build.proj index eb1c3adbb12..2a6dc297f2d 100644 --- a/src/runtime/src/tests/build.proj +++ b/src/runtime/src/tests/build.proj @@ -443,7 +443,12 @@ - + + - + - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee - + https://github.com/dotnet/dotnet - 85778473549347b3e4bad3ea009e9438df7b11bb + ad8565092bbfdd5c8b4a94a718d10b2d394f7aee diff --git a/src/scenario-tests/eng/Versions.props b/src/scenario-tests/eng/Versions.props index 61b3604659c..bf31f229bf7 100644 --- a/src/scenario-tests/eng/Versions.props +++ b/src/scenario-tests/eng/Versions.props @@ -4,6 +4,6 @@ preview - 2.0.0-beta5.25260.104 + 2.0.0-beta5.25265.101 diff --git a/src/scenario-tests/eng/common/build.sh b/src/scenario-tests/eng/common/build.sh index 36fba82a379..27ae2c85601 100755 --- a/src/scenario-tests/eng/common/build.sh +++ b/src/scenario-tests/eng/common/build.sh @@ -136,7 +136,7 @@ while [[ $# > 0 ]]; do restore=true pack=true ;; - -productBuild|-pb) + -productbuild|-pb) build=true product_build=true restore=true diff --git a/src/scenario-tests/eng/common/core-templates/steps/source-build.yml b/src/scenario-tests/eng/common/core-templates/steps/source-build.yml index f2a0f347fdd..0dde553c3eb 100644 --- a/src/scenario-tests/eng/common/core-templates/steps/source-build.yml +++ b/src/scenario-tests/eng/common/core-templates/steps/source-build.yml @@ -51,13 +51,12 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack -bl \ + --source-build \ ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ $targetRidArgs \ $baseRidArgs \ $portableBuildArgs \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ displayName: Build - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml diff --git a/src/scenario-tests/eng/common/darc-init.sh b/src/scenario-tests/eng/common/darc-init.sh index 36dbd45e1ce..e889f439b8d 100755 --- a/src/scenario-tests/eng/common/darc-init.sh +++ b/src/scenario-tests/eng/common/darc-init.sh @@ -68,7 +68,7 @@ function InstallDarcCli { fi fi - local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." diff --git a/src/scenario-tests/eng/common/tools.ps1 b/src/scenario-tests/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/scenario-tests/eng/common/tools.ps1 +++ b/src/scenario-tests/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/scenario-tests/eng/common/tools.sh b/src/scenario-tests/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/scenario-tests/eng/common/tools.sh +++ b/src/scenario-tests/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/scenario-tests/global.json b/src/scenario-tests/global.json index 58f82faf262..851c51e8903 100644 --- a/src/scenario-tests/global.json +++ b/src/scenario-tests/global.json @@ -10,6 +10,6 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25260.104" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25265.101" } } diff --git a/src/source-manifest.json b/src/source-manifest.json index 5815c2972e3..b2d4d01e9eb 100644 --- a/src/source-manifest.json +++ b/src/source-manifest.json @@ -1,11 +1,11 @@ { "repositories": [ { - "packageVersion": "10.0.0-beta.25265.1", - "barId": 268405, + "packageVersion": "10.0.0-beta.25268.1", + "barId": 268746, "path": "arcade", "remoteUri": "https://github.com/dotnet/arcade", - "commitSha": "9bbce22e13f399ad3cb8b4b7e53960b621f92ea1" + "commitSha": "35a34fa5ab9b2f97d3f7bdf48a7c2100727308b3" }, { "packageVersion": "8.2.2-preview.1.24521.5", @@ -15,18 +15,18 @@ "commitSha": "5fa9337a84a52e9bd185d04d156eccbdcf592f74" }, { - "packageVersion": "10.0.0-preview.5.25265.1", - "barId": 268378, + "packageVersion": "10.0.0-preview.5.25269.5", + "barId": 268904, "path": "aspnetcore", "remoteUri": "https://github.com/dotnet/aspnetcore", - "commitSha": "f0a43e8c514e5b2f013bdac6f4eca3a107e0438a" + "commitSha": "e26a16c185cf95e3c52b028920aeba6bcea814ce" }, { - "packageVersion": "0.11.5-alpha.25261.1", - "barId": 267824, + "packageVersion": "0.11.5-alpha.25266.1", + "barId": 268579, "path": "cecil", "remoteUri": "https://github.com/dotnet/cecil", - "commitSha": "091a4d494e2575c524c316013a482ec88bba28f2" + "commitSha": "50f19ccf4b834a6eab54496486a114ba3839f7b9" }, { "packageVersion": "0.1.626501", @@ -43,39 +43,39 @@ "commitSha": "e6ca69bc0712d70d910709016ed4fe30140e3a19" }, { - "packageVersion": "9.0.626302", - "barId": 268091, + "packageVersion": "9.0.626601", + "barId": 268685, "path": "diagnostics", "remoteUri": "https://github.com/dotnet/diagnostics", - "commitSha": "a3c354f4ea00123f55276619455569d53c4c402d" + "commitSha": "ce653def0d1969dca0d518a95559b2acc3c6128c" }, { - "packageVersion": "10.0.0-preview.4.25265.1", - "barId": 268415, + "packageVersion": "10.0.0-preview.4.25266.5", + "barId": 268656, "path": "efcore", "remoteUri": "https://github.com/dotnet/efcore", - "commitSha": "059c5c2d125d5fc8a5cce3438c0e38be108f1b6a" + "commitSha": "d0237c7e0c3192b00766d6a163fe2a5def526e03" }, { - "packageVersion": "10.0.0-preview.5.25264.1", - "barId": 268180, + "packageVersion": "10.0.0-preview.5.25266.1", + "barId": 268589, "path": "emsdk", "remoteUri": "https://github.com/dotnet/emsdk", - "commitSha": "b01d7473b8d3088bad808f83bf9ed89b4c341563" + "commitSha": "1f91ec07aa2b387b41ec464fd876dca5552150e2" }, { - "packageVersion": "10.0.100-beta.25224.6", - "barId": 265640, + "packageVersion": "10.0.100-beta.25266.3", + "barId": 268571, "path": "fsharp", "remoteUri": "https://github.com/dotnet/fsharp", - "commitSha": "e8bfd3562774b2939c252d907de2f3173a2bd5bd" + "commitSha": "665b1f1cdff308582aa1a944aa03c3a62d1fbcd0" }, { - "packageVersion": "17.15.0-preview-25265-04", - "barId": 268395, + "packageVersion": "17.15.0-preview-25266-01", + "barId": 268496, "path": "msbuild", "remoteUri": "https://github.com/dotnet/msbuild", - "commitSha": "b6fb9dd6c70366421497d6621ea966e610784e15" + "commitSha": "f5b4822ee5fdbb9001e38fc324c43fd1d1b090c9" }, { "packageVersion": "6.15.0-preview.1.50", @@ -85,39 +85,39 @@ "commitSha": "e4e3b79701686199bc804a06533d2df054924d7e" }, { - "packageVersion": "10.0.0-preview.25265.1", - "barId": 268475, + "packageVersion": "10.0.0-preview.25267.1", + "barId": 268713, "path": "razor", "remoteUri": "https://github.com/dotnet/razor", - "commitSha": "6352a3201c903ffe907d9c7d2eb3a10507f1bd4e" + "commitSha": "013cbf58b149ad9664d2b1252a73ed51f7cdb564" }, { - "packageVersion": "5.0.0-1.25265.6", - "barId": 268491, + "packageVersion": "5.0.0-1.25266.4", + "barId": 268710, "path": "roslyn", "remoteUri": "https://github.com/dotnet/roslyn", - "commitSha": "015a854ffc3fc7660eca31b0ffe168ea8d1d65a0" + "commitSha": "c3c7ad6a866dd0b857ad14ce683987c39d2b8fe0" }, { - "packageVersion": "10.0.0-preview.25263.1", - "barId": 268093, + "packageVersion": "10.0.0-preview.25267.1", + "barId": 268740, "path": "roslyn-analyzers", "remoteUri": "https://github.com/dotnet/roslyn-analyzers", - "commitSha": "df7fa025647ab226d55b7de67a72ebbfed60f5a0" + "commitSha": "5db220ec96a406ab3a095bdf73ffa37fd94a8c9b" }, { "packageVersion": "10.0.0-preview.5.25262.10", - "barId": 268477, + "barId": 268758, "path": "runtime", "remoteUri": "https://github.com/dotnet/runtime", - "commitSha": "1307a67de8039d70a9e3d6e74d6651afbd8a0278" + "commitSha": "29638e8e84335bc877f064766be3f1b3038da311" }, { "packageVersion": "10.0.0-preview.25221.1", - "barId": 267832, + "barId": 268572, "path": "scenario-tests", "remoteUri": "https://github.com/dotnet/scenario-tests", - "commitSha": "828faff7300aac7fae6f9544393f9cb317baeb6d" + "commitSha": "ba23e62da145ef04187aabe1e1fb64029fe2317d" }, { "packageVersion": "10.0.100-preview.5.25265.12", @@ -155,39 +155,39 @@ "commitSha": "d38d92c12935201bb8852096935c267d53592ad9" }, { - "packageVersion": "10.0.100-preview.5.25261.1", - "barId": 267808, + "packageVersion": "10.0.100-preview.5.25266.4", + "barId": 268676, "path": "templating", "remoteUri": "https://github.com/dotnet/templating", - "commitSha": "db6256f5f72d0b954273e94f616f1bf0ba9c443c" + "commitSha": "297983ef280be4b32f0d36b5bb2742b8e6e947b3" }, { - "packageVersion": "17.15.0-preview-25224-01", - "barId": 265567, + "packageVersion": "17.15.0-preview-25266-01", + "barId": 268581, "path": "vstest", "remoteUri": "https://github.com/microsoft/vstest", - "commitSha": "2e6d9288d3aa0269ae710844f3aa9e0a3981b26e" + "commitSha": "f93fcd2943f39c2cbb65d25e3949e21c3190184c" }, { - "packageVersion": "10.0.0-preview.5.25265.1", - "barId": 268391, + "packageVersion": "10.0.0-preview.5.25267.3", + "barId": 268739, "path": "windowsdesktop", "remoteUri": "https://github.com/dotnet/windowsdesktop", - "commitSha": "b9faa7e590d3e5acc87d0600072167d140b811f1" + "commitSha": "66210f6e71c81c26d9c6dd3fef6a6a600528ee9d" }, { - "packageVersion": "10.0.0-preview.5.25265.1", - "barId": 268429, + "packageVersion": "10.0.0-preview.5.25267.1", + "barId": 268741, "path": "winforms", "remoteUri": "https://github.com/dotnet/winforms", - "commitSha": "05a904f4c1419562b2ae01bad597f9cd3bad5872" + "commitSha": "716bc129a4679a98394b2e2d24d1d1d3d4f54ddb" }, { - "packageVersion": "10.0.0-preview.5.25265.3", - "barId": 268423, + "packageVersion": "10.0.0-preview.5.25266.3", + "barId": 268569, "path": "wpf", "remoteUri": "https://github.com/dotnet/wpf", - "commitSha": "1002fa520a05b240ace9f51a69a60314edc8c762" + "commitSha": "57fe8ac611226f89c1a213fea520a7a82000c6a4" }, { "packageVersion": "10.0.0-preview.25262.1", @@ -201,7 +201,7 @@ { "path": "aspnetcore/src/submodules/googletest", "remoteUri": "https://github.com/google/googletest", - "commitSha": "90a41521142c978131f38c6da07b4eb96a9f1ff6" + "commitSha": "571930618fa96eabcd05b573285edbee9fc13bae" }, { "path": "aspnetcore/src/submodules/MessagePack-CSharp", diff --git a/src/templating/eng/common/sdl/packages.config b/src/templating/eng/common/sdl/packages.config index e5f543ea68c..4585cfd6bba 100644 --- a/src/templating/eng/common/sdl/packages.config +++ b/src/templating/eng/common/sdl/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/templating/github-merge-flow.jsonc b/src/templating/github-merge-flow.jsonc index fe686d5ff56..d7a5b4ef056 100644 --- a/src/templating/github-merge-flow.jsonc +++ b/src/templating/github-merge-flow.jsonc @@ -16,13 +16,8 @@ "MergeToBranch": "release/9.0.1xx", "ExtraSwitches": "-QuietComments" }, - // Automate opening PRs to merge sdk repos from release/9.0.1xx to release/9.0.2xx + // Automate opening PRs to merge sdk repos from release/9.0.1xx to release/9.0.3xx "release/9.0.1xx":{ - "MergeToBranch": "release/9.0.2xx", - "ExtraSwitches": "-QuietComments" - }, - // Automate opening PRs to merge sdk repos from release/9.0.2xx to release/9.0.3xx - "release/9.0.2xx":{ "MergeToBranch": "release/9.0.3xx", "ExtraSwitches": "-QuietComments" }, diff --git a/src/templating/test/Microsoft.TemplateEngine.Edge.UnitTests/NuGetApiPackageManagerTests.cs b/src/templating/test/Microsoft.TemplateEngine.Edge.UnitTests/NuGetApiPackageManagerTests.cs index b2b80d56102..5c71d02937c 100644 --- a/src/templating/test/Microsoft.TemplateEngine.Edge.UnitTests/NuGetApiPackageManagerTests.cs +++ b/src/templating/test/Microsoft.TemplateEngine.Edge.UnitTests/NuGetApiPackageManagerTests.cs @@ -146,13 +146,13 @@ internal async Task DownloadPackage_HasVulnerabilities() var exception = await Assert.ThrowsAsync(() => packageManager.DownloadPackageAsync( installPath, - "log4net", - "2.0.3", + "System.Text.Json", + "8.0.4", // add the source for getting vulnerability info additionalSources: _additionalSources)); - exception.PackageIdentifier.Should().Be("log4net"); - exception.PackageVersion.Should().Be("2.0.3"); + exception.PackageIdentifier.Should().Be("System.Text.Json"); + exception.PackageVersion.Should().Be("8.0.4"); exception.Vulnerabilities.Should().NotBeNullOrEmpty(); } @@ -167,15 +167,15 @@ internal async Task DownloadPackage_HasVulnerabilitiesForce() var result = await packageManager.DownloadPackageAsync( installPath, - "log4net", - "2.0.3", + "System.Text.Json", + "8.0.4", // add the source for getting vulnerability info additionalSources: _additionalSources, force: true); - result.PackageIdentifier.Should().Be("log4net"); - result.Author.Should().Be("Apache Software Foundation"); - result.PackageVersion.Should().Be("2.0.3"); + result.PackageIdentifier.Should().Be("System.Text.Json"); + result.Author.Should().Be("Microsoft"); + result.PackageVersion.Should().Be("8.0.4"); Assert.True(File.Exists(result.FullPath)); result.PackageVulnerabilities.Should().NotBeNullOrEmpty(); result.NuGetSource.Should().Be(_additionalSources[0]); diff --git a/src/vstest/eng/DotNetBuild.props b/src/vstest/eng/DotNetBuild.props index ca7b2fbbeb0..e8416d87c97 100644 --- a/src/vstest/eng/DotNetBuild.props +++ b/src/vstest/eng/DotNetBuild.props @@ -1,10 +1,13 @@ - + vstest true + $(DotNetBuildOrchestrator) + false + false diff --git a/src/vstest/eng/SourceBuildPrebuiltBaseline.xml b/src/vstest/eng/SourceBuildPrebuiltBaseline.xml deleted file mode 100644 index 6168c26bd33..00000000000 --- a/src/vstest/eng/SourceBuildPrebuiltBaseline.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vstest/eng/Version.Details.xml b/src/vstest/eng/Version.Details.xml index a2d57ffe1c3..149f7d69106 100644 --- a/src/vstest/eng/Version.Details.xml +++ b/src/vstest/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage @@ -10,24 +10,6 @@ https://github.com/dotnet/diagnostics 22431fb5bfe047454e652a7274f4b4bc4b367527 - - - https://github.com/dotnet/diagnostics - 22431fb5bfe047454e652a7274f4b4bc4b367527 - - - - - https://github.com/dotnet/source-build-externals - 4df883d781a4290873b3b968afc0ff0df7132507 - - - - - https://github.com/dotnet/source-build-reference-packages - 7dbf5deea5bdccf513df73cba179c4c0ad106010 - - https://github.com/dotnet/runtime @@ -45,15 +27,9 @@ - + https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 - - - - https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 - + 1cfa39f82d00b3659a3d367bc344241946e10681 https://github.com/dotnet/symreader-converter @@ -63,9 +39,5 @@ https://github.com/dotnet/symreader-converter c5ba7c88f92e2dde156c324a8c8edc04d9fa4fe0 - - https://github.com/dotnet/arcade - 97cbc7361ff28b2948c8182720c166a744049f55 - diff --git a/src/vstest/eng/common/core-templates/job/source-build.yml b/src/vstest/eng/common/core-templates/job/source-build.yml index c4713c8b6ed..d47f09d58fd 100644 --- a/src/vstest/eng/common/core-templates/job/source-build.yml +++ b/src/vstest/eng/common/core-templates/job/source-build.yml @@ -26,6 +26,8 @@ parameters: # Specifies the build script to invoke to perform the build in the repo. The default # './build.sh' should work for typical Arcade repositories, but this is customizable for # difficult situations. + # buildArguments: '' + # Specifies additional build arguments to pass to the build script. # jobProperties: {} # A list of job properties to inject at the top level, for potential extensibility beyond # container and pool. diff --git a/src/vstest/eng/common/core-templates/job/source-index-stage1.yml b/src/vstest/eng/common/core-templates/job/source-index-stage1.yml index 205fb5b3a39..8b833332b3e 100644 --- a/src/vstest/eng/common/core-templates/job/source-index-stage1.yml +++ b/src/vstest/eng/common/core-templates/job/source-index-stage1.yml @@ -1,7 +1,7 @@ parameters: runAsPublic: false - sourceIndexUploadPackageVersion: 2.0.0-20240522.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20240522.1 + sourceIndexUploadPackageVersion: 2.0.0-20250425.2 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250425.2 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] diff --git a/src/vstest/eng/common/core-templates/steps/source-build.yml b/src/vstest/eng/common/core-templates/steps/source-build.yml index 2915d29bb7f..37133b55b75 100644 --- a/src/vstest/eng/common/core-templates/steps/source-build.yml +++ b/src/vstest/eng/common/core-templates/steps/source-build.yml @@ -79,6 +79,7 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ --restore --build --pack $publishArgs -bl \ + ${{ parameters.platform.buildArguments }} \ $officialBuildArgs \ $internalRuntimeDownloadArgs \ $internalRestoreArgs \ diff --git a/src/vstest/global.json b/src/vstest/global.json index 2cae330dffa..a06d2ef71e1 100644 --- a/src/vstest/global.json +++ b/src/vstest/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.104", + "version": "9.0.105", "rollForward": "minor", "allowPrerelease": false, "architecture": "x64" @@ -28,9 +28,9 @@ "version": "17.8.0" }, "vswhere": "2.2.7", - "dotnet": "9.0.104" + "dotnet": "9.0.105" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25204.5" + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25255.5" } } diff --git a/src/windowsdesktop/eng/Version.Details.xml b/src/windowsdesktop/eng/Version.Details.xml index a825221232b..b3562e80f74 100644 --- a/src/windowsdesktop/eng/Version.Details.xml +++ b/src/windowsdesktop/eng/Version.Details.xml @@ -1,176 +1,176 @@ - + - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - ad8565092bbfdd5c8b4a94a718d10b2d394f7aee + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 diff --git a/src/windowsdesktop/eng/Versions.props b/src/windowsdesktop/eng/Versions.props index 51b0f52b7ee..fd984850ee3 100644 --- a/src/windowsdesktop/eng/Versions.props +++ b/src/windowsdesktop/eng/Versions.props @@ -8,16 +8,16 @@ false release - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-beta.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-beta.25267.102 + 10.0.0-preview.5.25267.102 - 10.0.0-beta.25265.101 - 10.0.0-beta.25265.101 - 10.0.0-beta.25265.101 + 10.0.0-beta.25267.102 + 10.0.0-beta.25267.102 + 10.0.0-beta.25267.102 4.5.0 6.12.1 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 5.0.0 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 5.0.0 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 5.0.0 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 6.0.0 5.0.0 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 5.0.0 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 8.1.2 - 10.0.0-preview.5.25265.101 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 - 10.0.0-preview.5.25265.101 + 10.0.0-preview.5.25267.102 diff --git a/src/windowsdesktop/eng/common/build.ps1 b/src/windowsdesktop/eng/common/build.ps1 index 6b3be1916fc..ae2309e312d 100644 --- a/src/windowsdesktop/eng/common/build.ps1 +++ b/src/windowsdesktop/eng/common/build.ps1 @@ -127,7 +127,7 @@ function Build { /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` - /p:DotNetBuildRepo=$productBuild ` + /p:DotNetBuild=$productBuild ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` diff --git a/src/windowsdesktop/eng/common/build.sh b/src/windowsdesktop/eng/common/build.sh index 27ae2c85601..da906da2026 100755 --- a/src/windowsdesktop/eng/common/build.sh +++ b/src/windowsdesktop/eng/common/build.sh @@ -129,14 +129,14 @@ while [[ $# > 0 ]]; do -pack) pack=true ;; - -sourcebuild|-sb) + -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; - -productbuild|-pb) + -productbuild|-product-build|-pb) build=true product_build=true restore=true @@ -241,7 +241,7 @@ function Build { /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ - /p:DotNetBuildRepo=$product_build \ + /p:DotNetBuild=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ /p:Rebuild=$rebuild \ /p:Test=$test \ diff --git a/src/windowsdesktop/global.json b/src/windowsdesktop/global.json index d16d0dbc9ec..51c138461b1 100644 --- a/src/windowsdesktop/global.json +++ b/src/windowsdesktop/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "10.0.100-preview.3.25201.16", + "dotnet": "10.0.100-preview.5.25265.106", "runtimes": { "dotnet": [ "$(MicrosoftNETCorePlatformsVersion)" @@ -8,8 +8,8 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25265.101", - "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.25265.101", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25267.102", + "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.25267.102", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.4.0" } diff --git a/src/winforms/eng/Version.Details.xml b/src/winforms/eng/Version.Details.xml index 0dc29a1f6b7..69c38a6c136 100644 --- a/src/winforms/eng/Version.Details.xml +++ b/src/winforms/eng/Version.Details.xml @@ -6,106 +6,106 @@ Note: if the Uri is a new place, you will need to add a subscription from that p And you can check these with "darc get-dependencies target-repo "winforms" --> - + - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 - + https://github.com/dotnet/dotnet - 362a4227bd38985f75f6a6d7f6733c7d62414569 + 170498a9429a5553fe7ac0ec2341d19bbb97cbe8 diff --git a/src/winforms/eng/Versions.props b/src/winforms/eng/Versions.props index 36d8eb30b35..5ae2e78b3f5 100644 --- a/src/winforms/eng/Versions.props +++ b/src/winforms/eng/Versions.props @@ -10,30 +10,30 @@ $(MajorVersion).$(MinorVersion).$(PatchVersion) false release - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 5.0.0-preview.7.20320.5 - 10.0.0-preview.5.25263.108 + 10.0.0-preview.5.25267.102 6.1.0-preview.1.24511.1 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 10.0.0-preview.5.25227.101 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 - 10.0.0-preview.5.25263.108 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 + 10.0.0-preview.5.25267.102 @@ -46,9 +46,9 @@ - 10.0.0-beta.25263.108 - 10.0.0-beta.25263.108 - 10.0.0-beta.25263.108 + 10.0.0-beta.25267.102 + 10.0.0-beta.25267.102 + 10.0.0-beta.25267.102 17.4.0-preview-20220707-01 diff --git a/src/winforms/eng/common/build.ps1 b/src/winforms/eng/common/build.ps1 index 6b3be1916fc..ae2309e312d 100644 --- a/src/winforms/eng/common/build.ps1 +++ b/src/winforms/eng/common/build.ps1 @@ -127,7 +127,7 @@ function Build { /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` - /p:DotNetBuildRepo=$productBuild ` + /p:DotNetBuild=$productBuild ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` diff --git a/src/winforms/eng/common/build.sh b/src/winforms/eng/common/build.sh index 27ae2c85601..da906da2026 100755 --- a/src/winforms/eng/common/build.sh +++ b/src/winforms/eng/common/build.sh @@ -129,14 +129,14 @@ while [[ $# > 0 ]]; do -pack) pack=true ;; - -sourcebuild|-sb) + -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; - -productbuild|-pb) + -productbuild|-product-build|-pb) build=true product_build=true restore=true @@ -241,7 +241,7 @@ function Build { /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ - /p:DotNetBuildRepo=$product_build \ + /p:DotNetBuild=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ /p:Rebuild=$rebuild \ /p:Test=$test \ diff --git a/src/winforms/eng/common/tools.ps1 b/src/winforms/eng/common/tools.ps1 index 7373e530546..5f40a3f8238 100644 --- a/src/winforms/eng/common/tools.ps1 +++ b/src/winforms/eng/common/tools.ps1 @@ -68,8 +68,6 @@ $ErrorActionPreference = 'Stop' # True if the build is a product build [bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } - function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null } @@ -853,7 +851,7 @@ function MSBuild-Core() { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/winforms/eng/common/tools.sh b/src/winforms/eng/common/tools.sh index cc007b1f15a..25f5932eee9 100755 --- a/src/winforms/eng/common/tools.sh +++ b/src/winforms/eng/common/tools.sh @@ -507,7 +507,7 @@ function MSBuild-Core { # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/src/winforms/global.json b/src/winforms/global.json index 5c49ec21d6d..42f6f940fc7 100644 --- a/src/winforms/global.json +++ b/src/winforms/global.json @@ -1,6 +1,6 @@ { "tools": { - "dotnet": "10.0.100-preview.3.25201.16", + "dotnet": "10.0.100-preview.5.25265.106", "runtimes": { "dotnet/x64": [ "$(MicrosoftNETCorePlatformsPackageVersion)" @@ -11,14 +11,14 @@ } }, "sdk": { - "version": "10.0.100-preview.3.25201.16" + "version": "10.0.100-preview.5.25265.106" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25263.108", - "Microsoft.DotNet.CMake.Sdk": "10.0.0-beta.25263.108", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25263.108", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25267.102", + "Microsoft.DotNet.CMake.Sdk": "10.0.0-beta.25267.102", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25267.102", "FIX-85B6-MERGE-9C38-CONFLICT": "1.0.0", - "Microsoft.NET.Sdk.IL": "10.0.0-preview.5.25263.108" + "Microsoft.NET.Sdk.IL": "10.0.0-preview.5.25267.102" }, "native-tools": { "cmake": "latest" diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputProcessorProfiles.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputProcessorProfiles.cs index be116765831..c7602cf3321 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputProcessorProfiles.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputProcessorProfiles.cs @@ -1,44 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// // // Description: Creates ITfInputProcessorProfiles instances. // -// using System.Runtime.InteropServices; +using System.Globalization; using System.Threading; using MS.Win32; -using System.Globalization; -using System.Collections; namespace System.Windows.Input { - //------------------------------------------------------ - // - // InputProcessorProfiles class - // - //------------------------------------------------------ - /// - /// The InputProcessorProfiles class is always associated with - /// hwndInputLanguage class. + /// The class is always associated with hwndInputLanguage class. /// - internal class InputProcessorProfiles + internal sealed class InputProcessorProfiles { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - /// /// InputProcessorProfiles Constructor; /// - /// Critical - as this sets the value for _ipp. - /// Safe - as this just initializes it to null. internal InputProcessorProfiles() { // _ipp is a ValueType, hence no need for new. @@ -46,14 +27,6 @@ internal InputProcessorProfiles() _cookie = UnsafeNativeMethods.TF_INVALID_COOKIE; } - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - /// /// Initialize an interface and notify sink. /// @@ -81,19 +54,11 @@ internal void Uninitialize() { Debug.Assert(_ipp != null, "Uninitialize called without initializing"); - UnadviseNotifySink(); + UnadviseNotifySink(); Marshal.ReleaseComObject(_ipp); _ipp = null; } - #endregion Internal Methods - - //------------------------------------------------------ - // - // Internal Properties - // - //------------------------------------------------------ - /// /// Get the current input language of the current thread. /// @@ -112,7 +77,7 @@ internal short CurrentInputLanguage IntPtr[] hklList = null; int count = (int)SafeNativeMethods.GetKeyboardLayoutList(0, null); - if (count > 1) + if (count > 1) { hklList = new IntPtr[count]; @@ -123,7 +88,7 @@ internal short CurrentInputLanguage { if (value == (short)hklList[i]) { - SafeNativeMethods.ActivateKeyboardLayout(new HandleRef(this,hklList[i]), 0); + SafeNativeMethods.ActivateKeyboardLayout(new HandleRef(this, hklList[i]), 0); break; } } @@ -134,43 +99,29 @@ internal short CurrentInputLanguage } /// - /// Get the list of the input languages that are available in the - /// current thread. + /// Get the list of the input languages that are available in the current thread. /// - internal ArrayList InputLanguageList + internal unsafe CultureInfo[] InputLanguageList { - get - { - int nCount; - IntPtr langids; - - // ITfInputProcessorProfiles::GetLanguageList returns the pointer that was allocated by - // CoTaskMemAlloc(). - _ipp.GetLanguageList(out langids, out nCount); + get + { + // ITfInputProcessorProfiles::GetLanguageList returns the pointer that was allocated by CoTaskMemAlloc(). + _ipp.GetLanguageList(out nint ptrLanguageIDs, out int nCount); - ArrayList arrayLang = new ArrayList(); + ReadOnlySpan languageIDs = new((void*)ptrLanguageIDs, nCount); + CultureInfo[] langArray = new CultureInfo[nCount]; - for (int i = 0; i < nCount; i++) - { - // Unmarshal each langid from short array. - short langid = Marshal.PtrToStructure((IntPtr)((Int64)langids + sizeof(short) * i)); - arrayLang.Add(new CultureInfo(langid)); - } + // Create CultureInfo from each ID and store it + for (int i = 0; i < langArray.Length; i++) + langArray[i] = new CultureInfo(languageIDs[i]); - // Call CoTaskMemFree(). - Marshal.FreeCoTaskMem(langids); + // Call CoTaskMemFree(). + Marshal.FreeCoTaskMem(ptrLanguageIDs); - return arrayLang; - } + return langArray; + } } - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - /// /// This advices the input language notify sink to /// ITfInputProcessorProfile. @@ -201,16 +152,14 @@ private void UnadviseNotifySink() _cookie = UnsafeNativeMethods.TF_INVALID_COOKIE; } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - // The reference to ITfInputProcessorProfile. + /// + /// The reference to . + /// private UnsafeNativeMethods.ITfInputProcessorProfiles _ipp; - // The cookie for the advised sink. + /// + /// The cookie for the advised sink. + /// private int _cookie; } } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Knowncolors.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Knowncolors.cs index 6d5634aeac7..2a22f29652c 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Knowncolors.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Knowncolors.cs @@ -6,8 +6,9 @@ #if PBTCOMPILER namespace MS.Internal.Markup #else -using System.Collections.Generic; using MS.Internal; +using System.Collections.Generic; +using System.Globalization; namespace System.Windows.Media #endif @@ -168,16 +169,6 @@ internal static class KnownColors { #if !PBTCOMPILER - static KnownColors() - { - KnownColor[] knownColorValues = Enum.GetValues(); - foreach (KnownColor colorValue in knownColorValues) - { - string aRGBString = String.Format("#{0,8:X8}", (uint)colorValue); - s_knownArgbColors[aRGBString] = colorValue; - } - } - /// Return the solid color brush from a color string. If there's no match, null public static SolidColorBrush ColorStringToKnownBrush(string s) { @@ -818,20 +809,28 @@ internal static KnownColor ColorStringToKnownColor(string colorString) #if !PBTCOMPILER internal static KnownColor ArgbStringToKnownColor(string argbString) { - string argbUpper = argbString.Trim().ToUpper(System.Globalization.CultureInfo.InvariantCulture); + ArgumentNullException.ThrowIfNull(argbString); + + ReadOnlySpan argbSpan = argbString.AsSpan().Trim(); + + // Use NumberStyles.AllowHexSpecifier instead of NumberStyles.HexNumber because NumberStyles.HexNumber + // trims the whitespaces when we already trim it in the code above and we don't consider values + // with whitespaces between the "#" and the hex value to be valid values. + if (argbSpan.StartsWith('#') && uint.TryParse(argbSpan[1..], NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out uint uintValue)) + { + KnownColor color = (KnownColor)uintValue; - KnownColor color; - if (s_knownArgbColors.TryGetValue(argbUpper, out color)) - return color; + if (Enum.IsDefined(color)) + return color; + } - return KnownColor.UnknownColor; + return KnownColor.UnknownColor; } #if DEBUG private static int s_count = 0; #endif private static Dictionary s_solidColorBrushCache = new Dictionary(); - private static Dictionary s_knownArgbColors = new Dictionary(); #endif } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/TextDecorationCollectionConverter.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/TextDecorationCollectionConverter.cs index b829ea3a3bf..b431c3f226e 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/TextDecorationCollectionConverter.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/TextDecorationCollectionConverter.cs @@ -1,272 +1,159 @@ // 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.ComponentModel.Design.Serialization; +using System.ComponentModel; using System.Globalization; using System.Reflection; namespace System.Windows { /// - /// TypeConverter for TextDecorationCollection - /// + /// Provides a type converter to convert from to only. + /// public sealed class TextDecorationCollectionConverter : TypeConverter { /// - /// CanConvertTo method + /// Returns whether this converter can convert the object to the specified + /// , using the specified . /// - /// ITypeDescriptorContext - /// Type to convert to - /// false will always be returned because TextDecorations cannot be converted to any other type. + /// Context information used for conversion. + /// Type being evaluated for conversion. + /// + /// will always be returned because cannot be converted to any other type. + /// public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - if (destinationType == typeof(InstanceDescriptor)) - { - return true; - } - - // return false for any other target type. Don't call base.CanConvertTo() because it would be confusing - // in some cases. For example, for destination typeof(String), base convertor just converts the TDC to the - // string full name of the type. - return false; + // Return false for any other target type. Don't call base.CanConvertTo() because it would be confusing + // in some cases. For example, for destination typeof(string), base TypeConverter just converts the + // ITypeDescriptorContext to the full name string of the given type. + return destinationType == typeof(InstanceDescriptor); } /// - /// CanConvertFrom + /// Returns whether this class can convert specific into . /// /// ITypeDescriptorContext - /// Type to convert to - /// true if it can convert from sourceType to TextDecorations, false otherwise + /// Type being evaluated for conversion. + /// + /// if is , otherwise . + /// public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - if (sourceType == typeof(string)) - { - return true; - } - - return false; + return sourceType == typeof(string); } /// - /// ConvertFrom + /// Converts of type to its representation. /// - /// ITypeDescriptorContext - /// CultureInfo - /// The input object to be converted to TextDecorations - /// the converted value of the input object + /// Context information used for conversion, ignored currently. + /// The culture specifier to use, ignored currently. + /// The string to convert from. + /// A representing the specified by . public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object input) { - if (input == null) - { + if (input is null) throw GetConvertFromException(input); - } - string value = input as string; - - if (null == value) - { + if (input is not string value) throw new ArgumentException(SR.Format(SR.General_BadType, "ConvertFrom"), nameof(input)); - } - - return ConvertFromString(value); + + return ConvertFromString(value); } /// - /// ConvertFromString + /// Converts to its representation. /// - /// The string to be converted into TextDecorationCollection object - /// the converted value of the string flag + /// The string to be converted into TextDecorationCollection object. + /// A representing the specified by . /// - /// The text parameter can be either string "None" or a combination of the predefined - /// TextDecoration names delimited by commas (,). One or more blanks spaces can precede - /// or follow each text decoration name or comma. There can't be duplicate TextDecoration names in the - /// string. The operation is case-insensitive. + /// The text parameter can be either be ; ; the string "None" + /// or a combination of the predefined names delimited by commas (,). + /// One or more blanks spaces can precede or follow each text decoration name or comma. + /// There can't be duplicate TextDecoration names in the string. The operation is case-insensitive. /// public static new TextDecorationCollection ConvertFromString(string text) - { - if (text == null) - { - return null; - } + { + if (text is null) + return null; + + // Flags indicating which pre-defined TextDecoration have been matched + Decorations matchedDecorations = Decorations.None; + + // Sanitize the input + ReadOnlySpan decorationsSpan = text.AsSpan().Trim(); - TextDecorationCollection textDecorations = new TextDecorationCollection(); + // Test for "None", which equals to empty collection and needs to be specified alone + if (decorationsSpan.IsEmpty || decorationsSpan.Equals("None", StringComparison.OrdinalIgnoreCase)) + return new TextDecorationCollection(); - // Flags indicating which Predefined textDecoration has alrady been added. - byte MatchedTextDecorationFlags = 0; - - // Start from the 1st non-whitespace character - // Negative index means error is encountered - int index = AdvanceToNextNonWhiteSpace(text, 0); - while (index >= 0 && index < text.Length) + // Create new collection, save re-allocations + TextDecorationCollection textDecorations = new(1 + decorationsSpan.Count(',')); + foreach (Range segment in decorationsSpan.Split(',')) { - if (Match(None, text, index)) + ReadOnlySpan decoration = decorationsSpan[segment].Trim(); + + if (decoration.Equals("Overline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.OverlineMatch)) + { + textDecorations.Add(TextDecorations.OverLine[0]); + matchedDecorations |= Decorations.OverlineMatch; + } + else if (decoration.Equals("Baseline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.BaselineMatch)) { - // Matched "None" in the input - index = AdvanceToNextNonWhiteSpace(text, index + None.Length); - if (textDecorations.Count > 0 || index < text.Length) - { - // Error: "None" can only be specified by its own - index = -1; - } + textDecorations.Add(TextDecorations.Baseline[0]); + matchedDecorations |= Decorations.BaselineMatch; + } + else if (decoration.Equals("Underline", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.UnderlineMatch)) + { + textDecorations.Add(TextDecorations.Underline[0]); + matchedDecorations |= Decorations.UnderlineMatch; + } + else if (decoration.Equals("Strikethrough", StringComparison.OrdinalIgnoreCase) && !matchedDecorations.HasFlag(Decorations.StrikethroughMatch)) + { + textDecorations.Add(TextDecorations.Strikethrough[0]); + matchedDecorations |= Decorations.StrikethroughMatch; } else { - // Match the input with one of the predefined text decoration names - int i; - for(i = 0; - i < TextDecorationNames.Length - && !Match(TextDecorationNames[i], text, index); - i++ - ); - - if (i < TextDecorationNames.Length) - { - // Found a match within the predefined names - if ((MatchedTextDecorationFlags & (1 << i)) > 0) - { - // Error: The matched value is duplicated. - index = -1; - } - else - { - // Valid match. Add to the collection and remember that this text decoration - // has been added - textDecorations.Add(PredefinedTextDecorations[i]); - MatchedTextDecorationFlags |= (byte)(1 << i); - - // Advance to the start of next name - index = AdvanceToNextNameStart(text, index + TextDecorationNames[i].Length); - } - } - else - { - // Error: no match found in the predefined names - index = -1; - } + throw new ArgumentException(SR.Format(SR.InvalidTextDecorationCollectionString, text)); } } - if (index < 0) - { - throw new ArgumentException(SR.Format(SR.InvalidTextDecorationCollectionString, text)); - } - - return textDecorations; - } + return textDecorations; + } /// - /// ConvertTo + /// Converts a of to the specified . /// - /// ITypeDescriptorContext - /// CultureInfo - /// the object to be converted to another type - /// The destination type of the conversion - /// null will always be returned because TextDecorations cannot be converted to any other type. + /// Context information used for conversion. + /// The culture specifier to use. + /// Duration value to convert from. + /// Type being evaluated for conversion. + /// will always be returned because cannot be converted to any other type. public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(InstanceDescriptor) && value is IEnumerable) { - ConstructorInfo ci = typeof(TextDecorationCollection).GetConstructor( - new Type[]{typeof(IEnumerable)} - ); - - return new InstanceDescriptor(ci, new object[]{value}); - } - - // Pass unhandled cases to base class (which will throw exceptions for null value or destinationType.) - return base.ConvertTo(context, culture, value, destinationType); - } - - //--------------------------------- - // Private methods - //--------------------------------- - /// - /// Match the input against a predefined pattern from certain index onwards - /// - private static bool Match(string pattern, string input, int index) - { - int i = 0; - for (; - i < pattern.Length - && index + i < input.Length - && pattern[i] == Char.ToUpperInvariant(input[index + i]); - i++) ; - - return (i == pattern.Length); - } - - /// - /// Advance to the start of next name - /// - private static int AdvanceToNextNameStart(string input, int index) - { - // Two names must be seperated by a comma and optionally spaces - int separator = AdvanceToNextNonWhiteSpace(input, index); + ConstructorInfo ci = typeof(TextDecorationCollection).GetConstructor(new Type[] { typeof(IEnumerable) }); - int nextNameStart; - if (separator >= input.Length) - { - // reach the end - nextNameStart = input.Length; - } - else - { - if (input[separator] == Separator) - { - nextNameStart = AdvanceToNextNonWhiteSpace(input, separator + 1); - if (nextNameStart >= input.Length) - { - // Error: Separator is at the end of the input - nextNameStart = -1; - } - } - else - { - // Error: There is a non-whitespace, non-separator character following - // the matched value - nextNameStart = -1; - } + return new InstanceDescriptor(ci, new object[] { value }); } - return nextNameStart; + // Pass unhandled cases to base class (which will throw exceptions for null value or destinationType.) + return base.ConvertTo(context, culture, value, destinationType); } /// - /// Advance to the next non-whitespace character + /// Abstraction helper of matched decorations during conversion. /// - private static int AdvanceToNextNonWhiteSpace(string input, int index) + [Flags] + private enum Decorations : byte { - for (; index < input.Length && Char.IsWhiteSpace(input[index]); index++) ; - return (index > input.Length) ? input.Length : index; + None = 0, + OverlineMatch = 1 << 0, + BaselineMatch = 1 << 1, + UnderlineMatch = 1 << 2, + StrikethroughMatch = 1 << 3, } - - //--------------------------------- - // Private members - //--------------------------------- - - // - // Predefined valid names for TextDecorations - // Names should be normalized to be upper case - // - private const string None = "NONE"; - private const char Separator = ','; - - private static readonly string[] TextDecorationNames = new string[] { - "OVERLINE", - "BASELINE", - "UNDERLINE", - "STRIKETHROUGH" - }; - - // Predefined TextDecorationCollection values. It should match - // the TextDecorationNames array - private static readonly TextDecorationCollection[] PredefinedTextDecorations = - new TextDecorationCollection[] { - TextDecorations.OverLine, - TextDecorations.Baseline, - TextDecorations.Underline, - TextDecorations.Strikethrough - }; -} + } } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/ManagedWndProcTracker.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/ManagedWndProcTracker.cs index 0dc0d383e47..56c86f47158 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/ManagedWndProcTracker.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/ManagedWndProcTracker.cs @@ -3,8 +3,9 @@ //#define LOGGING -using System.Collections; +using System.ComponentModel; using System.Runtime.InteropServices; +using System.Threading; using MS.Internal; using MS.Internal.Interop; @@ -16,19 +17,20 @@ static ManagedWndProcTracker() { // Listen for ProcessExit so we can detach ourselves when the CLR shuts down // and avoid unmanaged code from calling back in to managed code during shutdown. - ManagedWndProcTrackerShutDownListener listener = new ManagedWndProcTrackerShutDownListener(); + // Note: This subscribes to AppDomain events in base class, hence the ref is kept around + _ = new ManagedWndProcTrackerShutDownListener(); } internal static void TrackHwndSubclass(HwndSubclass subclass, IntPtr hwnd) { - lock (_hwndList) + lock (s_hwndList) { // We use HwndSubclass as the key and the hwnd ptr as the value. // This supports the case where two (or more) HwndSubclasses // get attached to the same Hwnd. At AppDomain shutdown, we may // end up sending the Detach message to the Hwnd more than once, // but that won't cause any harm. - _hwndList[subclass] = hwnd; + s_hwndList[subclass] = hwnd; } #if LOGGING @@ -38,14 +40,15 @@ internal static void TrackHwndSubclass(HwndSubclass subclass, IntPtr hwnd) internal static void UnhookHwndSubclass(HwndSubclass subclass) { - // if exiting the AppDomain, ignore this call. This avoids changing - // the list during the loop in OnAppDomainProcessExit - if (_exiting) + // If we're exiting the AppDomain, ignore this call. + // Since this can be called from multiple threads, + // we want to be sure to get the freshest value possible. + if (Volatile.Read(ref s_exiting)) return; - lock (_hwndList) + lock (s_hwndList) { - _hwndList.Remove(subclass); + s_hwndList.Remove(subclass); } } @@ -58,16 +61,16 @@ private static void OnAppDomainProcessExit() // the DefaultWindowProc. //DbgUserBreakPoint(); - _exiting = true; + Volatile.Write(ref s_exiting, true); - lock (_hwndList) + lock (s_hwndList) { - foreach (DictionaryEntry entry in _hwndList) + foreach (KeyValuePair entry in s_hwndList) { - IntPtr hwnd = (IntPtr)entry.Value; + IntPtr hwnd = entry.Value; - int windowStyle = UnsafeNativeMethods.GetWindowLong(new HandleRef(null,hwnd), NativeMethods.GWL_STYLE); - if((windowStyle & NativeMethods.WS_CHILD) != 0) + int windowStyle = UnsafeNativeMethods.GetWindowLong(new HandleRef(null, hwnd), NativeMethods.GWL_STYLE); + if ((windowStyle & NativeMethods.WS_CHILD) != 0) { // Tell all the HwndSubclass WndProcs for WS_CHILD windows // to detach themselves. This is particularly important when @@ -86,8 +89,8 @@ private static void OnAppDomainProcessExit() // of Hwnd subclasses. UnsafeNativeMethods.SendMessage(hwnd, HwndSubclass.DetachMessage, - IntPtr.Zero /* wildcard */, - (IntPtr) 2 /* force and forward */); + IntPtr.Zero /* wildcard */, + 2 /* force and forward */); } // the last WndProc on the chain might be managed as well @@ -95,7 +98,7 @@ private static void OnAppDomainProcessExit() // Just in case, restore the DefaultWindowProc. HookUpDefWindowProc(hwnd); } -} + } } private static void HookUpDefWindowProc(IntPtr hwnd) @@ -105,7 +108,7 @@ private static void HookUpDefWindowProc(IntPtr hwnd) LogFinishHWND(hwnd, "Core HookUpDWP"); #endif - IntPtr result = IntPtr.Zero ; + IntPtr result = IntPtr.Zero; // We've already cleaned up, return immediately. if (hwnd == IntPtr.Zero) @@ -119,14 +122,14 @@ private static void HookUpDefWindowProc(IntPtr hwnd) { try { - result = UnsafeNativeMethods.SetWindowLong(new HandleRef(null,hwnd), NativeMethods.GWL_WNDPROC, defWindowProc); + result = UnsafeNativeMethods.SetWindowLong(new HandleRef(null, hwnd), NativeMethods.GWL_WNDPROC, defWindowProc); if (result != IntPtr.Zero) { UnsafeNativeMethods.PostMessage(new HandleRef(null, hwnd), WindowMessage.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); } } - catch(System.ComponentModel.Win32Exception e) + catch (Win32Exception e) { // We failed to change the window proc. Now what? @@ -147,23 +150,23 @@ private static IntPtr GetDefWindowProcAddress(IntPtr hwnd) { // We need to swap back in the DefWindowProc, but which one we use depends on // what the Unicode-ness of the window. - if (SafeNativeMethods.IsWindowUnicode(new HandleRef(null,hwnd))) + if (SafeNativeMethods.IsWindowUnicode(new HandleRef(null, hwnd))) { - if (_cachedDefWindowProcW == IntPtr.Zero) + if (s_cachedDefWindowProcW == IntPtr.Zero) { - _cachedDefWindowProcW = GetUser32ProcAddress("DefWindowProcW"); + s_cachedDefWindowProcW = GetUser32ProcAddress("DefWindowProcW"); } - return _cachedDefWindowProcW; + return s_cachedDefWindowProcW; } else { - if (_cachedDefWindowProcA == IntPtr.Zero) + if (s_cachedDefWindowProcA == IntPtr.Zero) { - _cachedDefWindowProcA = GetUser32ProcAddress("DefWindowProcA") ; + s_cachedDefWindowProcA = GetUser32ProcAddress("DefWindowProcA"); } - return _cachedDefWindowProcA; + return s_cachedDefWindowProcA; } } @@ -171,24 +174,21 @@ private static IntPtr GetUser32ProcAddress(string export) { IntPtr hModule = UnsafeNativeMethods.GetModuleHandle(ExternDll.User32); - if (hModule != IntPtr.Zero) - { return UnsafeNativeMethods.GetProcAddress(new HandleRef(null, hModule), export); -} + return IntPtr.Zero; } private sealed class ManagedWndProcTrackerShutDownListener : ShutDownListener { - public ManagedWndProcTrackerShutDownListener() - : base(null, ShutDownEvents.AppDomain) + public ManagedWndProcTrackerShutDownListener() : base(null, ShutDownEvents.AppDomain) { } internal override void OnShutDown(object target, object sender, EventArgs e) { - ManagedWndProcTracker.OnAppDomainProcessExit(); + OnAppDomainProcessExit(); } } @@ -236,10 +236,10 @@ private static void Log(string msg) #endif - private static IntPtr _cachedDefWindowProcA = IntPtr.Zero; - private static IntPtr _cachedDefWindowProcW = IntPtr.Zero; + private static IntPtr s_cachedDefWindowProcA = IntPtr.Zero; + private static IntPtr s_cachedDefWindowProcW = IntPtr.Zero; - private static Hashtable _hwndList = new Hashtable(10); - private static bool _exiting = false; + private static readonly Dictionary s_hwndList = new(10); + private static bool s_exiting; } } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/MS/Impl/AssemblyNamespacePair.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/MS/Impl/AssemblyNamespacePair.cs index bbf45a28ca9..216aa8ff51e 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/MS/Impl/AssemblyNamespacePair.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/MS/Impl/AssemblyNamespacePair.cs @@ -1,32 +1,33 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System.Reflection; namespace System.Xaml.MS.Impl { + /// + /// Holds a to an associated with the current . + /// [DebuggerDisplay("{ClrNamespace} {Assembly.FullName}")] - internal class AssemblyNamespacePair + internal readonly struct AssemblyNamespacePair { - private WeakReference _assembly; - private string _clrNamespace; + private readonly WeakReference _assembly; + private readonly string _clrNamespace; public AssemblyNamespacePair(Assembly asm, string clrNamespace) { - _assembly = new WeakReference(asm); + _assembly = new WeakReference(asm); _clrNamespace = clrNamespace; } - public Assembly Assembly + public Assembly? Assembly { - get { return (Assembly)_assembly.Target; } + get => _assembly.TryGetTarget(out Assembly? assembly) ? assembly : null; } public string ClrNamespace { - get { return _clrNamespace; } + get => _clrNamespace; } } } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Schema/XamlNamespace.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Schema/XamlNamespace.cs index a04f98d71d7..18860e17dbc 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Schema/XamlNamespace.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Schema/XamlNamespace.cs @@ -17,7 +17,7 @@ internal class XamlNamespace { public readonly XamlSchemaContext SchemaContext; - private List _assemblyNamespaces; + private AssemblyNamespacePair[] _assemblyNamespaces; private ConcurrentDictionary _typeCache; private ICollection _allPublicTypes; @@ -169,7 +169,7 @@ internal int RevisionNumber { // The only external mutation we allow is adding new namespaces. So the count of // namespaces also serves as a revision number. - get => (_assemblyNamespaces is not null) ? _assemblyNamespaces.Count : 0; + get => _assemblyNamespaces?.Length ?? 0; } private Type TryGetType(string typeName) @@ -177,7 +177,7 @@ private Type TryGetType(string typeName) Type type = SearchAssembliesForShortName(typeName); if (type is null && IsClrNamespace) { - Debug.Assert(_assemblyNamespaces.Count == 1); + Debug.Assert(_assemblyNamespaces.Length == 1); type = XamlLanguage.LookupClrNamespaceType(_assemblyNamespaces[0], typeName); } @@ -233,17 +233,13 @@ private ICollection LookupAllTypes() return xamlTypeList.AsReadOnly(); } - private List GetClrNamespacePair(string clrNs, string assemblyName) + private AssemblyNamespacePair[] GetClrNamespacePair(string clrNs, string assemblyName) { Assembly asm = SchemaContext.OnAssemblyResolve(assemblyName); if (asm is null) - { return null; - } - List onePair = new List(); - onePair.Add(new AssemblyNamespacePair(asm, clrNs)); - return onePair; + return new AssemblyNamespacePair[1] { new AssemblyNamespacePair(asm, clrNs) }; } private Type SearchAssembliesForShortName(string shortName) @@ -272,21 +268,25 @@ private Type SearchAssembliesForShortName(string shortName) // This method should only be called inside SchemaContext._syncExaminingAssemblies lock internal void AddAssemblyNamespacePair(AssemblyNamespacePair pair) { - // To allow the list to be read by multiple threads, we create a new list, add the pair, - // then assign it back to the original variable. Assignments are assured to be atomic. + // To allow the array to be read concurrently by multiple threads, we create a new array, add the pair, + // then assign it back to the original variable. Assignments are guaranteed to be atomic for word size. - List assemblyNamespacesCopy; + AssemblyNamespacePair[] assemblyNamespacesCopy; if (_assemblyNamespaces is null) { - assemblyNamespacesCopy = new List(); + assemblyNamespacesCopy = new AssemblyNamespacePair[1]; Initialize(); } else { - assemblyNamespacesCopy = new List(_assemblyNamespaces); + // Copy items over to the new collection + assemblyNamespacesCopy = new AssemblyNamespacePair[_assemblyNamespaces.Length + 1]; + _assemblyNamespaces.CopyTo(assemblyNamespacesCopy, 0); } - assemblyNamespacesCopy.Add(pair); + // Add new pair as the last one + assemblyNamespacesCopy[^1] = pair; + _assemblyNamespaces = assemblyNamespacesCopy; } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlSchemaContext.cs b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlSchemaContext.cs index 1b57fba8b04..fe940885fb3 100644 --- a/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlSchemaContext.cs +++ b/src/wpf/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/XamlSchemaContext.cs @@ -1180,17 +1180,18 @@ private bool UpdateXmlNsInfo(XmlNsInfo nsInfo) return foundNew; } + // This method should be called inside _syncExaminingAssemblies lock private bool UpdateNamespaceByUriList(XmlNsInfo nsInfo) { - bool foundNew = false; IList xmlnsDefs = nsInfo.NsDefs; - int xmlnsDefsCount = xmlnsDefs.Count; - for (int i = 0; i < xmlnsDefsCount; i++) + bool foundNew = false; + + for (int i = 0; i < xmlnsDefs.Count; i++) { XmlNsInfo.XmlNsDefinition xmlnsDef = xmlnsDefs[i]; - AssemblyNamespacePair pair = new AssemblyNamespacePair(nsInfo.Assembly, xmlnsDef.ClrNamespace); XamlNamespace ns = GetXamlNamespace(xmlnsDef.XmlNamespace); - ns.AddAssemblyNamespacePair(pair); + + ns.AddAssemblyNamespacePair(new AssemblyNamespacePair(nsInfo.Assembly, xmlnsDef.ClrNamespace)); foundNew = true; } diff --git a/src/wpf/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Media/KnownColorsTests.cs b/src/wpf/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Media/KnownColorsTests.cs new file mode 100644 index 00000000000..77cc0daad68 --- /dev/null +++ b/src/wpf/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Media/KnownColorsTests.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Media; + +public sealed class KnownColorsTests +{ + [Theory] + // Supported values. + [InlineData(KnownColor.AliceBlue, "#FFF0F8FF")] + [InlineData(KnownColor.AliceBlue, " #FFF0F8FF")] + [InlineData(KnownColor.AliceBlue, " #FFF0F8FF ")] + [InlineData(KnownColor.AliceBlue, "#FFF0F8FF ")] + // Unsupported values. + [InlineData(KnownColor.UnknownColor, "")] + [InlineData(KnownColor.UnknownColor, " ")] + [InlineData(KnownColor.UnknownColor, "#020B37EF")] // Random ARGB that is not a known color. + [InlineData(KnownColor.UnknownColor, "# FFF0F8FF")] + public void ArgbStringToKnownColor_ReturnsExpected(object expected, string? argbString) + { + Assert.Equal((KnownColor)expected, KnownColors.ArgbStringToKnownColor(argbString)); + } + + [Fact] + public void ArgbStringToKnownColor_NullValue_ThrowsArgumentNullException() + { + Assert.Throws(() => KnownColors.ArgbStringToKnownColor(argbString: null)); + } +}