diff --git a/.github/workflows/jit-format.yml b/.github/workflows/jit-format.yml index 0f04abf58cce10..46c3477d00fb22 100644 --- a/.github/workflows/jit-format.yml +++ b/.github/workflows/jit-format.yml @@ -15,7 +15,7 @@ jobs: os: - name: linux image: ubuntu-latest - container: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64 + container: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 extension: '.sh' cross: '--cross' rootfs: '/crossrootfs/x64' diff --git a/docs/project/linux-build-methodology.md b/docs/project/linux-build-methodology.md index 3add1adec6fc8e..5d9ddba7c39159 100644 --- a/docs/project/linux-build-methodology.md +++ b/docs/project/linux-build-methodology.md @@ -100,9 +100,9 @@ style amd64 text-align: left; style arm64 text-align: left; style x86 text-align: left; style arm text-align: left; -style amd64-alpine text-align: left; -style arm64-alpine text-align: left; -style arm-alpine text-align: left; +style amd64-musl text-align: left; +style arm64-musl text-align: left; +style arm-musl text-align: left; style deps text-align: left; style builder text-align: left; @@ -119,9 +119,9 @@ amd64("cross-amd64") arm64("cross-arm64") x86("cross-x86") arm("cross-arm") -amd64-alpine("cross-amd64-alpine") -arm64-alpine("cross-arm64-alpine") -arm-alpine("cross-arm-alpine") +amd64-musl("cross-amd64-musl") +arm64-musl("cross-arm64-musl") +arm-musl("cross-arm-musl") llvm("crossdeps-llvm • source-built LLVM") @@ -134,8 +134,8 @@ builder("crossdeps-builder • source-built LLVM") base("Azure Linux base image") -amd64 & arm64 & x86 & arm & amd64-alpine & arm64-alpine & arm-alpine ----> llvm -amd64 & arm64 & x86 & arm & amd64-alpine & arm64-alpine & arm-alpine -.-> builder +amd64 & arm64 & x86 & arm & amd64-musl & arm64-musl & arm-musl ----> llvm +amd64 & arm64 & x86 & arm & amd64-musl & arm64-musl & arm-musl -.-> builder llvm --> deps llvm -.-> builder diff --git a/docs/workflow/using-docker.md b/docs/workflow/using-docker.md index 03760dc588210c..821668fe858fea 100644 --- a/docs/workflow/using-docker.md +++ b/docs/workflow/using-docker.md @@ -26,26 +26,26 @@ The main Docker images are the most commonly used ones, and the ones you will pr | Host OS | Target OS | Target Arch | Image | crossrootfs dir | | ----------------- | ------------ | --------------- | -------------------------------------------------------------------------------------- | -------------------- | -| Azure Linux (x64) | Alpine 3.13 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64-alpine` | `/crossrootfs/x64` | -| Azure Linux (x64) | Ubuntu 16.04 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64` | `/crossrootfs/x64` | -| Azure Linux (x64) | Alpine 3.13 | Arm32 (armhf) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm-alpine` | `/crossrootfs/arm` | -| Azure Linux (x64) | Ubuntu 22.04 | Arm32 (armhf) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm` | `/crossrootfs/arm` | -| Azure Linux (x64) | Alpine 3.13 | Arm64 (arm64v8) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm64-alpine` | `/crossrootfs/arm64` | -| Azure Linux (x64) | Ubuntu 16.04 | Arm64 (arm64v8) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm64` | `/crossrootfs/arm64` | -| Azure Linux (x64) | Ubuntu 16.04 | x86 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-x86` | `/crossrootfs/x86` | +| Azure Linux (x64) | Alpine 3.13 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64-musl` | `/crossrootfs/x64` | +| Azure Linux (x64) | Ubuntu 16.04 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64` | `/crossrootfs/x64` | +| Azure Linux (x64) | Alpine 3.13 | Arm32 (armhf) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm-musl` | `/crossrootfs/arm` | +| Azure Linux (x64) | Ubuntu 22.04 | Arm32 (armhf) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm` | `/crossrootfs/arm` | +| Azure Linux (x64) | Alpine 3.13 | Arm64 (arm64v8) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64-musl` | `/crossrootfs/arm64` | +| Azure Linux (x64) | Ubuntu 16.04 | Arm64 (arm64v8) | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64` | `/crossrootfs/arm64` | +| Azure Linux (x64) | Ubuntu 16.04 | x86 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-x86` | `/crossrootfs/x86` | **Extended Docker Images** | Host OS | Target OS | Target Arch | Image | crossrootfs dir | | ----------------- | -------------------------- | ------------- | --------------------------------------------------------------------------------------- | ---------------------- | -| Azure Linux (x64) | Android Bionic | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-android-amd64` | *N/A* | -| Azure Linux (x64) | Android Bionic (w/OpenSSL) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-android-openssl` | *N/A* | -| Azure Linux (x64) | Android Bionic (w/Docker) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-android-docker` | *N/A* | -| Azure Linux (x64) | FreeBSD 13 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-freebsd-13` | `/crossrootfs/x64` | -| Azure Linux (x64) | Ubuntu 18.04 | PPC64le | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-ppc64le` | `/crossrootfs/ppc64le` | -| Azure Linux (x64) | Ubuntu 24.04 | RISC-V | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-riscv64` | `/crossrootfs/riscv64` | -| Azure Linux (x64) | Ubuntu 18.04 | S390x | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-s390x` | `/crossrootfs/s390x` | -| Azure Linux (x64) | Ubuntu 16.04 (Wasm) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-webassembly-amd64` | `/crossrootfs/x64` | +| Azure Linux (x64) | Android Bionic | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-android-amd64` | *N/A* | +| Azure Linux (x64) | Android Bionic (w/OpenSSL) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-openssl` | *N/A* | +| Azure Linux (x64) | Android Bionic (w/Docker) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-docker` | *N/A* | +| Azure Linux (x64) | FreeBSD 14 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-freebsd-14` | `/crossrootfs/x64` | +| Azure Linux (x64) | Ubuntu 18.04 | PPC64le | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-ppc64le` | `/crossrootfs/ppc64le` | +| Azure Linux (x64) | Ubuntu 24.04 | RISC-V | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-riscv64` | `/crossrootfs/riscv64` | +| Azure Linux (x64) | Ubuntu 18.04 | S390x | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-s390x` | `/crossrootfs/s390x` | +| Azure Linux (x64) | Ubuntu 16.04 (Wasm) | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-webassembly-amd64` | `/crossrootfs/x64` | | Debian (x64) | Debian 12 | x64 | `mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-gcc14-amd64` | *N/A* | | Ubuntu (x64) | Tizen 9.0 | Arm32 (armel) | `mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-cross-armel-tizen` | `/crossrootfs/armel` | | Ubuntu (x64) | Ubuntu 20.04 | Arm32 (v6) | `mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-cross-armv6-raspbian-10` | `/crossrootfs/armv6` | @@ -58,7 +58,7 @@ Once you've chosen the image that suits your needs, you can issue `docker run` w docker run --rm \ -v :/runtime \ -w /runtime \ - mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64 \ + mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 \ ./build.sh --subset clr --configuration Checked ``` @@ -67,7 +67,7 @@ Now, dissecting the command: - `--rm`: Erase the created container after it finishes running. - `-v :/runtime`: Mount the runtime repo clone located in `` to the container path `/runtime`. - `-w /runtime`: Start the container in the `/runtime` directory. -- `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64`: The fully qualified name of the Docker image to download. In this case, we want to use an *Azure Linux* image to target the *x64* architecture. +- `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64`: The fully qualified name of the Docker image to download. In this case, we want to use an *Azure Linux* image to target the *x64* architecture. - `./build.sh --subset clr --configuration Checked`: The build command to run in the repo. In this case, we want to build the *Clr* subset in the *Checked* configuration. You might also want to interact with the container directly for a myriad of reasons, like running multiple builds in different paths for example. In this case, instead of passing the build script command to the `docker` command-line, pass the flag `-it`. When you do this, you will get access to a small shell within the container, which allows you to explore it, run builds manually, and so on, like you would on a regular terminal in your machine. Note that the containers' shell's built-in tools are very limited in comparison to the ones you probably have on your machine, so don't expect to be able to do full work there. diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 345d36e9846f9d..c3b4352c5655fa 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/icu - 713960266e4dbd60fdb5d0199f8d89e681718f93 + b1f1c3a7e16ce80c472eb64ae494d0664e411f35 https://github.com/dotnet/wcf @@ -71,9 +71,9 @@ - + https://github.com/dotnet/source-build-reference-packages - 94798e07efab2663f2d1a71862780bc365d2e3ab + 44f3339be9fac0f8777bea0b9f74f20434110d6b @@ -84,87 +84,87 @@ - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 https://github.com/dotnet/runtime-assets @@ -348,9 +348,9 @@ https://github.com/dotnet/xharness 3119edb6d70fb252e6128b0c7e45d3fc2f49f249 - + https://github.com/dotnet/arcade - 4f2968fce08735a7e22fca6bd4c99d003221d716 + 8589bd2a216025d753b5f107081bfa28a2d51bb8 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/Versions.props b/eng/Versions.props index 509eee9258037c..53baa89d12b15b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -85,22 +85,22 @@ 10.0.100-alpha.1.24616.1 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 2.9.2-beta.24617.2 - 10.0.0-beta.24617.2 - 2.9.2-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 - 10.0.0-beta.24617.2 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 2.9.2-beta.24622.1 + 10.0.0-beta.24622.1 + 2.9.2-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 + 10.0.0-beta.24622.1 1.4.0 @@ -221,7 +221,7 @@ 0.11.5-alpha.24616.1 - 10.0.0-alpha.1.24616.2 + 10.0.0-alpha.1.24619.1 2.4.3 9.0.0-alpha.1.24167.3 diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index 9abe726e54bf09..2a6a529482b522 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -1,19 +1,49 @@ parameters: - # Enable cleanup tasks for MicroBuild + # Enable install tasks for MicroBuild enableMicrobuild: false - # Enable cleanup tasks for MicroBuild on Mac and Linux + # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false + # Location of the MicroBuild output folder + microBuildOutputFolder: '$(Agent.TempDirectory)' continueOnError: false steps: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - # Remove Python downgrade with https://github.com/dotnet/arcade/issues/15151 - - ${{ if and(eq(parameters.enableMicrobuildForMacAndLinux, 'true'), ne(variables['Agent.Os'], 'Windows_NT')) }}: + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: + # Install Python 3.12.x on when Python > 3.12.x is installed - https://github.com/dotnet/source-build/issues/4802 + - script: | + version=$(python3 --version | awk '{print $2}') + major=$(echo $version | cut -d. -f1) + minor=$(echo $version | cut -d. -f2) + + installPython=false + if [ "$major" -gt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -gt 12 ]; }; then + installPython=true + fi + + echo "Python version: $version." + echo "Install Python 3.12.x: $installPython." + echo "##vso[task.setvariable variable=installPython;isOutput=true]$installPython" + name: InstallPython + displayName: 'Determine Python installation' + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) + - task: UsePythonVersion@0 - displayName: 'Use Python 3.11.x' inputs: - versionSpec: '3.11.x' + versionSpec: '3.12.x' + displayName: 'Use Python 3.12.x' + condition: and(succeeded(), eq(variables['InstallPython.installPython'], 'true'), ne(variables['Agent.Os'], 'Windows_NT')) + + # Needed to download the MicroBuild plugin nupkgs on Mac and Linux when nuget.exe is unavailable + - task: UseDotNet@2 + displayName: Install .NET 8.0 SDK for MicroBuild Plugin + inputs: + packageType: sdk + version: 8.0.x + installationPath: ${{ parameters.microBuildOutputFolder }}/dotnet + workingDirectory: ${{ parameters.microBuildOutputFolder }} + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin @@ -25,7 +55,7 @@ steps: azureSubscription: 'MicroBuild Signing Task (DevDiv)' env: TeamName: $(_TeamName) - MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} condition: and( diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh index 7e9ba2b75ed35b..fbd8d80848a6ce 100755 --- a/eng/common/cross/build-android-rootfs.sh +++ b/eng/common/cross/build-android-rootfs.sh @@ -6,10 +6,11 @@ usage() { echo "Creates a toolchain and sysroot used for cross-compiling for Android." echo - echo "Usage: $0 [BuildArch] [ApiLevel]" + echo "Usage: $0 [BuildArch] [ApiLevel] [--ndk NDKVersion]" echo echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo "NDKVersion is the version of Android NDK. The default is r21. See https://developer.android.com/ndk/downloads/revision_history" echo echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" echo "by setting the TOOLCHAIN_DIR environment variable" @@ -25,10 +26,15 @@ __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android -for i in "$@" - do - lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" - case $lowerI in +while :; do + if [[ "$#" -le 0 ]]; then + break + fi + + i=$1 + + lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" + case $lowerI in -?|-h|--help) usage exit 1 @@ -43,6 +49,10 @@ for i in "$@" __AndroidArch=arm __AndroidToolchain=arm-linux-androideabi ;; + --ndk) + shift + __NDK_Version=$1 + ;; *[0-9]) __ApiLevel=$i ;; @@ -50,8 +60,17 @@ for i in "$@" __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" ;; esac + shift done +if [[ "$__NDK_Version" == "r21" ]] || [[ "$__NDK_Version" == "r22" ]]; then + __NDK_File_Arch_Spec=-x86_64 + __SysRoot=sysroot +else + __NDK_File_Arch_Spec= + __SysRoot=toolchains/llvm/prebuilt/linux-x86_64/sysroot +fi + # Obtain the location of the bash script to figure out where the root of the repo is. __ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -78,6 +97,7 @@ fi echo "Target API level: $__ApiLevel" echo "Target architecture: $__BuildArch" +echo "NDK version: $__NDK_Version" echo "NDK location: $__NDK_Dir" echo "Target Toolchain location: $__ToolchainDir" @@ -85,8 +105,8 @@ echo "Target Toolchain location: $__ToolchainDir" if [ ! -d $__NDK_Dir ]; then echo Downloading the NDK into $__NDK_Dir mkdir -p $__NDK_Dir - wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip - unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux$__NDK_File_Arch_Spec.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux.zip -d $__CrossDir fi if [ ! -d $__lldb_Dir ]; then @@ -116,16 +136,11 @@ for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/ fi done -cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/$__SysRoot/usr/" # Generate platform file for build.sh script to assign to __DistroRid echo "Generating platform file..." -echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform - -echo "Now to build coreclr, libraries and installers; run:" -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory coreclr -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory libraries -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory installer +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/$__SysRoot/android_platform + +echo "Now to build coreclr, libraries and host; run:" +echo ROOTFS_DIR=$(realpath $__ToolchainDir/$__SysRoot) ./build.sh clr+libs+host --cross --arch $__BuildArch diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 3eef7409f729d9..71bde0e4573b26 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -27,7 +27,7 @@ case "$os" in libssl-dev libkrb5-dev zlib1g-dev pigz cpio localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ]; then + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ]; then dnf install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel zlib-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev zlib-dev openssl-dev pigz cpio diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index bd80ccccb51634..f46b3c692f8ba7 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -262,7 +262,9 @@ function GetDotNetInstallScript([string] $dotnetRoot) { if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + # $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + # Pre-flighting new install script + $uri = "https://raw.githubusercontent.com/dotnet/install-scripts/fe7622c52c1ed67871a8d2ad9e794be9be7eea01/src/dotnet-install.ps1" Retry({ Write-Host "GET $uri" diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 79b4a28e1707c2..b4d7b0b4ee91ff 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -295,7 +295,9 @@ function with_retries { function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" - local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + # local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + # Pre-flight install script + local install_script_url="https://raw.githubusercontent.com/dotnet/install-scripts/fe7622c52c1ed67871a8d2ad9e794be9be7eea01/src/dotnet-install.sh" if [[ ! -a "$install_script" ]]; then mkdir -p "$root" diff --git a/eng/pipelines/common/templates/pipeline-with-resources.yml b/eng/pipelines/common/templates/pipeline-with-resources.yml index 0a7130cc8977ee..bcc4963c34a73f 100644 --- a/eng/pipelines/common/templates/pipeline-with-resources.yml +++ b/eng/pipelines/common/templates/pipeline-with-resources.yml @@ -17,7 +17,7 @@ extends: containers: linux_arm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm env: ROOTFS_DIR: /crossrootfs/arm @@ -27,44 +27,44 @@ extends: ROOTFS_DIR: /crossrootfs/armv6 linux_arm64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64 env: ROOTFS_DIR: /crossrootfs/arm64 linux_musl_x64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64-musl env: ROOTFS_DIR: /crossrootfs/x64 linux_musl_arm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm-musl env: ROOTFS_DIR: /crossrootfs/arm linux_musl_arm64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm64-alpine + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64-musl env: ROOTFS_DIR: /crossrootfs/arm64 # This container contains all required toolsets to build for Android and for Linux with bionic libc. android: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-android-amd64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-android-amd64 # This container contains all required toolsets to build for Android and for Linux with bionic libc and a special layout of OpenSSL. linux_bionic: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-android-openssl + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-android-openssl-amd64 # This container contains all required toolsets to build for Android as well as tooling to build docker images. android_docker: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-android-docker + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-docker-amd64 linux_x64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 env: ROOTFS_DIR: /crossrootfs/x64 linux_x86: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-x86 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-x86 env: ROOTFS_DIR: /crossrootfs/x86 @@ -75,7 +75,7 @@ extends: image: mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.19-WithNode linux_x64_sanitizer: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-amd64-sanitizer + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64-sanitizer env: ROOTFS_DIR: /crossrootfs/x64 @@ -88,17 +88,17 @@ extends: image: mcr.microsoft.com/dotnet-buildtools/prereqs:almalinux-8-source-build linux_s390x: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-s390x + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-s390x env: ROOTFS_DIR: /crossrootfs/s390x linux_ppc64le: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-ppc64le + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-ppc64le env: ROOTFS_DIR: /crossrootfs/ppc64le linux_riscv64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-riscv64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-riscv64 env: ROOTFS_DIR: /crossrootfs/riscv64 @@ -109,17 +109,17 @@ extends: image: mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8 browser_wasm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-webassembly-amd64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-webassembly-amd64 env: ROOTFS_DIR: /crossrootfs/x64 wasi_wasm: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-webassembly-amd64 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-webassembly-amd64 env: ROOTFS_DIR: /crossrootfs/x64 freebsd_x64: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-freebsd-13 + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-freebsd-14-amd64 env: ROOTFS_DIR: /crossrootfs/x64 diff --git a/global.json b/global.json index 7f9bf316741538..ba7ea659f7a237 100644 --- a/global.json +++ b/global.json @@ -8,9 +8,9 @@ "dotnet": "10.0.100-alpha.1.24610.7" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.24617.2", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.24617.2", - "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.24617.2", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.24622.1", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.24622.1", + "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.24622.1", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.Build.Traversal": "3.4.0", "Microsoft.NET.Sdk.IL": "10.0.0-alpha.1.24616.1" diff --git a/src/coreclr/gcinfo/gcinfoencoder.cpp b/src/coreclr/gcinfo/gcinfoencoder.cpp index fb2f06a80c44a5..d1988ba34a5394 100644 --- a/src/coreclr/gcinfo/gcinfoencoder.cpp +++ b/src/coreclr/gcinfo/gcinfoencoder.cpp @@ -490,13 +490,6 @@ GcInfoEncoder::GcInfoEncoder( m_IsSlotTableFrozen = FALSE; #endif //_DEBUG -#ifndef TARGET_X86 - // If the compiler doesn't set the GCInfo, report RT_Unset. - // This is used for compatibility with JITs that aren't updated to use the new API. - m_ReturnKind = RT_Unset; -#else - m_ReturnKind = RT_Illegal; -#endif // TARGET_X86 m_CodeLength = 0; #ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA m_SizeOfStackOutgoingAndScratchArea = -1; @@ -776,13 +769,6 @@ void GcInfoEncoder::SetReversePInvokeFrameSlot(INT32 spOffset) m_ReversePInvokeFrameSlot = spOffset; } -void GcInfoEncoder::SetReturnKind(ReturnKind returnKind) -{ - _ASSERTE(IsValidReturnKind(returnKind)); - - m_ReturnKind = returnKind; -} - struct GcSlotDescAndId { GcSlotDesc m_SlotDesc; @@ -1045,16 +1031,15 @@ void GcInfoEncoder::Build() BOOL slimHeader = (!m_IsVarArg && !hasGSCookie && (m_PSPSymStackSlot == NO_PSP_SYM) && !hasContextParamType && (m_InterruptibleRanges.Count() == 0) && !hasReversePInvokeFrame && ((m_StackBaseRegister == NO_STACK_BASE_REGISTER) || (NORMALIZE_STACK_BASE_REGISTER(m_StackBaseRegister) == 0))) && - (m_SizeOfEditAndContinuePreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) && #ifdef TARGET_AMD64 !m_WantsReportOnlyLeaf && #elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) !m_HasTailCalls && #endif // TARGET_AMD64 - !IsStructReturnKind(m_ReturnKind); + (m_SizeOfEditAndContinuePreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA); // All new code is generated for the latest GCINFO_VERSION. - // So, always encode RetunrKind and encode ReversePInvokeFrameSlot where applicable. + // So, always encode ReversePInvokeFrameSlot where applicable. if (slimHeader) { // Slim encoding means nothing special, partially interruptible, maybe a default frame register @@ -1065,8 +1050,6 @@ void GcInfoEncoder::Build() assert(m_StackBaseRegister == 8 || 2 == m_StackBaseRegister); #endif GCINFO_WRITE(m_Info1, (m_StackBaseRegister == NO_STACK_BASE_REGISTER) ? 0 : 1, 1, FlagsSize); - - GCINFO_WRITE(m_Info1, m_ReturnKind, SIZE_OF_RETURN_KIND_IN_SLIM_HEADER, RetKindSize); } else { @@ -1089,8 +1072,6 @@ void GcInfoEncoder::Build() #endif // TARGET_AMD64 GCINFO_WRITE(m_Info1, ((m_SizeOfEditAndContinuePreservedArea != NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) ? 1 : 0), 1, FlagsSize); GCINFO_WRITE(m_Info1, (hasReversePInvokeFrame ? 1 : 0), 1, FlagsSize); - - GCINFO_WRITE(m_Info1, m_ReturnKind, SIZE_OF_RETURN_KIND_IN_FAT_HEADER, RetKindSize); } _ASSERTE( m_CodeLength > 0 ); @@ -1217,42 +1198,19 @@ void GcInfoEncoder::Build() /////////////////////////////////////////////////////////////////////// // Normalize call sites - // Eliminate call sites that fall inside interruptible ranges /////////////////////////////////////////////////////////////////////// + _ASSERTE(m_NumCallSites == 0 || numInterruptibleRanges == 0); + UINT32 numCallSites = 0; for(UINT32 callSiteIndex = 0; callSiteIndex < m_NumCallSites; callSiteIndex++) { UINT32 callSite = m_pCallSites[callSiteIndex]; - // There's a contract with the EE that says for non-leaf stack frames, where the - // method is stopped at a call site, the EE will not query with the return PC, but - // rather the return PC *minus 1*. - // The reason is that variable/register liveness may change at the instruction immediately after the - // call, so we want such frames to appear as if they are "within" the call. - // Since we use "callSite" as the "key" when we search for the matching descriptor, also subtract 1 here - // (after, of course, adding the size of the call instruction to get the return PC). - callSite += m_pCallSiteSizes[callSiteIndex] - 1; + callSite += m_pCallSiteSizes[callSiteIndex]; _ASSERTE(DENORMALIZE_CODE_OFFSET(NORMALIZE_CODE_OFFSET(callSite)) == callSite); UINT32 normOffset = NORMALIZE_CODE_OFFSET(callSite); - - BOOL keepIt = TRUE; - - for(UINT32 intRangeIndex = 0; intRangeIndex < numInterruptibleRanges; intRangeIndex++) - { - InterruptibleRange *pRange = &pRanges[intRangeIndex]; - if(pRange->NormStopOffset > normOffset) - { - if(pRange->NormStartOffset <= normOffset) - { - keepIt = FALSE; - } - break; - } - } - - if(keepIt) - m_pCallSites[numCallSites++] = normOffset; + m_pCallSites[numCallSites++] = normOffset; } GCINFO_WRITE_VARL_U(m_Info1, NORMALIZE_NUM_SAFE_POINTS(numCallSites), NUM_SAFE_POINTS_ENCBASE, NumCallSitesSize); @@ -1419,7 +1377,7 @@ void GcInfoEncoder::Build() for(pCurrent = pTransitions; pCurrent < pEndTransitions; ) { - if(pCurrent->CodeOffset > callSite) + if(pCurrent->CodeOffset >= callSite) { couldBeLive |= liveState; @@ -1774,7 +1732,7 @@ void GcInfoEncoder::Build() { for(pCurrent = pTransitions; pCurrent < pEndTransitions; ) { - if(pCurrent->CodeOffset > callSite) + if(pCurrent->CodeOffset >= callSite) { // Time to record the call site @@ -1873,7 +1831,7 @@ void GcInfoEncoder::Build() for(pCurrent = pTransitions; pCurrent < pEndTransitions; ) { - if(pCurrent->CodeOffset > callSite) + if(pCurrent->CodeOffset >= callSite) { // Time to encode the call site @@ -1920,7 +1878,7 @@ void GcInfoEncoder::Build() for(pCurrent = pTransitions; pCurrent < pEndTransitions; ) { - if(pCurrent->CodeOffset > callSite) + if(pCurrent->CodeOffset >= callSite) { // Time to encode the call site GCINFO_WRITE_VECTOR(m_Info1, liveState, CallSiteStateSize); diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index a400ecfbf5c9e8..80ff267d583e0e 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -36,7 +36,7 @@ const unsigned this_OFFSET_FLAG = 0x2; // the offset is "this" // The current GCInfo Version //----------------------------------------------------------------------------- -#define GCINFO_VERSION 3 +#define GCINFO_VERSION 4 //----------------------------------------------------------------------------- // GCInfoToken: A wrapper that contains the GcInfo data and version number. diff --git a/src/coreclr/inc/gcinfoencoder.h b/src/coreclr/inc/gcinfoencoder.h index 0cf7f67800b9c3..8c5daf92c23b3f 100644 --- a/src/coreclr/inc/gcinfoencoder.h +++ b/src/coreclr/inc/gcinfoencoder.h @@ -409,13 +409,6 @@ class GcInfoEncoder GcSlotState slotState ); - - //------------------------------------------------------------------------ - // ReturnKind - //------------------------------------------------------------------------ - - void SetReturnKind(ReturnKind returnKind); - //------------------------------------------------------------------------ // Miscellaneous method information //------------------------------------------------------------------------ @@ -509,7 +502,6 @@ class GcInfoEncoder INT32 m_PSPSymStackSlot; INT32 m_GenericsInstContextStackSlot; GENERIC_CONTEXTPARAM_TYPE m_contextParamType; - ReturnKind m_ReturnKind; UINT32 m_CodeLength; UINT32 m_StackBaseRegister; UINT32 m_SizeOfEditAndContinuePreservedArea; diff --git a/src/coreclr/inc/gcinfotypes.h b/src/coreclr/inc/gcinfotypes.h index b770bb1bbcff9b..ef33fc275fe282 100644 --- a/src/coreclr/inc/gcinfotypes.h +++ b/src/coreclr/inc/gcinfotypes.h @@ -637,8 +637,6 @@ void FASTCALL decodeCallPattern(int pattern, #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 #define GS_COOKIE_STACK_SLOT_ENCBASE 6 #define CODE_LENGTH_ENCBASE 8 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 4 #define STACK_BASE_REGISTER_ENCBASE 3 #define SIZE_OF_STACK_AREA_ENCBASE 3 #define SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE 4 @@ -679,8 +677,8 @@ void FASTCALL decodeCallPattern(int pattern, #define NORMALIZE_SIZE_OF_STACK_AREA(x) ((x)>>2) #define DENORMALIZE_SIZE_OF_STACK_AREA(x) ((x)<<2) #define CODE_OFFSETS_NEED_NORMALIZATION 1 -#define NORMALIZE_CODE_OFFSET(x) (x) // Instructions are 2/4 bytes long in Thumb/ARM states, -#define DENORMALIZE_CODE_OFFSET(x) (x) // but the safe-point offsets are encoded with a -1 adjustment. +#define NORMALIZE_CODE_OFFSET(x) ((x)>>1) // Instructions are 2/4 bytes long in Thumb/ARM states, +#define DENORMALIZE_CODE_OFFSET(x) ((x)<<1) #define NORMALIZE_REGISTER(x) (x) #define DENORMALIZE_REGISTER(x) (x) #define NORMALIZE_NUM_SAFE_POINTS(x) (x) @@ -695,8 +693,6 @@ void FASTCALL decodeCallPattern(int pattern, #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 5 #define GS_COOKIE_STACK_SLOT_ENCBASE 5 #define CODE_LENGTH_ENCBASE 7 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 2 #define STACK_BASE_REGISTER_ENCBASE 1 #define SIZE_OF_STACK_AREA_ENCBASE 3 #define SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE 3 @@ -735,9 +731,9 @@ void FASTCALL decodeCallPattern(int pattern, #define DENORMALIZE_STACK_BASE_REGISTER(x) ((x)^29) #define NORMALIZE_SIZE_OF_STACK_AREA(x) ((x)>>3) #define DENORMALIZE_SIZE_OF_STACK_AREA(x) ((x)<<3) -#define CODE_OFFSETS_NEED_NORMALIZATION 0 -#define NORMALIZE_CODE_OFFSET(x) (x) // Instructions are 4 bytes long, but the safe-point -#define DENORMALIZE_CODE_OFFSET(x) (x) // offsets are encoded with a -1 adjustment. +#define CODE_OFFSETS_NEED_NORMALIZATION 1 +#define NORMALIZE_CODE_OFFSET(x) ((x)>>2) // Instructions are 4 bytes long +#define DENORMALIZE_CODE_OFFSET(x) ((x)<<2) #define NORMALIZE_REGISTER(x) (x) #define DENORMALIZE_REGISTER(x) (x) #define NORMALIZE_NUM_SAFE_POINTS(x) (x) @@ -750,8 +746,6 @@ void FASTCALL decodeCallPattern(int pattern, #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 #define GS_COOKIE_STACK_SLOT_ENCBASE 6 #define CODE_LENGTH_ENCBASE 8 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 4 #define STACK_BASE_REGISTER_ENCBASE 2 // FP encoded as 0, SP as 2. #define SIZE_OF_STACK_AREA_ENCBASE 3 #define SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE 4 @@ -790,9 +784,9 @@ void FASTCALL decodeCallPattern(int pattern, #define DENORMALIZE_STACK_BASE_REGISTER(x) ((x) == 0 ? 22 : 3) #define NORMALIZE_SIZE_OF_STACK_AREA(x) ((x)>>3) #define DENORMALIZE_SIZE_OF_STACK_AREA(x) ((x)<<3) -#define CODE_OFFSETS_NEED_NORMALIZATION 0 -#define NORMALIZE_CODE_OFFSET(x) (x) // Instructions are 4 bytes long, but the safe-point -#define DENORMALIZE_CODE_OFFSET(x) (x) // offsets are encoded with a -1 adjustment. +#define CODE_OFFSETS_NEED_NORMALIZATION 1 +#define NORMALIZE_CODE_OFFSET(x) ((x)>>2) // Instructions are 4 bytes long +#define DENORMALIZE_CODE_OFFSET(x) ((x)<<2) #define NORMALIZE_REGISTER(x) (x) #define DENORMALIZE_REGISTER(x) (x) #define NORMALIZE_NUM_SAFE_POINTS(x) (x) @@ -805,8 +799,6 @@ void FASTCALL decodeCallPattern(int pattern, #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 #define GS_COOKIE_STACK_SLOT_ENCBASE 6 #define CODE_LENGTH_ENCBASE 8 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 4 // FP/SP encoded as 0 or 1. #define STACK_BASE_REGISTER_ENCBASE 2 #define SIZE_OF_STACK_AREA_ENCBASE 3 @@ -845,9 +837,9 @@ void FASTCALL decodeCallPattern(int pattern, #define DENORMALIZE_STACK_BASE_REGISTER(x) ((x) == 0 ? 8 : 2) #define NORMALIZE_SIZE_OF_STACK_AREA(x) ((x)>>3) #define DENORMALIZE_SIZE_OF_STACK_AREA(x) ((x)<<3) -#define CODE_OFFSETS_NEED_NORMALIZATION 0 -#define NORMALIZE_CODE_OFFSET(x) (x) // Instructions are 4 bytes long, but the safe-point -#define DENORMALIZE_CODE_OFFSET(x) (x) // offsets are encoded with a -1 adjustment. +#define CODE_OFFSETS_NEED_NORMALIZATION 1 +#define NORMALIZE_CODE_OFFSET(x) ((x)>>2) // Instructions are 4 bytes long +#define DENORMALIZE_CODE_OFFSET(x) ((x)<<2) #define NORMALIZE_REGISTER(x) (x) #define DENORMALIZE_REGISTER(x) (x) #define NORMALIZE_NUM_SAFE_POINTS(x) (x) @@ -860,8 +852,6 @@ void FASTCALL decodeCallPattern(int pattern, #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 #define GS_COOKIE_STACK_SLOT_ENCBASE 6 #define CODE_LENGTH_ENCBASE 8 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 4 #define STACK_BASE_REGISTER_ENCBASE 2 // FP encoded as 0, SP as 1 #define SIZE_OF_STACK_AREA_ENCBASE 3 @@ -924,8 +914,6 @@ PORTABILITY_WARNING("Please specialize these definitions for your platform!") #define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 #define GS_COOKIE_STACK_SLOT_ENCBASE 6 #define CODE_LENGTH_ENCBASE 6 -#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 -#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 2 #define STACK_BASE_REGISTER_ENCBASE 3 #define SIZE_OF_STACK_AREA_ENCBASE 6 #define SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE 3 diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index f5737ab0de97c3..1ca6b12b594132 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -19,10 +19,10 @@ // src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. -#define READYTORUN_MAJOR_VERSION 10 -#define READYTORUN_MINOR_VERSION 0x0001 +#define READYTORUN_MAJOR_VERSION 11 +#define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 10 +#define MINIMUM_READYTORUN_MAJOR_VERSION 11 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section @@ -38,6 +38,7 @@ // uses GCInfo v3, which makes safe points in partially interruptible code interruptible. // R2R Version 10.0 adds support for the statics being allocated on a per type basis instead of on a per module basis, disable support for LogMethodEnter helper // R2R Version 10.1 adds Unbox_TypeTest helper +// R2R Version 11 uses GCInfo v4, which encodes safe points without -1 offset and does not track return kinds in GCInfo struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/jit/abi.cpp b/src/coreclr/jit/abi.cpp index 04043c359246e5..f31b14531aec8c 100644 --- a/src/coreclr/jit/abi.cpp +++ b/src/coreclr/jit/abi.cpp @@ -465,15 +465,24 @@ void ABIPassingInformation::Dump() const } const ABIPassingSegment& seg = Segment(i); + seg.Dump(); + printf("\n"); + } +} - if (seg.IsPassedInRegister()) - { - printf("[%02u..%02u) reg %s\n", seg.Offset, seg.Offset + seg.Size, getRegName(seg.GetRegister())); - } - else - { - printf("[%02u..%02u) stack @ +%02u\n", seg.Offset, seg.Offset + seg.Size, seg.GetStackOffset()); - } +//----------------------------------------------------------------------------- +// Dump: +// Dump the ABIPassingSegment to stdout. +// +void ABIPassingSegment::Dump() const +{ + if (IsPassedInRegister()) + { + printf("[%02u..%02u) reg %s", Offset, Offset + Size, getRegName(GetRegister())); + } + else + { + printf("[%02u..%02u) stack @ +%02u", Offset, Offset + Size, GetStackOffset()); } } #endif diff --git a/src/coreclr/jit/abi.h b/src/coreclr/jit/abi.h index 66566f44db5fa6..e1851ca8a6e92f 100644 --- a/src/coreclr/jit/abi.h +++ b/src/coreclr/jit/abi.h @@ -38,6 +38,10 @@ class ABIPassingSegment static ABIPassingSegment InRegister(regNumber reg, unsigned offset, unsigned size); static ABIPassingSegment OnStack(unsigned stackOffset, unsigned offset, unsigned size); + +#ifdef DEBUG + void Dump() const; +#endif }; class ABIPassingSegmentIterator diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 4df8674ccaaf17..d84ba8760dca00 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -260,9 +260,10 @@ class CodeGen final : public CodeGenInterface regMaskTP genGetParameterHomingTempRegisterCandidates(); var_types genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& seg); - void genSpillOrAddRegisterParam(unsigned lclNum, class RegGraph* graph); - void genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, class RegGraph* graph); - void genEnregisterIncomingStackArgs(); + void genSpillOrAddRegisterParam( + unsigned lclNum, unsigned offset, unsigned paramLclNum, const ABIPassingSegment& seg, class RegGraph* graph); + void genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, class RegGraph* graph); + void genEnregisterIncomingStackArgs(); #if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) void genEnregisterOSRArgsAndLocals(regNumber initReg, bool* pInitRegZeroed); #else diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index cb73aca0085642..ef5a45a8c7841c 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -3249,83 +3249,70 @@ var_types CodeGen::genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& se // to stack immediately, or by adding it to the register graph. // // Parameters: -// lclNum - Parameter local (or field of it) -// graph - The register graph to add to -// -void CodeGen::genSpillOrAddRegisterParam(unsigned lclNum, RegGraph* graph) +// lclNum - Target local +// offset - Offset into the target local +// paramLclNum - Local that is the actual parameter that has the incoming register +// segment - Register segment to either spill or put in the register graph +// graph - The register graph to add to +// +void CodeGen::genSpillOrAddRegisterParam( + unsigned lclNum, unsigned offset, unsigned paramLclNum, const ABIPassingSegment& segment, RegGraph* graph) { - regMaskTP paramRegs = intRegState.rsCalleeRegArgMaskLiveIn | floatRegState.rsCalleeRegArgMaskLiveIn; - LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum); + regMaskTP paramRegs = intRegState.rsCalleeRegArgMaskLiveIn | floatRegState.rsCalleeRegArgMaskLiveIn; - unsigned baseOffset = varDsc->lvIsStructField ? varDsc->lvFldOffset : 0; - unsigned size = varDsc->lvExactSize(); + if (!segment.IsPassedInRegister() || ((paramRegs & genRegMask(segment.GetRegister())) == 0)) + { + return; + } - unsigned paramLclNum = varDsc->lvIsStructField ? varDsc->lvParentLcl : lclNum; - LclVarDsc* paramVarDsc = compiler->lvaGetDesc(paramLclNum); - const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(paramLclNum); - for (const ABIPassingSegment& seg : abiInfo.Segments()) + LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum); + if (varDsc->lvOnFrame && (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr)) { - if (!seg.IsPassedInRegister() || ((paramRegs & genRegMask(seg.GetRegister())) == 0)) - { - continue; - } + LclVarDsc* paramVarDsc = compiler->lvaGetDesc(paramLclNum); - if (seg.Offset + seg.Size <= baseOffset) + var_types storeType = genParamStackType(paramVarDsc, segment); + if ((varDsc->TypeGet() != TYP_STRUCT) && (genTypeSize(genActualType(varDsc)) < genTypeSize(storeType))) { - continue; + // Can happen for struct fields due to padding. + storeType = genActualType(varDsc); } - if (baseOffset + size <= seg.Offset) - { - continue; - } - - if (varDsc->lvOnFrame && (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr)) - { - var_types storeType = genParamStackType(paramVarDsc, seg); - if ((varDsc->TypeGet() != TYP_STRUCT) && (genTypeSize(genActualType(varDsc)) < genTypeSize(storeType))) - { - // Can happen for struct fields due to padding. - storeType = genActualType(varDsc); - } - - GetEmitter()->emitIns_S_R(ins_Store(storeType), emitActualTypeSize(storeType), seg.GetRegister(), lclNum, - seg.Offset - baseOffset); - } + GetEmitter()->emitIns_S_R(ins_Store(storeType), emitActualTypeSize(storeType), segment.GetRegister(), lclNum, + offset); + } - if (!varDsc->lvIsInReg()) - { - continue; - } + if (!varDsc->lvIsInReg()) + { + return; + } - var_types edgeType = genActualType(varDsc->GetRegisterType()); - // Some parameters can be passed in multiple registers but enregistered - // in a single one (e.g. SIMD types on arm64). In this case the edges - // we add here represent insertions of each element. - if (seg.Size < genTypeSize(edgeType)) - { - edgeType = seg.GetRegisterType(); - } + var_types edgeType = genActualType(varDsc->GetRegisterType()); + // Some parameters can be passed in multiple registers but enregistered + // in a single one (e.g. SIMD types on arm64). In this case the edges + // we add here represent insertions of each element. + if (segment.Size < genTypeSize(edgeType)) + { + edgeType = segment.GetRegisterType(); + } - RegNode* sourceReg = graph->GetOrAdd(seg.GetRegister()); - RegNode* destReg = graph->GetOrAdd(varDsc->GetRegNum()); + RegNode* sourceReg = graph->GetOrAdd(segment.GetRegister()); + RegNode* destReg = graph->GetOrAdd(varDsc->GetRegNum()); - if ((sourceReg != destReg) || (baseOffset != seg.Offset)) - { + if ((sourceReg != destReg) || (offset != 0)) + { #ifdef TARGET_ARM - if (edgeType == TYP_DOUBLE) - { - assert(seg.Offset == baseOffset); - graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0); + if (edgeType == TYP_DOUBLE) + { + assert(offset == 0); + graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0); - sourceReg = graph->GetOrAdd(REG_NEXT(sourceReg->reg)); - destReg = graph->GetOrAdd(REG_NEXT(destReg->reg)); - graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0); - continue; - } -#endif - graph->AddEdge(sourceReg, destReg, edgeType, seg.Offset - baseOffset); + sourceReg = graph->GetOrAdd(REG_NEXT(sourceReg->reg)); + destReg = graph->GetOrAdd(REG_NEXT(destReg->reg)); + graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0); + return; } +#endif + graph->AddEdge(sourceReg, destReg, edgeType, offset); } } @@ -3396,7 +3383,8 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed) } } - if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE)) + if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE) && + compiler->lvaGetDesc(compiler->lvaStubArgumentVar)->lvOnFrame) { GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SECRET_STUB_PARAM, compiler->lvaStubArgumentVar, 0); @@ -3418,19 +3406,27 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed) for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++) { - LclVarDsc* lclDsc = compiler->lvaGetDesc(lclNum); + LclVarDsc* lclDsc = compiler->lvaGetDesc(lclNum); + const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(lclNum); - if (compiler->lvaGetPromotionType(lclNum) == Compiler::PROMOTION_TYPE_INDEPENDENT) + for (const ABIPassingSegment& segment : abiInfo.Segments()) { - for (unsigned fld = 0; fld < lclDsc->lvFieldCnt; fld++) + if (!segment.IsPassedInRegister()) { - unsigned fieldLclNum = lclDsc->lvFieldLclStart + fld; - genSpillOrAddRegisterParam(fieldLclNum, &graph); + continue; + } + + const ParameterRegisterLocalMapping* mapping = + compiler->FindParameterRegisterLocalMappingByRegister(segment.GetRegister()); + + if (mapping != nullptr) + { + genSpillOrAddRegisterParam(mapping->LclNum, mapping->Offset, lclNum, segment, &graph); + } + else + { + genSpillOrAddRegisterParam(lclNum, segment.Offset, lclNum, segment, &graph); } - } - else - { - genSpillOrAddRegisterParam(lclNum, &graph); } } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 0ed95870ea2e05..901c4039b5dff8 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4224,6 +4224,65 @@ bool Compiler::compRsvdRegCheck(FrameLayoutState curState) } #endif // TARGET_ARMARCH || TARGET_RISCV64 +//------------------------------------------------------------------------ +// FindParameterRegisterLocalMappingByRegister: +// Try to find a mapping that maps a particular parameter register to an +// incoming defined local. +// +// Returns: +// The mapping, or nullptr if no mapping was found for this register. +// +const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByRegister(regNumber reg) +{ + if (m_paramRegLocalMappings == nullptr) + { + return nullptr; + } + + for (int i = 0; i < m_paramRegLocalMappings->Height(); i++) + { + const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i); + if (mapping.RegisterSegment->GetRegister() == reg) + { + return &mapping; + } + } + + return nullptr; +} + +//------------------------------------------------------------------------ +// FindParameterRegisterLocalMappingByLocal: +// Try to find a mapping that maps a particular local from an incoming +// parameter register. +// +// Parameters: +// lclNum - The local to find a mapping for +// offset - The offset that the mapping maps to in the local +// +// Returns: +// The mapping, or nullptr if no mapping was found for this local. +// +const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByLocal(unsigned lclNum, + unsigned offset) +{ + if (m_paramRegLocalMappings == nullptr) + { + return nullptr; + } + + for (int i = 0; i < m_paramRegLocalMappings->Height(); i++) + { + const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i); + if ((mapping.LclNum == lclNum) && (mapping.Offset == offset)) + { + return &mapping; + } + } + + return nullptr; +} + //------------------------------------------------------------------------ // compGetTieringName: get a string describing tiered compilation settings // for this method diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ba1a1f34f89a41..72f2a5d4df8485 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -522,6 +522,7 @@ class LclVarDsc unsigned char lvIsParam : 1; // is this a parameter? unsigned char lvIsRegArg : 1; // is this an argument that was passed by register? + unsigned char lvIsParamRegTarget : 1; // is this the target of a param reg to local mapping? unsigned char lvFramePointerBased : 1; // 0 = off of REG_SPBASE (e.g., ESP), 1 = off of REG_FPBASE (e.g., EBP) unsigned char lvOnFrame : 1; // (part of) the variable lives on the frame @@ -2580,6 +2581,32 @@ struct CloneTryInfo bool ScaleOriginalBlockProfile = false; }; +//------------------------------------------------------------------------ +// ParameterRegisterLocalMapping: +// Contains mappings between a parameter register segment and a corresponding +// local. Used by the backend to know which locals are expected to contain +// which register parameters after the prolog. +// +struct ParameterRegisterLocalMapping +{ + const ABIPassingSegment* RegisterSegment; + unsigned LclNum; + // Offset at which the register is inserted into the local. Used e.g. for + // HFAs on arm64 that might have been promoted as a single local (e.g. + // System.Numerics.Plane is passed in 3 float regs but enregistered as + // TYP_SIMD12). + // SysV 64 also see similar situations e.g. Vector3 being passed in + // xmm0[0..8), xmm1[8..12), but enregistered as one register. + unsigned Offset; + + ParameterRegisterLocalMapping(const ABIPassingSegment* segment, unsigned lclNum, unsigned offset) + : RegisterSegment(segment) + , LclNum(lclNum) + , Offset(offset) + { + } +}; + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -8262,8 +8289,6 @@ class Compiler */ public: - regNumber raUpdateRegStateForArg(RegState* regState, LclVarDsc* argDsc); - void raMarkStkVars(); #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE @@ -8295,8 +8320,14 @@ class Compiler bool rpMustCreateEBPFrame(INDEBUG(const char** wbReason)); private: - Lowering* m_pLowering; // Lowering; needed to Lower IR that's added or modified after Lowering. - LinearScanInterface* m_pLinearScan; // Linear Scan allocator + Lowering* m_pLowering = nullptr; // Lowering; needed to Lower IR that's added or modified after Lowering. + LinearScanInterface* m_pLinearScan = nullptr; // Linear Scan allocator + +public: + ArrayStack* m_paramRegLocalMappings = nullptr; + + const ParameterRegisterLocalMapping* FindParameterRegisterLocalMappingByRegister(regNumber reg); + const ParameterRegisterLocalMapping* FindParameterRegisterLocalMappingByLocal(unsigned lclNum, unsigned offset); /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -8311,7 +8342,6 @@ class Compiler XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ -public: // Get handles void eeGetCallInfo(CORINFO_RESOLVED_TOKEN* pResolvedToken, diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 834a53be776a61..99233b77cd38fe 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -5456,6 +5456,12 @@ bool Compiler::ThreeOptLayout::RunGreedyThreeOptPass(unsigned startPos, unsigned continue; } + // Don't consider any cut points that would move try/handler entries + if (compiler->bbIsTryBeg(s3BlockPrev) || compiler->bbIsHandlerBeg(s3BlockPrev)) + { + continue; + } + // Compute the cost delta of this partition const weight_t currCost = currCostBase + GetCost(s3BlockPrev, s3Block); const weight_t newCost = diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp index 60b0de33c83836..8a62434e99a98e 100644 --- a/src/coreclr/jit/gcencode.cpp +++ b/src/coreclr/jit/gcencode.cpp @@ -3709,15 +3709,6 @@ class GcInfoEncoderWithLogging } } - void SetReturnKind(ReturnKind returnKind) - { - m_gcInfoEncoder->SetReturnKind(returnKind); - if (m_doLogging) - { - printf("Set ReturnKind to %s.\n", ReturnKindToString(returnKind)); - } - } - void SetStackBaseRegister(UINT32 registerNumber) { m_gcInfoEncoder->SetStackBaseRegister(registerNumber); @@ -3832,8 +3823,6 @@ void GCInfo::gcInfoBlockHdrSave(GcInfoEncoder* gcInfoEncoder, unsigned methodSiz gcInfoEncoderWithLog->SetCodeLength(methodSize); - gcInfoEncoderWithLog->SetReturnKind(getReturnKind()); - if (compiler->isFramePointerUsed()) { gcInfoEncoderWithLog->SetStackBaseRegister(REG_FPBASE); diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index b66da476b05c72..726b4a5e3b2763 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -1941,7 +1941,8 @@ void Compiler::fgInterBlockLocalVarLiveness() // liveness of such locals will bubble to the top (fgFirstBB) // in fgInterBlockLocalVarLiveness() - if (!varDsc->lvIsParam && VarSetOps::IsMember(this, fgFirstBB->bbLiveIn, varDsc->lvVarIndex) && + if (!varDsc->lvIsParam && !varDsc->lvIsParamRegTarget && + VarSetOps::IsMember(this, fgFirstBB->bbLiveIn, varDsc->lvVarIndex) && (info.compInitMem || varTypeIsGC(varDsc->TypeGet())) && !fieldOfDependentlyPromotedStruct) { varDsc->lvMustInit = true; @@ -1962,7 +1963,7 @@ void Compiler::fgInterBlockLocalVarLiveness() if (isFinallyVar) { // Set lvMustInit only if we have a non-arg, GC pointer. - if (!varDsc->lvIsParam && varTypeIsGC(varDsc->TypeGet())) + if (!varDsc->lvIsParam && !varDsc->lvIsParamRegTarget && varTypeIsGC(varDsc->TypeGet())) { varDsc->lvMustInit = true; } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 63a03c60975853..b11495b8713921 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -7850,6 +7850,11 @@ PhaseStatus Lowering::DoPhase() LowerBlock(block); } + if (comp->opts.OptimizationEnabled()) + { + MapParameterRegisterLocals(); + } + #ifdef DEBUG JITDUMP("Lower has completed modifying nodes.\n"); if (VERBOSE) @@ -7902,6 +7907,84 @@ PhaseStatus Lowering::DoPhase() return PhaseStatus::MODIFIED_EVERYTHING; } +//------------------------------------------------------------------------ +// Lowering::MapParameterRegisterLocals: +// Create mappings between parameter registers and locals corresponding to +// them. +// +void Lowering::MapParameterRegisterLocals() +{ + comp->m_paramRegLocalMappings = + new (comp, CMK_ABI) ArrayStack(comp->getAllocator(CMK_ABI)); + + // Create initial mappings for promotions. + for (unsigned lclNum = 0; lclNum < comp->info.compArgsCount; lclNum++) + { + LclVarDsc* lclDsc = comp->lvaGetDesc(lclNum); + const ABIPassingInformation& abiInfo = comp->lvaGetParameterABIInfo(lclNum); + + if (comp->lvaGetPromotionType(lclDsc) != Compiler::PROMOTION_TYPE_INDEPENDENT) + { + // If not promoted, then we do not need to create any mappings. + // If dependently promoted then the fields are never enregistered + // by LSRA, so no reason to try to create any mappings. + continue; + } + + if (!abiInfo.HasAnyRegisterSegment()) + { + // If the parameter is not passed in any registers, then there are + // no mappings to create. + continue; + } + + // Currently we do not support promotion of split parameters, so we + // should not see any split parameters here. + assert(!abiInfo.IsSplitAcrossRegistersAndStack()); + + for (int i = 0; i < lclDsc->lvFieldCnt; i++) + { + unsigned fieldLclNum = lclDsc->lvFieldLclStart + i; + LclVarDsc* fieldDsc = comp->lvaGetDesc(fieldLclNum); + + for (const ABIPassingSegment& segment : abiInfo.Segments()) + { + if (segment.Offset + segment.Size <= fieldDsc->lvFldOffset) + { + // This register does not map to this field (ends before the field starts) + continue; + } + + if (fieldDsc->lvFldOffset + fieldDsc->lvExactSize() <= segment.Offset) + { + // This register does not map to this field (starts after the field ends) + continue; + } + + comp->m_paramRegLocalMappings->Emplace(&segment, fieldLclNum, segment.Offset - fieldDsc->lvFldOffset); + } + + LclVarDsc* fieldLclDsc = comp->lvaGetDesc(fieldLclNum); + assert(!fieldLclDsc->lvIsParamRegTarget); + fieldLclDsc->lvIsParamRegTarget = true; + } + } + +#ifdef DEBUG + if (comp->verbose) + { + printf("%d parameter register to local mappings\n", comp->m_paramRegLocalMappings->Height()); + for (int i = 0; i < comp->m_paramRegLocalMappings->Height(); i++) + { + const ParameterRegisterLocalMapping& mapping = comp->m_paramRegLocalMappings->BottomRef(i); + printf(" "); + mapping.RegisterSegment->Dump(); + printf(" -> V%02u\n", mapping.LclNum); + } + } +#endif +} + #ifdef DEBUG //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 9aee0fd99a1209..659870c844cd73 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -132,6 +132,8 @@ class Lowering final : public Phase static bool CheckBlock(Compiler* compiler, BasicBlock* block); #endif // DEBUG + void MapParameterRegisterLocals(); + void LowerBlock(BasicBlock* block); GenTree* LowerNode(GenTree* node); diff --git a/src/coreclr/jit/lsra.h b/src/coreclr/jit/lsra.h index 9312eb46a52b24..d2b35184b81e57 100644 --- a/src/coreclr/jit/lsra.h +++ b/src/coreclr/jit/lsra.h @@ -1038,20 +1038,6 @@ class LinearScan : public LinearScanInterface Interval* lclVarInterval, LsraLocation currentLoc, GenTree* node, bool isUse, unsigned multiRegIdx); #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE -#if defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // For AMD64 on SystemV machines. This method - // is called as replacement for raUpdateRegStateForArg - // that is used on Windows. On System V systems a struct can be passed - // partially using registers from the 2 register files. - // - // For LoongArch64's ABI, a struct can be passed - // partially using registers from the 2 register files. - void UpdateRegStateForStructArg(LclVarDsc* argDsc); -#endif // defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - - // Update reg state for an incoming register argument - void updateRegStateForArg(LclVarDsc* argDsc); - inline bool isCandidateLocalRef(GenTree* tree) { if (tree->IsLocal()) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 56bba3469eb27b..72f88864d755a7 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -2029,7 +2029,7 @@ void LinearScan::insertZeroInitRefPositions() while (iter.NextElem(&varIndex)) { LclVarDsc* varDsc = compiler->lvaGetDescByTrackedIndex(varIndex); - if (!varDsc->lvIsParam && isCandidateVar(varDsc)) + if (!varDsc->lvIsParam && !varDsc->lvIsParamRegTarget && isCandidateVar(varDsc)) { JITDUMP("V%02u was live in to first block:", compiler->lvaTrackedIndexToLclNum(varIndex)); Interval* interval = getIntervalForLocalVar(varIndex); @@ -2094,110 +2094,6 @@ void LinearScan::insertZeroInitRefPositions() } } -#if defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) -//------------------------------------------------------------------------ -// UpdateRegStateForStructArg: -// Sets the register state for an argument of type STRUCT. -// This is shared between with AMD64's SystemV systems and LoongArch64-ABI. -// -// Arguments: -// argDsc - the LclVarDsc for the argument of interest -// -// Notes: -// See Compiler::raUpdateRegStateForArg(RegState *regState, LclVarDsc *argDsc) in regalloc.cpp -// for how state for argument is updated for unix non-structs and Windows AMD64 structs. -// -void LinearScan::UpdateRegStateForStructArg(LclVarDsc* argDsc) -{ - assert(varTypeIsStruct(argDsc)); - RegState* intRegState = &compiler->codeGen->intRegState; - RegState* floatRegState = &compiler->codeGen->floatRegState; - - if ((argDsc->GetArgReg() != REG_STK) && (argDsc->GetArgReg() != REG_NA)) - { - if ((genRegMask(argDsc->GetArgReg()) & RBM_ALLFLOAT) != RBM_NONE) - { - assert((genRegMask(argDsc->GetArgReg()) & RBM_FLTARG_REGS) != RBM_NONE); - floatRegState->rsCalleeRegArgMaskLiveIn.AddRegNumInMask(argDsc->GetArgReg()); - } - else - { - assert((genRegMask(argDsc->GetArgReg()) & fullIntArgRegMask(compiler->info.compCallConv)) != RBM_NONE); - intRegState->rsCalleeRegArgMaskLiveIn.AddRegNumInMask(argDsc->GetArgReg()); - } - } - - if ((argDsc->GetOtherArgReg() != REG_STK) && (argDsc->GetOtherArgReg() != REG_NA)) - { - if (genRegMask(argDsc->GetOtherArgReg()) & (RBM_ALLFLOAT)) - { - assert(genRegMask(argDsc->GetOtherArgReg()) & (RBM_FLTARG_REGS)); - floatRegState->rsCalleeRegArgMaskLiveIn.AddRegNumInMask(argDsc->GetOtherArgReg()); - } - else - { - assert((genRegMask(argDsc->GetOtherArgReg()) & fullIntArgRegMask(compiler->info.compCallConv)) != RBM_NONE); - intRegState->rsCalleeRegArgMaskLiveIn.AddRegNumInMask(argDsc->GetOtherArgReg()); - } - } -} - -#endif // defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - -//------------------------------------------------------------------------ -// updateRegStateForArg: Updates rsCalleeRegArgMaskLiveIn for the appropriate -// regState (either compiler->intRegState or compiler->floatRegState), -// with the lvArgReg on "argDsc" -// -// Arguments: -// argDsc - the argument for which the state is to be updated. -// -// Return Value: None -// -// Assumptions: -// The argument is live on entry to the function -// (or is untracked and therefore assumed live) -// -void LinearScan::updateRegStateForArg(LclVarDsc* argDsc) -{ -#if defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // For SystemV-AMD64 and LoongArch64 calls the argDsc - // can have 2 registers (for structs.). Handle them here. - if (varTypeIsStruct(argDsc)) - { - UpdateRegStateForStructArg(argDsc); - } - else -#endif // defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - { - RegState* intRegState = &compiler->codeGen->intRegState; - RegState* floatRegState = &compiler->codeGen->floatRegState; - bool isFloat = emitter::isFloatReg(argDsc->GetArgReg()); - - if (argDsc->lvIsHfaRegArg()) - { - isFloat = true; - } - - if (isFloat) - { - JITDUMP("Float arg V%02u in reg %s\n", compiler->lvaGetLclNum(argDsc), getRegName(argDsc->GetArgReg())); - compiler->raUpdateRegStateForArg(floatRegState, argDsc); - } - else - { - JITDUMP("Int arg V%02u in reg %s\n", compiler->lvaGetLclNum(argDsc), getRegName(argDsc->GetArgReg())); -#if FEATURE_MULTIREG_ARGS - if (argDsc->GetOtherArgReg() != REG_NA) - { - JITDUMP("(second half) in reg %s\n", getRegName(argDsc->GetOtherArgReg())); - } -#endif // FEATURE_MULTIREG_ARGS - compiler->raUpdateRegStateForArg(intRegState, argDsc); - } - } -} - template void LinearScan::buildIntervals(); template void LinearScan::buildIntervals(); @@ -2279,36 +2175,44 @@ void LinearScan::buildIntervals() regsInUseThisLocation = RBM_NONE; regsInUseNextLocation = RBM_NONE; -#ifdef SWIFT_SUPPORT - if (compiler->info.compCallConv == CorInfoCallConvExtension::Swift) + // Compute live incoming parameter registers. The liveness is based on the + // locals we are expecting to store the registers into in the prolog. + for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++) { - for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++) + const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(lclNum); + for (const ABIPassingSegment& seg : abiInfo.Segments()) { - LclVarDsc* argDsc = compiler->lvaGetDesc(lclNum); - - if ((argDsc->lvRefCnt() == 0) && !compiler->opts.compDbgCode) + if (!seg.IsPassedInRegister()) { continue; } - const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(lclNum); - for (const ABIPassingSegment& seg : abiInfo.Segments()) + const ParameterRegisterLocalMapping* mapping = + compiler->FindParameterRegisterLocalMappingByRegister(seg.GetRegister()); + + unsigned mappedLclNum = mapping != nullptr ? mapping->LclNum : lclNum; + JITDUMP("Arg V%02u in reg %s\n", mappedLclNum, getRegName(seg.GetRegister())); + LclVarDsc* argDsc = compiler->lvaGetDesc(mappedLclNum); + if (argDsc->lvTracked && !compiler->compJmpOpUsed && (argDsc->lvRefCnt() == 0) && + !compiler->opts.compDbgCode) { - if (seg.IsPassedInRegister()) - { - RegState* regState = genIsValidFloatReg(seg.GetRegister()) ? floatRegState : intRegState; - regState->rsCalleeRegArgMaskLiveIn |= seg.GetRegisterMask(); - } + // Not live + continue; } + + RegState* regState = genIsValidFloatReg(seg.GetRegister()) ? floatRegState : intRegState; + regState->rsCalleeRegArgMaskLiveIn |= seg.GetRegisterMask(); } } -#endif + // Now build initial definitions for all parameters, preferring their ABI + // register if passed in one. for (unsigned int varIndex = 0; varIndex < compiler->lvaTrackedCount; varIndex++) { - LclVarDsc* argDsc = compiler->lvaGetDescByTrackedIndex(varIndex); + unsigned lclNum = compiler->lvaTrackedIndexToLclNum(varIndex); + LclVarDsc* lclDsc = compiler->lvaGetDesc(lclNum); - if (!argDsc->lvIsParam) + if (!isCandidateVar(lclDsc)) { continue; } @@ -2319,70 +2223,53 @@ void LinearScan::buildIntervals() // Use lvRefCnt instead of checking bbLiveIn because if it's volatile we // won't have done dataflow on it, but it needs to be marked as live-in so // it will get saved in the prolog. - if (!compiler->compJmpOpUsed && argDsc->lvRefCnt() == 0 && !compiler->opts.compDbgCode) + if (!compiler->compJmpOpUsed && (lclDsc->lvRefCnt() == 0) && !compiler->opts.compDbgCode) { continue; } - if (argDsc->lvIsRegArg) - { - updateRegStateForArg(argDsc); - } - - if (isCandidateVar(argDsc)) + regNumber paramReg = REG_NA; + if (lclDsc->lvIsParamRegTarget) { - buildInitialParamDef(argDsc, argDsc->lvIsRegArg ? argDsc->GetArgReg() : REG_NA); + // Prefer the first ABI register. + const ParameterRegisterLocalMapping* mapping = + compiler->FindParameterRegisterLocalMappingByLocal(lclNum, 0); + assert(mapping != nullptr); + paramReg = mapping->RegisterSegment->GetRegister(); } - else if (argDsc->lvPromoted) + else if (lclDsc->lvIsParam) { - for (unsigned fieldVarNum = argDsc->lvFieldLclStart; - fieldVarNum < argDsc->lvFieldLclStart + argDsc->lvFieldCnt; ++fieldVarNum) + if (lclDsc->lvIsStructField) { - const LclVarDsc* fieldVarDsc = compiler->lvaGetDesc(fieldVarNum); - if (fieldVarDsc->lvLRACandidate) - { - assert(fieldVarDsc->lvTracked); - buildInitialParamDef(fieldVarDsc, REG_NA); - } - } - } - else - { - // We can overwrite the register (i.e. codegen saves it on entry) - assert(argDsc->lvRefCnt() == 0 || !argDsc->lvIsRegArg || argDsc->lvDoNotEnregister || - !argDsc->lvLRACandidate || (varTypeIsFloating(argDsc->TypeGet()) && compiler->opts.compDbgCode)); - } - } - - // Now set up the reg state for the non-tracked args - // (We do this here because we want to generate the ParamDef RefPositions in tracked - // order, so that loop doesn't hit the non-tracked args) - - for (unsigned argNum = 0; argNum < compiler->info.compArgsCount; argNum++) - { - LclVarDsc* argDsc = compiler->lvaGetDesc(argNum); + // All fields passed in registers should be assigned via the + // lvIsParamRegTarget mechanism, so this must be a stack + // argument. + assert(!compiler->lvaGetParameterABIInfo(lclDsc->lvParentLcl).HasAnyRegisterSegment()); - if (argDsc->lvPromoted) - { - for (unsigned fieldVarNum = argDsc->lvFieldLclStart; - fieldVarNum < argDsc->lvFieldLclStart + argDsc->lvFieldCnt; ++fieldVarNum) + // Fall through with paramReg == REG_NA + } + else { - LclVarDsc* fieldVarDsc = compiler->lvaGetDesc(fieldVarNum); - noway_assert(fieldVarDsc->lvIsParam); - if (!fieldVarDsc->lvTracked && fieldVarDsc->lvIsRegArg) + // Enregisterable parameter, may or may not be a stack arg. + // Prefer the first register if there is one. + const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(lclNum); + for (const ABIPassingSegment& seg : abiInfo.Segments()) { - updateRegStateForArg(fieldVarDsc); + if (seg.IsPassedInRegister()) + { + paramReg = seg.GetRegister(); + break; + } } } } else { - noway_assert(argDsc->lvIsParam); - if (!argDsc->lvTracked && argDsc->lvIsRegArg) - { - updateRegStateForArg(argDsc); - } + // Not a parameter or target of a parameter register + continue; } + + buildInitialParamDef(lclDsc, paramReg); } // If there is a secret stub param, it is also live in diff --git a/src/coreclr/jit/regalloc.cpp b/src/coreclr/jit/regalloc.cpp index ba07086a63c212..1de4dd8bd66992 100644 --- a/src/coreclr/jit/regalloc.cpp +++ b/src/coreclr/jit/regalloc.cpp @@ -95,80 +95,6 @@ bool Compiler::shouldDoubleAlign( } #endif // DOUBLE_ALIGN -// The code to set the regState for each arg is outlined for shared use -// by linear scan. (It is not shared for System V AMD64 platform.) -regNumber Compiler::raUpdateRegStateForArg(RegState* regState, LclVarDsc* argDsc) -{ - regNumber inArgReg = argDsc->GetArgReg(); - regMaskTP inArgMask = genRegMask(inArgReg); - - if (regState->rsIsFloat) - { - assert((inArgMask & RBM_FLTARG_REGS) != RBM_NONE); - } - else - { - assert((inArgMask & fullIntArgRegMask(info.compCallConv)) != RBM_NONE); - } - - regState->rsCalleeRegArgMaskLiveIn |= inArgMask; - -#ifdef TARGET_ARM - if (argDsc->lvType == TYP_DOUBLE) - { - if (info.compIsVarArgs || opts.compUseSoftFP) - { - assert((inArgReg == REG_R0) || (inArgReg == REG_R2)); - assert(!regState->rsIsFloat); - } - else - { - assert(regState->rsIsFloat); - assert(emitter::isDoubleReg(inArgReg)); - } - regState->rsCalleeRegArgMaskLiveIn |= genRegMask((regNumber)(inArgReg + 1)); - } - else if (argDsc->lvType == TYP_LONG) - { - assert((inArgReg == REG_R0) || (inArgReg == REG_R2)); - assert(!regState->rsIsFloat); - regState->rsCalleeRegArgMaskLiveIn |= genRegMask((regNumber)(inArgReg + 1)); - } -#endif // TARGET_ARM - -#if FEATURE_MULTIREG_ARGS - if (varTypeIsStruct(argDsc->lvType)) - { - if (argDsc->lvIsHfaRegArg()) - { - assert(regState->rsIsFloat); - unsigned cSlots = argDsc->lvHfaSlots(); - for (unsigned i = 1; i < cSlots; i++) - { - assert(inArgReg + i <= LAST_FP_ARGREG); - regState->rsCalleeRegArgMaskLiveIn |= genRegMask(static_cast(inArgReg + i)); - } - } - else - { - assert(!regState->rsIsFloat); - unsigned cSlots = argDsc->lvSize() / TARGET_POINTER_SIZE; - for (unsigned i = 1; i < cSlots; i++) - { - regNumber nextArgReg = (regNumber)(inArgReg + i); - if (nextArgReg > REG_ARG_LAST) - { - break; - } - regState->rsCalleeRegArgMaskLiveIn |= genRegMask(nextArgReg); - } - } - } -#endif // FEATURE_MULTIREG_ARGS - - return inArgReg; -} - //------------------------------------------------------------------------ // rpMustCreateEBPFrame: // Returns true when we must create an EBP frame diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 37e5883b4059b4..358807e6ab563e 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -11,8 +11,8 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 10; - static const uint32_t CurrentMinorVersion = 1; + static const uint32_t CurrentMajorVersion = 11; + static const uint32_t CurrentMinorVersion = 0; }; struct ReadyToRunHeader diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 7f1fdfecc58662..d7e0cce13655e4 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -213,44 +213,18 @@ void UnixNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, #ifdef TARGET_ARM // Ensure that code offset doesn't have the Thumb bit set. We need - // it to be aligned to instruction start to make the !isActiveStackFrame - // branch below work. + // it to be aligned to instruction start ASSERT(((uintptr_t)codeOffset & 1) == 0); #endif - bool executionAborted = ((UnixNativeMethodInfo*)pMethodInfo)->executionAborted; - - if (!isActiveStackFrame && !executionAborted) - { - // the reasons for this adjustment are explained in EECodeManager::EnumGcRefs - codeOffset--; - } - GcInfoDecoder decoder( GCInfoToken(gcInfo), GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), codeOffset ); - if (isActiveStackFrame) - { - // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. - // Or, better yet, maybe we should change the decoder to not require this adjustment. - // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) - // does not seem possible. - if (!decoder.HasInterruptibleRanges()) - { - decoder = GcInfoDecoder( - GCInfoToken(gcInfo), - GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), - codeOffset - 1 - ); - - assert(decoder.IsSafePoint()); - } - } - ICodeManagerFlags flags = (ICodeManagerFlags)0; + bool executionAborted = ((UnixNativeMethodInfo*)pMethodInfo)->executionAborted; if (executionAborted) flags = ICodeManagerFlags::ExecutionAborted; diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index 5fe6e446702836..a6437b56ac7b80 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -440,9 +440,8 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, PTR_uint8_t gcInfo; uint32_t codeOffset = GetCodeOffset(pMethodInfo, safePointAddress, &gcInfo); - bool executionAborted = ((CoffNativeMethodInfo *)pMethodInfo)->executionAborted; - ICodeManagerFlags flags = (ICodeManagerFlags)0; + bool executionAborted = ((CoffNativeMethodInfo *)pMethodInfo)->executionAborted; if (executionAborted) flags = ICodeManagerFlags::ExecutionAborted; @@ -453,11 +452,6 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, flags = (ICodeManagerFlags)(flags | ICodeManagerFlags::ActiveStackFrame); #ifdef USE_GC_INFO_DECODER - if (!isActiveStackFrame && !executionAborted) - { - // the reasons for this adjustment are explained in EECodeManager::EnumGcRefs - codeOffset--; - } GcInfoDecoder decoder( GCInfoToken(gcInfo), @@ -465,24 +459,6 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, codeOffset ); - if (isActiveStackFrame) - { - // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. - // Or, better yet, maybe we should change the decoder to not require this adjustment. - // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) - // does not seem possible. - if (!decoder.HasInterruptibleRanges()) - { - decoder = GcInfoDecoder( - GCInfoToken(gcInfo), - GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), - codeOffset - 1 - ); - - assert(decoder.IsSafePoint()); - } - } - if (!decoder.EnumerateLiveSlots( pRegisterSet, isActiveStackFrame /* reportScratchSlots */, diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 51860d1341c7b8..bca68c323f6523 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -15,8 +15,8 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 10; - public const ushort CurrentMinorVersion = 1; + public const ushort CurrentMajorVersion = 11; + public const ushort CurrentMinorVersion = 0; } #if READYTORUN #pragma warning disable 0169 diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs index b934e36719e8a2..860a18b73565a2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs @@ -47,7 +47,9 @@ public SafePointOffset(int index, uint value) } private const int MIN_GCINFO_VERSION_WITH_RETURN_KIND = 2; + private const int MAX_GCINFO_VERSION_WITH_RETURN_KIND = 3; private const int MIN_GCINFO_VERSION_WITH_REV_PINVOKE_FRAME = 2; + private const int MIN_GCINFO_VERSION_WITH_NORMALIZED_CODE_OFFSETS = 3; private bool _slimHeader; private bool _hasSecurityObject; @@ -88,7 +90,9 @@ public GcInfo() { } public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, ushort minorVersion) { Offset = offset; - _gcInfoTypes = new GcInfoTypes(machine); + Version = ReadyToRunVersionToGcInfoVersion(majorVersion, minorVersion); + bool denormalizeCodeOffsets = Version > MIN_GCINFO_VERSION_WITH_NORMALIZED_CODE_OFFSETS; + _gcInfoTypes = new GcInfoTypes(machine, denormalizeCodeOffsets); _machine = machine; SecurityObjectStackSlot = -1; @@ -100,12 +104,11 @@ public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, us SizeOfEditAndContinuePreservedArea = 0xffffffff; ReversePInvokeFrameStackSlot = -1; - Version = ReadyToRunVersionToGcInfoVersion(majorVersion, minorVersion); int bitOffset = offset * 8; ParseHeaderFlags(image, ref bitOffset); - if (Version >= MIN_GCINFO_VERSION_WITH_RETURN_KIND) // IsReturnKindAvailable + if (Version >= MIN_GCINFO_VERSION_WITH_RETURN_KIND && Version <= MAX_GCINFO_VERSION_WITH_RETURN_KIND) // IsReturnKindAvailable { int returnKindBits = (_slimHeader) ? _gcInfoTypes.SIZE_OF_RETURN_KIND_SLIM : _gcInfoTypes.SIZE_OF_RETURN_KIND_FAT; ReturnKind = (ReturnKinds)NativeReader.ReadBits(image, returnKindBits, ref bitOffset); @@ -118,12 +121,13 @@ public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, us uint normPrologSize = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; uint normEpilogSize = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_EPILOG_SIZE_ENCBASE, ref bitOffset); - ValidRangeStart = normPrologSize; - ValidRangeEnd = (uint)CodeLength - normEpilogSize; + ValidRangeStart = _gcInfoTypes.DenormalizeCodeOffset(normPrologSize); + ValidRangeEnd = (uint)CodeLength - _gcInfoTypes.DenormalizeCodeOffset(normEpilogSize); } else if (_hasSecurityObject || _hasGenericsInstContext) { - ValidRangeStart = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; + uint normValidRangeStart = NativeReader.DecodeVarLengthUnsigned(image, _gcInfoTypes.NORM_PROLOG_SIZE_ENCBASE, ref bitOffset) + 1; + ValidRangeStart = _gcInfoTypes.DenormalizeCodeOffset(normValidRangeStart); ValidRangeEnd = ValidRangeStart + 1; } @@ -352,11 +356,11 @@ private void ParseHeaderFlags(byte[] image, ref int bitOffset) private List EnumerateSafePoints(byte[] image, ref int bitOffset) { List safePoints = new List(); - uint numBitsPerOffset = GcInfoTypes.CeilOfLog2(CodeLength); + uint numBitsPerOffset = GcInfoTypes.CeilOfLog2((int)_gcInfoTypes.NormalizeCodeOffset((uint)CodeLength)); for (int i = 0; i < NumSafePoints; i++) { uint normOffset = (uint)NativeReader.ReadBits(image, (int)numBitsPerOffset, ref bitOffset); - safePoints.Add(new SafePointOffset(i, normOffset)); + safePoints.Add(new SafePointOffset(i, _gcInfoTypes.DenormalizeCodeOffset(normOffset))); } return safePoints; } @@ -367,18 +371,21 @@ private List EnumerateSafePoints(byte[] image, ref int bitOffse private List EnumerateInterruptibleRanges(byte[] image, int interruptibleRangeDelta1EncBase, int interruptibleRangeDelta2EncBase, ref int bitOffset) { List ranges = new List(); - uint lastinterruptibleRangeStopOffset = 0; + uint normLastinterruptibleRangeStopOffset = 0; for (uint i = 0; i < NumInterruptibleRanges; i++) { uint normStartDelta = NativeReader.DecodeVarLengthUnsigned(image, interruptibleRangeDelta1EncBase, ref bitOffset); uint normStopDelta = NativeReader.DecodeVarLengthUnsigned(image, interruptibleRangeDelta2EncBase, ref bitOffset) + 1; - uint rangeStartOffset = lastinterruptibleRangeStopOffset + normStartDelta; - uint rangeStopOffset = rangeStartOffset + normStopDelta; + uint normRangeStartOffset = normLastinterruptibleRangeStopOffset + normStartDelta; + uint normRangeStopOffset = normRangeStartOffset + normStopDelta; + + uint rangeStartOffset = _gcInfoTypes.DenormalizeCodeOffset(normRangeStopOffset); + uint rangeStopOffset = _gcInfoTypes.DenormalizeCodeOffset(normRangeStartOffset); ranges.Add(new InterruptibleRange(i, rangeStartOffset, rangeStopOffset)); - lastinterruptibleRangeStopOffset = rangeStopOffset; + normLastinterruptibleRangeStopOffset = normRangeStopOffset; } return ranges; } @@ -397,7 +404,11 @@ private int ReadyToRunVersionToGcInfoVersion(int readyToRunMajorVersion, int rea if (readyToRunMajorVersion < 9 || (readyToRunMajorVersion == 9 && readyToRunMinorVersion < 2)) return 2; - return 3; + // R2R 11.0+ uses GCInfo v4 + if (readyToRunMajorVersion < 11) + return 3; + + return 4; } private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bitOffset) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs index d3adc462409589..653425781a7cf6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs @@ -73,6 +73,7 @@ enum InfoHdrAdjust public class GcInfoTypes { private Machine _target; + private bool _denormalizeCodeOffsets; internal int SIZE_OF_RETURN_KIND_SLIM { get; } = 2; internal int SIZE_OF_RETURN_KIND_FAT { get; } = 2; @@ -105,9 +106,10 @@ public class GcInfoTypes internal int LIVESTATE_RLE_SKIP_ENCBASE { get; } = 4; internal int NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2 { get; } = 6; - internal GcInfoTypes(Machine machine) + internal GcInfoTypes(Machine machine, bool denormalizeCodeOffsets) { _target = machine; + _denormalizeCodeOffsets = denormalizeCodeOffsets; switch (machine) { @@ -176,6 +178,34 @@ internal int DenormalizeCodeLength(int x) return x; } + internal int NormalizeCodeLength(int x) + { + switch (_target) + { + case Machine.ArmThumb2: + return (x >> 1); + case Machine.Arm64: + case Machine.LoongArch64: + case Machine.RiscV64: + return (x >> 2); + } + return x; + } + + internal uint DenormalizeCodeOffset(uint x) + { + return _denormalizeCodeOffsets ? + (uint)DenormalizeCodeLength((int)x) : + x; + } + + internal uint NormalizeCodeOffset(uint x) + { + return _denormalizeCodeOffsets ? + (uint)NormalizeCodeLength((int)x) : + x; + } + internal int DenormalizeStackSlot(int x) { switch (_target) diff --git a/src/coreclr/vm/arm/profiler.cpp b/src/coreclr/vm/arm/profiler.cpp index 410417a667a01f..a57d1cc3eb81b6 100644 --- a/src/coreclr/vm/arm/profiler.cpp +++ b/src/coreclr/vm/arm/profiler.cpp @@ -55,7 +55,7 @@ void ProfileSetFunctionIDInPlatformSpecificHandle(void * pPlatformSpecificHandle { LIMITED_METHOD_CONTRACT; _ASSERTE(pPlatformSpecificHandle != NULL); - _ASSERTE(functionID != NULL); + _ASSERTE(functionID != (FunctionID)NULL); PROFILE_PLATFORM_SPECIFIC_DATA * pData = reinterpret_cast(pPlatformSpecificHandle); pData->functionId = functionID; diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index cef0bea5d64793..26daab9a06e3bb 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -1098,7 +1098,7 @@ void ResolveHolder::Initialize(ResolveHolder* pResolveHolderRX, _stub._cacheMask = CALL_STUB_CACHE_MASK * sizeof(void*); _ASSERTE(resolveWorkerTarget == (PCODE)ResolveWorkerChainLookupAsmStub); - _ASSERTE(patcherTarget == NULL); + _ASSERTE(patcherTarget == (PCODE)NULL); } Stub *GenerateInitPInvokeFrameHelper() diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 25a4c73b65ea0b..0c06eaf159e6f3 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -1426,37 +1426,6 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, } #endif - /* If we are not in the active method, we are currently pointing - * to the return address; at the return address stack variables - * can become dead if the call is the last instruction of a try block - * and the return address is the jump around the catch block. Therefore - * we simply assume an offset inside of call instruction. - * NOTE: The GcInfoDecoder depends on this; if you change it, you must - * revisit the GcInfoEncoder/Decoder - */ - - if (!(flags & ExecutionAborted)) - { - if (!(flags & ActiveStackFrame)) - { - curOffs--; - LOG((LF_GCINFO, LL_INFO1000, "Adjusted GC reporting offset due to flags !ExecutionAborted && !ActiveStackFrame. Now reporting GC refs for %s at offset %04x.\n", - methodName, curOffs)); - } - } - else - { - // Since we are aborting execution, we are either in a frame that actually faulted or in a throwing call. - // * We do not need to adjust in a leaf - // * A throwing call will have unreachable after it, thus GC info is the same as before the call. - // - // Either way we do not need to adjust. - - // NOTE: only fully interruptible methods may need to report anything here as without - // exception handling all current local variables are already unreachable. - // EnumerateLiveSlots will shortcircuit the partially interruptible case just a bit later. - } - // Check if we have been given an override value for relOffset if (relOffsetOverride != NO_OVERRIDE_OFFSET) { @@ -1500,7 +1469,6 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, // A frame is non-leaf if we are executing a call, or a fault occurred in the function. // The only case in which we need to report scratch slots for a non-leaf frame // is when execution has to be resumed at the point of interruption (via ResumableFrame) - //Implement ResumableFrame _ASSERTE( sizeof( BOOL ) >= sizeof( ActiveStackFrame ) ); reportScratchSlots = (flags & ActiveStackFrame) != 0; @@ -1511,24 +1479,6 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, curOffs ); - if ((flags & ActiveStackFrame) != 0) - { - // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. - // Or, better yet, maybe we should change the decoder to not require this adjustment. - // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) - // does not seem possible. - if (!gcInfoDecoder.HasInterruptibleRanges()) - { - gcInfoDecoder = GcInfoDecoder( - gcInfoToken, - GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), - curOffs - 1 - ); - - _ASSERTE(gcInfoDecoder.CouldBeSafePoint()); - } - } - if (!gcInfoDecoder.EnumerateLiveSlots( pRD, reportScratchSlots, diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 4197e456d8263f..1b11a3e8f7ceee 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -91,9 +91,6 @@ bool GcInfoDecoder::PredecodeFatHeader(int remainingFlags) int numFlagBits = (m_Version == 1) ? GC_INFO_FLAGS_BIT_SIZE_VERSION_1 : GC_INFO_FLAGS_BIT_SIZE; m_headerFlags = (GcInfoHeaderFlags)m_Reader.Read(numFlagBits); - // skip over the unused return kind. - m_Reader.Read(SIZE_OF_RETURN_KIND_IN_FAT_HEADER); - remainingFlags &= ~DECODE_VARARG; #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) remainingFlags &= ~DECODE_HAS_TAILCALLS; @@ -299,9 +296,6 @@ GcInfoDecoder::GcInfoDecoder( m_StackBaseRegister = NO_STACK_BASE_REGISTER; } - // skip over the unused return kind. - m_Reader.Read(SIZE_OF_RETURN_KIND_IN_SLIM_HEADER); - remainingFlags &= ~DECODE_VARARG; #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) remainingFlags &= ~DECODE_HAS_TAILCALLS; @@ -370,11 +364,7 @@ GcInfoDecoder::GcInfoDecoder( { if(m_NumSafePoints) { - // Safepoints are encoded with a -1 adjustment - // DECODE_GC_LIFETIMES adjusts the offset accordingly, but DECODE_INTERRUPTIBILITY does not - // adjust here - UINT32 offset = flags & DECODE_INTERRUPTIBILITY ? m_InstructionOffset - 1 : m_InstructionOffset; - m_SafePointIndex = FindSafePoint(offset); + m_SafePointIndex = FindSafePoint(m_InstructionOffset); } } else if(flags & DECODE_FOR_RANGES_CALLBACK) @@ -450,10 +440,6 @@ bool GcInfoDecoder::IsSafePoint(UINT32 codeOffset) if(m_NumSafePoints == 0) return false; -#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // Safepoints are encoded with a -1 adjustment - codeOffset--; -#endif size_t savedPos = m_Reader.GetCurrentPos(); UINT32 safePointIndex = FindSafePoint(codeOffset); m_Reader.SetCurrentPos(savedPos); @@ -500,32 +486,26 @@ UINT32 GcInfoDecoder::FindSafePoint(UINT32 breakOffset) const size_t savedPos = m_Reader.GetCurrentPos(); const UINT32 numBitsPerOffset = CeilOfLog2(NORMALIZE_CODE_OFFSET(m_CodeLength)); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // Safepoints are encoded with a -1 adjustment - if ((breakOffset & 1) != 0) -#endif + const UINT32 normBreakOffset = NORMALIZE_CODE_OFFSET(breakOffset); + UINT32 linearSearchStart = 0; + UINT32 linearSearchEnd = m_NumSafePoints; + if (linearSearchEnd - linearSearchStart > MAX_LINEAR_SEARCH) { - const UINT32 normBreakOffset = NORMALIZE_CODE_OFFSET(breakOffset); - UINT32 linearSearchStart = 0; - UINT32 linearSearchEnd = m_NumSafePoints; - if (linearSearchEnd - linearSearchStart > MAX_LINEAR_SEARCH) + linearSearchStart = NarrowSafePointSearch(savedPos, normBreakOffset, &linearSearchEnd); + } + + for (UINT32 i = linearSearchStart; i < linearSearchEnd; i++) + { + UINT32 spOffset = (UINT32)m_Reader.Read(numBitsPerOffset); + if (spOffset == normBreakOffset) { - linearSearchStart = NarrowSafePointSearch(savedPos, normBreakOffset, &linearSearchEnd); + result = i; + break; } - for (UINT32 i = linearSearchStart; i < linearSearchEnd; i++) + if (spOffset > normBreakOffset) { - UINT32 spOffset = (UINT32)m_Reader.Read(numBitsPerOffset); - if (spOffset == normBreakOffset) - { - result = i; - break; - } - - if (spOffset > normBreakOffset) - { - break; - } + break; } } @@ -546,13 +526,7 @@ void GcInfoDecoder::EnumerateSafePoints(EnumerateSafePointsCallback *pCallback, for(UINT32 i = 0; i < m_NumSafePoints; i++) { UINT32 normOffset = (UINT32)m_Reader.Read(numBitsPerOffset); - UINT32 offset = DENORMALIZE_CODE_OFFSET(normOffset) + 2; - -#if defined(TARGET_AMD64) || defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // Safepoints are encoded with a -1 adjustment - offset--; -#endif - + UINT32 offset = DENORMALIZE_CODE_OFFSET(normOffset); pCallback(this, offset, hCallback); } } @@ -715,15 +689,6 @@ bool GcInfoDecoder::EnumerateLiveSlots( return true; } - // - // If this is a non-leaf frame and we are executing a call, the unwinder has given us the PC - // of the call instruction. We should adjust it to the PC of the instruction after the call in order to - // obtain transition information for scratch slots. However, we always assume scratch slots to be - // dead for non-leaf frames (except for ResumableFrames), so we don't need to adjust the PC. - // If this is a non-leaf frame and we are not executing a call (i.e.: a fault occurred in the function), - // then it would be incorrect to adjust the PC - // - _ASSERTE(GC_SLOT_INTERIOR == GC_CALL_INTERIOR); _ASSERTE(GC_SLOT_PINNED == GC_CALL_PINNED); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index d042dc8a8f96ba..cd684755299a48 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -1022,7 +1022,7 @@ PCODE MethodDesc::GetNativeCode() PCODE pCode = *ppCode; #ifdef TARGET_ARM - if (pCode != NULL) + if (pCode != (PCODE)NULL) pCode |= THUMB_CODE; #endif return pCode; @@ -3135,10 +3135,10 @@ BOOL MethodDesc::SetNativeCodeInterlocked(PCODE addr, PCODE pExpected /*=NULL*/) if (HasNativeCodeSlot()) { #ifdef TARGET_ARM - _ASSERTE(IsThumbCode(addr) || (addr==NULL)); + _ASSERTE(IsThumbCode(addr) || (addr == (PCODE)NULL)); addr &= ~THUMB_CODE; - if (pExpected != NULL) + if (pExpected != (PCODE)NULL) { _ASSERTE(IsThumbCode(pExpected)); pExpected &= ~THUMB_CODE; diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 40b7f3330a6cce..b0984084685b2f 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -234,7 +234,6 @@ - diff --git a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CompareAttribute.cs b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CompareAttribute.cs index c18c91b3c20321..1f171fab27ccc3 100644 --- a/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CompareAttribute.cs +++ b/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations/CompareAttribute.cs @@ -33,6 +33,8 @@ public override string FormatErrorMessage(string name) => Justification = "The ctor is marked with RequiresUnreferencedCode informing the caller to preserve the other property.")] protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { + ArgumentNullException.ThrowIfNull(validationContext); + var otherPropertyInfo = validationContext.ObjectType.GetRuntimeProperty(OtherProperty); if (otherPropertyInfo == null) { diff --git a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/CompareAttributeTests.cs b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/CompareAttributeTests.cs index 09ad56f6d44b83..445b18993ffd70 100644 --- a/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/CompareAttributeTests.cs +++ b/src/libraries/System.ComponentModel.Annotations/tests/System/ComponentModel/DataAnnotations/CompareAttributeTests.cs @@ -99,6 +99,15 @@ public static void Validate_PropertyHasDisplayName_UpdatesFormatErrorMessageToCo Assert.Contains("CustomDisplayName", newErrorMessage); } + [Fact] + public static void IsValid_ValidationContextNull_ThrowsArgumentNullException() + { + var attribute = new CompareAttribute(nameof(CompareObject.ComparePropertyWithDisplayName)); + var compareObject = new CompareObject("test"); + + Assert.Throws(() => attribute.IsValid(compareObject.CompareProperty)); + } + private class DerivedCompareAttribute : CompareAttribute { public DerivedCompareAttribute(string otherProperty) : base(otherProperty) { } diff --git a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs index abc64cecd51e2e..2db91584c129d9 100644 --- a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs +++ b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs @@ -123,6 +123,8 @@ public static partial class Queryable public static TSource LastOrDefault(this System.Linq.IQueryable source, TSource defaultValue) { throw null; } public static TSource Last(this System.Linq.IQueryable source) { throw null; } public static TSource Last(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } + public static System.Linq.IQueryable LeftJoin(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector) { throw null; } + public static System.Linq.IQueryable LeftJoin(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static long LongCount(this System.Linq.IQueryable source) { throw null; } public static long LongCount(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static TSource? MaxBy(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> keySelector) { throw null; } @@ -146,6 +148,8 @@ public static partial class Queryable public static System.Linq.IOrderedQueryable Order(this System.Linq.IQueryable source, System.Collections.Generic.IComparer comparer) { throw null; } public static System.Linq.IQueryable Prepend(this System.Linq.IQueryable source, TSource element) { throw null; } public static System.Linq.IQueryable Reverse(this System.Linq.IQueryable source) { throw null; } + public static System.Linq.IQueryable RightJoin(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector) { throw null; } + public static System.Linq.IQueryable RightJoin(this System.Linq.IQueryable outer, System.Collections.Generic.IEnumerable inner, System.Linq.Expressions.Expression> outerKeySelector, System.Linq.Expressions.Expression> innerKeySelector, System.Linq.Expressions.Expression> resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static System.Linq.IQueryable SelectMany(this System.Linq.IQueryable source, System.Linq.Expressions.Expression>> selector) { throw null; } public static System.Linq.IQueryable SelectMany(this System.Linq.IQueryable source, System.Linq.Expressions.Expression>> selector) { throw null; } public static System.Linq.IQueryable SelectMany(this System.Linq.IQueryable source, System.Linq.Expressions.Expression>> collectionSelector, System.Linq.Expressions.Expression> resultSelector) { throw null; } diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs index 5914012df622fe..0bfe55333b9f1d 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -242,6 +242,38 @@ public static IQueryable GroupJoin(this outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector), Expression.Constant(comparer, typeof(IEqualityComparer)))); } + [DynamicDependency("LeftJoin`4", typeof(Enumerable))] + public static IQueryable LeftJoin(this IQueryable outer, IEnumerable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) + { + ArgumentNullException.ThrowIfNull(outer); + ArgumentNullException.ThrowIfNull(inner); + ArgumentNullException.ThrowIfNull(outerKeySelector); + ArgumentNullException.ThrowIfNull(innerKeySelector); + ArgumentNullException.ThrowIfNull(resultSelector); + + return outer.Provider.CreateQuery( + Expression.Call( + null, + new Func, IEnumerable, Expression>, Expression>, Expression>, IQueryable>(LeftJoin).Method, + outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector))); + } + + [DynamicDependency("LeftJoin`4", typeof(Enumerable))] + public static IQueryable LeftJoin(this IQueryable outer, IEnumerable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector, IEqualityComparer? comparer) + { + ArgumentNullException.ThrowIfNull(outer); + ArgumentNullException.ThrowIfNull(inner); + ArgumentNullException.ThrowIfNull(outerKeySelector); + ArgumentNullException.ThrowIfNull(innerKeySelector); + ArgumentNullException.ThrowIfNull(resultSelector); + + return outer.Provider.CreateQuery( + Expression.Call( + null, + new Func, IEnumerable, Expression>, Expression>, Expression>, IEqualityComparer, IQueryable>(LeftJoin).Method, + outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector), Expression.Constant(comparer, typeof(IEqualityComparer)))); + } + /// /// Sorts the elements of a sequence in ascending order. /// @@ -440,6 +472,38 @@ public static IOrderedQueryable OrderByDescending(this I source.Expression, Expression.Quote(keySelector), Expression.Constant(comparer, typeof(IComparer)))); } + [DynamicDependency("RightJoin`4", typeof(Enumerable))] + public static IQueryable RightJoin(this IQueryable outer, IEnumerable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector) + { + ArgumentNullException.ThrowIfNull(outer); + ArgumentNullException.ThrowIfNull(inner); + ArgumentNullException.ThrowIfNull(outerKeySelector); + ArgumentNullException.ThrowIfNull(innerKeySelector); + ArgumentNullException.ThrowIfNull(resultSelector); + + return outer.Provider.CreateQuery( + Expression.Call( + null, + new Func, IEnumerable, Expression>, Expression>, Expression>, IQueryable>(RightJoin).Method, + outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector))); + } + + [DynamicDependency("RightJoin`4", typeof(Enumerable))] + public static IQueryable RightJoin(this IQueryable outer, IEnumerable inner, Expression> outerKeySelector, Expression> innerKeySelector, Expression> resultSelector, IEqualityComparer? comparer) + { + ArgumentNullException.ThrowIfNull(outer); + ArgumentNullException.ThrowIfNull(inner); + ArgumentNullException.ThrowIfNull(outerKeySelector); + ArgumentNullException.ThrowIfNull(innerKeySelector); + ArgumentNullException.ThrowIfNull(resultSelector); + + return outer.Provider.CreateQuery( + Expression.Call( + null, + new Func, IEnumerable, Expression>, Expression>, Expression>, IEqualityComparer, IQueryable>(RightJoin).Method, + outer.Expression, GetSourceExpression(inner), Expression.Quote(outerKeySelector), Expression.Quote(innerKeySelector), Expression.Quote(resultSelector), Expression.Constant(comparer, typeof(IEqualityComparer)))); + } + [DynamicDependency("ThenBy`2", typeof(Enumerable))] public static IOrderedQueryable ThenBy(this IOrderedQueryable source, Expression> keySelector) { diff --git a/src/libraries/System.Linq.Queryable/tests/JoinTests.cs b/src/libraries/System.Linq.Queryable/tests/JoinTests.cs index c170867faea414..2677a404871d98 100644 --- a/src/libraries/System.Linq.Queryable/tests/JoinTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/JoinTests.cs @@ -243,8 +243,9 @@ public void ResultSelectorNullNoComparer() [Fact] public void SelectorsReturnNull() { - int?[] inner = { null, null, null }; int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + Assert.Empty(outer.AsQueryable().Join(inner.AsQueryable(), e => e, e => e, (x, y) => x)); } diff --git a/src/libraries/System.Linq.Queryable/tests/LeftJoinTests.cs b/src/libraries/System.Linq.Queryable/tests/LeftJoinTests.cs new file mode 100644 index 00000000000000..9666a1c7c8bb29 --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/LeftJoinTests.cs @@ -0,0 +1,274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq.Expressions; +using Xunit; + +namespace System.Linq.Tests +{ + public class LeftJoinTests : EnumerableBasedTests + { + public struct CustomerRec + { + public string name; + public int custID; + } + + public struct OrderRec + { + public int orderID; + public int custID; + public int total; + } + + public struct AnagramRec + { + public string name; + public int orderID; + public int total; + } + + public struct JoinRec + { + public string name; + public int orderID; + public int total; + } + + [Fact] + public void FirstOuterMatchesLastInnerLastOuterMatchesFirstInnerSameNumberElements() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = { + new OrderRec{ orderID = 45321, custID = 99022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 98022, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = "Prakash", orderID = 95421, total = 9 }, + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Robert", orderID = 45321, total = 50 } + }; + + Assert.Equal(expected, outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.custID, e => e.custID, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void NullComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 }, + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Robert", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, null)); + } + + [Fact] + public void CustomComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 }, + new JoinRec{ name = "Tim", orderID = 43455, total = 10 }, + new JoinRec{ name = "Robert", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNull() + { + IQueryable outer = null; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + IQueryable inner = null; + + AssertExtensions.Throws("inner", () => outer.AsQueryable().LeftJoin(inner, e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterKeySelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), null, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerKeySelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, null, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void ResultSelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (Expression>)null, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNullNoComparer() + { + IQueryable outer = null; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void InnerNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + IQueryable inner = null; + + AssertExtensions.Throws("inner", () => outer.AsQueryable().LeftJoin(inner, e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void OuterKeySelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), null, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void InnerKeySelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, null, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void ResultSelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e.name, e => e.name, (Expression>)null)); + } + + [Fact] + public void SelectorsReturnNull() + { + int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + int?[] expected = { null, null }; + + Assert.Equal(expected, outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e, e => e, (x, y) => x)); + Assert.Equal(expected, outer.AsQueryable().LeftJoin(inner.AsQueryable(), e => e, e => e, (x, y) => y)); + } + + [Fact] + public void Join1() + { + var count = new[] { 0, 1, 2 }.AsQueryable().LeftJoin(new[] { 1, 2, 3 }, n1 => n1, n2 => n2, (n1, n2) => n1 + n2).Count(); + Assert.Equal(3, count); + } + + [Fact] + public void Join2() + { + var count = new[] { 0, 1, 2 }.AsQueryable().LeftJoin(new[] { 1, 2, 3 }, n1 => n1, n2 => n2, (n1, n2) => n1 + n2, EqualityComparer.Default).Count(); + Assert.Equal(3, count); + } + } +} diff --git a/src/libraries/System.Linq.Queryable/tests/RightJoinTests.cs b/src/libraries/System.Linq.Queryable/tests/RightJoinTests.cs new file mode 100644 index 00000000000000..4ca4930a350edf --- /dev/null +++ b/src/libraries/System.Linq.Queryable/tests/RightJoinTests.cs @@ -0,0 +1,273 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using Xunit; + +namespace System.Linq.Tests +{ + public class RightJoinTests : EnumerableBasedTests + { + public struct CustomerRec + { + public string name; + public int custID; + } + + public struct OrderRec + { + public int orderID; + public int custID; + public int total; + } + + public struct AnagramRec + { + public string name; + public int orderID; + public int total; + } + + public struct JoinRec + { + public string name; + public int orderID; + public int total; + } + + [Fact] + public void FirstOuterMatchesLastInnerLastOuterMatchesFirstInnerSameNumberElements() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = { + new OrderRec{ orderID = 45321, custID = 99022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 98022, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = "Robert", orderID = 45321, total = 50 }, + new JoinRec{ name = null, orderID = 43421, total = 20 }, + new JoinRec{ name = "Prakash", orderID = 95421, total = 9 }, + }; + + Assert.Equal(expected, outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.custID, e => e.custID, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void NullComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = null, orderID = 43455, total = 10 }, + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + Assert.Equal(expected, outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, null)); + } + + [Fact] + public void CustomComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = { + new JoinRec{ name = "Tim", orderID = 43455, total = 10 }, + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + Assert.Equal(expected, outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNull() + { + IQueryable outer = null; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + IQueryable inner = null; + + AssertExtensions.Throws("inner", () => outer.AsQueryable().RightJoin(inner, e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterKeySelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), null, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerKeySelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, null, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }, new AnagramEqualityComparer())); + } + + [Fact] + public void ResultSelectorNull() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (Expression>)null, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNullNoComparer() + { + IQueryable outer = null; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void InnerNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + IQueryable inner = null; + + AssertExtensions.Throws("inner", () => outer.AsQueryable().RightJoin(inner, e => e.name, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void OuterKeySelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), null, e => e.name, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void InnerKeySelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, null, (cr, or) => new JoinRec { name = cr.name, orderID = or.orderID, total = or.total })); + } + + [Fact] + public void ResultSelectorNullNoComparer() + { + CustomerRec[] outer = { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e.name, e => e.name, (Expression>)null)); + } + + [Fact] + public void SelectorsReturnNull() + { + int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + int?[] expected = { null, null, null }; + + Assert.Equal(expected, outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e, e => e, (x, y) => x)); + Assert.Equal(expected, outer.AsQueryable().RightJoin(inner.AsQueryable(), e => e, e => e, (x, y) => y)); + } + + [Fact] + public void Join1() + { + var count = new[] { 0, 1, 2 }.AsQueryable().RightJoin(new[] { 1, 2, 3 }, n1 => n1, n2 => n2, (n1, n2) => n1 + n2).Count(); + Assert.Equal(3, count); + } + + [Fact] + public void Join2() + { + var count = new[] { 0, 1, 2 }.AsQueryable().RightJoin(new[] { 1, 2, 3 }, n1 => n1, n2 => n2, (n1, n2) => n1 + n2, EqualityComparer.Default).Count(); + Assert.Equal(3, count); + } + } +} diff --git a/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj b/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj index 7cffc2826f5a87..5d35929945edfd 100644 --- a/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj +++ b/src/libraries/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj @@ -31,6 +31,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index 85ab6fd35d0fd2..20682db0deb76f 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -94,6 +94,8 @@ public static System.Collections.Generic.IEnumerable< public static TSource LastOrDefault(this System.Collections.Generic.IEnumerable source, System.Func predicate, TSource defaultValue) { throw null; } public static TSource Last(this System.Collections.Generic.IEnumerable source) { throw null; } public static TSource Last(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } + public static System.Collections.Generic.IEnumerable LeftJoin(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector) { throw null; } + public static System.Collections.Generic.IEnumerable LeftJoin(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static long LongCount(this System.Collections.Generic.IEnumerable source) { throw null; } public static long LongCount(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static decimal Max(this System.Collections.Generic.IEnumerable source) { throw null; } @@ -160,6 +162,8 @@ public static System.Collections.Generic.IEnumerable< public static System.Collections.Generic.IEnumerable Repeat(TResult element, int count) { throw null; } public static System.Collections.Generic.IEnumerable Reverse(this System.Collections.Generic.IEnumerable source) { throw null; } public static System.Collections.Generic.IEnumerable Reverse(this TSource[] source) { throw null; } + public static System.Collections.Generic.IEnumerable RightJoin(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector) { throw null; } + public static System.Collections.Generic.IEnumerable RightJoin(this System.Collections.Generic.IEnumerable outer, System.Collections.Generic.IEnumerable inner, System.Func outerKeySelector, System.Func innerKeySelector, System.Func resultSelector, System.Collections.Generic.IEqualityComparer? comparer) { throw null; } public static System.Collections.Generic.IEnumerable SelectMany(this System.Collections.Generic.IEnumerable source, System.Func> selector) { throw null; } public static System.Collections.Generic.IEnumerable SelectMany(this System.Collections.Generic.IEnumerable source, System.Func> selector) { throw null; } public static System.Collections.Generic.IEnumerable SelectMany(this System.Collections.Generic.IEnumerable source, System.Func> collectionSelector, System.Func resultSelector) { throw null; } diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 79c10444f489b2..6bea440207f3a2 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -66,6 +66,7 @@ + @@ -77,6 +78,7 @@ + diff --git a/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs b/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs new file mode 100644 index 00000000000000..e114e151e39dd0 --- /dev/null +++ b/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Linq +{ + public static partial class Enumerable + { + public static IEnumerable LeftJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => + LeftJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); + + public static IEnumerable LeftJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) + { + if (outer is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outer); + } + + if (inner is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.inner); + } + + if (outerKeySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outerKeySelector); + } + + if (innerKeySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.innerKeySelector); + } + + if (resultSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + } + + if (IsEmptyArray(outer)) + { + return []; + } + + return LeftJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + } + + private static IEnumerable LeftJoinIterator(IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) + { + using (IEnumerator e = outer.GetEnumerator()) + { + if (e.MoveNext()) + { + Lookup innerLookup = Lookup.CreateForJoin(inner, innerKeySelector, comparer); + do + { + TOuter item = e.Current; + Grouping? g = innerLookup.GetGrouping(outerKeySelector(item), create: false); + if (g is null) + { + yield return resultSelector(item, default); + } + else + { + int count = g._count; + TInner[] elements = g._elements; + for (int i = 0; i != count; ++i) + { + yield return resultSelector(item, elements[i]); + } + } + } + while (e.MoveNext()); + } + } + } + } +} diff --git a/src/libraries/System.Linq/src/System/Linq/RightJoin.cs b/src/libraries/System.Linq/src/System/Linq/RightJoin.cs new file mode 100644 index 00000000000000..8fcf483d89a293 --- /dev/null +++ b/src/libraries/System.Linq/src/System/Linq/RightJoin.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#pragma warning disable CS0162 +#pragma warning disable IDE0060 + +namespace System.Linq +{ + public static partial class Enumerable + { + public static IEnumerable RightJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => + RightJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); + + public static IEnumerable RightJoin(this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) + { + if (outer is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outer); + } + + if (inner is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.inner); + } + + if (outerKeySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.outerKeySelector); + } + + if (innerKeySelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.innerKeySelector); + } + + if (resultSelector is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.resultSelector); + } + + if (IsEmptyArray(inner)) + { + return []; + } + + return RightJoinIterator(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + } + + private static IEnumerable RightJoinIterator(IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) + { + using (IEnumerator e = inner.GetEnumerator()) + { + if (e.MoveNext()) + { + Lookup outerLookup = Lookup.CreateForJoin(outer, outerKeySelector, comparer); + do + { + TInner item = e.Current; + Grouping? g = outerLookup.GetGrouping(innerKeySelector(item), create: false); + if (g is null) + { + yield return resultSelector(default, item); + } + else + { + int count = g._count; + TOuter[] elements = g._elements; + for (int i = 0; i != count; ++i) + { + yield return resultSelector(elements[i], item); + } + } + } + while (e.MoveNext()); + } + } + } + } +} diff --git a/src/libraries/System.Linq/tests/JoinTests.cs b/src/libraries/System.Linq/tests/JoinTests.cs index 496f49a6fcb5a7..a2141426499a68 100644 --- a/src/libraries/System.Linq/tests/JoinTests.cs +++ b/src/libraries/System.Linq/tests/JoinTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using Xunit; @@ -329,8 +328,9 @@ public void SingleElementEachAndDoesntMatch() [Fact] public void SelectorsReturnNull() { - int?[] inner = { null, null, null }; int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + Assert.Empty(outer.Join(inner, e => e, e => e, (x, y) => x)); } diff --git a/src/libraries/System.Linq/tests/LeftJoinTests.cs b/src/libraries/System.Linq/tests/LeftJoinTests.cs new file mode 100644 index 00000000000000..4e3928bfef24fd --- /dev/null +++ b/src/libraries/System.Linq/tests/LeftJoinTests.cs @@ -0,0 +1,450 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.Linq.Tests +{ + public class LeftJoinTests : EnumerableTests + { + public struct CustomerRec + { + public string name; + public int custID; + } + + public struct OrderRec + { + public int orderID; + public int custID; + public int total; + } + + public struct AnagramRec + { + public string name; + public int orderID; + public int total; + } + + public struct JoinRec + { + public string name; + public int orderID; + public int total; + } + + public static JoinRec createJoinRec(CustomerRec cr, OrderRec or) + { + return new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }; + } + + public static JoinRec createJoinRec(CustomerRec cr, AnagramRec or) + { + return new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }; + } + + [Fact] + public void OuterEmptyInnerNonEmpty() + { + CustomerRec[] outer = { }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 97865, custID = 32103, total = 25 } + }; + + Assert.Empty(outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void FirstOuterMatchesLastInnerLastOuterMatchesFirstInnerSameNumberElements() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 99022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 98022, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 95421, total = 9 }, + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Robert", orderID = 45321, total = 50 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void NullComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 }, + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Robert", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec, null)); + } + + [Fact] + public void CustomComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 }, + new JoinRec{ name = "Tim", orderID = 43455, total = 10 }, + new JoinRec{ name = "Robert", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNull() + { + CustomerRec[] outer = null; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = null; + + AssertExtensions.Throws("inner", () => outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterKeySelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.LeftJoin(inner, null, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerKeySelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.LeftJoin(inner, e => e.name, null, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void ResultSelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.LeftJoin(inner, e => e.name, e => e.name, (Func)null, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNullNoComparer() + { + CustomerRec[] outer = null; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec)); + } + + [Fact] + public void InnerNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = null; + + AssertExtensions.Throws("inner", () => outer.LeftJoin(inner, e => e.name, e => e.name, createJoinRec)); + } + + [Fact] + public void OuterKeySelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.LeftJoin(inner, null, e => e.name, createJoinRec)); + } + + [Fact] + public void InnerKeySelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.LeftJoin(inner, e => e.name, null, createJoinRec)); + } + + [Fact] + public void ResultSelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.LeftJoin(inner, e => e.name, e => e.name, (Func)null)); + } + + [Fact] + public void NullElements() + { + string[] outer = { null, string.Empty }; + string[] inner = { null, string.Empty }; + string[] expected = { null, string.Empty }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e, e => e, (x, y) => y, EqualityComparer.Default)); + } + + [Fact] + public void OuterNonEmptyInnerEmpty() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Tim", custID = 43434 }, + new CustomerRec{ name = "Bob", custID = 34093 } + }; + OrderRec[] inner = { }; + JoinRec[] expected = + { + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Bob", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SingleElementEachAndMatches() + { + CustomerRec[] outer = { new CustomerRec { name = "Prakash", custID = 98022 } }; + OrderRec[] inner = { new OrderRec { orderID = 45321, custID = 98022, total = 50 } }; + JoinRec[] expected = { new JoinRec { name = "Prakash", orderID = 45321, total = 50 } }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SingleElementEachAndDoesntMatch() + { + CustomerRec[] outer = { new CustomerRec { name = "Prakash", custID = 98922 } }; + OrderRec[] inner = { new OrderRec { orderID = 45321, custID = 98022, total = 50 } }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SelectorsReturnNull() + { + int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + int?[] expected = { null, null }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e, e => e, (x, y) => x)); + Assert.Equal(expected, outer.LeftJoin(inner, e => e, e => e, (x, y) => y)); + } + + [Fact] + public void InnerSameKeyMoreThanOneElementAndMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 45421, custID = 98022, total = 10 }, + new OrderRec{ orderID = 43421, custID = 99022, total = 20 }, + new OrderRec{ orderID = 85421, custID = 98022, total = 18 }, + new OrderRec{ orderID = 95421, custID = 99021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 45321, total = 50 }, + new JoinRec{ name = "Prakash", orderID = 45421, total = 10 }, + new JoinRec{ name = "Prakash", orderID = 85421, total = 18 }, + new JoinRec{ name = "Tim", orderID = 95421, total = 9 }, + new JoinRec{ name = "Robert", orderID = 43421, total = 20 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void OuterSameKeyMoreThanOneElementAndMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Bob", custID = 99022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 99022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 99021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 45321, total = 50 }, + new JoinRec{ name = "Bob", orderID = 43421, total = 20 }, + new JoinRec{ name = "Tim", orderID = 95421, total = 9 }, + new JoinRec{ name = "Robert", orderID = 43421, total = 20 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void NoMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Bob", custID = 99022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 18022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 39021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 0, total = 0 }, + new JoinRec{ name = "Bob", orderID = 0, total = 0 }, + new JoinRec{ name = "Tim", orderID = 0, total = 0 }, + new JoinRec{ name = "Robert", orderID = 0, total = 0 } + }; + + Assert.Equal(expected, outer.LeftJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void ForcedToEnumeratorDoesntEnumerate() + { + var iterator = NumberRangeGuaranteedNotCollectionType(0, 3).LeftJoin(Enumerable.Empty(), i => i, i => i, (o, i) => i); + // Don't insist on this behaviour, but check it's correct if it happens + var en = iterator as IEnumerator; + Assert.False(en is not null && en.MoveNext()); + } + } +} diff --git a/src/libraries/System.Linq/tests/RightJoinTests.cs b/src/libraries/System.Linq/tests/RightJoinTests.cs new file mode 100644 index 00000000000000..fa8fa8c89b11a5 --- /dev/null +++ b/src/libraries/System.Linq/tests/RightJoinTests.cs @@ -0,0 +1,447 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace System.Linq.Tests +{ + public class RightJoinTests : EnumerableTests + { + public struct CustomerRec + { + public string name; + public int custID; + } + + public struct OrderRec + { + public int orderID; + public int custID; + public int total; + } + + public struct AnagramRec + { + public string name; + public int orderID; + public int total; + } + + public struct JoinRec + { + public string name; + public int orderID; + public int total; + } + + public static JoinRec createJoinRec(CustomerRec cr, OrderRec or) + { + return new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }; + } + + public static JoinRec createJoinRec(CustomerRec cr, AnagramRec or) + { + return new JoinRec { name = cr.name, orderID = or.orderID, total = or.total }; + } + + [Fact] + public void OuterEmptyInnerNonEmpty() + { + CustomerRec[] outer = { }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 97865, custID = 32103, total = 25 } + }; + JoinRec[] expected = + { + new JoinRec{ name = null, orderID = 45321, total = 50 }, + new JoinRec{ name = null, orderID = 97865, total = 25 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void FirstOuterMatchesLastInnerLastOuterMatchesFirstInnerSameNumberElements() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 99022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 98022, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Robert", orderID = 45321, total = 50 }, + new JoinRec{ name = null, orderID = 43421, total = 20 }, + new JoinRec{ name = "Prakash", orderID = 95421, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void NullComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = null, orderID = 43455, total = 10 }, + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec, null)); + } + + [Fact] + public void CustomComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Tim", orderID = 43455, total = 10 }, + new JoinRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNull() + { + CustomerRec[] outer = null; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = null; + + AssertExtensions.Throws("inner", () => outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterKeySelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.RightJoin(inner, null, e => e.name, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void InnerKeySelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.RightJoin(inner, e => e.name, null, createJoinRec, new AnagramEqualityComparer())); + } + + [Fact] + public void ResultSelectorNull() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.RightJoin(inner, e => e.name, e => e.name, (Func)null, new AnagramEqualityComparer())); + } + + [Fact] + public void OuterNullNoComparer() + { + CustomerRec[] outer = null; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outer", () => outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec)); + } + + [Fact] + public void InnerNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = null; + + AssertExtensions.Throws("inner", () => outer.RightJoin(inner, e => e.name, e => e.name, createJoinRec)); + } + + [Fact] + public void OuterKeySelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("outerKeySelector", () => outer.RightJoin(inner, null, e => e.name, createJoinRec)); + } + + [Fact] + public void InnerKeySelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("innerKeySelector", () => outer.RightJoin(inner, e => e.name, null, createJoinRec)); + } + + [Fact] + public void ResultSelectorNullNoComparer() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + AnagramRec[] inner = + { + new AnagramRec{ name = "miT", orderID = 43455, total = 10 }, + new AnagramRec{ name = "Prakash", orderID = 323232, total = 9 } + }; + + AssertExtensions.Throws("resultSelector", () => outer.RightJoin(inner, e => e.name, e => e.name, (Func)null)); + } + + [Fact] + public void NullElements() + { + string[] outer = { null, string.Empty }; + string[] inner = { null, string.Empty }; + string[] expected = { null, string.Empty }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e, e => e, (x, y) => y, EqualityComparer.Default)); + } + + [Fact] + public void OuterNonEmptyInnerEmpty() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Tim", custID = 43434 }, + new CustomerRec{ name = "Bob", custID = 34093 } + }; + OrderRec[] inner = { }; + Assert.Empty(outer.Join(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SingleElementEachAndMatches() + { + CustomerRec[] outer = { new CustomerRec { name = "Prakash", custID = 98022 } }; + OrderRec[] inner = { new OrderRec { orderID = 45321, custID = 98022, total = 50 } }; + JoinRec[] expected = { new JoinRec { name = "Prakash", orderID = 45321, total = 50 } }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SingleElementEachAndDoesntMatch() + { + CustomerRec[] outer = { new CustomerRec { name = "Prakash", custID = 98922 } }; + OrderRec[] inner = { new OrderRec { orderID = 45321, custID = 98022, total = 50 } }; + JoinRec[] expected = + { + new JoinRec{ name = null, orderID = 45321, total = 50 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void SelectorsReturnNull() + { + int?[] outer = { null, null }; + int?[] inner = { null, null, null }; + int?[] expected = { null, null, null }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e, e => e, (x, y) => x)); + Assert.Equal(expected, outer.RightJoin(inner, e => e, e => e, (x, y) => y)); + } + + [Fact] + public void InnerSameKeyMoreThanOneElementAndMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 45421, custID = 98022, total = 10 }, + new OrderRec{ orderID = 43421, custID = 99022, total = 20 }, + new OrderRec{ orderID = 85421, custID = 98022, total = 18 }, + new OrderRec{ orderID = 95421, custID = 99021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 45321, total = 50 }, + new JoinRec{ name = "Prakash", orderID = 45421, total = 10 }, + new JoinRec{ name = "Robert", orderID = 43421, total = 20 }, + new JoinRec{ name = "Prakash", orderID = 85421, total = 18 }, + new JoinRec{ name = "Tim", orderID = 95421, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void OuterSameKeyMoreThanOneElementAndMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Bob", custID = 99022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 98022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 99022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 99021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = "Prakash", orderID = 45321, total = 50 }, + new JoinRec{ name = "Bob", orderID = 43421, total = 20 }, + new JoinRec{ name = "Robert", orderID = 43421, total = 20 }, + new JoinRec{ name = "Tim", orderID = 95421, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void NoMatches() + { + CustomerRec[] outer = + { + new CustomerRec{ name = "Prakash", custID = 98022 }, + new CustomerRec{ name = "Bob", custID = 99022 }, + new CustomerRec{ name = "Tim", custID = 99021 }, + new CustomerRec{ name = "Robert", custID = 99022 } + }; + OrderRec[] inner = + { + new OrderRec{ orderID = 45321, custID = 18022, total = 50 }, + new OrderRec{ orderID = 43421, custID = 29022, total = 20 }, + new OrderRec{ orderID = 95421, custID = 39021, total = 9 } + }; + JoinRec[] expected = + { + new JoinRec{ name = null, orderID = 45321, total = 50 }, + new JoinRec{ name = null, orderID = 43421, total = 20 }, + new JoinRec{ name = null, orderID = 95421, total = 9 } + }; + + Assert.Equal(expected, outer.RightJoin(inner, e => e.custID, e => e.custID, createJoinRec)); + } + + [Fact] + public void ForcedToEnumeratorDoesntEnumerate() + { + var iterator = NumberRangeGuaranteedNotCollectionType(0, 3).RightJoin(Enumerable.Empty(), i => i, i => i, (o, i) => i); + // Don't insist on this behaviour, but check it's correct if it happens + var en = iterator as IEnumerator; + Assert.False(en is not null && en.MoveNext()); + } + } +} diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index 1e4692999a1743..2e1ad5368b276b 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -39,6 +39,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs index 8197ebaa3b03a9..bba0ddb088d462 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/Normalization/StringNormalizationTests.cs @@ -68,7 +68,6 @@ public static IEnumerable NormalizeTestData() } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/110720", TestPlatforms.tvOS | TestPlatforms.iOS | TestPlatforms.MacCatalyst)] [MemberData(nameof(NormalizeTestData))] public void Normalize(string value, NormalizationForm normalizationForm, string expected) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs index 75328f71473c36..d804cca460c2bd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs @@ -553,10 +553,11 @@ public static partial class JsonSerializer { Debug.Assert(listTypeInfo.IsConfigured); - ReadBufferState bufferState = new(listTypeInfo.Options.DefaultBufferSize); ReadStack readStack = default; readStack.Initialize(listTypeInfo, supportContinuation: true); JsonReaderState jsonReaderState = new(readerOptions); + // Note: The ReadBufferState ctor rents pooled buffers. + ReadBufferState bufferState = new(listTypeInfo.Options.DefaultBufferSize); try { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.ReadHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.ReadHelper.cs index bfe0f394d42717..3ea5e94b8ce9cb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.ReadHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.ReadHelper.cs @@ -26,10 +26,11 @@ public partial class JsonTypeInfo { Debug.Assert(IsConfigured); JsonSerializerOptions options = Options; - var bufferState = new ReadBufferState(options.DefaultBufferSize); ReadStack readStack = default; readStack.Initialize(this, supportContinuation: true); var jsonReaderState = new JsonReaderState(options.GetReaderOptions()); + // Note: The ReadBufferState ctor rents pooled buffers. + ReadBufferState bufferState = new(options.DefaultBufferSize); try { @@ -58,10 +59,11 @@ public partial class JsonTypeInfo { Debug.Assert(IsConfigured); JsonSerializerOptions options = Options; - var bufferState = new ReadBufferState(options.DefaultBufferSize); ReadStack readStack = default; readStack.Initialize(this, supportContinuation: true); var jsonReaderState = new JsonReaderState(options.GetReaderOptions()); + // Note: The ReadBufferState ctor rents pooled buffers. + ReadBufferState bufferState = new(options.DefaultBufferSize); try { diff --git a/src/mono/mono/mini/mini-posix.c b/src/mono/mono/mini/mini-posix.c index 429d1dc3744f39..fd8b480e5362dc 100644 --- a/src/mono/mono/mini/mini-posix.c +++ b/src/mono/mono/mini/mini-posix.c @@ -188,7 +188,7 @@ save_old_signal_handler (int signo, struct sigaction *old_action) * * Call the original signal handler for the signal given by the arguments, which * should be the same as for a signal handler. Returns TRUE if the original handler - * was called, false otherwise. + * was called, false otherwise. NOTE: sigaction.sa_handler == SIG_DFL handlers are not considered. */ gboolean MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal) @@ -196,6 +196,7 @@ MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal) int signal = MONO_SIG_HANDLER_GET_SIGNO (); struct sigaction *saved_handler = (struct sigaction *)get_saved_signal_handler (signal); + // Ignores chaining to default signal handlers i.e. when saved_handler->sa_handler == SIG_DFL if (saved_handler && saved_handler->sa_handler) { if (!(saved_handler->sa_flags & SA_SIGINFO)) { saved_handler->sa_handler (signal); @@ -209,6 +210,27 @@ MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal) return FALSE; } + +/* + * mono_chain_signal_to_default_sigsegv_handler: + * + * Call the original SIGSEGV signal handler in cases when the original handler is + * sigaction.sa_handler == SIG_DFL. This is used to propagate the crash to the OS. + */ +void +mono_chain_signal_to_default_sigsegv_handler (void) +{ + struct sigaction *saved_handler = (struct sigaction *)get_saved_signal_handler (SIGSEGV); + + if (saved_handler && saved_handler->sa_handler == SIG_DFL) { + sigaction (SIGSEGV, saved_handler, NULL); + raise (SIGSEGV); + } else { + g_async_safe_printf ("\nFailed to chain SIGSEGV signal to the default handler.\n"); + } +} + + MONO_SIG_HANDLER_FUNC (static, sigabrt_signal_handler) { MonoJitInfo *ji = NULL; @@ -348,8 +370,14 @@ add_signal_handler (int signo, MonoSignalHandler handler, int flags) /* if there was already a handler in place for this signal, store it */ if (! (previous_sa.sa_flags & SA_SIGINFO) && - (SIG_DFL == previous_sa.sa_handler)) { - /* it there is no sa_sigaction function and the sa_handler is default, we can safely ignore this */ + (SIG_DFL == previous_sa.sa_handler) && signo != SIGSEGV) { + /* + * If there is no sa_sigaction function and the sa_handler is default, + * it means the currently registered handler for this signal is the default one. + * For signal chaining, we need to store the default SIGSEGV handler so that the crash + * is properly propagated, while default handlers for other signals are ignored and + * are not considered. + */ } else { if (mono_do_signal_chaining) save_old_signal_handler (signo, &previous_sa); diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index f431897becd001..bcd7754da660a6 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -3908,7 +3908,8 @@ MONO_SIG_HANDLER_FUNC (, mono_sigsegv_signal_handler) mono_handle_native_crash (mono_get_signame (SIGSEGV), &mctx, (MONO_SIG_HANDLER_INFO_TYPE*)info); if (mono_do_crash_chaining) { - mono_chain_signal (MONO_SIG_HANDLER_PARAMS); + if (!mono_chain_signal (MONO_SIG_HANDLER_PARAMS)) + mono_chain_signal_to_default_sigsegv_handler (); return; } } @@ -3918,7 +3919,8 @@ MONO_SIG_HANDLER_FUNC (, mono_sigsegv_signal_handler) } else { mono_handle_native_crash (mono_get_signame (SIGSEGV), &mctx, (MONO_SIG_HANDLER_INFO_TYPE*)info); if (mono_do_crash_chaining) { - mono_chain_signal (MONO_SIG_HANDLER_PARAMS); + if (!mono_chain_signal (MONO_SIG_HANDLER_PARAMS)) + mono_chain_signal_to_default_sigsegv_handler (); return; } } diff --git a/src/mono/mono/mini/mini-runtime.h b/src/mono/mono/mini/mini-runtime.h index 5104a053b30c77..7779909bd944e2 100644 --- a/src/mono/mono/mini/mini-runtime.h +++ b/src/mono/mono/mini/mini-runtime.h @@ -678,6 +678,7 @@ void MONO_SIG_HANDLER_SIGNATURE (mono_sigsegv_signal_handler); void MONO_SIG_HANDLER_SIGNATURE (mono_sigint_signal_handler) ; void MONO_SIG_HANDLER_SIGNATURE (mono_sigterm_signal_handler) ; gboolean MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal); +void mono_chain_signal_to_default_sigsegv_handler (void); #if defined (HOST_WASM) diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c index b4c1aaa1729b93..4a9eb21b137adb 100644 --- a/src/mono/mono/mini/mini-wasm.c +++ b/src/mono/mono/mini/mini-wasm.c @@ -587,6 +587,12 @@ MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal) return FALSE; } +void +mono_chain_signal_to_default_sigsegv_handler (void) +{ + g_error ("mono_chain_signal_to_default_sigsegv_handler not supported on WASM"); +} + gboolean mono_thread_state_init_from_handle (MonoThreadUnwindState *tctx, MonoThreadInfo *info, void *sigctx) { diff --git a/src/mono/mono/mini/mini-windows.c b/src/mono/mono/mini/mini-windows.c index 322488abdaab76..cb79586c98a5ee 100644 --- a/src/mono/mono/mini/mini-windows.c +++ b/src/mono/mono/mini/mini-windows.c @@ -252,6 +252,12 @@ MONO_SIG_HANDLER_SIGNATURE (mono_chain_signal) return TRUE; } +void +mono_chain_signal_to_default_sigsegv_handler (void) +{ + g_error ("mono_chain_signal_to_default_sigsegv_handler not supported on Windows"); +} + #if !HAVE_EXTERN_DEFINED_NATIVE_CRASH_HANDLER #ifndef MONO_CROSS_COMPILE void diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net9.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net9.Manifest/WorkloadManifest.json.in index cef3fc23f871ca..0897896dd5ad9f 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net9.Manifest/WorkloadManifest.json.in +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net9.Manifest/WorkloadManifest.json.in @@ -35,7 +35,7 @@ "extends": [ "microsoft-net-runtime-mono-tooling-net9" ], "platforms": [ "win-x64", "win-arm64", "linux-x64", "linux-arm64", "linux-musl-x64", "linux-musl-arm64", "osx-x64", "osx-arm64" ] }, - "mobile-librarybuilder-net9.0": { + "mobile-librarybuilder-net9": { "description": "Mobile SDK for building a self-contained .NET native library in net9.0", "packs": [ "Microsoft.NET.Runtime.LibraryBuilder.Sdk.net9" diff --git a/src/native/external/zlib-ng.cmake b/src/native/external/zlib-ng.cmake index ed86660ea70e81..b2338d682e1459 100644 --- a/src/native/external/zlib-ng.cmake +++ b/src/native/external/zlib-ng.cmake @@ -15,6 +15,10 @@ set(Z_PREFIX ON) set(WITH_RVV OFF) # We don't support ARMv6 and the check works incorrectly when compiling for ARMv7 w/ Thumb instruction set set(WITH_ARMV6 OFF) +# The checks for NEON_AVAILABLE and NEON_HAS_LD4 work incorrectly when compiling for arm32. +if(CLR_CMAKE_TARGET_ARCH_ARM AND CLR_CMAKE_TARGET_LINUX) + set(WITH_NEON OFF) +endif() if (CLR_CMAKE_TARGET_BROWSER OR CLR_CMAKE_TARGET_WASI) # 'aligned_alloc' is not available in browser/wasi, yet it is set by zlib-ng/CMakeLists.txt. diff --git a/src/native/libs/System.Globalization.Native/pal_normalization.m b/src/native/libs/System.Globalization.Native/pal_normalization.m index 4dac9969a27dc8..282c267c384ac4 100644 --- a/src/native/libs/System.Globalization.Native/pal_normalization.m +++ b/src/native/libs/System.Globalization.Native/pal_normalization.m @@ -75,16 +75,23 @@ int32_t GlobalizationNative_NormalizeStringNative(NormalizationForm normalizatio return 0; } - int32_t index = 0, dstIdx = 0, isError = 0; + // Calling with empty or null destination buffer to get the required buffer size. + if (lpDst == NULL || cwDstLength == 0) + { + return (int32_t)[normalizedString length]; + } + + ResultCode isError = Success; + int32_t index = 0, dstIdx = 0; uint16_t dstCodepoint; - while ((NSUInteger)index < normalizedString.length) + while ((NSUInteger)index < normalizedString.length && isError == Success) { dstCodepoint = [normalizedString characterAtIndex: (NSUInteger)index]; Append(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); index++; } - return !isError ? (int32_t)[normalizedString length] : 0; + return (isError == Success || isError == InsufficientBuffer) ? (int32_t)[normalizedString length] : 0; } } #endif diff --git a/src/native/libs/System.IO.Compression.Native/CMakeLists.txt b/src/native/libs/System.IO.Compression.Native/CMakeLists.txt index 0734e8a3112b6d..2f8b16276783f6 100644 --- a/src/native/libs/System.IO.Compression.Native/CMakeLists.txt +++ b/src/native/libs/System.IO.Compression.Native/CMakeLists.txt @@ -7,6 +7,10 @@ if (NOT CLR_CMAKE_USE_SYSTEM_ZLIB) endif() if (CLR_CMAKE_USE_SYSTEM_BROTLI) + find_library(BROTLIDEC brotlidec REQUIRED) + find_library(BROTLIENC brotlienc REQUIRED) + list(APPEND ${BROTLI_LIBRARIES} ${BROTLIDEC} ${BROTLIENC}) + if (CLR_CMAKE_HOST_FREEBSD) set(CMAKE_REQUIRED_INCLUDES ${CROSS_ROOTFS}/usr/local/include) endif() diff --git a/src/native/libs/System.IO.Compression.Native/extra_libs.cmake b/src/native/libs/System.IO.Compression.Native/extra_libs.cmake index f10684f561bf70..03b40533e2109d 100644 --- a/src/native/libs/System.IO.Compression.Native/extra_libs.cmake +++ b/src/native/libs/System.IO.Compression.Native/extra_libs.cmake @@ -19,4 +19,11 @@ macro(append_extra_compression_libs NativeLibsExtra) list(APPEND ZLIB_LIBRARIES $,z,zlib>) endif () list(APPEND ${NativeLibsExtra} ${ZLIB_LIBRARIES}) + + if (CLR_CMAKE_USE_SYSTEM_BROTLI) + find_library(BROTLIDEC brotlidec REQUIRED) + find_library(BROTLIENC brotlienc REQUIRED) + + list(APPEND ${NativeLibsExtra} ${BROTLIDEC} ${BROTLIENC}) + endif () endmacro()