From 5bcbc4b5efda67bc06c012001157f4bf1d533242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 3 Sep 2020 16:36:16 +0200 Subject: [PATCH 01/34] Add Native AOT smoke test --- .../templates/runtimes/send-to-helix-step.yml | 1 + src/coreclr/tests/runtest.cmd | 5 + src/coreclr/tests/runtest.py | 11 + .../tests/src/CLRTest.Execute.Batch.targets | 3 + src/coreclr/tests/src/CLRTest.Execute.targets | 1 + .../tests/src/CLRTest.NativeAot.targets | 116 +++++ .../tests/src/Common/Directory.Build.targets | 8 + src/coreclr/tests/src/dirs.proj | 6 +- .../SmokeTests/Delegates/Delegates.cs | 472 ++++++++++++++++++ .../SmokeTests/Delegates/Delegates.csproj | 11 + 10 files changed, 631 insertions(+), 3 deletions(-) create mode 100644 src/coreclr/tests/src/CLRTest.NativeAot.targets create mode 100644 src/tests/nativeaot/SmokeTests/Delegates/Delegates.cs create mode 100644 src/tests/nativeaot/SmokeTests/Delegates/Delegates.csproj diff --git a/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml b/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml index 3602929653dc..6ab482d307f5 100644 --- a/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml +++ b/eng/pipelines/common/templates/runtimes/send-to-helix-step.yml @@ -51,6 +51,7 @@ steps: _HelixType: ${{ parameters.helixType }} _RunCrossGen: ${{ parameters.runCrossGen }} _RunCrossGen2: ${{ parameters.runCrossGen2 }} + _RunNativeAot: true _CompositeBuildMode: ${{ parameters.compositeBuildMode }} _RunInUnloadableContext: ${{ parameters.runInUnloadableContext }} _LongRunningGcTests: ${{ parameters.longRunningGcTests }} diff --git a/src/coreclr/tests/runtest.cmd b/src/coreclr/tests/runtest.cmd index 1d8037063d56..1f3644010d42 100644 --- a/src/coreclr/tests/runtest.cmd +++ b/src/coreclr/tests/runtest.cmd @@ -72,6 +72,7 @@ if /i "%1" == "ilasmroundtrip" (set __IlasmRoundTrip=1& if /i "%1" == "printlastresultsonly" (set __PrintLastResultsOnly=1&shift&goto Arg_Loop) if /i "%1" == "runcrossgentests" (set RunCrossGen=true&shift&goto Arg_Loop) if /i "%1" == "runcrossgen2tests" (set RunCrossGen2=true&shift&goto Arg_Loop) +if /i "%1" == "runnativeaottests" (set RunNativeAot=true&shift&goto Arg_Loop) REM This test feature is currently intentionally undocumented if /i "%1" == "runlargeversionbubblecrossgentests" (set RunCrossGen=true&set CrossgenLargeVersionBubble=true&shift&goto Arg_Loop) if /i "%1" == "runlargeversionbubblecrossgen2tests" (set RunCrossGen2=true&set CrossgenLargeVersionBubble=true&shift&goto Arg_Loop) @@ -157,6 +158,10 @@ if defined RunCrossGen2 ( set __RuntestPyArgs=%__RuntestPyArgs% --run_crossgen2_tests ) +if defined RunNativeAot ( + set __RuntestPyArgs=%__RuntestPyArgs% --run_nativeaot_tests +) + if defined __DoCrossgen ( set __RuntestPyArgs=%__RuntestPyArgs% --precompile_core_root ) diff --git a/src/coreclr/tests/runtest.py b/src/coreclr/tests/runtest.py index 5914b332f8b8..39cf263c2db5 100755 --- a/src/coreclr/tests/runtest.py +++ b/src/coreclr/tests/runtest.py @@ -121,6 +121,7 @@ parser.add_argument("--ilasmroundtrip", dest="ilasmroundtrip", action="store_true", default=False) parser.add_argument("--run_crossgen_tests", dest="run_crossgen_tests", action="store_true", default=False) parser.add_argument("--run_crossgen2_tests", dest="run_crossgen2_tests", action="store_true", default=False) +parser.add_argument("--run_nativeaot_tests", dest="run_nativeaot_tests", action="store_true", default=False) parser.add_argument("--large_version_bubble", dest="large_version_bubble", action="store_true", default=False) parser.add_argument("--precompile_core_root", dest="precompile_core_root", action="store_true", default=False) parser.add_argument("--skip_test_run", dest="skip_test_run", action="store_true", default=False, help="Does not run tests. Useful in conjunction with --precompile_core_root") @@ -907,6 +908,11 @@ def run_tests(args, print("Setting RunCrossGen2=true") os.environ["RunCrossGen2"] = "true" + if args.run_nativeaot_tests: + print("Running tests Native AOT") + print("Setting RunNativeAot=true") + os.environ["RunNativeAot"] = "true" + if args.large_version_bubble: print("Large Version Bubble enabled") os.environ["LargeVersionBubble"] = "true" @@ -1080,6 +1086,11 @@ def setup_args(args): lambda unused: True, "Error setting run_crossgen2_tests") + coreclr_setup_args.verify(args, + "run_nativeaot_tests", + lambda unused: True, + "Error setting run_nativeaot_tests") + coreclr_setup_args.verify(args, "precompile_core_root", lambda arg: True, diff --git a/src/coreclr/tests/src/CLRTest.Execute.Batch.targets b/src/coreclr/tests/src/CLRTest.Execute.Batch.targets index 3c800a7ba5eb..c7814fb342b5 100644 --- a/src/coreclr/tests/src/CLRTest.Execute.Batch.targets +++ b/src/coreclr/tests/src/CLRTest.Execute.Batch.targets @@ -297,6 +297,9 @@ IF NOT "%CLRCustomTestLauncher%"=="" ( ) ELSE ( set LAUNCHER=%_DebuggerFullPath% $(_CLRTestRunFile) ) +IF NOT "%RunNativeAot%"=="" ( + set LAUNCHER=%_DebuggerFullPath% +) $(BatchIlrtTestLaunchCmds) diff --git a/src/coreclr/tests/src/CLRTest.Execute.targets b/src/coreclr/tests/src/CLRTest.Execute.targets index 50050e5ee416..5c2ddae6193f 100644 --- a/src/coreclr/tests/src/CLRTest.Execute.targets +++ b/src/coreclr/tests/src/CLRTest.Execute.targets @@ -69,6 +69,7 @@ This file contains the logic for providing Execution Script generation. + diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets new file mode 100644 index 000000000000..8e531945a856 --- /dev/null +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -0,0 +1,116 @@ + + + + $(BashScriptSnippetGen);GetNativeAotBashScript + $(BatchScriptSnippetGen);GetNativeAotBatchScript + + + + + + + + + + + $(BashCLRTestPreCommands);$(NativeAotBashScript) + + + + + + + + + + $(CLRTestBatchPreCommands);$(NativeAotBatchScript) + + + + + + <_NativeAotBuildProjectFile> + + + + $(MSBuildProjectName) + .dll + Exe + %24(MSBuildProjectDirectory)\ + %24(MSBuildProjectDirectory)\ + true + + + + + + + + + + + + + + ]]> + + + + + + + + \ No newline at end of file diff --git a/src/coreclr/tests/src/Common/Directory.Build.targets b/src/coreclr/tests/src/Common/Directory.Build.targets index 65ff6bd27594..712e46f64632 100644 --- a/src/coreclr/tests/src/Common/Directory.Build.targets +++ b/src/coreclr/tests/src/Common/Directory.Build.targets @@ -86,6 +86,14 @@ TargetDir="%(RunTimeArtifactsIncludeFolders.Identity)" /> + + + + + + + + - - - + + + + From 64775ee613861c03c851b121f0e26233119ae4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 7 Sep 2020 15:03:48 +0200 Subject: [PATCH 08/34] Do not require building CoreCLR CoreLib --- src/libraries/restore/runtime/runtime.depproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/restore/runtime/runtime.depproj b/src/libraries/restore/runtime/runtime.depproj index 792cb8f840a2..710d0a65f13d 100644 --- a/src/libraries/restore/runtime/runtime.depproj +++ b/src/libraries/restore/runtime/runtime.depproj @@ -2,7 +2,7 @@ $(PackageRID) $(NoWarn);NU1603;NU1605 - true + true false true false From a93001e2f6cab6241464213c2e51ab9a405b252c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 7 Sep 2020 15:04:54 +0200 Subject: [PATCH 09/34] Do not run xunit with corerun --- src/coreclr/tests/tests.targets | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/tests/tests.targets b/src/coreclr/tests/tests.targets index 1637017dd85e..c9531ba5144b 100644 --- a/src/coreclr/tests/tests.targets +++ b/src/coreclr/tests/tests.targets @@ -52,9 +52,7 @@ $(XunitArgs) -nocolor - $(CORE_ROOT)\corerun - $(CORE_ROOT)\corerun.exe - $(DotnetRoot)/dotnet + $(DotnetRoot)/dotnet true @@ -38,7 +42,7 @@ - + PreserveNewest false false @@ -49,6 +53,19 @@ false false + + + PreserveNewest + false + false + + + + $(LibraryNamePrefix)clrjitilc$(LibraryNameExtension) + PreserveNewest + false + false + From 71338af780559458c2a1c874e795de52e962c9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 7 Sep 2020 15:49:30 +0200 Subject: [PATCH 11/34] Fix up build script --- src/coreclr/tests/src/CLRTest.NativeAot.targets | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets index 42fb99670767..1e22f891cadb 100644 --- a/src/coreclr/tests/src/CLRTest.NativeAot.targets +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -60,8 +60,10 @@ if defined RunNativeAot ( call !__Command! if errorlevel 1 ( - set TestExitCode=!ERRORLEVEL! - exit /b !TestExitCode! + ECHO END COMPILATION - FAILED + ECHO FAILED + rem TODO: Why doesn't "exit /b" end with failure? Crossgen uses that above, but it results in success here!? + exit 1 ) set ExePath=native\$(MSBuildProjectName) From 4b7a62db5ef7b21333bfb24d066614a846e6df00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 7 Sep 2020 15:49:49 +0200 Subject: [PATCH 12/34] Ok let's see --- eng/pipelines/runtimelab.yml | 89 +------------------ .../runtimelab-post-build-steps.yml | 53 +---------- 2 files changed, 6 insertions(+), 136 deletions(-) diff --git a/eng/pipelines/runtimelab.yml b/eng/pipelines/runtimelab.yml index 8e8149421ed8..679d13956269 100644 --- a/eng/pipelines/runtimelab.yml +++ b/eng/pipelines/runtimelab.yml @@ -65,7 +65,7 @@ jobs: jobParameters: timeoutInMinutes: 90 testGroup: innerloop - buildArgs: -s clr+libs+installer -c debug -runtimeConfiguration Checked + buildArgs: -s nativeaot+libs+installer -c debug -runtimeConfiguration Checked extraStepsTemplate: /eng/pipelines/runtimelab/runtimelab-post-build-steps.yml # @@ -81,7 +81,7 @@ jobs: jobParameters: timeoutInMinutes: 90 testGroup: innerloop - buildArgs: -s clr+libs+libs.tests+installer -c $(_BuildConfig) /p:ArchiveTests=true + buildArgs: -s nativeaot+libs+installer -c $(_BuildConfig) /p:ArchiveTests=true extraStepsTemplate: /eng/pipelines/runtimelab/runtimelab-post-build-steps.yml extraStepsParameters: uploadTests: true @@ -100,88 +100,3 @@ jobs: nameSuffix: All_Configurations buildArgs: -s clr.runtime+libs -c $(_BuildConfig) -allConfigurations -# -# CoreCLR Test builds using live libraries release build -# -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/build-test-job.yml - buildConfig: Checked - platforms: - - CoreClrTestBuildHost # Either OSX_x64 or Linux_x64 - jobParameters: - testGroup: innerloop - liveLibrariesBuildConfig: Release - dependsOn: - - build_Linux_x64_Checked_ - - build_Linux_x64_Release_ - -# -# CoreCLR Test executions using live libraries -# -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/run-test-job.yml - buildConfig: Checked - platforms: - - Linux_x64 - helixQueueGroup: pr - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: innerloop - liveLibrariesBuildConfig: Release - dependsOn: - - coreclr_common_test_build_p0_AnyOS_AnyCPU_Checked - -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/templates/runtimes/run-test-job.yml - buildConfig: Checked - platforms: - - Windows_NT_x64 - helixQueueGroup: pr - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - jobParameters: - testGroup: innerloop - liveLibrariesBuildConfig: Release - dependsOn: - - coreclr_common_test_build_p0_AnyOS_AnyCPU_Checked - - build_Windows_NT_x64_Checked_ - - build_Windows_NT_x64_Release_ - -# -# Libraries Release Test Execution against a release coreclr runtime -# -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - Linux_x64 - helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml - jobParameters: - isFullMatrix: false - isOfficialBuild: false - testScope: innerloop - liveRuntimeBuildConfig: Release - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - dependsOn: - - build_Linux_x64_Release_ - -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/libraries/run-test-job.yml - buildConfig: Release - platforms: - - Windows_NT_x64 - helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml - jobParameters: - isFullMatrix: false - isOfficialBuild: false - testScope: innerloop - liveRuntimeBuildConfig: Release - dependsOnTestBuildConfiguration: Release - dependsOnTestArchitecture: x64 - dependsOn: - - build_Windows_NT_x64_Release_ diff --git a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml index e5fbcba7bc95..97b25575155f 100644 --- a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml +++ b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml @@ -7,53 +7,8 @@ parameters: steps: # Build coreclr native test output - - script: $(Build.SourcesDirectory)/src/coreclr/build-test$(scriptExt) skipstressdependencies skipmanaged skipgeneratelayout $(buildConfigUpper) ${{ parameters.archType }} - displayName: Build native test components + - script: $(Build.SourcesDirectory)/src/coreclr/build-test$(scriptExt) skipstressdependencies $(buildConfigUpper) ${{ parameters.archType }} + displayName: Build tests - # Copy all build output into artifacts staging directory - - template: /eng/pipelines/libraries/prepare-for-bin-publish.yml - - # Zip CoreCLR Build Output - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin/coreclr/${{ parameters.osGroup }}.${{ parameters.archType }}.$(buildConfigUpper) - archiveType: $(archiveType) - tarCompression: $(tarCompression) - includeRootFolder: false - archiveExtension: $(archiveExtension) - artifactName: CoreCLRProduct__${{ parameters.osGroup }}${{ parameters.osSubgroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }} - displayName: 'CoreCLR product build' - - # Zip Test Build - - ${{ if eq(parameters.uploadTests, true) }}: - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/helix - includeRootFolder: true - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: libraries_test_assets_${{ parameters.osGroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }} - displayName: Test Assets - - # Zip product native assets for use by Tests - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/tests/coreclr/obj/${{ parameters.osGroup }}.${{ parameters.archType }}.$(buildConfigUpper) - includeRootFolder: false - archiveType: $(archiveType) - tarCompression: $(tarCompression) - archiveExtension: $(archiveExtension) - artifactName: CoreCLRNativeTestArtifacts_${{ parameters.osGroup }}${{ parameters.osSubgroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }} - displayName: 'native test components' - - # Zip Libraries Build Output - - template: /eng/pipelines/common/upload-artifact-step.yml - parameters: - rootFolder: $(Build.ArtifactStagingDirectory)/artifacts - archiveType: $(archiveType) - tarCompression: $(tarCompression) - includeRootFolder: false - archiveExtension: $(archiveExtension) - artifactName: libraries_bin_${{ parameters.osGroup }}${{ parameters.osSubgroup }}_${{ parameters.archType }}_${{ parameters.buildConfig }} - displayName: Build Assets + - script: $(Build.SourcesDirectory)/src/coreclr/tests/runtest$(scriptExt) runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} + displayName: Run tests From 44b8b4d9eeb63fe188dda3db777e75d349d04deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 7 Sep 2020 16:37:19 +0200 Subject: [PATCH 13/34] Maybe get linux support --- src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj index 5276c48878b4..56b46cf9bbe5 100644 --- a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj +++ b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj @@ -54,14 +54,14 @@ false - + PreserveNewest false false - - $(LibraryNamePrefix)clrjitilc$(LibraryNameExtension) + + clrjitilc$(LibraryNameExtension) PreserveNewest false false From b6407802fe6e1a679c9bf163f7aa5b629a2abe40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 8 Sep 2020 10:13:09 +0200 Subject: [PATCH 14/34] Revert "Maybe get linux support" This reverts commit 44b8b4d9eeb63fe188dda3db777e75d349d04deb. --- src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj index 56b46cf9bbe5..5276c48878b4 100644 --- a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj +++ b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj @@ -54,14 +54,14 @@ false - + PreserveNewest false false - - clrjitilc$(LibraryNameExtension) + + $(LibraryNamePrefix)clrjitilc$(LibraryNameExtension) PreserveNewest false false From a236229217b90caac103d83bc69995a3c17d21fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 8 Sep 2020 10:13:23 +0200 Subject: [PATCH 15/34] Revert "Build accelerator" This reverts commit 6cec9baa4d920d482c0735888d84d6e5a3ae9e2a. --- src/coreclr/CMakeLists.txt | 2 +- src/coreclr/src/CMakeLists.txt | 37 +++++++++++----------- src/coreclr/src/vm/eventing/CMakeLists.txt | 2 +- src/libraries/Directory.Build.props | 1 - 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 2333844418e2..e58c23e478a1 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -119,7 +119,7 @@ include_directories("../../artifacts/obj/coreclr") if(FEATURE_STANDALONE_GC) add_definitions(-DFEATURE_STANDALONE_GC) - #add_subdirectory(src/gc) + add_subdirectory(src/gc) endif(FEATURE_STANDALONE_GC) if (CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index c47616e4b0e6..e269657e5add 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -12,11 +12,11 @@ if(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) include_directories("${GENERATED_INCLUDE_DIR}/etw") endif(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) -#add_subdirectory(debug/dbgutil) +add_subdirectory(debug/dbgutil) if(CLR_CMAKE_HOST_UNIX) if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) -# add_subdirectory(debug/createdump) + add_subdirectory(debug/createdump) endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) # Include the dummy c++ include files @@ -55,29 +55,28 @@ if(CLR_CMAKE_HOST_UNIX) endfunction() -# add_subdirectory(nativeresources) + add_subdirectory(nativeresources) endif(CLR_CMAKE_HOST_UNIX) add_subdirectory(utilcode) add_subdirectory(gcinfo) add_subdirectory(jit) -include_directories("vm") -add_subdirectory(vm/eventing) -#add_subdirectory(md) -#add_subdirectory(debug) -#add_subdirectory(inc) -#add_subdirectory(binder) -#add_subdirectory(classlibnative) -#add_subdirectory(dlls) -#add_subdirectory(ToolBox) -#add_subdirectory(tools) -#add_subdirectory(unwinder) -#add_subdirectory(ildasm) -#add_subdirectory(ilasm) -#add_subdirectory(interop) +add_subdirectory(vm) +add_subdirectory(md) +add_subdirectory(debug) +add_subdirectory(inc) +add_subdirectory(binder) +add_subdirectory(classlibnative) +add_subdirectory(dlls) +add_subdirectory(ToolBox) +add_subdirectory(tools) +add_subdirectory(unwinder) +add_subdirectory(ildasm) +add_subdirectory(ilasm) +add_subdirectory(interop) if(CLR_CMAKE_HOST_UNIX) -# add_subdirectory(palrt) + add_subdirectory(palrt) elseif(CLR_CMAKE_HOST_WIN32) -# add_subdirectory(hosts) + add_subdirectory(hosts) endif(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/vm/eventing/CMakeLists.txt b/src/coreclr/src/vm/eventing/CMakeLists.txt index 06691da577ce..e2bf024fc59f 100644 --- a/src/coreclr/src/vm/eventing/CMakeLists.txt +++ b/src/coreclr/src/vm/eventing/CMakeLists.txt @@ -34,7 +34,7 @@ set_source_files_properties(${EventingHeaders} PROPERTIES GENERATED TRUE) add_dependencies(eventing_headers eventprovider) -#add_subdirectory(eventpipe) +add_subdirectory(eventpipe) if(CLR_CMAKE_HOST_WIN32) add_subdirectory(EtwProvider) diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 9be36b9aca69..21141ccaa4bf 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -99,7 +99,6 @@ - From 806b3797f623c440fa2da31e057f84181ad34129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 8 Sep 2020 15:42:02 +0200 Subject: [PATCH 16/34] Add more tests --- .../tests/src/CLRTest.NativeAot.targets | 2 +- .../BasicThreading/BasicThreading.cs | 630 +++++ .../BasicThreading/BasicThreading.csproj | 11 + .../SmokeTests/Delegates/Delegates.cs | 472 ---- .../SmokeTests/Exceptions/Exceptions.cs | 202 ++ .../SmokeTests/Exceptions/Exceptions.csproj | 11 + .../nativeaot/SmokeTests/Generics/Generics.cs | 2492 +++++++++++++++++ .../Generics.csproj} | 2 +- .../SmokeTests/Interfaces/Interfaces.cs | 395 +++ .../SmokeTests/Interfaces/Interfaces.csproj | 11 + .../SmokeTests/Threading/Threading.cs | 1612 +++++++++++ .../SmokeTests/Threading/Threading.csproj | 11 + 12 files changed, 5377 insertions(+), 474 deletions(-) create mode 100644 src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.cs create mode 100644 src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.csproj delete mode 100644 src/tests/nativeaot/SmokeTests/Delegates/Delegates.cs create mode 100644 src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.cs create mode 100644 src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.csproj create mode 100644 src/tests/nativeaot/SmokeTests/Generics/Generics.cs rename src/tests/nativeaot/SmokeTests/{Delegates/Delegates.csproj => Generics/Generics.csproj} (87%) create mode 100644 src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.cs create mode 100644 src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.csproj create mode 100644 src/tests/nativeaot/SmokeTests/Threading/Threading.cs create mode 100644 src/tests/nativeaot/SmokeTests/Threading/Threading.csproj diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets index 1e22f891cadb..fc7bdcc6403a 100644 --- a/src/coreclr/tests/src/CLRTest.NativeAot.targets +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -98,7 +98,7 @@ if defined RunNativeAot ( - + diff --git a/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.cs b/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.cs new file mode 100644 index 000000000000..5f1891639623 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.cs @@ -0,0 +1,630 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +class Program +{ + public const int Pass = 100; + public const int Fail = -1; + + static int Main() + { + SimpleReadWriteThreadStaticTest.Run(42, "SimpleReadWriteThreadStatic"); + + ThreadStaticsTestWithTasks.Run(); + + if (ThreadTest.Run() != Pass) + return Fail; + + if (TimerTest.Run() != Pass) + return Fail; + + if (FinalizeTest.Run() != Pass) + return Fail; + + return Pass; + } +} + +class FinalizeTest +{ + public static bool visited = false; + public class Dummy + { + ~Dummy() + { + FinalizeTest.visited = true; + } + } + + public static int Run() + { + int iterationCount = 0; + while (!visited && iterationCount++ < 10000) + { + GC.KeepAlive(new Dummy()); + GC.Collect(); + } + + if (visited) + { + Console.WriteLine("FinalizeTest passed"); + return Program.Pass; + } + else + { + Console.WriteLine("FinalizeTest failed"); + return Program.Fail; + } + } +} + +class SimpleReadWriteThreadStaticTest +{ + public static void Run(int intValue, string stringValue) + { + NonGenericReadWriteThreadStaticsTest(intValue, "NonGeneric" + stringValue); + GenericReadWriteThreadStaticsTest(intValue + 1, "Generic" + stringValue); + } + + class NonGenericType + { + [ThreadStatic] + public static int IntValue; + + [ThreadStatic] + public static string StringValue; + } + + class GenericType + { + [ThreadStatic] + public static T ValueT; + + [ThreadStatic] + public static V ValueV; + } + + static void NonGenericReadWriteThreadStaticsTest(int intValue, string stringValue) + { + NonGenericType.IntValue = intValue; + NonGenericType.StringValue = stringValue; + + if (NonGenericType.IntValue != intValue) + { + throw new Exception("SimpleReadWriteThreadStaticsTest: wrong integer value: " + NonGenericType.IntValue.ToString()); + } + + if (NonGenericType.StringValue != stringValue) + { + throw new Exception("SimpleReadWriteThreadStaticsTest: wrong string value: " + NonGenericType.StringValue); + } + } + + static void GenericReadWriteThreadStaticsTest(int intValue, string stringValue) + { + GenericType.ValueT = intValue; + GenericType.ValueV = stringValue; + + if (GenericType.ValueT != intValue) + { + throw new Exception("GenericReadWriteThreadStaticsTest1a: wrong integer value: " + GenericType.ValueT.ToString()); + } + + if (GenericType.ValueV != stringValue) + { + throw new Exception("GenericReadWriteThreadStaticsTest1b: wrong string value: " + GenericType.ValueV); + } + + intValue++; + GenericType.ValueT = intValue; + GenericType.ValueV = intValue + 1; + + if (GenericType.ValueT != intValue) + { + throw new Exception("GenericReadWriteThreadStaticsTest2a: wrong integer value: " + GenericType.ValueT.ToString()); + } + + if (GenericType.ValueV != (intValue + 1)) + { + throw new Exception("GenericReadWriteThreadStaticsTest2b: wrong integer value: " + GenericType.ValueV.ToString()); + } + + GenericType.ValueT = stringValue + "a"; + GenericType.ValueV = stringValue + "b"; + + if (GenericType.ValueT != (stringValue + "a")) + { + throw new Exception("GenericReadWriteThreadStaticsTest3a: wrong string value: " + GenericType.ValueT); + } + + if (GenericType.ValueV != (stringValue + "b")) + { + throw new Exception("GenericReadWriteThreadStaticsTest3b: wrong string value: " + GenericType.ValueV); + } + } +} + +class ThreadStaticsTestWithTasks +{ + static object lockObject = new object(); + const int TotalTaskCount = 32; + + public static void Run() + { + Task[] tasks = new Task[TotalTaskCount]; + for (int i = 0; i < tasks.Length; ++i) + { + tasks[i] = Task.Factory.StartNew((param) => + { + int index = (int)param; + int intTestValue = index * 10; + string stringTestValue = "ThreadStaticsTestWithTasks" + index; + + // Try to run the on every other task + if ((index % 2) == 0) + { + lock (lockObject) + { + SimpleReadWriteThreadStaticTest.Run(intTestValue, stringTestValue); + } + } + else + { + SimpleReadWriteThreadStaticTest.Run(intTestValue, stringTestValue); + } + }, i); + } + for (int i = 0; i < tasks.Length; ++i) + { + tasks[i].Wait(); + } + } +} + +class ThreadTest +{ + private static readonly List s_startedThreads = new List(); + + private static int s_passed; + private static int s_failed; + + private static void Expect(bool condition, string message) + { + if (condition) + { + Interlocked.Increment(ref s_passed); + } + else + { + Interlocked.Increment(ref s_failed); + Console.WriteLine("ERROR: " + message); + } + } + + private static void ExpectException(Action action, string message) + { + Exception ex = null; + try + { + action(); + } + catch (Exception e) + { + ex = e; + } + + if (!(ex is T)) + { + message += string.Format(" (caught {0})", (ex == null) ? "no exception" : ex.GetType().Name); + } + Expect(ex is T, message); + } + + private static void ExpectPassed(string testName, int expectedPassed) + { + // Wait for all started threads to finish execution + foreach (Thread t in s_startedThreads) + { + t.Join(); + } + + s_startedThreads.Clear(); + + Expect(s_passed == expectedPassed, string.Format("{0}: Expected s_passed == {1}, got {2}", testName, expectedPassed, s_passed)); + s_passed = 0; + } + + private static void TestStartMethod() + { + // Case 1: new Thread(ThreadStart).Start() + var t1 = new Thread(() => Expect(true, "Expected t1 to start")); + t1.Start(); + s_startedThreads.Add(t1); + + // Case 2: new Thread(ThreadStart).Start(parameter) + var t2 = new Thread(() => Expect(false, "This thread must not be started")); + // InvalidOperationException: The thread was created with a ThreadStart delegate that does not accept a parameter. + ExpectException(() => t2.Start(null), "Expected InvalidOperationException for t2.Start()"); + + // Case 3: new Thread(ParameterizedThreadStart).Start() + var t3 = new Thread(obj => Expect(obj == null, "Expected obj == null")); + t3.Start(); + s_startedThreads.Add(t3); + + // Case 4: new Thread(ParameterizedThreadStart).Start(parameter) + var t4 = new Thread(obj => Expect((int)obj == 42, "Expected (int)obj == 42")); + t4.Start(42); + s_startedThreads.Add(t4); + + // Start an unstarted resurrected thread. + // CoreCLR: ThreadStateException, CoreRT: no exception. + Thread unstartedResurrected = Resurrector.CreateUnstartedResurrected(); + unstartedResurrected.Start(); + s_startedThreads.Add(unstartedResurrected); + + // Threads cannot started more than once + t1.Join(); + ExpectException(() => t1.Start(), "Expected ThreadStateException for t1.Start()"); + + ExpectException(() => Thread.CurrentThread.Start(), + "Expected ThreadStateException for CurrentThread.Start()"); + + Thread stoppedResurrected = Resurrector.CreateStoppedResurrected(); + ExpectException(() => stoppedResurrected.Start(), + "Expected ThreadStateException for stoppedResurrected.Start()"); + + ExpectPassed(nameof(TestStartMethod), 7); + } + + private static void TestJoinMethod() + { + var t = new Thread(() => { }); + ExpectException(() => t.Start(null), "Expected InvalidOperationException for t.Start()"); + ExpectException(() => t.Join(), "Expected ThreadStateException for t.Join()"); + + Thread stoppedResurrected = Resurrector.CreateStoppedResurrected(); + Expect(stoppedResurrected.Join(1), "Expected stoppedResurrected.Join(1) to return true"); + + Expect(!Thread.CurrentThread.Join(1), "Expected CurrentThread.Join(1) to return false"); + + ExpectPassed(nameof(TestJoinMethod), 4); + } + + private static void TestCurrentThreadProperty() + { + Thread t = null; + t = new Thread(() => Expect(Thread.CurrentThread == t, "Expected CurrentThread == t on thread t")); + t.Start(); + s_startedThreads.Add(t); + + Expect(Thread.CurrentThread != t, "Expected CurrentThread != t on main thread"); + + ExpectPassed(nameof(TestCurrentThreadProperty), 2); + } + + private static void TestNameProperty() + { + var t = new Thread(() => { }); + + t.Name = null; + // It is OK to set the null Name multiple times + t.Name = null; + Expect(t.Name == null, "Expected t.Name == null"); + + const string ThreadName = "My thread"; + t.Name = ThreadName; + Expect(t.Name == ThreadName, string.Format("Expected t.Name == \"{0}\"", ThreadName)); + ExpectException(() => { t.Name = null; }, + "Expected InvalidOperationException setting Thread.Name back to null"); + + ExpectPassed(nameof(TestNameProperty), 3); + } + + private static void TestIsBackgroundProperty() + { + // Thread created using Thread.Start + var t_event = new AutoResetEvent(false); + var t = new Thread(() => t_event.WaitOne()); + + t.Start(); + s_startedThreads.Add(t); + + Expect(!t.IsBackground, "Expected t.IsBackground == false"); + t_event.Set(); + t.Join(); + ExpectException(() => Console.WriteLine(t.IsBackground), + "Expected ThreadStateException for t.IsBackground"); + + // Thread pool thread + Task.Factory.StartNew(() => Expect(Thread.CurrentThread.IsBackground, "Expected IsBackground == true")).Wait(); + + // Resurrected threads + Thread unstartedResurrected = Resurrector.CreateUnstartedResurrected(); + Expect(unstartedResurrected.IsBackground == false, "Expected unstartedResurrected.IsBackground == false"); + + Thread stoppedResurrected = Resurrector.CreateStoppedResurrected(); + ExpectException(() => Console.WriteLine(stoppedResurrected.IsBackground), + "Expected ThreadStateException for stoppedResurrected.IsBackground"); + + // Main thread + Expect(!Thread.CurrentThread.IsBackground, "Expected CurrentThread.IsBackground == false"); + + ExpectPassed(nameof(TestIsBackgroundProperty), 6); + } + + private static void TestIsThreadPoolThreadProperty() + { +#if false // The IsThreadPoolThread property is not in the contract version we compile against at present + var t = new Thread(() => { }); + + Expect(!t.IsThreadPoolThread, "Expected t.IsThreadPoolThread == false"); + Task.Factory.StartNew(() => Expect(Thread.CurrentThread.IsThreadPoolThread, "Expected IsThreadPoolThread == true")).Wait(); + Expect(!Thread.CurrentThread.IsThreadPoolThread, "Expected CurrentThread.IsThreadPoolThread == false"); + + ExpectPassed(nameof(TestIsThreadPoolThreadProperty), 3); +#endif + } + + private static void TestManagedThreadIdProperty() + { + int t_id = 0; + var t = new Thread(() => { + Expect(Thread.CurrentThread.ManagedThreadId == t_id, "Expected CurrentTread.ManagedThreadId == t_id on thread t"); + Expect(Environment.CurrentManagedThreadId == t_id, "Expected Environment.CurrentManagedThreadId == t_id on thread t"); + }); + + t_id = t.ManagedThreadId; + Expect(t_id != 0, "Expected t_id != 0"); + Expect(Thread.CurrentThread.ManagedThreadId != t_id, "Expected CurrentTread.ManagedThreadId != t_id on main thread"); + Expect(Environment.CurrentManagedThreadId != t_id, "Expected Environment.CurrentManagedThreadId != t_id on main thread"); + + t.Start(); + s_startedThreads.Add(t); + + // Resurrected threads + Thread unstartedResurrected = Resurrector.CreateUnstartedResurrected(); + Expect(unstartedResurrected.ManagedThreadId != 0, "Expected unstartedResurrected.ManagedThreadId != 0"); + + Thread stoppedResurrected = Resurrector.CreateStoppedResurrected(); + Expect(stoppedResurrected.ManagedThreadId != 0, "Expected stoppedResurrected.ManagedThreadId != 0"); + + ExpectPassed(nameof(TestManagedThreadIdProperty), 7); + } + + private static void TestThreadStateProperty() + { + var t_event = new AutoResetEvent(false); + var t = new Thread(() => t_event.WaitOne()); + + Expect(t.ThreadState == ThreadState.Unstarted, "Expected t.ThreadState == ThreadState.Unstarted"); + t.Start(); + s_startedThreads.Add(t); + + Expect(t.ThreadState == ThreadState.Running || t.ThreadState == ThreadState.WaitSleepJoin, + "Expected t.ThreadState is either ThreadState.Running or ThreadState.WaitSleepJoin"); + t_event.Set(); + t.Join(); + Expect(t.ThreadState == ThreadState.Stopped, "Expected t.ThreadState == ThreadState.Stopped"); + + // Resurrected threads + Thread unstartedResurrected = Resurrector.CreateUnstartedResurrected(); + Expect(unstartedResurrected.ThreadState == ThreadState.Unstarted, + "Expected unstartedResurrected.ThreadState == ThreadState.Unstarted"); + + Thread stoppedResurrected = Resurrector.CreateStoppedResurrected(); + Expect(stoppedResurrected.ThreadState == ThreadState.Stopped, + "Expected stoppedResurrected.ThreadState == ThreadState.Stopped"); + + ExpectPassed(nameof(TestThreadStateProperty), 5); + } + + private static unsafe void DoStackAlloc(int size) + { + byte* buffer = stackalloc byte[size]; + Volatile.Write(ref buffer[0], 0); + } + + private static void TestMaxStackSize() + { +#if false // The constructors with maxStackSize are not in the contract version we compile against at present + // Allocate a 3 MiB buffer on the 4 MiB stack + var t = new Thread(() => DoStackAlloc(3 << 20), 4 << 20); + t.Start(); + s_startedThreads.Add(t); +#endif + + ExpectPassed(nameof(TestMaxStackSize), 0); + } + + static int s_startedThreadCount = 0; + private static void TestStartShutdown() + { + Thread[] threads = new Thread[2048]; + + // Creating a large number of threads + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(() => { Interlocked.Increment(ref s_startedThreadCount); }); + threads[i].Start(); + } + + // Wait for all threads to shutdown; + for (int i = 0; i < threads.Length; i++) + { + threads[i].Join(); + } + + Expect(s_startedThreadCount == threads.Length, + String.Format("Not all threads completed. Expected: {0}, Actual: {1}", threads.Length, s_startedThreadCount)); + } + + public static int Run() + { + TestStartMethod(); + TestJoinMethod(); + + TestCurrentThreadProperty(); + TestNameProperty(); + TestIsBackgroundProperty(); + TestIsThreadPoolThreadProperty(); + TestManagedThreadIdProperty(); + TestThreadStateProperty(); + + TestMaxStackSize(); + TestStartShutdown(); + + return (s_failed == 0) ? Program.Pass : Program.Fail; + } + + /// + /// Creates resurrected Thread objects. + /// + class Resurrector + { + static Thread s_unstartedResurrected; + static Thread s_stoppedResurrected; + + bool _unstarted; + Thread _thread = new Thread(() => { }); + + Resurrector(bool unstarted) + { + _unstarted = unstarted; + if (!unstarted) + { + _thread.Start(); + _thread.Join(); + } + } + + ~Resurrector() + { + if (_unstarted && (s_unstartedResurrected == null)) + { + s_unstartedResurrected = _thread; + } + else if(!_unstarted && (s_stoppedResurrected == null)) + { + s_stoppedResurrected = _thread; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CreateInstance(bool unstarted) + { + GC.KeepAlive(new Resurrector(unstarted)); + } + + static Thread CreateResurrectedThread(ref Thread trap, bool unstarted) + { + trap = null; + + while (trap == null) + { + // Call twice to override the address of the first allocation on the stack (for conservative GC) + CreateInstance(unstarted); + CreateInstance(unstarted); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + // We would like to get a Thread object with its internal SafeHandle member disposed. + // The current implementation of SafeHandle postpones disposing until the next garbage + // collection. For this reason we do a couple more collections. + for (int i = 0; i < 2; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + return trap; + } + + public static Thread CreateUnstartedResurrected() + { + return CreateResurrectedThread(ref s_unstartedResurrected, unstarted: true); + } + + public static Thread CreateStoppedResurrected() + { + return CreateResurrectedThread(ref s_stoppedResurrected, unstarted: false); + } + } +} + +class TimerTest +{ + private static AutoResetEvent s_event; + private static Timer s_timer; + private static volatile int s_periodicTimerCount; + + public static int Run() + { + s_event = new AutoResetEvent(false); + s_timer = new Timer(TimerCallback, null, 200, Timeout.Infinite); + + bool timerFired = s_event.WaitOne(TimeSpan.FromSeconds(5)); + if (!timerFired) + { + Console.WriteLine("The timer test failed: timer has not fired."); + return Program.Fail; + } + + // Change the timer to a very long value + s_event.Reset(); + s_timer.Change(3000000, Timeout.Infinite); + timerFired = s_event.WaitOne(500); + if (timerFired) + { + Console.WriteLine("The timer test failed: timer fired earlier than expected."); + return Program.Fail; + } + + // Try change existing timer to a small value and make sure it fires + s_event.Reset(); + s_timer.Change(200, Timeout.Infinite); + timerFired = s_event.WaitOne(TimeSpan.FromSeconds(5)); + if (!timerFired) + { + Console.WriteLine("The timer test failed: failed to change the existing timer."); + return Program.Fail; + } + + // Test a periodic timer + s_periodicTimerCount = 0; + s_event.Reset(); + s_timer = new Timer(PeriodicTimerCallback, null, 200, 20); + while (s_periodicTimerCount < 3) + { + timerFired = s_event.WaitOne(TimeSpan.FromSeconds(5)); + if (!timerFired) + { + Console.WriteLine("The timer test failed: the periodic timer has not fired."); + return Program.Fail; + } + } + + // Stop the periodic timer + s_timer.Change(Timeout.Infinite, Timeout.Infinite); + + return Program.Pass; + } + + private static void TimerCallback(object state) + { + s_event.Set(); + } + + private static void PeriodicTimerCallback(object state) + { + Interlocked.Increment(ref s_periodicTimerCount); + s_event.Set(); + } +} diff --git a/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.csproj b/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.csproj new file mode 100644 index 000000000000..5d426f9735d3 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/BasicThreading/BasicThreading.csproj @@ -0,0 +1,11 @@ + + + Exe + BuildAndRun + 0 + true + + + + + diff --git a/src/tests/nativeaot/SmokeTests/Delegates/Delegates.cs b/src/tests/nativeaot/SmokeTests/Delegates/Delegates.cs deleted file mode 100644 index bc04e57a168b..000000000000 --- a/src/tests/nativeaot/SmokeTests/Delegates/Delegates.cs +++ /dev/null @@ -1,472 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Linq.Expressions; - -using Pointer = System.Reflection.Pointer; - -public class BringUpTests -{ - const int Pass = 100; - const int Fail = -1; - - public static int Main() - { - int result = Pass; - - if (!TestValueTypeDelegates()) - { - Console.WriteLine("Failed"); - result = Fail; - } - - if (!TestVirtualDelegates()) - { - Console.WriteLine("Failed"); - result = Fail; - } - - if (!TestInterfaceDelegates()) - { - Console.WriteLine("Failed"); - result = Fail; - } - - if (!TestStaticOpenClosedDelegates()) - { - Console.WriteLine("Failed"); - result = Fail; - } - - if (!TestMulticastDelegates()) - { - Console.WriteLine("Failed"); - result = Fail; - } - - if (!TestDynamicInvoke()) - { - Console.WriteLine("Failed"); - result = Fail; - } - -#if !CODEGEN_CPP - TestLinqExpressions.Run(); -#endif - - return result; - } - - public static bool TestValueTypeDelegates() - { - Console.Write("Testing delegates to value types..."); - - { - TestValueType t = new TestValueType { X = 123 }; - Func d = t.GiveX; - string result = d("MyPrefix"); - if (result != "MyPrefix123") - return false; - } - - { - object t = new TestValueType { X = 456 }; - Func d = t.ToString; - string result = d(); - if (result != "456") - return false; - } - - { - Func d = TestValueType.MakeValueType; - TestValueType result = d(789); - if (result.X != 789) - return false; - } - - Console.WriteLine("OK"); - return true; - } - - public static bool TestVirtualDelegates() - { - Console.Write("Testing delegates to virtual methods..."); - - { - Mid t = new Mid(); - if (t.GetBaseDo()() != "Base") - return false; - - if (t.GetDerivedDo()() != "Mid") - return false; - } - - { - Mid t = new Derived(); - if (t.GetBaseDo()() != "Base") - return false; - - if (t.GetDerivedDo()() != "Derived") - return false; - } - - { - // This will end up being a delegate to a sealed virtual method. - ClassWithIFoo t = new ClassWithIFoo("Class"); - Func d = t.DoFoo; - if (d(987) != "Class987") - return false; - } - - Console.WriteLine("OK"); - return true; - } - - public static bool TestInterfaceDelegates() - { - Console.Write("Testing delegates to interface methods..."); - - { - IFoo t = new ClassWithIFoo("Class"); - Func d = t.DoFoo; - if (d(987) != "Class987") - return false; - } - - { - IFoo t = new StructWithIFoo("Struct"); - Func d = t.DoFoo; - if (d(654) != "Struct654") - return false; - } - - Console.WriteLine("OK"); - return true; - } - - public static bool TestStaticOpenClosedDelegates() - { - Console.Write("Testing static open and closed delegates..."); - - { - Func d = ExtensionClass.Combine; - if (d("Hello", "World") != "HelloWorld") - return false; - } - - { - Func d = "Hi".Combine; - if (d("There") != "HiThere") - return false; - } - - Console.WriteLine("OK"); - return true; - } - - public static bool TestMulticastDelegates() - { - Console.Write("Testing multicast delegates..."); - - { - ClassThatMutates t = new ClassThatMutates(); - - Action d = t.AddOne; - d(); - - if (t.State != 1) - return false; - t.State = 0; - - d += t.AddTwo; - d(); - - if (t.State != 3) - return false; - t.State = 0; - - d += t.AddOne; - d(); - - if (t.State != 4) - return false; - } - - Console.WriteLine("OK"); - return true; - } - - public static bool TestDynamicInvoke() - { - Console.Write("Testing dynamic invoke..."); - - { - TestValueType t = new TestValueType { X = 123 }; - Func d = t.GiveX; - string result = (string)d.DynamicInvoke(new object[] { "MyPrefix" }); - if (result != "MyPrefix123") - return false; - } - - { - Func d = TestValueType.MakeValueType; - TestValueType result = (TestValueType)d.DynamicInvoke(new object[] { 789 }); - if (result.X != 789) - return false; - } - - { - IFoo t = new ClassWithIFoo("Class"); - Func d = t.DoFoo; - if ((string)d.DynamicInvoke(new object[] { 987 }) != "Class987") - return false; - } - - { - IFoo t = new StructWithIFoo("Struct"); - Func d = t.DoFoo; - if ((string)d.DynamicInvoke(new object[] { 654 }) != "Struct654") - return false; - } - - { - Func d = ExtensionClass.Combine; - if ((string)d.DynamicInvoke(new object[] { "Hello", "World" }) != "HelloWorld") - return false; - } - - { - Func d = "Hi".Combine; - if ((string)d.DynamicInvoke(new object[] { "There" }) != "HiThere") - return false; - } - - { - Mutate d = ClassWithByRefs.Mutate; - object[] args = new object[] { 8 }; - d.DynamicInvoke(args); - if ((int)args[0] != 50) - return false; - } - - { - Mutate d = ClassWithByRefs.Mutate; - object[] args = new object[] { "Hello" }; - d.DynamicInvoke(args); - if ((string)args[0] != "HelloMutated") - return false; - } - - unsafe - { - GetAndReturnPointerDelegate d = ClassWithPointers.GetAndReturnPointer; - if (Pointer.Unbox(d.DynamicInvoke(new object[] { (IntPtr)8 })) != (void*)50) - return false; - - if (Pointer.Unbox(d.DynamicInvoke(new object[] { Pointer.Box((void*)9, typeof(void*)) })) != (void*)51) - return false; - } - -#if false - // This is hitting an EH bug around throw/rethrow from a catch block (pass is not set properly) - unsafe - { - PassPointerByRefDelegate d = ClassWithPointers.PassPointerByRef; - var args = new object[] { (IntPtr)8 }; - - bool caught = false; - try - { - d.DynamicInvoke(args); - } - catch (ArgumentException) - { - caught = true; - } - - if (!caught) - return false; - } -#endif - - Console.WriteLine("OK"); - return true; - } - - struct TestValueType - { - public int X; - - public string GiveX(string prefix) - { - return prefix + X.ToString(); - } - - public static TestValueType MakeValueType(int value) - { - return new TestValueType { X = value }; - } - - public override string ToString() - { - return X.ToString(); - } - } - - class Base - { - public virtual string Do() - { - return "Base"; - } - } - - class Mid : Base - { - public override string Do() - { - return "Mid"; - } - - public Func GetBaseDo() - { - return base.Do; - } - - public Func GetDerivedDo() - { - return Do; - } - } - - class Derived : Mid - { - public override string Do() - { - return "Derived"; - } - } - - interface IFoo - { - string DoFoo(int x); - } - - class ClassWithIFoo : IFoo - { - string _prefix; - - public ClassWithIFoo(string prefix) - { - _prefix = prefix; - } - - public string DoFoo(int x) - { - return _prefix + x.ToString(); - } - } - - struct StructWithIFoo : IFoo - { - string _prefix; - - public StructWithIFoo(string prefix) - { - _prefix = prefix; - } - - public string DoFoo(int x) - { - return _prefix + x.ToString(); - } - } - - class ClassThatMutates - { - public int State; - - public void AddOne() - { - State++; - } - - public void AddTwo() - { - State += 2; - } - } -} - -static class ExtensionClass -{ - public static string Combine(this string s1, string s2) - { - return s1 + s2; - } -} - -unsafe delegate byte* GetAndReturnPointerDelegate(void* ptr); -unsafe delegate void PassPointerByRefDelegate(ref void* ptr); - -unsafe static class ClassWithPointers -{ - public static byte* GetAndReturnPointer(void* ptr) - { - return (byte*)ptr + 42; - } - - public static void PassPointerByRef(ref void* ptr) - { - ptr = (byte*)ptr + 42; - } -} - -delegate void Mutate(ref T x); - -class ClassWithByRefs -{ - public static void Mutate(ref int x) - { - x += 42; - } - - public static void Mutate(ref string x) - { - x += "Mutated"; - } -} - -class TestLinqExpressions -{ - public static void ModifyByRefAndThrow(ref int i) - { - i = 123; - throw new Exception(); - } - - delegate void RefIntDelegate(ref int i); - - public static void Run() - { - Console.WriteLine("Testing LINQ Expressions..."); - - { - ParameterExpression pX = Expression.Parameter(typeof(int).MakeByRefType()); - RefIntDelegate del = - Expression.Lambda( - Expression.Call(null, typeof(TestLinqExpressions).GetMethod(nameof(ModifyByRefAndThrow)), pX), pX).Compile(); - - int i = 0; - try - { - del(ref i); - } - catch (Exception) { } - - if (i != 123) - throw new Exception(); - } - } -} diff --git a/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.cs b/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.cs new file mode 100644 index 000000000000..ea8bc3f271e4 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.cs @@ -0,0 +1,202 @@ +// 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.Text; + +public class BringUpTest +{ + const int Pass = 100; + const int Fail = -1; + + volatile int myField; + volatile Object myObjectField; + + public BringUpTest() + { + myField = 1; + } + + static BringUpTest g = null; + + static int finallyCounter = 0; + + public static int Main() + { + if (string.Empty.Length > 0) + { + // Just something to make sure we generate reflection metadata for the type + new BringUpTest().ToString(); + } + + int counter = 0; + + try + { + try + { + throw new Exception("My exception"); + } + catch (OutOfMemoryException) + { + Console.WriteLine("Unexpected exception caught"); + return Fail; + } + } + catch (Exception e) + { + Console.WriteLine("Exception caught!"); + if (e.Message != "My exception") + { + Console.WriteLine("Unexpected exception message!"); + return Fail; + } + + string stackTrace = e.StackTrace; + if (!stackTrace.Contains("BringUpTest.Main")) + { + Console.WriteLine("Unexpected stack trace: " + stackTrace); + return Fail; + } + counter++; + } + + try + { + g.myObjectField = new Object(); + } + catch (NullReferenceException) + { + Console.WriteLine("Null reference exception in write barrier caught!"); + counter++; + } + + try + { + try + { + g.myField++; + } + finally + { + counter++; + } + } + catch (NullReferenceException) + { + Console.WriteLine("Null reference exception caught!"); + counter++; + } + + try + { + throw new Exception("Testing filter"); + } + catch (Exception e) when (e.Message == "Testing filter" && counter++ > 0) + { + Console.WriteLine("Exception caught via filter!"); + if (e.Message != "Testing filter") + { + Console.WriteLine("Unexpected exception message!"); + return Fail; + } + counter++; + } + + // test interaction of filters and finally clauses with GC + try + { + ThrowExcThroughMethodsWithFinalizers1("Main"); + } + catch (Exception e) when (FilterWithGC() && counter++ > 0) + { + Console.WriteLine(e.Message); + if (e.Message != "ThrowExcThroughMethodsWithFinalizers2") + { + Console.WriteLine("Unexpected exception message!"); + return Fail; + } + if (finallyCounter != 2) + { + Console.WriteLine("Finalizers didn't execute!"); + return Fail; + } + counter++; + } + + try + { + try + { + throw new Exception("Hello"); + } + catch + { + counter++; + throw; + } + } + catch (Exception ex) + { + if (ex.Message != "Hello") + return Fail; + counter++; + } + + if (counter != 10) + { + Console.WriteLine("Unexpected counter value"); + return Fail; + } + + return Pass; + } + static void CreateSomeGarbage() + { + for (int i = 0; i < 100; i++) + { + string s = new string('.', 100); + } + } + + static void ThrowExcThroughMethodsWithFinalizers1(string caller) + { + CreateSomeGarbage(); + string s = caller + " + ThrowExcThroughMethodsWithFinalizers1"; + CreateSomeGarbage(); + try + { + ThrowExcThroughMethodsWithFinalizers2(s); + } + finally + { + Console.WriteLine("Executing finally in {0}", s); + finallyCounter++; + } + } + + static void ThrowExcThroughMethodsWithFinalizers2(string caller) + { + CreateSomeGarbage(); + string s = caller + " + ThrowExcThroughMethodsWithFinalizers2"; + CreateSomeGarbage(); + try + { + throw new Exception("ThrowExcThroughMethodsWithFinalizers2"); + } + finally + { + Console.WriteLine("Executing finally in {0}", s); + finallyCounter++; + } + } + + static bool FilterWithGC() + { + CreateSomeGarbage(); + GC.Collect(); + CreateSomeGarbage(); + return true; + } +} + diff --git a/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.csproj b/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.csproj new file mode 100644 index 000000000000..0b1a23bf56a1 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Exceptions/Exceptions.csproj @@ -0,0 +1,11 @@ + + + Exe + BuildAndRun + 0 + true + + + + + diff --git a/src/tests/nativeaot/SmokeTests/Generics/Generics.cs b/src/tests/nativeaot/SmokeTests/Generics/Generics.cs new file mode 100644 index 000000000000..9a774fe89a7e --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Generics/Generics.cs @@ -0,0 +1,2492 @@ +// 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.Reflection; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +#if CODEGEN_WASM +using System.Runtime.InteropServices; +using Console=Program.Console; +#endif + +class Program +{ + static int Main() + { + TestDictionaryDependencyTracking.Run(); + TestStaticBaseLookups.Run(); + TestInitThisClass.Run(); + TestDelegateFatFunctionPointers.Run(); + TestDelegateToCanonMethods.Run(); + TestVirtualMethodUseTracking.Run(); + TestSlotsInHierarchy.Run(); + TestDelegateVirtualMethod.Run(); + TestDelegateInterfaceMethod.Run(); + TestThreadStaticFieldAccess.Run(); + TestConstrainedMethodCalls.Run(); + TestInstantiatingUnboxingStubs.Run(); + TestNameManglingCollisionRegression.Run(); + TestSimpleGVMScenarios.Run(); + TestGvmDelegates.Run(); + TestGvmDependencies.Run(); + TestInterfaceVTableTracking.Run(); + TestClassVTableTracking.Run(); + TestReflectionInvoke.Run(); + TestFieldAccess.Run(); + TestDevirtualization.Run(); + TestGenericInlining.Run(); +#if !CODEGEN_CPP + TestNullableCasting.Run(); + TestVariantCasting.Run(); + TestMDArrayAddressMethod.Run(); + TestNativeLayoutGeneration.Run(); + TestByRefLikeVTables.Run(); +#endif + return 100; + } + + /// + /// Tests that we properly track dictionary dependencies of generic methods. + /// (Getting this wrong is a linker failure.) + /// + class TestDictionaryDependencyTracking + { + static object Gen1() + { + return MakeArray>(); + } + + static object MakeArray() + { + return new T[0]; + } + + class Gen + { + public object Frob() + { + return new ValueGen(); + } + + public object Futz() + { + return Gen1>(); + } + } + + struct ValueGen + { + } + + class ClassGen + { + } + + public static void Run() + { + new Gen().Frob(); + new Gen().Futz(); + } + } + + /// + /// Tests static base access. + /// + class TestStaticBaseLookups + { + class C1 { } + class C2 { } + class C3 { } + + class GenHolder + { + public static int IntField; + public static string StringField; + } + + class GenAccessor + { + public static string Read() + { + return GenHolder.IntField.ToString() + GenHolder.StringField; + } + + public static void SetSimple(int i, string s) + { + GenHolder.IntField = i; + GenHolder.StringField = s; + } + + public static void SetComplex(int i, string s) + { + GenHolder.IntField = i; + GenHolder.StringField = s; + GenHolder.IntField = i + 1; + GenHolder.StringField = s + "`"; + } + } + + public static void Run() + { + GenAccessor.SetComplex(42, "Hello"); + GenAccessor.SetSimple(85, "World"); + + if (GenAccessor.Read() != "42Hello") + throw new Exception(); + + if (GenHolder.IntField != 43 || GenHolder.StringField != "Hello`") + throw new Exception(); + + if (GenAccessor.Read() != "85World") + throw new Exception(); + } + } + + /// + /// Tests that we can use a delegate that points to a generic method. + /// + class TestDelegateFatFunctionPointers + { + struct SmallStruct + { + public int X; + } + + struct MediumStruct + { + public int X, Y, Z, W; + } + + unsafe struct BigStruct + { + public const int Length = 128; + public fixed byte Bytes[Length]; + } + + T Generic(object o) where T : class + { + Func f = OtherGeneric; + return f(o); + } + + T OtherGeneric(object o) where T : class + { + return o as T; + } + + delegate void VoidGenericDelegate(ref T x, T val); + void VoidGeneric(ref T x, T val) + { + x = val; + } + + SmallStruct SmallStructGeneric(SmallStruct x) + { + return x; + } + + MediumStruct MediumStructGeneric(MediumStruct x) + { + return x; + } + + BigStruct BigStructGeneric(BigStruct x) + { + return x; + } + + public static void Run() + { + var o = new TestDelegateFatFunctionPointers(); + + string hw = "Hello World"; + string roundtrip = o.Generic(hw); + if (roundtrip != hw) + throw new Exception(); + + { + VoidGenericDelegate f = o.VoidGeneric; + object obj = new object(); + object location = null; + f(ref location, obj); + if (location != obj) + throw new Exception(); + } + + { + Func f = o.SmallStructGeneric; + SmallStruct x = new SmallStruct { X = 12345 }; + SmallStruct result = f(x); + if (result.X != x.X) + throw new Exception(); + } + + { + Func f = o.MediumStructGeneric; + MediumStruct x = new MediumStruct { X = 12, Y = 34, Z = 56, W = 78 }; + MediumStruct result = f(x); + if (result.X != x.X || result.Y != x.Y || result.Z != x.Z || result.W != x.W) + throw new Exception(); + } + + unsafe + { + Func f = o.BigStructGeneric; + BigStruct x = new BigStruct(); + for (int i = 0; i < BigStruct.Length; i++) + x.Bytes[i] = (byte)(i * 2); + + BigStruct result = f(x); + + for (int i = 0; i < BigStruct.Length; i++) + if (x.Bytes[i] != result.Bytes[i]) + throw new Exception(); + } + } + } + + class TestDelegateToCanonMethods + { + class Foo + { + public readonly int Value; + public Foo(int value) + { + Value = value; + } + + public override string ToString() + { + return Value.ToString(); + } + } + + class Bar + { + public readonly int Value; + public Bar(int value) + { + Value = value; + } + + public override string ToString() + { + return Value.ToString(); + } + } + + class FooShared + { + public readonly int Value; + public FooShared(int value) + { + Value = value; + } + + public override string ToString() + { + return Value.ToString(); + } + } + + class BarShared + { + public readonly int Value; + public BarShared(int value) + { + Value = value; + } + + public override string ToString() + { + return Value.ToString(); + } + } + + class GenClass + { + public readonly T X; + + public GenClass(T x) + { + X = x; + } + + public string MakeString() + { + // Use a constructed type that is not used elsewhere + return typeof(T[,]).GetElementType().Name + ": " + X.ToString(); + } + + public string MakeGenString() + { + // Use a constructed type that is not used elsewhere + return typeof(T[,,]).GetElementType().Name + ", " + + typeof(U[,,,]).GetElementType().Name + ": " + X.ToString(); + } + } + + struct GenStruct + { + public readonly T X; + + public GenStruct(T x) + { + X = x; + } + + public string MakeString() + { + // Use a constructed type that is not used elsewhere + return typeof(T[,]).GetElementType().Name + ": " + X.ToString(); + } + + public string MakeGenString() + { + // Use a constructed type that is not used elsewhere + return typeof(T[,,]).GetElementType().Name + ", " + + typeof(U[,,,]).GetElementType().Name + ": " + X.ToString(); + } + } + + private static void RunReferenceTypeShared(T value) + { + // Delegate to a shared nongeneric reference type instance method + { + GenClass g = new GenClass(value); + Func f = g.MakeString; + if (f() != "FooShared: 42") + throw new Exception(); + } + + // Delegate to a shared generic reference type instance method + { + GenClass g = new GenClass(value); + Func f = g.MakeGenString; + if (f() != "FooShared, FooShared: 42") + throw new Exception(); + } + } + + private static void RunValueTypeShared(T value) + { + // Delegate to a shared nongeneric value type instance method + { + GenStruct g = new GenStruct(value); + Func f = g.MakeString; + if (f() != "BarShared: 42") + throw new Exception(); + } + + // Delegate to a shared generic value type instance method + { + GenStruct g = new GenStruct(value); + Func f = g.MakeGenString; + if (f() != "BarShared, BarShared: 42") + throw new Exception(); + } + } + + public static void Run() + { + // Delegate to a shared nongeneric reference type instance method + { + GenClass g = new GenClass(new Foo(42)); + Func f = g.MakeString; + if (f() != "Foo: 42") + throw new Exception(); + } + + // Delegate to a unshared nongeneric reference type instance method + { + GenClass g = new GenClass(85); + Func f = g.MakeString; + if (f() != "Int32: 85") + throw new Exception(); + } + + // Delegate to a shared generic reference type instance method + { + GenClass g = new GenClass(new Foo(42)); + Func f = g.MakeGenString; + if (f() != "Foo, Foo: 42") + throw new Exception(); + } + + // Delegate to a unshared generic reference type instance method + { + GenClass g = new GenClass(85); + Func f = g.MakeGenString; + if (f() != "Int32, Int32: 85") + throw new Exception(); + } + + // Delegate to a shared nongeneric value type instance method + { + GenStruct g = new GenStruct(new Bar(42)); + Func f = g.MakeString; + if (f() != "Bar: 42") + throw new Exception(); + } + + // Delegate to a unshared nongeneric value type instance method + { + GenStruct g = new GenStruct(85); + Func f = g.MakeString; + if (f() != "Int32: 85") + throw new Exception(); + } + + // Delegate to a shared generic value type instance method + { + GenStruct g = new GenStruct(new Bar(42)); + Func f = g.MakeGenString; + if (f() != "Bar, Bar: 42") + throw new Exception(); + } + + // Delegate to a unshared generic value type instance method + { + GenStruct g = new GenStruct(85); + Func f = g.MakeGenString; + if (f() != "Int32, Int32: 85") + throw new Exception(); + } + + // Now the same from shared code + RunReferenceTypeShared(new FooShared(42)); + RunValueTypeShared(new BarShared(42)); + } + } + + class TestDelegateVirtualMethod + { + static void Generic() + { + Base o = new Derived(); + Func f = o.Do; + if (f() != "Derived") + throw new Exception(); + + o = new Base(); + f = o.Do; + if (f() != "Base") + throw new Exception(); + } + + public static void Run() + { + Generic(); + } + + class Base + { + public virtual string Do() => "Base"; + } + + class Derived : Base + { + public override string Do() => "Derived"; + } + } + + class TestDelegateInterfaceMethod + { + static void Generic() + { + IFoo o = new Foo(); + Func f = o.Do; + if (f() != "Foo") + throw new Exception(); + } + + public static void Run() + { + Generic(); + } + + interface IFoo + { + string Do(); + } + + class Foo : IFoo + { + public string Do() => "Foo"; + } + } + + /// + /// Tests RyuJIT's initThisClass. + /// + class TestInitThisClass + { + class Gen1 where T : class + { + static string s_str1; + static string s_str2; + + static Gen1() + { + s_str1 = ("Hello" as T) as string; + s_str2 = ("World" as T) as string; + } + + public static string Get1() + { + return (s_str1 as T) as string; + } + + public static string Get2() + { + return (s_str2 as T) as string; + } + } + + class Gen2 where T : class + { + public static string GetFromClassParam() + { + return (Gen1.Get1() as T) as string; + } + + public static string GetFromMethodParam() + { + return (Gen1.Get2() as T) as string; + } + } + + class NonGeneric + { + public static readonly string Message; + + static NonGeneric() + { + Message = "Hi there"; + } + + public static string Get(object o) + { + if (o is T[]) + return Message; + return null; + } + } + + public static void Run() + { + if (Gen2.GetFromClassParam() != "Hello") + throw new Exception(); + + if (Gen2.GetFromMethodParam() != "World") + throw new Exception(); + + if (NonGeneric.Get(new object[0]) != "Hi there") + throw new Exception(); + } + } + + /// + /// Tests that lazily built vtables for canonically equivalent types have the same shape. + /// + class TestVirtualMethodUseTracking + { + class C1 { } + class C2 { } + + class Base1 where T : class + { + public virtual T As(object o) + { + return o as T; + } + } + + class Derived1 : Base1 where T : class + { + public T AsToo(object o) + { + return o as T; + } + } + + class Base2 + { + public virtual string Method1() => "Base2.Method1"; + public virtual string Method2() => "Base2.Method2"; + } + + class Derived2 : Base2 + { + public override string Method1() => "Derived2.Method1"; + public override string Method2() => "Derived2.Method2"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string TestMethod1FromSharedCode(Base2 o) => o.Method1(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static string TestMethod2FromSharedCode(Base2 o) => o.Method2(); + + public static void Run() + { + C1 c1 = new C1(); + if (new Derived1().As(c1) != c1) + throw new Exception(); + + C2 c2 = new C2(); + if (new Derived1().AsToo(c2) != c2) + throw new Exception(); + + // Also test the stability of the vtables. + Base2 b1 = new Derived2(); + if (b1.Method1() != "Derived2.Method1") + throw new Exception(); + Base2 b2 = new Derived2(); + if (b2.Method2() != "Derived2.Method2") + throw new Exception(); + if (TestMethod1FromSharedCode(b2) != "Derived2.Method1") + throw new Exception(); + if (TestMethod1FromSharedCode(b1) != "Derived2.Method1") + throw new Exception(); + if (TestMethod2FromSharedCode(b2) != "Derived2.Method2") + throw new Exception(); + if (TestMethod2FromSharedCode(b1) != "Derived2.Method2") + throw new Exception(); + } + } + + /// + /// Makes sure that during the base slot computation for types such as + /// Derived<__Canon> (where the base type ends up being Base<__Canon, string>), + /// the lazy vtable slot computation works. + /// + class TestSlotsInHierarchy + { + class Base + { + public virtual int Do() + { + return 42; + } + } + + class Derived : Base where T : class + { + public T Cast(object v) + { + return v as T; + } + } + + public static void Run() + { + var derived = new Derived(); + var derivedAsBase = (Base)derived; + + if (derivedAsBase.Do() != 42) + throw new Exception(); + + if (derived.Cast("Hello") != "Hello") + throw new Exception(); + } + } + + class TestReflectionInvoke + { + static int s_NumErrors = 0; + + struct Foo + { + public int Value; + + [MethodImpl(MethodImplOptions.NoInlining)] + public bool SetAndCheck(int value, U check) + { + Value = value; + return check != null && typeof(T) == typeof(U); + } + } + + public interface IFace + { + string IFaceMethod1(T t); + string IFaceGVMethod1(T t, U u); + } + + public class BaseClass : IFace + { + public virtual string Method1(T t) { return "BaseClass.Method1"; } + public virtual string Method2(T t) { return "BaseClass.Method2"; } + public virtual string Method3(T t) { return "BaseClass.Method3"; } + public virtual string Method4(T t) { return "BaseClass.Method4"; } + public virtual string GVMethod1(T t, U u) { return "BaseClass.GVMethod1"; } + public virtual string GVMethod2(T t, U u) { return "BaseClass.GVMethod2"; } + public virtual string GVMethod3(T t, U u) { return "BaseClass.GVMethod3"; } + public virtual string GVMethod4(T t, U u) { return "BaseClass.GVMethod4"; } + + public virtual string IFaceMethod1(T t) { return "BaseClass.IFaceMethod1"; } + public virtual string IFaceGVMethod1(T t, U u) { return "BaseClass.IFaceGVMethod1"; } + + [MethodImpl(MethodImplOptions.NoInlining)] + public virtual string VirtualButNotUsedVirtuallyMethod(T t) { return "BaseClass.VirtualButNotUsedVirtuallyMethod"; } + } + + public class DerivedClass1 : BaseClass, IFace + { + public override sealed string Method1(T t) { return "DerivedClass1.Method1"; } + public override string Method2(T t) { return "DerivedClass1.Method2"; } + public new virtual string Method3(T t) { return "DerivedClass1.Method3"; } + public override sealed string GVMethod1(T t, U u) { return "DerivedClass1.GVMethod1"; } + public override string GVMethod2(T t, U u) { return "DerivedClass1.GVMethod2"; } + public new virtual string GVMethod3(T t, U u) { return "DerivedClass1.GVMethod3"; } + + public override string IFaceMethod1(T t) { return "DerivedClass1.IFaceMethod1"; } + + public string UseVirtualButNotUsedVirtuallyMethod(T t) + { + // Calling through base produces a `call` instead of `callvirt` instruction. + return base.VirtualButNotUsedVirtuallyMethod(t); + } + } + + public class DerivedClass2 : DerivedClass1, IFace + { + public override string Method3(T t) { return "DerivedClass2.Method3"; } + public override string Method4(T t) { return "DerivedClass2.Method4"; } + public override string GVMethod3(T t, U u) { return "DerivedClass2.GVMethod3"; } + public override string GVMethod4(T t, U u) { return "DerivedClass2.GVMethod4"; } + + string IFace.IFaceMethod1(T t) { return "DerivedClass2.IFaceMethod1"; } + public override string IFaceGVMethod1(T t, U u) { return "DerivedClass2.IFaceGVMethod1"; } + } + + private static void Verify(T expected, T actual) + { + if (!actual.Equals(expected)) + { + Console.WriteLine("ACTUAL : " + actual); + Console.WriteLine("EXPECTED : " + expected); + s_NumErrors++; + } + } + + public static void Run() + { + if (String.Empty.Length > 0) + { + // Make sure we compile this method body. + var tmp = new Foo(); + tmp.SetAndCheck(0, null); + } + + object o = new Foo(); + + { + MethodInfo mi = typeof(Foo).GetTypeInfo().GetDeclaredMethod("SetAndCheck").MakeGenericMethod(typeof(string)); + if (!(bool)mi.Invoke(o, new object[] { 123, "hello" })) + s_NumErrors++; + + var foo = (Foo)o; + if (foo.Value != 123) + s_NumErrors++; + + if ((bool)mi.Invoke(o, new object[] { 123, null })) + s_NumErrors++; + } + + // Uncomment when we have the type loader to buld invoke stub dictionaries. + { + MethodInfo mi = typeof(Foo).GetTypeInfo().GetDeclaredMethod("SetAndCheck").MakeGenericMethod(typeof(object)); + if ((bool)mi.Invoke(o, new object[] { 123, new object() })) + s_NumErrors++; + } + + // VirtualInvokeMap testing + { + // Rooting some methods to make them reflectable + new BaseClass().Method1("string"); + new BaseClass().Method2("string"); + new BaseClass().Method3("string"); + new BaseClass().Method4("string"); + new BaseClass().GVMethod1("string", "string2"); + new BaseClass().GVMethod2("string", "string2"); + new BaseClass().GVMethod3("string", "string2"); + new BaseClass().GVMethod4("string", "string2"); + new DerivedClass1().Method1("string"); + new DerivedClass1().Method2("string"); + new DerivedClass1().Method3("string"); + new DerivedClass1().Method4("string"); + new DerivedClass1().GVMethod1("string", "string2"); + new DerivedClass1().GVMethod2("string", "string2"); + new DerivedClass1().GVMethod3("string", "string2"); + new DerivedClass1().GVMethod4("string", "string2"); + new DerivedClass1().UseVirtualButNotUsedVirtuallyMethod("string"); + new DerivedClass2().Method1("string"); + new DerivedClass2().Method2("string"); + new DerivedClass2().Method3("string"); + new DerivedClass2().Method4("string"); + new DerivedClass2().GVMethod1("string", "string2"); + new DerivedClass2().GVMethod2("string", "string2"); + new DerivedClass2().GVMethod3("string", "string2"); + new DerivedClass2().GVMethod4("string", "string2"); + Func> f = () => new BaseClass(); // Hack to prevent devirtualization + f().IFaceMethod1("string"); + ((IFace)new BaseClass()).IFaceGVMethod1("string1", "string2"); + + MethodInfo m1 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method1"); + MethodInfo m2 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method2"); + MethodInfo m3 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method3"); + MethodInfo m4 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method4"); + MethodInfo unusedMethod = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("VirtualButNotUsedVirtuallyMethod"); + MethodInfo gvm1 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("GVMethod1").MakeGenericMethod(typeof(string)); + MethodInfo gvm2 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("GVMethod2").MakeGenericMethod(typeof(string)); + MethodInfo gvm3 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("GVMethod3").MakeGenericMethod(typeof(string)); + MethodInfo gvm4 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("GVMethod4").MakeGenericMethod(typeof(string)); + Verify("BaseClass.Method1", m1.Invoke(new BaseClass(), new[] { "" })); + Verify("BaseClass.Method2", m2.Invoke(new BaseClass(), new[] { "" })); + Verify("BaseClass.Method3", m3.Invoke(new BaseClass(), new[] { "" })); + Verify("BaseClass.Method4", m4.Invoke(new BaseClass(), new[] { "" })); + Verify("BaseClass.VirtualButNotUsedVirtuallyMethod", unusedMethod.Invoke(new BaseClass(), new[] { "" })); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass1(), new[] { "" })); + Verify("DerivedClass1.Method2", m2.Invoke(new DerivedClass1(), new[] { "" })); + Verify("BaseClass.Method3", m3.Invoke(new DerivedClass1(), new[] { "" })); + Verify("BaseClass.Method4", m4.Invoke(new DerivedClass1(), new[] { "" })); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass1.Method2", m2.Invoke(new DerivedClass2(), new[] { "" })); + Verify("BaseClass.Method3", m3.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass2.Method4", m4.Invoke(new DerivedClass2(), new[] { "" })); + Verify("BaseClass.GVMethod1", gvm1.Invoke(new BaseClass(), new[] { "", "" })); + Verify("BaseClass.GVMethod2", gvm2.Invoke(new BaseClass(), new[] { "", "" })); + Verify("BaseClass.GVMethod3", gvm3.Invoke(new BaseClass(), new[] { "", "" })); + Verify("BaseClass.GVMethod4", gvm4.Invoke(new BaseClass(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod1", gvm1.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod2", gvm2.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("BaseClass.GVMethod3", gvm3.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("BaseClass.GVMethod4", gvm4.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod1", gvm1.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod2", gvm2.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("BaseClass.GVMethod3", gvm3.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("DerivedClass2.GVMethod4", gvm4.Invoke(new DerivedClass2(), new[] { "", "" })); + + m1 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("Method1"); + m2 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("Method2"); + m3 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("Method3"); + gvm1 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("GVMethod1").MakeGenericMethod(typeof(string)); + gvm2 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("GVMethod2").MakeGenericMethod(typeof(string)); + gvm3 = typeof(DerivedClass1).GetTypeInfo().GetDeclaredMethod("GVMethod3").MakeGenericMethod(typeof(string)); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass1(), new[] { "" })); + Verify("DerivedClass1.Method2", m2.Invoke(new DerivedClass1(), new[] { "" })); + Verify("DerivedClass1.Method3", m3.Invoke(new DerivedClass1(), new[] { "" })); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass1.Method2", m2.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass2.Method3", m3.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass1.GVMethod1", gvm1.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod2", gvm2.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod3", gvm3.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod1", gvm1.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("DerivedClass1.GVMethod2", gvm2.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("DerivedClass2.GVMethod3", gvm3.Invoke(new DerivedClass2(), new[] { "", "" })); + + m3 = typeof(DerivedClass2).GetTypeInfo().GetDeclaredMethod("Method3"); + m4 = typeof(DerivedClass2).GetTypeInfo().GetDeclaredMethod("Method4"); + gvm3 = typeof(DerivedClass2).GetTypeInfo().GetDeclaredMethod("GVMethod3").MakeGenericMethod(typeof(string)); + gvm4 = typeof(DerivedClass2).GetTypeInfo().GetDeclaredMethod("GVMethod4").MakeGenericMethod(typeof(string)); + Verify("DerivedClass2.Method3", m3.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass2.Method4", m4.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass2.GVMethod3", gvm3.Invoke(new DerivedClass2(), new[] { "", "" })); + Verify("DerivedClass2.GVMethod4", gvm4.Invoke(new DerivedClass2(), new[] { "", "" })); + + // BaseClass.Method1 has the same slot as BaseClass.Method3 on CoreRT, because vtable entries + // get populated on demand (the first type won't get a Method3 entry, and the latter won't get a Method1 entry) + // On ProjectN, both types will get vtable entries for both methods. + new BaseClass().Method1(1); + m1 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method1"); + Verify("BaseClass.Method1", m1.Invoke(new BaseClass(), new object[] { (int)1 })); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass1(), new object[] { (int)1 })); + Verify("DerivedClass1.Method1", m1.Invoke(new DerivedClass2(), new object[] { (int)1 })); + + new BaseClass().Method3(1); + m3 = typeof(BaseClass).GetTypeInfo().GetDeclaredMethod("Method3"); + Verify("BaseClass.Method3", m3.Invoke(new BaseClass(), new object[] { 1.1f })); + Verify("BaseClass.Method3", m3.Invoke(new DerivedClass1(), new object[] { 1.1f })); + Verify("BaseClass.Method3", m3.Invoke(new DerivedClass2(), new object[] { 1.1f })); + + m1 = typeof(IFace).GetTypeInfo().GetDeclaredMethod("IFaceMethod1"); + gvm1 = typeof(IFace).GetTypeInfo().GetDeclaredMethod("IFaceGVMethod1").MakeGenericMethod(typeof(string)); + Verify("BaseClass.IFaceMethod1", m1.Invoke(new BaseClass(), new[] { "" })); + Verify("BaseClass.IFaceGVMethod1", gvm1.Invoke(new BaseClass(), new[] { "", "" })); + Verify("DerivedClass1.IFaceMethod1", m1.Invoke(new DerivedClass1(), new[] { "" })); + Verify("BaseClass.IFaceGVMethod1", gvm1.Invoke(new DerivedClass1(), new[] { "", "" })); + Verify("DerivedClass2.IFaceMethod1", m1.Invoke(new DerivedClass2(), new[] { "" })); + Verify("DerivedClass2.IFaceGVMethod1", gvm1.Invoke(new DerivedClass2(), new[] { "", "" })); + } + + if (s_NumErrors != 0) + throw new Exception(); + } + } + + class TestThreadStaticFieldAccess + { + class TypeWithThreadStaticField + { + [ThreadStatic] + public static int X; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Read() + { + return X; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Write(int x) + { + X = x; + } + } + + class BeforeFieldInitType + { + [ThreadStatic] + public static int X = 1985; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int ReadFromBeforeFieldInitType() + { + return BeforeFieldInitType.X; + } + + public static void Run() + { + // This will set the field to a value from non-shared code + TypeWithThreadStaticField.X = 42; + + // Now read the value from shared code + if (TypeWithThreadStaticField.Read() != 42) + throw new Exception(); + + // Set the value from shared code + TypeWithThreadStaticField.Write(112); + + // Now read the value from non-shared code + if (TypeWithThreadStaticField.X != 112) + throw new Exception(); + + // Check that the storage locations for string and object instantiations differ + if (TypeWithThreadStaticField.Read() != 42) + throw new Exception(); + + // Make sure we run the cctor + if (ReadFromBeforeFieldInitType() != 1985) + throw new Exception(); + } + } + + class TestConstrainedMethodCalls + { + class Atom1 { } + class Atom2 { } + + interface IFoo + { + bool Frob(object o); + } + + struct Foo : IFoo + { + public int FrobbedValue; + + public bool Frob(object o) + { + FrobbedValue = 12345; + return o is T[,,]; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool DoFrob(ref T t, object o) where T : IFoo + { + // Perform a constrained interface call from shared code. + // This should have been resolved to a direct call at compile time. + return t.Frob(o); + } + + public static void Run() + { + var foo1 = new Foo(); + bool result = DoFrob, Atom1>(ref foo1, new Atom1[0, 0, 0]); + + // If the FrobbedValue doesn't change when we frob, we must have done box+interface call. + if (foo1.FrobbedValue != 12345) + throw new Exception(); + + // Also check we passed the right generic context to Foo.Frob + if (!result) + throw new Exception(); + + // Also check dependency analysis: + // EEType for Atom2[,,] that we'll check for was never allocated. + var foo2 = new Foo(); + if (DoFrob, Atom2>(ref foo2, new object())) + throw new Exception(); + } + } + + class TestInstantiatingUnboxingStubs + { + static volatile IFoo s_foo; + + interface IFoo + { + bool IsInst(object o); + + void Set(int value); + } + + struct Foo : IFoo + { + public int Value; + + public bool IsInst(object o) + { + return o is T; + } + + public void Set(int value) + { + Value = value; + } + } + + public static void Run() + { + s_foo = new Foo(); + + // Make sure the instantiation argument is properly passed + if (!s_foo.IsInst("ab")) + throw new Exception(); + + if (s_foo.IsInst(new object())) + throw new Exception(); + + // Make sure the byref to 'this' is properly passed + s_foo.Set(42); + + var foo = (Foo)s_foo; + if (foo.Value != 42) + throw new Exception(); + } + } + + class TestMDArrayAddressMethod + { + [MethodImpl(MethodImplOptions.NoInlining)] + private static void PassByRef(ref object x) + { + x = new Object(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DoGen(object[,] arr) + { + // Here, the array type is known statically at the time of compilation + PassByRef(ref arr[0, 0]); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void PassByRef2(ref T x) + { + x = default(T); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DoGen2(T[,] arr) + { + // Here, the array type needs to be looked up from the dictionary + PassByRef2(ref arr[0, 0]); + } + + public static void Run() + { + int exceptionsSeen = 0; + + try + { + DoGen(new string[1, 1]); + } + catch (ArrayTypeMismatchException) + { + exceptionsSeen++; + } + + DoGen(new object[1, 1]); + + try + { + DoGen2(new string[1, 1]); + } + catch (ArrayTypeMismatchException) + { + exceptionsSeen++; + } + + DoGen2(new object[1, 1]); + + if (exceptionsSeen != 2) + throw new Exception(); + } + } + + // + // Regression test for issue https://github.com/dotnet/corert/issues/1964 + // + class TestNameManglingCollisionRegression + { + class Gen1 + { + public Gen1(T t) { } + } + + public static void Run() + { + Gen1[] g1 = new Gen1[1]; + g1[0] = new Gen1(new object[] { new object[1] }); + + Gen1 g2 = new Gen1(new object[1][]); + } + } + + class TestSimpleGVMScenarios + { + interface IFoo + { + string IMethod1(T t1, T t2); + } + + interface ICovariant + { + string ICovariantGVM(); + } + + public interface IBar + { + U IBarGVMethod(Func arg); + } + + public interface IFace + { + string IFaceGVMethod1(T t, U u); + } + + class Base : IFoo, IFoo + { + public virtual string GMethod1(T t1, T t2) { return "Base.GMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + public virtual string IMethod1(T t1, T t2) { return "Base.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + class Derived : Base, IFoo, IFoo + { + public override string GMethod1(T t1, T t2) { return "Derived.GMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + string IFoo.IMethod1(T t1, T t2) { return "Derived.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + class SuperDerived : Derived, IFoo, IFoo + { + string IFoo.IMethod1(T t1, T t2) { return "SuperDerived.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + + + class GenBase : IFoo, IFoo + { + public virtual string GMethod1(T t1, T t2) { return "GenBase<" + typeof(A) + ">.GMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + public virtual string IMethod1(T t1, T t2) { return "GenBase<" + typeof(A) + ">.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + class GenDerived : GenBase, IFoo, IFoo + { + public override string GMethod1(T t1, T t2) { return "GenDerived<" + typeof(A) + ">.GMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + string IFoo.IMethod1(T t1, T t2) { return "GenDerived<" + typeof(A) + ">.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + class GenSuperDerived : GenDerived, IFoo, IFoo + { + string IFoo.IMethod1(T t1, T t2) { return "GenSuperDerived<" + typeof(A) + ">.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + + struct MyStruct1 : IFoo, IFoo + { + string IFoo.IMethod1(T t1, T t2) { return "MyStruct1.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + string IFoo.IMethod1(T t1, T t2) { return "MyStruct1.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + struct MyStruct2 : IFoo, IFoo + { + string IFoo.IMethod1(T t1, T t2) { return "MyStruct2.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + public string IMethod1(T t1, T t2) { return "MyStruct2.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + struct MyStruct3 : IFoo, IFoo + { + string IFoo.IMethod1(T t1, T t2) { return "MyStruct3.IFoo.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + public string IMethod1(T t1, T t2) { return "MyStruct3.IMethod1<" + typeof(T) + ">(" + t1 + "," + t2 + ")"; } + } + + public class AnotherBaseClass + { + public virtual string IFaceMethod1(T t) { return "AnotherBaseClass.IFaceMethod1"; } + public virtual string IFaceGVMethod1(T t, U u) { return "AnotherBaseClass.IFaceGVMethod1"; } + } + + public class AnotherDerivedClass : AnotherBaseClass, IFace + { + } + + public class BarImplementor : IBar + { + public virtual U IBarGVMethod(Func arg) { return arg(123); } + } + + public class Yahoo + { + public virtual U YahooGVM(Func arg) { return default(U); } + } + + public class YahooDerived : Yahoo + { + public override U YahooGVM(Func arg) { return arg(456); } + } + + public class Covariant : ICovariant + { + public string ICovariantGVM() { return String.Format("Covariant<{0}>.ICovariantGVM<{1}>", typeof(T).Name, typeof(U).Name); } + } + + static string s_GMethod1; + static string s_IFooString; + static string s_IFooObject; + static string s_IFooInt; + + static int s_NumErrors = 0; + + private static void TestWithStruct(IFoo ifooStr, IFoo ifooObj, IFoo ifooInt) + { + var res = ifooStr.IMethod1(1, 2); + WriteLineWithVerification(res, s_IFooString); + + res = ifooObj.IMethod1(3, 4); + WriteLineWithVerification(res, s_IFooObject); + + res = ifooInt.IMethod1(5, 6); + WriteLineWithVerification(res, s_IFooInt); + } + + private static void TestWithClass(object o) + { + Base b = o as Base; + var res = b.GMethod1(1, 2); + WriteLineWithVerification(res, s_GMethod1); + + IFoo ifoo1 = o as IFoo; + res = ifoo1.IMethod1(3, 4); + WriteLineWithVerification(res, s_IFooString); + + IFoo ifoo2 = o as IFoo; + res = ifoo2.IMethod1(5, 6); + WriteLineWithVerification(res, s_IFooObject); + + IFoo ifoo3 = o as IFoo; + res = ifoo3.IMethod1(7, 8); + WriteLineWithVerification(res, s_IFooInt); + } + + private static void TestWithGenClass(object o) + { + GenBase b = o as GenBase; + var res = b.GMethod1(1, 2); + WriteLineWithVerification(res, s_GMethod1); + + IFoo ifoo1 = o as IFoo; + res = ifoo1.IMethod1(3, 4); + WriteLineWithVerification(res, s_IFooString); + + IFoo ifoo2 = o as IFoo; + res = ifoo2.IMethod1(5, 6); + WriteLineWithVerification(res, s_IFooObject); + + IFoo ifoo3 = o as IFoo; + res = ifoo3.IMethod1(7, 8); + WriteLineWithVerification(res, s_IFooInt); + } + + private static void WriteLineWithVerification(string actual, string expected) + { + if (actual != expected) + { + Console.WriteLine("ACTUAL : " + actual); + Console.WriteLine("EXPECTED : " + expected); + s_NumErrors++; + } + else + { + Console.WriteLine(actual); + } + } + + public static void Run() + { + { + s_GMethod1 = "Base.GMethod1(1,2)"; + s_IFooString = "Base.IMethod1(3,4)"; + s_IFooObject = "Base.IMethod1(5,6)"; + s_IFooInt = "Base.IMethod1(7,8)"; + TestWithClass(new Base()); + Console.WriteLine("===================="); + + + s_GMethod1 = "Derived.GMethod1(1,2)"; + s_IFooString = "Derived.IFoo.IMethod1(3,4)"; + s_IFooObject = "Derived.IFoo.IMethod1(5,6)"; + s_IFooInt = "Base.IMethod1(7,8)"; + TestWithClass(new Derived()); + Console.WriteLine("===================="); + + + s_GMethod1 = "Derived.GMethod1(1,2)"; + s_IFooString = "Derived.IFoo.IMethod1(3,4)"; + s_IFooObject = "Derived.IFoo.IMethod1(5,6)"; + s_IFooInt = "SuperDerived.IFoo.IMethod1(7,8)"; + TestWithClass(new SuperDerived()); + Console.WriteLine("===================="); + } + + { + s_GMethod1 = "GenBase.GMethod1(1,2)"; + s_IFooString = "GenBase.IMethod1(3,4)"; + s_IFooObject = "GenBase.IMethod1(5,6)"; + s_IFooInt = "GenBase.IMethod1(7,8)"; + TestWithGenClass(new GenBase()); + Console.WriteLine("===================="); + + + s_GMethod1 = "GenDerived.GMethod1(1,2)"; + s_IFooString = "GenDerived.IFoo.IMethod1(3,4)"; + s_IFooObject = "GenDerived.IFoo.IMethod1(5,6)"; + s_IFooInt = "GenBase.IMethod1(7,8)"; + TestWithGenClass(new GenDerived()); + Console.WriteLine("===================="); + + + s_GMethod1 = "GenDerived.GMethod1(1,2)"; + s_IFooString = "GenDerived.IFoo.IMethod1(3,4)"; + s_IFooObject = "GenDerived.IFoo.IMethod1(5,6)"; + s_IFooInt = "GenBase.IMethod1(7,8)"; + TestWithGenClass(new GenDerived()); + Console.WriteLine("===================="); + + + s_GMethod1 = "GenDerived.GMethod1(1,2)"; + s_IFooString = "GenDerived.IFoo.IMethod1(3,4)"; + s_IFooObject = "GenDerived.IFoo.IMethod1(5,6)"; + s_IFooInt = "GenSuperDerived.IFoo.IMethod1(7,8)"; + TestWithGenClass(new GenSuperDerived()); + Console.WriteLine("===================="); + } + + { + s_IFooString = "MyStruct1.IFoo.IMethod1(1,2)"; + s_IFooObject = "MyStruct1.IFoo.IMethod1(3,4)"; + s_IFooInt = "MyStruct1.IFoo.IMethod1(5,6)"; + TestWithStruct(new MyStruct1(), new MyStruct1(), new MyStruct1()); + Console.WriteLine("===================="); + + + s_IFooString = "MyStruct2.IFoo.IMethod1(1,2)"; + s_IFooObject = "MyStruct2.IFoo.IMethod1(3,4)"; + s_IFooInt = "MyStruct2.IMethod1(5,6)"; + TestWithStruct(new MyStruct2(), new MyStruct2(), new MyStruct2()); + Console.WriteLine("===================="); + + + s_IFooString = "MyStruct3.IMethod1(1,2)"; + s_IFooObject = "MyStruct3.IMethod1(3,4)"; + s_IFooInt = "MyStruct3.IFoo.IMethod1(5,6)"; + TestWithStruct(new MyStruct3(), new MyStruct3(), new MyStruct3()); + Console.WriteLine("===================="); + } + + { + string res = ((IFace)new AnotherDerivedClass()).IFaceGVMethod1("string1", "string2"); + WriteLineWithVerification("AnotherBaseClass.IFaceGVMethod1", res); + + res = ((IBar)new BarImplementor()).IBarGVMethod((i) => "BarImplementor:" + i.ToString()); + WriteLineWithVerification("BarImplementor:123", res); + + Yahoo y = new YahooDerived(); + WriteLineWithVerification("YahooDerived:456", y.YahooGVM((i) => "YahooDerived:" + i.ToString())); + + ICovariant cov = new Covariant(); + WriteLineWithVerification("Covariant.ICovariantGVM", cov.ICovariantGVM()); + } + + if (s_NumErrors != 0) + throw new Exception(); + } + } + + class TestGvmDelegates + { + class Atom { } + + interface IFoo + { + string Frob(int arg); + } + + class FooUnshared : IFoo + { + public string Frob(int arg) + { + return typeof(T[,]).GetElementType().Name + arg.ToString(); + } + } + + class FooShared : IFoo + { + public string Frob(int arg) + { + return typeof(T[,,]).GetElementType().Name + arg.ToString(); + } + } + + class Base + { + public virtual string Frob(string s) + { + return typeof(T).Name + ": Base: " + s; + } + } + + class Derived : Base + { + public override string Frob(string s) + { + return typeof(T).Name + ": Derived: " + s; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void ValidateShared(string s) + { + Func f = Frob; + if (f(s) != typeof(T).Name + ": Derived: " + s) + throw new Exception(); + + f = base.Frob; + if (f(s) != typeof(T).Name + ": Base: " + s) + throw new Exception(); + } + + public void Validate(string s) + { + Func f = Frob; + if (f(s) != typeof(string).Name + ": Derived: " + s) + throw new Exception(); + + f = base.Frob; + if (f(s) != typeof(string).Name + ": Base: " + s) + throw new Exception(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void RunShared(IFoo foo) + { + Func a = foo.Frob; + if (a(456) != "Atom456") + throw new Exception(); + } + + public static void Run() + { + IFoo foo = new FooUnshared(); + Func a = foo.Frob; + if (a(123) != "Atom123") + throw new Exception(); + + RunShared(new FooShared()); + + new Derived().Validate("hello"); + new Derived().ValidateShared("ola"); + } + } + + class TestGvmDependencies + { + class Atom { } + + class Foo + { + public virtual object Frob() + { + return new T[0, 0]; + } + } + + class Bar : Foo + { + public override object Frob() + { + return new T[0, 0, 0]; + } + } + + public static void Run() + { + { + Foo x = new Foo(); + x.Frob(); + } + + { + Foo x = new Bar(); + x.Frob(); + } + } + } + + class TestFieldAccess + { + class ClassType { } + class ClassType2 { } + struct StructType { } + + class Foo + { + static Foo() + { + Console.WriteLine("Foo<" + typeof(T).Name + "> cctor"); + + if (typeof(T) == typeof(ClassType)) + TestFieldAccess.s_FooClassTypeCctorCount++; + else + TestFieldAccess.s_FooStructTypeCctorCount++; + } + + public static int s_intField; + public static float s_floatField; + public static string s_stringField; + public static object s_objectField; + public static long s_longField1; + public static long s_longField2; + public static long s_longField3; + public static KeyValuePair s_kvp; + + public int m_intField; + public float m_floatField; + public string m_stringField; + public object m_objectField; + } + + class Bar + { + static Bar() + { + Console.WriteLine("Bar cctor"); + TestFieldAccess.s_BarCctorCount++; + } + + public static int s_intField; + public static float s_floatField; + public static string s_stringField; + public static object s_objectField; + public static long s_longField1; + public static long s_longField2; + public static long s_longField3; + public static KeyValuePair s_kvp; + + public int m_intField; + public float m_floatField; + public string m_stringField; + public object m_objectField; + } + + public class DynamicBase + { + public T _t; + + [MethodImpl(MethodImplOptions.NoInlining)] + public DynamicBase() { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public int SimpleMethod() + { + return 123; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public int MethodWithTInSig(T t) + { + _t = t; + return 234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public virtual string VirtualMethod(T t) + { + _t = t; + return "DynamicBase.VirtualMethod"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public string GenericMethod(T t, U u) + { + _t = t; + return typeof(U).ToString() + u.ToString(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public virtual string GenericVirtualMethod(T t, U u) + { + _t = t; + return "DynamicBase" + typeof(U).ToString() + u.ToString(); + } + } + + public class DynamicDerived : DynamicBase + { + [MethodImpl(MethodImplOptions.NoInlining)] + public DynamicDerived() { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public override string VirtualMethod(T t) + { + _t = t; + return "DynamicDerived.VirtualMethod"; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public override string GenericVirtualMethod(T t, U u) + { + _t = t; + return "DynamicDerived" + typeof(U).ToString() + u.ToString(); + } + } + + class UnconstructedTypeWithGCStatics + { +#pragma warning disable 169 + static string s_gcField; +#pragma warning restore + } + + class UnconstructedTypeWithNonGCStatics + { +#pragma warning disable 169 + static float s_nonGcField; +#pragma warning restore + } + + class UnconstructedTypeInstantiator { } + + public static int s_FooClassTypeCctorCount = 0; + public static int s_FooStructTypeCctorCount = 0; + public static int s_BarCctorCount = 0; + public static int s_NumErrors = 0; + + private static void Verify(T expected, T actual) + { + if (!actual.Equals(expected)) + { + Console.WriteLine("ACTUAL : " + actual); + Console.WriteLine("EXPECTED : " + expected); + s_NumErrors++; + } + } + + private static void TestDynamicStaticFields() + { + Foo.s_intField = 1234; + Foo.s_floatField = 12.34f; + Foo.s_longField1 = 0x1111; + + var fooDynamicOfClassType = typeof(Foo<>).MakeGenericType(typeof(ClassType)).GetTypeInfo(); + var fooDynamicOfClassType2 = typeof(Foo<>).MakeGenericType(typeof(ClassType2)).GetTypeInfo(); + + FieldInfo fi = fooDynamicOfClassType.GetDeclaredField("s_intField"); + FieldInfo fi2 = fooDynamicOfClassType2.GetDeclaredField("s_intField"); + fi.SetValue(null, 1111); + fi2.SetValue(null, 2222); + Verify(1111, (int)fi.GetValue(null)); + Verify(2222, (int)fi2.GetValue(null)); + + fi = fooDynamicOfClassType.GetDeclaredField("s_floatField"); + fi2 = fooDynamicOfClassType2.GetDeclaredField("s_floatField"); + fi.SetValue(null, 1.1f); + fi2.SetValue(null, 2.2f); + Verify(1.1f, (float)fi.GetValue(null)); + Verify(2.2f, (float)fi2.GetValue(null)); + + fi = fooDynamicOfClassType.GetDeclaredField("s_longField1"); + fi2 = fooDynamicOfClassType2.GetDeclaredField("s_longField1"); + fi.SetValue(null, 0x11111111); + fi2.SetValue(null, 0x22222222); + Verify(0x11111111, (long)fi.GetValue(null)); + Verify(0x22222222, (long)fi2.GetValue(null)); + + fi = fooDynamicOfClassType.GetDeclaredField("s_stringField"); + fi2 = fooDynamicOfClassType2.GetDeclaredField("s_stringField"); + fi.SetValue(null, "abc123"); + fi2.SetValue(null, "omgroflpwn"); + Verify("abc123", (string)fi.GetValue(null)); + Verify("omgroflpwn", (string)fi2.GetValue(null)); + + fi = fooDynamicOfClassType.GetDeclaredField("s_objectField"); + fi2 = fooDynamicOfClassType2.GetDeclaredField("s_objectField"); + fi.SetValue(null, "qwerty"); + fi2.SetValue(null, "ytrewq"); + Verify("qwerty", (string)fi.GetValue(null)); + Verify("ytrewq", (string)fi2.GetValue(null)); + } + + private static void TestDynamicInvokeStubs() + { + Console.WriteLine("Testing dynamic invoke stubs..."); + // Root required methods / types statically instantiated over some unrelated type + DynamicBase heh = new DynamicBase(); + heh.MethodWithTInSig(new Program()); + heh.SimpleMethod(); + heh.VirtualMethod(new Program()); + heh.GenericMethod(new Program(), "hello"); + heh.GenericVirtualMethod(new Program(), "hello"); + + DynamicDerived heh2 = new DynamicDerived(); + heh2.VirtualMethod(new Program()); + heh2.GenericVirtualMethod(new Program(), "ayy"); + + // Simple method invocation + var dynamicBaseOfString = typeof(DynamicBase<>).MakeGenericType(typeof(string)); + object obj = Activator.CreateInstance(dynamicBaseOfString); + { + var simpleMethod = dynamicBaseOfString.GetTypeInfo().GetDeclaredMethod("SimpleMethod"); + int result = (int)simpleMethod.Invoke(obj, null); + Verify((int)123, result); + } + + // Method with T in the signature + { + var methodWithTInSig = dynamicBaseOfString.GetTypeInfo().GetDeclaredMethod("MethodWithTInSig"); + int result = (int)methodWithTInSig.Invoke(obj, new[] { "fad" }); + Verify((int)234, result); + } + + // Test virtual method invocation + { + var virtualMethodDynamicBase = dynamicBaseOfString.GetTypeInfo().GetDeclaredMethod("VirtualMethod"); + string result = (string)virtualMethodDynamicBase.Invoke(obj, new[] { "fad" }); + Verify("DynamicBase.VirtualMethod", result); + } + + { + var dynamicDerivedOfString = typeof(DynamicDerived<>).MakeGenericType(typeof(string)); + object dynamicDerivedObj = Activator.CreateInstance(dynamicDerivedOfString); + var virtualMethodDynamicDerived = dynamicDerivedOfString.GetTypeInfo().GetDeclaredMethod("VirtualMethod"); + string result = (string)virtualMethodDynamicDerived.Invoke(dynamicDerivedObj, new[] { "fad" }); + Verify("DynamicDerived.VirtualMethod", result); + } + + // Test generic method invocation + { + var genericMethod = dynamicBaseOfString.GetTypeInfo().GetDeclaredMethod("GenericMethod").MakeGenericMethod(new[] { typeof(string) }); + string result = (string)genericMethod.Invoke(obj, new[] { "hey", "hello" }); + + Verify("System.Stringhello", result); + } + + // Test GVM invocation + { + var genericMethod = dynamicBaseOfString.GetTypeInfo().GetDeclaredMethod("GenericVirtualMethod"); + genericMethod = genericMethod.MakeGenericMethod(new[] { typeof(string) }); + string result = (string)genericMethod.Invoke(obj, new[] { "hey", "hello" }); + Verify("DynamicBaseSystem.Stringhello", result); + } + + { + var dynamicDerivedOfString = typeof(DynamicDerived<>).MakeGenericType(typeof(string)); + object dynamicDerivedObj = Activator.CreateInstance(dynamicDerivedOfString); + var virtualMethodDynamicDerived = dynamicDerivedOfString.GetTypeInfo().GetDeclaredMethod("GenericVirtualMethod").MakeGenericMethod(new[] { typeof(string) }); + string result = (string)virtualMethodDynamicDerived.Invoke(dynamicDerivedObj, new[] { "hey", "fad" }); + Verify("DynamicDerivedSystem.Stringfad", result); + } + } + + private static void TestStaticFields() + { + Foo.s_intField = 11223344; + Foo.s_stringField = "abcd"; + Foo.s_floatField = 12.34f; + Foo.s_objectField = "123"; + Foo.s_kvp = new KeyValuePair("1122", "3344"); + + Foo.s_intField = 44332211; + Foo.s_stringField = "dcba"; + Foo.s_floatField = 43.21f; + Foo.s_objectField = "321"; + Foo.s_kvp = new KeyValuePair("4433", "2211"); + + + Bar.s_intField = 778899; + Bar.s_stringField = "xxyyzz"; + Bar.s_floatField = 88.99f; + Bar.s_objectField = "890"; + Bar.s_kvp = new KeyValuePair("7788", "8899"); + + // Testing correctness of cctor context + { + Foo.s_longField1 = 0xff00; + Foo.s_longField2 = 0xff00; + Foo.s_longField3 = 0xff00; + if (TestFieldAccess.s_FooClassTypeCctorCount != 1) + s_NumErrors++; + + Foo.s_longField1 = 0xff00; + Foo.s_longField2 = 0xff00; + Foo.s_longField3 = 0xff00; + if (TestFieldAccess.s_FooStructTypeCctorCount != 1) + s_NumErrors++; + + Bar.s_longField1 = 0xff00; + Bar.s_longField2 = 0xff00; + Bar.s_longField3 = 0xff00; + if (TestFieldAccess.s_BarCctorCount != 1) + s_NumErrors++; + } + + Console.WriteLine("Testing static fields on type Foo ..."); + { + FieldInfo fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_intField"); + Verify((int)11223344, (int)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_stringField"); + Verify("abcd", (string)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_floatField"); + Verify(12.34f, (float)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_objectField"); + Verify("123", fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_kvp"); + var result = (KeyValuePair)fi.GetValue(null); + Verify("1122", result.Key); + Verify("3344", result.Value); + + typeof(Foo).GetTypeInfo().GetDeclaredField("s_stringField").SetValue(null, "ThisIsAString1"); + typeof(Foo).GetTypeInfo().GetDeclaredField("s_objectField").SetValue(null, "ThisIsAString2"); + typeof(Foo).GetTypeInfo().GetDeclaredField("s_kvp").SetValue(null, new KeyValuePair("ThisIs", "AString")); + Verify("ThisIsAString1", (string)Foo.s_stringField); + Verify("ThisIsAString2", (string)Foo.s_objectField); + Verify("ThisIs", (string)Foo.s_kvp.Key); + Verify("AString", (string)Foo.s_kvp.Value); + } + + Console.WriteLine("Testing static fields on type Foo ..."); + { + FieldInfo fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_intField"); + Verify(44332211, (int)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_stringField"); + Verify("dcba", (string)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_floatField"); + Verify(43.21f, (float)fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_objectField"); + Verify("321", fi.GetValue(null)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("s_kvp"); + var result = (KeyValuePair)fi.GetValue(null); + Verify("4433", result.Key); + Verify("2211", result.Value); + + typeof(Foo).GetTypeInfo().GetDeclaredField("s_stringField").SetValue(null, "ThisIsAString3"); + typeof(Foo).GetTypeInfo().GetDeclaredField("s_objectField").SetValue(null, "ThisIsAString4"); + typeof(Foo).GetTypeInfo().GetDeclaredField("s_kvp").SetValue(null, new KeyValuePair("ThisIs1", "AString1")); + Verify("ThisIsAString3", (string)Foo.s_stringField); + Verify("ThisIsAString4", (string)Foo.s_objectField); + Verify("ThisIs1", (string)Foo.s_kvp.Key); + Verify("AString1", (string)Foo.s_kvp.Value); + } + + Console.WriteLine("Testing static fields on type Bar ..."); + { + FieldInfo fi = typeof(Bar).GetTypeInfo().GetDeclaredField("s_intField"); + Verify(778899, (int)fi.GetValue(null)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("s_stringField"); + Verify("xxyyzz", (string)fi.GetValue(null)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("s_floatField"); + Verify(88.99f, (float)fi.GetValue(null)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("s_objectField"); + Verify("890", fi.GetValue(null)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("s_kvp"); + var result = (KeyValuePair)fi.GetValue(null); + Verify("7788", result.Key); + Verify("8899", result.Value); + + typeof(Bar).GetTypeInfo().GetDeclaredField("s_stringField").SetValue(null, "ThisIsAString5"); + typeof(Bar).GetTypeInfo().GetDeclaredField("s_objectField").SetValue(null, "ThisIsAString6"); + typeof(Bar).GetTypeInfo().GetDeclaredField("s_kvp").SetValue(null, new KeyValuePair("ThisIs2", "AString2")); + Verify("ThisIsAString5", (string)Bar.s_stringField); + Verify("ThisIsAString6", (string)Bar.s_objectField); + Verify("ThisIs2", (string)Bar.s_kvp.Key); + Verify("AString2", (string)Bar.s_kvp.Value); + } + } + + private static void TestInstanceFields() + { + Foo fooClassType = new Foo + { + m_intField = 1212, + m_stringField = "2323", + m_floatField = 34.34f, + m_objectField = "4545", + }; + + Foo fooStructType = new Foo + { + m_intField = 2323, + m_stringField = "3434", + m_floatField = 45.45f, + m_objectField = "5656", + }; + + Bar bar = new Bar + { + m_intField = 3434, + m_stringField = "4545", + m_floatField = 56.56f, + m_objectField = "6767", + }; + + Console.WriteLine("Testing instance fields on type Foo ..."); + { + FieldInfo fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_intField"); + Verify(1212, (int)fi.GetValue(fooClassType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_stringField"); + Verify("2323", (string)fi.GetValue(fooClassType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_floatField"); + Verify(34.34f, (float)fi.GetValue(fooClassType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_objectField"); + Verify("4545", fi.GetValue(fooClassType)); + + typeof(Foo).GetTypeInfo().GetDeclaredField("m_stringField").SetValue(fooClassType, "ThisIsAString7"); + typeof(Foo).GetTypeInfo().GetDeclaredField("m_objectField").SetValue(fooClassType, "ThisIsAString8"); + Verify("ThisIsAString7", (string)fooClassType.m_stringField); + Verify("ThisIsAString8", (string)fooClassType.m_objectField); + } + + Console.WriteLine("Testing instance fields on type Foo ..."); + { + FieldInfo fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_intField"); + Verify(2323, (int)fi.GetValue(fooStructType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_stringField"); + Verify("3434", (string)fi.GetValue(fooStructType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_floatField"); + Verify(45.45f, (float)fi.GetValue(fooStructType)); + + fi = typeof(Foo).GetTypeInfo().GetDeclaredField("m_objectField"); + Verify("5656", fi.GetValue(fooStructType)); + + typeof(Foo).GetTypeInfo().GetDeclaredField("m_stringField").SetValue(fooStructType, "ThisIsAString9"); + typeof(Foo).GetTypeInfo().GetDeclaredField("m_objectField").SetValue(fooStructType, "ThisIsAString10"); + Verify("ThisIsAString9", (string)fooStructType.m_stringField); + Verify("ThisIsAString10", (string)fooStructType.m_objectField); + } + + Console.WriteLine("Testing instance fields on type Bar ..."); + { + FieldInfo fi = typeof(Bar).GetTypeInfo().GetDeclaredField("m_intField"); + Verify(3434, (int)fi.GetValue(bar)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("m_stringField"); + Verify("4545", (string)fi.GetValue(bar)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("m_floatField"); + Verify(56.56f, (float)fi.GetValue(bar)); + + fi = typeof(Bar).GetTypeInfo().GetDeclaredField("m_objectField"); + Verify("6767", fi.GetValue(bar)); + + typeof(Bar).GetTypeInfo().GetDeclaredField("m_stringField").SetValue(bar, "ThisIsAString11"); + typeof(Bar).GetTypeInfo().GetDeclaredField("m_objectField").SetValue(bar, "ThisIsAString12"); + Verify("ThisIsAString11", (string)bar.m_stringField); + Verify("ThisIsAString12", (string)bar.m_objectField); + } + } + + private static void TestUnconstructedTypes() + { + // Testing for compilation failures due to references to unused static bases + // See: https://github.com/dotnet/corert/issues/3211 + var a = typeof(UnconstructedTypeInstantiator).ToString(); + var b = typeof(UnconstructedTypeInstantiator).ToString(); + } + + public static void Run() + { + TestStaticFields(); + TestInstanceFields(); + TestDynamicStaticFields(); + TestDynamicInvokeStubs(); + TestUnconstructedTypes(); + + if (s_NumErrors != 0) + throw new Exception(s_NumErrors + " errors!"); + } + } + + // Regression test for https://github.com/dotnet/corert/issues/3659 + class TestNativeLayoutGeneration + { +#pragma warning disable 649 // s_ref was never assigned + private static object s_ref; +#pragma warning restore 649 + + class Used + { + [MethodImpl(MethodImplOptions.NoInlining)] + public virtual string DoStuff() + { + return "Used"; + } + } + + class Unused : Used + { + [MethodImpl(MethodImplOptions.NoInlining)] + public override string DoStuff() + { + return "Unused " + typeof(T).ToString(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Blagh() + { + } + } + + public static void Run() + { + new Used().DoStuff(); + + try + { + // Call an instance method on something we never allocated, but overrides a used virtual. + // This asserted the compiler when trying to build a template for Unused<__Canon>. + ((Unused)s_ref).Blagh(); + } + catch (NullReferenceException) + { + return; + } + + throw new Exception(); + } + } + + class TestInterfaceVTableTracking + { + class Gen { } + + interface IFoo + { + Array Frob(); + } + + class GenericBase : IFoo + { + public Array Frob() + { + return new Gen[1, 1]; + } + } + + class Derived : GenericBase> + { + } + + static volatile IFoo> s_foo; + + public static void Run() + { + // This only really tests whether we can compile this. + s_foo = new Derived(); + Array arr = s_foo.Frob(); + arr.SetValue(new Gen>(), new int[] { 0, 0 }); + } + } + + class TestClassVTableTracking + { + class Unit { } + + class Gen + { + public virtual int Test() + { + return 42; + } + } + + static int Call() + { + return new Gen().Test(); + } + + public static void Run() + { + // This only really tests whether we can compile this. + Call(); + } + } + + class TestNullableCasting + { + struct Mine { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CallWithNullable(object m) + { + return m is T; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CallWithReferenceType(object m) + { + return m is Nullable>; + } + + public static void Run() + { + if (!CallWithNullable>>(new Mine())) + throw new Exception(); + + if (CallWithNullable>>(new Mine())) + throw new Exception(); + + if (!CallWithReferenceType(new Mine())) + throw new Exception(); + + if (CallWithReferenceType(new Mine())) + throw new Exception(); + + if (!(((object)new Mine()) is Nullable>)) + throw new Exception(); + } + } + + class TestVariantCasting + { + private delegate T GenericDelegate(); + + class Base { } + class Derived : Base { } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsInstanceOfGenericDelegateOf(object o) + { + return o is GenericDelegate; + } + + public static void Run() + { + GenericDelegate del = () => null; + if (!IsInstanceOfGenericDelegateOf(del)) + throw new Exception(); + } + } + + class TestByRefLikeVTables + { + class Atom { } + + ref struct RefStruct + { + public override bool Equals(object o) => o is Atom; + public override int GetHashCode() => 0; + + public override string ToString() + { + return typeof(T).ToString(); + } + } + + public static void Run() + { + // This is a regression test making sure we can build a vtable for the byref-like type. + // The vtable is necessary for a generic dictionary lookup in the ToString method. + // Method bodies of Equals and GetHashCode become reachable through the magical + // "unboxing" thunks we generate for byref-like types, and only through them. + RefStruct r = default; + if (r.ToString() != "System.String") + throw new Exception(); + } + } + + class TestDevirtualization + { + interface IDevirt + { + int GetAndSet(int x); + } + + struct Devirt : IDevirt + { + public int X; + + public int GetAndSet(int x) + { + int result = X; + X = x; + return result; + } + } + + interface IGenericDevirt + { + int GetAndSet(int x); + Type GetTheType(); + } + + struct GenericDevirt : IGenericDevirt + { + public int X; + + public int GetAndSet(int x) + { + int result = X; + X = x; + return result; + } + + public Type GetTheType() + { + return typeof(T); + } + } + + static void DoSimpleDevirt() + { + // This will potentially transform to a direct call + int result = ((IDevirt)new Devirt { X = 123 }).GetAndSet(456); + if (result != 123) + throw new Exception(); + } + + static void DoSimpleDevirtBoxed() + { + object o = new Devirt { X = 123 }; + + // Force o to be boxed no matter what + o.ToString(); + + // This will potentially transform to a direct call + int result = ((IDevirt)o).GetAndSet(456); + if (result != 123) + throw new Exception(); + + if (((Devirt)o).X != 456) + throw new Exception(); + } + + static void DoGenericDevirt() + { + // This will potentially transform to a direct call + int result1 = ((IGenericDevirt)new GenericDevirt { X = 123 }).GetAndSet(456); + if (result1 != 123) + throw new Exception(); + + // This will potentially transform to a direct call + Type result2 = ((IGenericDevirt)new GenericDevirt()).GetTheType(); + if (result2 != typeof(string)) + throw new Exception(); + } + + static void DoGenericDevirtBoxed() + { + object o1 = new GenericDevirt { X = 123 }; + + // Force o1 to be boxed no matter what + o1.ToString(); + + // This will potentially transform to a direct call + int result1 = ((IGenericDevirt)o1).GetAndSet(456); + if (result1 != 123) + throw new Exception(); + + if (((GenericDevirt)o1).X != 456) + throw new Exception(); + + object o2 = new GenericDevirt { X = 123 }; + + // Force o2 to be boxed no matter what + o2.ToString(); + + // This will potentially transform to a direct call + Type result2 = ((IGenericDevirt)o2).GetTheType(); + if (result2 != typeof(string)) + throw new Exception(); + } + + static void DoGenericDevirtShared() + { + // This will potentially transform to a direct call + int result1 = ((IGenericDevirt)new GenericDevirt { X = 123 }).GetAndSet(456); + if (result1 != 123) + throw new Exception(); + + // This will potentially transform to a direct call + Type result2 = ((IGenericDevirt)new GenericDevirt()).GetTheType(); + if (result2 != typeof(T[])) + throw new Exception(); + } + + static void DoGenericDevirtBoxedShared() + { + object o1 = new GenericDevirt { X = 123 }; + + // Force o1 to be boxed no matter what + o1.ToString(); + + // This will potentially transform to a direct call + int result1 = ((IGenericDevirt)o1).GetAndSet(456); + if (result1 != 123) + throw new Exception(); + + if (((GenericDevirt)o1).X != 456) + throw new Exception(); + + object o2 = new GenericDevirt { X = 123 }; + + // Force o2 to be boxed no matter what + o2.ToString(); + + // This will potentially transform to a direct call + Type result2 = ((IGenericDevirt)o2).GetTheType(); + if (result2 != typeof(T[])) + throw new Exception(); + } + + public static void Run() + { + DoSimpleDevirt(); + DoSimpleDevirtBoxed(); + DoGenericDevirt(); + DoGenericDevirtBoxed(); + DoGenericDevirtShared(); + DoGenericDevirtBoxedShared(); + } + } + + class TestGenericInlining + { + class NeverSeenInstantiated { } + + class AnotherNeverSeenInstantiated { } + + class NeverAllocatedIndirection + { + public string GetString() => new AnotherNeverSeenInstantiated().ToString(); + } + + class NeverAllocated + { + static NeverAllocatedIndirection s_indirection = null; + + public string GetString() => new NeverSeenInstantiated().ToString(); + public string GetStringIndirect() => s_indirection.GetString(); + } + + class Dummy { } + + static NeverAllocated s_neverAllocated = null; + + class GenericInline + { + public GenericInline() + { + _arr = (T)(object)new string[1] { "ohai" }; + } + T _arr; + public T GetArr() => _arr; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object InnerTest(object o, object dummy) => o; + + static object OtherTest() => null; + + [MethodImpl(MethodImplOptions.NoInlining)] + static object Test(GenericInline t) + { + return InnerTest(t.GetArr()[0], OtherTest()); + } + + public static void Run() + { + // We're just making sure the compiler doesn't crash. + // Both of the calls below are expected to get inlined by an optimized codegen, + // triggering interesting behaviors in the dependency analysis of the scanner + // that runs before compilation. + if (s_neverAllocated != null) + { + Console.WriteLine(s_neverAllocated.GetString()); + Console.WriteLine(s_neverAllocated.GetStringIndirect()); + } + + // Regression test for https://github.com/dotnet/corert/issues/7625 + if ((string)Test(new GenericInline()) != "ohai") + throw new Exception(); + } + } + +#if CODEGEN_WASM + internal class Console + { + private static unsafe void PrintString(string s) + { + int length = s.Length; + fixed (char* curChar = s) + { + for (int i = 0; i < length; i++) + { + TwoByteStr curCharStr = new TwoByteStr(); + curCharStr.first = (byte)(*(curChar + i)); + printf((byte*)&curCharStr, null); + } + } + } + + internal static void WriteLine(string s) + { + PrintString(s); + PrintString("\n"); + } + } + + struct TwoByteStr + { + public byte first; + public byte second; + } + + [DllImport("*")] + private static unsafe extern int printf(byte* str, byte* unused); +#endif +} diff --git a/src/tests/nativeaot/SmokeTests/Delegates/Delegates.csproj b/src/tests/nativeaot/SmokeTests/Generics/Generics.csproj similarity index 87% rename from src/tests/nativeaot/SmokeTests/Delegates/Delegates.csproj rename to src/tests/nativeaot/SmokeTests/Generics/Generics.csproj index a8702cdd6f9d..2deadb8799dd 100644 --- a/src/tests/nativeaot/SmokeTests/Delegates/Delegates.csproj +++ b/src/tests/nativeaot/SmokeTests/Generics/Generics.csproj @@ -6,6 +6,6 @@ true - + diff --git a/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.cs b/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.cs new file mode 100644 index 000000000000..f7ffd98b5fee --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.cs @@ -0,0 +1,395 @@ +// 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.Text; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +public class BringUpTest +{ + const int Pass = 100; + const int Fail = -1; + + public static int Main() + { + if (TestInterfaceCache() == Fail) + return Fail; + + if (TestMultipleInterfaces() == Fail) + return Fail; + + if (TestArrayInterfaces() == Fail) + return Fail; + + if (TestVariantInterfaces() == Fail) + return Fail; + + if (TestSpecialArrayInterfaces() == Fail) + return Fail; + + if (TestIterfaceCallOptimization() == Fail) + return Fail; + + return Pass; + } + + #region Interface Dispatch Cache Test + + private static int TestInterfaceCache() + { + MyInterface[] itfs = new MyInterface[50]; + + itfs[0] = new Foo0(); + itfs[1] = new Foo1(); + itfs[2] = new Foo2(); + itfs[3] = new Foo3(); + itfs[4] = new Foo4(); + itfs[5] = new Foo5(); + itfs[6] = new Foo6(); + itfs[7] = new Foo7(); + itfs[8] = new Foo8(); + itfs[9] = new Foo9(); + itfs[10] = new Foo10(); + itfs[11] = new Foo11(); + itfs[12] = new Foo12(); + itfs[13] = new Foo13(); + itfs[14] = new Foo14(); + itfs[15] = new Foo15(); + itfs[16] = new Foo16(); + itfs[17] = new Foo17(); + itfs[18] = new Foo18(); + itfs[19] = new Foo19(); + itfs[20] = new Foo20(); + itfs[21] = new Foo21(); + itfs[22] = new Foo22(); + itfs[23] = new Foo23(); + itfs[24] = new Foo24(); + itfs[25] = new Foo25(); + itfs[26] = new Foo26(); + itfs[27] = new Foo27(); + itfs[28] = new Foo28(); + itfs[29] = new Foo29(); + itfs[30] = new Foo30(); + itfs[31] = new Foo31(); + itfs[32] = new Foo32(); + itfs[33] = new Foo33(); + itfs[34] = new Foo34(); + itfs[35] = new Foo35(); + itfs[36] = new Foo36(); + itfs[37] = new Foo37(); + itfs[38] = new Foo38(); + itfs[39] = new Foo39(); + itfs[40] = new Foo40(); + itfs[41] = new Foo41(); + itfs[42] = new Foo42(); + itfs[43] = new Foo43(); + itfs[44] = new Foo44(); + itfs[45] = new Foo45(); + itfs[46] = new Foo46(); + itfs[47] = new Foo47(); + itfs[48] = new Foo48(); + itfs[49] = new Foo49(); + + StringBuilder sb = new StringBuilder(); + int counter = 0; + for (int i = 0; i < 50; i++) + { + sb.Append(itfs[i].GetAString()); + counter += itfs[i].GetAnInt(); + } + + string expected = "Foo0Foo1Foo2Foo3Foo4Foo5Foo6Foo7Foo8Foo9Foo10Foo11Foo12Foo13Foo14Foo15Foo16Foo17Foo18Foo19Foo20Foo21Foo22Foo23Foo24Foo25Foo26Foo27Foo28Foo29Foo30Foo31Foo32Foo33Foo34Foo35Foo36Foo37Foo38Foo39Foo40Foo41Foo42Foo43Foo44Foo45Foo46Foo47Foo48Foo49"; + + if (!expected.Equals(sb.ToString())) + { + Console.WriteLine("Concatenating strings from interface calls failed."); + Console.Write("Expected: "); + Console.WriteLine(expected); + Console.Write(" Actual: "); + Console.WriteLine(sb.ToString()); + return Fail; + } + + if (counter != 1225) + { + Console.WriteLine("Summing ints from interface calls failed."); + Console.WriteLine("Expected: 1225"); + Console.Write("Actual: "); + Console.WriteLine(counter); + return Fail; + } + + return 100; + } + + interface MyInterface + { + int GetAnInt(); + string GetAString(); + } + + class Foo0 : MyInterface { public int GetAnInt() { return 0; } public string GetAString() { return "Foo0"; } } + class Foo1 : MyInterface { public int GetAnInt() { return 1; } public string GetAString() { return "Foo1"; } } + class Foo2 : MyInterface { public int GetAnInt() { return 2; } public string GetAString() { return "Foo2"; } } + class Foo3 : MyInterface { public int GetAnInt() { return 3; } public string GetAString() { return "Foo3"; } } + class Foo4 : MyInterface { public int GetAnInt() { return 4; } public string GetAString() { return "Foo4"; } } + class Foo5 : MyInterface { public int GetAnInt() { return 5; } public string GetAString() { return "Foo5"; } } + class Foo6 : MyInterface { public int GetAnInt() { return 6; } public string GetAString() { return "Foo6"; } } + class Foo7 : MyInterface { public int GetAnInt() { return 7; } public string GetAString() { return "Foo7"; } } + class Foo8 : MyInterface { public int GetAnInt() { return 8; } public string GetAString() { return "Foo8"; } } + class Foo9 : MyInterface { public int GetAnInt() { return 9; } public string GetAString() { return "Foo9"; } } + class Foo10 : MyInterface { public int GetAnInt() { return 10; } public string GetAString() { return "Foo10"; } } + class Foo11 : MyInterface { public int GetAnInt() { return 11; } public string GetAString() { return "Foo11"; } } + class Foo12 : MyInterface { public int GetAnInt() { return 12; } public string GetAString() { return "Foo12"; } } + class Foo13 : MyInterface { public int GetAnInt() { return 13; } public string GetAString() { return "Foo13"; } } + class Foo14 : MyInterface { public int GetAnInt() { return 14; } public string GetAString() { return "Foo14"; } } + class Foo15 : MyInterface { public int GetAnInt() { return 15; } public string GetAString() { return "Foo15"; } } + class Foo16 : MyInterface { public int GetAnInt() { return 16; } public string GetAString() { return "Foo16"; } } + class Foo17 : MyInterface { public int GetAnInt() { return 17; } public string GetAString() { return "Foo17"; } } + class Foo18 : MyInterface { public int GetAnInt() { return 18; } public string GetAString() { return "Foo18"; } } + class Foo19 : MyInterface { public int GetAnInt() { return 19; } public string GetAString() { return "Foo19"; } } + class Foo20 : MyInterface { public int GetAnInt() { return 20; } public string GetAString() { return "Foo20"; } } + class Foo21 : MyInterface { public int GetAnInt() { return 21; } public string GetAString() { return "Foo21"; } } + class Foo22 : MyInterface { public int GetAnInt() { return 22; } public string GetAString() { return "Foo22"; } } + class Foo23 : MyInterface { public int GetAnInt() { return 23; } public string GetAString() { return "Foo23"; } } + class Foo24 : MyInterface { public int GetAnInt() { return 24; } public string GetAString() { return "Foo24"; } } + class Foo25 : MyInterface { public int GetAnInt() { return 25; } public string GetAString() { return "Foo25"; } } + class Foo26 : MyInterface { public int GetAnInt() { return 26; } public string GetAString() { return "Foo26"; } } + class Foo27 : MyInterface { public int GetAnInt() { return 27; } public string GetAString() { return "Foo27"; } } + class Foo28 : MyInterface { public int GetAnInt() { return 28; } public string GetAString() { return "Foo28"; } } + class Foo29 : MyInterface { public int GetAnInt() { return 29; } public string GetAString() { return "Foo29"; } } + class Foo30 : MyInterface { public int GetAnInt() { return 30; } public string GetAString() { return "Foo30"; } } + class Foo31 : MyInterface { public int GetAnInt() { return 31; } public string GetAString() { return "Foo31"; } } + class Foo32 : MyInterface { public int GetAnInt() { return 32; } public string GetAString() { return "Foo32"; } } + class Foo33 : MyInterface { public int GetAnInt() { return 33; } public string GetAString() { return "Foo33"; } } + class Foo34 : MyInterface { public int GetAnInt() { return 34; } public string GetAString() { return "Foo34"; } } + class Foo35 : MyInterface { public int GetAnInt() { return 35; } public string GetAString() { return "Foo35"; } } + class Foo36 : MyInterface { public int GetAnInt() { return 36; } public string GetAString() { return "Foo36"; } } + class Foo37 : MyInterface { public int GetAnInt() { return 37; } public string GetAString() { return "Foo37"; } } + class Foo38 : MyInterface { public int GetAnInt() { return 38; } public string GetAString() { return "Foo38"; } } + class Foo39 : MyInterface { public int GetAnInt() { return 39; } public string GetAString() { return "Foo39"; } } + class Foo40 : MyInterface { public int GetAnInt() { return 40; } public string GetAString() { return "Foo40"; } } + class Foo41 : MyInterface { public int GetAnInt() { return 41; } public string GetAString() { return "Foo41"; } } + class Foo42 : MyInterface { public int GetAnInt() { return 42; } public string GetAString() { return "Foo42"; } } + class Foo43 : MyInterface { public int GetAnInt() { return 43; } public string GetAString() { return "Foo43"; } } + class Foo44 : MyInterface { public int GetAnInt() { return 44; } public string GetAString() { return "Foo44"; } } + class Foo45 : MyInterface { public int GetAnInt() { return 45; } public string GetAString() { return "Foo45"; } } + class Foo46 : MyInterface { public int GetAnInt() { return 46; } public string GetAString() { return "Foo46"; } } + class Foo47 : MyInterface { public int GetAnInt() { return 47; } public string GetAString() { return "Foo47"; } } + class Foo48 : MyInterface { public int GetAnInt() { return 48; } public string GetAString() { return "Foo48"; } } + class Foo49 : MyInterface { public int GetAnInt() { return 49; } public string GetAString() { return "Foo49"; } } + + #endregion + + #region Implicit Interface Test + + private static int TestMultipleInterfaces() + { + TestClass testInt = new TestClass(5); + + MyInterface myInterface = testInt as MyInterface; + if (!myInterface.GetAString().Equals("TestClass")) + { + Console.Write("On type TestClass, MyInterface.GetAString() returned "); + Console.Write(myInterface.GetAString()); + Console.WriteLine(" Expected: TestClass"); + return Fail; + } + + + if (myInterface.GetAnInt() != 1) + { + Console.Write("On type TestClass, MyInterface.GetAnInt() returned "); + Console.Write(myInterface.GetAnInt()); + Console.WriteLine(" Expected: 1"); + return Fail; + } + + Interface itf = testInt as Interface; + if (itf.GetT() != 5) + { + Console.Write("On type TestClass, Interface::GetT() returned "); + Console.Write(itf.GetT()); + Console.WriteLine(" Expected: 5"); + return Fail; + } + + return Pass; + } + + interface Interface + { + T GetT(); + } + + class TestClass : MyInterface, Interface + { + T _t; + public TestClass(T t) + { + _t = t; + } + + public T GetT() + { + return _t; + } + + public int GetAnInt() + { + return 1; + } + + public string GetAString() + { + return "TestClass"; + } + } + #endregion + + #region Array Interfaces Test + private static int TestArrayInterfaces() + { + { + object stringArray = new string[] { "A", "B", "C", "D" }; + + Console.WriteLine("Testing IEnumerable on array..."); + string result = String.Empty; + foreach (var s in (System.Collections.Generic.IEnumerable)stringArray) + result += s; + + if (result != "ABCD") + { + Console.WriteLine("Failed."); + return Fail; + } + } + + { + object stringArray = new string[] { "A", "B", "C", "D" }; + + Console.WriteLine("Testing IEnumerable on array..."); + string result = String.Empty; + foreach (var s in (System.Collections.IEnumerable)stringArray) + result += s; + + if (result != "ABCD") + { + Console.WriteLine("Failed."); + return Fail; + } + } + + { + object intArray = new int[5, 5]; + + Console.WriteLine("Testing IList on MDArray..."); + if (((System.Collections.IList)intArray).Count != 25) + { + Console.WriteLine("Failed."); + return Fail; + } + } + + return Pass; + } + #endregion + + #region Variant interface tests + + interface IContravariantInterface + { + string DoContravariant(T value); + } + + interface ICovariantInterface + { + T DoCovariant(object value); + } + + class TypeWithVariantInterfaces : IContravariantInterface, ICovariantInterface + { + public string DoContravariant(T value) + { + return value.ToString(); + } + + public T DoCovariant(object value) + { + return value is T ? (T)value : default(T); + } + } + + static IContravariantInterface s_contravariantObject = new TypeWithVariantInterfaces(); + static ICovariantInterface s_covariantObject = new TypeWithVariantInterfaces(); + static IEnumerable s_arrayCovariantObject = (IEnumerable)(object)new uint[] { 5, 10, 15 }; + + private static int TestVariantInterfaces() + { + if (s_contravariantObject.DoContravariant("Hello") != "Hello") + return Fail; + + if (s_covariantObject.DoCovariant("World") as string != "World") + return Fail; + + int sum = 0; + foreach (var e in s_arrayCovariantObject) + sum += e; + + if (sum != 30) + return Fail; + + return Pass; + } + + class SpecialArrayBase { } + class SpecialArrayDerived : SpecialArrayBase { } + + // NOTE: ICollection is not a variant interface, but arrays can cast with it as if it was + static ICollection s_specialDerived = new SpecialArrayDerived[42]; + static ICollection s_specialInt = (ICollection)(object)new int[85]; + + private static int TestSpecialArrayInterfaces() + { + if (s_specialDerived.Count != 42) + return Fail; + + if (s_specialInt.Count != 85) + return Fail; + + return Pass; + } + + #endregion + + #region Interface call optimization tests + + public interface ISomeInterface + { + int SomeValue { get; } + } + + public abstract class SomeAbstractBaseClass : ISomeInterface + { + public abstract int SomeValue { get; } + } + + public class SomeClass : SomeAbstractBaseClass + { + public override int SomeValue + { + [MethodImpl(MethodImplOptions.NoInlining)] + get { return 14; } + } + } + + private static int TestIterfaceCallOptimization() + { + ISomeInterface test = new SomeClass(); + int v = test.SomeValue; + return (v == 14) ? Pass : Fail; + } + + #endregion +} diff --git a/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.csproj b/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.csproj new file mode 100644 index 000000000000..ffad48395d2c --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Interfaces/Interfaces.csproj @@ -0,0 +1,11 @@ + + + Exe + BuildAndRun + 0 + true + + + + + diff --git a/src/tests/nativeaot/SmokeTests/Threading/Threading.cs b/src/tests/nativeaot/SmokeTests/Threading/Threading.cs new file mode 100644 index 000000000000..c544bf3e41fe --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Threading/Threading.cs @@ -0,0 +1,1612 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +// TODO: Move these tests to CoreFX once they can be run on CoreRT + +internal static class Runner +{ + private const int Pass = 100; + + public static int Main() + { + Console.WriteLine(" WaitSubsystemTests.DoubleSetOnEventWithTimedOutWaiterShouldNotStayInWaitersList"); + WaitSubsystemTests.DoubleSetOnEventWithTimedOutWaiterShouldNotStayInWaitersList(); + + Console.WriteLine(" WaitSubsystemTests.ManualResetEventTest"); + WaitSubsystemTests.ManualResetEventTest(); + + Console.WriteLine(" WaitSubsystemTests.AutoResetEventTest"); + WaitSubsystemTests.AutoResetEventTest(); + + Console.WriteLine(" WaitSubsystemTests.SemaphoreTest"); + WaitSubsystemTests.SemaphoreTest(); + + Console.WriteLine(" WaitSubsystemTests.MutexTest"); + WaitSubsystemTests.MutexTest(); + + Console.WriteLine(" WaitSubsystemTests.WaitDurationTest"); + WaitSubsystemTests.WaitDurationTest(); + + // This test takes a long time to run in release and especially in debug builds. Enable for manual testing. + //Console.WriteLine(" WaitSubsystemTests.MutexMaximumReacquireCountTest"); + //WaitSubsystemTests.MutexMaximumReacquireCountTest(); + + Console.WriteLine(" TimersCreatedConcurrentlyOnDifferentThreadsAllFire"); + TimerTests.TimersCreatedConcurrentlyOnDifferentThreadsAllFire(); + + Console.WriteLine(" ThreadPoolTests.RunProcessorCountItemsInParallel"); + ThreadPoolTests.RunProcessorCountItemsInParallel(); + + Console.WriteLine(" ThreadPoolTests.RunMoreThanMaxJobsMakesOneJobWaitForStarvationDetection"); + ThreadPoolTests.RunMoreThanMaxJobsMakesOneJobWaitForStarvationDetection(); + + Console.WriteLine(" ThreadPoolTests.ThreadPoolCanPickUpOneJobWhenThreadIsAvailable"); + ThreadPoolTests.ThreadPoolCanPickUpOneJobWhenThreadIsAvailable(); + + Console.WriteLine(" ThreadPoolTests.ThreadPoolCanPickUpMultipleJobsWhenThreadsAreAvailable"); + ThreadPoolTests.ThreadPoolCanPickUpMultipleJobsWhenThreadsAreAvailable(); + + Console.WriteLine(" ThreadPoolTests.ThreadPoolCanProcessManyWorkItemsInParallelWithoutDeadlocking"); + ThreadPoolTests.ThreadPoolCanProcessManyWorkItemsInParallelWithoutDeadlocking(); + + // This test takes a long time to run (min 42 seconds sleeping). Enable for manual testing. + // Console.WriteLine(" ThreadPoolTests.RunJobsAfterThreadTimeout"); + // ThreadPoolTests.RunJobsAfterThreadTimeout(); + + Console.WriteLine(" ThreadPoolTests.WorkQueueDepletionTest"); + ThreadPoolTests.WorkQueueDepletionTest(); + + Console.WriteLine(" ThreadPoolTests.WorkerThreadStateReset"); + ThreadPoolTests.WorkerThreadStateReset(); + + // This test is not applicable (and will not pass) on Windows since it uses the Windows OS-provided thread pool. + // Console.WriteLine(" ThreadPoolTests.SettingMinThreadsWillCreateThreadsUpToMinimum"); + // ThreadPoolTests.SettingMinThreadsWillCreateThreadsUpToMinimum(); + + Console.WriteLine(" WaitThreadTests.SignalingRegisteredHandleCallsCalback"); + WaitThreadTests.SignalingRegisteredHandleCallsCalback(); + + Console.WriteLine(" WaitThreadTests.TimingOutRegisteredHandleCallsCallback"); + WaitThreadTests.TimingOutRegisteredHandleCallsCallback(); + + Console.WriteLine(" WaitThreadTests.UnregisteringBeforeSignalingDoesNotCallCallback"); + WaitThreadTests.UnregisteringBeforeSignalingDoesNotCallCallback(); + + Console.WriteLine(" WaitThreadTests.RepeatingWaitFiresUntilUnregistered"); + WaitThreadTests.RepeatingWaitFiresUntilUnregistered(); + + Console.WriteLine(" WaitThreadTests.UnregisterEventSignaledWhenUnregistered"); + WaitThreadTests.UnregisterEventSignaledWhenUnregistered(); + + Console.WriteLine(" WaitThreadTests.CanRegisterMoreThan64Waits"); + WaitThreadTests.CanRegisterMoreThan64Waits(); + + Console.WriteLine(" WaitThreadTests.StateIsPasssedThroughToCallback"); + WaitThreadTests.StateIsPasssedThroughToCallback(); + + + // This test takes a long time to run. Enable for manual testing. + // Console.WriteLine(" WaitThreadTests.WaitWithLongerTimeoutThanWaitThreadCanStillTimeout"); + // WaitThreadTests.WaitWithLongerTimeoutThanWaitThreadCanStillTimeout(); + + Console.WriteLine(" WaitThreadTests.UnregisterCallbackIsNotCalledAfterCallbackFinishesIfAnotherCallbackOnSameWaitRunning"); + WaitThreadTests.UnregisterCallbackIsNotCalledAfterCallbackFinishesIfAnotherCallbackOnSameWaitRunning(); + + Console.WriteLine(" WaitThreadTests.CallingUnregisterOnAutomaticallyUnregisteredHandleReturnsTrue"); + WaitThreadTests.CallingUnregisterOnAutomaticallyUnregisteredHandleReturnsTrue(); + + Console.WriteLine(" WaitThreadTests.EventSetAfterUnregisterNotObservedOnWaitThread"); + WaitThreadTests.EventSetAfterUnregisterNotObservedOnWaitThread(); + + Console.WriteLine(" WaitThreadTests.BlockingUnregister"); + WaitThreadTests.BlockingUnregister(); + + Console.WriteLine(" WaitThreadTests.CanDisposeEventAfterUnblockingUnregister"); + WaitThreadTests.CanDisposeEventAfterUnblockingUnregister(); + + Console.WriteLine(" WaitThreadTests.UnregisterEventSignaledWhenUnregisteredEvenIfAutoUnregistered"); + WaitThreadTests.UnregisterEventSignaledWhenUnregisteredEvenIfAutoUnregistered(); + + + Console.WriteLine(" WaitThreadTests.BlockingUnregisterBlocksEvenIfCallbackExecuting"); + WaitThreadTests.BlockingUnregisterBlocksEvenIfCallbackExecuting(); + + return Pass; + } +} + +internal static class WaitSubsystemTests +{ + private const int AcceptableEarlyWaitTerminationDiffMilliseconds = -100; + private const int AcceptableLateWaitTerminationDiffMilliseconds = 300; + + [Fact] + public static void ManualResetEventTest() + { + // Constructor and dispose + + var e = new ManualResetEvent(false); + Assert.False(e.WaitOne(0)); + e.Dispose(); + Assert.Throws(() => e.Reset()); + Assert.Throws(() => e.Set()); + Assert.Throws(() => e.WaitOne(0)); + + e = new ManualResetEvent(true); + Assert.True(e.WaitOne(0)); + + // Set and reset + + e = new ManualResetEvent(true); + e.Reset(); + Assert.False(e.WaitOne(0)); + Assert.False(e.WaitOne(0)); + e.Reset(); + Assert.False(e.WaitOne(0)); + e.Set(); + Assert.True(e.WaitOne(0)); + Assert.True(e.WaitOne(0)); + e.Set(); + Assert.True(e.WaitOne(0)); + + // Wait + + e.Set(); + Assert.True(e.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.True(e.WaitOne()); + + e.Reset(); + Assert.False(e.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + + e = null; + + // Multi-wait with all indexes set + var es = + new ManualResetEvent[] + { + new ManualResetEvent(true), + new ManualResetEvent(true), + new ManualResetEvent(true), + new ManualResetEvent(true) + }; + Assert.Equal(0, WaitHandle.WaitAny(es, 0)); + Assert.Equal(0, WaitHandle.WaitAny(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.Equal(0, WaitHandle.WaitAny(es)); + Assert.True(WaitHandle.WaitAll(es, 0)); + Assert.True(WaitHandle.WaitAll(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.True(WaitHandle.WaitAll(es)); + for (int i = 0; i < es.Length; ++i) + { + Assert.True(es[i].WaitOne(0)); + } + + // Multi-wait with indexes 1 and 2 set + es[0].Reset(); + es[3].Reset(); + Assert.Equal(1, WaitHandle.WaitAny(es, 0)); + Assert.Equal(1, WaitHandle.WaitAny(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.False(WaitHandle.WaitAll(es, 0)); + Assert.False(WaitHandle.WaitAll(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i == 1 || i == 2, es[i].WaitOne(0)); + } + + // Multi-wait with all indexes reset + es[1].Reset(); + es[2].Reset(); + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(es, 0)); + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + Assert.False(WaitHandle.WaitAll(es, 0)); + Assert.False(WaitHandle.WaitAll(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.False(es[i].WaitOne(0)); + } + } + + [Fact] + public static void AutoResetEventTest() + { + // Constructor and dispose + + var e = new AutoResetEvent(false); + Assert.False(e.WaitOne(0)); + e.Dispose(); + Assert.Throws(() => e.Reset()); + Assert.Throws(() => e.Set()); + Assert.Throws(() => e.WaitOne(0)); + + e = new AutoResetEvent(true); + Assert.True(e.WaitOne(0)); + + // Set and reset + + e = new AutoResetEvent(true); + e.Reset(); + Assert.False(e.WaitOne(0)); + Assert.False(e.WaitOne(0)); + e.Reset(); + Assert.False(e.WaitOne(0)); + e.Set(); + Assert.True(e.WaitOne(0)); + Assert.False(e.WaitOne(0)); + e.Set(); + e.Set(); + Assert.True(e.WaitOne(0)); + + // Wait + + e.Set(); + Assert.True(e.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.False(e.WaitOne(0)); + e.Set(); + Assert.True(e.WaitOne()); + Assert.False(e.WaitOne(0)); + + e.Reset(); + Assert.False(e.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + + e = null; + + // Multi-wait with all indexes set + var es = + new AutoResetEvent[] + { + new AutoResetEvent(true), + new AutoResetEvent(true), + new AutoResetEvent(true), + new AutoResetEvent(true) + }; + Assert.Equal(0, WaitHandle.WaitAny(es, 0)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i > 0, es[i].WaitOne(0)); + es[i].Set(); + } + Assert.Equal(0, WaitHandle.WaitAny(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i > 0, es[i].WaitOne(0)); + es[i].Set(); + } + Assert.Equal(0, WaitHandle.WaitAny(es)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i > 0, es[i].WaitOne(0)); + es[i].Set(); + } + Assert.True(WaitHandle.WaitAll(es, 0)); + for (int i = 0; i < es.Length; ++i) + { + Assert.False(es[i].WaitOne(0)); + es[i].Set(); + } + Assert.True(WaitHandle.WaitAll(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.False(es[i].WaitOne(0)); + es[i].Set(); + } + Assert.True(WaitHandle.WaitAll(es)); + for (int i = 0; i < es.Length; ++i) + { + Assert.False(es[i].WaitOne(0)); + } + + // Multi-wait with indexes 1 and 2 set + es[1].Set(); + es[2].Set(); + Assert.Equal(1, WaitHandle.WaitAny(es, 0)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i == 2, es[i].WaitOne(0)); + } + es[1].Set(); + es[2].Set(); + Assert.Equal(1, WaitHandle.WaitAny(es, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i == 2, es[i].WaitOne(0)); + } + es[1].Set(); + es[2].Set(); + Assert.False(WaitHandle.WaitAll(es, 0)); + Assert.False(WaitHandle.WaitAll(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.Equal(i == 1 || i == 2, es[i].WaitOne(0)); + } + + // Multi-wait with all indexes reset + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(es, 0)); + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + Assert.False(WaitHandle.WaitAll(es, 0)); + Assert.False(WaitHandle.WaitAll(es, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < es.Length; ++i) + { + Assert.False(es[i].WaitOne(0)); + } + } + + [Fact] + public static void SemaphoreTest() + { + // Constructor and dispose + + Assert.Throws(() => new Semaphore(-1, 1)); + Assert.Throws(() => new Semaphore(0, 0)); + Assert.Throws(() => new Semaphore(2, 1)); + + var s = new Semaphore(0, 1); + Assert.False(s.WaitOne(0)); + s.Dispose(); + Assert.Throws(() => s.Release()); + Assert.Throws(() => s.WaitOne(0)); + + s = new Semaphore(1, 1); + Assert.True(s.WaitOne(0)); + + // Signal and unsignal + + Assert.Throws(() => s.Release(0)); + + s = new Semaphore(1, 1); + Assert.True(s.WaitOne(0)); + Assert.False(s.WaitOne(0)); + Assert.False(s.WaitOne(0)); + Assert.Equal(0, s.Release()); + Assert.True(s.WaitOne(0)); + Assert.Throws(() => s.Release(2)); + Assert.Equal(0, s.Release()); + Assert.Throws(() => s.Release()); + + s = new Semaphore(1, 2); + Assert.Throws(() => s.Release(2)); + Assert.Equal(1, s.Release(1)); + Assert.True(s.WaitOne(0)); + Assert.True(s.WaitOne(0)); + Assert.Throws(() => s.Release(3)); + Assert.Equal(0, s.Release(2)); + Assert.Throws(() => s.Release()); + + // Wait + + s = new Semaphore(1, 2); + Assert.True(s.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.False(s.WaitOne(0)); + s.Release(); + Assert.True(s.WaitOne()); + Assert.False(s.WaitOne(0)); + s.Release(2); + Assert.True(s.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + s.Release(); + Assert.True(s.WaitOne()); + + s = new Semaphore(0, 2); + Assert.False(s.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + + s = null; + + // Multi-wait with all indexes signaled + var ss = + new Semaphore[] + { + new Semaphore(1, 1), + new Semaphore(1, 1), + new Semaphore(1, 1), + new Semaphore(1, 1) + }; + Assert.Equal(0, WaitHandle.WaitAny(ss, 0)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i > 0, ss[i].WaitOne(0)); + ss[i].Release(); + } + Assert.Equal(0, WaitHandle.WaitAny(ss, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i > 0, ss[i].WaitOne(0)); + ss[i].Release(); + } + Assert.Equal(0, WaitHandle.WaitAny(ss)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i > 0, ss[i].WaitOne(0)); + ss[i].Release(); + } + Assert.True(WaitHandle.WaitAll(ss, 0)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.False(ss[i].WaitOne(0)); + ss[i].Release(); + } + Assert.True(WaitHandle.WaitAll(ss, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.False(ss[i].WaitOne(0)); + ss[i].Release(); + } + Assert.True(WaitHandle.WaitAll(ss)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.False(ss[i].WaitOne(0)); + } + + // Multi-wait with indexes 1 and 2 signaled + ss[1].Release(); + ss[2].Release(); + Assert.Equal(1, WaitHandle.WaitAny(ss, 0)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i == 2, ss[i].WaitOne(0)); + } + ss[1].Release(); + ss[2].Release(); + Assert.Equal(1, WaitHandle.WaitAny(ss, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i == 2, ss[i].WaitOne(0)); + } + ss[1].Release(); + ss[2].Release(); + Assert.False(WaitHandle.WaitAll(ss, 0)); + Assert.False(WaitHandle.WaitAll(ss, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.Equal(i == 1 || i == 2, ss[i].WaitOne(0)); + } + + // Multi-wait with all indexes unsignaled + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(ss, 0)); + Assert.Equal(WaitHandle.WaitTimeout, WaitHandle.WaitAny(ss, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + Assert.False(WaitHandle.WaitAll(ss, 0)); + Assert.False(WaitHandle.WaitAll(ss, ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + for (int i = 0; i < ss.Length; ++i) + { + Assert.False(ss[i].WaitOne(0)); + } + } + + // There is a race condition between a timed out WaitOne and a Set call not clearing the waiters list + // in the wait subsystem (Unix only). More information can be found at + // https://github.com/dotnet/corert/issues/3616 and https://github.com/dotnet/corert/pull/3782. + [Fact] + public static void DoubleSetOnEventWithTimedOutWaiterShouldNotStayInWaitersList() + { + AutoResetEvent threadStartedEvent = new AutoResetEvent(false); + AutoResetEvent resetEvent = new AutoResetEvent(false); + Thread thread = new Thread(() => { + threadStartedEvent.Set(); + Thread.Sleep(50); + resetEvent.Set(); + resetEvent.Set(); + }); + + thread.IsBackground = true; + thread.Start(); + threadStartedEvent.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + resetEvent.WaitOne(50); + thread.Join(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + } + + [Fact] + public static void MutexTest() + { + // Constructor and dispose + + var m = new Mutex(); + Assert.True(m.WaitOne(0)); + m.ReleaseMutex(); + m.Dispose(); + Assert.Throws(() => m.ReleaseMutex()); + Assert.Throws(() => m.WaitOne(0)); + + m = new Mutex(false); + Assert.True(m.WaitOne(0)); + m.ReleaseMutex(); + + m = new Mutex(true); + Assert.True(m.WaitOne(0)); + m.ReleaseMutex(); + m.ReleaseMutex(); + + m = new Mutex(true); + Assert.True(m.WaitOne(0)); + m.Dispose(); + Assert.Throws(() => m.ReleaseMutex()); + Assert.Throws(() => m.WaitOne(0)); + + // Acquire and release + + m = new Mutex(); + VerifyThrowsApplicationException(() => m.ReleaseMutex()); + Assert.True(m.WaitOne(0)); + m.ReleaseMutex(); + VerifyThrowsApplicationException(() => m.ReleaseMutex()); + Assert.True(m.WaitOne(0)); + Assert.True(m.WaitOne(0)); + Assert.True(m.WaitOne(0)); + m.ReleaseMutex(); + m.ReleaseMutex(); + m.ReleaseMutex(); + VerifyThrowsApplicationException(() => m.ReleaseMutex()); + + // Wait + + Assert.True(m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.True(m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + m.ReleaseMutex(); + m.ReleaseMutex(); + Assert.True(m.WaitOne()); + Assert.True(m.WaitOne()); + m.ReleaseMutex(); + m.ReleaseMutex(); + + m = null; + + // Multi-wait with all indexes unlocked + var ms = + new Mutex[] + { + new Mutex(), + new Mutex(), + new Mutex(), + new Mutex() + }; + Assert.Equal(0, WaitHandle.WaitAny(ms, 0)); + ms[0].ReleaseMutex(); + for (int i = 1; i < ms.Length; ++i) + { + VerifyThrowsApplicationException(() => ms[i].ReleaseMutex()); + } + Assert.Equal(0, WaitHandle.WaitAny(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + ms[0].ReleaseMutex(); + for (int i = 1; i < ms.Length; ++i) + { + VerifyThrowsApplicationException(() => ms[i].ReleaseMutex()); + } + Assert.Equal(0, WaitHandle.WaitAny(ms)); + ms[0].ReleaseMutex(); + for (int i = 1; i < ms.Length; ++i) + { + VerifyThrowsApplicationException(() => ms[i].ReleaseMutex()); + } + Assert.True(WaitHandle.WaitAll(ms, 0)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + + // Multi-wait with indexes 0 and 3 locked + ms[0].WaitOne(0); + ms[3].WaitOne(0); + Assert.Equal(0, WaitHandle.WaitAny(ms, 0)); + ms[0].ReleaseMutex(); + VerifyThrowsApplicationException(() => ms[1].ReleaseMutex()); + VerifyThrowsApplicationException(() => ms[2].ReleaseMutex()); + Assert.Equal(0, WaitHandle.WaitAny(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + ms[0].ReleaseMutex(); + VerifyThrowsApplicationException(() => ms[1].ReleaseMutex()); + VerifyThrowsApplicationException(() => ms[2].ReleaseMutex()); + Assert.Equal(0, WaitHandle.WaitAny(ms)); + ms[0].ReleaseMutex(); + VerifyThrowsApplicationException(() => ms[1].ReleaseMutex()); + VerifyThrowsApplicationException(() => ms[2].ReleaseMutex()); + Assert.True(WaitHandle.WaitAll(ms, 0)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + ms[0].ReleaseMutex(); + VerifyThrowsApplicationException(() => ms[1].ReleaseMutex()); + VerifyThrowsApplicationException(() => ms[2].ReleaseMutex()); + ms[3].ReleaseMutex(); + + // Multi-wait with all indexes locked + for (int i = 0; i < ms.Length; ++i) + { + Assert.True(ms[i].WaitOne(0)); + } + Assert.Equal(0, WaitHandle.WaitAny(ms, 0)); + ms[0].ReleaseMutex(); + Assert.Equal(0, WaitHandle.WaitAny(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + ms[0].ReleaseMutex(); + Assert.Equal(0, WaitHandle.WaitAny(ms)); + ms[0].ReleaseMutex(); + Assert.True(WaitHandle.WaitAll(ms, 0)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + Assert.True(WaitHandle.WaitAll(ms)); + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + } + for (int i = 0; i < ms.Length; ++i) + { + ms[i].ReleaseMutex(); + VerifyThrowsApplicationException(() => ms[i].ReleaseMutex()); + } + } + + private static void VerifyThrowsApplicationException(Action action) + { + // TODO: netstandard2.0 - After switching to ns2.0 contracts, use Assert.Throws instead of this function + // TODO: Enable this verification. There currently seem to be some reliability issues surrounding exceptions on Unix. + //try + //{ + // action(); + //} + //catch (Exception ex) + //{ + // if (ex.HResult == unchecked((int)0x80131600)) + // return; + // Console.WriteLine( + // "VerifyThrowsApplicationException: Assertion failure - Assert.Throws: got {1}", + // ex.GetType()); + // throw new AssertionFailureException(ex); + //} + //Console.WriteLine( + // "VerifyThrowsApplicationException: Assertion failure - Assert.Throws: got no exception"); + //throw new AssertionFailureException(); + } + + [Fact] + [OuterLoop] + public static void WaitDurationTest() + { + VerifyWaitDuration( + new ManualResetEvent(false), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds); + VerifyWaitDuration( + new ManualResetEvent(true), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: 0); + + VerifyWaitDuration( + new AutoResetEvent(false), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds); + VerifyWaitDuration( + new AutoResetEvent(true), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: 0); + + VerifyWaitDuration( + new Semaphore(0, 1), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds); + VerifyWaitDuration( + new Semaphore(1, 1), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: 0); + + VerifyWaitDuration( + new Mutex(true), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: 0); + VerifyWaitDuration( + new Mutex(false), + waitTimeoutMilliseconds: ThreadTestHelpers.ExpectedMeasurableTimeoutMilliseconds, + expectedWaitTerminationAfterMilliseconds: 0); + } + + private static void VerifyWaitDuration( + WaitHandle wh, + int waitTimeoutMilliseconds, + int expectedWaitTerminationAfterMilliseconds) + { + Assert.True(waitTimeoutMilliseconds > 0); + Assert.True(expectedWaitTerminationAfterMilliseconds >= 0); + + var sw = new Stopwatch(); + sw.Restart(); + Assert.Equal(expectedWaitTerminationAfterMilliseconds == 0, wh.WaitOne(waitTimeoutMilliseconds)); + sw.Stop(); + int waitDurationDiffMilliseconds = (int)sw.ElapsedMilliseconds - expectedWaitTerminationAfterMilliseconds; + Assert.True(waitDurationDiffMilliseconds >= AcceptableEarlyWaitTerminationDiffMilliseconds); + Assert.True(waitDurationDiffMilliseconds <= AcceptableLateWaitTerminationDiffMilliseconds); + } + + //[Fact] // This test takes a long time to run in release and especially in debug builds. Enable for manual testing. + public static void MutexMaximumReacquireCountTest() + { + // Create a mutex with the maximum reacquire count + var m = new Mutex(); + int tenPercent = int.MaxValue / 10; + int progressPercent = 0; + Console.Write(" 0%"); + for (int i = 0; i >= 0; ++i) + { + if (i >= (progressPercent + 1) * tenPercent) + { + ++progressPercent; + if (progressPercent < 10) + { + Console.Write(" {0}%", progressPercent * 10); + } + } + Assert.True(m.WaitOne(0)); + } + Assert.Throws( + () => + { + // Windows allows a slightly higher maximum reacquire count than this implementation + Assert.True(m.WaitOne(0)); + Assert.True(m.WaitOne(0)); + }); + Console.WriteLine(" 100%"); + + // Single wait fails + Assert.Throws(() => m.WaitOne(0)); + Assert.Throws(() => m.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + Assert.Throws(() => m.WaitOne()); + + var e0 = new AutoResetEvent(false); + var s0 = new Semaphore(0, 1); + var e1 = new AutoResetEvent(false); + var s1 = new Semaphore(0, 1); + var h = new WaitHandle[] { e0, s0, m, e1, s1 }; + Action init = + (signale0, signals0, signale1, signals1) => + { + if (signale0) + e0.Set(); + else + e0.Reset(); + s0.WaitOne(0); + if (signals0) + s0.Release(); + if (signale1) + e1.Set(); + else + e1.Reset(); + s1.WaitOne(0); + if (signals1) + s1.Release(); + }; + Action verify = + (e0signaled, s0signaled, e1signaled, s1signaled) => + { + Assert.Equal(e0signaled, e0.WaitOne(0)); + Assert.Equal(s0signaled, s0.WaitOne(0)); + Assert.Throws(() => m.WaitOne(0)); + Assert.Equal(e1signaled, e1.WaitOne(0)); + Assert.Equal(s1signaled, s1.WaitOne(0)); + init(e0signaled, s0signaled, e1signaled, s1signaled); + }; + + // WaitAny succeeds when a signaled object is before the mutex + init(true, true, true, true); + Assert.Equal(0, WaitHandle.WaitAny(h, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + verify(false, true, true, true); + Assert.Equal(1, WaitHandle.WaitAny(h, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + verify(false, false, true, true); + + // WaitAny fails when all objects before the mutex are unsignaled + init(false, false, true, true); + Assert.Throws(() => WaitHandle.WaitAny(h, 0)); + verify(false, false, true, true); + Assert.Throws(() => WaitHandle.WaitAny(h, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + verify(false, false, true, true); + Assert.Throws(() => WaitHandle.WaitAny(h)); + verify(false, false, true, true); + + // WaitAll fails and does not unsignal any signaled object + init(true, true, true, true); + Assert.Throws(() => WaitHandle.WaitAll(h, 0)); + verify(true, true, true, true); + Assert.Throws(() => WaitHandle.WaitAll(h, ThreadTestHelpers.UnexpectedTimeoutMilliseconds)); + verify(true, true, true, true); + Assert.Throws(() => WaitHandle.WaitAll(h)); + verify(true, true, true, true); + + m.Dispose(); + } +} + +internal static class TimerTests +{ + [Fact] + public static void TimersCreatedConcurrentlyOnDifferentThreadsAllFire() + { + int processorCount = Environment.ProcessorCount; + + int timerTickCount = 0; + TimerCallback timerCallback = data => Interlocked.Increment(ref timerTickCount); + + var threadStarted = new AutoResetEvent(false); + var createTimers = new ManualResetEvent(false); + var timers = new Timer[processorCount]; + Action createTimerThreadStart = data => + { + int i = (int)data; + var sw = new Stopwatch(); + threadStarted.Set(); + createTimers.WaitOne(); + + // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently + sw.Restart(); + do + { + Thread.SpinWait(1000); + } while (sw.ElapsedMilliseconds < 10); + + timers[i] = new Timer(timerCallback, null, 1, Timeout.Infinite); + + // Use the CPU a bit around creating the timer to try to have some of these threads run concurrently + sw.Restart(); + do + { + Thread.SpinWait(1000); + } while (sw.ElapsedMilliseconds < 10); + }; + + var waitsForThread = new Action[timers.Length]; + for (int i = 0; i < timers.Length; ++i) + { + var t = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], createTimerThreadStart); + t.IsBackground = true; + t.Start(i); + threadStarted.CheckedWait(); + } + + createTimers.Set(); + ThreadTestHelpers.WaitForCondition(() => timerTickCount == timers.Length); + + foreach (var waitForThread in waitsForThread) + { + waitForThread(); + } + } +} + +internal static class ThreadPoolTests +{ + [Fact] + public static void RunProcessorCountItemsInParallel() + { + int count = 0; + AutoResetEvent e0 = new AutoResetEvent(false); + for(int i = 0; i < Environment.ProcessorCount; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount) + { + e0.Set(); + } + }); + } + e0.CheckedWait(); + // Run the test again to make sure we can reuse the threads. + count = 0; + for(int i = 0; i < Environment.ProcessorCount; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount) + { + e0.Set(); + } + }); + } + e0.CheckedWait(); + } + + [Fact] + public static void RunMoreThanMaxJobsMakesOneJobWaitForStarvationDetection() + { + ManualResetEvent e0 = new ManualResetEvent(false); + AutoResetEvent jobsQueued = new AutoResetEvent(false); + int count = 0; + AutoResetEvent e1 = new AutoResetEvent(false); + for(int i = 0; i < Environment.ProcessorCount; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount) + { + jobsQueued.Set(); + } + e0.CheckedWait(); + }); + } + jobsQueued.CheckedWait(); + ThreadPool.QueueUserWorkItem( _ => e1.Set()); + Thread.Sleep(500); // Sleep for the gate thread delay to wait for starvation + e1.CheckedWait(); + e0.Set(); + } + + [Fact] + public static void ThreadPoolCanPickUpOneJobWhenThreadIsAvailable() + { + ManualResetEvent e0 = new ManualResetEvent(false); + AutoResetEvent jobsQueued = new AutoResetEvent(false); + AutoResetEvent testJobCompleted = new AutoResetEvent(false); + int count = 0; + + for(int i = 0; i < Environment.ProcessorCount - 1; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount - 1) + { + jobsQueued.Set(); + } + e0.CheckedWait(); + }); + } + jobsQueued.CheckedWait(); + ThreadPool.QueueUserWorkItem( _ => testJobCompleted.Set()); + testJobCompleted.CheckedWait(); + e0.Set(); + } + + [Fact] + public static void ThreadPoolCanPickUpMultipleJobsWhenThreadsAreAvailable() + { + ManualResetEvent e0 = new ManualResetEvent(false); + AutoResetEvent jobsQueued = new AutoResetEvent(false); + AutoResetEvent testJobCompleted = new AutoResetEvent(false); + int count = 0; + + for(int i = 0; i < Environment.ProcessorCount - 1; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount - 1) + { + jobsQueued.Set(); + } + e0.CheckedWait(); + }); + } + jobsQueued.CheckedWait(); + int testJobsCount = 0; + int maxCount = 5; + void Job(object _) + { + if(Interlocked.Increment(ref testJobsCount) != maxCount) + { + ThreadPool.QueueUserWorkItem(Job); + } + else + { + testJobCompleted.Set(); + } + } + ThreadPool.QueueUserWorkItem(Job); + testJobCompleted.CheckedWait(); + e0.Set(); + } + + // See https://github.com/dotnet/corert/issues/6780 + [Fact] + public static void ThreadPoolCanProcessManyWorkItemsInParallelWithoutDeadlocking() + { + int iterationCount = 100_000; + var done = new ManualResetEvent(false); + + WaitCallback wc = null; + wc = data => + { + int n = Interlocked.Decrement(ref iterationCount); + if (n == 0) + { + done.Set(); + } + else if (n > 0) + { + ThreadPool.QueueUserWorkItem(wc); + } + }; + + for (int i = 0, n = Environment.ProcessorCount; i < n; ++i) + { + ThreadPool.QueueUserWorkItem(wc); + } + done.WaitOne(); + } + + private static WaitCallback CreateRecursiveJob(int jobCount, int targetJobCount, AutoResetEvent testJobCompleted) + { + return _ => + { + if (jobCount == targetJobCount) + { + testJobCompleted.Set(); + } + else + { + ThreadPool.QueueUserWorkItem(CreateRecursiveJob(jobCount + 1, targetJobCount, testJobCompleted)); + } + }; + } + + [Fact] + [OuterLoop] + public static void RunJobsAfterThreadTimeout() + { + ManualResetEvent e0 = new ManualResetEvent(false); + AutoResetEvent jobsQueued = new AutoResetEvent(false); + AutoResetEvent testJobCompleted = new AutoResetEvent(false); + int count = 0; + + for(int i = 0; i < Environment.ProcessorCount - 1; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == Environment.ProcessorCount - 1) + { + jobsQueued.Set(); + } + e0.CheckedWait(); + }); + } + jobsQueued.CheckedWait(); + ThreadPool.QueueUserWorkItem( _ => testJobCompleted.Set()); + testJobCompleted.CheckedWait(); + Console.Write("Sleeping to time out thread\n"); + Thread.Sleep(21000); + ThreadPool.QueueUserWorkItem( _ => testJobCompleted.Set()); + testJobCompleted.CheckedWait(); + e0.Set(); + Console.Write("Sleeping to time out all threads\n"); + Thread.Sleep(21000); + ThreadPool.QueueUserWorkItem( _ => testJobCompleted.Set()); + testJobCompleted.CheckedWait(); + } + + [Fact] + public static void WorkQueueDepletionTest() + { + ManualResetEvent e0 = new ManualResetEvent(false); + int numLocalScheduled = 1; + int numGlobalScheduled = 1; + int numToSchedule = Environment.ProcessorCount * 64; + int numCompleted = 0; + object syncRoot = new object(); + void ThreadLocalJob() + { + if(Interlocked.Increment(ref numLocalScheduled) <= numToSchedule) + { + Task.Factory.StartNew(ThreadLocalJob); + } + if(Interlocked.Increment(ref numLocalScheduled) <= numToSchedule) + { + Task.Factory.StartNew(ThreadLocalJob); + } + if (Interlocked.Increment(ref numCompleted) == numToSchedule * 2) + { + e0.Set(); + } + } + void GlobalJob(object _) + { + if(Interlocked.Increment(ref numGlobalScheduled) <= numToSchedule) + { + ThreadPool.QueueUserWorkItem(GlobalJob); + } + if(Interlocked.Increment(ref numGlobalScheduled) <= numToSchedule) + { + ThreadPool.QueueUserWorkItem(GlobalJob); + } + if (Interlocked.Increment(ref numCompleted) == numToSchedule * 2) + { + e0.Set(); + } + } + Task.Factory.StartNew(ThreadLocalJob); + ThreadPool.QueueUserWorkItem(GlobalJob); + e0.CheckedWait(); + } + + [Fact] + public static void WorkerThreadStateReset() + { + var cultureInfo = new CultureInfo("pt-BR"); + var expectedCultureInfo = CultureInfo.CurrentCulture; + var expectedUICultureInfo = CultureInfo.CurrentUICulture; + int count = 0; + AutoResetEvent e0 = new AutoResetEvent(false); + for(int i = 0; i < Environment.ProcessorCount; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + CultureInfo.CurrentCulture = cultureInfo; + CultureInfo.CurrentUICulture = cultureInfo; + Thread.CurrentThread.Priority = ThreadPriority.AboveNormal; + if(Interlocked.Increment(ref count) == Environment.ProcessorCount) + { + e0.Set(); + } + }); + } + e0.CheckedWait(); + // Run the test again to make sure we can reuse the threads. + count = 0; + for(int i = 0; i < Environment.ProcessorCount; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + Assert.Equal(expectedCultureInfo, CultureInfo.CurrentCulture); + Assert.Equal(expectedUICultureInfo, CultureInfo.CurrentUICulture); + Assert.Equal(ThreadPriority.Normal, Thread.CurrentThread.Priority); + if(Interlocked.Increment(ref count) == Environment.ProcessorCount) + { + e0.Set(); + } + }); + } + e0.CheckedWait(); + } + + [Fact] + public static void SettingMinThreadsWillCreateThreadsUpToMinimum() + { + ThreadPool.GetMinThreads(out int minThreads, out int unusedMin); + ThreadPool.GetMaxThreads(out int maxThreads, out int unusedMax); + try + { + ManualResetEvent e0 = new ManualResetEvent(false); + AutoResetEvent jobsQueued = new AutoResetEvent(false); + int count = 0; + ThreadPool.SetMaxThreads(minThreads, unusedMax); + for(int i = 0; i < minThreads + 1; ++i) + { + ThreadPool.QueueUserWorkItem( _ => { + if(Interlocked.Increment(ref count) == minThreads + 1) + { + jobsQueued.Set(); + } + e0.CheckedWait(); + }); + } + Assert.False(jobsQueued.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + Assert.True(ThreadPool.SetMaxThreads(minThreads + 1, unusedMax)); + Assert.True(ThreadPool.SetMinThreads(minThreads + 1, unusedMin)); + + jobsQueued.CheckedWait(); + + e0.Set(); + } + finally + { + ThreadPool.SetMinThreads(minThreads, unusedMin); + ThreadPool.SetMaxThreads(maxThreads, unusedMax); + } + } +} + +internal static class WaitThreadTests +{ + private const int WaitThreadTimeoutTimeMs = 20000; + + [Fact] + public static void SignalingRegisteredHandleCallsCalback() + { + var e0 = new AutoResetEvent(false); + var e1 = new AutoResetEvent(false); + ThreadPool.RegisterWaitForSingleObject(e0, (_, timedOut) => + { + if(!timedOut) + { + e1.Set(); + } + }, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + e0.Set(); + e1.CheckedWait(); + } + + [Fact] + public static void TimingOutRegisteredHandleCallsCallback() + { + var e0 = new AutoResetEvent(false); + var e1 = new AutoResetEvent(false); + ThreadPool.RegisterWaitForSingleObject(e0, (_, timedOut) => + { + if(timedOut) + { + e1.Set(); + } + }, null, ThreadTestHelpers.ExpectedTimeoutMilliseconds, true); + e1.CheckedWait(); + } + + [Fact] + public static void UnregisteringBeforeSignalingDoesNotCallCallback() + { + var e0 = new AutoResetEvent(false); + var e1 = new AutoResetEvent(false); + var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => + { + e1.Set(); + }, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + registeredWaitHandle.Unregister(null); + Assert.False(e1.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + } + + [Fact] + public static void RepeatingWaitFiresUntilUnregistered() + { + var e0 = new AutoResetEvent(false); + var e1 = new AutoResetEvent(false); + var registered = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => + { + e1.Set(); + }, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, false); + for (int i = 0; i < 4; ++i) + { + e0.Set(); + e1.CheckedWait(); + } + registered.Unregister(null); + e0.Set(); + Assert.False(e1.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + } + + [Fact] + public static void UnregisterEventSignaledWhenUnregistered() + { + var e0 = new AutoResetEvent(false); + var e1 = new AutoResetEvent(false); + var registered = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + registered.Unregister(e1); + e1.CheckedWait(); + } + + [Fact] + public static void CanRegisterMoreThan64Waits() + { + RegisteredWaitHandle[] handles = new RegisteredWaitHandle[65]; + for(int i = 0; i < 65; ++i) { + handles[i] = ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (_, __) => {}, null, -1, true); + } + for(int i = 0; i < 65; ++i) { + handles[i].Unregister(null); + } + } + + [Fact] + public static void StateIsPasssedThroughToCallback() + { + object state = new object(); + AutoResetEvent e0 = new AutoResetEvent(false); + ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), (callbackState, _) => + { + if(state == callbackState) + { + e0.Set(); + } + }, state, 0, true); + e0.CheckedWait(); + } + + [Fact] + [OuterLoop] + public static void WaitWithLongerTimeoutThanWaitThreadCanStillTimeout() + { + AutoResetEvent e0 = new AutoResetEvent(false); + ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (_, __) => e0.Set(), null, WaitThreadTimeoutTimeMs + 1000, true); + Thread.Sleep(WaitThreadTimeoutTimeMs); + e0.CheckedWait(); + } + + [Fact] + public static void UnregisterCallbackIsNotCalledAfterCallbackFinishesIfAnotherCallbackOnSameWaitRunning() + { + AutoResetEvent e0 = new AutoResetEvent(false); + AutoResetEvent e1 = new AutoResetEvent(false); + AutoResetEvent e2 = new AutoResetEvent(false); + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => + { + e2.WaitOne(ThreadTestHelpers.UnexpectedTimeoutMilliseconds); + }, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, false); + e0.Set(); + Thread.Sleep(50); + e0.Set(); + Thread.Sleep(50); + handle.Unregister(e1); + Assert.False(e1.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); + e2.Set(); + } + + [Fact] + public static void CallingUnregisterOnAutomaticallyUnregisteredHandleReturnsTrue() + { + AutoResetEvent e0 = new AutoResetEvent(false); + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + e0.Set(); + Thread.Sleep(ThreadTestHelpers.ExpectedTimeoutMilliseconds); + Assert.True(handle.Unregister(null)); + } + + [Fact] + public static void EventSetAfterUnregisterNotObservedOnWaitThread() + { + AutoResetEvent e0 = new AutoResetEvent(false); + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + handle.Unregister(null); + e0.Set(); + e0.CheckedWait(); + } + + [Fact] + public static void BlockingUnregister() + { + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + handle.Unregister(new InvalidWaitHandle()); + } + + [Fact] + public static void CanDisposeEventAfterUnblockingUnregister() + { + using(var e0 = new AutoResetEvent(false)) + { + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + handle.Unregister(null); + } + } + + [Fact] + public static void UnregisterEventSignaledWhenUnregisteredEvenIfAutoUnregistered() + { + var e0 = new AutoResetEvent(false); + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => {}, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + e0.Set(); + Thread.Sleep(50); // Ensure the callback has happened + var e1 = new AutoResetEvent(false); + handle.Unregister(e1); + e1.CheckedWait(); + } + + [Fact] + public static void BlockingUnregisterBlocksEvenIfCallbackExecuting() + { + bool callbackComplete = false; + var e0 = new AutoResetEvent(false); + RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(e0, (_, __) => + { + Thread.Sleep(300); + callbackComplete = true; + }, null, ThreadTestHelpers.UnexpectedTimeoutMilliseconds, true); + e0.Set(); + Thread.Sleep(100); // Give the wait thread time to process removals. + handle.Unregister(new InvalidWaitHandle()); + Assert.True(callbackComplete); + } +} + +internal static class ThreadTestHelpers +{ + public const int ExpectedTimeoutMilliseconds = 50; + public const int ExpectedMeasurableTimeoutMilliseconds = 500; + public const int UnexpectedTimeoutMilliseconds = 1000 * 30; + + // Wait longer for a thread to time out, so that an unexpected timeout in the thread is more likely to expire first and + // provide a better stack trace for the failure + public const int UnexpectedThreadTimeoutMilliseconds = UnexpectedTimeoutMilliseconds * 2; + + public static Thread CreateGuardedThread(out Action waitForThread, Action start) + { + Action checkForThreadErrors; + return CreateGuardedThread(out checkForThreadErrors, out waitForThread, start); + } + + public static Thread CreateGuardedThread(out Action checkForThreadErrors, out Action waitForThread, Action start) + { + Exception backgroundEx = null; + var t = + new Thread(parameter => + { + try + { + start(parameter); + } + catch (Exception ex) + { + backgroundEx = ex; + Interlocked.MemoryBarrier(); + } + }); + Action localCheckForThreadErrors = checkForThreadErrors = // cannot use ref or out parameters in lambda + () => + { + Interlocked.MemoryBarrier(); + if (backgroundEx != null) + { + throw new AggregateException(backgroundEx); + } + }; + waitForThread = + () => + { + Assert.True(t.Join(UnexpectedThreadTimeoutMilliseconds)); + localCheckForThreadErrors(); + }; + return t; + } + + public static void WaitForCondition(Func condition) + { + WaitForConditionWithCustomDelay(condition, () => Thread.Sleep(1)); + } + + public static void WaitForConditionWithCustomDelay(Func condition, Action delay) + { + if (condition()) + { + return; + } + + var startTime = Environment.TickCount; + do + { + delay(); + Assert.True(Environment.TickCount - startTime < UnexpectedTimeoutMilliseconds); + } while (!condition()); + } + + public static void CheckedWait(this WaitHandle wh) + { + Assert.True(wh.WaitOne(UnexpectedTimeoutMilliseconds)); + } +} + +internal sealed class InvalidWaitHandle : WaitHandle +{ +} + +internal sealed class Stopwatch +{ + private int _startTimeMs; + private int _endTimeMs; + private bool _isStopped; + + public void Restart() + { + _isStopped = false; + _startTimeMs = Environment.TickCount; + } + + public void Stop() + { + _endTimeMs = Environment.TickCount; + _isStopped = true; + } + + public long ElapsedMilliseconds => (_isStopped ? _endTimeMs : Environment.TickCount) - _startTimeMs; +} + +internal static class Assert +{ + public static void False(bool condition) + { + if (!condition) + return; + Console.WriteLine("Assertion failure - Assert.False"); + throw new AssertionFailureException(); + } + + public static void True(bool condition) + { + if (condition) + return; + Console.WriteLine("Assertion failure - Assert.True"); + throw new AssertionFailureException(); + } + + public static void Same(T expected, T actual) where T : class + { + if (expected == actual) + return; + Console.WriteLine("Assertion failure - Assert.Same({0}, {1})", expected, actual); + throw new AssertionFailureException(); + } + + public static void Equal(T expected, T actual) + { + if (EqualityComparer.Default.Equals(expected, actual)) + return; + Console.WriteLine("Assertion failure - Assert.Equal({0}, {1})", expected, actual); + throw new AssertionFailureException(); + } + + public static void NotEqual(T notExpected, T actual) + { + if (!EqualityComparer.Default.Equals(notExpected, actual)) + return; + Console.WriteLine("Assertion failure - Assert.NotEqual({0}, {1})", notExpected, actual); + throw new AssertionFailureException(); + } + + public static void Throws(Action action) where T : Exception + { + // TODO: Enable Assert.Throws tests. There currently seem to be some reliability issues surrounding exceptions on Unix. + //try + //{ + // action(); + //} + //catch (T ex) + //{ + // if (ex.GetType() == typeof(T)) + // return; + // Console.WriteLine("Assertion failure - Assert.Throws<{0}>: got {1}", typeof(T), ex.GetType()); + // throw new AssertionFailureException(ex); + //} + //catch (Exception ex) + //{ + // Console.WriteLine("Assertion failure - Assert.Throws<{0}>: got {1}", typeof(T), ex.GetType()); + // throw new AssertionFailureException(ex); + //} + //Console.WriteLine("Assertion failure - Assert.Throws<{0}>: got no exception", typeof(T)); + //throw new AssertionFailureException(); + } + + public static void Null(object value) + { + if (value == null) + return; + Console.WriteLine("Assertion failure - Assert.Null"); + throw new AssertionFailureException(); + } + + public static void NotNull(object value) + { + if (value != null) + return; + Console.WriteLine("Assertion failure - Assert.NotNull"); + throw new AssertionFailureException(); + } +} + +internal class AssertionFailureException : Exception +{ + public AssertionFailureException() + { + } + + public AssertionFailureException(string message) : base(message) + { + } + + public AssertionFailureException(Exception innerException) : base(null, innerException) + { + } +} + +internal class FactAttribute : Attribute +{ +} + +internal class OuterLoopAttribute : Attribute +{ +} diff --git a/src/tests/nativeaot/SmokeTests/Threading/Threading.csproj b/src/tests/nativeaot/SmokeTests/Threading/Threading.csproj new file mode 100644 index 000000000000..c22a6b897328 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/Threading/Threading.csproj @@ -0,0 +1,11 @@ + + + Exe + BuildAndRun + 0 + true + + + + + From 34ec632a40e0146055d5b6c93cf51be9d8f975a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 8 Sep 2020 15:47:17 +0200 Subject: [PATCH 17/34] Always build libs as Release --- eng/pipelines/runtimelab.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/runtimelab.yml b/eng/pipelines/runtimelab.yml index 679d13956269..35f3e0405624 100644 --- a/eng/pipelines/runtimelab.yml +++ b/eng/pipelines/runtimelab.yml @@ -65,7 +65,7 @@ jobs: jobParameters: timeoutInMinutes: 90 testGroup: innerloop - buildArgs: -s nativeaot+libs+installer -c debug -runtimeConfiguration Checked + buildArgs: -s nativeaot+libs+installer -lc release -rc checked extraStepsTemplate: /eng/pipelines/runtimelab/runtimelab-post-build-steps.yml # From 2d2c0849f5e8d6fdb757d60441c82b63c44b53d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 8 Sep 2020 16:01:10 +0200 Subject: [PATCH 18/34] Linux support --- src/coreclr/tests/runtest.sh | 8 +++++++ .../tests/src/CLRTest.Execute.Bash.targets | 4 ++++ .../tests/src/CLRTest.NativeAot.targets | 21 +++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/coreclr/tests/runtest.sh b/src/coreclr/tests/runtest.sh index 1b73a5d7f6bc..267592aa4121 100755 --- a/src/coreclr/tests/runtest.sh +++ b/src/coreclr/tests/runtest.sh @@ -23,6 +23,7 @@ function print_usage { echo ' --crossgen : Precompiles the framework managed assemblies' echo ' --runcrossgentests : Runs the ready to run tests' echo ' --runcrossgen2tests : Runs the ready to run tests compiled with Crossgen2' + echo ' --runnativeaottests : Runs the ready to run tests compiled with Native AOT' echo ' --jitstress= : Runs the tests with COMPlus_JitStress=n' echo ' --jitstressregs= : Runs the tests with COMPlus_JitStressRegs=n' echo ' --jitminopts : Runs the tests with COMPlus_JITMinOpts=1' @@ -242,6 +243,9 @@ do --runcrossgen2tests) export RunCrossGen2=1 ;; + --runnativeaottests) + export RunNativeAot=1 + ;; --sequential) runSequential=1 ;; @@ -395,6 +399,10 @@ if [ ! -z "$RunCrossGen2" ]; then runtestPyArguments+=("--run_crossgen2_tests") fi +if [ ! -z "$RunNativeAot" ]; then + runtestPyArguments+=("--run_nativeaot_tests") +fi + if (($doCrossgen!=0)); then runtestPyArguments+=("--precompile_core_root") fi diff --git a/src/coreclr/tests/src/CLRTest.Execute.Bash.targets b/src/coreclr/tests/src/CLRTest.Execute.Bash.targets index fbb62424e167..538496eeb462 100644 --- a/src/coreclr/tests/src/CLRTest.Execute.Bash.targets +++ b/src/coreclr/tests/src/CLRTest.Execute.Bash.targets @@ -282,6 +282,10 @@ then else LAUNCHER="$_DebuggerFullPath $(_CLRTestRunFile)" fi +if [ ! -z "$RunNativeAot" ] +then + LAUNCHER="$_DebuggerFullPath" +fi $(BashIlrtTestLaunchCmds) diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets index fc7bdcc6403a..262e95067039 100644 --- a/src/coreclr/tests/src/CLRTest.NativeAot.targets +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -34,6 +34,27 @@ WARNING: When setting properties based on their current state (for example: From f79f81c70f2081af6d90a6e47979d77869ebe524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 09:49:04 +0200 Subject: [PATCH 19/34] Maybe fix linux --- .../runtimelab/runtimelab-post-build-steps.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml index 1f78ec9ec9d8..aeee0f68313c 100644 --- a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml +++ b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml @@ -5,10 +5,18 @@ parameters: osSubgroup: '' uploadTests: false +variables: + - ${{ if eq(parameters.osGroup, 'Windows_NT') }}: + - name: scriptParamPrefix + value: '' + - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: + - name: scriptParamPrefix + value: '--' + steps: # Build coreclr native test output - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) skipstressdependencies $(buildConfigUpper) ${{ parameters.archType }} displayName: Build tests - - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} + - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) $(scriptParamPrefix)runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} displayName: Run tests From 0e28c087b45e6d0d7c310ea731c5393ec32d68b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 09:57:50 +0200 Subject: [PATCH 20/34] Yaml bs /eng/pipelines/runtimelab/runtimelab-post-build-steps.yml (Line: 8, Col: 1): Unexpected value 'variables' --- .../runtimelab/runtimelab-post-build-steps.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml index aeee0f68313c..abe30c1d1013 100644 --- a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml +++ b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml @@ -5,18 +5,14 @@ parameters: osSubgroup: '' uploadTests: false -variables: - - ${{ if eq(parameters.osGroup, 'Windows_NT') }}: - - name: scriptParamPrefix - value: '' - - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: - - name: scriptParamPrefix - value: '--' - steps: # Build coreclr native test output - script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) skipstressdependencies $(buildConfigUpper) ${{ parameters.archType }} displayName: Build tests - - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) $(scriptParamPrefix)runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} - displayName: Run tests + - ${{ if eq(parameters.osGroup, 'Windows_NT') }}: + - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} + displayName: Run tests + - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: + - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) --runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} + displayName: Run tests From ae0cd8dbd7d19ba50a063db97666c0e040fd62a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 10:42:31 +0200 Subject: [PATCH 21/34] Revert "Revert "Build accelerator"" This reverts commit a236229217b90caac103d83bc69995a3c17d21fb. --- src/coreclr/CMakeLists.txt | 2 +- src/coreclr/src/CMakeLists.txt | 37 +++++++++++----------- src/coreclr/src/vm/eventing/CMakeLists.txt | 2 +- src/libraries/Directory.Build.props | 1 + 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index e58c23e478a1..2333844418e2 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -119,7 +119,7 @@ include_directories("../../artifacts/obj/coreclr") if(FEATURE_STANDALONE_GC) add_definitions(-DFEATURE_STANDALONE_GC) - add_subdirectory(src/gc) + #add_subdirectory(src/gc) endif(FEATURE_STANDALONE_GC) if (CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index e269657e5add..c47616e4b0e6 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -12,11 +12,11 @@ if(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) include_directories("${GENERATED_INCLUDE_DIR}/etw") endif(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) -add_subdirectory(debug/dbgutil) +#add_subdirectory(debug/dbgutil) if(CLR_CMAKE_HOST_UNIX) if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) - add_subdirectory(debug/createdump) +# add_subdirectory(debug/createdump) endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) # Include the dummy c++ include files @@ -55,28 +55,29 @@ if(CLR_CMAKE_HOST_UNIX) endfunction() - add_subdirectory(nativeresources) +# add_subdirectory(nativeresources) endif(CLR_CMAKE_HOST_UNIX) add_subdirectory(utilcode) add_subdirectory(gcinfo) add_subdirectory(jit) -add_subdirectory(vm) -add_subdirectory(md) -add_subdirectory(debug) -add_subdirectory(inc) -add_subdirectory(binder) -add_subdirectory(classlibnative) -add_subdirectory(dlls) -add_subdirectory(ToolBox) -add_subdirectory(tools) -add_subdirectory(unwinder) -add_subdirectory(ildasm) -add_subdirectory(ilasm) -add_subdirectory(interop) +include_directories("vm") +add_subdirectory(vm/eventing) +#add_subdirectory(md) +#add_subdirectory(debug) +#add_subdirectory(inc) +#add_subdirectory(binder) +#add_subdirectory(classlibnative) +#add_subdirectory(dlls) +#add_subdirectory(ToolBox) +#add_subdirectory(tools) +#add_subdirectory(unwinder) +#add_subdirectory(ildasm) +#add_subdirectory(ilasm) +#add_subdirectory(interop) if(CLR_CMAKE_HOST_UNIX) - add_subdirectory(palrt) +# add_subdirectory(palrt) elseif(CLR_CMAKE_HOST_WIN32) - add_subdirectory(hosts) +# add_subdirectory(hosts) endif(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/vm/eventing/CMakeLists.txt b/src/coreclr/src/vm/eventing/CMakeLists.txt index e2bf024fc59f..06691da577ce 100644 --- a/src/coreclr/src/vm/eventing/CMakeLists.txt +++ b/src/coreclr/src/vm/eventing/CMakeLists.txt @@ -34,7 +34,7 @@ set_source_files_properties(${EventingHeaders} PROPERTIES GENERATED TRUE) add_dependencies(eventing_headers eventprovider) -add_subdirectory(eventpipe) +#add_subdirectory(eventpipe) if(CLR_CMAKE_HOST_WIN32) add_subdirectory(EtwProvider) diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 1446d65ba55a..851d863b5a5b 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -98,6 +98,7 @@ + From 7b27d043998d11c2a684d9118e71c21d35472853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 11:34:52 +0200 Subject: [PATCH 22/34] Update ConfigurablePInvokePolicy.cs --- .../src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs b/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs index f0bac319a967..e3edc1d60cf7 100644 --- a/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs +++ b/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs @@ -52,7 +52,8 @@ public override bool GenerateDirectCall(string importModule, string methodName) methodName == "LoadLibraryExW" || methodName == "GetProcAddress" || methodName == "SetLastError" || - methodName == "GetLastError") + methodName == "GetLastError" || + methodName == "LocalAlloc") { return true; } From da4fc0b52fc8bd0984c0bca1ec63fa6accc776ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 11:44:20 +0200 Subject: [PATCH 23/34] Sigh --- src/coreclr/tests/src/CLRTest.NativeAot.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets index 262e95067039..c743eab33bba 100644 --- a/src/coreclr/tests/src/CLRTest.NativeAot.targets +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -34,7 +34,7 @@ WARNING: When setting properties based on their current state (for example: Date: Wed, 9 Sep 2020 12:37:49 +0200 Subject: [PATCH 24/34] Revert "Revert "Revert "Build accelerator""" This reverts commit ae0cd8dbd7d19ba50a063db97666c0e040fd62a4. --- src/coreclr/CMakeLists.txt | 2 +- src/coreclr/src/CMakeLists.txt | 37 +++++++++++----------- src/coreclr/src/vm/eventing/CMakeLists.txt | 2 +- src/libraries/Directory.Build.props | 1 - 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 2333844418e2..e58c23e478a1 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -119,7 +119,7 @@ include_directories("../../artifacts/obj/coreclr") if(FEATURE_STANDALONE_GC) add_definitions(-DFEATURE_STANDALONE_GC) - #add_subdirectory(src/gc) + add_subdirectory(src/gc) endif(FEATURE_STANDALONE_GC) if (CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index c47616e4b0e6..e269657e5add 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -12,11 +12,11 @@ if(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) include_directories("${GENERATED_INCLUDE_DIR}/etw") endif(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) -#add_subdirectory(debug/dbgutil) +add_subdirectory(debug/dbgutil) if(CLR_CMAKE_HOST_UNIX) if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) -# add_subdirectory(debug/createdump) + add_subdirectory(debug/createdump) endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) # Include the dummy c++ include files @@ -55,29 +55,28 @@ if(CLR_CMAKE_HOST_UNIX) endfunction() -# add_subdirectory(nativeresources) + add_subdirectory(nativeresources) endif(CLR_CMAKE_HOST_UNIX) add_subdirectory(utilcode) add_subdirectory(gcinfo) add_subdirectory(jit) -include_directories("vm") -add_subdirectory(vm/eventing) -#add_subdirectory(md) -#add_subdirectory(debug) -#add_subdirectory(inc) -#add_subdirectory(binder) -#add_subdirectory(classlibnative) -#add_subdirectory(dlls) -#add_subdirectory(ToolBox) -#add_subdirectory(tools) -#add_subdirectory(unwinder) -#add_subdirectory(ildasm) -#add_subdirectory(ilasm) -#add_subdirectory(interop) +add_subdirectory(vm) +add_subdirectory(md) +add_subdirectory(debug) +add_subdirectory(inc) +add_subdirectory(binder) +add_subdirectory(classlibnative) +add_subdirectory(dlls) +add_subdirectory(ToolBox) +add_subdirectory(tools) +add_subdirectory(unwinder) +add_subdirectory(ildasm) +add_subdirectory(ilasm) +add_subdirectory(interop) if(CLR_CMAKE_HOST_UNIX) -# add_subdirectory(palrt) + add_subdirectory(palrt) elseif(CLR_CMAKE_HOST_WIN32) -# add_subdirectory(hosts) + add_subdirectory(hosts) endif(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/src/vm/eventing/CMakeLists.txt b/src/coreclr/src/vm/eventing/CMakeLists.txt index 06691da577ce..e2bf024fc59f 100644 --- a/src/coreclr/src/vm/eventing/CMakeLists.txt +++ b/src/coreclr/src/vm/eventing/CMakeLists.txt @@ -34,7 +34,7 @@ set_source_files_properties(${EventingHeaders} PROPERTIES GENERATED TRUE) add_dependencies(eventing_headers eventprovider) -#add_subdirectory(eventpipe) +add_subdirectory(eventpipe) if(CLR_CMAKE_HOST_WIN32) add_subdirectory(EtwProvider) diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 851d863b5a5b..1446d65ba55a 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -98,7 +98,6 @@ - From aa7c9a1087e441998ec75d7c729e35bc540ff361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 15:39:33 +0200 Subject: [PATCH 25/34] Enable debug symbols --- src/coreclr/tests/src/CLRTest.NativeAot.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tests/src/CLRTest.NativeAot.targets b/src/coreclr/tests/src/CLRTest.NativeAot.targets index c743eab33bba..64ca7070a6da 100644 --- a/src/coreclr/tests/src/CLRTest.NativeAot.targets +++ b/src/coreclr/tests/src/CLRTest.NativeAot.targets @@ -112,6 +112,7 @@ if defined RunNativeAot ( %24(MSBuildProjectDirectory)\ %24(MSBuildProjectDirectory)\ true + true From 8fbc53192877b9249282059a21b5d428769f1c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 15:39:40 +0200 Subject: [PATCH 26/34] Try to fix linux --- dotnet.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dotnet.sh b/dotnet.sh index a612ebac1ce1..76f916908f9b 100755 --- a/dotnet.sh +++ b/dotnet.sh @@ -23,5 +23,9 @@ source $scriptroot/eng/common/tools.sh InitializeDotNetCli true # Install __dotnetDir=${_InitializeDotNetCli} +# Temporarily make this dotnet more permanent +export DOTNET_ROLL_FORWARD=Major +export DOTNET_ROOT=${__dotnetDir} + dotnetPath=${__dotnetDir}/dotnet ${dotnetPath} "$@" From 582faada9b11405dc252a8015ae8b368875df0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 9 Sep 2020 16:41:22 +0200 Subject: [PATCH 27/34] Do we need this? --- .../BuildIntegration/Microsoft.NETCore.Native.Unix.props | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props b/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props index f161562a380d..33e914de685e 100644 --- a/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props +++ b/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props @@ -55,7 +55,6 @@ The .NET Foundation licenses this file to you under the MIT license. - From 4ce96800a8c8091a23e8e09d6784d96874d2383d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Sep 2020 11:13:28 +0200 Subject: [PATCH 28/34] Revert "Do we need this?" This reverts commit 582faada9b11405dc252a8015ae8b368875df0d6. --- .../BuildIntegration/Microsoft.NETCore.Native.Unix.props | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props b/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props index 33e914de685e..f161562a380d 100644 --- a/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props +++ b/src/coreclr/src/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.props @@ -55,6 +55,7 @@ The .NET Foundation licenses this file to you under the MIT license. + From 790737ac8884e39aeb6f74fafff9e1c86571797f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Sep 2020 11:14:39 +0200 Subject: [PATCH 29/34] Don't run tests on Linux --- eng/pipelines/runtimelab/runtimelab-post-build-steps.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml index abe30c1d1013..20e1c0606678 100644 --- a/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml +++ b/eng/pipelines/runtimelab/runtimelab-post-build-steps.yml @@ -13,6 +13,6 @@ steps: - ${{ if eq(parameters.osGroup, 'Windows_NT') }}: - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} displayName: Run tests - - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: - - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) --runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} - displayName: Run tests + #- ${{ if ne(parameters.osGroup, 'Windows_NT') }}: + # - script: $(Build.SourcesDirectory)/src/tests/run$(scriptExt) --runnativeaottests $(buildConfigUpper) ${{ parameters.archType }} + # displayName: Run tests From 27578b7b3872e5f99b6f9d4e51820bcad187369c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Sep 2020 21:18:58 +0200 Subject: [PATCH 30/34] CR feedback --- .../src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs b/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs index e3edc1d60cf7..3f53d9a4902e 100644 --- a/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs +++ b/src/coreclr/src/tools/aot/ILCompiler/ConfigurablePInvokePolicy.cs @@ -47,13 +47,12 @@ public override bool GenerateDirectCall(string importModule, string methodName) methodName == "CreateEventExW" || methodName == "SetEvent" || methodName == "ResetEvent" || - methodName == "GetProcessHeap" || - methodName == "HeapAlloc" || methodName == "LoadLibraryExW" || methodName == "GetProcAddress" || methodName == "SetLastError" || methodName == "GetLastError" || - methodName == "LocalAlloc") + methodName == "LocalAlloc" || + methodName == "LocalFree") { return true; } From 4de5b889a79d51eba549b875c449e329e913288e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Sep 2020 22:01:45 +0200 Subject: [PATCH 31/34] Fix conflict with master --- src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj index 5276c48878b4..f97b07f50633 100644 --- a/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj +++ b/src/coreclr/src/tools/aot/ILCompiler/ILCompiler.csproj @@ -54,7 +54,7 @@ false - + PreserveNewest false false From 33c4c6e6b63739ef3ba8269694e9c92bb7f08da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 10 Sep 2020 22:16:16 +0200 Subject: [PATCH 32/34] Fix building JIT without runtime on Linux * Don't build createdump * We need mscorrc --- src/coreclr/src/CMakeLists.txt | 10 ++++++---- src/coreclr/src/dlls/CMakeLists.txt | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index 0866be360fd0..c6a510d9e478 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -15,9 +15,11 @@ endif(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) add_subdirectory(debug/dbgutil) if(CLR_CMAKE_HOST_UNIX) - if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) - add_subdirectory(debug/createdump) - endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) + if(CLR_CMAKE_BUILD_SUBSET_RUNTIME) + if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) + add_subdirectory(debug/createdump) + endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) + endif(CLR_CMAKE_BUILD_SUBSET_RUNTIME) # Include the dummy c++ include files include_directories("pal/inc/rt/cpp") @@ -68,12 +70,12 @@ if(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_HOST_UNIX) add_subdirectory(vm) +add_subdirectory(dlls) if (CLR_CMAKE_BUILD_SUBSET_RUNTIME) add_subdirectory(md) add_subdirectory(debug) add_subdirectory(binder) add_subdirectory(classlibnative) - add_subdirectory(dlls) add_subdirectory(ToolBox) add_subdirectory(tools) add_subdirectory(unwinder) diff --git a/src/coreclr/src/dlls/CMakeLists.txt b/src/coreclr/src/dlls/CMakeLists.txt index 6ef420d165f1..49191a9f2e09 100644 --- a/src/coreclr/src/dlls/CMakeLists.txt +++ b/src/coreclr/src/dlls/CMakeLists.txt @@ -1,11 +1,13 @@ -if(CLR_CMAKE_TARGET_WIN32) - add_subdirectory(clretwrc) -endif(CLR_CMAKE_TARGET_WIN32) -add_subdirectory(dbgshim) -if (NOT (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM) AND CLR_CMAKE_HOST_ARCH_AMD64)) - add_subdirectory(mscordbi) - add_subdirectory(mscordac) -endif() -add_subdirectory(mscoree) -add_subdirectory(mscorpe) +if(CLR_CMAKE_BUILD_SUBSET_RUNTIME) + if(CLR_CMAKE_TARGET_WIN32) + add_subdirectory(clretwrc) + endif(CLR_CMAKE_TARGET_WIN32) + add_subdirectory(dbgshim) + if (NOT (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM) AND CLR_CMAKE_HOST_ARCH_AMD64)) + add_subdirectory(mscordbi) + add_subdirectory(mscordac) + endif() + add_subdirectory(mscoree) + add_subdirectory(mscorpe) +endif(CLR_CMAKE_BUILD_SUBSET_RUNTIME) add_subdirectory(mscorrc) From 77a25996163e98b56e23ec55411e4d374a96eb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 11 Sep 2020 10:19:27 +0200 Subject: [PATCH 33/34] Do mscorrc differently --- src/coreclr/src/CMakeLists.txt | 9 ++++++++- src/coreclr/src/dlls/CMakeLists.txt | 22 ++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index c6a510d9e478..1ee527d6604e 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -70,12 +70,12 @@ if(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_HOST_UNIX) add_subdirectory(vm) -add_subdirectory(dlls) if (CLR_CMAKE_BUILD_SUBSET_RUNTIME) add_subdirectory(md) add_subdirectory(debug) add_subdirectory(binder) add_subdirectory(classlibnative) + add_subdirectory(dlls) add_subdirectory(ToolBox) add_subdirectory(tools) add_subdirectory(unwinder) @@ -86,5 +86,12 @@ if (CLR_CMAKE_BUILD_SUBSET_RUNTIME) if(CLR_CMAKE_HOST_WIN32) add_subdirectory(hosts) endif(CLR_CMAKE_HOST_WIN32) +else() + if(CLR_CMAKE_HOST_UNIX) + # this is needed to compile the jit on unix platforms. + # When the runtime subset is compiled, the add_subdirectory(dlls) above + # brings the mscorrc library into the build graph + add_subdirectory(dlls/mscorrc) + endif(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_BUILD_SUBSET_RUNTIME) diff --git a/src/coreclr/src/dlls/CMakeLists.txt b/src/coreclr/src/dlls/CMakeLists.txt index 49191a9f2e09..6ef420d165f1 100644 --- a/src/coreclr/src/dlls/CMakeLists.txt +++ b/src/coreclr/src/dlls/CMakeLists.txt @@ -1,13 +1,11 @@ -if(CLR_CMAKE_BUILD_SUBSET_RUNTIME) - if(CLR_CMAKE_TARGET_WIN32) - add_subdirectory(clretwrc) - endif(CLR_CMAKE_TARGET_WIN32) - add_subdirectory(dbgshim) - if (NOT (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM) AND CLR_CMAKE_HOST_ARCH_AMD64)) - add_subdirectory(mscordbi) - add_subdirectory(mscordac) - endif() - add_subdirectory(mscoree) - add_subdirectory(mscorpe) -endif(CLR_CMAKE_BUILD_SUBSET_RUNTIME) +if(CLR_CMAKE_TARGET_WIN32) + add_subdirectory(clretwrc) +endif(CLR_CMAKE_TARGET_WIN32) +add_subdirectory(dbgshim) +if (NOT (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM) AND CLR_CMAKE_HOST_ARCH_AMD64)) + add_subdirectory(mscordbi) + add_subdirectory(mscordac) +endif() +add_subdirectory(mscoree) +add_subdirectory(mscorpe) add_subdirectory(mscorrc) From e7646b4128397b56218bc901b4c91a63c5e48d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 11 Sep 2020 11:33:53 +0200 Subject: [PATCH 34/34] Add comment --- dotnet.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet.sh b/dotnet.sh index 76f916908f9b..ca0d14e6f61e 100755 --- a/dotnet.sh +++ b/dotnet.sh @@ -24,6 +24,7 @@ InitializeDotNetCli true # Install __dotnetDir=${_InitializeDotNetCli} # Temporarily make this dotnet more permanent +# We need this for Native AOT CI testing until ilc becomes a selfcontained app. export DOTNET_ROLL_FORWARD=Major export DOTNET_ROOT=${__dotnetDir}