diff --git a/docs/design/coreclr/jit/viewing-jit-dumps.md b/docs/design/coreclr/jit/viewing-jit-dumps.md index cef68fdac51871..6a13d9859fd798 100644 --- a/docs/design/coreclr/jit/viewing-jit-dumps.md +++ b/docs/design/coreclr/jit/viewing-jit-dumps.md @@ -120,29 +120,59 @@ These can be set in one of three ways: ## Specifying method names -The complete syntax for specifying a single method name (for a flag that takes a method name, such as `COMPlus_JitDump`) is: - +Some environment variables such as `COMPlus_JitDump` take a set of patterns specifying method names. The matching works in the following way: +* A method set string is a space-separated list of patterns. Patterns can arbitrarily contain both '*' (match any characters) and '?' (match any 1 character). +* The string matched against depends on characters in the pattern: + + If the pattern contains a ':' character, the string matched against is prefixed by the class name and a colon + + If the pattern contains a '(' character, the string matched against is suffixed by the signature + + If the class name (part before colon) contains a '[', the class contains its generic instantiation + + If the method name (part between colon and '(') contains a '[', the method contains its generic instantiation + +In particular, the matching is done against strings of the following format which coincides with how the JIT displays method signatures (so these can be copy pasted into the environment variable). ``` -[[.]::][([)] +[ClassName[Instantiation]:]MethodName[Instantiation][()] ``` -For example +For example, consider the following: +```csharp +namespace MyNamespace +{ + public class C + { + [MethodImpl(MethodImplOptions.NoInlining)] + public void M(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + } + } +} -``` -System.Object::ToString(System.Object) +new C().M(default, default, default, default); // compilation 1 +new C().M(default, default, default, default); // compilation 2 ``` -The namespace, class name, and argument types are optional, and if they are not present, default to a wildcard. Thus stating: +The full names of these instantiations are the following, as printed by `COMPlus_JitDisasmSummary`: ``` -Main +MyNamespace.C`2[byte,System.__Canon]:M[int,System.__Canon](byte,System.__Canon,int,System.__Canon) +MyNamespace.C`2[int,int]:M[int,int](int,int,int,int) ``` +Note that ``C`2`` here is the name put into metadata by Roslyn; the suffix is not added by RyuJIT. +For Powershell users keep in mind that backtick is the escape character and itself has to be escaped via double backtick. -will match all methods named Main from any class and any number of arguments. - -`` is a comma separated list of type names. Note that presently only the number of arguments and not the types themselves are used to distinguish methods. Thus, `Main(Foo, Bar)` and `Main(int, int)` will both match any main method with two arguments. +The following strings will match both compilations: +``` +M +*C`2:M +*C`2[*]:M[*](*) +MyNamespace.C`2:M +``` -The wildcard character `*` can be used for `` and ``. In particular `*` by itself indicates every method. +The following match only the first compilation: +``` +M[int,*Canon] +MyNamespace.C`2[byte,*]:M +M(*Canon) +``` ## Useful COMPlus variables diff --git a/docs/design/features/OsrDetailsAndDebugging.md b/docs/design/features/OsrDetailsAndDebugging.md index 1b1303eccb402d..60d697473c147b 100644 --- a/docs/design/features/OsrDetailsAndDebugging.md +++ b/docs/design/features/OsrDetailsAndDebugging.md @@ -338,7 +338,7 @@ Note if a Tier0 method is recursive and has loops there can be some interesting ### Seeing which OSR methods are created -* `DOTNET_DumpJittedMethods=1` will specially mark OSR methods with the inspiring IL offsets. +* `DOTNET_JitDisasmSummary=1` will specially mark OSR methods with the inspiring IL offsets. For example, running a libraries test with some stressful OSR settings, there ended up being 699 OSR methods jitted out of 160675 total methods. Grepping for OSR in the dump output, the last few lines were: diff --git a/docs/workflow/testing/testing-workloads.md b/docs/workflow/testing/testing-workloads.md index 4bb29e96ec6570..481020d3135de1 100644 --- a/docs/workflow/testing/testing-workloads.md +++ b/docs/workflow/testing/testing-workloads.md @@ -2,7 +2,7 @@ Workloads based on packages in `artifacts` can be installed, and used for testing. -- This is done by installing a specified SDK version (`$(SdkVersionForWorkloadTesting)`) in `artifacts/bin/dotnet-workload`. +- This is done by installing a specified SDK version (`$(SdkVersionForWorkloadTesting)`) in `artifacts/bin/dotnet-net7`. - Then the manifest for the workload in `@(WorkloadIdForTesting)` is installed - Then workload packs are installed - packs, or manifests not generated by `runtime`, are restored from nuget diff --git a/eng/Signing.props b/eng/Signing.props index b4fb10e90e4a8f..f76a7aa22c2481 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -1,5 +1,4 @@ - - + + + + + @@ -31,8 +37,6 @@ - - @@ -61,33 +65,4 @@ - - - - - false - - - - - - - - - - - - - - - - diff --git a/eng/Subsets.props b/eng/Subsets.props index 08a9d5dfaefc61..cd5f39ef63879d 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -61,7 +61,8 @@ mono.llvm+ $(DefaultMonoSubsets)mono.wasmruntime+ $(DefaultMonoSubsets)mono.aotcross+ - $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+mono.tools+ + $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+ + $(DefaultMonoSubsets)mono.tools+ $(DefaultMonoSubsets)host.native+ + https://github.com/dotnet/icu - c04d1340510269c5cd07a285abb097f587924d5b + 99a36e5c61fc30b9307ad950df6da7cf1e9e8e3e https://github.com/dotnet/msquic dc012a715ceb9b5d5258f2fda77520586af5a36a - - https://github.com/dotnet/emsdk - 216093204c415b6e37dfadfcbcf183881b443636 - https://github.com/dotnet/wcf 7f504aabb1988e9a093c1e74d8040bd52feb2f01 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 - + https://github.com/dotnet/llvm-project - e73d65f0f80655b463162bd41a8365377ba6565d + cb1c615abd1a871bd2d2a105325aaa84ee5913b5 https://github.com/dotnet/command-line-api 5618b2d243ccdeb5c7e50a298b33b13036b4351b + + https://github.com/dotnet/emsdk + ee3e095a636c0c93e579ca4e5eedb8773d92c6af + + + https://github.com/dotnet/emsdk + ee3e095a636c0c93e579ca4e5eedb8773d92c6af + - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee - + https://github.com/dotnet/llvm-project - 33e2c0435810d0110785ef33e50432c4990f7bba + 9ae4f6119efcb1b27d2b13905ac1eed79f804fee https://github.com/dotnet/runtime @@ -234,9 +238,9 @@ https://github.com/dotnet/runtime e680411c22e33f45821f4ae64365a2970b2430a6 - + https://github.com/dotnet/linker - fda7b09fc005acb865deaf526c7adbb1be27a5f9 + 313671b195b1b36d56a8888a9a0e12edaac52c57 https://github.com/dotnet/xharness @@ -250,41 +254,49 @@ https://github.com/dotnet/xharness 5ebf69650b9f7b4ecab485be840b3022420f7812 - + https://github.com/dotnet/arcade - 6a638cd0c13962ab2a1943cb1c878be5a41dd82e + 0c027eede69ba22bafca9a1955f1e00848655ece - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - e01e5b0aed54a5a8d9df74e717d1b13f0fb0e056 + 5e0b0da43f660de5798186f4fd3bc900fc90576c - + https://github.com/dotnet/hotreload-utils - f82b82000caf3e7a9789e1425a0baa12fdc70d09 + ff20bb1f646f5e5c75c7e03748faece79597acf7 - + https://github.com/dotnet/runtime-assets - 77acd39a813579e1e9b12cd98466787e7f90e059 + 3453104731a7fd5af1b7613bb9dc9dfa6263b312 - + https://github.com/dotnet/roslyn-analyzers - 793113a41d7f21b03470521bf48438f2abd9b12f + a26d1a203d0116aa769bce0fd3a917cc70d88a66 https://github.com/dotnet/sdk 3f2524bd65a6ab77b9160bcc23824dbc03990f3d + + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization + 5e0b0da43f660de5798186f4fd3bc900fc90576c + + + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization + 5e0b0da43f660de5798186f4fd3bc900fc90576c + diff --git a/eng/Versions.props b/eng/Versions.props index 72f17c83aa2662..205956e1954e8b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,8 +7,9 @@ 0 0 7.0.100 + 6.0.9 rc - 1 + 2 $(MajorVersion).$(MinorVersion).0.0 @@ -21,6 +22,8 @@ false $(AssemblyVersion) true + 7.0.0-rc.2.22458.3 + 7.0.0-rc.2.22458.3 - 4.4.0-2.22412.11 + 4.4.0-3.22452.8 0.2.0 7.0.100-rc.1.22402.1 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 2.5.1-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 - 7.0.0-beta.22411.2 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 2.5.1-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 + 7.0.0-beta.22418.4 6.0.0-preview.1.102 @@ -79,14 +82,14 @@ 6.0.0 7.0.0-rc.1.22414.6 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 - 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 + 1.0.0-alpha.1.22457.2 6.0.0 1.1.1 @@ -99,9 +102,9 @@ 4.5.0 5.0.0 5.0.0 - 4.5.4 + 4.5.5 4.5.0 - 6.0.0 + 6.0.1 4.7.1 4.7.0 4.7.0 @@ -119,24 +122,26 @@ 4.5.0 7.0.0-rc.1.22414.6 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 - 7.0.0-beta.22409.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 + 7.0.0-beta.22456.1 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 - 1.0.0-prerelease.22375.7 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 + 1.0.0-prerelease.22415.6 16.9.0-beta1.21055.5 2.0.0-beta4.22355.1 @@ -155,9 +160,9 @@ 1.0.0-prerelease.22411.1 1.0.0-prerelease.22411.1 1.0.0-prerelease.22411.1 - 1.1.0-alpha.0.22408.2 - 2.4.2-pre.22 - 0.12.0-pre.20 + 1.1.0-alpha.0.22456.2 + 2.4.2 + 1.0.0 2.4.5 3.1.2 13.0.1 @@ -169,27 +174,26 @@ 1.1.2-beta1.22403.2 - 7.0.0-preview-20220721.1 + 7.0.0-preview-20220822.1 - 7.0.100-1.22412.4 + 7.0.100-1.22423.4 $(MicrosoftNETILLinkTasksVersion) - 7.0.0-rc.1.22408.1 + 7.0.0-rc.2.22458.3 2.1 7.0.0-alpha.1.22406.1 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 - 11.1.0-alpha.1.22412.3 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 + 11.1.0-alpha.1.22455.2 - 7.0.0-rc.1.22411.1 - $(MicrosoftNETWorkloadEmscriptenManifest70100Version) + $(MicrosoftNETWorkloadEmscriptennet7Manifest70100Version) 1.1.87-gba258badda 1.0.0-v3.14.0.5722 diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index d3b0ac3ba7b600..032f5f193732a1 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -76,10 +76,10 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" -__IllumosPackages="icu-64.2nb2" -__IllumosPackages+=" mit-krb5-1.16.2nb4" -__IllumosPackages+=" openssl-1.1.1e" -__IllumosPackages+=" zlib-1.2.11" +__IllumosPackages="icu" +__IllumosPackages+=" mit-krb5" +__IllumosPackages+=" openssl" +__IllumosPackages+=" zlib" __HaikuPackages="gmp" __HaikuPackages+=" gmp_devel" @@ -390,14 +390,18 @@ elif [[ "$__CodeName" == "illumos" ]]; then if [[ "$__UseMirror" == 1 ]]; then BaseUrl=http://pkgsrc.smartos.skylime.net fi - BaseUrl="$BaseUrl/packages/SmartOS/2020Q1/${__illumosArch}/All" + BaseUrl="$BaseUrl/packages/SmartOS/trunk/${__illumosArch}/All" + echo "Downloading manifest" + wget "$BaseUrl" echo "Downloading dependencies." read -ra array <<<"$__IllumosPackages" for package in "${array[@]}"; do - echo "Installing $package..." + echo "Installing '$package'" + package="$(grep ">$package-[0-9]" All | sed -En 's/.*href="(.*)\.tgz".*/\1/p')" + echo "Resolved name '$package'" wget "$BaseUrl"/"$package".tgz ar -x "$package".tgz - tar --skip-old-files -xzf "$package".tmp.tgz -C "$__RootfsDir" 2>/dev/null + tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null done echo "Cleaning up temporary files." popd diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 index afdd1750290923..846e7950ce945b 100644 --- a/eng/common/generate-locproject.ps1 +++ b/eng/common/generate-locproject.ps1 @@ -33,6 +33,8 @@ $jsonTemplateFiles | ForEach-Object { $jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern +$wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them + $xlfFiles = @() $allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" @@ -77,8 +79,7 @@ $locJson = @{ CopyOption = "LangIDOnPath" OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" } - } - else { + } else { return @{ SourceFile = $sourceFile CopyOption = "LangIDOnName" @@ -88,6 +89,32 @@ $locJson = @{ } } ) + }, + @{ + CloneLanguageSet = "WiX_CloneLanguages" + LssFiles = @( "wxl_loc.lss" ) + LocItems = @( + $wxlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($outputPath.Contains($exclusion)) + { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) + { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + Languages = "cs-CZ;de-DE;es-ES;fr-FR;it-IT;ja-JP;ko-KR;pl-PL;pt-BR;ru-RU;tr-TR;zh-CN;zh-TW" + } + } + } + ) } ) } diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 119a6c660d1a4d..c35087a06019ef 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -64,7 +64,7 @@ try { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.1.0" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.2.1" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff --git a/eng/common/sdl/sdl.ps1 b/eng/common/sdl/sdl.ps1 new file mode 100644 index 00000000000000..ac196e164a4016 --- /dev/null +++ b/eng/common/sdl/sdl.ps1 @@ -0,0 +1,37 @@ + +function Install-Gdn { + param( + [string]$Path, + + # If omitted, install the latest version of Guardian, otherwise install that specific version. + [string]$Version + ) + + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") + + if ($Version) { + $argumentList += "-Version $Version" + } + + Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait + + $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path + + if (!$gdnCliPath) + { + Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' + } + + return $gdnCliPath.FullName +} \ No newline at end of file diff --git a/eng/common/templates/job/source-build.yml b/eng/common/templates/job/source-build.yml index 5cd5325d7b4e6b..88f6f75a622dc6 100644 --- a/eng/common/templates/job/source-build.yml +++ b/eng/common/templates/job/source-build.yml @@ -46,7 +46,7 @@ jobs: # source-build builds run in Docker, including the default managed platform. pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: NetCore1ESPool-Public + name: NetCore-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: NetCore1ESPool-Internal diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml index c85044a6849054..21fd12276b6580 100644 --- a/eng/common/templates/job/source-index-stage1.yml +++ b/eng/common/templates/job/source-index-stage1.yml @@ -28,7 +28,7 @@ jobs: ${{ if eq(parameters.pool, '') }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: NetCore1ESPool-Public + name: NetCore-Public demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: NetCore1ESPool-Internal diff --git a/eng/common/templates/steps/execute-sdl.yml b/eng/common/templates/steps/execute-sdl.yml index 73245593cef53d..86cf578c431443 100644 --- a/eng/common/templates/steps/execute-sdl.yml +++ b/eng/common/templates/steps/execute-sdl.yml @@ -8,29 +8,26 @@ parameters: condition: '' steps: -- ${{ if ne(parameters.overrideGuardianVersion, '') }}: - - powershell: | - $content = Get-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content was:`n$content" - - $content = $content.Replace('$(DefaultGuardianVersion)', '$(GuardianVersion)') - $content | Set-Content $(GuardianPackagesConfigFile) - - Write-Host "packages.config content updated to:`n$content" - displayName: Use overridden Guardian version ${{ parameters.overrideGuardianVersion }} +- task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: GuardianConnect - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' -- task: NuGetCommand@2 - displayName: 'Install Guardian' - inputs: - restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config - feedsToUse: config - nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config - externalFeedCredentials: GuardianConnect - restoreDirectory: $(Build.SourcesDirectory)\.packages +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + . $(Build.SourcesDirectory)\eng\common\sdl\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + . $(Build.SourcesDirectory)\eng\common\sdl\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian - ${{ if ne(parameters.overrideParameters, '') }}: - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} @@ -40,7 +37,7 @@ steps: - ${{ if eq(parameters.overrideParameters, '') }}: - powershell: ${{ parameters.executeAllSdlToolsScript }} - -GuardianPackageName Microsoft.Guardian.Cli.$(GuardianVersion) + -GuardianCliLocation $(GuardianCliLocation) -NugetPackageDirectory $(Build.SourcesDirectory)\.packages -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) ${{ parameters.additionalParameters }} diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index f83a748c37e9cf..aba6308ad313c5 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -365,8 +365,8 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=17.1.0&view=overview - $defaultXCopyMSBuildVersion = '17.1.0' + # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=17.2.1&view=overview + $defaultXCopyMSBuildVersion = '17.2.1' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 5802882a82a0b0..6ba9ce672785bd 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -179,7 +179,6 @@ + + true - true + true + true false false @@ -16,14 +16,20 @@ + + - + diff --git a/eng/pipelines/common/evaluate-default-paths.yml b/eng/pipelines/common/evaluate-default-paths.yml index 7198ecd5540c1e..358027fe7152b6 100644 --- a/eng/pipelines/common/evaluate-default-paths.yml +++ b/eng/pipelines/common/evaluate-default-paths.yml @@ -113,7 +113,8 @@ jobs: - src/mono/wasm/host/* - src/mono/wasm/runtime/* - src/mono/wasm/templates/* - - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/* + - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/* + - src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/* - src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/* - src/mono/nuget/Microsoft.NET.Runtime.wasm.Sample.Mono/* - src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/* diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 83a0efd39874f0..ef9a6bb735cd5f 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -135,12 +135,12 @@ jobs: pool: # Public Linux Build Pool ${{ if and(or(in(parameters.osGroup, 'Linux', 'FreeBSD', 'Android', 'Tizen'), eq(parameters.jobParameters.hostedOs, 'Linux')), eq(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open # Official Build Linux Pool ${{ if and(or(in(parameters.osGroup, 'Linux', 'FreeBSD', 'Browser', 'Android', 'Tizen'), eq(parameters.jobParameters.hostedOs, 'Linux')), ne(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals Build.Ubuntu.1804.Amd64 # OSX Build Pool (we don't have on-prem OSX BuildPool @@ -149,12 +149,12 @@ jobs: # Official Build Windows Pool ${{ if and(eq(parameters.osGroup, 'windows'), ne(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals windows.vs2022.amd64 # Public Windows Build Pool ${{ if and(or(eq(parameters.osGroup, 'windows'), eq(parameters.jobParameters.hostedOs, 'windows')), eq(variables['System.TeamProject'], 'public')) }}: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals windows.vs2022.amd64.open diff --git a/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml b/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml index 54c4baf70a366e..d9bb50d068a640 100644 --- a/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml +++ b/eng/pipelines/coreclr/perf-wasm-prepare-artifacts-steps.yml @@ -12,7 +12,7 @@ steps: - script: >- mkdir -p $(Build.SourcesDirectory)/artifacts/staging && - cp -r $(Build.SourcesDirectory)/artifacts/bin/dotnet-workload $(Build.SourcesDirectory)/artifacts/staging && + cp -r $(Build.SourcesDirectory)/artifacts/bin/dotnet-net7 $(Build.SourcesDirectory)/artifacts/staging && cp -r $(Build.SourcesDirectory)/artifacts/bin/microsoft.netcore.app.runtime.browser-wasm $(Build.SourcesDirectory)/artifacts/staging && cp -r $(Build.SourcesDirectory)/artifacts/bin/microsoft.netcore.app.ref $(Build.SourcesDirectory)/artifacts/staging displayName: "Prepare artifacts staging directory" diff --git a/eng/pipelines/coreclr/templates/build-job.yml b/eng/pipelines/coreclr/templates/build-job.yml index a226e0fd35e759..930580eae47391 100644 --- a/eng/pipelines/coreclr/templates/build-job.yml +++ b/eng/pipelines/coreclr/templates/build-job.yml @@ -261,25 +261,12 @@ jobs: targetFolder: $(buildProductRootFolderPath)/sharedFramework overWrite: true - # Sign diagnostic files on Windows - ${{ if and(eq(parameters.osGroup, 'windows'), eq(parameters.signBinaries, true)) }}: - - powershell: >- - eng\common\build.ps1 -ci -sign -restore -configuration:$(buildConfig) -warnaserror:0 $(officialBuildIdArg) - /p:DiagnosticsFilesRoot="$(buildProductRootFolderPath)" - /p:SignDiagnostics=true - /p:DotNetSignType=$(SignType) - -noBl - /bl:$(Build.SourcesDirectory)/artifacts/log/$(buildConfig)/SignDiagnostics.binlog - -projects $(Build.SourcesDirectory)\eng\empty.csproj - displayName: Sign Diagnostic Binaries - - - task: PublishPipelineArtifact@1 - displayName: Publish Signing Logs - inputs: - targetPath: '$(Build.SourcesDirectory)/artifacts/log/' - artifactName: ${{ format('SignLogs_{0}{1}_{2}_{3}', parameters.osGroup, parameters.osSubgroup, parameters.archType, parameters.buildConfig) }} - continueOnError: true - condition: always() + - template: /eng/pipelines/coreclr/templates/sign-diagnostic-files.yml + parameters: + basePath: $(buildProductRootFolderPath) + isOfficialBuild: ${{ parameters.signBinaries }} + timeoutInMinutes: 30 # Builds using gcc are not tested, and clrTools unitests do not publish the build artifacts - ${{ if and(ne(parameters.compilerName, 'gcc'), ne(parameters.testGroup, 'clrTools'), ne(parameters.disableClrTest, true)) }}: @@ -300,6 +287,7 @@ jobs: archType: ${{ parameters.archType }} osGroup: ${{ parameters.osGroup }} osSubgroup: ${{ parameters.osSubgroup }} + isOfficialBuild: ${{ parameters.signBinaries }} ${{ if eq(parameters.archType, 'arm') }}: hostArchType: x86 ${{ else }}: diff --git a/eng/pipelines/coreclr/templates/crossdac-build.yml b/eng/pipelines/coreclr/templates/crossdac-build.yml index 9bc3125f6a050b..31154ec5487986 100644 --- a/eng/pipelines/coreclr/templates/crossdac-build.yml +++ b/eng/pipelines/coreclr/templates/crossdac-build.yml @@ -1,5 +1,6 @@ parameters: archType: '' + isOfficialBuild: false osGroup: '' osSubgroup: '' hostArchType: '' @@ -52,6 +53,12 @@ steps: displayName: Gather CrossDac Artifacts + - template: /eng/pipelines/coreclr/templates/sign-diagnostic-files.yml + parameters: + basePath: $(crossDacArtifactPath) + isOfficialBuild: ${{ parameters.isOfficialBuild }} + timeoutInMinutes: 30 + - ${{ if eq(parameters.osGroup, 'Linux') }}: - task: CopyFiles@2 displayName: Gather runtime for CrossDac diff --git a/eng/pipelines/coreclr/templates/crossdac-pack.yml b/eng/pipelines/coreclr/templates/crossdac-pack.yml index 40e375bb9375c4..fc3ef404c171a9 100644 --- a/eng/pipelines/coreclr/templates/crossdac-pack.yml +++ b/eng/pipelines/coreclr/templates/crossdac-pack.yml @@ -54,19 +54,6 @@ jobs: - ${{ parameters.runtimeFlavor }}_${{ parameters.runtimeVariant }}_product_build_${{ platform }}_${{ parameters.buildConfig }} steps: - # Install MicroBuild for signing the package - - ${{ if eq(parameters.isOfficialBuild, true) }}: - - template: /eng/pipelines/common/restore-internal-tools.yml - - - task: MicroBuildSigningPlugin@2 - displayName: Install MicroBuild plugin for Signing - inputs: - signType: $(SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - continueOnError: false - condition: and(succeeded(), in(variables['SignType'], 'real', 'test')) - - task: DownloadBuildArtifacts@0 displayName: Download CrossDac artifacts inputs: @@ -77,16 +64,6 @@ jobs: - script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -subset crossdacpack -arch $(archType) $(osArg) -c $(buildConfig) $(officialBuildIdArg) $(crossDacArgs) -ci displayName: Build crossdac packaging - # Sign diagnostic files - - ${{ if eq(parameters.isOfficialBuild, true) }}: - - powershell: >- - eng\common\build.ps1 -ci -sign -restore -configuration:$(buildConfig) -warnaserror:0 $(officialBuildIdArg) - /p:PackagesFolder="$(Build.SourcesDirectory)/artifacts/packages/$(buildConfig)" - /p:SignDiagnosticsPackages=true - /p:DotNetSignType=$(SignType) - -projects $(Build.SourcesDirectory)\eng\empty.csproj - displayName: Sign CrossDac package and contents - # Save packages using the prepare-signed-artifacts format. - template: /eng/pipelines/common/upload-intermediate-artifacts-step.yml parameters: diff --git a/eng/pipelines/coreclr/templates/perf-job.yml b/eng/pipelines/coreclr/templates/perf-job.yml index 60f603de7962b3..9ad28b5f94aca0 100644 --- a/eng/pipelines/coreclr/templates/perf-job.yml +++ b/eng/pipelines/coreclr/templates/perf-job.yml @@ -184,7 +184,7 @@ jobs: - script: >- mkdir -p $(librariesDownloadDir)/bin/wasm/wasm-data && mkdir -p $(librariesDownloadDir)/bin/wasm/dotnet && - cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-workload/* $(librariesDownloadDir)/bin/wasm/dotnet && + cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-net7/* $(librariesDownloadDir)/bin/wasm/dotnet && cp src/mono/wasm/test-main.js $(librariesDownloadDir)/bin/wasm/wasm-data/test-main.js && find $(librariesDownloadDir)/bin/wasm -type d && find $(librariesDownloadDir)/bin/wasm -type f -exec chmod 664 {} \; diff --git a/eng/pipelines/coreclr/templates/run-performance-job.yml b/eng/pipelines/coreclr/templates/run-performance-job.yml index d4e25aff3f4985..218c8262ad52de 100644 --- a/eng/pipelines/coreclr/templates/run-performance-job.yml +++ b/eng/pipelines/coreclr/templates/run-performance-job.yml @@ -64,29 +64,35 @@ jobs: - HelixPerfUploadTokenValue: '$(PerfCommandUploadTokenLinux)' - ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.osGroup, 'windows')) }}: - HelixPerfUploadTokenValue: '$(PerfCommandUploadToken)' + - ${{ if eq(parameters.runtimeType, 'wasm') }}: + - HelixPreCommandsWasmOnLinux: >- + sudo apt-get -y remove nodejs && + curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - && + sudo apt-get -y install nodejs && + npm install --prefix $HELIX_WORKITEM_PAYLOAD jsvu -g && + $HELIX_WORKITEM_PAYLOAD/bin/jsvu --os=linux64 --engines=v8,javascriptcore + - ${{ if ne(parameters.runtimeType, 'wasm') }}: + - HelixPreCommandsWasmOnLinux: echo - HelixPreCommandStemWindows: 'set ORIGPYPATH=%PYTHONPATH%;py -m pip install -U pip;py -3 -m venv %HELIX_WORKITEM_PAYLOAD%\.venv;call %HELIX_WORKITEM_PAYLOAD%\.venv\Scripts\activate.bat;set PYTHONPATH=;py -3 -m pip install -U pip;py -3 -m pip install azure.storage.blob==12.0.0;py -3 -m pip install azure.storage.queue==12.0.0;set "PERFLAB_UPLOAD_TOKEN=$(HelixPerfUploadTokenValue)"' - HelixPreCommandStemLinux: >- export ORIGPYPATH=$PYTHONPATH export CRYPTOGRAPHY_ALLOW_OPENSSL_102=true; echo "** Installing prerequistes **"; - python3 -m pip install -U pip && + python3 -m pip install --user -U pip && sudo apt-get -y install python3-venv && python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv && ls -l $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate && export PYTHONPATH= && - python3 -m pip install -U pip && - pip3 install azure.storage.blob==12.0.0 && - pip3 install azure.storage.queue==12.0.0 && + python3 -m pip install --user -U pip && + pip3 install --user azure.storage.blob==12.0.0 && + pip3 install --user azure.storage.queue==12.0.0 && sudo apt-get update && sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates && - curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - && - sudo apt-get -y install nodejs && - npm install --prefix $HELIX_WORKITEM_PAYLOAD jsvu -g && - $HELIX_WORKITEM_PAYLOAD/bin/jsvu --os=linux64 --engines=v8,javascriptcore && - export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)" && - export PERF_PREREQS_INSTALLED=1; - test "x$PERF_PREREQS_INSTALLED" = "x1" || echo "** Error: Failed to install prerequites **" - - HelixPreCommandStemMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib cargo;sudo apk add libgdiplus --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing; python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install -U pip;pip3 install azure.storage.blob==12.7.1;pip3 install azure.storage.queue==12.1.5;export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)"' + $(HelixPreCommandsWasmOnLinux) && + export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)" + || export PERF_PREREQS_INSTALL_FAILED=1; + test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **" + - HelixPreCommandStemMusl: 'export ORIGPYPATH=$PYTHONPATH;sudo apk add icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib cargo;sudo apk add libgdiplus --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing; python3 -m venv $HELIX_WORKITEM_PAYLOAD/.venv;source $HELIX_WORKITEM_PAYLOAD/.venv/bin/activate;export PYTHONPATH=;python3 -m pip install --user -U pip;pip3 install --user azure.storage.blob==12.7.1;pip3 install --user azure.storage.queue==12.1.5;export PERFLAB_UPLOAD_TOKEN="$(HelixPerfUploadTokenValue)"' - ExtraMSBuildLogsWindows: 'set MSBUILDDEBUGCOMM=1;set "MSBUILDDEBUGPATH=%HELIX_WORKITEM_UPLOAD_ROOT%"' - ExtraMSBuildLogsLinux: 'export MSBUILDDEBUGCOMM=1;export "MSBUILDDEBUGPATH=$HELIX_WORKITEM_UPLOAD_ROOT"' - HelixPreCommand: '' diff --git a/eng/pipelines/coreclr/templates/run-scenarios-job.yml b/eng/pipelines/coreclr/templates/run-scenarios-job.yml index f22aa119d0d81c..8767e90fbe9ba5 100644 --- a/eng/pipelines/coreclr/templates/run-scenarios-job.yml +++ b/eng/pipelines/coreclr/templates/run-scenarios-job.yml @@ -144,7 +144,7 @@ jobs: # copy wasm packs if running on wasm - script: >- mkdir -p $(librariesDownloadDir)/bin/wasm/data && - cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-workload $(librariesDownloadDir)/bin/wasm && + cp -r $(librariesDownloadDir)/BrowserWasm/staging/dotnet-net7 $(librariesDownloadDir)/bin/wasm && cp src/mono/wasm/test-main.js $(librariesDownloadDir)/bin/wasm/data/test-main.js && find $(librariesDownloadDir)/bin/wasm -type f -exec chmod 664 {} \; displayName: "Create wasm directory (Linux)" diff --git a/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml b/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml new file mode 100644 index 00000000000000..578a4f9999d5a7 --- /dev/null +++ b/eng/pipelines/coreclr/templates/sign-diagnostic-files.yml @@ -0,0 +1,80 @@ +parameters: + basePath: '' + isOfficialBuild: '' + timeoutInMinutes: '' + +steps: +- ${{ if and(eq(parameters.isOfficialBuild, true), ne(variables['Build.Reason'], 'PullRequest'), or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + - task: UseDotNet@2 + displayName: Install .NET 6 SDK for signing. + inputs: + packageType: 'sdk' + version: '6.0.x' + installationPath: '$(Agent.TempDirectory)/dotnet' + + - task: EsrpCodeSigning@1 + displayName: Sign Diagnostic Binaries + inputs: + ConnectedServiceName: 'dotnetesrp-diagnostics-dnceng' + FolderPath: ${{ parameters.basePath }} + Pattern: | + **/mscordaccore*.dll + **/mscordbi*.dll + UseMinimatch: true + signConfigType: 'inlineSignParams' + inlineOperation: >- + [ + { + "keyCode": "CP-471322", + "operationCode": "SigntoolSign", + "parameters": { + "OpusName": "Microsoft", + "OpusInfo": "http://www.microsoft.com", + "PageHash": "/NPH", + "FileDigest": "/fd sha256", + "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + }, + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "KeyCode": "CP-471322", + "OperationCode": "SigntoolVerify", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + SessionTimeout: ${{ parameters.timeoutInMinutes }} + MaxConcurrency: '50' + MaxRetryAttempts: '5' + env: + DOTNET_MULTILEVEL_LOOKUP: 0 + DOTNET_ROOT: '$(Agent.TempDirectory)/dotnet' + DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR: '$(Agent.TempDirectory)/dotnet' + + - powershell: | + $filesToSign = $(Get-ChildItem -Recurse ${{ parameters.basePath }} -Include mscordaccore*.dll, mscordbi*.dll) + foreach ($file in $filesToSign) { + $signingCert = $(Get-AuthenticodeSignature $file).SignerCertificate + if ($signingCert -eq $null) + { + throw "File $file does not contain a signature." + } + + if ($signingCert.Subject -ne "CN=.NET DAC, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" ` + -or $signingCert.Issuer -ne "CN=Microsoft Code Signing PCA 2010, O=Microsoft Corporation, L=Redmond, S=Washington, C=US") + { + throw "File $file not in expected trust chain." + } + + $certEKU = $signingCert.Extensions.Where({ $_.Oid.FriendlyName -eq "Enhanced Key Usage" }) | Select -First 1 + + if ($certEKU.EnhancedKeyUsages.Where({ $_.Value -eq "1.3.6.1.4.1.311.84.4.1" }).Count -ne 1) + { + throw "Signature for $file does not contain expected EKU." + } + + Write-Host "$file is correctly signed." + } + displayName: Validate diagnostic signatures diff --git a/eng/pipelines/libraries/enterprise/linux.yml b/eng/pipelines/libraries/enterprise/linux.yml index ca2ec8777146d6..2a6e5ea99e880d 100644 --- a/eng/pipelines/libraries/enterprise/linux.yml +++ b/eng/pipelines/libraries/enterprise/linux.yml @@ -33,7 +33,7 @@ jobs: - job: EnterpriseLinuxTests timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open steps: - bash: | diff --git a/eng/pipelines/libraries/stress/http.yml b/eng/pipelines/libraries/stress/http.yml index 6a270c0a60a7af..6100d90c010f96 100644 --- a/eng/pipelines/libraries/stress/http.yml +++ b/eng/pipelines/libraries/stress/http.yml @@ -12,6 +12,7 @@ schedules: include: - main - release/6.0 + - release/7.0 variables: - template: ../variables.yml @@ -30,7 +31,7 @@ jobs: variables: DUMPS_SHARE_MOUNT_ROOT: "/dumps-share" pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-ubuntu-1804-open steps: @@ -96,7 +97,7 @@ jobs: variables: DUMPS_SHARE_MOUNT_ROOT: "C:/dumps-share" pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-windows-2022-open steps: diff --git a/eng/pipelines/libraries/stress/ssl.yml b/eng/pipelines/libraries/stress/ssl.yml index 856627aac2ba10..163b73c028d5b2 100644 --- a/eng/pipelines/libraries/stress/ssl.yml +++ b/eng/pipelines/libraries/stress/ssl.yml @@ -12,6 +12,7 @@ schedules: include: - main - release/6.0 + - release/7.0 variables: - template: ../variables.yml @@ -29,7 +30,7 @@ jobs: displayName: Docker Linux timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open steps: @@ -54,7 +55,7 @@ jobs: displayName: Docker NanoServer timeoutInMinutes: 120 pool: - name: NetCore1ESPool-Public + name: NetCore-Svc-Public demands: ImageOverride -equals 1es-windows-2022-open steps: diff --git a/eng/pipelines/mono/templates/workloads-build.yml b/eng/pipelines/mono/templates/workloads-build.yml index 28fb20114c89a3..2dc0cc8a3ae7f4 100644 --- a/eng/pipelines/mono/templates/workloads-build.yml +++ b/eng/pipelines/mono/templates/workloads-build.yml @@ -56,12 +56,15 @@ jobs: IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.android-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.browser-wasm*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.ios-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.iossimulator-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.maccatalyst-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvos-*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NETCore.App.Runtime.Mono.tvossimulator-*.nupkg - IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.Manifest*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest*.nupkg + IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoTargets.Sdk*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.MonoAOTCompiler.Task*.nupkg IntermediateArtifacts/MonoRuntimePacks/Shipping/Microsoft.NET.Runtime.WebAssembly.Sdk*.nupkg diff --git a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml index 8b250869d444ed..016b799e0099fc 100644 --- a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml +++ b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml @@ -9,7 +9,7 @@ jobs: displayName: Prepare Signed Artifacts dependsOn: ${{ parameters.dependsOn }} pool: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals 1es-windows-2022 # Double the default timeout. timeoutInMinutes: 240 diff --git a/eng/pipelines/official/stages/publish.yml b/eng/pipelines/official/stages/publish.yml index 1061c783f2ab57..86aaa49a8bae31 100644 --- a/eng/pipelines/official/stages/publish.yml +++ b/eng/pipelines/official/stages/publish.yml @@ -19,7 +19,7 @@ stages: publishUsingPipelines: true dependsOn: PrepareSignedArtifacts pool: - name: NetCore1ESPool-Internal + name: NetCore1ESPool-Svc-Internal demands: ImageOverride -equals 1es-windows-2022 # Stages-based publishing entry point diff --git a/eng/pipelines/runtime-community.yml b/eng/pipelines/runtime-community.yml index 2ceed817ca6989..50ba34100d3ea6 100644 --- a/eng/pipelines/runtime-community.yml +++ b/eng/pipelines/runtime-community.yml @@ -1,4 +1,23 @@ -trigger: none +trigger: + batch: true + branches: + include: + - release/*.* + exclude: + - release/6.0* + paths: + include: + - '*' + - docs/manpages/* + exclude: + - '**.md' + - eng/Version.Details.xml + - .devcontainer/* + - .github/* + - docs/* + - LICENSE.TXT + - PATENTS.TXT + - THIRD-PARTY-NOTICES.TXT schedules: - cron: "0 7,19 * * *" # run at 7:00 and 19:00 (UTC) which is 23:00 and 11:00 (PST). diff --git a/eng/pipelines/runtime-extra-platforms-other.yml b/eng/pipelines/runtime-extra-platforms-other.yml index 28e2e1b6a83300..7ef2f1bee1e6a5 100644 --- a/eng/pipelines/runtime-extra-platforms-other.yml +++ b/eng/pipelines/runtime-extra-platforms-other.yml @@ -412,6 +412,47 @@ jobs: eq(variables['monoContainsChange'], true), eq(variables['isRollingBuild'], true)) +# +# Build the whole product using Mono for Android Linux with Bionic libc +# +- template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml + buildConfig: Release + runtimeFlavor: mono + platforms: + - Linux_bionic_arm64 + - Linux_bionic_x64 + variables: + - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: + - name: _HelixSource + value: pr/dotnet/runtime/$(Build.SourceBranch) + - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: + - name: _HelixSource + value: ci/dotnet/runtime/$(Build.SourceBranch) + - name: timeoutPerTestInMinutes + value: 60 + - name: timeoutPerTestCollectionInMinutes + value: 180 + jobParameters: + testGroup: innerloop + targetRid: Linux-bionic-x64 + nameSuffix: AllSubsets_Mono + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true + timeoutInMinutes: 240 + condition: >- + or( + eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), + eq(variables['isRollingBuild'], true)) + # extra steps, run tests + extraStepsTemplate: /eng/pipelines/libraries/helix.yml + extraStepsParameters: + creator: dotnet-bot + testRunNamePrefixSuffix: Mono_$(_BuildConfig)_LinuxBionic + # # Build the whole product using Mono and run runtime tests # Build Mono release diff --git a/eng/pipelines/runtime-extra-platforms.yml b/eng/pipelines/runtime-extra-platforms.yml index 02591d91b6b0e3..392779ca6e9892 100644 --- a/eng/pipelines/runtime-extra-platforms.yml +++ b/eng/pipelines/runtime-extra-platforms.yml @@ -7,6 +7,7 @@ # if there is a push while a build in progress, it will wait, # until the running build finishes, and produce a build with all the changes # that happened during the last build. + trigger: none schedules: diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index d011ddb32f28f7..730aafee23682f 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -32,8 +32,12 @@ variables: value: .NETCore - name: _DotNetValidationArtifactsCategory value: .NETCoreValidation -- name: PostBuildSign - value: true +- ${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/')) }}: + - name: PostBuildSign + value: false +- ${{ else }}: + - name: PostBuildSign + value: true stages: - stage: Build diff --git a/eng/pipelines/runtime-staging.yml b/eng/pipelines/runtime-staging.yml index b5ce3e8bf43144..2d7576c6fbe88e 100644 --- a/eng/pipelines/runtime-staging.yml +++ b/eng/pipelines/runtime-staging.yml @@ -201,47 +201,6 @@ jobs: creator: dotnet-bot testRunNamePrefixSuffix: Mono_$(_BuildConfig) -# -# Build the whole product using Mono for Android Linux with Bionic libc -# -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml - buildConfig: Release - runtimeFlavor: mono - platforms: - - Linux_bionic_arm64 - - Linux_bionic_x64 - variables: - - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: pr/dotnet/runtime/$(Build.SourceBranch) - - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: ci/dotnet/runtime/$(Build.SourceBranch) - - name: timeoutPerTestInMinutes - value: 60 - - name: timeoutPerTestCollectionInMinutes - value: 180 - jobParameters: - testGroup: innerloop - targetRid: Linux-bionic-x64 - nameSuffix: AllSubsets_Mono - buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true - timeoutInMinutes: 240 - condition: >- - or( - eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), - eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), - eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), - eq(variables['isRollingBuild'], true)) - # extra steps, run tests - extraStepsTemplate: /eng/pipelines/libraries/helix.yml - extraStepsParameters: - creator: dotnet-bot - testRunNamePrefixSuffix: Mono_$(_BuildConfig)_LinuxBionic - # # Build the whole product using Mono for Android and run runtime tests with Android devices # diff --git a/eng/pipelines/runtime-wasm-features.yml b/eng/pipelines/runtime-wasm-features.yml new file mode 100644 index 00000000000000..940b59c800ab25 --- /dev/null +++ b/eng/pipelines/runtime-wasm-features.yml @@ -0,0 +1,27 @@ +# This pipeline is meant to be run manually. It contains +# jobs that exercise extra/optional features for wasm, eg. SIMD + +trigger: none + +variables: + - template: /eng/pipelines/common/variables.yml + +jobs: + +# Evaluate paths +- template: /eng/pipelines/common/evaluate-default-paths.yml + +# Run AOT tests with SIMD enabled +- template: /eng/pipelines/common/templates/wasm-library-tests.yml + parameters: + platforms: + - Browser_wasm + nameSuffix: _SIMD_AOT + isExtraPlatformsBuild: false + isWasmOnlyBuild: true + extraBuildArgs: /p:EnableAggressiveTrimming=true /p:BuildAOTTestsOnHelix=true /p:RunAOTCompilation=true + extraHelixArgs: /p:NeedsToBuildWasmAppsOnHelix=true /p:WasmEnableSIMD=true + runSmokeOnlyArg: '' + alwaysRun: true + scenarios: + - WasmTestOnNodeJS diff --git a/eng/pipelines/runtime-wasm.yml b/eng/pipelines/runtime-wasm.yml index 62de930be9d1f9..04eac67a9399cf 100644 --- a/eng/pipelines/runtime-wasm.yml +++ b/eng/pipelines/runtime-wasm.yml @@ -19,3 +19,22 @@ jobs: parameters: isWasmOnlyBuild: ${{ variables.isWasmOnlyBuild }} isRollingBuild: ${{ variables.isRollingBuild }} + +# Run AOT tests with SIMD enabled +- template: /eng/pipelines/common/templates/wasm-library-tests.yml + parameters: + platforms: + - Browser_wasm + nameSuffix: _SIMD_AOT + isExtraPlatformsBuild: false + isWasmOnlyBuild: true + extraBuildArgs: /p:EnableAggressiveTrimming=true /p:BuildAOTTestsOnHelix=true /p:RunAOTCompilation=true + extraHelixArgs: /p:NeedsToBuildWasmAppsOnHelix=true /p:WasmEnableSIMD=true + runSmokeOnlyArg: '' + alwaysRun: true + # failures due to + # https://github.com/dotnet/runtime/issues/75044 + # and https://github.com/dotnet/runtime/issues/75098 + shouldContinueOnError: true + scenarios: + - WasmTestOnNodeJS diff --git a/eng/regenerate-third-party-notices.proj b/eng/regenerate-third-party-notices.proj index 994e54daa6d10e..011a6832c3903b 100644 --- a/eng/regenerate-third-party-notices.proj +++ b/eng/regenerate-third-party-notices.proj @@ -20,6 +20,7 @@ + diff --git a/eng/restore/optimizationData.targets b/eng/restore/optimizationData.targets index 2072c6a28d2f06..9d8717803f1946 100644 --- a/eng/restore/optimizationData.targets +++ b/eng/restore/optimizationData.targets @@ -3,7 +3,9 @@ + + diff --git a/eng/testing/performance/microbenchmarks.proj b/eng/testing/performance/microbenchmarks.proj index 1331e1e7a811c0..3793751775f92d 100644 --- a/eng/testing/performance/microbenchmarks.proj +++ b/eng/testing/performance/microbenchmarks.proj @@ -132,10 +132,10 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"; + if [ "x$PERF_PREREQS_INSTALL_FAILED" = "x1" ]; then + echo "\n\n** Error: Failed to install prerequisites **\n\n"; (exit 1); else - echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"; fi $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand) @@ -148,10 +148,10 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)" - if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"; + if [ "x$PERF_PREREQS_INSTALL_FAILED" = "x1" ]; then + echo "\n\n** Error: Failed to install prerequisites **\n\n"; (exit 1); else - echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"; fi $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults) diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index bbbca109e575c1..7a92f4e164eb3d 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -17,6 +17,8 @@ Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmNativeDefaultsTests -Wasm.Build.Tests.WorkloadTests Wasm.Build.Tests.WasmRunOutOfAppBundleTests +Wasm.Build.Tests.WasmSIMDTests Wasm.Build.Tests.WasmTemplateTests +Wasm.Build.Tests.WorkloadTests + diff --git a/eng/testing/tests.singlefile.targets b/eng/testing/tests.singlefile.targets index 86a8b98d545f84..d307e837e6235a 100644 --- a/eng/testing/tests.singlefile.targets +++ b/eng/testing/tests.singlefile.targets @@ -26,48 +26,13 @@ $(CoreCLRILCompilerDir)netstandard/ILCompiler.Build.Tasks.dll $(CoreCLRAotSdkDir) $(NetCoreAppCurrentTestHostSharedFrameworkPath) - $(NoWarn);IL3050;IL3051;IL3052;IL3054;IL3055;IL1005;IL3002 + $(NoWarn);IL1005;IL3002 partial - false true + true true - - - - $(NoWarn);IL2026;IL2116 - - $(NoWarn);IL2041;IL2042;IL2043;IL2056 - - $(NoWarn);IL2045 - - $(NoWarn);IL2046 - - $(NoWarn);IL2050 - - $(NoWarn);IL2032;IL2055;IL2057;IL2058;IL2059;IL2060;IL2061;IL2096 - - $(NoWarn);IL2062;IL2063;IL2064;IL2065;IL2066 - - $(NoWarn);IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091 - - $(NoWarn);IL2092;IL2093;IL2094;IL2095 - - $(NoWarn);IL2097;IL2098;IL2099;IL2106 - - $(NoWarn);IL2103 - - $(NoWarn);IL2107;IL2117 - - $(NoWarn);IL2109 - - $(NoWarn);IL2110;IL2111;IL2114;IL2115 - - $(NoWarn);IL2112;IL2113 - - $(NoWarn);IL2118;IL2119;IL2120 - diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 02af8e04189c71..b85acb34fb6196 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -27,6 +27,8 @@ <_WasmStrictVersionMatch Condition="'$(ContinuousIntegrationBuild)' == 'true'">true true <_UseWasmSymbolicator Condition="'$(TestTrimming)' != 'true'">true + true + false @@ -103,6 +105,8 @@ <_AOTBuildCommand Condition="'$(ContinuousIntegrationBuild)' != 'true'">$(_AOTBuildCommand) /p:RuntimeSrcDir=$(RepoRoot) /p:RuntimeConfig=$(Configuration) <_AOTBuildCommand>$(_AOTBuildCommand) /p:RunAOTCompilation=$(RunAOTCompilation) + <_AOTBuildCommand Condition="'$(BrowserHost)' == 'windows'">$(_AOTBuildCommand) %AOT_BUILD_ARGS% + <_AOTBuildCommand Condition="'$(BrowserHost)' != 'windows'">$(_AOTBuildCommand) %24AOT_BUILD_ARGS <_AOTBuildCommand>$(_AOTBuildCommand) $(_ShellCommandSeparator) cd wasm_build/AppBundle $(_AOTBuildCommand) @@ -138,14 +142,27 @@ - - + VersionBand="$(SdkBandVersion)" + IgnoreErrors="$(WasmIgnoreNet6WorkloadInstallErrors)" + /> + + + + + diff --git a/eng/testing/workloads-testing.targets b/eng/testing/workloads-testing.targets index 5da047758f8185..280d9160b9c9e5 100644 --- a/eng/testing/workloads-testing.targets +++ b/eng/testing/workloads-testing.targets @@ -5,6 +5,12 @@ true + + <_SdkForWorkloadTestingBasePath>$(ArtifactsBinDir) + <_SdkWithNoWorkloadPath>$([MSBuild]::NormalizeDirectory($(_SdkForWorkloadTestingBasePath), 'dotnet-none')) + <_SdkWithNoWorkloadStampPath>$([MSBuild]::NormalizePath($(_SdkWithNoWorkloadPath), '.version-$(SdkVersionForWorkloadTesting).stamp')) + + <_DefaultPropsForNuGetBuild Include="Configuration=$(Configuration)" /> <_DefaultPropsForNuGetBuild Include="TargetOS=Browser" /> @@ -12,43 +18,21 @@ <_DefaultPropsForNuGetBuild Include="ContinuousIntegrationBuild=$(ContinuousIntegrationBuild)" /> - + + - - - - - - - - <_SourceFiles Include="$(SdkWithNoWorkloadForTestingPath)\**" /> + <_SdkWithNoWorkloadTarget Include="none" InstallPath="$(_SdkWithNoWorkloadPath)" /> - - - - - - - - - - - - - - + + <_DotNetInstallScriptName Condition="!$([MSBuild]::IsOSPlatform('windows'))">dotnet-install.sh @@ -59,16 +43,16 @@ <_DotNetInstallCommand Condition="!$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -i $(SdkWithNoWorkloadForTestingPath) -v $(SdkVersionForWorkloadTesting) + >$(_DotNetInstallScriptPath) -i $(_SdkWithNoWorkloadPath) -v $(SdkVersionForWorkloadTesting) <_DotNetInstallCommand Condition="$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Version $(SdkVersionForWorkloadTesting) + >$(_DotNetInstallScriptPath) -InstallDir $(_SdkWithNoWorkloadPath) -Version $(SdkVersionForWorkloadTesting) <_DotNetInstallCommand Condition="!$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -i $(SdkWithNoWorkloadForTestingPath) -v latest -q daily --channel 7.0 + >$(_DotNetInstallScriptPath) -i $(_SdkWithNoWorkloadPath) -v latest -q daily --channel 7.0 <_DotNetInstallCommand Condition="$([MSBuild]::IsOSPlatform('windows'))" - >$(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Quality daily -Channel 7.0 + >$(_DotNetInstallScriptPath) -InstallDir $(_SdkWithNoWorkloadPath) -Quality daily -Channel 7.0 - - + + + <_SdkWithWorkloadToInstall Include="@(WorkloadCombinationsToInstall)" /> + <_SdkWithWorkloadToInstall InstallPath="$(_SdkForWorkloadTestingBasePath)\dotnet-%(Identity)" /> + + <_SdkWithWorkloadToInstall StampPath="%(InstallPath)\version.stamp" /> @@ -104,7 +92,7 @@ @@ -168,7 +156,7 @@ + Text="Expected to find either one or three in $(LibrariesShippingPackagesDir): @(_RuntimePackNugetAvailable->'%(FileName)%(Extension)')" /> <_BuildVariants Include="multithread" Condition="'$(_DefaultBuildVariant)' != '.multithread.'" /> @@ -183,7 +171,7 @@ <_NuGetsToBuild Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.browser-wasm.$(_PackageVersion).nupkg" Project="$(InstallerProjectRoot)pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.sfxproj" - Properties="@(_DefaultPropsForNuGetBuild, ';')" + Properties="@(_DefaultPropsForNuGetBuild, ';');MonoWasmBuildVariant=" Dependencies="$(_DefaultRuntimePackNuGetPath)" Descriptor="single threaded runtime pack" Condition="'$(_DefaultBuildVariant)' != '.'" /> @@ -202,42 +190,24 @@ *******************" /> - + Outputs="@(_SdkWithWorkloadToInstall->'%(StampPath)');$(_SdkWithNoWorkloadStampPath)"> <_BuiltNuGets Include="$(LibrariesShippingPackagesDir)\*.nupkg" /> - - - - - - - - - - - + SdkWithNoWorkloadInstalledPath="$(_SdkWithNoWorkloadPath)" + /> - + diff --git a/global.json b/global.json index 9f0baa92e79cf3..555895afe031e4 100644 --- a/global.json +++ b/global.json @@ -8,9 +8,9 @@ "dotnet": "7.0.100-preview.7.22377.5" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22411.2", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22411.2", - "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22411.2", + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22418.4", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22418.4", + "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22418.4", "Microsoft.Build.NoTargets": "3.5.0", "Microsoft.Build.Traversal": "3.1.6", "Microsoft.NET.Sdk.IL": "7.0.0-rc.1.22414.6" diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index ac322bdf20c002..8ff2878596daf6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -18,23 +18,12 @@ internal sealed unsafe class RtFieldInfo : RuntimeFieldInfo, IRuntimeFieldInfo // lazy caching private string? m_name; private RuntimeType? m_fieldType; - internal FieldAccessor? m_invoker; - + private InvocationFlags m_invocationFlags; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Invoker._invocationFlags & InvocationFlags.Initialized) != 0 ? - Invoker._invocationFlags : InitializeInvocationFlags(); - } - - private FieldAccessor Invoker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - m_invoker ??= new FieldAccessor(this); - return m_invoker; - } + get => (m_invocationFlags & InvocationFlags.Initialized) != 0 ? + m_invocationFlags : InitializeInvocationFlags(); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -67,7 +56,7 @@ private InvocationFlags InitializeInvocationFlags() } // must be last to avoid threading problems - return Invoker._invocationFlags = invocationFlags | InvocationFlags.Initialized; + return m_invocationFlags = invocationFlags | InvocationFlags.Initialized; } #endregion @@ -113,63 +102,6 @@ internal override bool CacheEquals(object? o) return o is RtFieldInfo m && m.m_fieldHandle == m_fieldHandle; } - [DebuggerStepThrough] - [DebuggerHidden] - internal object? GetValueNonEmit(object? obj) - { - RuntimeType? declaringType = DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - return retVal; - } - } - - [DebuggerStepThrough] - [DebuggerHidden] - internal void SetValueNonEmit(object? obj, object? value) - { - RuntimeType? declaringType = DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - RuntimeFieldHandle.SetValue( - this, - obj, - value, - fieldType, - Attributes, - declaringType: null, - ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - - RuntimeFieldHandle.SetValue( - this, - obj, - value, - fieldType, - Attributes, - declaringType, - ref domainInitialized); - - declaringType.DomainInitialized = domainInitialized; - } - } - #endregion #region MemberInfo Overrides @@ -211,7 +143,20 @@ public override int GetHashCode() => CheckConsistency(obj); - return Invoker.GetValue(obj); + RuntimeType fieldType = (RuntimeType)FieldType; + + bool domainInitialized = false; + if (declaringType == null) + { + return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + return retVal; + } } public override object GetRawConstantValue() { throw new InvalidOperationException(); } @@ -258,7 +203,17 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); } - Invoker.SetValue(obj, value); + bool domainInitialized = false; + if (declaringType is null) + { + RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + } } [DebuggerStepThrough] diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 6b7d2425135cc0..4b8846c4ad34f5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1185,7 +1185,6 @@ public RuntimeFieldInfoStub(RuntimeFieldHandleInternal fieldHandle, object keepa private object? m_d; private int m_b; private object? m_e; - private object? m_f; private RuntimeFieldHandleInternal m_fieldHandle; #pragma warning restore 414, 169 diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 595f5482fca5b9..bc731b674d2e97 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -176,7 +176,7 @@ internal static unsafe void ConvertFixedToNative(int flags, string strManaged, I internal static unsafe string ConvertFixedToManaged(IntPtr cstr, int length) { - int end = SpanHelpers.IndexOf(ref *(byte*)cstr, 0, length); + int end = new ReadOnlySpan((byte*)cstr, length).IndexOf((byte)0); if (end >= 0) { length = end; @@ -450,7 +450,7 @@ internal static unsafe void ConvertToNative(string? strManaged, IntPtr nativeHom internal static unsafe string ConvertToManaged(IntPtr nativeHome, int length) { - int end = SpanHelpers.IndexOf(ref *(char*)nativeHome, '\0', length); + int end = new ReadOnlySpan((char*)nativeHome, length).IndexOf('\0'); if (end >= 0) { length = end; diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index 23601cfde1b5d9..529ed2f7f35777 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "createdump.h" +#include // This is for the PAL_VirtualUnwindOutOfProc read memory adapter. CrashInfo* g_crashInfo; @@ -328,24 +329,34 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) { TRACE("EnumerateMemoryRegionsWithDAC: Memory enumeration STARTED (%d %d)\n", m_enumMemoryPagesAdded, m_dataTargetPagesAdded); - // Since on both Linux and MacOS all the RW regions will be added for heap - // dumps by createdump, the only thing differentiating a MiniDumpNormal and - // a MiniDumpWithPrivateReadWriteMemory is that the later uses the EnumMemory - // APIs. This is kind of expensive on larger applications (4 minutes, or even - // more), and this should already be in RW pages. Change the dump type to the - // faster normal one. This one already ensures necessary DAC globals, etc. - // without the costly assembly, module, class, type runtime data structures - // enumeration. + // Since on MacOS all the RW regions will be added for heap dumps by createdump, the + // only thing differentiating a MiniDumpNormal and a MiniDumpWithPrivateReadWriteMemory + // is that the later uses the EnumMemoryRegions APIs. This is kind of expensive on larger + // applications (4 minutes, or even more), and this should already be in RW pages. Change + // the dump type to the faster normal one. This one already ensures necessary DAC globals, + // etc. without the costly assembly, module, class, type runtime data structures enumeration. + CLRDataEnumMemoryFlags flags = CLRDATA_ENUM_MEM_DEFAULT; if (minidumpType & MiniDumpWithPrivateReadWriteMemory) { - char* fastHeapDumps = getenv("COMPlus_DbgEnableFastHeapDumps"); - if (fastHeapDumps != nullptr && strcmp(fastHeapDumps, "1") == 0) + // This is the old fast heap env var for backwards compatibility for VS4Mac. + CLRConfigNoCache fastHeapDumps = CLRConfigNoCache::Get("DbgEnableFastHeapDumps", /*noprefix*/ false, &getenv); + DWORD val = 0; + if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1) { minidumpType = MiniDumpNormal; } + // This the new variable that also skips the expensive (in both time and memory usage) + // enumeration of the low level data structures and adds all the loader allocator heaps + // instead. The above original env var didn't generate a complete enough heap dump on + // Linux and this new one does. + fastHeapDumps = CLRConfigNoCache::Get("EnableFastHeapDumps", /*noprefix*/ false, &getenv); + if (fastHeapDumps.IsSet() && fastHeapDumps.TryAsInteger(10, val) && val == 1) + { + flags = CLRDATA_ENUM_MEM_HEAP2; + } } // Calls CrashInfo::EnumMemoryRegion for each memory region found by the DAC - HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, CLRDATA_ENUM_MEM_DEFAULT); + HRESULT hr = m_pClrDataEnumRegions->EnumMemoryRegions(this, minidumpType, flags); if (FAILED(hr)) { printf_error("EnumMemoryRegions FAILED %s (%08x)\n", GetHResultString(hr), hr); diff --git a/src/coreclr/debug/createdump/crashinfomac.cpp b/src/coreclr/debug/createdump/crashinfomac.cpp index 02534a7a9e7eb0..f1f91ff4a55ca2 100644 --- a/src/coreclr/debug/createdump/crashinfomac.cpp +++ b/src/coreclr/debug/createdump/crashinfomac.cpp @@ -487,7 +487,7 @@ ModuleInfo::LoadModule() } else { - TRACE("LoadModule: dlopen(%s) FAILED %d %s\n", m_moduleName.c_str(), errno, strerror(errno)); + TRACE("LoadModule: dlopen(%s) FAILED %s\n", m_moduleName.c_str(), dlerror()); } } } diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 71bfcd7f898f47..bb476fa8145aaf 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -6246,24 +6246,15 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= { if (!IsFullyReadable(addr, size)) { - if (!fExpectSuccess) + if (fExpectSuccess) { - // We know the read might fail (eg. we're trying to find mapped pages in - // a module image), so just skip this block silently. - // Note that the EnumMemoryRegion callback won't necessarily do anything if any part of - // the region is unreadable, and so there is no point in calling it. For cases where we expect - // the read might fail, but we want to report any partial blocks, we have to break up the region - // into pages and try reporting each page anyway - return true; + // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort + // reporting and continue with the next data structure (where the exception is caught), + // just like we would for a DAC read error (otherwise we might do something stupid + // like get into an infinite loop, or otherwise waste time with corrupt data). + TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering"); + return false; } - - // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort - // reporting and continue with the next data structure (where the exception is caught), - // just like we would for a DAC read error (otherwise we might do something stupid - // like get into an infinite loop, or otherwise waste time with corrupt data). - - TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering"); - return false; } } @@ -6275,9 +6266,7 @@ bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= // data structure at all. Hopefully experience will help guide this going forward. // @dbgtodo : Extend dump-gathering API to allow a dump-log to be included. const TSIZE_T kMaxMiniDumpRegion = 4*1024*1024 - 3; // 4MB-3 - if( size > kMaxMiniDumpRegion - && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI - || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE)) + if (size > kMaxMiniDumpRegion && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE)) { TARGET_CONSISTENCY_CHECK( false, "Dump target consistency failure - truncating minidump data structure"); size = kMaxMiniDumpRegion; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 91a6e4ecc7e261..acf458a39d2bea 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3618,7 +3618,7 @@ void DacDbiInterfaceImpl::EnumerateMemRangesForLoaderAllocator(PTR_LoaderAllocat // GetVirtualCallStubManager returns VirtualCallStubManager*, but it's really an address to target as // pLoaderAllocator is DACized. Cast it so we don't try to to a Host to Target translation. - VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager(TO_TADDR(pLoaderAllocator->GetVirtualCallStubManager())); + VirtualCallStubManager *pVcsMgr = pLoaderAllocator->GetVirtualCallStubManager(); LOG((LF_CORDB, LL_INFO10000, "DDBII::EMRFLA: VirtualCallStubManager 0x%x\n", PTR_HOST_TO_TADDR(pVcsMgr))); if (pVcsMgr) { diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index c6a01bd2d48ff9..33a2149e9a56e3 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1328,6 +1328,7 @@ class ClrDataAccess HRESULT EnumMemCollectImages(); HRESULT EnumMemCLRStatic(CLRDataEnumMemoryFlags flags); + HRESULT EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags); HRESULT EnumMemCLRHeapCrticalStatic(CLRDataEnumMemoryFlags flags); HRESULT EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags); HRESULT EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags); diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 7db97915d132d1..ee30bf593bb5f3 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -283,6 +283,21 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) return S_OK; } +HRESULT ClrDataAccess::EnumMemDumpJitManagerInfo(IN CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + + HRESULT status = S_OK; + + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + EEJitManager* managerPtr = ExecutionManager::GetEEJitManager(); + managerPtr->EnumMemoryRegions(flags); + } + + return status; +} + //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // This function reports memory that a heap dump need to debug CLR @@ -325,6 +340,9 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla // Dump AppDomain-specific info CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) + // Dump jit manager info + CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpJitManagerInfo(flags); ) + // Dump the Debugger object data needed CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); ) @@ -680,6 +698,11 @@ HRESULT ClrDataAccess::EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + SystemDomain::System()->GetLoaderAllocator()->EnumMemoryRegions(flags); + } + AppDomainIterator adIter(FALSE); EX_TRY { @@ -1853,7 +1876,7 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags) // triage micro-dump status = EnumMemoryRegionsWorkerMicroTriage(flags); } - else if (flags == CLRDATA_ENUM_MEM_HEAP) + else if (flags == CLRDATA_ENUM_MEM_HEAP || flags == CLRDATA_ENUM_MEM_HEAP2) { status = EnumMemoryRegionsWorkerHeap(flags); } @@ -1946,7 +1969,15 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, if (miniDumpFlags & MiniDumpWithPrivateReadWriteMemory) { // heap dump - status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_HEAP); + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + DacLogMessage("EnumMemoryRegions(CLRDATA_ENUM_MEM_HEAP2)\n"); + } + else + { + flags = CLRDATA_ENUM_MEM_HEAP; + } + status = EnumMemoryRegionsWrapper(flags); } else if (miniDumpFlags & MiniDumpWithFullAuxiliaryState) { diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 7e2906cfcae443..5d755b2bf556e6 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3497,7 +3497,7 @@ ClrDataAccess::TraverseVirtCallStubHeap(CLRDATA_ADDRESS pAppDomain, VCSHeapType SOSDacEnter(); BaseDomain* pBaseDomain = PTR_BaseDomain(TO_TADDR(pAppDomain)); - VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager((TADDR)pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager()); + VirtualCallStubManager *pVcsMgr = pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager(); if (!pVcsMgr) { hr = E_POINTER; diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index df05ee034a0d96..f8a3adc971a460 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -13191,19 +13191,14 @@ void STDCALL ExceptionHijackWorker( // See code:ExceptionHijackPersonalityRoutine for more information. // // Arguments: -// * pExceptionRecord - not used -// * MemoryStackFp - not used -// * BackingStoreFp - not used -// * pContextRecord - not used -// * pDispatcherContext - not used -// * GlobalPointer - not used +// Standard personality routine signature. // // Return Value: // Always return ExceptionContinueSearch. // EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, - IN ULONG64 MemoryStackFp, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext) { @@ -13216,7 +13211,7 @@ EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExcept // Personality routine for unwinder the assembly hijack stub on 64-bit. // // Arguments: -// standard Personality routine signature. +// Standard personality routine signature. // // Assumptions: // This is caleld by the OS exception logic during exception handling. @@ -13243,9 +13238,8 @@ EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExcept // On AMD64, we work around this by using an empty personality routine. EXTERN_C EXCEPTION_DISPOSITION -ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG32 MemoryStackFp), +ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -13268,7 +13262,7 @@ ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord // This copies pHijackContext into pDispatcherContext, which the OS can then // use to walk the stack. - FixupDispatcherContext(pDispatcherContext, pHijackContext, pContextRecord, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine); + FixupDispatcherContext(pDispatcherContext, pHijackContext, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine); #else _ASSERTE(!"NYI - ExceptionHijackPersonalityRoutine()"); #endif @@ -16596,22 +16590,23 @@ Debugger::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { DAC_ENUM_VTHIS(); SUPPORTS_DAC; - _ASSERTE(m_rgHijackFunction != NULL); - if ( flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_TRIAGE) { if (m_pMethodInfos.IsValid()) { m_pMethodInfos->EnumMemoryRegions(flags); } - DacEnumMemoryRegion(dac_cast(m_pLazyData), - sizeof(DebuggerLazyInit)); + DacEnumMemoryRegion(dac_cast(m_pLazyData), sizeof(DebuggerLazyInit)); } // Needed for stack walking from an initial native context. If the debugger can find the // on-disk image of clr.dll, then this is not necessary. - DacEnumMemoryRegion(dac_cast(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions); + if (m_rgHijackFunction.IsValid()) + { + DacEnumMemoryRegion(dac_cast(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions); + } } diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 695b88aaef1ba4..f73f9c83810dc3 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -3872,7 +3872,7 @@ HANDLE OpenWin32EventOrThrow( bool DbgIsSpecialILOffset(DWORD offset); #if !defined(TARGET_X86) -void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, T_CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); +void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); #endif #endif /* DEBUGGER_H_ */ diff --git a/src/coreclr/debug/ee/funceval.cpp b/src/coreclr/debug/ee/funceval.cpp index 1d09d9df25355f..82fe538d9f3fb7 100644 --- a/src/coreclr/debug/ee/funceval.cpp +++ b/src/coreclr/debug/ee/funceval.cpp @@ -3579,7 +3579,7 @@ static void GCProtectArgsAndDoNormalFuncEval(DebuggerEval *pDE, GCX_FORBID(); RecordFuncEvalException( pDE, ppException); } - // Note: we need to catch all exceptioins here because they all get reported as the result of + // Note: we need to catch all exceptions here because they all get reported as the result of // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually. EX_END_CATCH(SwallowAllExceptions); @@ -3994,32 +3994,34 @@ void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE) #if defined(FEATURE_EH_FUNCLETS) && !defined(TARGET_UNIX) EXTERN_C EXCEPTION_DISPOSITION -FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG32 MemoryStackFp), +FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) { - DebuggerEval* pDE = NULL; + // The offset of the DebuggerEval pointer relative to the establisher frame. + SIZE_T debuggerEvalPtrOffset = 0; #if defined(TARGET_AMD64) - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame); + // On AMD64 the establisher frame is the SP of FuncEvalHijack itself. + // In FuncEvalHijack we store RCX at the current SP. + debuggerEvalPtrOffset = 0; #elif defined(TARGET_ARM) - // on ARM the establisher frame is the SP of the caller of FuncEvalHijack, on other platforms it's FuncEvalHijack's SP. - // in FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from + // On ARM the establisher frame is the SP of the FuncEvalHijack's caller. + // In FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from // the establisher frame we can get the stack location where R0 was stored. - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 8); - + debuggerEvalPtrOffset = 8; #elif defined(TARGET_ARM64) - // on ARM64 the establisher frame is the SP of the caller of FuncEvalHijack. - // in FuncEvalHijack we allocate 32 bytes of stack space and then store R0 at the current SP + 16, so if we subtract 16 from - // the establisher frame we can get the stack location where R0 was stored. - pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 16); + // On ARM64 the establisher frame is the SP of the FuncEvalHijack's caller. + // In FuncEvalHijack we allocate 32 bytes of stack space and then store X0 at the current SP + 16, so if we subtract 16 from + // the establisher frame we can get the stack location where X0 was stored. + debuggerEvalPtrOffset = 16; #else _ASSERTE(!"NYI - FuncEvalHijackPersonalityRoutine()"); #endif - FixupDispatcherContext(pDispatcherContext, &(pDE->m_context), pContextRecord); + DebuggerEval* pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - debuggerEvalPtrOffset); + FixupDispatcherContext(pDispatcherContext, &(pDE->m_context)); // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and // dispatcher context and restart the exception dispatching on this call frame, which is diff --git a/src/coreclr/debug/ee/functioninfo.cpp b/src/coreclr/debug/ee/functioninfo.cpp index 4cef128986074b..54c31f50f17127 100644 --- a/src/coreclr/debug/ee/functioninfo.cpp +++ b/src/coreclr/debug/ee/functioninfo.cpp @@ -2456,9 +2456,7 @@ DebuggerMethodInfoEntry::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // For a MiniDumpNormal, what is needed for modules is already enumerated elsewhere. // Don't waste time doing it here an extra time. Also, this will add many MB extra into the dump. - if ((key.pModule.IsValid()) && - CLRDATA_ENUM_MEM_MINI != flags - && CLRDATA_ENUM_MEM_TRIAGE != flags) + if ((key.pModule.IsValid()) && CLRDATA_ENUM_MEM_MINI != flags && CLRDATA_ENUM_MEM_TRIAGE != flags && CLRDATA_ENUM_MEM_HEAP2 != flags) { key.pModule->EnumMemoryRegions(flags, true); } @@ -2476,7 +2474,7 @@ DebuggerMethodInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DAC_ENUM_DTHIS(); SUPPORTS_DAC; - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { // Modules are enumerated already for minidumps, save the empty calls. if (m_module.IsValid()) @@ -2505,7 +2503,7 @@ DebuggerJitInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) m_methodInfo->EnumMemoryRegions(flags); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { if (m_nativeCodeVersion.GetMethodDesc().IsValid()) { diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index 892c1f1c27bfcb..e91bb09807bbdb 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -271,6 +271,7 @@ BEGIN IDS_EE_BADMARSHAL_ABSTRACTRETCRITICALHANDLE "Returned CriticalHandles cannot be abstract." IDS_EE_BADMARSHAL_CUSTOMMARSHALER "Custom marshalers are only allowed on classes, strings, arrays, and boxed value types." IDS_EE_BADMARSHAL_GENERICS_RESTRICTION "Non-blittable generic types cannot be marshaled." + IDS_EE_BADMARSHAL_INT128_RESTRICTION "System.Int128 and System.UInt128 cannot be passed by value to unmanaged." IDS_EE_BADMARSHAL_AUTOLAYOUT "Structures marked with [StructLayout(LayoutKind.Auto)] cannot be marshaled." IDS_EE_BADMARSHAL_STRING_OUT "Cannot marshal a string by-value with the [Out] attribute." IDS_EE_BADMARSHAL_MARSHAL_DISABLED "Cannot marshal managed types when the runtime marshalling system is disabled." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index d2177bd56db2fd..3245329339c91f 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -283,6 +283,7 @@ #define IDS_EE_BADMARSHAL_ABSTRACTOUTCRITICALHANDLE 0x1a63 #define IDS_EE_BADMARSHAL_RETURNCHCOMTONATIVE 0x1a64 #define IDS_EE_BADMARSHAL_CRITICALHANDLE 0x1a65 +#define IDS_EE_BADMARSHAL_INT128_RESTRICTION 0x1a66 #define IDS_EE_BADMARSHAL_ABSTRACTRETCRITICALHANDLE 0x1a6a #define IDS_EE_CH_IN_VARIANT_NOT_SUPPORTED 0x1a6b diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 993719b1536cf8..e7ea0c268fcc96 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -4473,8 +4473,12 @@ class CObjectHeader : public Object return ((ArrayBase *)this)->GetNumComponents(); } - void Validate(BOOL bDeep=TRUE) + void Validate(BOOL bDeep=TRUE, BOOL bVerifyNextHeader = FALSE, BOOL bVerifySyncBlock = FALSE) { + // declaration of extra parameters just so the call site would need no #ifdefs + UNREFERENCED_PARAMETER(bVerifyNextHeader); + UNREFERENCED_PARAMETER(bVerifySyncBlock); + MethodTable * pMT = GetMethodTable(); _ASSERTE(pMT->SanityCheck()); @@ -6085,20 +6089,22 @@ class heap_select uint16_t proc_no[MAX_SUPPORTED_CPUS]; uint16_t node_no[MAX_SUPPORTED_CPUS]; uint16_t max_node_no = 0; - for (uint16_t i = 0; i < n_heaps; i++) + uint16_t heap_num; + for (heap_num = 0; heap_num < n_heaps; heap_num++) { - if (!GCToOSInterface::GetProcessorForHeap (i, &proc_no[i], &node_no[i])) + if (!GCToOSInterface::GetProcessorForHeap (heap_num, &proc_no[heap_num], &node_no[heap_num])) break; - if (!do_numa || node_no[i] == NUMA_NODE_UNDEFINED) - node_no[i] = 0; - max_node_no = max(max_node_no, node_no[i]); + assert(proc_no[heap_num] < MAX_SUPPORTED_CPUS); + if (!do_numa || node_no[heap_num] == NUMA_NODE_UNDEFINED) + node_no[heap_num] = 0; + max_node_no = max(max_node_no, node_no[heap_num]); } // Pass 2: assign heap numbers by numa node int cur_heap_no = 0; for (uint16_t cur_node_no = 0; cur_node_no <= max_node_no; cur_node_no++) { - for (int i = 0; i < n_heaps; i++) + for (int i = 0; i < heap_num; i++) { if (node_no[i] != cur_node_no) continue; @@ -24659,12 +24665,7 @@ void gc_heap::set_background_overflow_p (uint8_t* oo) heap_segment* overflow_region = get_region_info_for_address (oo); overflow_region->flags |= heap_segment_flags_overflow; dprintf (3,("setting overflow flag for region %p", heap_segment_mem (overflow_region))); -#ifdef MULTIPLE_HEAPS - gc_heap* overflow_heap = heap_segment_heap (overflow_region); -#else - gc_heap* overflow_heap = nullptr; -#endif - overflow_heap->background_overflow_p = TRUE; + background_overflow_p = TRUE; } #endif //USE_REGIONS @@ -25311,13 +25312,13 @@ void gc_heap::background_process_mark_overflow_internal (uint8_t* min_add, uint8 dprintf (2, ("h%d: SOH: ov-mo: %Id", heap_number, total_marked_objects)); fire_overflow_event (min_add, max_add, total_marked_objects, i); - if (small_object_segments) + if (i >= soh_gen2) { concurrent_print_time_delta (concurrent_p ? "Cov SOH" : "Nov SOH"); + small_object_segments = FALSE; } total_marked_objects = 0; - small_object_segments = FALSE; } } } @@ -33980,23 +33981,35 @@ void gc_heap::background_scan_dependent_handles (ScanContext *sc) if (!s_fScanRequired) { -#ifndef USE_REGIONS +#ifdef USE_REGIONS + BOOL all_heaps_background_overflow_p = FALSE; +#else //USE_REGIONS uint8_t* all_heaps_max = 0; uint8_t* all_heaps_min = MAX_PTR; +#endif //USE_REGIONS int i; for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + // in the regions case, compute the OR of all the per-heap flags + if (g_heaps[i]->background_overflow_p) + all_heaps_background_overflow_p = TRUE; +#else //USE_REGIONS if (all_heaps_max < g_heaps[i]->background_max_overflow_address) all_heaps_max = g_heaps[i]->background_max_overflow_address; if (all_heaps_min > g_heaps[i]->background_min_overflow_address) all_heaps_min = g_heaps[i]->background_min_overflow_address; +#endif //USE_REGIONS } for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + g_heaps[i]->background_overflow_p = all_heaps_background_overflow_p; +#else //USE_REGIONS g_heaps[i]->background_max_overflow_address = all_heaps_max; g_heaps[i]->background_min_overflow_address = all_heaps_min; +#endif //USE_REGIONS } -#endif //!USE_REGIONS } dprintf(2, ("Starting all gc thread mark stack overflow processing")); @@ -34740,15 +34753,24 @@ void gc_heap::background_mark_phase () enable_preemptive (); -#if defined(MULTIPLE_HEAPS) && !defined(USE_REGIONS) +#if defined(MULTIPLE_HEAPS) bgc_t_join.join(this, gc_join_concurrent_overflow); if (bgc_t_join.joined()) { +#ifdef USE_REGIONS + BOOL all_heaps_background_overflow_p = FALSE; +#else //USE_REGIONS uint8_t* all_heaps_max = 0; uint8_t* all_heaps_min = MAX_PTR; +#endif //USE_REGIONS int i; for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + // in the regions case, compute the OR of all the per-heap flags + if (g_heaps[i]->background_overflow_p) + all_heaps_background_overflow_p = TRUE; +#else //USE_REGIONS dprintf (3, ("heap %d overflow max is %Ix, min is %Ix", i, g_heaps[i]->background_max_overflow_address, @@ -34757,17 +34779,21 @@ void gc_heap::background_mark_phase () all_heaps_max = g_heaps[i]->background_max_overflow_address; if (all_heaps_min > g_heaps[i]->background_min_overflow_address) all_heaps_min = g_heaps[i]->background_min_overflow_address; - +#endif //USE_REGIONS } for (i = 0; i < n_heaps; i++) { +#ifdef USE_REGIONS + g_heaps[i]->background_overflow_p = all_heaps_background_overflow_p; +#else //USE_REGIONS g_heaps[i]->background_max_overflow_address = all_heaps_max; g_heaps[i]->background_min_overflow_address = all_heaps_min; +#endif //USE_REGIONS } dprintf(3, ("Starting all bgc threads after updating the overflow info")); bgc_t_join.restart(); } -#endif //MULTIPLE_HEAPS && !USE_REGIONS +#endif //MULTIPLE_HEAPS disable_preemptive (true); @@ -44777,8 +44803,9 @@ HRESULT GCHeap::Initialize() uint32_t nhp = 1; uint32_t nhp_from_config = 0; -#ifdef MULTIPLE_HEAPS - +#ifndef MULTIPLE_HEAPS + GCConfig::SetServerGC(false); +#else //!MULTIPLE_HEAPS GCConfig::SetServerGC(true); AffinitySet config_affinity_set; GCConfigStringHolder cpu_index_ranges_holder(GCConfig::GetGCHeapAffinitizeRanges()); @@ -44808,7 +44835,9 @@ HRESULT GCHeap::Initialize() nhp_from_config = static_cast(GCConfig::GetHeapCount()); - g_num_active_processors = GCToEEInterface::GetCurrentProcessCpuCount(); + // The CPU count may be overriden by the user. Ensure that we create no more than g_num_processors + // heaps as that is the number of slots we have allocated for handle tables. + g_num_active_processors = min (GCToEEInterface::GetCurrentProcessCpuCount(), g_num_processors); if (nhp_from_config) { @@ -44833,7 +44862,7 @@ HRESULT GCHeap::Initialize() nhp = min(nhp, num_affinitized_processors); } } -#endif //MULTIPLE_HEAPS +#endif //!MULTIPLE_HEAPS size_t seg_size = 0; size_t large_seg_size = 0; @@ -44917,6 +44946,7 @@ HRESULT GCHeap::Initialize() #endif //USE_REGIONS #ifdef MULTIPLE_HEAPS + assert (nhp <= g_num_processors); gc_heap::n_heaps = nhp; hr = gc_heap::initialize_gc (seg_size, large_seg_size, pin_seg_size, nhp); #else @@ -44951,7 +44981,7 @@ HRESULT GCHeap::Initialize() int available_mem_th = 10; if (gc_heap::total_physical_mem >= ((uint64_t)80 * 1024 * 1024 * 1024)) { - int adjusted_available_mem_th = 3 + (int)((float)47 / (float)(GCToOSInterface::GetTotalProcessorCount())); + int adjusted_available_mem_th = 3 + (int)((float)47 / (float)g_num_processors); available_mem_th = min (available_mem_th, adjusted_available_mem_th); } @@ -45122,15 +45152,10 @@ HRESULT GCHeap::Initialize() // GC callback functions bool GCHeap::IsPromoted(Object* object) { -#ifdef _DEBUG - if (object) - { - ((CObjectHeader*)object)->Validate(); - } -#endif //_DEBUG - uint8_t* o = (uint8_t*)object; + bool is_marked; + if (gc_heap::settings.condemned_generation == max_generation) { #ifdef MULTIPLE_HEAPS @@ -45142,27 +45167,35 @@ bool GCHeap::IsPromoted(Object* object) #ifdef BACKGROUND_GC if (gc_heap::settings.concurrent) { - bool is_marked = (!((o < hp->background_saved_highest_address) && (o >= hp->background_saved_lowest_address))|| + is_marked = (!((o < hp->background_saved_highest_address) && (o >= hp->background_saved_lowest_address))|| hp->background_marked (o)); - return is_marked; } else #endif //BACKGROUND_GC { - return (!((o < hp->highest_address) && (o >= hp->lowest_address)) - || hp->is_mark_set (o)); + is_marked = (!((o < hp->highest_address) && (o >= hp->lowest_address)) + || hp->is_mark_set (o)); } } else { #ifdef USE_REGIONS - return (gc_heap::is_in_gc_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); + is_marked = (gc_heap::is_in_gc_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); #else gc_heap* hp = gc_heap::heap_of (o); - return (!((o < hp->gc_high) && (o >= hp->gc_low)) - || hp->is_mark_set (o)); + is_marked = (!((o < hp->gc_high) && (o >= hp->gc_low)) + || hp->is_mark_set (o)); #endif //USE_REGIONS } + +#ifdef _DEBUG + if (o) + { + ((CObjectHeader*)o)->Validate(TRUE, TRUE, is_marked); + } +#endif //_DEBUG + + return is_marked; } size_t GCHeap::GetPromotedBytes(int heap_index) diff --git a/src/coreclr/gc/gcconfig.cpp b/src/coreclr/gc/gcconfig.cpp index abf4b28e4f5d76..9957e43dc5c5db 100644 --- a/src/coreclr/gc/gcconfig.cpp +++ b/src/coreclr/gc/gcconfig.cpp @@ -7,13 +7,18 @@ #define BOOL_CONFIG(name, unused_private_key, unused_public_key, default, unused_doc) \ bool GCConfig::Get##name() { return s_##name; } \ + bool GCConfig::Get##name(bool defaultValue) \ + { \ + return s_##name##Provided ? s_##name : defaultValue; \ + } \ void GCConfig::Set##name(bool value) { s_Updated##name = value; } \ bool GCConfig::s_##name = default; \ + bool GCConfig::s_##name##Provided = false; \ bool GCConfig::s_Updated##name = default; #define INT_CONFIG(name, unused_private_key, unused_public_key, default, unused_doc) \ int64_t GCConfig::Get##name() { return s_##name; } \ - void GCConfig::Set##name(int64_t value) { s_Updated##name = value; } \ + void GCConfig::Set##name(int64_t value) { s_Updated##name = value; } \ int64_t GCConfig::s_##name = default; \ int64_t GCConfig::s_Updated##name = default; @@ -36,7 +41,7 @@ GC_CONFIGURATION_KEYS void GCConfig::EnumerateConfigurationValues(void* context, ConfigurationValueFunc configurationValueFunc) { -#define INT_CONFIG(name, unused_private_key, public_key, default, unused_doc) \ +#define INT_CONFIG(name, unused_private_key, public_key, unused_default, unused_doc) \ configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::Int64, static_cast(s_Updated##name)); #define STRING_CONFIG(name, private_key, public_key, unused_doc) \ @@ -47,7 +52,7 @@ void GCConfig::EnumerateConfigurationValues(void* context, ConfigurationValueFun configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::StringUtf8, reinterpret_cast(resultStr)); \ } -#define BOOL_CONFIG(name, unused_private_key, public_key, default, unused_doc) \ +#define BOOL_CONFIG(name, unused_private_key, public_key, unused_default, unused_doc) \ configurationValueFunc(context, (void*)(#name), (void*)(public_key), GCConfigurationType::Boolean, static_cast(s_Updated##name)); GC_CONFIGURATION_KEYS @@ -59,10 +64,10 @@ GC_CONFIGURATION_KEYS void GCConfig::Initialize() { -#define BOOL_CONFIG(name, private_key, public_key, default, unused_doc) \ - GCToEEInterface::GetBooleanConfigValue(private_key, public_key, &s_##name); +#define BOOL_CONFIG(name, private_key, public_key, unused_default, unused_doc) \ + s_##name##Provided = GCToEEInterface::GetBooleanConfigValue(private_key, public_key, &s_##name); -#define INT_CONFIG(name, private_key, public_key, default, unused_doc) \ +#define INT_CONFIG(name, private_key, public_key, unused_default, unused_doc) \ GCToEEInterface::GetIntConfigValue(private_key, public_key, &s_##name); #define STRING_CONFIG(unused_name, unused_private_key, unused_public_key, unused_doc) diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index f1111a99eae5f6..78cf1b41234a06 100644 --- a/src/coreclr/gc/gcconfig.h +++ b/src/coreclr/gc/gcconfig.h @@ -142,8 +142,10 @@ class GCConfig { #define BOOL_CONFIG(name, unused_private_key, unused_public_key, unused_default, unused_doc) \ public: static bool Get##name(); \ + public: static bool Get##name(bool defaultValue); \ public: static void Set##name(bool value); \ private: static bool s_##name; \ + private: static bool s_##name##Provided; \ private: static bool s_Updated##name; #define INT_CONFIG(name, unused_private_key, unused_public_key, unused_default, unused_doc) \ diff --git a/src/coreclr/gc/gcload.cpp b/src/coreclr/gc/gcload.cpp index 5a886d36ad3d9e..d12c09d603b809 100644 --- a/src/coreclr/gc/gcload.cpp +++ b/src/coreclr/gc/gcload.cpp @@ -74,11 +74,11 @@ GC_Initialize( assert(clrToGC == nullptr); #endif +#ifndef FEATURE_NATIVEAOT // GCConfig and GCToOSInterface are initialized in PalInit // Initialize GCConfig before anything else - initialization of our // various components may want to query the current configuration. GCConfig::Initialize(); -#ifndef FEATURE_NATIVEAOT // GCToOSInterface is initialized directly if (!GCToOSInterface::Initialize()) { return E_FAIL; @@ -92,7 +92,7 @@ GC_Initialize( } #ifdef FEATURE_SVR_GC - if (GCConfig::GetServerGC()) + if (GCConfig::GetServerGC() && GCToEEInterface::GetCurrentProcessCpuCount() > 1) { #ifdef WRITE_BARRIER_CHECK g_GCShadow = 0; diff --git a/src/coreclr/gc/unix/events.cpp b/src/coreclr/gc/unix/events.cpp index 7cc55680aaf2f5..767b414f04c174 100644 --- a/src/coreclr/gc/unix/events.cpp +++ b/src/coreclr/gc/unix/events.cpp @@ -131,6 +131,7 @@ class GCEvent::Impl if (milliseconds != INFINITE) { uint64_t nanoseconds = (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds; + NanosecondsToTimeSpec(nanoseconds, &endTime); endMachTime = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + nanoseconds; } #elif HAVE_PTHREAD_CONDATTR_SETCLOCK diff --git a/src/coreclr/gc/windows/gcenv.windows.cpp b/src/coreclr/gc/windows/gcenv.windows.cpp index d8675e5e1060bb..57203a3ad19050 100644 --- a/src/coreclr/gc/windows/gcenv.windows.cpp +++ b/src/coreclr/gc/windows/gcenv.windows.cpp @@ -61,7 +61,6 @@ struct CPU_Group_Info }; static bool g_fEnableGCCPUGroups; -static bool g_fHadSingleProcessorAtStartup; static DWORD g_nGroups; static DWORD g_nProcessors; static CPU_Group_Info *g_CPUGroupInfoArray; @@ -220,26 +219,26 @@ void InitCPUGroupInfo() g_fEnableGCCPUGroups = false; #if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) - if (!GCConfig::GetGCCpuGroup()) + USHORT groupCount = 0; + + // On Windows 11+ and Windows Server 2022+, a process is no longer restricted to a single processor group by default. + // If more than one processor group is available to the process (a non-affinitized process on Windows 11+), + // default to using multiple processor groups; otherwise, default to using a single processor group. This default + // behavior may be overridden by the configuration value below. + if (GetProcessGroupAffinity(GetCurrentProcess(), &groupCount, NULL) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + groupCount = 1; + + bool enableGCCPUGroups = GCConfig::GetGCCpuGroup(/* defaultValue */ groupCount > 1); + + if (!enableGCCPUGroups) return; if (!InitCPUGroupInfoArray()) return; - // only enable CPU groups if more than one group exists + // Enable processor groups only if more than one group exists g_fEnableGCCPUGroups = g_nGroups > 1; #endif // TARGET_AMD64 || TARGET_ARM64 - - // Determine if the process is affinitized to a single processor (or if the system has a single processor) - DWORD_PTR processAffinityMask, systemAffinityMask; - if (::GetProcessAffinityMask(::GetCurrentProcess(), &processAffinityMask, &systemAffinityMask)) - { - if (processAffinityMask != 0 && // only one CPU group is involved - (processAffinityMask & (processAffinityMask - 1)) == 0) // only one bit is set - { - g_fHadSingleProcessorAtStartup = true; - } - } } void GetProcessMemoryLoad(LPMEMORYSTATUSEX pMSEX) @@ -475,17 +474,12 @@ size_t GetLogicalProcessorCacheSizeFromOS() return cache_size; } -bool CanEnableGCCPUGroups() -{ - return g_fEnableGCCPUGroups; -} - // Get the CPU group for the specified processor void GetGroupForProcessor(uint16_t processor_number, uint16_t* group_number, uint16_t* group_processor_number) { assert(g_fEnableGCCPUGroups); -#if !defined(FEATURE_NATIVEAOT) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) WORD bTemp = 0; WORD bDiff = processor_number - bTemp; @@ -1190,7 +1184,7 @@ bool GCToOSInterface::GetProcessorForHeap(uint16_t heap_number, uint16_t* proc_n // Locate heap_number-th available processor uint16_t procIndex = 0; size_t cnt = heap_number; - for (uint16_t i = 0; i < GCToOSInterface::GetTotalProcessorCount(); i++) + for (uint16_t i = 0; i < MAX_SUPPORTED_CPUS; i++) { if (g_processAffinitySet.Contains(i)) { diff --git a/src/coreclr/inc/clrdata.idl b/src/coreclr/inc/clrdata.idl index 2663a7b057fd7d..81b0e0bc7fc653 100644 --- a/src/coreclr/inc/clrdata.idl +++ b/src/coreclr/inc/clrdata.idl @@ -308,10 +308,12 @@ interface ICLRDataLoggingCallback : IUnknown typedef enum CLRDataEnumMemoryFlags { CLRDATA_ENUM_MEM_DEFAULT = 0x0, - CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump - CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump - CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump - + CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, // generating skinny mini-dump + CLRDATA_ENUM_MEM_HEAP = 0x1, // generating heap dump + CLRDATA_ENUM_MEM_TRIAGE = 0x2, // generating triage mini-dump + /* Generate heap dumps faster with less memory usage than CLRDATA_ENUM_MEM_HEAP by adding + the loader heaps instead of traversing all the individual runtime data structures. */ + CLRDATA_ENUM_MEM_HEAP2 = 0x3, /* More bits to be added here later */ } CLRDataEnumMemoryFlags; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 91ae75acc4f006..374fb7b8ab960f 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2893,9 +2893,15 @@ class ICorStaticInfo CORINFO_METHOD_HANDLE hMethod ) = 0; - // this function is for debugging only. It returns the method name - // and if 'moduleName' is non-null, it sets it to something that will - // says which method (a class name, or a module name) + // This function returns the method name and if 'moduleName' is non-null, + // it sets it to something that contains the method (a class + // name, or a module name). Note that the moduleName parameter is for + // diagnostics only. + // + // The method name returned is the same as getMethodNameFromMetadata except + // in the case of functions without metadata (e.g. IL stubs), where this + // function still returns a reasonable name while getMethodNameFromMetadata + // returns null. virtual const char* getMethodName ( CORINFO_METHOD_HANDLE ftn, /* IN */ const char **moduleName /* OUT */ diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index c2053d748b0c73..a8056a5451561a 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -2466,7 +2466,7 @@ typedef DPTR(PTR_PCODE) PTR_PTR_PCODE; // Helper macro for tracking EnumMemoryRegions progress. #if 0 -#define EMEM_OUT(args) DacWarning args +#define EMEM_OUT(args) DacLogMessage args #else #define EMEM_OUT(args) #endif diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index c003b6be776b20..2447d63a10be31 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* 1b9551b8-21f4-4233-9c90-f3eabd6a322b */ - 0x1b9551b8, - 0x21f4, - 0x4233, - {0x9c, 0x90, 0xf3, 0xea, 0xbd, 0x6a, 0x32, 0x2b} +constexpr GUID JITEEVersionIdentifier = { /* 6be47e5d-a92b-4d16-9280-f63df646ada4 */ + 0x6be47e5d, + 0xa92b, + 0x4d16, + {0x92, 0x80, 0xf6, 0x3d, 0xf6, 0x46, 0xad, 0xa4} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 0934f2ea627481..20a1462125534c 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -15,10 +15,10 @@ #define READYTORUN_SIGNATURE 0x00525452 // 'RTR' // Keep these in sync with src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs -#define READYTORUN_MAJOR_VERSION 0x0007 -#define READYTORUN_MINOR_VERSION 0x0001 +#define READYTORUN_MAJOR_VERSION 0x0008 +#define READYTORUN_MINOR_VERSION 0x0000 -#define MINIMUM_READYTORUN_MAJOR_VERSION 0x006 +#define MINIMUM_READYTORUN_MAJOR_VERSION 0x008 // R2R Version 2.1 adds the InliningInfo section // R2R Version 2.2 adds the ProfileDataInfo section @@ -26,6 +26,7 @@ // R2R 3.0 is not backward compatible with 2.x. // R2R Version 6.0 changes managed layout for sequential types with any unmanaged non-blittable fields. // R2R 6.0 is not backward compatible with 5.x or earlier. +// R2R Version 8.0 Changes the alignment of the Int128 type struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 6850014a1d710a..40d6e7cbbadbdf 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2412,8 +2412,14 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre // Get a temp integer register to compute long address. regNumber addrReg = tree->GetSingleTempReg(); - simd16_t constValue = vecCon->gtSimd16Val; - CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); + simd16_t constValue = {}; + + if (vecCon->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &vecCon->gtSimd12Val, sizeof(simd12_t)); + else + constValue = vecCon->gtSimd16Val; + + CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); emit->emitIns_R_C(INS_ldr, attr, targetReg, addrReg, hnd, 0); } diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 536797eaba9593..4a24f88d398026 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4404,12 +4404,26 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) else { #ifdef TARGET_ARM64 - // Handle LEA with "contained" BFIZ - if (index->isContained() && index->OperIs(GT_BFIZ)) + + if (index->isContained()) { - assert(scale == 0); - scale = (DWORD)index->gtGetOp2()->AsIntConCommon()->IconValue(); - index = index->gtGetOp1()->gtGetOp1(); + if (index->OperIs(GT_BFIZ)) + { + // Handle LEA with "contained" BFIZ + assert(scale == 0); + scale = (DWORD)index->gtGetOp2()->AsIntConCommon()->IconValue(); + index = index->gtGetOp1()->gtGetOp1(); + } + else if (index->OperIs(GT_CAST)) + { + index = index->AsCast()->gtGetOp1(); + } + else + { + // Only BFIZ/CAST nodes should be present for for contained index on ARM64. + // If there are more, we need to handle them here. + unreached(); + } } #endif diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index f4c0a37ce224bb..45cbd8ac6c0737 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1887,31 +1887,9 @@ void CodeGen::genGenerateMachineCode() if (compiler->fgHaveProfileData()) { - const char* pgoKind; - switch (compiler->fgPgoSource) - { - case ICorJitInfo::PgoSource::Static: - pgoKind = "Static"; - break; - case ICorJitInfo::PgoSource::Dynamic: - pgoKind = "Dynamic"; - break; - case ICorJitInfo::PgoSource::Blend: - pgoKind = "Blend"; - break; - case ICorJitInfo::PgoSource::Text: - pgoKind = "Textual"; - break; - case ICorJitInfo::PgoSource::Sampling: - pgoKind = "Sample-based"; - break; - default: - pgoKind = "Unknown"; - break; - } - - printf("; with %s PGO: edge weights are %s, and fgCalledCount is " FMT_WT "\n", pgoKind, - compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", compiler->fgCalledCount); + printf("; with %s: edge weights are %s, and fgCalledCount is " FMT_WT "\n", + compiler->compGetPgoSourceName(), compiler->fgHaveValidEdgeWeights ? "valid" : "invalid", + compiler->fgCalledCount); } if (compiler->fgPgoFailReason != nullptr) diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 9c95e410eb3d97..7a8c6b362c2943 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -561,8 +561,14 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre case TYP_SIMD12: case TYP_SIMD16: { - simd16_t constValue = vecCon->gtSimd16Val; - CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); + simd16_t constValue = {}; + + if (vecCon->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &vecCon->gtSimd12Val, sizeof(simd12_t)); + else + constValue = vecCon->gtSimd16Val; + + CORINFO_FIELD_HANDLE hnd = emit->emitSimd16Const(constValue); emit->emitIns_R_C(ins_Load(targetType), attr, targetReg, hnd, 0); break; @@ -7886,9 +7892,9 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) for (GenTreeFieldList::Use& use : fieldList->Uses()) { - GenTree* const fieldNode = use.GetNode(); - const unsigned fieldOffset = use.GetOffset(); - var_types fieldType = use.GetType(); + GenTree* const fieldNode = use.GetNode(); + const unsigned fieldOffset = use.GetOffset(); + const var_types fieldType = use.GetType(); // Long-typed nodes should have been handled by the decomposition pass, and lowering should have sorted the // field list in descending order by offset. @@ -7911,8 +7917,7 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) int adjustment = roundUp(currentOffset - fieldOffset, 4); if (fieldIsSlot && !varTypeIsSIMD(fieldType)) { - fieldType = genActualType(fieldType); - unsigned pushSize = genTypeSize(fieldType); + unsigned pushSize = genTypeSize(genActualType(fieldType)); assert((pushSize % 4) == 0); adjustment -= pushSize; while (adjustment != 0) @@ -7960,13 +7965,22 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } } - bool canStoreWithPush = fieldIsSlot; - bool canLoadWithPush = varTypeIsI(fieldNode) || genIsValidIntReg(argReg); + bool canStoreFullSlot = fieldIsSlot; + bool canLoadFullSlot = genIsValidIntReg(argReg); + if (argReg == REG_NA) + { + assert((genTypeSize(fieldNode) <= TARGET_POINTER_SIZE)); + assert(genTypeSize(genActualType(fieldNode)) == genTypeSize(genActualType(fieldType))); + + // We can widen local loads if the excess only affects padding bits. + canLoadFullSlot = (genTypeSize(fieldNode) == TARGET_POINTER_SIZE) || fieldNode->isUsedFromSpillTemp() || + (fieldNode->OperIsLocalRead() && (genTypeSize(fieldNode) >= genTypeSize(fieldType))); + } - if (canStoreWithPush && canLoadWithPush) + if (canStoreFullSlot && canLoadFullSlot) { assert(m_pushStkArg); - assert(genTypeSize(fieldNode) == TARGET_POINTER_SIZE); + assert(genTypeSize(fieldNode) <= TARGET_POINTER_SIZE); inst_TT(INS_push, emitActualTypeSize(fieldNode), fieldNode); currentOffset -= TARGET_POINTER_SIZE; @@ -7989,9 +8003,10 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } else { - // TODO-XArch-CQ: using "ins_Load" here is conservative, as it will always - // extend, which we can avoid if the field type is smaller than the node type. - inst_RV_TT(ins_Load(fieldNode->TypeGet()), emitTypeSize(fieldNode), intTmpReg, fieldNode); + // Use the smaller "mov" instruction in case we do not need a sign/zero-extending load. + instruction loadIns = canLoadFullSlot ? INS_mov : ins_Load(fieldNode->TypeGet()); + emitAttr loadSize = canLoadFullSlot ? EA_PTRSIZE : emitTypeSize(fieldNode); + inst_RV_TT(loadIns, loadSize, intTmpReg, fieldNode); } argReg = intTmpReg; @@ -8006,13 +8021,16 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) else #endif // defined(FEATURE_SIMD) { - genStoreRegToStackArg(fieldType, argReg, fieldOffset - currentOffset); + // Using wide stores here avoids having to reserve a byteable register when we could not + // use "push" due to the field node being an indirection (i. e. for "!canLoadFullSlot"). + var_types storeType = canStoreFullSlot ? genActualType(fieldType) : fieldType; + genStoreRegToStackArg(storeType, argReg, fieldOffset - currentOffset); } if (m_pushStkArg) { - // We always push a slot-rounded size - currentOffset -= genTypeSize(fieldType); + // We always push a slot-rounded size. + currentOffset -= roundUp(genTypeSize(fieldType), TARGET_POINTER_SIZE); } } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index e73052a780f60f..8ece59eefb1f8e 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -34,9 +34,7 @@ extern ICorJitHost* g_jitHost; #define COLUMN_FLAGS (COLUMN_KINDS + 32) #endif -#if defined(DEBUG) unsigned Compiler::jitTotalMethodCompiled = 0; -#endif // defined(DEBUG) #if defined(DEBUG) LONG Compiler::jitNestingLevel = 0; @@ -1771,6 +1769,7 @@ void Compiler::compInit(ArenaAllocator* pAlloc, info.compCompHnd = compHnd; info.compMethodHnd = methodHnd; info.compMethodInfo = methodInfo; + info.compClassHnd = compHnd->getMethodClass(methodHnd); #ifdef DEBUG bRangeAllowStress = false; @@ -1790,17 +1789,10 @@ void Compiler::compInit(ArenaAllocator* pAlloc, info.compClassName = nullptr; info.compFullName = nullptr; - const char* classNamePtr; - const char* methodName; - - methodName = eeGetMethodName(methodHnd, &classNamePtr); - unsigned len = (unsigned)roundUp(strlen(classNamePtr) + 1); - info.compClassName = getAllocator(CMK_DebugOnly).allocate(len); - info.compMethodName = methodName; - strcpy_s((char*)info.compClassName, len, classNamePtr); - - info.compFullName = eeGetMethodFullName(methodHnd); - info.compPerfScore = 0.0; + info.compMethodName = eeGetMethodName(methodHnd, nullptr); + info.compClassName = eeGetClassName(info.compClassHnd); + info.compFullName = eeGetMethodFullName(methodHnd); + info.compPerfScore = 0.0; info.compMethodSuperPMIIndex = g_jitHost->getIntConfigValue(W("SuperPMIMethodContextNumber"), -1); #endif // defined(DEBUG) || defined(LATE_DISASM) || DUMP_FLOWGRAPHS @@ -2539,7 +2531,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_ALT_JIT)) { - if (pfAltJit->contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (pfAltJit->contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.altJit = true; } @@ -2620,7 +2612,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // if (compIsForImportOnly() && (!altJitConfig || opts.altJit)) { - if (JitConfig.JitImportBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitImportBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { assert(!"JitImportBreak reached"); } @@ -2635,7 +2627,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // if (!compIsForInlining()) { - if (JitConfig.JitDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { verboseDump = true; } @@ -2870,32 +2862,32 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.dspOrder = true; } - if (JitConfig.JitGCDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitGCDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspGCtbls = true; } - if (JitConfig.JitDisasm().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.disAsm = true; } - if (JitConfig.JitDisasm().contains("SPILLED", nullptr, nullptr)) + if (JitConfig.JitDisasmSpilled()) { opts.disAsmSpilled = true; } - if (JitConfig.JitUnwindDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitUnwindDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspUnwind = true; } - if (JitConfig.JitEHDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitEHDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspEHTable = true; } - if (JitConfig.JitDebugDump().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitDebugDump().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.dspDebugInfo = true; } @@ -2933,7 +2925,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) opts.compLongAddress = true; } - if (JitConfig.JitOptRepeat().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitOptRepeat().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.optRepeat = true; } @@ -2943,7 +2935,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // JitEarlyExpandMDArraysFilter. if (JitConfig.JitEarlyExpandMDArrays() == 0) { - if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitEarlyExpandMDArraysFilter().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compJitEarlyExpandMDArrays = true; @@ -2984,7 +2976,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) printf(""); // in our logic this causes a flush } - if (JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { assert(!"JitBreak reached"); } @@ -2996,8 +2988,8 @@ void Compiler::compInitOptions(JitFlags* jitFlags) } if (verbose || - JitConfig.JitDebugBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args) || - JitConfig.JitBreak().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + JitConfig.JitDebugBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args) || + JitConfig.JitBreak().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { compDebugBreak = true; } @@ -3016,14 +3008,9 @@ void Compiler::compInitOptions(JitFlags* jitFlags) s_pJitFunctionFileInitialized = true; } #else // DEBUG - if (!JitConfig.JitDisasm().isEmpty()) + if (JitConfig.JitDisasm().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { - const char* methodName = info.compCompHnd->getMethodName(info.compMethodHnd, nullptr); - const char* className = info.compCompHnd->getClassName(info.compClassHnd); - if (JitConfig.JitDisasm().contains(methodName, className, &info.compMethodInfo->args)) - { - opts.disAsm = true; - } + opts.disAsm = true; } #endif // !DEBUG @@ -3191,21 +3178,21 @@ void Compiler::compInitOptions(JitFlags* jitFlags) // JitForceProcedureSplitting is used to force procedure splitting on checked assemblies. // This is useful for debugging on a checked build. Note that we still only do procedure // splitting in the zapper. - if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitForceProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplitting = true; } // JitNoProcedureSplitting will always disable procedure splitting. - if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitNoProcedureSplitting().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplitting = false; } // // JitNoProcedureSplittingEH will disable procedure splitting in functions with EH. - if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodName, info.compClassName, + if (JitConfig.JitNoProcedureSplittingEH().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { opts.compProcedureSplittingEH = false; @@ -3213,6 +3200,21 @@ void Compiler::compInitOptions(JitFlags* jitFlags) #endif } +#ifdef TARGET_64BIT + opts.compCollect64BitCounts = JitConfig.JitCollect64BitCounts() != 0; + +#ifdef DEBUG + if (JitConfig.JitRandomlyCollect64BitCounts() != 0) + { + CLRRandom rng; + rng.Init(info.compMethodHash() ^ JitConfig.JitRandomlyCollect64BitCounts() ^ 0x3485e20e); + opts.compCollect64BitCounts = rng.Next(2) == 0; + } +#endif +#else + opts.compCollect64BitCounts = false; +#endif + #ifdef DEBUG // Now, set compMaxUncheckedOffsetForNullObject for STRESS_NULL_OBJECT_CHECK @@ -3306,7 +3308,7 @@ bool Compiler::compJitHaltMethod() /* This method returns true when we use an INS_BREAKPOINT to allow us to step into the generated native code */ /* Note that this these two "Jit" environment variables also work for ngen images */ - if (JitConfig.JitHalt().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitHalt().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } @@ -3410,7 +3412,7 @@ bool Compiler::compStressCompileHelper(compStressArea stressArea, unsigned weigh } if (!JitConfig.JitStressOnly().isEmpty() && - !JitConfig.JitStressOnly().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + !JitConfig.JitStressOnly().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return false; } @@ -3693,7 +3695,7 @@ void Compiler::compSetOptimizationLevel() if (!theMinOptsValue) { - if (JitConfig.JitMinOptsName().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitMinOptsName().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { theMinOptsValue = true; } @@ -4082,8 +4084,9 @@ bool Compiler::compRsvdRegCheck(FrameLayoutState curState) // const char* Compiler::compGetTieringName(bool wantShortName) const { - const bool tier0 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0); - const bool tier1 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1); + const bool tier0 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0); + const bool tier1 = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER1); + const bool instrumenting = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR); if (!opts.compMinOptsIsSet) { @@ -4097,13 +4100,13 @@ const char* Compiler::compGetTieringName(bool wantShortName) const if (tier0) { - return "Tier0"; + return instrumenting ? "Instrumented Tier0" : "Tier0"; } else if (tier1) { if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_OSR)) { - return "Tier1-OSR"; + return instrumenting ? "Instrumented Tier1-OSR" : "Tier1-OSR"; } else { @@ -4149,6 +4152,31 @@ const char* Compiler::compGetTieringName(bool wantShortName) const } } +//------------------------------------------------------------------------ +// compGetPgoSourceName: get a string describing PGO source +// +// Returns: +// String describing describing PGO source (e.g. Dynamic, Static, etc) +// +const char* Compiler::compGetPgoSourceName() const +{ + switch (fgPgoSource) + { + case ICorJitInfo::PgoSource::Static: + return "Static PGO"; + case ICorJitInfo::PgoSource::Dynamic: + return "Dynamic PGO"; + case ICorJitInfo::PgoSource::Blend: + return "Blend PGO"; + case ICorJitInfo::PgoSource::Text: + return "Textual PGO"; + case ICorJitInfo::PgoSource::Sampling: + return "Sample-based PGO"; + default: + return ""; + } +} + //------------------------------------------------------------------------ // compGetStressMessage: get a string describing jitstress capability // for this method @@ -4171,8 +4199,7 @@ const char* Compiler::compGetStressMessage() const { // Or is it excluded via name? if (!JitConfig.JitStressOnly().isEmpty() || - !JitConfig.JitStressOnly().contains(info.compMethodName, info.compClassName, - &info.compMethodInfo->args)) + !JitConfig.JitStressOnly().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { // Not excluded -- stress can happen stressMessage = " JitStress"; @@ -5106,9 +5133,32 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl compJitTelemetry.NotifyEndOfCompilation(); #endif -#if defined(DEBUG) - ++Compiler::jitTotalMethodCompiled; -#endif // defined(DEBUG) + unsigned methodsCompiled = (unsigned)InterlockedIncrement((LONG*)&Compiler::jitTotalMethodCompiled); + + if (JitConfig.JitDisasmSummary() && !compIsForInlining()) + { + char osrBuffer[20] = {0}; + if (opts.IsOSR()) + { + // Tiering name already includes "OSR", we just want the IL offset + sprintf_s(osrBuffer, 20, " @0x%x", info.compILEntry); + } + +#ifdef DEBUG + const char* fullName = info.compFullName; +#else + const char* fullName = + eeGetMethodFullName(info.compMethodHnd, /* includeReturnType */ false, /* includeThisSpecifier */ false); +#endif + + char debugPart[128] = {0}; + INDEBUG(sprintf_s(debugPart, 128, ", hash=0x%08x%s", info.compMethodHash(), compGetStressMessage())); + + const bool hasProf = fgHaveProfileData(); + printf("%4d: JIT compiled %s [%s%s%s%s, IL size=%u, code size=%u%s]\n", methodsCompiled, fullName, + compGetTieringName(), osrBuffer, hasProf ? " with " : "", hasProf ? compGetPgoSourceName() : "", + info.compILCodeSize, *methodCodeSize, debugPart); + } compFunctionTraceEnd(*methodCodePtr, *methodCodeSize, false); JITDUMP("Method code size: %d\n", (unsigned)(*methodCodeSize)); @@ -5461,13 +5511,13 @@ bool Compiler::skipMethod() return true; } - if (JitConfig.JitExclude().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + if (JitConfig.JitExclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } if (!JitConfig.JitInclude().isEmpty() && - !JitConfig.JitInclude().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args)) + !JitConfig.JitInclude().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args)) { return true; } @@ -5773,9 +5823,7 @@ int Compiler::compCompile(CORINFO_MODULE_HANDLE classPtr, { impTokenLookupContextHandle = impInlineInfo->tokenLookupContextHandle; - assert(impInlineInfo->inlineCandidateInfo->clsHandle == info.compCompHnd->getMethodClass(info.compMethodHnd)); - info.compClassHnd = impInlineInfo->inlineCandidateInfo->clsHandle; - + assert(impInlineInfo->inlineCandidateInfo->clsHandle == info.compClassHnd); assert(impInlineInfo->inlineCandidateInfo->clsAttr == info.compCompHnd->getClassAttribs(info.compClassHnd)); // printf("%x != %x\n", impInlineInfo->inlineCandidateInfo->clsAttr, // info.compCompHnd->getClassAttribs(info.compClassHnd)); @@ -5785,7 +5833,6 @@ int Compiler::compCompile(CORINFO_MODULE_HANDLE classPtr, { impTokenLookupContextHandle = METHOD_BEING_COMPILED_CONTEXT(); - info.compClassHnd = info.compCompHnd->getMethodClass(info.compMethodHnd); info.compClassAttr = info.compCompHnd->getClassAttribs(info.compClassHnd); } @@ -6710,24 +6757,6 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, } #ifdef DEBUG - if ((JitConfig.DumpJittedMethods() == 1) && !compIsForInlining()) - { - enum - { - BUFSIZE = 20 - }; - char osrBuffer[BUFSIZE] = {0}; - if (opts.IsOSR()) - { - // Tiering name already includes "OSR", we just want the IL offset - // - sprintf_s(osrBuffer, BUFSIZE, " @0x%x", info.compILEntry); - } - - printf("Compiling %4d %s::%s, IL size = %u, hash=0x%08x %s%s%s\n", Compiler::jitTotalMethodCompiled, - info.compClassName, info.compMethodName, info.compILCodeSize, info.compMethodHash(), - compGetTieringName(), osrBuffer, compGetStressMessage()); - } if (compIsForInlining()) { compGenTreeID = impInlineInfo->InlinerCompiler->compGenTreeID; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3291aca767c382..b8b2dfb0581786 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4145,9 +4145,7 @@ class Compiler regNumber getCallArgIntRegister(regNumber floatReg); regNumber getCallArgFloatRegister(regNumber intReg); -#if defined(DEBUG) static unsigned jitTotalMethodCompiled; -#endif #ifdef DEBUG static LONG jitNestingLevel; @@ -7519,10 +7517,31 @@ class Compiler var_types eeGetFieldType(CORINFO_FIELD_HANDLE fldHnd, CORINFO_CLASS_HANDLE* pStructHnd = nullptr); -#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) || defined(TRACK_LSRA_STATS) + void eePrintJitType(class StringPrinter* printer, var_types jitType); + void eePrintType(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespaces, + bool includeInstantiation); + void eePrintTypeOrJitAlias(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespaces, + bool includeInstantiation); + void eePrintMethod(class StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE methodHnd, + CORINFO_SIG_INFO* sig, + bool includeNamespaces, + bool includeClassInstantiation, + bool includeMethodInstantiation, + bool includeSignature, + bool includeReturnType, + bool includeThisSpecifier); +#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) || defined(TRACK_LSRA_STATS) const char* eeGetMethodName(CORINFO_METHOD_HANDLE hnd, const char** className); - const char* eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd); + const char* eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd, + bool includeReturnType = true, + bool includeThisSpecifier = true); unsigned compMethodHash(CORINFO_METHOD_HANDLE methodHandle); bool eeIsNativeMethod(CORINFO_METHOD_HANDLE method); @@ -7748,6 +7767,12 @@ class Compiler return eeRunWithSPMIErrorTrapImp(reinterpret_cast(function), reinterpret_cast(param)); } + template + bool eeRunFunctorWithSPMIErrorTrap(Functor f) + { + return eeRunWithSPMIErrorTrap([](Functor* pf) { (*pf)(); }, &f); + } + bool eeRunWithSPMIErrorTrapImp(void (*function)(void*), void* param); // Utility functions @@ -9293,6 +9318,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // Use early multi-dimensional array operator expansion (expand after loop optimizations; before lowering). bool compJitEarlyExpandMDArrays; + // Collect 64 bit counts for PGO data. + bool compCollect64BitCounts; + } opts; static bool s_pAltJitExcludeAssembliesListInitialized; @@ -9472,6 +9500,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX } const char* compGetTieringName(bool wantShortName = false) const; + const char* compGetPgoSourceName() const; const char* compGetStressMessage() const; codeOptimize compCodeOpt() const @@ -11265,6 +11294,46 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +class StringPrinter +{ + CompAllocator m_alloc; + char* m_buffer; + size_t m_bufferMax; + size_t m_bufferIndex = 0; + +public: + StringPrinter(CompAllocator alloc, char* buffer = nullptr, size_t bufferMax = 0) + : m_alloc(alloc), m_buffer(buffer), m_bufferMax(bufferMax) + { + if ((m_buffer == nullptr) || (m_bufferMax == 0)) + { + m_bufferMax = 128; + m_buffer = alloc.allocate(m_bufferMax); + } + + m_buffer[0] = '\0'; + } + + size_t GetLength() + { + return m_bufferIndex; + } + + char* GetBuffer() + { + assert(m_buffer[GetLength()] == '\0'); + return m_buffer; + } + void Truncate(size_t newLength) + { + assert(newLength <= m_bufferIndex); + m_bufferIndex = newLength; + m_buffer[m_bufferIndex] = '\0'; + } + + void Printf(const char* format, ...); +}; + /***************************************************************************** * * Variables to keep track of total code amounts. diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 7b5b2961510907..11cfc55305e97c 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -1506,25 +1506,24 @@ const char* Compiler::eeGetFieldName(CORINFO_FIELD_HANDLE field, const char** cl return param.fieldOrMethodOrClassNamePtr; } +//------------------------------------------------------------------------ +// eeGetClassName: +// Get the name (including namespace and instantiation) of a type. +// If missing information (in SPMI), then return a placeholder string. +// +// Return value: +// The name string. +// const char* Compiler::eeGetClassName(CORINFO_CLASS_HANDLE clsHnd) { - FilterSuperPMIExceptionsParam_ee_il param; - - param.pThis = this; - param.pJitInfo = &info; - param.clazz = clsHnd; - - bool success = eeRunWithSPMIErrorTrap( - [](FilterSuperPMIExceptionsParam_ee_il* pParam) { - pParam->fieldOrMethodOrClassNamePtr = pParam->pJitInfo->compCompHnd->getClassName(pParam->clazz); - }, - ¶m); - - if (!success) + StringPrinter printer(getAllocator(CMK_DebugOnly)); + if (!eeRunFunctorWithSPMIErrorTrap([&]() { eePrintType(&printer, clsHnd, true, true); })) { - param.fieldOrMethodOrClassNamePtr = "hackishClassName"; + printer.Truncate(0); + printer.Printf("hackishClassName"); } - return param.fieldOrMethodOrClassNamePtr; + + return printer.GetBuffer(); } #endif // DEBUG || FEATURE_JIT_METHOD_PERF diff --git a/src/coreclr/jit/eeinterface.cpp b/src/coreclr/jit/eeinterface.cpp index 60c685e142b35a..df3a65af43be6d 100644 --- a/src/coreclr/jit/eeinterface.cpp +++ b/src/coreclr/jit/eeinterface.cpp @@ -19,228 +19,327 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #pragma hdrstop #endif -#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) +//------------------------------------------------------------------------ +// StringPrinter::Printf: +// Print a formatted string. +// +// Arguments: +// format - the format +// +void StringPrinter::Printf(const char* format, ...) +{ + va_list args; + va_start(args, format); -/*****************************************************************************/ + while (true) + { + size_t bufferLeft = m_bufferMax - m_bufferIndex; + assert(bufferLeft >= 1); // always fit null terminator + + va_list argsCopy; + va_copy(argsCopy, args); + int printed = _vsnprintf_s(m_buffer + m_bufferIndex, bufferLeft, _TRUNCATE, format, argsCopy); + va_end(argsCopy); + + if (printed < 0) + { + // buffer too small + size_t newSize = m_bufferMax * 2; + char* newBuffer = m_alloc.allocate(newSize); + memcpy(newBuffer, m_buffer, m_bufferIndex + 1); // copy null terminator too -/***************************************************************************** - * - * Filter wrapper to handle exception filtering. - * On Unix compilers don't support SEH. - */ + m_buffer = newBuffer; + m_bufferMax = newSize; + } + else + { + m_bufferIndex = m_bufferIndex + static_cast(printed); + break; + } + } + + va_end(args); +} -struct FilterSuperPMIExceptionsParam_eeinterface +#if defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) + +//------------------------------------------------------------------------ +// eePrintJitType: +// Print a JIT type. +// +// Arguments: +// printer - the printer +// jitType - the JIT type +// +void Compiler::eePrintJitType(StringPrinter* printer, var_types jitType) { - Compiler* pThis; - Compiler::Info* pJitInfo; - bool hasThis; - size_t siglength; - CORINFO_SIG_INFO sig; - CORINFO_ARG_LIST_HANDLE argLst; - CORINFO_METHOD_HANDLE hnd; - const char* returnType; - const char** pArgNames; - EXCEPTION_POINTERS exceptionPointers; -}; - -const char* Compiler::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd) + printer->Printf("%s", varTypeName(jitType)); +} + +//------------------------------------------------------------------------ +// eePrintType: +// Print a type given by a class handle. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the class +// includeNamespace - Whether to print namespaces before type names +// includeInstantiation - Whether to print the instantiation of the class +// +void Compiler::eePrintType(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespace, + bool includeInstantiation) { - const char* className; - const char* methodName = eeGetMethodName(hnd, &className); - if ((eeGetHelperNum(hnd) != CORINFO_HELP_UNDEF) || eeIsNativeMethod(hnd)) + const char* namespaceName; + const char* className = info.compCompHnd->getClassNameFromMetadata(clsHnd, &namespaceName); + if (className == nullptr) { - return methodName; + namespaceName = nullptr; + className = ""; + } + + if (includeNamespace && (namespaceName != nullptr) && (namespaceName[0] != '\0')) + { + printer->Printf("%s.", namespaceName); } - FilterSuperPMIExceptionsParam_eeinterface param; - param.returnType = nullptr; - param.pThis = this; - param.hasThis = false; - param.siglength = 0; - param.hnd = hnd; - param.pJitInfo = &info; + printer->Printf("%s", className); + + if (!includeInstantiation) + { + return; + } - size_t length = 0; - unsigned i; + char pref = '['; + for (unsigned typeArgIndex = 0;; typeArgIndex++) + { + CORINFO_CLASS_HANDLE typeArg = info.compCompHnd->getTypeInstantiationArgument(clsHnd, typeArgIndex); - /* Generating the full signature is a two-pass process. First we have to walk - the components in order to assess the total size, then we allocate the buffer - and copy the elements into it. - */ + if (typeArg == NO_CLASS_HANDLE) + { + break; + } - /* Right now there is a race-condition in the EE, className can be nullptr */ + printer->Printf("%c", pref); + pref = ','; + eePrintTypeOrJitAlias(printer, typeArg, includeNamespace, true); + } - /* initialize length with length of className and '.' */ + if (pref != '[') + { + printer->Printf("]"); + } +} - if (className) +//------------------------------------------------------------------------ +// eePrintTypeOrJitAlias: +// Print a type given by a class handle. If the type is a primitive type, +// prints its JIT alias. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the class +// includeNamespace - Whether to print namespaces before type names +// includeInstantiation - Whether to print the instantiation of the class +// +void Compiler::eePrintTypeOrJitAlias(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + bool includeNamespace, + bool includeInstantiation) +{ + CorInfoType typ = info.compCompHnd->asCorInfoType(clsHnd); + if ((typ == CORINFO_TYPE_CLASS) || (typ == CORINFO_TYPE_VALUECLASS)) { - length = strlen(className) + 1; + eePrintType(printer, clsHnd, includeNamespace, includeInstantiation); } else { - assert(strlen(".") == 7); - length = 7; + eePrintJitType(printer, JitType2PreciseVarType(typ)); } +} - /* add length of methodName and opening bracket */ - length += strlen(methodName) + 1; - - bool success = eeRunWithSPMIErrorTrap( - [](FilterSuperPMIExceptionsParam_eeinterface* pParam) { - - /* figure out the signature */ - - pParam->pThis->eeGetMethodSig(pParam->hnd, &pParam->sig); +//------------------------------------------------------------------------ +// eePrintMethod: +// Print a method given by a method handle, its owning class handle and its +// signature. +// +// Arguments: +// printer - the printer +// clsHnd - Handle for the owning class, or NO_CLASS_HANDLE to not print the class. +// sig - The signature of the method. +// includeNamespaces - Whether to print namespaces before type names. +// includeClassInstantiation - Whether to print the class instantiation. Only valid when clsHnd is passed. +// includeMethodInstantiation - Whether to print the method instantiation. Requires the signature to be passed. +// includeSignature - Whether to print the signature. +// includeReturnType - Whether to include the return type at the end. +// includeThisSpecifier - Whether to include a specifier at the end for whether the method is an instance +// method. +// +void Compiler::eePrintMethod(StringPrinter* printer, + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE methHnd, + CORINFO_SIG_INFO* sig, + bool includeNamespaces, + bool includeClassInstantiation, + bool includeMethodInstantiation, + bool includeSignature, + bool includeReturnType, + bool includeThisSpecifier) +{ + if (clsHnd != NO_CLASS_HANDLE) + { + eePrintType(printer, clsHnd, includeNamespaces, includeClassInstantiation); + printer->Printf(":"); + } - // allocate space to hold the class names for each of the parameters + const char* methName = info.compCompHnd->getMethodName(methHnd, nullptr); + printer->Printf("%s", methName); - if (pParam->sig.numArgs > 0) - { - pParam->pArgNames = - pParam->pThis->getAllocator(CMK_DebugOnly).allocate(pParam->sig.numArgs); - } - else + if (includeMethodInstantiation && (sig->sigInst.methInstCount > 0)) + { + printer->Printf("["); + for (unsigned i = 0; i < sig->sigInst.methInstCount; i++) + { + if (i > 0) { - pParam->pArgNames = nullptr; + printer->Printf(","); } - unsigned i; - pParam->argLst = pParam->sig.args; + eePrintTypeOrJitAlias(printer, sig->sigInst.methInst[i], includeNamespaces, true); + } + printer->Printf("]"); + } + + if (includeSignature) + { + printer->Printf("("); - for (i = 0; i < pParam->sig.numArgs; i++) + CORINFO_ARG_LIST_HANDLE argLst = sig->args; + for (unsigned i = 0; i < sig->numArgs; i++) + { + if (i > 0) + printer->Printf(","); + + CORINFO_CLASS_HANDLE vcClsHnd; + var_types type = JitType2PreciseVarType(strip(info.compCompHnd->getArgType(sig, argLst, &vcClsHnd))); + switch (type) { - var_types type = pParam->pThis->eeGetArgType(pParam->argLst, &pParam->sig); - switch (type) + case TYP_REF: + case TYP_STRUCT: { - case TYP_REF: - case TYP_STRUCT: + CORINFO_CLASS_HANDLE clsHnd = eeGetArgClass(sig, argLst); + // For some SIMD struct types we can get a nullptr back from eeGetArgClass on Linux/X64 + if (clsHnd != NO_CLASS_HANDLE) { - CORINFO_CLASS_HANDLE clsHnd = pParam->pThis->eeGetArgClass(&pParam->sig, pParam->argLst); - // For some SIMD struct types we can get a nullptr back from eeGetArgClass on Linux/X64 - if (clsHnd != NO_CLASS_HANDLE) - { - const char* clsName = pParam->pThis->eeGetClassName(clsHnd); - if (clsName != nullptr) - { - pParam->pArgNames[i] = clsName; - break; - } - } - } - FALLTHROUGH; - default: - pParam->pArgNames[i] = varTypeName(type); + eePrintType(printer, clsHnd, includeNamespaces, true); break; + } } - pParam->siglength += strlen(pParam->pArgNames[i]); - pParam->argLst = pParam->pJitInfo->compCompHnd->getArgNext(pParam->argLst); + + FALLTHROUGH; + default: + eePrintJitType(printer, type); + break; } - /* add ',' if there is more than one argument */ + argLst = info.compCompHnd->getArgNext(argLst); + } - if (pParam->sig.numArgs > 1) - { - pParam->siglength += (pParam->sig.numArgs - 1); - } + printer->Printf(")"); - var_types retType = JITtype2varType(pParam->sig.retType); + if (includeReturnType) + { + var_types retType = JitType2PreciseVarType(sig->retType); if (retType != TYP_VOID) { + printer->Printf(":"); switch (retType) { case TYP_REF: case TYP_STRUCT: { - CORINFO_CLASS_HANDLE clsHnd = pParam->sig.retTypeClass; + CORINFO_CLASS_HANDLE clsHnd = sig->retTypeClass; if (clsHnd != NO_CLASS_HANDLE) { - const char* clsName = pParam->pThis->eeGetClassName(clsHnd); - if (clsName != nullptr) - { - pParam->returnType = clsName; - break; - } + eePrintType(printer, clsHnd, includeNamespaces, true); + break; } } FALLTHROUGH; default: - pParam->returnType = varTypeName(retType); + eePrintJitType(printer, retType); break; } - pParam->siglength += strlen(pParam->returnType) + 1; // don't forget the delimiter ':' - } - - // Does it have a 'this' pointer? Don't count explicit this, which has the this pointer type as the first - // element of the arg type list - if (pParam->sig.hasThis() && !pParam->sig.hasExplicitThis()) - { - assert(strlen(":this") == 5); - pParam->siglength += 5; - pParam->hasThis = true; } - }, - ¶m); + } - if (!success) - { - param.siglength = 0; + // Does it have a 'this' pointer? Don't count explicit this, which has + // the this pointer type as the first element of the arg type list + if (includeThisSpecifier && sig->hasThis() && !sig->hasExplicitThis()) + { + printer->Printf(":this"); + } } +} - /* add closing bracket and null terminator */ - - length += param.siglength + 2; - - char* retName = getAllocator(CMK_DebugOnly).allocate(length); - - /* Now generate the full signature string in the allocated buffer */ - - if (className) - { - strcpy_s(retName, length, className); - strcat_s(retName, length, ":"); - } - else +//------------------------------------------------------------------------ +// eeGetMethodFullName: +// Get a string describing a method. +// +// Arguments: +// hnd - the method handle +// includeReturnType - Whether to include the return type in the string +// includeThisSpecifier - Whether to include a specifier for whether this is an instance method. +// +// Returns: +// The string. +// +const char* Compiler::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd, bool includeReturnType, bool includeThisSpecifier) +{ + const char* className; + const char* methodName = eeGetMethodName(hnd, &className); + if ((eeGetHelperNum(hnd) != CORINFO_HELP_UNDEF) || eeIsNativeMethod(hnd)) { - strcpy_s(retName, length, "."); + return methodName; } - strcat_s(retName, length, methodName); + StringPrinter p(getAllocator(CMK_DebugOnly)); + CORINFO_CLASS_HANDLE clsHnd = NO_CLASS_HANDLE; + bool success = eeRunFunctorWithSPMIErrorTrap([&]() { + clsHnd = info.compCompHnd->getMethodClass(hnd); + CORINFO_SIG_INFO sig; + eeGetMethodSig(hnd, &sig); + eePrintMethod(&p, clsHnd, hnd, &sig, + /* includeNamespaces */ true, + /* includeClassInstantiation */ true, + /* includeMethodInstantiation */ true, + /* includeSignature */ true, includeReturnType, includeThisSpecifier); - // append the signature - strcat_s(retName, length, "("); + }); - if (param.siglength > 0) + if (!success) { - param.argLst = param.sig.args; - - for (i = 0; i < param.sig.numArgs; i++) + // Try with bare minimum + p.Truncate(0); + + success = eeRunFunctorWithSPMIErrorTrap([&]() { + eePrintMethod(&p, clsHnd, hnd, + /* sig */ nullptr, + /* includeNamespaces */ true, + /* includeClassInstantiation */ false, + /* includeMethodInstantiation */ false, + /* includeSignature */ false, includeReturnType, includeThisSpecifier); + }); + + if (!success) { - var_types type = eeGetArgType(param.argLst, ¶m.sig); - strcat_s(retName, length, param.pArgNames[i]); - param.argLst = info.compCompHnd->getArgNext(param.argLst); - if (i + 1 < param.sig.numArgs) - { - strcat_s(retName, length, ","); - } + p.Truncate(0); + p.Printf("hackishClassName:hackishMethodName(?)"); } } - strcat_s(retName, length, ")"); - - if (param.returnType != nullptr) - { - strcat_s(retName, length, ":"); - strcat_s(retName, length, param.returnType); - } - - if (param.hasThis) - { - strcat_s(retName, length, ":this"); - } - - assert(strlen(retName) == (length - 1)); - - return (retName); + return p.GetBuffer(); } #endif // defined(DEBUG) || defined(FEATURE_JIT_METHOD_PERF) || defined(FEATURE_SIMD) diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index b083f7869d2633..73445b88d223da 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -467,7 +467,7 @@ FILE* Compiler::fgOpenFlowGraphFile(bool* wbDontClose, Phases phase, PhasePositi } #ifdef DEBUG - dumpFunction = JitConfig.JitDumpFg().contains(info.compMethodName, info.compClassName, &info.compMethodInfo->args); + dumpFunction = JitConfig.JitDumpFg().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args); filename = JitConfig.JitDumpFgFile(); pathname = JitConfig.JitDumpFgDir(); diff --git a/src/coreclr/jit/fginline.cpp b/src/coreclr/jit/fginline.cpp index 86a42a2b87a88e..243eecf117675f 100644 --- a/src/coreclr/jit/fginline.cpp +++ b/src/coreclr/jit/fginline.cpp @@ -658,8 +658,8 @@ PhaseStatus Compiler::fgInline() } #ifdef DEBUG - fgPrintInlinedMethods = JitConfig.JitPrintInlinedMethods().contains(info.compMethodName, info.compClassName, - &info.compMethodInfo->args); + fgPrintInlinedMethods = + JitConfig.JitPrintInlinedMethods().contains(info.compMethodHnd, info.compClassHnd, &info.compMethodInfo->args); #endif // DEBUG noway_assert(fgFirstBB != nullptr); diff --git a/src/coreclr/jit/fgprofile.cpp b/src/coreclr/jit/fgprofile.cpp index bf3e77fc78405e..39190c6a48fa65 100644 --- a/src/coreclr/jit/fgprofile.cpp +++ b/src/coreclr/jit/fgprofile.cpp @@ -570,7 +570,7 @@ void BlockCountInstrumentor::BuildSchemaElements(BasicBlock* block, Schema& sche ICorJitInfo::PgoInstrumentationSchema schemaElem; schemaElem.Count = 1; schemaElem.Other = 0; - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = m_comp->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::BasicBlockLongCount : ICorJitInfo::PgoInstrumentationKind::BasicBlockIntCount; schemaElem.ILOffset = offset; @@ -1314,7 +1314,7 @@ void EfficientEdgeCountInstrumentor::BuildSchemaElements(BasicBlock* block, Sche ICorJitInfo::PgoInstrumentationSchema schemaElem; schemaElem.Count = 1; schemaElem.Other = targetKey; - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = m_comp->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::EdgeLongCount : ICorJitInfo::PgoInstrumentationKind::EdgeIntCount; schemaElem.ILOffset = sourceKey; @@ -1503,7 +1503,7 @@ class BuildHandleHistogramProbeSchemaGen schemaElem.Other |= ICorJitInfo::HandleHistogram32::DELEGATE_FLAG; } - schemaElem.InstrumentationKind = JitConfig.JitCollect64BitCounts() + schemaElem.InstrumentationKind = compiler->opts.compCollect64BitCounts ? ICorJitInfo::PgoInstrumentationKind::HandleHistogramLongCount : ICorJitInfo::PgoInstrumentationKind::HandleHistogramIntCount; schemaElem.ILOffset = (int32_t)call->gtHandleHistogramProfileCandidateInfo->ilOffset; diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 6fbafbcc993313..6cd927dedb619e 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -21589,8 +21589,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call, // Optionally, print info on devirtualization Compiler* const rootCompiler = impInlineRoot(); - const bool doPrint = JitConfig.JitPrintDevirtualizedMethods().contains(rootCompiler->info.compMethodName, - rootCompiler->info.compClassName, + const bool doPrint = JitConfig.JitPrintDevirtualizedMethods().contains(rootCompiler->info.compMethodHnd, + rootCompiler->info.compClassHnd, &rootCompiler->info.compMethodInfo->args); #endif // DEBUG diff --git a/src/coreclr/jit/instr.cpp b/src/coreclr/jit/instr.cpp index 6d9494e59b541b..572a556c0858d6 100644 --- a/src/coreclr/jit/instr.cpp +++ b/src/coreclr/jit/instr.cpp @@ -765,7 +765,13 @@ CodeGen::OperandDesc CodeGen::genOperandDesc(GenTree* op) case TYP_SIMD12: case TYP_SIMD16: { - simd16_t constValue = op->AsVecCon()->gtSimd16Val; + simd16_t constValue = {}; + + if (op->TypeIs(TYP_SIMD12)) + memcpy(&constValue, &op->AsVecCon()->gtSimd12Val, sizeof(simd12_t)); + else + constValue = op->AsVecCon()->gtSimd16Val; + return OperandDesc(emit->emitSimd16Const(constValue)); } diff --git a/src/coreclr/jit/jitconfig.cpp b/src/coreclr/jit/jitconfig.cpp index 6fdbad427fe8a4..7e055f0726bb93 100644 --- a/src/coreclr/jit/jitconfig.cpp +++ b/src/coreclr/jit/jitconfig.cpp @@ -36,261 +36,51 @@ void JitConfigValues::MethodSet::initialize(const WCHAR* list, ICorJitHost* host } } - const char SEP_CHAR = ' '; // character used to separate each entry - const char WILD_CHAR = '*'; // character used as the wildcard match everything - char currChar = '?'; // The current character - int nameStart = -1; // Index of the start of the current class or method name - MethodName** lastName = &m_names; // Last entry inserted into the list - bool isQuoted = false; // true while parsing inside a quote "this-is-a-quoted-region" - MethodName currentName; // Buffer used while parsing the current entry - - currentName.m_next = nullptr; - currentName.m_methodNameStart = -1; - currentName.m_methodNameLen = -1; - currentName.m_methodNameWildcardAtEnd = false; - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - currentName.m_classNameWildcardAtEnd = false; - currentName.m_numArgs = -1; - - enum State - { - NO_NAME, - CLS_NAME, - FUNC_NAME, - ARG_LIST - }; // parsing state machine - - State state = NO_NAME; - for (int i = 0; (currChar != '\0'); i++) - { - currChar = m_list[i]; + auto commitPattern = [this, host](const char* start, const char* end) { + if (end <= start) + { + return; + } + + MethodName* name = static_cast(host->allocateMemory(sizeof(MethodName))); + name->m_next = m_names; + name->m_patternStart = start; + name->m_patternEnd = end; + const char* colon = static_cast(memchr(start, ':', end - start)); + const char* startOfMethodName = colon != nullptr ? colon + 1 : start; - switch (state) + const char* parens = static_cast(memchr(startOfMethodName, '(', end - startOfMethodName)); + const char* endOfMethodName = parens != nullptr ? parens : end; + name->m_methodNameContainsInstantiation = + memchr(startOfMethodName, '[', endOfMethodName - startOfMethodName) != nullptr; + + if (colon != nullptr) + { + name->m_containsClassName = true; + name->m_classNameContainsInstantiation = memchr(start, '[', colon - start) != nullptr; + } + else { - case NO_NAME: - // skip over zero or more blanks, then expect CLS_NAME - if (currChar != SEP_CHAR) - { - nameStart = i; - state = CLS_NAME; // we have found the start of the next entry - } - break; - - case CLS_NAME: - // Check for a quoted Class Name: (i.e. "MyClass") - if (m_list[nameStart] == '"') - { - // Advance until we see the second " - // - for (; (currChar != '\0'); i++) - { - currChar = m_list[i]; - // Advance until we see the second " - if (currChar == '"') - { - break; - } - // or until we see the end of string - if (currChar == '\0') - { - break; - } - } - - // skip the initial " - nameStart++; - isQuoted = true; - } - - // A colon denotes the end of the Class name and the start of the Method name - if (currChar == ':') - { - // Record the class name - currentName.m_classNameStart = nameStart; - currentName.m_classNameLen = i - nameStart; - - // Also accept the double colon syntax as well (i.e class::method) - // - if (m_list[i + 1] == ':') - { - i++; - } - - if (isQuoted) - { - // Remove the trailing " - currentName.m_classNameLen--; - isQuoted = false; - } - - // Is the first character a wildcard? - if (m_list[currentName.m_classNameStart] == WILD_CHAR) - { - // The class name is a full wildcard; mark it as such. - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - } - // Is there a wildcard at the end of the class name? - // - else if (m_list[currentName.m_classNameStart + currentName.m_classNameLen - 1] == WILD_CHAR) - { - // i.e. bar*:method, will match any class that starts with "bar" - - // Remove the trailing WILD_CHAR from class name - currentName.m_classNameWildcardAtEnd = true; - currentName.m_classNameLen--; // backup for WILD_CHAR - } - - // The method name will start at the next character - nameStart = i + 1; - - // Now expect FUNC_NAME - state = FUNC_NAME; - } - else if ((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')) - { - // Treat this as a method name without a class name. - currentName.m_classNameStart = -1; - currentName.m_classNameLen = -1; - goto DONE_FUNC_NAME; - } - break; - - case FUNC_NAME: - // Check for a quoted method name: i.e. className:"MyFunc" - // - // Note that we may have already parsed a quoted string above in CLS_NAME, i.e. "Func": - if (!isQuoted && (m_list[nameStart] == '"')) - { - // Advance until we see the second " - // - for (; (currChar != '\0'); i++) - { - currChar = m_list[i]; - // Advance until we see the second " - if (currChar == '"') - { - break; - } - // or until we see the end of string - if (currChar == '\0') - { - break; - } - } - - // skip the initial " - nameStart++; - isQuoted = true; - } - - if ((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')) - { - DONE_FUNC_NAME: - assert((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == '(')); - - // Record the method name - currentName.m_methodNameStart = nameStart; - currentName.m_methodNameLen = i - nameStart; - - if (isQuoted) - { - // Remove the trailing " - currentName.m_methodNameLen--; - isQuoted = false; - } - - // Is the first character a wildcard? - if (m_list[currentName.m_methodNameStart] == WILD_CHAR) - { - // The method name is a full wildcard; mark it as such. - currentName.m_methodNameStart = -1; - currentName.m_methodNameLen = -1; - } - // Is there a wildcard at the end of the method name? - // - else if (m_list[currentName.m_methodNameStart + currentName.m_methodNameLen - 1] == WILD_CHAR) - { - // i.e. class:foo*, will match any method that starts with "foo" - - // Remove the trailing WILD_CHAR from method name - currentName.m_methodNameLen--; // backup for WILD_CHAR - currentName.m_methodNameWildcardAtEnd = true; - } - - // should we expect an ARG_LIST? - // - if (currChar == '(') - { - currentName.m_numArgs = -1; - // Expect an ARG_LIST - state = ARG_LIST; - } - else // reached the end of string or a SEP_CHAR - { - assert((currChar == '\0') || (currChar == SEP_CHAR)); - - currentName.m_numArgs = -1; - - // There isn't an ARG_LIST - goto DONE_ARG_LIST; - } - } - break; - - case ARG_LIST: - if ((currChar == '\0') || (currChar == ')')) - { - if (currentName.m_numArgs == -1) - { - currentName.m_numArgs = 0; - } - - DONE_ARG_LIST: - assert((currChar == '\0') || (currChar == SEP_CHAR) || (currChar == ')')); - - // We have parsed an entire method name; create a new entry in the list for it. - MethodName* name = static_cast(host->allocateMemory(sizeof(MethodName))); - *name = currentName; - - assert(name->m_next == nullptr); - *lastName = name; - lastName = &name->m_next; - - state = NO_NAME; - - // Skip anything after the argument list until we find the next - // separator character. Otherwise if we see "func(a,b):foo" we - // would create entries for "func(a,b)" as well as ":foo". - if (currChar == ')') - { - do - { - currChar = m_list[++i]; - } while ((currChar != '\0') && (currChar != SEP_CHAR)); - } - } - else // We are looking at the ARG_LIST - { - if ((currChar != SEP_CHAR) && (currentName.m_numArgs == -1)) - { - currentName.m_numArgs = 1; - } - - // A comma means that there is an additional arg - if (currChar == ',') - { - currentName.m_numArgs++; - } - } - break; - - default: - assert(!"Bad state"); - break; + name->m_containsClassName = false; + name->m_classNameContainsInstantiation = false; + } + + name->m_containsSignature = parens != nullptr; + m_names = name; + }; + + const char* curPatternStart = m_list; + const char* curChar; + for (curChar = curPatternStart; *curChar != '\0'; curChar++) + { + if (*curChar == ' ') + { + commitPattern(curPatternStart, curChar); + curPatternStart = curChar + 1; } } + + commitPattern(curPatternStart, curChar); } void JitConfigValues::MethodSet::destroy(ICorJitHost* host) @@ -309,83 +99,91 @@ void JitConfigValues::MethodSet::destroy(ICorJitHost* host) m_names = nullptr; } -static bool matchesName(const char* const name, int nameLen, bool wildcardAtEnd, const char* const s2) +// Quadratic string matching algorithm that supports * and ? wildcards +static bool matchGlob(const char* pattern, const char* patternEnd, const char* str) { - // 's2' must start with 'nameLen' characters of 'name' - if (strncmp(name, s2, nameLen) != 0) + // Invariant: [patternStart..backtrackPattern) matches [stringStart..backtrackStr) + const char* backtrackPattern = nullptr; + const char* backtrackStr = nullptr; + + while (true) { - return false; + if (pattern == patternEnd) + { + if (*str == '\0') + return true; + } + else if (*pattern == '*') + { + backtrackPattern = ++pattern; + backtrackStr = str; + continue; + } + else if (*str == '\0') + { + // No match since pattern needs at least one char in remaining cases. + } + else if ((*pattern == '?') || (*pattern == *str)) + { + pattern++; + str++; + continue; + } + + // In this case there was no match, see if we can backtrack to a wild + // card and consume one more character from the string. + if ((backtrackPattern == nullptr) || (*backtrackStr == '\0')) + return false; + + // Consume one more character for the wildcard. + pattern = backtrackPattern; + str = ++backtrackStr; } +} - // if we don't have a wildcardAtEnd then s2 also need to be zero terminated - if (!wildcardAtEnd && (s2[nameLen] != '\0')) +bool JitConfigValues::MethodSet::contains(CORINFO_METHOD_HANDLE methodHnd, + CORINFO_CLASS_HANDLE classHnd, + CORINFO_SIG_INFO* sigInfo) const +{ + if (isEmpty()) { return false; } - // we have a successful match - return true; -} - -bool JitConfigValues::MethodSet::contains(const char* methodName, - const char* className, - CORINFO_SIG_INFO* sigInfo) const -{ - int numArgs = sigInfo != nullptr ? sigInfo->numArgs : -1; + Compiler* comp = JitTls::GetCompiler(); + char buffer[1024]; + StringPrinter printer(comp->getAllocator(CMK_DebugOnly), buffer, ArrLen(buffer)); + MethodName* prevPattern = nullptr; - // Try to match any the entries in the list. for (MethodName* name = m_names; name != nullptr; name = name->m_next) { - // If m_numArgs is valid, check for a mismatch - if (name->m_numArgs != -1 && name->m_numArgs != numArgs) - { - continue; - } - - // If m_methodNameStart is valid, check for a mismatch - if (name->m_methodNameStart != -1) + if ((prevPattern == nullptr) || (name->m_containsClassName != prevPattern->m_containsClassName) || + (name->m_classNameContainsInstantiation != prevPattern->m_classNameContainsInstantiation) || + (name->m_methodNameContainsInstantiation != prevPattern->m_methodNameContainsInstantiation) || + (name->m_containsSignature != prevPattern->m_containsSignature)) { - const char* expectedMethodName = &m_list[name->m_methodNameStart]; - if (!matchesName(expectedMethodName, name->m_methodNameLen, name->m_methodNameWildcardAtEnd, methodName)) - { - // C++ embeds the class name into the method name; deal with that here. - const char* colon = strchr(methodName, ':'); - if (colon != nullptr && colon[1] == ':' && - matchesName(expectedMethodName, name->m_methodNameLen, name->m_methodNameWildcardAtEnd, methodName)) - { - int classLen = (int)(colon - methodName); - if (name->m_classNameStart == -1 || - (classLen == name->m_classNameLen && - strncmp(&m_list[name->m_classNameStart], methodName, classLen) == 0)) - { - return true; - } - } + printer.Truncate(0); + bool success = comp->eeRunFunctorWithSPMIErrorTrap([&]() { + comp->eePrintMethod(&printer, name->m_containsClassName ? classHnd : NO_CLASS_HANDLE, methodHnd, + sigInfo, + /* includeNamespaces */ true, + /* includeClassInstantiation */ name->m_classNameContainsInstantiation, + /* includeMethodInstantiation */ name->m_methodNameContainsInstantiation, + /* includeSignature */ name->m_containsSignature, + /* includeReturnType */ false, + /* includeThis */ false); + }); + + if (!success) continue; - } - } - // If m_classNameStart is valid, check for a mismatch - if (className == nullptr || name->m_classNameStart == -1 || - matchesName(&m_list[name->m_classNameStart], name->m_classNameLen, name->m_classNameWildcardAtEnd, - className)) - { - return true; + prevPattern = name; } -#ifdef _DEBUG - // Maybe className doesn't include the namespace. Try to match that - const char* nsSep = strrchr(className, '.'); - if (nsSep != nullptr && nsSep != className) + if (matchGlob(name->m_patternStart, name->m_patternEnd, printer.GetBuffer())) { - const char* onlyClass = nsSep[-1] == '.' ? nsSep : &nsSep[1]; - if (matchesName(&m_list[name->m_classNameStart], name->m_classNameLen, name->m_classNameWildcardAtEnd, - onlyClass)) - { - return true; - } + return true; } -#endif } return false; diff --git a/src/coreclr/jit/jitconfig.h b/src/coreclr/jit/jitconfig.h index 12d327d292b399..e19021cd52f22b 100644 --- a/src/coreclr/jit/jitconfig.h +++ b/src/coreclr/jit/jitconfig.h @@ -7,6 +7,8 @@ #include "switches.h" struct CORINFO_SIG_INFO; +typedef struct CORINFO_CLASS_STRUCT_* CORINFO_CLASS_HANDLE; +typedef struct CORINFO_METHOD_STRUCT_* CORINFO_METHOD_HANDLE; class ICorJitHost; class JitConfigValues @@ -18,13 +20,12 @@ class JitConfigValues struct MethodName { MethodName* m_next; - int m_methodNameStart; - int m_methodNameLen; - bool m_methodNameWildcardAtEnd; - int m_classNameStart; - int m_classNameLen; - bool m_classNameWildcardAtEnd; - int m_numArgs; + const char* m_patternStart; + const char* m_patternEnd; + bool m_containsClassName; + bool m_classNameContainsInstantiation; + bool m_methodNameContainsInstantiation; + bool m_containsSignature; }; char* m_list; @@ -50,7 +51,7 @@ class JitConfigValues { return m_names == nullptr; } - bool contains(const char* methodName, const char* className, CORINFO_SIG_INFO* sigInfo) const; + bool contains(CORINFO_METHOD_HANDLE methodHnd, CORINFO_CLASS_HANDLE classHnd, CORINFO_SIG_INFO* sigInfo) const; }; private: diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 3e6df61b1f71fd..ce7df4f88e54f6 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -26,15 +26,14 @@ CONFIG_INTEGER(DiffableDasm, W("JitDiffableDasm"), 0) // Make the disas CONFIG_INTEGER(JitDasmWithAddress, W("JitDasmWithAddress"), 0) // Print the process address next to each instruction of // the disassembly CONFIG_INTEGER(DisplayLoopHoistStats, W("JitLoopHoistStats"), 0) // Display JIT loop hoisting statistics -CONFIG_INTEGER(DisplayLsraStats, W("JitLsraStats"), 0) // Display JIT Linear Scan Register Allocator statistics - // If set to "1", display the stats in textual format. - // If set to "2", display the stats in csv format. - // If set to "3", display the stats in summarize format. - // Recommended to use with JitStdOutFile flag. -CONFIG_STRING(JitLsraOrdering, W("JitLsraOrdering")) // LSRA heuristics ordering -CONFIG_INTEGER(DumpJittedMethods, W("DumpJittedMethods"), 0) // Prints all jitted methods to the console -CONFIG_INTEGER(EnablePCRelAddr, W("JitEnablePCRelAddr"), 1) // Whether absolute addr be encoded as PC-rel offset by - // RyuJIT where possible +CONFIG_INTEGER(DisplayLsraStats, W("JitLsraStats"), 0) // Display JIT Linear Scan Register Allocator statistics + // If set to "1", display the stats in textual format. + // If set to "2", display the stats in csv format. + // If set to "3", display the stats in summarize format. + // Recommended to use with JitStdOutFile flag. +CONFIG_STRING(JitLsraOrdering, W("JitLsraOrdering")) // LSRA heuristics ordering +CONFIG_INTEGER(EnablePCRelAddr, W("JitEnablePCRelAddr"), 1) // Whether absolute addr be encoded as PC-rel offset by + // RyuJIT where possible CONFIG_INTEGER(JitAssertOnMaxRAPasses, W("JitAssertOnMaxRAPasses"), 0) CONFIG_INTEGER(JitBreakEmitOutputInstr, W("JitBreakEmitOutputInstr"), -1) CONFIG_INTEGER(JitBreakMorphTree, W("JitBreakMorphTree"), 0xffffffff) @@ -191,10 +190,11 @@ CONFIG_STRING(JitDisasmAssemblies, W("JitDisasmAssemblies")) // Only show JitDis CONFIG_INTEGER(JitDisasmWithGC, W("JitDisasmWithGC"), 0) // Dump interleaved GC Info for any method disassembled. CONFIG_INTEGER(JitDisasmWithDebugInfo, W("JitDisasmWithDebugInfo"), 0) // Dump interleaved debug info for any method // disassembled. -CONFIG_METHODSET(JitDump, W("JitDump")) // Dumps trees for specified method -CONFIG_INTEGER(JitDumpTier0, W("JitDumpTier0"), 1) // Dump tier0 requests -CONFIG_INTEGER(JitDumpAtOSROffset, W("JitDumpAtOSROffset"), -1) // Only dump OSR requests for this offset -CONFIG_INTEGER(JitDumpInlinePhases, W("JitDumpInlinePhases"), 1) // Dump inline compiler phases +CONFIG_INTEGER(JitDisasmSpilled, W("JitDisasmSpilled"), 0) // Display native code when any register spilling occurs +CONFIG_METHODSET(JitDump, W("JitDump")) // Dumps trees for specified method +CONFIG_INTEGER(JitDumpTier0, W("JitDumpTier0"), 1) // Dump tier0 requests +CONFIG_INTEGER(JitDumpAtOSROffset, W("JitDumpAtOSROffset"), -1) // Only dump OSR requests for this offset +CONFIG_INTEGER(JitDumpInlinePhases, W("JitDumpInlinePhases"), 1) // Dump inline compiler phases CONFIG_METHODSET(JitEHDump, W("JitEHDump")) // Dump the EH table for the method, as reported to the VM CONFIG_METHODSET(JitExclude, W("JitExclude")) CONFIG_INTEGER(JitFakeProcedureSplitting, W("JitFakeProcedureSplitting"), 0) // Do code splitting independent of VM. @@ -257,6 +257,8 @@ CONFIG_INTEGER(EnableIncompleteISAClass, W("EnableIncompleteISAClass"), 0) // En CONFIG_METHODSET(JitDisasm, W("JitDisasm")) #endif // !defined(DEBUG) +CONFIG_INTEGER(JitDisasmSummary, W("JitDisasmSummary"), 0) // Prints all jitted methods to the console + CONFIG_INTEGER(RichDebugInfo, W("RichDebugInfo"), 0) // If 1, keep rich debug info and report it back to the EE #ifdef DEBUG @@ -563,7 +565,9 @@ CONFIG_STRING(JitEnablePgoRange, W("JitEnablePgoRange")) // Enable pgo d CONFIG_INTEGER(JitRandomEdgeCounts, W("JitRandomEdgeCounts"), 0) // Substitute random values for edge counts CONFIG_INTEGER(JitCrossCheckDevirtualizationAndPGO, W("JitCrossCheckDevirtualizationAndPGO"), 0) CONFIG_INTEGER(JitNoteFailedExactDevirtualization, W("JitNoteFailedExactDevirtualization"), 0) -#endif // debug +CONFIG_INTEGER(JitRandomlyCollect64BitCounts, W("JitRandomlyCollect64BitCounts"), 0) // Collect 64-bit counts randomly + // for some methods. +#endif // debug // Devirtualize virtual calls with getExactClasses (NativeAOT only for now) CONFIG_INTEGER(JitEnableExactDevirtualization, W("JitEnableExactDevirtualization"), 1) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 2871a506679acd..478ce8ee859dea 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -1425,7 +1425,7 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) break; } GenTree* node = use.GetNode(); - if (varTypeIsFloating(node)) + if (varTypeUsesFloatReg(node)) { GenTree* intNode = LowerFloatArgReg(node, currRegNumber); assert(intNode != nullptr); @@ -1447,7 +1447,7 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) // List fields were replaced in place. return arg; } - else if (varTypeIsFloating(arg)) + else if (varTypeUsesFloatReg(arg)) { GenTree* intNode = LowerFloatArgReg(arg, callArg->AbiInfo.GetRegNum()); assert(intNode != nullptr); @@ -1470,11 +1470,13 @@ GenTree* Lowering::LowerFloatArg(GenTree** pArg, CallArg* callArg) // GenTree* Lowering::LowerFloatArgReg(GenTree* arg, regNumber regNum) { + assert(varTypeUsesFloatReg(arg)); + var_types floatType = arg->TypeGet(); - assert(varTypeIsFloating(floatType)); - var_types intType = (floatType == TYP_DOUBLE) ? TYP_LONG : TYP_INT; - GenTree* intArg = comp->gtNewBitCastNode(intType, arg); + var_types intType = (floatType == TYP_FLOAT) ? TYP_INT : TYP_LONG; + GenTree* intArg = comp->gtNewBitCastNode(intType, arg); intArg->SetRegNum(regNum); + #ifdef TARGET_ARM if (floatType == TYP_DOUBLE) { @@ -3819,6 +3821,11 @@ void Lowering::LowerCallStruct(GenTreeCall* call) assert(user->TypeIs(origType) || (returnType == user->TypeGet())); break; + case GT_CALL: + // Argument lowering will deal with register file mismatches if needed. + assert(varTypeIsSIMD(origType)); + break; + case GT_STOREIND: #ifdef FEATURE_SIMD if (varTypeIsSIMD(user)) @@ -5345,28 +5352,43 @@ bool Lowering::TryCreateAddrMode(GenTree* addr, bool isContainable, GenTree* par } #ifdef TARGET_ARM64 - if ((index != nullptr) && index->OperIs(GT_CAST) && (scale == 1) && (offset == 0) && varTypeIsByte(targetType)) - { - MakeSrcContained(addrMode, index); - } - // Check if we can "contain" LEA(BFIZ) in order to extend 32bit index to 64bit as part of load/store. - if ((index != nullptr) && index->OperIs(GT_BFIZ) && index->gtGetOp1()->OperIs(GT_CAST) && - index->gtGetOp2()->IsCnsIntOrI() && !varTypeIsStruct(targetType)) + if (index != nullptr) { - // BFIZ node is a binary op where op1 is GT_CAST and op2 is GT_CNS_INT - GenTreeCast* cast = index->gtGetOp1()->AsCast(); - assert(cast->isContained()); + if (index->OperIs(GT_CAST) && (scale == 1) && (offset == 0) && varTypeIsByte(targetType)) + { + if (IsSafeToContainMem(parent, index)) + { + // Check containment safety against the parent node - this will ensure that LEA with the contained + // index will itself always be contained. We do not support uncontained LEAs with contained indices. + index->AsCast()->CastOp()->ClearContained(); // Uncontain any memory operands. + MakeSrcContained(addrMode, index); + } + } + else if (index->OperIs(GT_BFIZ) && index->gtGetOp1()->OperIs(GT_CAST) && index->gtGetOp2()->IsCnsIntOrI() && + !varTypeIsStruct(targetType)) + { + // Check if we can "contain" LEA(BFIZ) in order to extend 32bit index to 64bit as part of load/store. + // BFIZ node is a binary op where op1 is GT_CAST and op2 is GT_CNS_INT + GenTreeCast* cast = index->gtGetOp1()->AsCast(); + assert(cast->isContained()); - const unsigned shiftBy = (unsigned)index->gtGetOp2()->AsIntCon()->IconValue(); + const unsigned shiftBy = (unsigned)index->gtGetOp2()->AsIntCon()->IconValue(); - // 'scale' and 'offset' have to be unset since we're going to use [base + index * SXTW/UXTW scale] form - // where there is no room for additional offsets/scales on ARM64. 'shiftBy' has to match target's width. - if (cast->CastOp()->TypeIs(TYP_INT) && cast->TypeIs(TYP_LONG) && (genTypeSize(targetType) == (1U << shiftBy)) && - (scale == 1) && (offset == 0)) - { - // TODO: Make sure that genCreateAddrMode marks such BFIZ candidates as GTF_DONT_CSE for better CQ. - MakeSrcContained(addrMode, index); + // 'scale' and 'offset' have to be unset since we're going to use [base + index * SXTW/UXTW scale] form + // where there is no room for additional offsets/scales on ARM64. 'shiftBy' has to match target's width. + if (cast->CastOp()->TypeIs(TYP_INT) && cast->TypeIs(TYP_LONG) && + (genTypeSize(targetType) == (1U << shiftBy)) && (scale == 1) && (offset == 0)) + { + if (IsSafeToContainMem(parent, index)) + { + // Check containment safety against the parent node - this will ensure that LEA with the contained + // index will itself always be contained. We do not support uncontained LEAs with contained indices. + + // TODO: Make sure that genCreateAddrMode marks such BFIZ candidates as GTF_DONT_CSE for better CQ. + MakeSrcContained(addrMode, index); + } + } } } #endif diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 0ff88151503201..3cc7000f442595 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -2086,7 +2086,8 @@ bool Lowering::IsValidCompareChain(GenTree* child, GenTree* parent) return IsValidCompareChain(child->AsOp()->gtGetOp2(), child) && IsValidCompareChain(child->AsOp()->gtGetOp1(), child); } - else if (child->OperIsCmpCompare()) + else if (child->OperIsCmpCompare() && varTypeIsIntegral(child->gtGetOp1()) && + varTypeIsIntegral(child->gtGetOp2())) { // Can the child compare be contained. return IsSafeToContainMem(parent, child); @@ -2148,7 +2149,8 @@ bool Lowering::ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree child->SetContained(); return true; } - else if (child->OperIsCmpCompare()) + else if (child->OperIsCmpCompare() && varTypeIsIntegral(child->gtGetOp1()) && + varTypeIsIntegral(child->gtGetOp2())) { child->AsOp()->SetContained(); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 5f2e384daa7ce7..7800ee166d2cc8 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -507,18 +507,6 @@ void Lowering::LowerPutArgStk(GenTreePutArgStk* putArgStk) // registers to be consumed atomically by the call. if (varTypeIsIntegralOrI(fieldNode)) { - // If we are loading from an in-memory local, we would like to use "push", but this - // is only legal if we can safely load all 4 bytes. Retype the local node here to - // TYP_INT for such legal cases to make downstream (LSRA & codegen) logic simpler. - // Retyping is ok because we model this node as STORE(LOAD). - // If the field came from promotion, we allow the padding to remain undefined, if - // from decomposition, the field type will be INT (naturally blocking the retyping). - if (varTypeIsSmall(fieldNode) && (genTypeSize(fieldType) <= genTypeSize(fieldNode)) && - fieldNode->OperIsLocalRead()) - { - fieldNode->ChangeType(TYP_INT); - } - if (IsContainableImmed(putArgStk, fieldNode)) { MakeSrcContained(putArgStk, fieldNode); @@ -3419,6 +3407,20 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) { assert(simdBaseType == TYP_FLOAT); + // We need to mask off the most significant element to avoid the shuffle + add + // from including it in the computed result. We need to do this for both op1 and + // op2 in case one of them is `NaN` (because Zero * NaN == NaN) + + simd16_t simd16Val = {}; + + simd16Val.i32[0] = -1; + simd16Val.i32[1] = -1; + simd16Val.i32[2] = -1; + simd16Val.i32[3] = +0; + + simdType = TYP_SIMD16; + simdSize = 16; + // We will be constructing the following parts: // ... // +--* CNS_INT int -1 @@ -3426,7 +3428,7 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) // +--* CNS_INT int -1 // +--* CNS_INT int 0 // tmp1 = * HWINTRINSIC simd16 T Create - // /--* op2 simd16 + // /--* op1 simd16 // +--* tmp1 simd16 // op1 = * HWINTRINSIC simd16 T And // ... @@ -3434,30 +3436,48 @@ GenTree* Lowering::LowerHWIntrinsicDot(GenTreeHWIntrinsic* node) // This is roughly the following managed code: // ... // tmp1 = Vector128.Create(-1, -1, -1, 0); - // op1 = Sse.And(op1, tmp2); + // op1 = Sse.And(op1, tmp1); // ... - GenTree* cns0 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(op1, cns0); + GenTreeVecCon* vecCon1 = comp->gtNewVconNode(simdType, simdBaseJitType); + vecCon1->gtSimd16Val = simd16Val; + + BlockRange().InsertAfter(op1, vecCon1); + + op1 = comp->gtNewSimdHWIntrinsicNode(simdType, op1, vecCon1, NI_SSE_And, simdBaseJitType, simdSize); + BlockRange().InsertAfter(vecCon1, op1); - GenTree* cns1 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(cns0, cns1); + LowerNode(vecCon1); + LowerNode(op1); + + // We will be constructing the following parts: + // ... + // +--* CNS_INT int -1 + // +--* CNS_INT int -1 + // +--* CNS_INT int -1 + // +--* CNS_INT int 0 + // tmp2 = * HWINTRINSIC simd16 T Create + // /--* op2 simd16 + // +--* tmp2 simd16 + // op2 = * HWINTRINSIC simd16 T And + // ... - GenTree* cns2 = comp->gtNewIconNode(-1, TYP_INT); - BlockRange().InsertAfter(cns1, cns2); + // This is roughly the following managed code: + // ... + // tmp2 = Vector128.Create(-1, -1, -1, 0); + // op2 = Sse.And(op2, tmp2); + // ... - GenTree* cns3 = comp->gtNewIconNode(0, TYP_INT); - BlockRange().InsertAfter(cns2, cns3); + GenTreeVecCon* vecCon2 = comp->gtNewVconNode(simdType, simdBaseJitType); + vecCon2->gtSimd16Val = simd16Val; - tmp1 = comp->gtNewSimdHWIntrinsicNode(simdType, cns0, cns1, cns2, cns3, NI_Vector128_Create, - CORINFO_TYPE_INT, 16); - BlockRange().InsertAfter(cns3, tmp1); + BlockRange().InsertAfter(op2, vecCon2); - op1 = comp->gtNewSimdHWIntrinsicNode(simdType, op1, tmp1, NI_SSE_And, simdBaseJitType, simdSize); - BlockRange().InsertAfter(tmp1, op1); + op2 = comp->gtNewSimdHWIntrinsicNode(simdType, op2, vecCon2, NI_SSE_And, simdBaseJitType, simdSize); + BlockRange().InsertAfter(vecCon2, op2); - LowerNode(tmp1); - LowerNode(op1); + LowerNode(vecCon2); + LowerNode(op2); } } diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 144d5f3ab17753..4417858838d548 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -2721,7 +2721,11 @@ bool LinearScan::isMatchingConstant(RegRecord* physRegRecord, RefPosition* refPo case GT_CNS_VEC: { - return GenTreeVecCon::Equals(refPosition->treeNode->AsVecCon(), otherTreeNode->AsVecCon()); + return +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + !Compiler::varTypeNeedsPartialCalleeSave(physRegRecord->assignedInterval->registerType) && +#endif + GenTreeVecCon::Equals(refPosition->treeNode->AsVecCon(), otherTreeNode->AsVecCon()); } default: diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index d90320db3b3e08..8adb111ebc88b7 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3524,11 +3524,11 @@ int LinearScan::BuildStoreLoc(GenTreeLclVarCommon* storeLoc) else if (op1->isContained() && op1->OperIs(GT_BITCAST)) { GenTree* bitCastSrc = op1->gtGetOp1(); - RegisterType registerType = bitCastSrc->TypeGet(); + RegisterType registerType = regType(bitCastSrc->TypeGet()); singleUseRef = BuildUse(bitCastSrc, allRegs(registerType)); Interval* srcInterval = singleUseRef->getInterval(); - assert(srcInterval->registerType == registerType); + assert(regType(srcInterval->registerType) == registerType); srcCount = 1; } #ifndef TARGET_64BIT diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 9fdfe585c68e8e..1c97652c5a4764 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -1512,17 +1512,18 @@ int LinearScan::BuildPutArgStk(GenTreePutArgStk* putArgStk) // We can treat as a slot any field that is stored at a slot boundary, where the previous // field is not in the same slot. (Note that we store the fields in reverse order.) - const bool fieldIsSlot = ((fieldOffset % 4) == 0) && ((prevOffset - fieldOffset) >= 4); - const bool canStoreWithPush = fieldIsSlot; - const bool canLoadWithPush = varTypeIsI(fieldNode); + const bool canStoreFullSlot = ((fieldOffset % 4) == 0) && ((prevOffset - fieldOffset) >= 4); + const bool canLoadFullSlot = + (genTypeSize(fieldNode) == TARGET_POINTER_SIZE) || + (fieldNode->OperIsLocalRead() && (genTypeSize(fieldNode) >= genTypeSize(fieldType))); - if ((!canStoreWithPush || !canLoadWithPush) && (intTemp == nullptr)) + if ((!canStoreFullSlot || !canLoadFullSlot) && (intTemp == nullptr)) { intTemp = buildInternalIntRegisterDefForNode(putArgStk); } // We can only store bytes using byteable registers. - if (!canStoreWithPush && varTypeIsByte(fieldType)) + if (!canStoreFullSlot && varTypeIsByte(fieldType)) { intTemp->registerAssignment &= allByteRegs(); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index fff00343e888ab..dcf1fd4c619ad5 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -11198,13 +11198,14 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) temp = nullptr; } } - else if (op1->OperGet() == GT_ADD) + else { #ifdef TARGET_ARM + GenTree* effOp1 = op1->gtEffectiveVal(true); // Check for a misalignment floating point indirection. - if (varTypeIsFloating(typ)) + if (effOp1->OperIs(GT_ADD) && varTypeIsFloating(typ)) { - GenTree* addOp2 = op1->AsOp()->gtGetOp2(); + GenTree* addOp2 = effOp1->gtGetOp2(); if (addOp2->IsCnsIntOrI()) { ssize_t offset = addOp2->AsIntCon()->gtIconVal; diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets index 0018c70f987796..5e4110529b1c10 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.SingleEntry.targets @@ -36,25 +36,34 @@ SetupProperties - - - <_PackageReferenceExceptILCompiler Include="@(PackageReference)" Exclude="Microsoft.DotNet.ILCompiler" /> - <_ILCompilerPackageReference Include="@(PackageReference)" Exclude="@(_PackageReferenceExceptILCompiler)" /> - - @(_ILCompilerPackageReference->'%(Version)') + + + + $([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetDirectoryName($(ILCompilerTargetsPath))))))) + + + + + - - + + - + @(ResolvedILCompilerPack->'%(PackageDirectory)') @(ResolvedTargetILCompilerPack->'%(PackageDirectory)') @(ResolvedILCompilerPack->'%(PackageDirectory)') + + %(PackageDefinitions.ResolvedPath) + %(PackageDefinitions.ResolvedPath) + + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props index da6b72b12d7ce1..c2f2f45bce605b 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.props @@ -11,8 +11,6 @@ Copyright (c) .NET Foundation. All rights reserved. --> - - true $(MSBuildThisFileDirectory)Microsoft.DotNet.ILCompiler.SingleEntry.targets diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets new file mode 100644 index 00000000000000..28ca04be98b91b --- /dev/null +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.DotNet.ILCompiler.targets @@ -0,0 +1,18 @@ + + + + + $(NETCoreSdkVersion.StartsWith('6')) + + + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 690ef724af07e1..417d4567a95189 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -49,6 +49,10 @@ The .NET Foundation licenses this file to you under the MIT license. false + + false + + .obj .o @@ -241,6 +245,8 @@ The .NET Foundation licenses this file to you under the MIT license. + + diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index 4ccefca81f5328..2e37b1708d195e 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -50,6 +50,7 @@ set(COMMON_RUNTIME_SOURCES ${GC_DIR}/handletablecore.cpp ${GC_DIR}/handletablescan.cpp ${GC_DIR}/objecthandle.cpp + ${GC_DIR}/softwarewritewatch.cpp ) set(SERVER_GC_SOURCES @@ -206,6 +207,12 @@ include_directories(${ARCH_SOURCES_DIR}) add_definitions(-DFEATURE_BASICFREEZE) add_definitions(-DFEATURE_CONSERVATIVE_GC) + +if(CLR_CMAKE_TARGET_UNIX) + add_definitions(-DFEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) + add_definitions(-DFEATURE_MANUALLY_MANAGED_CARD_BUNDLES) +endif() + add_definitions(-DFEATURE_CUSTOM_IMPORTS) add_definitions(-DFEATURE_DYNAMIC_CODE) add_compile_definitions($<$,$>:FEATURE_GC_STRESS>) diff --git a/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl b/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl index aff497778d99e0..8bf7eb68585cfc 100644 --- a/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl +++ b/src/coreclr/nativeaot/Runtime/GCMemoryHelpers.inl @@ -229,7 +229,7 @@ FORCEINLINE void InlinedBulkWriteBarrier(void* pMemStart, size_t cbMemSize) // Compute the shadow heap address corresponding to the beginning of the range of heap addresses modified // and in the process range check it to make sure we have the shadow version allocated. uintptr_t* shadowSlot = (uintptr_t*)(g_GCShadow + ((uint8_t*)pMemStart - g_lowest_address)); - if (shadowSlot <= (uintptr_t*)g_GCShadowEnd) + if (shadowSlot < (uintptr_t*)g_GCShadowEnd) { // Iterate over every pointer sized slot in the range, copying data from the real heap to the shadow heap. // As we perform each copy we need to recheck the real heap contents with an ordered read to ensure we're @@ -239,6 +239,7 @@ FORCEINLINE void InlinedBulkWriteBarrier(void* pMemStart, size_t cbMemSize) uintptr_t* realSlot = (uintptr_t*)pMemStart; uintptr_t slotCount = cbMemSize / sizeof(uintptr_t); + ASSERT(slotCount < (uintptr_t*)g_GCShadowEnd - shadowSlot); do { // Update shadow slot from real slot. diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 2fcceff6d0dd85..3da12598ba196f 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -1105,13 +1105,13 @@ struct UniversalTransitionStackFrame // Conservative GC reporting must be applied to everything between the base of the // ReturnBlock and the top of the StackPassedArgs. private: - uintptr_t m_pushedFP; // ChildSP+000 CallerSP-0C0 (0x08 bytes) (fp) - uintptr_t m_pushedLR; // ChildSP+008 CallerSP-0B8 (0x08 bytes) (lr) - uint64_t m_fpArgRegs[8]; // ChildSP+010 CallerSP-0B0 (0x40 bytes) (d0-d7) - uintptr_t m_returnBlock[4]; // ChildSP+050 CallerSP-070 (0x40 bytes) - uintptr_t m_intArgRegs[9]; // ChildSP+070 CallerSP-050 (0x48 bytes) (x0-x8) - uintptr_t m_alignmentPad; // ChildSP+0B8 CallerSP-008 (0x08 bytes) - uintptr_t m_stackPassedArgs[1]; // ChildSP+0C0 CallerSP+000 (unknown size) + uintptr_t m_pushedFP; // ChildSP+000 CallerSP-100 (0x08 bytes) (fp) + uintptr_t m_pushedLR; // ChildSP+008 CallerSP-0F8 (0x08 bytes) (lr) + Fp128 m_fpArgRegs[8]; // ChildSP+010 CallerSP-0F0 (0x80 bytes) (q0-q7) + uintptr_t m_returnBlock[4]; // ChildSP+090 CallerSP-070 (0x40 bytes) + uintptr_t m_intArgRegs[9]; // ChildSP+0B0 CallerSP-050 (0x48 bytes) (x0-x8) + uintptr_t m_alignmentPad; // ChildSP+0F8 CallerSP-008 (0x08 bytes) + uintptr_t m_stackPassedArgs[1]; // ChildSP+100 CallerSP+000 (unknown size) public: PTR_UIntNative get_CallerSP() { return GET_POINTER_TO_FIELD(m_stackPassedArgs[0]); } diff --git a/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp b/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp index 30a1ff269290ab..649aac21ac8d18 100644 --- a/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/UniversalTransitionHelpers.cpp @@ -21,7 +21,7 @@ // // In the absence of trashing, such bugs can become undetectable if the code that // dispatches the call happens to never touch the impacted argument register (e.g., xmm3 on -// amd64 or d5 on arm32). In such a case, the original enregistered argument will flow +// amd64 or q5 on arm64). In such a case, the original enregistered argument will flow // unmodified into the eventual callee, obscuring the fact that the dispatcher failed to // propagate the transition frame copy of this register. // diff --git a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S index ac4c4b496252a4..c31a95c9bec5a3 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.S @@ -27,7 +27,7 @@ jb LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) add \DESTREG, [C_VAR(g_GCShadow)] cmp \DESTREG, [C_VAR(g_GCShadowEnd)] - ja LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) + jae LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) // Update the shadow heap. mov [\DESTREG], \REFREG @@ -84,6 +84,21 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // we're in a debug build and write barrier checking has been enabled). UPDATE_GC_SHADOW \BASENAME, \REFREG, rdi +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + mov r11, [C_VAR(g_write_watch_table)] + cmp r11, 0x0 + je LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG) + + mov r10, rdi + shr r10, 0xC // SoftwareWriteWatch::AddressToTableByteIndexShift + add r10, r11 + cmp byte ptr [r10], 0x0 + jne LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG) + mov byte ptr [r10], 0xFF +#endif + +LOCAL_LABEL(\BASENAME\()_CheckCardTable_\REFREG): + // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). cmp \REFREG, [C_VAR(g_ephemeral_low)] @@ -95,17 +110,25 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // track this write. The location address is translated into an offset in the card table bitmap. We set // an entire byte in the card table since it's quicker than messing around with bitmasks and we only write // the byte if it hasn't already been done since writes are expensive and impact scaling. - shr rdi, 11 - add rdi, [C_VAR(g_card_table)] - cmp byte ptr [rdi], 0x0FF - jne LOCAL_LABEL(\BASENAME\()_UpdateCardTable_\REFREG) - -LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG): - ret + shr rdi, 0x0B + mov r10, [C_VAR(g_card_table)] + cmp byte ptr [rdi + r10], 0x0FF + je LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) // We get here if it's necessary to update the card table. -LOCAL_LABEL(\BASENAME\()_UpdateCardTable_\REFREG): - mov byte ptr [rdi], 0x0FF + mov byte ptr [rdi + r10], 0xFF + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + // Shift rdi by 0x0A more to get the card bundle byte (we shifted by 0x0B already) + shr rdi, 0x0A + add rdi, [C_VAR(g_card_bundle_table)] + cmp byte ptr [rdi], 0xFF + je LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) + + mov byte ptr [rdi], 0xFF +#endif + +LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG): ret .endm @@ -252,6 +275,21 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT // we're in a debug build and write barrier checking has been enabled). UPDATE_GC_SHADOW BASENAME, rcx, rdi +#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + mov r11, [C_VAR(g_write_watch_table)] + cmp r11, 0x0 + je LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable) + + mov r10, rdi + shr r10, 0xC // SoftwareWriteWatch::AddressToTableByteIndexShift + add r10, r11 + cmp byte ptr [r10], 0x0 + jne LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable) + mov byte ptr [r10], 0xFF +#endif + +LOCAL_LABEL(RhpByRefAssignRef_CheckCardTable): + // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). cmp rcx, [C_VAR(g_ephemeral_low)] @@ -259,25 +297,30 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT cmp rcx, [C_VAR(g_ephemeral_high)] jae LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) - // move current rdi value into rcx and then increment the pointers + // move current rdi value into rcx, we need to keep rdi and eventually increment by 8 mov rcx, rdi - add rsi, 0x8 - add rdi, 0x8 // We have a location on the GC heap being updated with a reference to an ephemeral object so we must // track this write. The location address is translated into an offset in the card table bitmap. We set // an entire byte in the card table since it's quicker than messing around with bitmasks and we only write // the byte if it hasn't already been done since writes are expensive and impact scaling. - shr rcx, 11 - add rcx, [C_VAR(g_card_table)] - cmp byte ptr [rcx], 0x0FF - jne LOCAL_LABEL(RhpByRefAssignRef_UpdateCardTable) - ret + shr rcx, 0x0B + mov r10, [C_VAR(g_card_table)] + cmp byte ptr [rcx + r10], 0x0FF + je LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // We get here if it's necessary to update the card table. -LOCAL_LABEL(RhpByRefAssignRef_UpdateCardTable): - mov byte ptr [rcx], 0x0FF - ret + mov byte ptr [rcx + r10], 0xFF + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + // Shift rcx by 0x0A more to get the card bundle byte (we shifted by 0x0B already) + shr rcx, 0x0A + add rcx, [C_VAR(g_card_bundle_table)] + cmp byte ptr [rcx], 0xFF + je LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + + mov byte ptr [rcx], 0xFF +#endif LOCAL_LABEL(RhpByRefAssignRef_NotInHeap): // Increment the pointers before leaving diff --git a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm index a249f0f043d29b..5a6dcc666fec5f 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/amd64/WriteBarriers.asm @@ -43,7 +43,7 @@ UPDATE_GC_SHADOW macro BASENAME, REFREG, DESTREG jb &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& add DESTREG, [g_GCShadow] cmp DESTREG, [g_GCShadowEnd] - ja &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& + jae &BASENAME&_UpdateShadowHeap_PopThenDone_&REFREG& ;; Update the shadow heap. mov [DESTREG], REFREG diff --git a/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S index 1db0dbdc01231a..863e17cc9fdf9f 100644 --- a/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/arm/WriteBarriers.S @@ -38,7 +38,7 @@ ldr r12, =C_FUNC(g_GCShadowEnd) ldr r12, [r12] cmp \DESTREG, r12 - jhi LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) + bhs LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_PopThenDone_\REFREG) // Update the shadow heap. str \REFREG, [\DESTREG] @@ -105,15 +105,15 @@ LOCAL_LABEL(\BASENAME\()_UpdateShadowHeap_Done_\REFREG): // If the reference is to an object that's not in an ephemeral generation we have no need to track it // (since the object won't be collected or moved by an ephemeral collection). - ldr r12, =C_FUNC(g_ephemeral_low) + ldr r12, =C_FUNC(g_ephemeral_low) ldr r12, [r12] cmp \REFREG, r12 blo LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) - ldr r12, =C_FUNC(g_ephemeral_high) + ldr r12, =C_FUNC(g_ephemeral_high) ldr r12, [r12] - cmp \REFREG, r12 - bhi LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) + cmp \REFREG, r12 + bhs LOCAL_LABEL(\BASENAME\()_EXIT_\REFREG) // We have a location on the GC heap being updated with a reference to an ephemeral object so we must // track this write. The location address is translated into an offset in the card table bitmap. We set @@ -167,11 +167,11 @@ ALTERNATE_ENTRY RhpAssignRef // // Note that none of this is relevant for single cpu machines. We may choose to implement a // uniprocessor specific version of this barrier if uni-proc becomes a significant scenario again. - dmb + dmb // Write the reference into the location. Note that we rely on the fact that no GC can occur between here // and the card table update we may perform below. -ALTERNATE_ENTRY "RhpAssignRefAvLocation"\EXPORT_REG_NAME // WriteBarrierFunctionAvLocation +ALTERNATE_ENTRY "RhpAssignRefAvLocation"\EXPORT_REG_NAME // WriteBarrierFunctionAvLocation .ifc \REFREG, r1 ALTERNATE_ENTRY RhpAssignRefAVLocation .endif @@ -198,14 +198,14 @@ DEFINE_UNCHECKED_WRITE_BARRIER r1, r1 // The location being updated might not even lie in the GC heap (a handle or stack location for instance), // in which case no write barrier is required. - ldr r12, =C_FUNC(g_lowest_address) + ldr r12, =C_FUNC(g_lowest_address) ldr r12, [r12] cmp r0, r12 blo LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) - ldr r12, =C_FUNC(g_highest_address) + ldr r12, =C_FUNC(g_highest_address) ldr r12, [r12] cmp r0, r12 - bhi LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) + bhs LOCAL_LABEL(\BASENAME\()_NoBarrierRequired_\REFREG) DEFINE_UNCHECKED_WRITE_BARRIER_CORE \BASENAME, \REFREG @@ -270,7 +270,7 @@ LEAF_ENTRY RhpCheckedLockCmpXchg, _TEXT // barrier must occur before the object reference update, so we have to do it unconditionally even // though the update may fail below. dmb -ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation +ALTERNATE_ENTRY RhpCheckedLockCmpXchgAVLocation LOCAL_LABEL(RhpCheckedLockCmpXchgRetry): ldrex r3, [r0] cmp r2, r3 @@ -337,7 +337,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT ldr r3, =C_FUNC(g_highest_address) ldr r3, [r3] cmp r0, r3 - bhi LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + bhs LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless // we're in a debug build and write barrier checking has been enabled). @@ -352,7 +352,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT ldr r3, =C_FUNC(g_ephemeral_high) ldr r3, [r3] cmp r2, r3 - bhi LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) + bhs LOCAL_LABEL(RhpByRefAssignRef_NotInHeap) // move current r0 value into r2 and then increment the pointers mov r2, r0 diff --git a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S index 12fa42365f4c2e..8274b6b1110a6a 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S +++ b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.S @@ -23,7 +23,7 @@ #define RETURN_BLOCK_SIZE (32) #define COUNT_FLOAT_ARG_REGISTERS (8) -#define FLOAT_REGISTER_SIZE (8) +#define FLOAT_REGISTER_SIZE (16) #define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE) #define PUSHED_LR_SIZE (8) @@ -50,7 +50,7 @@ // // RhpUniversalTransition // -// At input to this function, x0-8, d0-7 and the stack may contain any number of arguments. +// At input to this function, x0-8, q0-7 and the stack may contain any number of arguments. // // In addition, there are 2 extra arguments passed in the intra-procedure-call scratch register: // xip0 will contain the managed function that is to be called by this transition function @@ -63,16 +63,16 @@ // // Frame layout is: // -// {StackPassedArgs} ChildSP+0C0 CallerSP+000 -// {AlignmentPad (0x8 bytes)} ChildSP+0B8 CallerSP-008 -// {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+070 CallerSP-050 -// {ReturnBlock (0x20 bytes)} ChildSP+050 CallerSP-070 +// {StackPassedArgs} ChildSP+100 CallerSP+000 +// {AlignmentPad (0x8 bytes)} ChildSP+0F8 CallerSP-008 +// {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+0B0 CallerSP-050 +// {ReturnBlock (0x20 bytes)} ChildSP+090 CallerSP-070 // -- The base address of the Return block is the TransitionBlock pointer, the floating point args are // in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact // layout of all pieces of the frame that lie at or above the pushed floating point registers. -// {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+010 CallerSP-0B0 -// {PushedLR} ChildSP+008 CallerSP-0B8 -// {PushedFP} ChildSP+000 CallerSP-0C0 +// {FpArgRegs (q0-q7) (0x80 bytes)} ChildSP+010 CallerSP-0F0 +// {PushedLR} ChildSP+008 CallerSP-0F8 +// {PushedFP} ChildSP+000 CallerSP-100 // // NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure // must be updated as well. @@ -95,10 +95,10 @@ PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -STACK_SIZE // ;; Push down stack pointer and store FP and LR // Floating point registers - stp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - stp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - stp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - stp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + stp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + stp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + stp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + stp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] // Space for return buffer data (0x40 bytes) @@ -112,10 +112,10 @@ #ifdef TRASH_SAVED_ARGUMENT_REGISTERS PREPARE_EXTERNAL_VAR RhpFpTrashValues, x1 - ldp d0,d1, [x1, 0x0] - ldp d2,d3, [x1, 0x10] - ldp d4,d5, [x1, 0x20] - ldp d6,d7, [x1, 0x30] + ldp q0,q1, [x1, 0x0] + ldp q2,q3, [x1, 0x20] + ldp q4,q5, [x1, 0x40] + ldp q6,q7, [x1, 0x60] PREPARE_EXTERNAL_VAR RhpIntegerTrashValues, x1 @@ -139,10 +139,10 @@ mov x12, x0 // Restore floating point registers - ldp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - ldp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - ldp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - ldp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + ldp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + ldp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + ldp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + ldp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] // Restore the argument registers ldp x0, x1, [sp, #(ARGUMENT_REGISTERS_OFFSET )] diff --git a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm index 6f1fc0953cd985..2e23ea4302a4fc 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/UniversalTransition.asm @@ -23,7 +23,7 @@ #define RETURN_BLOCK_SIZE (32) #define COUNT_FLOAT_ARG_REGISTERS (8) -#define FLOAT_REGISTER_SIZE (8) +#define FLOAT_REGISTER_SIZE (16) #define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE) #define PUSHED_LR_SIZE (8) @@ -51,7 +51,7 @@ ;; ;; RhpUniversalTransition ;; -;; At input to this function, x0-8, d0-7 and the stack may contain any number of arguments. +;; At input to this function, x0-8, q0-7 and the stack may contain any number of arguments. ;; ;; In addition, there are 2 extra arguments passed in the intra-procedure-call scratch register: ;; xip0 will contain the managed function that is to be called by this transition function @@ -64,16 +64,16 @@ ;; ;; Frame layout is: ;; -;; {StackPassedArgs} ChildSP+0C0 CallerSP+000 -;; {AlignmentPad (0x8 bytes)} ChildSP+0B8 CallerSP-008 -;; {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+070 CallerSP-050 -;; {ReturnBlock (0x20 bytes)} ChildSP+050 CallerSP-070 +;; {StackPassedArgs} ChildSP+100 CallerSP+000 +;; {AlignmentPad (0x8 bytes)} ChildSP+0F8 CallerSP-008 +;; {IntArgRegs (x0-x8) (0x48 bytes)} ChildSP+0A0 CallerSP-050 +;; {ReturnBlock (0x20 bytes)} ChildSP+090 CallerSP-070 ;; -- The base address of the Return block is the TransitionBlock pointer, the floating point args are ;; in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact ;; layout of all pieces of the frame that lie at or above the pushed floating point registers. -;; {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+010 CallerSP-0B0 -;; {PushedLR} ChildSP+008 CallerSP-0B8 -;; {PushedFP} ChildSP+000 CallerSP-0C0 +;; {FpArgRegs (q0-q7) (0x80 bytes)} ChildSP+010 CallerSP-0F0 +;; {PushedLR} ChildSP+008 CallerSP-0F8 +;; {PushedFP} ChildSP+000 CallerSP-100 ;; ;; NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure ;; must be updated as well. @@ -97,10 +97,10 @@ PROLOG_SAVE_REG_PAIR fp, lr, #-STACK_SIZE! ;; Push down stack pointer and store FP and LR ;; Floating point registers - stp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - stp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - stp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - stp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + stp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + stp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + stp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + stp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] ;; Space for return buffer data (0x40 bytes) @@ -130,10 +130,10 @@ mov x12, x0 ;; Restore floating point registers - ldp d0, d1, [sp, #(FLOAT_ARG_OFFSET )] - ldp d2, d3, [sp, #(FLOAT_ARG_OFFSET + 0x10)] - ldp d4, d5, [sp, #(FLOAT_ARG_OFFSET + 0x20)] - ldp d6, d7, [sp, #(FLOAT_ARG_OFFSET + 0x30)] + ldp q0, q1, [sp, #(FLOAT_ARG_OFFSET )] + ldp q2, q3, [sp, #(FLOAT_ARG_OFFSET + 0x20)] + ldp q4, q5, [sp, #(FLOAT_ARG_OFFSET + 0x40)] + ldp q6, q7, [sp, #(FLOAT_ARG_OFFSET + 0x60)] ;; Restore the argument registers ldp x0, x1, [sp, #(ARGUMENT_REGISTERS_OFFSET )] diff --git a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S index b02737673caa60..c2d2bf4bdabfb2 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S +++ b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.S @@ -38,14 +38,14 @@ // Transform destReg into the equivalent address in the shadow heap. PREPARE_EXTERNAL_VAR_INDIRECT g_lowest_address, X9 subs \destReg, \destReg, x9 - blt 0f + blo 0f PREPARE_EXTERNAL_VAR_INDIRECT g_GCShadow, X9 add \destReg, \destReg, x9 PREPARE_EXTERNAL_VAR_INDIRECT g_GCShadowEnd, X9 cmp \destReg, x9 - bgt 0f + bhs 0f // Update the shadow heap. str \refReg, [\destReg] @@ -92,12 +92,11 @@ // destReg: location to be updated // refReg: objectref to be stored // trash: register nr than can be trashed - // trash2: register than can be trashed // // On exit: // destReg: trashed // - .macro INSERT_UNCHECKED_WRITE_BARRIER_CORE destReg, refReg, trash, trash2 + .macro INSERT_UNCHECKED_WRITE_BARRIER_CORE destReg, refReg, trash // Update the shadow copy of the heap with the same value just written to the same heap. (A no-op unless // we are in a debug build and write barrier checking has been enabled). @@ -120,36 +119,36 @@ // an object not on the epehemeral segment. PREPARE_EXTERNAL_VAR_INDIRECT g_ephemeral_low, x\trash cmp \refReg, x\trash - blt 0f + blo 0f PREPARE_EXTERNAL_VAR_INDIRECT g_ephemeral_high, x\trash cmp \refReg, x\trash - bge 0f + bhs 0f // Set this objects card, if it has not already been set. PREPARE_EXTERNAL_VAR_INDIRECT g_card_table, x\trash - add \trash2, x\trash, \destReg, lsr #11 + add x17, x\trash, \destReg, lsr #11 // Check that this card has not already been written. Avoiding useless writes is a big win on // multi-proc systems since it avoids cache thrashing. - ldrb w\trash, [\trash2] + ldrb w\trash, [x17] cmp x\trash, 0xFF beq 0f mov x\trash, 0xFF - strb w\trash, [\trash2] + strb w\trash, [x17] #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES // Check if we need to update the card bundle table PREPARE_EXTERNAL_VAR_INDIRECT g_card_bundle_table, x\trash - add \trash2, x\trash, \destReg, lsr #21 - ldrb w\trash, [\trash2] + add x17, x\trash, \destReg, lsr #21 + ldrb w\trash, [x17] cmp x\trash, 0xFF beq 0f mov x\trash, 0xFF - strb w\trash, [\trash2] + strb w\trash, [x17] #endif 0: @@ -160,25 +159,26 @@ // destReg: location to be updated // refReg: objectref to be stored // trash: register nr than can be trashed - // trash2: register than can be trashed // // On exit: // destReg: trashed // - .macro INSERT_CHECKED_WRITE_BARRIER_CORE destReg, refReg, trash, trash2 + .macro INSERT_CHECKED_WRITE_BARRIER_CORE destReg, refReg, trash // The "check" of this checked write barrier - is destReg // within the heap? if no, early out. PREPARE_EXTERNAL_VAR_INDIRECT g_lowest_address, x\trash cmp \destReg, x\trash - blt 0f PREPARE_EXTERNAL_VAR_INDIRECT g_highest_address, x\trash - cmp \destReg, x\trash - bgt 0f - INSERT_UNCHECKED_WRITE_BARRIER_CORE \destReg, \refReg, \trash, \trash2 + // If \destReg >= g_lowest_address, compare \destReg to g_highest_address. + // Otherwise, set the C flag (0x2) to take the next branch. + ccmp \destReg, x\trash, #0x2, hs + bhs 0f + + INSERT_UNCHECKED_WRITE_BARRIER_CORE \destReg, \refReg, \trash 0: // Exit label @@ -263,7 +263,7 @@ CmpXchgRetry: // The following barrier code takes the destination in x0 and the value in x1 so the arguments are // already correctly set up. - INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9, x0 + INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9 CmpXchgNoUpdate: // x10 still contains the original value. @@ -305,7 +305,7 @@ ExchangeRetry: // The following barrier code takes the destination in x0 and the value in x1 so the arguments are // already correctly set up. - INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9, x0 + INSERT_CHECKED_WRITE_BARRIER_CORE x0, x1, 9 // x10 still contains the original value. mov x0, x10 @@ -319,7 +319,7 @@ LEAF_ENTRY RhpAssignRefArm64, _TEXT ALTERNATE_ENTRY RhpAssignRefX1AVLocation stlr x15, [x14] - INSERT_UNCHECKED_WRITE_BARRIER_CORE x14, x15, 12, X14 + INSERT_UNCHECKED_WRITE_BARRIER_CORE x14, x15, 12 ret LEAF_END RhpAssignRefArm64, _TEXT @@ -341,9 +341,7 @@ LEAF_ENTRY RhpCheckedAssignRefArm64, _TEXT stlr x15, [x14] - INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12, X15 - - add x14, x14, #8 + INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12 ret LEAF_END RhpCheckedAssignRefArm64, _TEXT @@ -364,7 +362,7 @@ LEAF_ENTRY RhpByRefAssignRefArm64, _TEXT ldr x15, [x13] stlr x15, [x14] - INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12, X15 + INSERT_CHECKED_WRITE_BARRIER_CORE x14, x15, 12 add X13, x13, #8 add x14, x14, #8 diff --git a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm index 9aff215ffc57be..3889b08efcf02a 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/WriteBarriers.asm @@ -54,7 +54,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_lowest_address ldr x12, [x12, g_lowest_address] subs $destReg, $destReg, x12 - blt %ft0 + blo %ft0 adrp x12, $g_GCShadow ldr x12, [x12, $g_GCShadow] @@ -63,7 +63,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, $g_GCShadowEnd ldr x12, [x12, $g_GCShadowEnd] cmp $destReg, x12 - bgt %ft0 + bhs %ft0 ;; Update the shadow heap. str $refReg, [$destReg] @@ -127,12 +127,12 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_ephemeral_low ldr x12, [x12, g_ephemeral_low] cmp $refReg, x12 - blt %ft0 + blo %ft0 adrp x12, g_ephemeral_high ldr x12, [x12, g_ephemeral_high] cmp $refReg, x12 - bge %ft0 + bhs %ft0 ;; Set this object's card, if it hasn't already been set. adrp x12, g_card_table @@ -170,12 +170,14 @@ INVALIDGCVALUE EQU 0xCCCCCCCD adrp x12, g_lowest_address ldr x12, [x12, g_lowest_address] cmp $destReg, x12 - blt %ft0 adrp x12, g_highest_address ldr x12, [x12, g_highest_address] - cmp $destReg, x12 - bgt %ft0 + + ;; If $destReg >= g_lowest_address, compare $destReg to g_highest_address. + ;; Otherwise, set the C flag (0x2) to take the next branch. + ccmp $destReg, x12, #0x2, hs + bhs %ft0 INSERT_UNCHECKED_WRITE_BARRIER_CORE $destReg, $refReg, $trashReg diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index f3d3010ff5a0c2..e75eb1c66be27e 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -1018,8 +1018,8 @@ void GCToEEInterface::DiagWalkBGCSurvivors(void* gcContext) #endif // FEATURE_EVENT_TRACE } -#if defined(FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) && (!defined(TARGET_ARM64) || !defined(TARGET_UNIX)) -#error FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is only implemented for ARM64 and UNIX +#if defined(FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP) && !defined(TARGET_UNIX) +#error FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP is only implemented for UNIX #endif void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) @@ -1307,13 +1307,6 @@ MethodTable* GCToEEInterface::GetFreeObjectMethodTable() bool GCToEEInterface::GetBooleanConfigValue(const char* privateKey, const char* publicKey, bool* value) { - // these configuration values are given to us via startup flags. - if (strcmp(privateKey, "gcServer") == 0) - { - *value = g_heap_type == GC_HEAP_SVR; - return true; - } - if (strcmp(privateKey, "gcConservative") == 0) { *value = true; diff --git a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm index a7038354094567..d718f7aa085f0a 100644 --- a/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm +++ b/src/coreclr/nativeaot/Runtime/i386/WriteBarriers.asm @@ -48,7 +48,7 @@ UPDATE_GC_SHADOW macro BASENAME, DESTREG, REFREG jb &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& add DESTREG, [g_GCShadow] cmp DESTREG, [g_GCShadowEnd] - ja &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& + jae &BASENAME&_UpdateShadowHeap_PopThenDone_&DESTREG&_&REFREG& ;; Update the shadow heap. mov [DESTREG], REFREG diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index 176aef2ad1033b..6f77813cd06143 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -10,8 +10,8 @@ struct ReadyToRunHeaderConstants { static const uint32_t Signature = 0x00525452; // 'RTR' - static const uint32_t CurrentMajorVersion = 7; - static const uint32_t CurrentMinorVersion = 1; + static const uint32_t CurrentMajorVersion = 8; + static const uint32_t CurrentMinorVersion = 0; }; struct ReadyToRunHeader diff --git a/src/coreclr/nativeaot/Runtime/startup.cpp b/src/coreclr/nativeaot/Runtime/startup.cpp index 0cfa7efe36003f..62a0f3ba9221cb 100644 --- a/src/coreclr/nativeaot/Runtime/startup.cpp +++ b/src/coreclr/nativeaot/Runtime/startup.cpp @@ -401,7 +401,7 @@ static void UninitDLL() #endif // PROFILE_STARTUP } -volatile bool g_processShutdownHasStarted = false; +volatile Thread* g_threadPerformingShutdown = NULL; static void DllThreadDetach() { @@ -413,7 +413,7 @@ static void DllThreadDetach() { // Once shutdown starts, RuntimeThreadShutdown callbacks are ignored, implying that // it is no longer guaranteed that exiting threads will be detached. - if (!g_processShutdownHasStarted) + if (g_threadPerformingShutdown != NULL) { ASSERT_UNCONDITIONALLY("Detaching thread whose home fiber has not been detached"); RhFailFast(); @@ -439,9 +439,17 @@ void RuntimeThreadShutdown(void* thread) } #else ASSERT((Thread*)thread == ThreadStore::GetCurrentThread()); + + // Do not do shutdown for the thread that performs the shutdown. + // other threads could be terminated before it and could leave TLS locked + if ((Thread*)thread == g_threadPerformingShutdown) + { + return; + } + #endif - ThreadStore::DetachCurrentThread(g_processShutdownHasStarted); + ThreadStore::DetachCurrentThread(g_threadPerformingShutdown != NULL); } extern "C" bool RhInitialize() @@ -474,11 +482,11 @@ COOP_PINVOKE_HELPER(void, RhpEnableConservativeStackReporting, ()) COOP_PINVOKE_HELPER(void, RhpShutdown, ()) { // Indicate that runtime shutdown is complete and that the caller is about to start shutting down the entire process. - g_processShutdownHasStarted = true; + g_threadPerformingShutdown = ThreadStore::RawGetCurrentThread(); } #ifdef _WIN32 -EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, uint32_t dwReason, void* /*pvReserved*/) +EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, uint32_t dwReason, void* pvReserved) { switch (dwReason) { diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 526810177a5ed7..f8be025c062aef 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -13,6 +13,8 @@ #include "UnixHandle.h" #include #include "gcenv.h" +#include "gcenv.ee.h" +#include "gcconfig.h" #include "holder.h" #include "UnixSignals.h" #include "UnixContext.h" @@ -417,6 +419,8 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() ConfigureSignals(); + GCConfig::Initialize(); + if (!GCToOSInterface::Initialize()) { return false; @@ -869,7 +873,22 @@ extern "C" UInt32_BOOL DuplicateHandle( extern "C" UInt32_BOOL InitializeCriticalSection(CRITICAL_SECTION * lpCriticalSection) { - return pthread_mutex_init(&lpCriticalSection->mutex, NULL) == 0; + pthread_mutexattr_t mutexAttributes; + int st = pthread_mutexattr_init(&mutexAttributes); + if (st != 0) + { + return false; + } + + st = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); + if (st == 0) + { + st = pthread_mutex_init(&lpCriticalSection->mutex, &mutexAttributes); + } + + pthread_mutexattr_destroy(&mutexAttributes); + + return (st == 0); } extern "C" UInt32_BOOL InitializeCriticalSectionEx(CRITICAL_SECTION * lpCriticalSection, uint32_t arg2, uint32_t arg3) diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 95e6d6778b9752..8ca31f5ae05bcc 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -222,7 +222,7 @@ uintptr_t UnixNativeCodeManager::GetConservativeUpperBoundForOutgoingArgs(Method UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; - PTR_UInt8 p = pNativeMethodInfo->pMainLSDA; + PTR_UInt8 p = pNativeMethodInfo->pLSDA; uint8_t unwindBlockFlags = *p++; @@ -283,7 +283,7 @@ bool UnixNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo, { UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; - PTR_UInt8 p = pNativeMethodInfo->pMainLSDA; + PTR_UInt8 p = pNativeMethodInfo->pLSDA; uint8_t unwindBlockFlags = *p++; @@ -868,7 +868,7 @@ PTR_VOID UnixNativeCodeManager::GetAssociatedData(PTR_VOID ControlPC) if (!FindMethodInfo(ControlPC, (MethodInfo*)&methodInfo)) return NULL; - PTR_UInt8 p = methodInfo.pMainLSDA; + PTR_UInt8 p = methodInfo.pLSDA; uint8_t unwindBlockFlags = *p++; if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) == 0) diff --git a/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc b/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc index 1fb8e47aa628f3..c69149cc69f743 100644 --- a/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc +++ b/src/coreclr/nativeaot/Runtime/unix/unixasmmacrosarm64.inc @@ -164,49 +164,6 @@ C_FUNC(\Name): brk #0 .endm -//----------------------------------------------------------------------------- -// The Following sets of SAVE_*_REGISTERS expect the memory to be reserved and -// base address to be passed in $reg -// - -// Reserve 64 bytes of memory before calling SAVE_ARGUMENT_REGISTERS -.macro SAVE_ARGUMENT_REGISTERS reg, ofs - - stp x0, x1, [\reg, #(\ofs)] - stp x2, x3, [\reg, #(\ofs + 16)] - stp x4, x5, [\reg, #(\ofs + 32)] - stp x6, x7, [\reg, #(\ofs + 48)] - -.endm - -// Reserve 64 bytes of memory before calling SAVE_FLOAT_ARGUMENT_REGISTERS -.macro SAVE_FLOAT_ARGUMENT_REGISTERS reg, ofs - - stp d0, d1, [\reg, #(\ofs)] - stp d2, d3, [\reg, #(\ofs + 16)] - stp d4, d5, [\reg, #(\ofs + 32)] - stp d6, d7, [\reg, #(\ofs + 48)] - -.endm - -.macro RESTORE_ARGUMENT_REGISTERS reg, ofs - - ldp x0, x1, [\reg, #(\ofs)] - ldp x2, x3, [\reg, #(\ofs + 16)] - ldp x4, x5, [\reg, #(\ofs + 32)] - ldp x6, x7, [\reg, #(\ofs + 48)] - -.endm - -.macro RESTORE_FLOAT_ARGUMENT_REGISTERS reg, ofs - - ldp d0, d1, [\reg, #(\ofs)] - ldp d2, d3, [\reg, #(\ofs + 16)] - ldp d4, d5, [\reg, #(\ofs + 32)] - ldp d6, d7, [\reg, #(\ofs + 48)] - -.endm - .macro EPILOG_BRANCH_REG reg br \reg diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp index 32c072d05d06a2..39f8ffd00a0cbb 100644 --- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp @@ -30,6 +30,8 @@ uint32_t PalEventWrite(REGHANDLE arg1, const EVENT_DESCRIPTOR * arg2, uint32_t a } #include "gcenv.h" +#include "gcenv.ee.h" +#include "gcconfig.h" #define REDHAWK_PALEXPORT extern "C" @@ -71,29 +73,36 @@ void InitializeCurrentProcessCpuCount() } else { - DWORD_PTR pmask, smask; - - if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) + if (GCToOSInterface::CanEnableGCCPUGroups()) { - count = 1; + count = GCToOSInterface::GetTotalProcessorCount(); } else { - count = 0; + DWORD_PTR pmask, smask; - while (pmask) + if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) { - pmask &= (pmask - 1); - count++; + count = 1; + } + else + { + count = 0; + + while (pmask) + { + pmask &= (pmask - 1); + count++; + } + + // GetProcessAffinityMask can return pmask=0 and smask=0 on systems with more + // than 64 processors, which would leave us with a count of 0. Since the GC + // expects there to be at least one processor to run on (and thus at least one + // heap), we'll return 64 here if count is 0, since there are likely a ton of + // processors available in that case. + if (count == 0) + count = 64; } - - // GetProcessAffinityMask can return pmask=0 and smask=0 on systems with more - // than 64 processors, which would leave us with a count of 0. Since the GC - // expects there to be at least one processor to run on (and thus at least one - // heap), we'll return 64 here if count is 0, since there are likely a ton of - // processors available in that case. - if (count == 0) - count = 64; } JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpuRateControl; @@ -119,10 +128,7 @@ void InitializeCurrentProcessCpuCount() if (0 < maxRate && maxRate < MAXIMUM_CPU_RATE) { - SYSTEM_INFO systemInfo; - GetSystemInfo(&systemInfo); - - DWORD cpuLimit = (maxRate * systemInfo.dwNumberOfProcessors + MAXIMUM_CPU_RATE - 1) / MAXIMUM_CPU_RATE; + DWORD cpuLimit = (maxRate * GCToOSInterface::GetTotalProcessorCount() + MAXIMUM_CPU_RATE - 1) / MAXIMUM_CPU_RATE; if (cpuLimit < count) count = cpuLimit; } @@ -145,6 +151,8 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() return false; } + GCConfig::Initialize(); + if (!GCToOSInterface::Initialize()) { return false; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs index 4e8bfcd492dde1..8e383b44d85033 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs @@ -55,8 +55,8 @@ internal static unsafe void StringToByValAnsiString(string str, byte* pNative, i public static unsafe string ByValAnsiStringToString(byte* buffer, int length) { - int end = SpanHelpers.IndexOf(ref *(byte*)buffer, 0, length); - if (end != -1) + int end = new ReadOnlySpan(buffer, length).IndexOf((byte)0); + if (end >= 0) { length = end; } @@ -77,8 +77,8 @@ internal static unsafe void StringToUnicodeFixedArray(string str, ushort* buffer internal static unsafe string UnicodeToStringFixedArray(ushort* buffer, int length) { - int end = SpanHelpers.IndexOf(ref *(char*)buffer, '\0', length); - if (end != -1) + int end = new ReadOnlySpan(buffer, length).IndexOf('\0'); + if (end >= 0) { length = end; } diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs index ebfbab7a8e2c02..41226385ea86ac 100644 --- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs +++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Text; @@ -49,7 +47,6 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, enclosingTypeHandle, methodHandle)); Method method = metadataReader.GetMethod(methodHandle); - MethodSignature methodSignature = metadataReader.GetMethodSignature(method.Signature); formatter.EmitTypeName(enclosingTypeHandle, namespaceQualified: true); formatter._outputBuilder.Append('.'); formatter.EmitString(method.Name); @@ -64,7 +61,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit } else { - formatter._outputBuilder.Append(", "); + formatter._outputBuilder.Append(','); } formatter.EmitTypeName(handle, namespaceQualified: false); } @@ -73,7 +70,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit formatter._outputBuilder.Append(']'); } - formatter.EmitMethodParameters(methodSignature); + formatter.EmitMethodParameters(methodHandle); return formatter._outputBuilder.ToString(); } @@ -124,28 +121,30 @@ private void EmitMethodReferenceName(MemberReferenceHandle memberRefHandle) private void EmitMethodInstantiationName(MethodInstantiationHandle methodInstHandle) { MethodInstantiation methodInst = _metadataReader.GetMethodInstantiation(methodInstHandle); - MethodSignature methodSignature; + if (methodInst.Method.HandleType == HandleType.MemberReference) { MemberReferenceHandle methodRefHandle = methodInst.Method.ToMemberReferenceHandle(_metadataReader); MemberReference methodRef = methodRefHandle.GetMemberReference(_metadataReader); - EmitContainingTypeAndMethodName(methodRef, out methodSignature); + EmitContainingTypeAndMethodName(methodRef, out MethodSignature methodSignature); + EmitGenericArguments(methodInst.GenericTypeArguments); + EmitMethodParameters(methodSignature); } else { QualifiedMethodHandle qualifiedMethodHandle = methodInst.Method.ToQualifiedMethodHandle(_metadataReader); QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle); - EmitContainingTypeAndMethodName(qualifiedMethod, out methodSignature); + EmitContainingTypeAndMethodName(qualifiedMethod); + EmitGenericArguments(methodInst.GenericTypeArguments); + EmitMethodParameters(qualifiedMethod.Method); } - EmitGenericArguments(methodInst.GenericTypeArguments); - EmitMethodParameters(methodSignature); } private void EmitMethodDefinitionName(QualifiedMethodHandle qualifiedMethodHandle) { QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle); - EmitContainingTypeAndMethodName(qualifiedMethod, out MethodSignature methodSignature); - EmitMethodParameters(methodSignature); + EmitContainingTypeAndMethodName(qualifiedMethod); + EmitMethodParameters(qualifiedMethod.Method); } /// @@ -161,10 +160,9 @@ private void EmitContainingTypeAndMethodName(MemberReference methodRef, out Meth EmitString(methodRef.Name); } - private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod, out MethodSignature methodSignature) + private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod) { Method method = _metadataReader.GetMethod(qualifiedMethod.Method); - methodSignature = _metadataReader.GetMethodSignature(method.Signature); EmitTypeName(qualifiedMethod.EnclosingType, namespaceQualified: true); _outputBuilder.Append('.'); EmitString(method.Name); @@ -181,6 +179,57 @@ private void EmitMethodParameters(MethodSignature methodSignature) _outputBuilder.Append(')'); } + /// + /// Emit parenthesized method argument type list with parameter names. + /// + /// Method handle to use for parameter formatting + private void EmitMethodParameters(MethodHandle methodHandle) + { + bool TryGetNextParameter(ref ParameterHandleCollection.Enumerator enumerator, out Parameter parameter) + { + bool hasNext = enumerator.MoveNext(); + parameter = hasNext ? enumerator.Current.GetParameter(_metadataReader) : default; + return hasNext; + } + + Method method = methodHandle.GetMethod(_metadataReader); + HandleCollection typeVector = method.Signature.GetMethodSignature(_metadataReader).Parameters; + ParameterHandleCollection.Enumerator parameters = method.Parameters.GetEnumerator(); + + bool hasParameter = TryGetNextParameter(ref parameters, out Parameter parameter); + if (hasParameter && parameter.Sequence == 0) + { + hasParameter = TryGetNextParameter(ref parameters, out parameter); + } + + _outputBuilder.Append('('); + + uint typeIndex = 0; + foreach (Handle type in typeVector) + { + if (typeIndex != 0) + { + _outputBuilder.Append(", "); + } + + EmitTypeName(type, namespaceQualified: false); + + if (++typeIndex == parameter.Sequence && hasParameter) + { + string name = parameter.Name.GetConstantStringValue(_metadataReader).Value; + hasParameter = TryGetNextParameter(ref parameters, out parameter); + + if (!string.IsNullOrEmpty(name)) + { + _outputBuilder.Append(' '); + _outputBuilder.Append(name); + } + } + } + + _outputBuilder.Append(')'); + } + /// /// Emit comma-separated list of type names into the output string builder. /// diff --git a/src/coreclr/nativeaot/docs/compiling.md b/src/coreclr/nativeaot/docs/compiling.md index 1b4475a89362d3..f142fa3afecb17 100644 --- a/src/coreclr/nativeaot/docs/compiling.md +++ b/src/coreclr/nativeaot/docs/compiling.md @@ -2,8 +2,7 @@ Please consult [documentation](https://docs.microsoft.com/dotnet/core/deploying/native-aot) for instructions how to compile and publish application. -The rest of this document covers advanced topics only. - +The rest of this document covers advanced topics only. Adding an explicit package reference to `Microsoft.DotNet.ILCompiler` will generate warning when publishing and it can run into version errors. When possible, use the PublishAot property to publish a native AOT application. ## Using daily builds @@ -34,7 +33,21 @@ or by adding the following element to the project file: ## Cross-architecture compilation -Native AOT toolchain allows targeting ARM64 on an x64 host and vice versa for both Windows and Linux. Cross-OS compilation, such as targeting Linux on a Windows host, is not supported. To target win-arm64 on a Windows x64 host, in addition to the `Microsoft.DotNet.ILCompiler` package reference, also add the `runtime.win-x64.Microsoft.DotNet.ILCompiler` package reference to get the x64-hosted compiler: +Native AOT toolchain allows targeting ARM64 on an x64 host and vice versa for both Windows and Linux and is now supported in the SDK. Cross-OS compilation, such as targeting Linux on a Windows host, is not supported. For SDK support, add the following to your project file, + +```xml + + true + +``` + +Targeting win-arm64 on a Windows x64 host machine, + +```bash +> dotnet publish -r win-arm64 -c Release +``` + +To target win-arm64 on a Windows x64 host on an advanced scenario where the SDK support is not sufficient (note that these scenarios will generate warnings for using explicit package references), in addition to the `Microsoft.DotNet.ILCompiler` package reference, also add the `runtime.win-x64.Microsoft.DotNet.ILCompiler` package reference to get the x64-hosted compiler: ```xml ``` diff --git a/src/coreclr/pal/prebuilt/inc/clrdata.h b/src/coreclr/pal/prebuilt/inc/clrdata.h index 09b24af8ccf094..802c34a87bf0cb 100644 --- a/src/coreclr/pal/prebuilt/inc/clrdata.h +++ b/src/coreclr/pal/prebuilt/inc/clrdata.h @@ -1299,7 +1299,8 @@ enum CLRDataEnumMemoryFlags CLRDATA_ENUM_MEM_DEFAULT = 0, CLRDATA_ENUM_MEM_MINI = CLRDATA_ENUM_MEM_DEFAULT, CLRDATA_ENUM_MEM_HEAP = 0x1, - CLRDATA_ENUM_MEM_TRIAGE = 0x2 + CLRDATA_ENUM_MEM_TRIAGE = 0x2, + CLRDATA_ENUM_MEM_HEAP2 = 0x3 } CLRDataEnumMemoryFlags; diff --git a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs index 60be29fdce95c9..18f794a6f354e8 100644 --- a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs @@ -28,8 +28,11 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defTyp ComputedInstanceFieldLayout layoutFromMetadata = _fallbackAlgorithm.ComputeInstanceLayout(defType, layoutKind); - if (defType.Context.Target.IsWindows || (defType.Context.Target.PointerSize == 4)) + // 32bit platforms use standard metadata layout engine + if (defType.Context.Target.Architecture == TargetArchitecture.ARM) { + layoutFromMetadata.LayoutAbiStable = false; // Int128 parameter passing ABI is unstable at this time + layoutFromMetadata.IsInt128OrHasInt128Fields = true; return layoutFromMetadata; } @@ -42,7 +45,8 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defTyp FieldAlignment = new LayoutInt(16), FieldSize = layoutFromMetadata.FieldSize, Offsets = layoutFromMetadata.Offsets, - LayoutAbiStable = true + LayoutAbiStable = false, // Int128 parameter passing ABI is unstable at this time + IsInt128OrHasInt128Fields = true }; } @@ -72,7 +76,7 @@ public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristi public static bool IsIntegerType(DefType type) { return type.IsIntrinsic - && type.Namespace == "System." + && type.Namespace == "System" && ((type.Name == "Int128") || (type.Name == "UInt128")); } } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index c5fdfda3d0f58f..cc286f81b03619 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -14,8 +14,8 @@ internal struct ReadyToRunHeaderConstants { public const uint Signature = 0x00525452; // 'RTR' - public const ushort CurrentMajorVersion = 7; - public const ushort CurrentMinorVersion = 1; + public const ushort CurrentMajorVersion = 8; + public const ushort CurrentMinorVersion = 0; } #pragma warning disable 0169 diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index c1f59c124b6769..6d287dee31f44f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -776,27 +776,25 @@ private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, Meth if (method.IsArrayAddressMethod()) hasHiddenParameter = true; - - // We only populate sigInst for intrinsic methods because most of the time, - // JIT doesn't care what the instantiation is and this is expensive. - Instantiation owningTypeInst = method.OwningType.Instantiation; - sig->sigInst.classInstCount = (uint)owningTypeInst.Length; - if (owningTypeInst.Length != 0) - { - sig->sigInst.classInst = GetJitInstantiation(owningTypeInst); - } - - sig->sigInst.methInstCount = (uint)method.Instantiation.Length; - if (method.Instantiation.Length != 0) - { - sig->sigInst.methInst = GetJitInstantiation(method.Instantiation); - } } if (hasHiddenParameter) { sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_PARAMTYPE; } + + Instantiation owningTypeInst = method.OwningType.Instantiation; + sig->sigInst.classInstCount = (uint)owningTypeInst.Length; + if (owningTypeInst.Length != 0) + { + sig->sigInst.classInst = GetJitInstantiation(owningTypeInst); + } + + sig->sigInst.methInstCount = (uint)method.Instantiation.Length; + if (method.Instantiation.Length != 0) + { + sig->sigInst.methInst = GetJitInstantiation(method.Instantiation); + } } private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* sig, MethodILScope scope) @@ -823,7 +821,7 @@ private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* s sig->sigInst.classInst = null; // Not used by the JIT sig->sigInst.classInstCount = 0; // Not used by the JIT - sig->sigInst.methInst = null; // Not used by the JIT + sig->sigInst.methInst = null; sig->sigInst.methInstCount = (uint)signature.GenericParameterCount; sig->pSig = null; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs index 2190f9ac1a2785..4c3555c198af66 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs @@ -68,6 +68,11 @@ private static class FieldLayoutFlags /// True if the type transitively has any types with LayoutKind.Auto in its layout. /// public const int IsAutoLayoutOrHasAutoLayoutFields = 0x400; + + /// + /// True if the type transitively has an Int128 in it or is an Int128 + /// + public const int IsInt128OrHasInt128Fields = 0x800; } private class StaticBlockInfo @@ -135,6 +140,20 @@ public virtual bool IsAutoLayoutOrHasAutoLayoutFields } } + /// + /// Is a type Int128 or transitively have any fields of a type Int128. + /// + public virtual bool IsInt128OrHasInt128Fields + { + get + { + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.ComputedInstanceTypeLayout)) + { + ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields); + } + return _fieldLayoutFlags.HasFlags(FieldLayoutFlags.IsInt128OrHasInt128Fields); + } + } /// /// The number of bytes required to hold a field of this type @@ -430,6 +449,10 @@ public void ComputeInstanceLayout(InstanceLayoutKind layoutKind) { _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsAutoLayoutOrHasAutoLayoutFields); } + if (computedLayout.IsInt128OrHasInt128Fields) + { + _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsInt128OrHasInt128Fields); + } if (computedLayout.Offsets != null) { diff --git a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs index a19ec4b3603bf4..53388c915b85d8 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs @@ -83,6 +83,7 @@ public struct ComputedInstanceFieldLayout public LayoutInt ByteCountAlignment; public bool LayoutAbiStable; // Is the layout stable such that it can safely be used in function calling conventions public bool IsAutoLayoutOrHasAutoLayoutFields; + public bool IsInt128OrHasInt128Fields; /// /// If Offsets is non-null, then all field based layout is complete. diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs index 8da028a5190fb5..5ff6417e52e411 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs @@ -110,6 +110,7 @@ out instanceByteSizeAndAlignment FieldSize = sizeAndAlignment.Size, LayoutAbiStable = true, IsAutoLayoutOrHasAutoLayoutFields = false, + IsInt128OrHasInt128Fields = false, }; if (numInstanceFields > 0) @@ -211,7 +212,7 @@ public override ComputedStaticFieldLayout ComputeStaticFieldLayout(DefType defTy } ref StaticsBlock block = ref GetStaticsBlockForField(ref result, field); - SizeAndAlignment sizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout: false, context.Target.DefaultPackingSize, out bool _, out bool _); + SizeAndAlignment sizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout: false, context.Target.DefaultPackingSize, out bool _, out bool _, out bool _); block.Size = LayoutInt.AlignUp(block.Size, sizeAndAlignment.Alignment, context.Target); result.Offsets[index] = new FieldAndOffset(field, block.Size); @@ -303,15 +304,18 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty int fieldOrdinal = 0; bool layoutAbiStable = true; bool hasAutoLayoutField = false; + bool hasInt128Field = type.BaseType == null ? false : type.BaseType.IsInt128OrHasInt128Fields; foreach (var fieldAndOffset in layoutMetadata.Offsets) { TypeDesc fieldType = fieldAndOffset.Field.FieldType; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout, out bool fieldHasInt128Field); if (!fieldLayoutAbiStable) layoutAbiStable = false; if (fieldHasAutoLayout) hasAutoLayoutField = true; + if (fieldHasInt128Field) + hasInt128Field = true; largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); @@ -357,6 +361,7 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -392,17 +397,20 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType int packingSize = ComputePackingSize(type, layoutMetadata); bool layoutAbiStable = true; bool hasAutoLayoutField = false; + bool hasInt128Field = type.BaseType == null ? false : type.BaseType.IsInt128OrHasInt128Fields; foreach (var field in type.GetFields()) { if (field.IsStatic) continue; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType.UnderlyingType, hasLayout: true, packingSize, out bool fieldLayoutAbiStable, out bool fieldHasAutoLayout, out bool fieldHasInt128Field); if (!fieldLayoutAbiStable) layoutAbiStable = false; if (fieldHasAutoLayout) hasAutoLayoutField = true; + if (fieldHasInt128Field) + hasInt128Field = true; largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); @@ -424,6 +432,7 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -459,6 +468,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, int instanceValueClassFieldCount = 0; int instanceGCPointerFieldsCount = 0; int[] instanceNonGCPointerFieldsCount = new int[maxLog2Size + 1]; + bool hasInt128Field = false; foreach (var field in type.GetFields()) { @@ -469,7 +479,10 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, if (IsByValueClass(fieldType)) { + // Valuetypes which are not primitives or enums instanceValueClassFieldCount++; + if (((DefType)fieldType).IsInt128OrHasInt128Fields) + hasInt128Field = true; } else if (fieldType.IsGCPointer) { @@ -479,7 +492,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, { Debug.Assert(fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsFunctionPointer || fieldType.IsEnum || fieldType.IsByRef); - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool _, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool _, out bool _, out bool _); instanceNonGCPointerFieldsCount[CalculateLog2(fieldSizeAndAlignment.Size.AsInt)]++; } } @@ -516,36 +529,67 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, TypeDesc fieldType = field.FieldType; - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(fieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _, out bool _); if (!fieldLayoutAbiStable) layoutAbiStable = false; - largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); - if (IsByValueClass(fieldType)) { + // This block handles valuetypes which are not primitives or enums, it only has a meaningful effect, if the + // type has an alignment greater than pointer size. + largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); instanceValueClassFieldsArr[instanceValueClassFieldCount++] = field; } - else if (fieldType.IsGCPointer) - { - instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field; - } else { - int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt); - instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field; + // non-value-type (and primitive type) fields will add an alignment requirement of pointer size + // This alignment requirement will not be significant in the final alignment calculation unlesss the + // type is greater than the size of a single pointer. + // + // This does not account for types that are marked IsAlign8Candidate due to 8-byte fields + // but that is explicitly handled when we calculate the final alignment for the type. + + // This behavior is extremely strange for primitive types, as it makes a struct with a single byte in it + // have 8 byte alignment, but that is the current implementation. + + largestAlignmentRequired = LayoutInt.Max(new LayoutInt(context.Target.PointerSize), largestAlignmentRequired); + + if (fieldType.IsGCPointer) + { + instanceGCPointerFieldsArr[instanceGCPointerFieldsCount++] = field; + } + else + { + Debug.Assert(fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsFunctionPointer || fieldType.IsEnum || fieldType.IsByRef); + int log2size = CalculateLog2(fieldSizeAndAlignment.Size.AsInt); + instanceNonGCPointerFieldsArr[log2size][instanceNonGCPointerFieldsCount[log2size]++] = field; + + if (fieldType.IsPrimitive || fieldType.IsEnum) + { + // Handle alignment of long/ulong/double on ARM32 + largestAlignmentRequired = LayoutInt.Max(context.Target.GetObjectAlignment(fieldSizeAndAlignment.Size), largestAlignmentRequired); + } + } } } - largestAlignmentRequired = context.Target.GetObjectAlignment(largestAlignmentRequired); - bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && largestAlignmentRequired.AsInt > 4; + bool requiresAlign8 = !largestAlignmentRequired.IsIndeterminate && context.Target.PointerSize == 4 && context.Target.GetObjectAlignment(largestAlignmentRequired).AsInt > 4 && context.Target.PointerSize == 4; // For types inheriting from another type, field offsets continue on from where they left off // Base alignment is not always required, it's only applied when there's a version bubble boundary // between base type and the current type. LayoutInt cumulativeInstanceFieldPos = CalculateFieldBaseOffset(type, requiresAlign8, requiresAlignedBase: false); LayoutInt offsetBias = LayoutInt.Zero; - if (!type.IsValueType && cumulativeInstanceFieldPos != LayoutInt.Zero && type.Context.Target.Architecture == TargetArchitecture.X86) + + // The following conditional statement mimics the behavior of MethodTableBuilder::PlaceInstanceFields; + // the fundamental difference between CoreCLR native runtime and Crossgen2 regarding field placement is + // that the native runtime doesn't count the method table pointer at the beginning of reference types as a 'field' + // so that the first field in a class has offset 0 while its 'real' offset from the 'this' pointer is LayoutPointerSize. + // On ARM32, native runtime employs a special logic internally calculating the field offsets relative to the 'this' + // pointer (the Crossgen2 way) to ensure 8-alignment for longs and doubles as required by the ARM32 ISA. Please note + // that for 16-alignment used by Vector128 this logic actually ensures that the fields are 16-misaligned + // (they are 16-aligned after the 4-byte or 8-byte method table pointer). + if (!type.IsValueType && cumulativeInstanceFieldPos != LayoutInt.Zero && type.Context.Target.Architecture != TargetArchitecture.ARM) { offsetBias = type.Context.Target.LayoutPointerSize; cumulativeInstanceFieldPos -= offsetBias; @@ -655,7 +699,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, for (int i = 0; i < instanceValueClassFieldsArr.Length; i++) { // Align the cumulative field offset to the indeterminate value - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(instanceValueClassFieldsArr[i].FieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(instanceValueClassFieldsArr[i].FieldType, hasLayout, packingSize, out bool fieldLayoutAbiStable, out bool _, out bool _); if (!fieldLayoutAbiStable) layoutAbiStable = false; @@ -706,6 +750,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = true, + IsInt128OrHasInt128Fields = hasInt128Field, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -719,7 +764,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, private static void PlaceInstanceField(FieldDesc field, bool hasLayout, int packingSize, FieldAndOffset[] offsets, ref LayoutInt instanceFieldPos, ref int fieldOrdinal, LayoutInt offsetBias) { - var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType, hasLayout, packingSize, out bool _, out bool _); + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType, hasLayout, packingSize, out bool _, out bool _, out bool _); instanceFieldPos = AlignUpInstanceFieldOffset(field.OwningType, instanceFieldPos, fieldSizeAndAlignment.Alignment, field.Context.Target); offsets[fieldOrdinal] = new FieldAndOffset(field, instanceFieldPos + offsetBias); @@ -779,11 +824,12 @@ public LayoutInt CalculateFieldBaseOffset(MetadataType type, bool requiresAlign8 return cumulativeInstanceFieldPos; } - private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, bool hasLayout, int packingSize, out bool layoutAbiStable, out bool fieldTypeHasAutoLayout) + private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, bool hasLayout, int packingSize, out bool layoutAbiStable, out bool fieldTypeHasAutoLayout, out bool fieldTypeHasInt128Field) { SizeAndAlignment result; layoutAbiStable = true; fieldTypeHasAutoLayout = true; + fieldTypeHasInt128Field = false; if (fieldType.IsDefType) { @@ -794,6 +840,7 @@ private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, result.Alignment = defType.InstanceFieldAlignment; layoutAbiStable = defType.LayoutAbiStable; fieldTypeHasAutoLayout = defType.IsAutoLayoutOrHasAutoLayoutFields; + fieldTypeHasInt128Field = defType.IsInt128OrHasInt128Fields; } else { diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs index 658302d0faaf42..b70749272b0d38 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs @@ -434,6 +434,12 @@ internal static MarshallerKind GetMarshallerKind( return MarshallerKind.Invalid; } + if (!isField && InteropTypes.IsInt128Type(context, type)) + { + // Int128 types cannot be passed by value + return MarshallerKind.Invalid; + } + if (isBlittable) { if (nativeType != NativeTypeKind.Default && nativeType != NativeTypeKind.Struct) @@ -887,7 +893,7 @@ internal static MarshallerKind GetDisabledMarshallerKind( else if (underlyingType.IsValueType) { var defType = (DefType)underlyingType; - if (!defType.ContainsGCPointers && !defType.IsAutoLayoutOrHasAutoLayoutFields) + if (!defType.ContainsGCPointers && !defType.IsAutoLayoutOrHasAutoLayoutFields && !defType.IsInt128OrHasInt128Fields) { return MarshallerKind.BlittableValue; } diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs index 764e6e8bb8e570..b13711716b8179 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/Marshaller.cs @@ -1474,7 +1474,7 @@ private bool ShouldBePinned get { return MarshalDirection == MarshalDirection.Forward - && MarshallerType != MarshallerType.Field + && MarshallerType == MarshallerType.Argument && !IsManagedByRef && In && !Out; @@ -1672,7 +1672,11 @@ protected override void TransformManagedToNative(ILCodeStream codeStream) { ILEmitter emitter = _ilCodeStreams.Emitter; - if (In && !Out && !IsManagedByRef) + if (MarshalDirection == MarshalDirection.Forward + && MarshallerType == MarshallerType.Argument + && !IsManagedByRef + && In + && !Out) { TypeDesc marshallerIn = MarshallerIn; diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs b/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs index f69879a10ee15f..9b21bf91c26496 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/InteropTypes.cs @@ -137,6 +137,11 @@ public static bool IsSystemRuntimeIntrinsicsVector64T(TypeSystemContext context, return IsCoreNamedType(context, type, "System.Runtime.Intrinsics", "Vector64`1"); } + public static bool IsInt128Type(TypeSystemContext context, TypeDesc type) + { + return type is DefType defType && defType.IsInt128OrHasInt128Fields; + } + public static bool IsSystemRuntimeIntrinsicsVector128T(TypeSystemContext context, TypeDesc type) { return IsCoreNamedType(context, type, "System.Runtime.Intrinsics", "Vector128`1"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs index c41d5fa11d7a31..90584110173b88 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs @@ -36,6 +36,19 @@ internal static bool IsStateMachineType(string typeName) return typeName.Length > i + 1 && typeName[i + 1] == 'd'; } + internal static bool IsStateMachineCurrentField(string fieldName) + { + if (!IsGeneratedMemberName(fieldName)) + return false; + + int i = fieldName.LastIndexOf('>'); + if (i == -1) + return false; + + // Current field is <>2__current + return fieldName.Length > i + 1 && fieldName[i + 1] == '2'; + } + internal static bool IsGeneratedType(string name) => IsStateMachineType(name) || IsLambdaDisplayClass(name); internal static bool IsLambdaOrLocalFunction(string methodName) => IsLambdaMethod(methodName) || IsLocalFunction(methodName); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 6b6c6e36cd6674..c62233bd957a9c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -500,10 +500,17 @@ static IEnumerable GetCompilerGeneratedNestedTypes(MetadataType ty public static bool IsHoistedLocal(FieldDesc field) { - // Treat all fields on compiler-generated types as hoisted locals. - // This avoids depending on the name mangling scheme for hoisted locals. - var declaringTypeName = field.OwningType.Name; - return CompilerGeneratedNames.IsLambdaDisplayClass(declaringTypeName) || CompilerGeneratedNames.IsStateMachineType(declaringTypeName); + if (CompilerGeneratedNames.IsLambdaDisplayClass(field.OwningType.Name)) + return true; + + if (CompilerGeneratedNames.IsStateMachineType(field.OwningType.Name)) + { + // Don't track the "current" field which is used for state machine return values, + // because this can be expensive to track. + return !CompilerGeneratedNames.IsStateMachineCurrentField(field.Name); + } + + return false; } // "Nested function" refers to lambdas and local functions. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs index 46e6c9764782a5..afdb716f5af6ae 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs @@ -34,6 +34,19 @@ internal static bool IsStateMachineType (string typeName) return typeName.Length > i + 1 && typeName[i + 1] == 'd'; } + internal static bool IsStateMachineCurrentField (string fieldName) + { + if (!IsGeneratedMemberName (fieldName)) + return false; + + int i = fieldName.LastIndexOf ('>'); + if (i == -1) + return false; + + // Current field is <>2__current + return fieldName.Length > i + 1 && fieldName[i + 1] == '2'; + } + internal static bool IsGeneratedType (string name) => IsStateMachineType (name) || IsLambdaDisplayClass (name); internal static bool IsLambdaOrLocalFunction (string methodName) => IsLambdaMethod (methodName) || IsLocalFunction (methodName); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs index 3213f49fdc4931..3033cef70153c0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs @@ -55,10 +55,16 @@ static IEnumerable GetCompilerGeneratedNestedTypes (TypeDefiniti public static bool IsHoistedLocal (FieldDefinition field) { - // Treat all fields on compiler-generated types as hoisted locals. - // This avoids depending on the name mangling scheme for hoisted locals. - var declaringTypeName = field.DeclaringType.Name; - return CompilerGeneratedNames.IsLambdaDisplayClass (declaringTypeName) || CompilerGeneratedNames.IsStateMachineType (declaringTypeName); + if (CompilerGeneratedNames.IsLambdaDisplayClass (field.DeclaringType.Name)) + return true; + + if (CompilerGeneratedNames.IsStateMachineType (field.DeclaringType.Name)) { + // Don't track the "current" field which is used for state machine return values, + // because this can be expensive to track. + return !CompilerGeneratedNames.IsStateMachineCurrentField (field.Name); + } + + return false; } // "Nested function" refers to lambdas and local functions. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 0482731b0432b3..fac33d4b58cbcf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -26,6 +26,7 @@ public class Logger private readonly CompilerGeneratedState _compilerGeneratedState; private readonly HashSet _suppressedWarnings; + private readonly HashSet _suppressedCategories; private readonly bool _isSingleWarn; private readonly HashSet _singleWarnEnabledAssemblies; @@ -44,7 +45,8 @@ public Logger( IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, - IEnumerable singleWarnDisabledModules) + IEnumerable singleWarnDisabledModules, + IEnumerable suppressedCategories) { _logWriter = writer; _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this); @@ -53,15 +55,16 @@ public Logger( _isSingleWarn = singleWarn; _singleWarnEnabledAssemblies = new HashSet(singleWarnEnabledModules, StringComparer.OrdinalIgnoreCase); _singleWarnDisabledAssemblies = new HashSet(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase); + _suppressedCategories = new HashSet(suppressedCategories, StringComparer.Ordinal); } - public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) - : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules, IEnumerable suppressedCategories) + : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules, suppressedCategories) { } public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose) - : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) + : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty(), Array.Empty()) { } @@ -141,6 +144,8 @@ public void LogError(string text, int code, TypeSystemEntity origin, string subc public void LogError(TypeSystemEntity origin, DiagnosticId id, params string[] args) => LogError(new MessageOrigin(origin), id, args); + internal bool IsWarningSubcategorySuppressed(string category) => _suppressedCategories.Contains(category); + internal bool IsWarningSuppressed(int code, MessageOrigin origin) { // This is causing too much noise diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs index 25d849a46dee05..8f6e4f33efd2fd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageContainer.cs @@ -125,6 +125,9 @@ internal static MessageContainer CreateErrorMessage(MessageOrigin? origin, Diagn if (context.IsWarningSuppressed(code, origin)) return null; + if (context.IsWarningSubcategorySuppressed(subcategory)) + return null; + if (TryLogSingleWarning(context, code, origin, subcategory)) return null; @@ -139,6 +142,9 @@ internal static MessageContainer CreateErrorMessage(MessageOrigin? origin, Diagn if (context.IsWarningSuppressed((int)id, origin)) return null; + if (context.IsWarningSubcategorySuppressed(subcategory)) + return null; + if (TryLogSingleWarning(context, (int)id, origin, subcategory)) return null; diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs index 067486f858c168..f9fe3b7687439d 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs @@ -19,9 +19,16 @@ public class ArchitectureSpecificFieldLayoutTests ModuleDesc _testModuleX86; TestTypeSystemContext _contextX64; ModuleDesc _testModuleX64; + TestTypeSystemContext _contextX64Windows; + ModuleDesc _testModuleX64Windows; + TestTypeSystemContext _contextX64Linux; + ModuleDesc _testModuleX64Linux; TestTypeSystemContext _contextARM; ModuleDesc _testModuleARM; + TestTypeSystemContext _contextARM64; + ModuleDesc _testModuleARM64; + public ArchitectureSpecificFieldLayoutTests() { _contextX64 = new TestTypeSystemContext(TargetArchitecture.X64); @@ -30,6 +37,18 @@ public ArchitectureSpecificFieldLayoutTests() _testModuleX64 = systemModuleX64; + _contextX64Linux = new TestTypeSystemContext(TargetArchitecture.X64, TargetOS.Linux); + var systemModuleX64Linux = _contextX64Linux.CreateModuleForSimpleName("CoreTestAssembly"); + _contextX64Linux.SetSystemModule(systemModuleX64Linux); + + _testModuleX64Linux = systemModuleX64Linux; + + _contextX64Windows = new TestTypeSystemContext(TargetArchitecture.X64, TargetOS.Windows); + var systemModuleX64Windows = _contextX64Windows.CreateModuleForSimpleName("CoreTestAssembly"); + _contextX64Windows.SetSystemModule(systemModuleX64Windows); + + _testModuleX64Windows = systemModuleX64Windows; + _contextARM = new TestTypeSystemContext(TargetArchitecture.ARM); var systemModuleARM = _contextARM.CreateModuleForSimpleName("CoreTestAssembly"); _contextARM.SetSystemModule(systemModuleARM); @@ -41,6 +60,12 @@ public ArchitectureSpecificFieldLayoutTests() _contextX86.SetSystemModule(systemModuleX86); _testModuleX86 = systemModuleX86; + + _contextARM64 = new TestTypeSystemContext(TargetArchitecture.ARM64); + var systemModuleARM64 = _contextARM64.CreateModuleForSimpleName("CoreTestAssembly"); + _contextARM64.SetSystemModule(systemModuleARM64); + + _testModuleARM64 = systemModuleARM64; } [Fact] @@ -414,5 +439,116 @@ public void TestAlignmentBehavior_ShortByteEnumStructAuto() Assert.Equal(0x4, tX86FieldStruct.GetField("_struct").Offset.AsInt); Assert.Equal(0x4, tARMFieldStruct.GetField("_struct").Offset.AsInt); } + + [Fact] + public void TestAlignmentBehavior_StructStructByte_StructByteAuto() + { + string _namespace = "Sequential"; + string _type = "StructStructByte_StructByteAuto"; + + MetadataType tX64 = _testModuleX64.GetType(_namespace, _type); + MetadataType tX86 = _testModuleX86.GetType(_namespace, _type); + MetadataType tARM = _testModuleARM.GetType(_namespace, _type); + + Assert.Equal(0x1, tX64.InstanceFieldAlignment.AsInt); + Assert.Equal(0x1, tARM.InstanceFieldAlignment.AsInt); + Assert.Equal(0x1, tX86.InstanceFieldAlignment.AsInt); + + Assert.Equal(0x2, tX64.InstanceFieldSize.AsInt); + Assert.Equal(0x2, tARM.InstanceFieldSize.AsInt); + Assert.Equal(0x2, tX86.InstanceFieldSize.AsInt); + + Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt); + + Assert.Equal(0x1, tX64.GetField("fld2").Offset.AsInt); + Assert.Equal(0x1, tARM.GetField("fld2").Offset.AsInt); + Assert.Equal(0x1, tX86.GetField("fld2").Offset.AsInt); + } + + [Theory] + [InlineData("StructStructByte_StructByteAuto", new int[]{1,1,1}, new int[]{2,2,2})] + [InlineData("StructStructByte_Struct2BytesAuto", new int[]{2,2,2}, new int[]{4,4,4})] + [InlineData("StructStructByte_Struct3BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})] + [InlineData("StructStructByte_Struct4BytesAuto", new int[]{4,4,4}, new int[]{8,8,8})] + [InlineData("StructStructByte_Struct5BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})] + [InlineData("StructStructByte_Struct8BytesAuto", new int[]{8,4,4}, new int[]{16,12,12})] + [InlineData("StructStructByte_Struct9BytesAuto", new int[]{8,4,4}, new int[]{24,16,16})] + public void TestAlignmentBehavior_AutoAlignmentRules(string wrapperType, int[] alignment, int[] size) + { + string _namespace = "Sequential"; + string _type = wrapperType; + + MetadataType tX64 = _testModuleX64.GetType(_namespace, _type); + MetadataType tX86 = _testModuleX86.GetType(_namespace, _type); + MetadataType tARM = _testModuleARM.GetType(_namespace, _type); + + Assert.Equal(alignment[0], tX64.InstanceFieldAlignment.AsInt); + Assert.Equal(alignment[1], tARM.InstanceFieldAlignment.AsInt); + Assert.Equal(alignment[2], tX86.InstanceFieldAlignment.AsInt); + + Assert.Equal(size[0], tX64.InstanceFieldSize.AsInt); + Assert.Equal(size[1], tARM.InstanceFieldSize.AsInt); + Assert.Equal(size[2], tX86.InstanceFieldSize.AsInt); + + Assert.Equal(0x0, tX64.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tARM.GetField("fld1").Offset.AsInt); + Assert.Equal(0x0, tX86.GetField("fld1").Offset.AsInt); + + Assert.Equal(alignment[0], tX64.GetField("fld2").Offset.AsInt); + Assert.Equal(alignment[1], tARM.GetField("fld2").Offset.AsInt); + Assert.Equal(alignment[2], tX86.GetField("fld2").Offset.AsInt); + } + + [Theory] + [InlineData("StructStructByte_Int128StructAuto", "ARM64", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "ARM", 8, 24)] + [InlineData("StructStructByte_Int128StructAuto", "X86", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "X64Linux", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "X64Windows", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "ARM64", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "ARM", 8, 24)] + [InlineData("StructStructByte_UInt128StructAuto", "X86", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "X64Linux", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "X64Windows", 16, 32)] + // Variation of TestAlignmentBehavior_AutoAlignmentRules above that is able to deal with os specific behavior + public void TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence(string wrapperType, string osArch, int alignment, int size) + { + ModuleDesc testModule; + switch (osArch) + { + case "ARM64": + testModule = _testModuleARM64; + break; + case "ARM": + testModule = _testModuleARM; + break; + case "X64": + testModule = _testModuleX64; + break; + case "X64Linux": + testModule = _testModuleX64Linux; + break; + case "X64Windows": + testModule = _testModuleX64Windows; + break; + case "X86": + testModule = _testModuleX86; + break; + default: + throw new Exception(); + } + + string _namespace = "Sequential"; + string _type = wrapperType; + + MetadataType type = testModule.GetType(_namespace, _type); + + Assert.Equal(alignment, type.InstanceFieldAlignment.AsInt); + Assert.Equal(size, type.InstanceFieldSize.AsInt); + Assert.Equal(0x0, type.GetField("fld1").Offset.AsInt); + Assert.Equal(alignment, type.GetField("fld2").Offset.AsInt); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs index b49dd4ff729309..249090e38eae51 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs @@ -3,56 +3,57 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; #pragma warning disable 169 namespace ContainsGCPointers { - struct NoPointers + public struct NoPointers { - int int1; - byte byte1; - char char1; + public int int1; + public byte byte1; + public char char1; } - struct StillNoPointers + public struct StillNoPointers { - NoPointers noPointers1; - bool bool1; + public NoPointers noPointers1; + public bool bool1; } - class ClassNoPointers + public class ClassNoPointers { - char char1; + public char char1; } - struct HasPointers + public struct HasPointers { - string string1; + public string string1; } - struct FieldHasPointers + public struct FieldHasPointers { - HasPointers hasPointers1; + public HasPointers hasPointers1; } - class ClassHasPointers + public class ClassHasPointers { - ClassHasPointers classHasPointers1; + public ClassHasPointers classHasPointers1; } - class BaseClassHasPointers : ClassHasPointers + public class BaseClassHasPointers : ClassHasPointers { } public class ClassHasIntArray { - int[] intArrayField; + public int[] intArrayField; } public class ClassHasArrayOfClassType { - ClassNoPointers[] classTypeArray; + public ClassNoPointers[] classTypeArray; } } @@ -61,29 +62,29 @@ namespace Explicit [StructLayout(LayoutKind.Explicit)] class Class1 { - static int Stat; + public static int Stat; [FieldOffset(4)] - bool Bar; + public bool Bar; [FieldOffset(10)] - char Baz; + public char Baz; } [StructLayout(LayoutKind.Explicit)] class Class2 : Class1 { [FieldOffset(0)] - int Lol; + public int Lol; [FieldOffset(20)] - byte Omg; + public byte Omg; } [StructLayout(LayoutKind.Explicit, Size = 40)] class ExplicitSize { [FieldOffset(0)] - int Lol; + public int Lol; [FieldOffset(20)] - byte Omg; + public byte Omg; } [StructLayout(LayoutKind.Explicit)] @@ -123,194 +124,356 @@ ref struct ByRefStruct namespace Sequential { [StructLayout(LayoutKind.Sequential)] - class Class1 + public class Class1 { - int MyInt; - bool MyBool; - char MyChar; - string MyString; - byte[] MyByteArray; - Class1 MyClass1SelfRef; + public int MyInt; + public bool MyBool; + public char MyChar; + public string MyString; + public byte[] MyByteArray; + public Class1 MyClass1SelfRef; } [StructLayout(LayoutKind.Sequential)] - class Class2 : Class1 + public class Class2 : Class1 { - int MyInt2; + public int MyInt2; } // [StructLayout(LayoutKind.Sequential)] is applied by default by the C# compiler - struct Struct0 + public struct Struct0 { - bool b1; - bool b2; - bool b3; - int i1; - string s1; + public bool b1; + public bool b2; + public bool b3; + public int i1; + public string s1; } // [StructLayout(LayoutKind.Sequential)] is applied by default by the C# compiler - struct Struct1 + public struct Struct1 { - Struct0 MyStruct0; - bool MyBool; + public Struct0 MyStruct0; + public bool MyBool; } [StructLayout(LayoutKind.Sequential)] public class ClassDoubleBool { - double double1; - bool bool1; + public double double1; + public bool bool1; } [StructLayout(LayoutKind.Sequential)] public class ClassBoolDoubleBool { - bool bool1; - double double1; - bool bool2; + public bool bool1; + public double double1; + public bool bool2; + } + + public struct StructByte + { + public byte fld1; + } + + public struct StructStructByte_StructByteAuto + { + public StructByte fld1; + public Auto.StructByte fld2; + } + public struct StructStructByte_Struct2BytesAuto + { + public StructByte fld1; + public Auto.Struct2Bytes fld2; + } + public struct StructStructByte_Struct3BytesAuto + { + public StructByte fld1; + public Auto.Struct3Bytes fld2; + } + public struct StructStructByte_Struct4BytesAuto + { + public StructByte fld1; + public Auto.Struct4Bytes fld2; + } + public struct StructStructByte_Struct5BytesAuto + { + public StructByte fld1; + public Auto.Struct5Bytes fld2; + } + public struct StructStructByte_Struct8BytesAuto + { + public StructByte fld1; + public Auto.Struct8Bytes fld2; + } + public struct StructStructByte_Struct9BytesAuto + { + public StructByte fld1; + public Auto.Struct9Bytes fld2; + } + + public struct StructStructByte_Int128StructAuto + { + public StructByte fld1; + public Auto.Int128Struct fld2; + } + + public struct StructStructByte_UInt128StructAuto + { + public StructByte fld1; + public Auto.UInt128Struct fld2; + } + + [StructLayout(LayoutKind.Sequential)] + public class Class16Align + { + Vector128 vector16Align; } } namespace Auto { [StructLayout(LayoutKind.Auto)] - struct StructWithBool + public struct StructWithBool { - bool MyStructBool; + public bool MyStructBool; } [StructLayout(LayoutKind.Auto)] - struct StructWithIntChar + public struct StructWithIntChar { - char MyStructChar; - int MyStructInt; + public char MyStructChar; + public int MyStructInt; } [StructLayout(LayoutKind.Auto)] - struct StructWithChar + public struct StructWithChar { - char MyStructChar; + public char MyStructChar; } - class ClassContainingStructs + public class ClassContainingStructs { - static int MyStaticInt; + public static int MyStaticInt; - StructWithBool MyStructWithBool; - bool MyBool1; - char MyChar1; - int MyInt; - double MyDouble; - long MyLong; - byte[] MyByteArray; - string MyString1; - bool MyBool2; - StructWithIntChar MyStructWithIntChar; - StructWithChar MyStructWithChar; + public StructWithBool MyStructWithBool; + public bool MyBool1; + public char MyChar1; + public int MyInt; + public double MyDouble; + public long MyLong; + public byte[] MyByteArray; + public string MyString1; + public bool MyBool2; + public StructWithIntChar MyStructWithIntChar; + public StructWithChar MyStructWithChar; } - class BaseClass7BytesRemaining + public class BaseClass7BytesRemaining { - bool MyBool1; - double MyDouble1; - long MyLong1; - byte[] MyByteArray1; - string MyString1; + public bool MyBool1; + public double MyDouble1; + public long MyLong1; + public byte[] MyByteArray1; + public string MyString1; } - class BaseClass4BytesRemaining + public class BaseClass4BytesRemaining { - long MyLong1; - uint MyUint1; + public long MyLong1; + public uint MyUint1; } - class BaseClass3BytesRemaining + public class BaseClass3BytesRemaining { - int MyInt1; - string MyString1; - bool MyBool1; + public int MyInt1; + public string MyString1; + public bool MyBool1; } - class OptimizePartial : BaseClass7BytesRemaining + public class OptimizePartial : BaseClass7BytesRemaining { - bool OptBool; - char OptChar; - long NoOptLong; - string NoOptString; + public bool OptBool; + public char OptChar; + public long NoOptLong; + public string NoOptString; } - class Optimize7Bools : BaseClass7BytesRemaining + public class Optimize7Bools : BaseClass7BytesRemaining { - bool OptBool1; - bool OptBool2; - bool OptBool3; - bool OptBool4; - bool OptBool5; - bool OptBool6; - bool OptBool7; - bool NoOptBool8; - string NoOptString; + public bool OptBool1; + public bool OptBool2; + public bool OptBool3; + public bool OptBool4; + public bool OptBool5; + public bool OptBool6; + public bool OptBool7; + public bool NoOptBool8; + public string NoOptString; } - class OptimizeAlignedFields : BaseClass7BytesRemaining + public class OptimizeAlignedFields : BaseClass7BytesRemaining { - bool OptBool1; - bool OptBool2; - bool OptBool3; - bool NoOptBool4; - char OptChar1; - char OptChar2; - string NoOptString; + public bool OptBool1; + public bool OptBool2; + public bool OptBool3; + public bool NoOptBool4; + public char OptChar1; + public char OptChar2; + public string NoOptString; } - class OptimizeLargestField : BaseClass4BytesRemaining + public class OptimizeLargestField : BaseClass4BytesRemaining { - bool NoOptBool; - char NoOptChar; - int OptInt; - string NoOptString; + public bool NoOptBool; + public char NoOptChar; + public int OptInt; + public string NoOptString; } - class NoOptimizeMisaligned : BaseClass3BytesRemaining + public class NoOptimizeMisaligned : BaseClass3BytesRemaining { - char NoOptChar; - int NoOptInt; - string NoOptString; + public char NoOptChar; + public int NoOptInt; + public string NoOptString; } - class NoOptimizeCharAtSize2Alignment : BaseClass3BytesRemaining + public class NoOptimizeCharAtSize2Alignment : BaseClass3BytesRemaining { - char NoOptChar; + public char NoOptChar; } [StructLayout(LayoutKind.Auto, Pack = 1)] - struct MinPacking + public struct MinPacking { public byte _byte; public T _value; } + + [StructLayout(LayoutKind.Auto)] + public struct int8x16x2 + { + public Vector128 _0; + public Vector128 _1; + } + + public struct Wrapper_int8x16x2 + { + public int8x16x2 fld; + } + + public struct Wrapper_int8x16x2_2 + { + public bool fld1; + public int8x16x2 fld2; + } + + [StructLayout(LayoutKind.Auto)] + public struct StructByte + { + public byte fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct2Bytes + { + public byte fld1; + public byte fld2; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct3Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct4Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct5Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct8Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + public byte fld6; + public byte fld7; + public byte fld8; + } + + [StructLayout(LayoutKind.Auto)] + public struct Struct9Bytes + { + public byte fld1; + public byte fld2; + public byte fld3; + public byte fld4; + public byte fld5; + public byte fld6; + public byte fld7; + public byte fld8; + public byte fld9; + } + + [StructLayout(LayoutKind.Auto)] + public struct UInt128Struct + { + UInt128 fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Int128Struct + { + Int128 fld1; + } + + [StructLayout(LayoutKind.Sequential)] + public class Class16Align + { + Vector128 vector16Align; + } } namespace IsByRefLike { public ref struct ByRefLikeStruct { - ref object ByRef; + public ref object ByRef; } public struct NotByRefLike { - int X; + public int X; } } namespace EnumAlignment { - public enum ByteEnum : byte {} - public enum ShortEnum : short {} - public enum IntEnum : int {} - public enum LongEnum : long {} + public enum ByteEnum : byte { Val } + public enum ShortEnum : short { Val } + public enum IntEnum : int { Val } + public enum LongEnum : long { Val } public struct LongIntEnumStruct { diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs index 2850c56a10f3c9..2239925645c77c 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs @@ -1,5 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #pragma warning disable 649 #pragma warning disable 169 @@ -59,6 +61,14 @@ public struct RuntimeMethodHandle { } public struct RuntimeFieldHandle { } public class Attribute { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets targets) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + + public enum AttributeTargets { } public class ThreadStaticAttribute : Attribute { } @@ -71,6 +81,24 @@ public ref struct TypedReference private readonly ref byte _value; private readonly RuntimeTypeHandle _typeHandle; } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Int128 + { + + private readonly ulong _lower; + private readonly ulong _upper; + } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct UInt128 + { + + private readonly ulong _lower; + private readonly ulong _upper; + } } namespace System.Collections @@ -136,4 +164,22 @@ public static class RuntimeFeature public const string ByRefFields = nameof(ByRefFields); public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces); } + + internal sealed class IntrinsicAttribute : Attribute + { + } +} + +namespace System.Runtime.Intrinsics +{ + [Intrinsic] + [StructLayout(LayoutKind.Sequential, Size = 16)] + public readonly struct Vector128 + where T : struct + { + // These fields exist to ensure the alignment is 8, rather than 1. + // This also allows the debug view to work https://github.com/dotnet/runtime/issues/9495) + private readonly ulong _00; + private readonly ulong _01; + } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj index a9e0faf8ac8177..1f6b33ff18ba39 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj @@ -46,6 +46,8 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs index 0a60a72a4a640b..083a4e65af3844 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/InstanceFieldLayoutTests.cs @@ -281,6 +281,28 @@ public void TestSequentialTypeLayoutStructEmbedded() } } + [Fact] + public void TestSequentialTypeLayoutClass16Align() + { + MetadataType classType = _testModule.GetType("Sequential", "Class16Align"); + Assert.Equal(0x18, classType.InstanceByteCount.AsInt); + foreach (var f in classType.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "vector16Align": + Assert.Equal(0x8, f.Offset.AsInt); + break; + default: + Assert.True(false); + break; + } + } + } + [Fact] public void TestAutoLayoutStruct() { @@ -782,6 +804,28 @@ public void TestAutoTypeLayoutMinPacking(WellKnownType type, int expectedSize) Assert.Equal(expectedSize, inst.InstanceFieldSize.AsInt); } + [Fact] + public void TestAutoTypeLayoutClass16Align() + { + MetadataType classType = _testModule.GetType("Auto", "Class16Align"); + Assert.Equal(0x18, classType.InstanceByteCount.AsInt); + foreach (var f in classType.GetFields()) + { + if (f.IsStatic) + continue; + + switch (f.Name) + { + case "vector16Align": + Assert.Equal(0x8, f.Offset.AsInt); + break; + default: + Assert.True(false); + break; + } + } + } + [Fact] public void TestTypeContainsGCPointers() { @@ -850,5 +894,25 @@ public void TestInvalidByRefLikeTypes() Assert.Throws(() => type.ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields)); } } + + [Fact] + public void TestWrapperAroundVectorTypes() + { + { + MetadataType type = (MetadataType)_testModule.GetType("System.Runtime.Intrinsics", "Vector128`1"); + MetadataType instantiatedType = type.MakeInstantiatedType(_context.GetWellKnownType(WellKnownType.Byte)); + Assert.Equal(16, instantiatedType.InstanceFieldAlignment.AsInt); + } + + { + DefType type = _testModule.GetType("Auto", "int8x16x2"); + Assert.Equal(16, type.InstanceFieldAlignment.AsInt); + } + + { + DefType type = _testModule.GetType("Auto", "Wrapper_int8x16x2"); + Assert.Equal(16, type.InstanceFieldAlignment.AsInt); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs index 0fbd0b6a35329e..3f963c24104eb1 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; +using ILCompiler; using Internal.TypeSystem; using System.Reflection; using System.Reflection.PortableExecutable; @@ -22,6 +23,9 @@ class TestTypeSystemContext : MetadataTypeSystemContext { Dictionary _modules = new Dictionary(StringComparer.OrdinalIgnoreCase); + private VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm; + private Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm; + MetadataFieldLayoutAlgorithm _metadataFieldLayout = new TestMetadataFieldLayoutAlgorithm(); MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm(); ArrayOfTRuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm; @@ -29,9 +33,11 @@ class TestTypeSystemContext : MetadataTypeSystemContext public CanonicalizationMode CanonMode { get; set; } = CanonicalizationMode.RuntimeDetermined; - public TestTypeSystemContext(TargetArchitecture arch) - : base(new TargetDetails(arch, TargetOS.Unknown, TargetAbi.Unknown)) + public TestTypeSystemContext(TargetArchitecture arch, TargetOS targetOS = TargetOS.Unknown) + : base(new TargetDetails(arch, targetOS, TargetAbi.Unknown)) { + _vectorFieldLayoutAlgorithm = new VectorFieldLayoutAlgorithm(_metadataFieldLayout, true); + _int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_metadataFieldLayout); } public ModuleDesc GetModuleForSimpleName(string simpleName) @@ -67,6 +73,14 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type) { if (type == UniversalCanonType) return UniversalCanonLayoutAlgorithm.Instance; + else if (VectorFieldLayoutAlgorithm.IsVectorType(type)) + { + return _vectorFieldLayoutAlgorithm; + } + else if (Int128FieldLayoutAlgorithm.IsIntegerType(type)) + { + return _int128FieldLayoutAlgorithm; + } return _metadataFieldLayout; } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs index 541361160f2b58..6b81314b9f9765 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/UniversalGenericFieldLayoutTests.cs @@ -372,6 +372,7 @@ private void CommonClassLayoutTestBits(ModuleDesc testModule, AssertClassIndeterminateSize(context, genOfUL, expectedIndeterminateByteAlignment); } + /* This test exercises universal shared generic layout that is currently unsupported and known to be buggy. [Fact] public void TestClassLayout() { @@ -511,5 +512,6 @@ public void TestClassLayout() Assert.Equal(LayoutInt.Indeterminate, genOfUI.GetFields().First().Offset); Assert.Equal(LayoutInt.Indeterminate, genOfUL.GetFields().First().Offset); } + */ } } diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index f33053fc898f17..f3c064835d8bff 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -15,9 +15,11 @@ using Internal.CommandLine; +using ILCompiler.Dataflow; +using ILLink.Shared; + using Debug = System.Diagnostics.Debug; using InstructionSet = Internal.JitInterface.InstructionSet; -using ILCompiler.Dataflow; namespace ILCompiler { @@ -103,6 +105,8 @@ internal class Program private IReadOnlyList _singleWarnEnabledAssemblies = Array.Empty(); private IReadOnlyList _singleWarnDisabledAssemblies = Array.Empty(); private bool _singleWarn; + private bool _noTrimWarn; + private bool _noAotWarn; private string _makeReproPath; @@ -228,6 +232,8 @@ private ArgumentSyntax ParseCommandLine(string[] args) syntax.DefineOption("nopreinitstatics", ref _noPreinitStatics, "Do not interpret static constructors at compile time"); syntax.DefineOptionList("nowarn", ref _suppressedWarnings, "Disable specific warning messages"); syntax.DefineOption("singlewarn", ref _singleWarn, "Generate single AOT/trimming warning per assembly"); + syntax.DefineOption("notrimwarn", ref _noTrimWarn, "Disable warnings related to trimming"); + syntax.DefineOption("noaotwarn", ref _noAotWarn, "Disable warnings related to AOT"); syntax.DefineOptionList("singlewarnassembly", ref _singleWarnEnabledAssemblies, "Generate single AOT/trimming warning for given assembly"); syntax.DefineOptionList("nosinglewarnassembly", ref _singleWarnDisabledAssemblies, "Expand AOT/trimming warnings for given assembly"); syntax.DefineOptionList("directpinvoke", ref _directPInvokes, "PInvoke to call directly"); @@ -768,7 +774,13 @@ static string ILLinkify(string rootedAssembly) } ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); - var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + var suppressedWarningCategories = new List(); + if (_noTrimWarn) + suppressedWarningCategories.Add(MessageSubCategory.TrimAnalysis); + if (_noAotWarn) + suppressedWarningCategories.Add(MessageSubCategory.AotAnalysis); + + var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies, suppressedWarningCategories); CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); var stackTracePolicy = _emitStackTraceData ? diff --git a/src/coreclr/tools/aot/crossgen2/crossgen2.csproj b/src/coreclr/tools/aot/crossgen2/crossgen2.csproj index 8696352abc03e8..0d50278228a310 100644 --- a/src/coreclr/tools/aot/crossgen2/crossgen2.csproj +++ b/src/coreclr/tools/aot/crossgen2/crossgen2.csproj @@ -7,6 +7,10 @@ false + + + false + true linux-x64;linux-musl-x64;linux-arm;linux-musl-arm;linux-arm64;linux-musl-arm64;freebsd-x64;osx-x64;osx-arm64;win-x64;win-x86;win-arm64;win-arm diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index 503e2c42bcf1f1..ce147abc89be60 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -68,7 +68,7 @@ LWM(GetClassGClayout, DWORDLONG, Agnostic_GetClassGClayout) LWM(GetClassModuleIdForStatics, DWORDLONG, Agnostic_GetClassModuleIdForStatics) LWM(GetClassName, DWORDLONG, DWORD) LWM(GetClassNameFromMetadata, DLD, DD) -LWM(GetTypeInstantiationArgument, DWORDLONG, DWORDLONG) +LWM(GetTypeInstantiationArgument, DLD, DWORDLONG) LWM(GetClassNumInstanceFields, DWORDLONG, DWORD) LWM(GetClassSize, DWORDLONG, DWORD) LWM(GetHeapClassSize, DWORDLONG, DWORD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index e8a883ed2d606f..fa4441812314d3 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6449,27 +6449,33 @@ const char* MethodContext::repGetClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, } void MethodContext::recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, - CORINFO_CLASS_HANDLE result, - unsigned index) + unsigned index, + CORINFO_CLASS_HANDLE result) { if (GetTypeInstantiationArgument == nullptr) - GetTypeInstantiationArgument = new LightWeightMap(); + GetTypeInstantiationArgument = new LightWeightMap(); - DWORDLONG key = CastHandle(cls); + DLD key; + ZeroMemory(&key, sizeof(key)); + key.A = CastHandle(cls); + key.B = index; DWORDLONG value = CastHandle(result); GetTypeInstantiationArgument->Add(key, value); DEBUG_REC(dmpGetTypeInstantiationArgument(key, value)); } -void MethodContext::dmpGetTypeInstantiationArgument(DWORDLONG key, DWORDLONG value) +void MethodContext::dmpGetTypeInstantiationArgument(DLD key, DWORDLONG value) { - printf("GetTypeInstantiationArgument key - classNonNull-%llu, value NonNull-%llu", key, value); + printf("GetTypeInstantiationArgument key - classNonNull-%llu, index-%u, value NonNull-%llu", key.A, key.B, value); GetTypeInstantiationArgument->Unlock(); } CORINFO_CLASS_HANDLE MethodContext::repGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index) { CORINFO_CLASS_HANDLE result = nullptr; - DWORDLONG key = CastHandle(cls); + DLD key; + ZeroMemory(&key, sizeof(key)); + key.A = CastHandle(cls); + key.B = index; int itemIndex = -1; if (GetTypeInstantiationArgument != nullptr) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 3fc5247ef66c20..66a3b90e49a601 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -796,8 +796,8 @@ class MethodContext void dmpGetClassNameFromMetadata(DLD key, DD value); const char* repGetClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, const char** namespaceName); - void recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result, unsigned index); - void dmpGetTypeInstantiationArgument(DWORDLONG key, DWORDLONG value); + void recGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index, CORINFO_CLASS_HANDLE result); + void dmpGetTypeInstantiationArgument(DLD key, DWORDLONG value); CORINFO_CLASS_HANDLE repGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index); void recAppendClassName(int nBufLenIn, diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 5d146d2de2cd77..2943db7a5376f4 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -489,9 +489,9 @@ const char* interceptor_ICJI::getClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index) { mc->cr->AddCall("getTypeInstantiationArgument"); - CORINFO_CLASS_HANDLE temp = original_ICorJitInfo->getTypeInstantiationArgument(cls, index); - mc->recGetTypeInstantiationArgument(cls, temp, index); - return temp; + CORINFO_CLASS_HANDLE result = original_ICorJitInfo->getTypeInstantiationArgument(cls, index); + mc->recGetTypeInstantiationArgument(cls, index, result); + return result; } // Append a (possibly truncated) textual representation of the type `cls` to a preallocated buffer. diff --git a/src/coreclr/utilcode/executableallocator.cpp b/src/coreclr/utilcode/executableallocator.cpp index 9536b673f53a51..7872954adc485c 100644 --- a/src/coreclr/utilcode/executableallocator.cpp +++ b/src/coreclr/utilcode/executableallocator.cpp @@ -259,7 +259,7 @@ bool ExecutableAllocator::Initialize() return true; } -//#define ENABLE_CACHED_MAPPINGS +#define ENABLE_CACHED_MAPPINGS void ExecutableAllocator::UpdateCachedMapping(BlockRW* pBlock) { diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp index 5c55e6283c16b4..db6489026b2921 100644 --- a/src/coreclr/utilcode/loaderheap.cpp +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -341,6 +341,7 @@ RangeList::RangeListBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // code:LoaderHeap::UnlockedReservePages adds a range for the entire reserved region, instead // of updating the RangeList when pages are committed. But in that case, the committed region of // memory will be enumerated by the LoaderHeap anyway, so it's OK if this fails + EMEM_OUT(("MEM: RangeListBlock %p - %p\n", range->start, range->end)); DacEnumMemoryRegion(range->start, size, false); } } @@ -1933,8 +1934,6 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { WRAPPER_NO_CONTRACT; - DAC_ENUM_DTHIS(); - PTR_LoaderHeapBlock block = m_pFirstBlock; while (block.IsValid()) { @@ -1946,6 +1945,7 @@ void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) // but it seems wasteful (eg. makes each AppDomain objects 32 bytes larger on x64). TADDR addr = dac_cast(block->pVirtualAddress); TSIZE_T size = block->dwVirtualSize; + EMEM_OUT(("MEM: UnlockedLoaderHeap %p - %p\n", addr, addr + size)); DacEnumMemoryRegion(addr, size, false); block = block->pNext; diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 39a02a3ffdd639..1ca1fe8cad79c6 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -5086,26 +5086,7 @@ DomainLocalModule::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) } void -BaseDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) -{ - SUPPORTS_DAC; - if (enumThis) - { - // This is wrong. Don't do it. - // BaseDomain cannot be instantiated. - // The only thing this code can hope to accomplish is to potentially break - // memory enumeration walking through the derived class if we - // explicitly call the base class enum first. -// DAC_ENUM_VTHIS(); - } - - EMEM_OUT(("MEM: %p BaseDomain\n", dac_cast(this))); -} - -void -AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) +AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) { SUPPORTS_DAC; @@ -5113,8 +5094,8 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, { //sizeof(AppDomain) == 0xeb0 DAC_ENUM_VTHIS(); + EMEM_OUT(("MEM: %p AppDomain\n", dac_cast(this))); } - BaseDomain::EnumMemoryRegions(flags, false); // We don't need AppDomain name in triage dumps. if (flags != CLRDATA_ENUM_MEM_TRIAGE) @@ -5122,6 +5103,11 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, m_friendlyName.EnumMemoryRegions(flags); } + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } + m_Assemblies.EnumMemoryRegions(flags); AssemblyIterator assem = IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; @@ -5133,16 +5119,19 @@ AppDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, } void -SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis) +SystemDomain::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) { SUPPORTS_DAC; if (enumThis) { DAC_ENUM_VTHIS(); + EMEM_OUT(("MEM: %p SystemAppomain\n", dac_cast(this))); } - BaseDomain::EnumMemoryRegions(flags, false); + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } if (m_pSystemPEAssembly.IsValid()) { m_pSystemPEAssembly->EnumMemoryRegions(flags); diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 5798301810d2d0..e25b2d56cd6573 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -1260,8 +1260,7 @@ class BaseDomain #ifdef DACCESS_COMPILE public: - virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, - bool enumThis); + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags, bool enumThis) = 0; #endif }; // class BaseDomain diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 97f1d26d1c281e..9bb246a62f49e8 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -2132,21 +2132,28 @@ Assembly::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) DAC_ENUM_DTHIS(); EMEM_OUT(("MEM: %p Assembly\n", dac_cast(this))); - if (m_pDomain.IsValid()) + if (flags == CLRDATA_ENUM_MEM_HEAP2) { - m_pDomain->EnumMemoryRegions(flags, true); + GetLoaderAllocator()->EnumMemoryRegions(flags); } - if (m_pClassLoader.IsValid()) - { - m_pClassLoader->EnumMemoryRegions(flags); - } - if (m_pModule.IsValid()) - { - m_pModule->EnumMemoryRegions(flags, true); - } - if (m_pPEAssembly.IsValid()) + else { - m_pPEAssembly->EnumMemoryRegions(flags); + if (m_pDomain.IsValid()) + { + m_pDomain->EnumMemoryRegions(flags, true); + } + if (m_pClassLoader.IsValid()) + { + m_pClassLoader->EnumMemoryRegions(flags); + } + if (m_pModule.IsValid()) + { + m_pModule->EnumMemoryRegions(flags, true); + } + if (m_pPEAssembly.IsValid()) + { + m_pPEAssembly->EnumMemoryRegions(flags); + } } } diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 04e65cbfbdbe1c..e6a1f86114c4e8 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -5123,7 +5123,6 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, { m_ModuleID->EnumMemoryRegions(flags); } - if (m_pPEAssembly.IsValid()) { m_pPEAssembly->EnumMemoryRegions(flags); @@ -5136,7 +5135,11 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, m_TypeRefToMethodTableMap.ListEnumMemoryRegions(flags); m_TypeDefToMethodTableMap.ListEnumMemoryRegions(flags); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags == CLRDATA_ENUM_MEM_HEAP2) + { + GetLoaderAllocator()->EnumMemoryRegions(flags); + } + else if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) { if (m_pAvailableClasses.IsValid()) { @@ -5224,7 +5227,7 @@ void Module::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, } } - } // !CLRDATA_ENUM_MEM_MINI && !CLRDATA_ENUM_MEM_TRIAGE + } // !CLRDATA_ENUM_MEM_MINI && !CLRDATA_ENUM_MEM_TRIAGE && !CLRDATA_ENUM_MEM_HEAP2 LookupMap::Iterator fileRefIter(&m_FileReferencesMap); @@ -5589,4 +5592,4 @@ void DECLSPEC_NORETURN Module::ThrowTypeLoadExceptionImpl(IMDInternalImport *pIn WRAPPER_NO_CONTRACT; GetAssembly()->ThrowTypeLoadException(pInternalImport, token, NULL, resIDWhy); } -#endif \ No newline at end of file +#endif diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 4492c025d71a51..fd6053c64bfb50 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -2994,7 +2994,7 @@ EEClass::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, MethodTable * pMT) if (HasOptionalFields()) DacEnumMemoryRegion(dac_cast(GetOptionalFields()), sizeof(EEClassOptionalFields)); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { PTR_Module pModule = pMT->GetModule(); if (pModule.IsValid()) diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 1fc909efe9c623..22ef2b9919cc19 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -376,7 +376,9 @@ class EEClassLayoutInfo // The size of the struct is explicitly specified in the meta-data. e_HAS_EXPLICIT_SIZE = 0x08, // The type recursively has a field that is LayoutKind.Auto and not an enum. - e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT = 0x10 + e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT = 0x10, + // Type type recursively has a field which is an Int128 + e_IS_OR_HAS_INT128_FIELD = 0x20, }; BYTE m_bFlags; @@ -426,6 +428,12 @@ class EEClassLayoutInfo return (m_bFlags & e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT) == e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT; } + BOOL IsInt128OrHasInt128Fields() const + { + LIMITED_METHOD_CONTRACT; + return (m_bFlags & e_IS_OR_HAS_INT128_FIELD) == e_IS_OR_HAS_INT128_FIELD; + } + BYTE GetPackingSize() const { LIMITED_METHOD_CONTRACT; @@ -467,6 +475,13 @@ class EEClassLayoutInfo m_bFlags = hasAutoLayoutField ? (m_bFlags | e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT) : (m_bFlags & ~e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT); } + + void SetIsInt128OrHasInt128Fields(BOOL hasInt128Field) + { + LIMITED_METHOD_CONTRACT; + m_bFlags = hasInt128Field ? (m_bFlags | e_IS_OR_HAS_INT128_FIELD) + : (m_bFlags & ~e_IS_OR_HAS_INT128_FIELD); + } }; // @@ -1410,6 +1425,9 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! BOOL HasExplicitSize(); BOOL IsAutoLayoutOrHasAutoLayoutField(); + + // Only accurate on non-auto layout types + BOOL IsInt128OrHasInt128Fields(); static void GetBestFitMapping(MethodTable * pMT, BOOL *pfBestFitMapping, BOOL *pfThrowOnUnmappableChar); @@ -2105,6 +2123,15 @@ inline BOOL EEClass::IsAutoLayoutOrHasAutoLayoutField() return !HasLayout() || GetLayoutInfo()->HasAutoLayoutField(); } +inline BOOL EEClass::IsInt128OrHasInt128Fields() +{ + // The name of this type is a slight misnomer as it doesn't detect Int128 fields on + // auto layout types, but since we only need this for interop scenarios, it works out. + LIMITED_METHOD_CONTRACT; + // If this type is not auto + return HasLayout() && GetLayoutInfo()->IsInt128OrHasInt128Fields(); +} + //========================================================================== // These routines manage the prestub (a bootstrapping stub that all // FunctionDesc's are initialized with.) diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index 34b04dcd6f7ab3..a37d7a06521216 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -328,6 +328,16 @@ namespace return FALSE; } + BOOL TypeHasInt128Field(CorElementType corElemType, TypeHandle pNestedType) + { + if (corElemType == ELEMENT_TYPE_VALUETYPE) + { + _ASSERTE(!pNestedType.IsNull()); + return pNestedType.GetMethodTable()->IsInt128OrHasInt128Fields(); + } + return FALSE; + } + #ifdef UNIX_AMD64_ABI void SystemVAmd64CheckForPassNativeStructInRegister(MethodTable* pMT, EEClassNativeLayoutInfo* pNativeLayoutInfo) { @@ -454,6 +464,7 @@ namespace const SigTypeContext* pTypeContext, BOOL* fDisqualifyFromManagedSequential, BOOL* fHasAutoLayoutField, + BOOL* fHasInt128Field, LayoutRawFieldInfo* pFieldInfoArrayOut, BOOL* pIsBlittableOut, ULONG* cInstanceFields @@ -532,6 +543,7 @@ namespace pFieldInfoArrayOut->m_placement = GetFieldPlacementInfo(corElemType, typeHandleMaybe); *fDisqualifyFromManagedSequential |= TypeHasGCPointers(corElemType, typeHandleMaybe); *fHasAutoLayoutField |= TypeHasAutoLayoutField(corElemType, typeHandleMaybe); + *fHasInt128Field |= TypeHasInt128Field(corElemType, typeHandleMaybe); if (!IsFieldBlittable(pModule, fd, fsig.GetArgProps(), pTypeContext, nativeTypeFlags)) *pIsBlittableOut = FALSE; @@ -625,6 +637,7 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( // function exits. BOOL fDisqualifyFromManagedSequential; BOOL hasAutoLayoutField = FALSE; + BOOL hasInt128Field = FALSE; // Check if this type might be ManagedSequential. Only valuetypes marked Sequential can be // ManagedSequential. Other issues checked below might also disqualify the type. @@ -639,9 +652,12 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( fDisqualifyFromManagedSequential = TRUE; } - if (pParentMT && !pParentMT->IsValueTypeClass() && pParentMT->IsAutoLayoutOrHasAutoLayoutField()) + if (pParentMT && !pParentMT->IsValueTypeClass()) { - hasAutoLayoutField = TRUE; + if (pParentMT->IsAutoLayoutOrHasAutoLayoutField()) + hasAutoLayoutField = TRUE; + if (pParentMT->IsInt128OrHasInt128Fields()) + hasInt128Field = TRUE; } @@ -692,6 +708,7 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( pTypeContext, &fDisqualifyFromManagedSequential, &hasAutoLayoutField, + &hasInt128Field, pInfoArrayOut, &isBlittable, &cInstanceFields @@ -706,6 +723,8 @@ VOID EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing( pEEClassLayoutInfoOut->SetHasAutoLayoutField(hasAutoLayoutField); + pEEClassLayoutInfoOut->SetIsInt128OrHasInt128Fields(hasInt128Field); + S_UINT32 cbSortArraySize = S_UINT32(cTotalFields) * S_UINT32(sizeof(LayoutRawFieldInfo*)); if (cbSortArraySize.IsOverflow()) { diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index b526383f755ea7..e0c5c063ac26cb 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -3851,7 +3851,8 @@ void ComMethodTable::SetITypeInfo(ITypeInfo *pNew) } CONTRACTL_END; - if (InterlockedCompareExchangeT(&m_pITypeInfo, pNew, NULL) == NULL) + ExecutableWriterHolder comMTWriterHolder(this, sizeof(ComMethodTable)); + if (InterlockedCompareExchangeT(&comMTWriterHolder.GetRW()->m_pITypeInfo, pNew, NULL) == NULL) { SafeAddRef(pNew); } diff --git a/src/coreclr/vm/dacenumerablehash.inl b/src/coreclr/vm/dacenumerablehash.inl index 02024c229ca7b3..cfee7b8f684be3 100644 --- a/src/coreclr/vm/dacenumerablehash.inl +++ b/src/coreclr/vm/dacenumerablehash.inl @@ -309,8 +309,8 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindFirstEntryByHash // +2 to skip "length" and "next" slots DWORD dwBucket = iHash % cBuckets + SKIP_SPECIAL_SLOTS; - // Point at the first entry in the bucket chain which would contain any entries with the given hash code. - PTR_VolatileEntry pEntry = curBuckets[dwBucket]; + // Point at the first entry in the bucket chain that stores entries with the given hash code. + PTR_VolatileEntry pEntry = VolatileLoadWithoutBarrier(&curBuckets[dwBucket]); // Walk the bucket chain one entry at a time. while (pEntry) @@ -329,13 +329,13 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindFirstEntryByHash } // Move to the next entry in the chain. - pEntry = pEntry->m_pNextEntry; + pEntry = VolatileLoadWithoutBarrier(&pEntry->m_pNextEntry); } // in a rare case if resize is in progress, look in the new table as well. // if existing entry is not in the old table, it must be in the new // since we unlink it from old only after linking into the new. - // check for next table must hapen after we looked through the current. + // check for next table must happen after we looked through the current. VolatileLoadBarrier(); curBuckets = GetNext(curBuckets); } while (curBuckets != nullptr); @@ -367,11 +367,9 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindNextEntryByHash( PTR_VolatileEntry pVolatileEntry = dac_cast(pContext->m_pEntry); iHash = pVolatileEntry->m_iHashValue; - // Iterate over the bucket chain. - while (pVolatileEntry->m_pNextEntry) + // Iterate over the rest ot the bucket chain. + while ((pVolatileEntry = VolatileLoadWithoutBarrier(&pVolatileEntry->m_pNextEntry)) != nullptr) { - // Advance to the next entry. - pVolatileEntry = pVolatileEntry->m_pNextEntry; if (pVolatileEntry->m_iHashValue == iHash) { // Found a match on hash code. Update our find context to indicate where we got to and return @@ -381,7 +379,7 @@ DPTR(VALUE) DacEnumerableHashTable::BaseFindNextEntryByHash( } } - // check for next table must hapen after we looked through the current. + // check for next table must happen after we looked through the current. VolatileLoadBarrier(); // in a case if resize is in progress, look in the new table as well. diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 239480035443d7..de49ed7b1781b4 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -3326,6 +3326,12 @@ BOOL NDirect::MarshalingRequired( { TypeHandle hndArgType = arg.GetTypeHandleThrowing(pModule, &emptyTypeContext); + if (hndArgType.GetMethodTable()->IsInt128OrHasInt128Fields()) + { + // Int128 cannot be marshalled by value at this time + return TRUE; + } + // When the runtime runtime marshalling system is disabled, we don't support // any types that contain gc pointers, but all "unmanaged" types are treated as blittable // as long as they aren't auto-layout and don't have any auto-layout fields. diff --git a/src/coreclr/vm/domainassembly.cpp b/src/coreclr/vm/domainassembly.cpp index 3e5706e48c817f..2ae508ca073c5b 100644 --- a/src/coreclr/vm/domainassembly.cpp +++ b/src/coreclr/vm/domainassembly.cpp @@ -1067,14 +1067,17 @@ void DomainAssembly::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) m_pPEAssembly->EnumMemoryRegions(flags); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE - && m_pDomain.IsValid()) + if (flags == CLRDATA_ENUM_MEM_HEAP2) { - m_pDomain->EnumMemoryRegions(flags, true); + GetLoaderAllocator()->EnumMemoryRegions(flags); } - - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + else if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) { + if (m_pDomain.IsValid()) + { + m_pDomain->EnumMemoryRegions(flags, true); + } + if (m_pAssembly.IsValid()) { m_pAssembly->EnumMemoryRegions(flags); diff --git a/src/coreclr/vm/eventpipeinternal.cpp b/src/coreclr/vm/eventpipeinternal.cpp index 454a4bdae04e0e..4ed2b819aee42c 100644 --- a/src/coreclr/vm/eventpipeinternal.cpp +++ b/src/coreclr/vm/eventpipeinternal.cpp @@ -60,7 +60,7 @@ extern "C" void QCALLTYPE EventPipeInternal_Disable(UINT64 sessionID) END_QCALL; } -extern "C" bool QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo) +extern "C" BOOL QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo) { QCALL_CONTRACT; @@ -229,7 +229,7 @@ extern "C" void QCALLTYPE EventPipeInternal_WriteEventData( END_QCALL; } -extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, EventPipeEventInstanceData *pInstance) +extern "C" BOOL QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, EventPipeEventInstanceData *pInstance) { QCALL_CONTRACT; @@ -255,7 +255,7 @@ extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent(UINT64 sessionID, Event return pNextInstance != NULL; } -extern "C" bool QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) +extern "C" BOOL QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) { QCALL_CONTRACT; @@ -268,7 +268,7 @@ extern "C" bool QCALLTYPE EventPipeInternal_SignalSession(UINT64 sessionID) return result; } -extern "C" bool QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionID, INT32 timeoutMs) +extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionID, INT32 timeoutMs) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/eventpipeinternal.h b/src/coreclr/vm/eventpipeinternal.h index 7da96b67ea7b56..b18bfe96ff66af 100644 --- a/src/coreclr/vm/eventpipeinternal.h +++ b/src/coreclr/vm/eventpipeinternal.h @@ -51,7 +51,7 @@ extern "C" UINT64 QCALLTYPE EventPipeInternal_Enable( //! extern "C" void QCALLTYPE EventPipeInternal_Disable(UINT64 sessionID); -extern "C" bool QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo); +extern "C" BOOL QCALLTYPE EventPipeInternal_GetSessionInfo(UINT64 sessionID, EventPipeSessionInfo *pSessionInfo); extern "C" INT_PTR QCALLTYPE EventPipeInternal_CreateProvider( _In_z_ LPCWSTR providerName, @@ -82,14 +82,14 @@ extern "C" void QCALLTYPE EventPipeInternal_WriteEventData( UINT32 eventDataCount, LPCGUID pActivityId, LPCGUID pRelatedActivityId); -extern "C" bool QCALLTYPE EventPipeInternal_GetNextEvent( +extern "C" BOOL QCALLTYPE EventPipeInternal_GetNextEvent( UINT64 sessionID, EventPipeEventInstanceData *pInstance); -extern "C" bool QCALLTYPE EventPipeInternal_SignalSession( +extern "C" BOOL QCALLTYPE EventPipeInternal_SignalSession( UINT64 sessionID); -extern "C" bool QCALLTYPE EventPipeInternal_WaitForSessionSignal( +extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal( UINT64 sessionID, INT32 timeoutMs); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index c23a4de544ce43..183bee15b52493 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -5637,7 +5637,7 @@ LONG CallOutFilter(PEXCEPTION_POINTERS pExceptionInfo, PVOID pv) { CallOutFilterParam *pParam = static_cast(pv); - _ASSERTE(pParam->OneShot && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); + _ASSERTE(pParam && (pParam->OneShot == TRUE || pParam->OneShot == FALSE)); if (pParam->OneShot == TRUE) { @@ -6576,10 +6576,7 @@ static LONG HandleManagedFaultFilter(EXCEPTION_POINTERS* ep, LPVOID pv) return EXCEPTION_CONTINUE_SEARCH; } -void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, - CONTEXT* pContext, - EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, - Thread* pThread) +void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, CONTEXT* pContext) { WRAPPER_NO_CONTRACT; @@ -6726,27 +6723,16 @@ bool ShouldHandleManagedFault( #ifndef TARGET_UNIX -LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); - -enum VEH_ACTION -{ - VEH_NO_ACTION = 0, - VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION, - VEH_CONTINUE_EXECUTION, - VEH_CONTINUE_SEARCH, - VEH_EXECUTE_HANDLER -}; - - +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo); VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase3(PEXCEPTION_POINTERS pExceptionInfo); -LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +VEH_ACTION WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { // It is not safe to execute code inside VM after we shutdown EE. One example is DisablePreemptiveGC // will block forever. if (g_fForbidEnterEE) { - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } @@ -6836,7 +6822,7 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) pExceptionInfo->ContextRecord->Rip = hijackArgs.ReturnAddress; } - return EXCEPTION_CONTINUE_EXECUTION; + return VEH_CONTINUE_EXECUTION; } #endif @@ -6860,11 +6846,9 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) // // Not an Out-of-memory situation, so no need for a forbid fault region here // - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } - LONG retVal = 0; - // We can't probe here, because we won't return from the CLRVectoredExceptionHandlerPhase2 // on WIN64 // @@ -6875,15 +6859,10 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) CantAllocHolder caHolder; } - retVal = CLRVectoredExceptionHandlerPhase2(pExceptionInfo); - - // - //END_ENTRYPOINT_VOIDRET; - // - return retVal; + return CLRVectoredExceptionHandlerPhase2(pExceptionInfo); } -LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) +VEH_ACTION WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo) { // // DO NOT USE CONTRACTS HERE AS THIS ROUTINE MAY NEVER RETURN. You can use @@ -6917,31 +6896,16 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo action = CLRVectoredExceptionHandlerPhase3(pExceptionInfo); } - if (action == VEH_CONTINUE_EXECUTION) - { - return EXCEPTION_CONTINUE_EXECUTION; - } - - if (action == VEH_CONTINUE_SEARCH) + if ((action == VEH_CONTINUE_EXECUTION) || (action == VEH_CONTINUE_SEARCH) || (action == VEH_EXECUTE_HANDLER)) { - return EXCEPTION_CONTINUE_SEARCH; - } - - if (action == VEH_EXECUTE_HANDLER) - { - return EXCEPTION_EXECUTE_HANDLER; + return action; } #if defined(FEATURE_EH_FUNCLETS) if (action == VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION) { - HandleManagedFault(pExceptionInfo->ExceptionRecord, - pExceptionInfo->ContextRecord, - NULL, // establisher frame (x86 only) - NULL // pThread (x86 only) - ); - return EXCEPTION_CONTINUE_EXECUTION; + return action; } #endif // defined(FEATURE_EH_FUNCLETS) @@ -6961,7 +6925,7 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo // the choice to break the no-trigger region after taking all necessary precautions. if (IsDebuggerFault(pExceptionRecord, pExceptionInfo->ContextRecord, pExceptionRecord->ExceptionCode, GetThreadNULLOk())) { - return EXCEPTION_CONTINUE_EXECUTION; + return VEH_CONTINUE_EXECUTION; } } @@ -6993,11 +6957,11 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo { // The breakpoint was not ours. Someone else can handle it. (Or if not, we'll get it again as // an unhandled exception.) - return EXCEPTION_CONTINUE_SEARCH; + return VEH_CONTINUE_SEARCH; } // The breakpoint was from managed or the runtime. Handle it. - return UserBreakpointFilter(pExceptionInfo); + return (VEH_ACTION)UserBreakpointFilter(pExceptionInfo); } #if defined(FEATURE_EH_FUNCLETS) @@ -7015,19 +6979,11 @@ LONG WINAPI CLRVectoredExceptionHandlerPhase2(PEXCEPTION_POINTERS pExceptionInfo if (fShouldHandleManagedFault) { - // - // HandleManagedFault may never return, so we cannot use a forbid fault region around it. - // - HandleManagedFault(pExceptionInfo->ExceptionRecord, - pExceptionInfo->ContextRecord, - NULL, // establisher frame (x86 only) - NULL // pThread (x86 only) - ); - return EXCEPTION_CONTINUE_EXECUTION; -} + return VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION; + } #endif // defined(FEATURE_EH_FUNCLETS) - return EXCEPTION_EXECUTE_HANDLER; + return VEH_EXECUTE_HANDLER; } /* @@ -7594,13 +7550,34 @@ LONG WINAPI CLRVectoredExceptionHandlerShim(PEXCEPTION_POINTERS pExceptionInfo) if (pThread || fExceptionInEE) { if (!bIsGCMarker) - result = CLRVectoredExceptionHandler(pExceptionInfo); - else - result = EXCEPTION_CONTINUE_EXECUTION; + { + VEH_ACTION action = CLRVectoredExceptionHandler(pExceptionInfo); + +#ifdef FEATURE_EH_FUNCLETS + if (VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION == action) + { + // + // HandleManagedFault may never return, so we cannot use a forbid fault region around it. + // + HandleManagedFault(pExceptionInfo->ExceptionRecord, pExceptionInfo->ContextRecord); + return EXCEPTION_CONTINUE_EXECUTION; + } +#endif // FEATURE_EH_FUNCLETS + + if (VEH_EXECUTE_HANDLER == action) + { + result = EXCEPTION_CONTINUE_SEARCH; + } + else + { + _ASSERTE((action == VEH_CONTINUE_EXECUTION) || (action == VEH_CONTINUE_SEARCH)); + result = (LONG)action; + } - if (EXCEPTION_EXECUTE_HANDLER == result) + } + else { - result = EXCEPTION_CONTINUE_SEARCH; + result = EXCEPTION_CONTINUE_EXECUTION; } #ifdef _DEBUG @@ -8321,7 +8298,7 @@ void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, #if defined(TARGET_X86) EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame #elif defined(FEATURE_EH_FUNCLETS) - ULONG64 pEstablisherFrame + PVOID pEstablisherFrame #else #error Unsupported platform #endif diff --git a/src/coreclr/vm/excep.h b/src/coreclr/vm/excep.h index eefaaa1ffc48eb..d30cff50f25b9e 100644 --- a/src/coreclr/vm/excep.h +++ b/src/coreclr/vm/excep.h @@ -744,14 +744,11 @@ bool IsGcMarker(T_CONTEXT *pContext, EXCEPTION_RECORD *pExceptionRecord); bool ShouldHandleManagedFault( EXCEPTION_RECORD* pExceptionRecord, - T_CONTEXT* pContext, + T_CONTEXT* pContext, EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, Thread* pThread); -void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, - T_CONTEXT* pContext, - EXCEPTION_REGISTRATION_RECORD* pEstablisherFrame, - Thread* pThread); +void HandleManagedFault(EXCEPTION_RECORD* pExceptionRecord, T_CONTEXT* pContext); LONG WatsonLastChance( Thread *pThread, @@ -778,7 +775,7 @@ void SetReversePInvokeEscapingUnhandledExceptionStatus(BOOL fIsUnwinding, #ifdef TARGET_X86 EXCEPTION_REGISTRATION_RECORD * pEstablisherFrame #elif defined(FEATURE_EH_FUNCLETS) - ULONG64 pEstablisherFrame + PVOID pEstablisherFrame #else #error Unsupported platform #endif diff --git a/src/coreclr/vm/exceptionhandling.cpp b/src/coreclr/vm/exceptionhandling.cpp index 21fe40769384be..9623dee9dfdcfa 100644 --- a/src/coreclr/vm/exceptionhandling.cpp +++ b/src/coreclr/vm/exceptionhandling.cpp @@ -837,12 +837,9 @@ UINT_PTR ExceptionTracker::FinishSecondPass( void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFrameChain, LPVOID MemoryStackFp); -// On CoreARM, the MemoryStackFp is ULONG when passed by RtlDispatchException, -// unlike its 64bit counterparts. EXTERN_C EXCEPTION_DISPOSITION -ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -861,7 +858,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord EXCEPTION_DISPOSITION returnDisposition = ExceptionContinueSearch; STRESS_LOG5(LF_EH, LL_INFO10, "Processing exception at establisher=%p, ip=%p disp->cxr: %p, sp: %p, cxr @ exception: %p\n", - MemoryStackFp, pDispatcherContext->ControlPc, + pEstablisherFrame, pDispatcherContext->ControlPc, pDispatcherContext->ContextRecord, GetSP(pDispatcherContext->ContextRecord), pContextRecord); AMD64_ONLY(STRESS_LOG3(LF_EH, LL_INFO10, " rbx=%p, rsi=%p, rdi=%p\n", pContextRecord->Rbx, pContextRecord->Rsi, pContextRecord->Rdi)); @@ -928,7 +925,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord } } - StackFrame sf((UINT_PTR)MemoryStackFp); + StackFrame sf((UINT_PTR)pEstablisherFrame); { @@ -954,7 +951,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord // { EH_LOG((LL_INFO100, "..................................................................................\n")); - EH_LOG((LL_INFO100, "ProcessCLRException enter, sp = 0x%p, ControlPc = 0x%p\n", MemoryStackFp, pDispatcherContext->ControlPc)); + EH_LOG((LL_INFO100, "ProcessCLRException enter, sp = 0x%p, ControlPc = 0x%p\n", pEstablisherFrame, pDispatcherContext->ControlPc)); DebugLogExceptionRecord(pExceptionRecord); if (STATUS_UNWIND_CONSOLIDATE == pExceptionRecord->ExceptionCode) @@ -1236,7 +1233,7 @@ lExit: ; // Exception is being propagated from a method marked UnmanagedCallersOnlyAttribute into its native caller. // The explicit frame chain needs to be unwound at this boundary. bool fIsSO = pExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW; - CleanUpForSecondPass(pThread, fIsSO, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, fIsSO, pEstablisherFrame, pEstablisherFrame); } } } @@ -4453,7 +4450,7 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte // Perform unwinding of the current frame disposition = ProcessCLRException(exceptionRecord, - establisherFrame, + (void*)establisherFrame, currentFrameContext, &dispatcherContext); @@ -4621,7 +4618,7 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT // Find exception handler in the current frame disposition = ProcessCLRException(ex.GetExceptionRecord(), - establisherFrame, + (void*)establisherFrame, ex.GetContextRecord(), &dispatcherContext); @@ -5497,16 +5494,7 @@ void TrackerAllocator::FreeTrackerMemory(ExceptionTracker* pTracker) // specify pUnwindPersonalityRoutine. For instance the debugger uses this to unwind from ExceptionHijack back // to RaiseException in win32 and specifies an empty personality routine. For more details about this // see the comments in the code below. -// -// -// AMD64 is more "advanced", in that the DISPATCHER_CONTEXT contains a field for the TargetIp. So we don't have -// to use the control PC in pDispatcherContext->ContextRecord to indicate the target IP for the unwind. However, -// this also means that pDispatcherContext->ContextRecord is expected to be consistent. -// -// -// For more information, refer to vctools\crt\crtw32\misc\{ia64|amd64}\chandler.c for __C_specific_handler() and -// nt\base\ntos\rtl\{ia64|amd64}\exdsptch.c for RtlUnwindEx(). -void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, LPVOID originalControlPC, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine) +void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL) { if (pContext) { @@ -5514,7 +5502,7 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo CopyOSContext(pDispatcherContext->ContextRecord, pContext); } - pDispatcherContext->ControlPc = (UINT_PTR) GetIP(pDispatcherContext->ContextRecord); + pDispatcherContext->ControlPc = (UINT_PTR) GetIP(pDispatcherContext->ContextRecord); #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) // Since this routine is used to fixup contexts for async exceptions, @@ -5634,14 +5622,6 @@ void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pCo } -// See the comment above for the overloaded version of this function. -void FixupDispatcherContext(DISPATCHER_CONTEXT* pDispatcherContext, CONTEXT* pContext, CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL) -{ - _ASSERTE(pOriginalContext != NULL); - FixupDispatcherContext(pDispatcherContext, pContext, (LPVOID)::GetIP(pOriginalContext), pUnwindPersonalityRoutine); -} - - BOOL FirstCallToHandler ( DISPATCHER_CONTEXT *pDispatcherContext, CONTEXT **ppContextRecord) @@ -5673,9 +5653,8 @@ BOOL FirstCallToHandler ( EXTERN_C EXCEPTION_DISPOSITION -HijackHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) -NOT_BIT64_ARG(IN ULONG MemoryStackFp), +HijackHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5716,7 +5695,7 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), pThread->SetThrowControlForThread(Thread::InducedThreadStop); } - FixupDispatcherContext(pDispatcherContext, pNewContext, pContextRecord); + FixupDispatcherContext(pDispatcherContext, pNewContext); STRESS_LOG4(LF_EH, LL_INFO10, "HijackHandler: new establisher: %p, disp->cxr: %p, new ip: %p, new sp: %p\n", pDispatcherContext->EstablisherFrame, @@ -5803,7 +5782,7 @@ void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFr EH_LOG((LL_INFO100, "Exception is going into unmanaged code, unwinding frame chain to %p\n", MemoryStackFpForFrameChain)); // On AMD64 the establisher pointer is the live stack pointer, but on - // IA64 and ARM it's the caller's stack pointer. It makes no difference, since there + // ARM and ARM64 it's the caller's stack pointer. It makes no difference, since there // is no Frame anywhere in CallDescrWorker's region of stack. // First make sure that unwinding the frame chain does not remove any transition frames @@ -5822,7 +5801,7 @@ void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFr // (stack grows up). if (!fIsSO) { - ExceptionTracker::PopTrackerIfEscaping((void*)MemoryStackFp); + ExceptionTracker::PopTrackerIfEscaping(MemoryStackFp); } } @@ -5862,9 +5841,8 @@ UnhandledExceptionHandlerUnix( #else // TARGET_UNIX EXTERN_C EXCEPTION_DISPOSITION -UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5885,7 +5863,7 @@ UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord pThread->DisablePreemptiveGC(); } } - CleanUpForSecondPass(pThread, fIsSO, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, fIsSO, pEstablisherFrame, pEstablisherFrame); } // The asm stub put us into COOP mode, but we're about to scan unmanaged call frames @@ -5908,16 +5886,15 @@ UMThunkUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord EXTERN_C EXCEPTION_DISPOSITION UMEntryPrestubUnwindFrameChainHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) { EXCEPTION_DISPOSITION disposition = UMThunkUnwindFrameChainHandler( pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext ); @@ -5927,9 +5904,8 @@ UMEntryPrestubUnwindFrameChainHandler( EXTERN_C EXCEPTION_DISPOSITION UMThunkStubUnwindFrameChainHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) -NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5944,14 +5920,14 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), if (GetThreadNULLOk() != NULL) { SetReversePInvokeEscapingUnhandledExceptionStatus(IS_UNWINDING(pExceptionRecord->ExceptionFlags), - MemoryStackFp + pEstablisherFrame ); } #endif // _DEBUG EXCEPTION_DISPOSITION disposition = UMThunkUnwindFrameChainHandler( pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext ); @@ -5963,9 +5939,8 @@ NOT_BIT64_ARG(IN ULONG MemoryStackFp), // This is the personality routine setup for the assembly helper (CallDescrWorker) that calls into // managed code. EXTERN_C EXCEPTION_DISPOSITION -CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -5977,7 +5952,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) { GCX_COOP_NO_DTOR(); - CleanUpForSecondPass(pThread, true, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, true, pEstablisherFrame, pEstablisherFrame); } InterlockedAnd((LONG*)&pThread->m_fPreemptiveGCDisabled, 0); @@ -5987,7 +5962,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco } EXCEPTION_DISPOSITION retVal = ProcessCLRException(pExceptionRecord, - MemoryStackFp, + pEstablisherFrame, pContextRecord, pDispatcherContext); @@ -5996,7 +5971,7 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco if (IS_UNWINDING(pExceptionRecord->ExceptionFlags)) { - CleanUpForSecondPass(pThread, false, (void*)MemoryStackFp, (void*)MemoryStackFp); + CleanUpForSecondPass(pThread, false, pEstablisherFrame, pEstablisherFrame); } // We're scanning out from CallDescr and potentially through the EE and out to unmanaged. @@ -6011,9 +5986,8 @@ CallDescrWorkerUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionReco #ifdef FEATURE_COMINTEROP EXTERN_C EXCEPTION_DISPOSITION -ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -6029,9 +6003,8 @@ ReverseComUnwindFrameChainHandler(IN PEXCEPTION_RECORD pExceptionRecord #ifndef TARGET_UNIX EXTERN_C EXCEPTION_DISPOSITION FixRedirectContextHandler( - IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), + IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PCONTEXT pContextRecord, IN OUT PDISPATCHER_CONTEXT pDispatcherContext ) @@ -6052,7 +6025,7 @@ FixRedirectContextHandler( CONTEXT *pRedirectedContext = GetCONTEXTFromRedirectedStubStackFrame(pDispatcherContext); - FixupDispatcherContext(pDispatcherContext, pRedirectedContext, pContextRecord); + FixupDispatcherContext(pDispatcherContext, pRedirectedContext); // Returning ExceptionCollidedUnwind will cause the OS to take our new context record // and dispatcher context and restart the exception dispatching on this call frame, diff --git a/src/coreclr/vm/exceptionhandling.h b/src/coreclr/vm/exceptionhandling.h index a69740d175f4d1..a9c2086b8636c3 100644 --- a/src/coreclr/vm/exceptionhandling.h +++ b/src/coreclr/vm/exceptionhandling.h @@ -17,9 +17,8 @@ #define INVALID_RESUME_ADDRESS 0x000000000000bad0 EXTERN_C EXCEPTION_DISPOSITION -ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord - BIT64_ARG(IN ULONG64 MemoryStackFp) - NOT_BIT64_ARG(IN ULONG MemoryStackFp), +ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord, + IN PVOID pEstablisherFrame, IN OUT PT_CONTEXT pContextRecord, IN OUT PT_DISPATCHER_CONTEXT pDispatcherContext); diff --git a/src/coreclr/vm/exceptmacros.h b/src/coreclr/vm/exceptmacros.h index 3b33292044ddd4..1627e3d3d65cc4 100644 --- a/src/coreclr/vm/exceptmacros.h +++ b/src/coreclr/vm/exceptmacros.h @@ -240,7 +240,16 @@ VOID DECLSPEC_NORETURN RealCOMPlusThrowOM(); #endif // !defined(FEATURE_EH_FUNCLETS) -LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); +enum VEH_ACTION +{ + VEH_NO_ACTION = -3, + VEH_EXECUTE_HANDLE_MANAGED_EXCEPTION = -2, + VEH_CONTINUE_EXECUTION = EXCEPTION_CONTINUE_EXECUTION, + VEH_CONTINUE_SEARCH = EXCEPTION_CONTINUE_SEARCH, + VEH_EXECUTE_HANDLER = EXCEPTION_EXECUTE_HANDLER +}; + +VEH_ACTION CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // Actual UEF worker prototype for use by GCUnhandledExceptionFilter. extern LONG InternalUnhandledExceptionFilter_Worker(PEXCEPTION_POINTERS pExceptionInfo); diff --git a/src/coreclr/vm/i386/excepx86.cpp b/src/coreclr/vm/i386/excepx86.cpp index 68c68f7f258a0e..51df72d448705e 100644 --- a/src/coreclr/vm/i386/excepx86.cpp +++ b/src/coreclr/vm/i386/excepx86.cpp @@ -1254,13 +1254,13 @@ CPFH_FirstPassHandler(EXCEPTION_RECORD *pExceptionRecord, // Call to the vectored handler to give other parts of the Runtime a chance to jump in and take over an // exception before we do too much with it. The most important point in the vectored handler is not to toggle // the GC mode. - DWORD filter = CLRVectoredExceptionHandler(&ptrs); + VEH_ACTION filter = CLRVectoredExceptionHandler(&ptrs); - if (filter == (DWORD) EXCEPTION_CONTINUE_EXECUTION) + if (filter == VEH_CONTINUE_EXECUTION) { return ExceptionContinueExecution; } - else if (filter == EXCEPTION_CONTINUE_SEARCH) + else if (filter == VEH_CONTINUE_SEARCH) { return ExceptionContinueSearch; } @@ -1300,22 +1300,16 @@ CPFH_FirstPassHandler(EXCEPTION_RECORD *pExceptionRecord, CPFH_VerifyThreadIsInValidState(pThread, exceptionCode, pEstablisherFrame); - // If we were in cooperative mode when we came in here, then its okay to see if we should do HandleManagedFault + // If we were in cooperative mode when we came in here, then it's okay to see if we should do HandleManagedFault // and push a FaultingExceptionFrame. If we weren't in coop mode coming in here, then it means that there's no - // way the exception could really be from managed code. I might look like it was from managed code, but in - // reality its a rethrow from unmanaged code, either unmanaged user code, or unmanaged EE implementation. + // way the exception could really be from managed code. It might look like it was from managed code, but in + // reality it's a rethrow from unmanaged code, either unmanaged user code, or unmanaged EE implementation. if (disabled && ShouldHandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread)) { #if defined(USE_FEF) - HandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread); + HandleManagedFault(pExceptionRecord, pContext); retval = ExceptionContinueExecution; goto exit; -#else // USE_FEF - // Save the context pointer in the Thread's EXInfo, so that a stack crawl can recover the - // register values from the fault. - - //@todo: I haven't yet found any case where we need to do anything here. If there are none, eliminate - // this entire if () {} block. #endif // USE_FEF } diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index e2b23d5c5dfae1..86503f3d0efa53 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -5571,7 +5571,7 @@ HCIMPL2(void, JIT_DelegateProfile32, Object *obj, ICorJitInfo::HandleHistogram32 HCIMPLEND // Version of helper above used when the count is 64-bit -HCIMPL3(void, JIT_DelegateProfile64, Object *obj, CORINFO_METHOD_HANDLE baseMethod, ICorJitInfo::HandleHistogram64* methodProfile) +HCIMPL2(void, JIT_DelegateProfile64, Object *obj, ICorJitInfo::HandleHistogram64* methodProfile) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3163a593440080..fb46cde8964eb4 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -4882,7 +4882,7 @@ void CEEInfo::getCallInfo( MethodDesc * pTargetMD = pMDAfterConstraintResolution; DWORD dwTargetMethodAttrs = pTargetMD->GetAttrs(); - pResult->exactContextNeedsRuntimeLookup = (!constrainedType.IsNull() && constrainedType.IsCanonicalSubtype()); + pResult->exactContextNeedsRuntimeLookup = (fIsStaticVirtualMethod && !fResolvedConstraint && !constrainedType.IsNull() && constrainedType.IsCanonicalSubtype()); if (pTargetMD->HasMethodInstantiation()) { diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index b7eebd07afecb5..86a72ff57fac5a 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -1445,6 +1445,7 @@ void LoaderAllocator::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; DAC_ENUM_DTHIS(); + EMEM_OUT(("MEM: %p LoaderAllocator\n", dac_cast(this))); if (m_pLowFrequencyHeap.IsValid()) { m_pLowFrequencyHeap->EnumMemoryRegions(flags); @@ -1461,9 +1462,27 @@ void LoaderAllocator::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { m_pPrecodeHeap->EnumMemoryRegions(flags); } - if (m_pPrecodeHeap.IsValid()) + if (m_pExecutableHeap.IsValid()) { - m_pPrecodeHeap->EnumMemoryRegions(flags); + m_pExecutableHeap->EnumMemoryRegions(flags); + } +#ifdef FEATURE_READYTORUN + if (m_pDynamicHelpersHeap.IsValid()) + { + m_pDynamicHelpersHeap->EnumMemoryRegions(flags); + } +#endif + if (m_pFixupPrecodeHeap.IsValid()) + { + m_pFixupPrecodeHeap->EnumMemoryRegions(flags); + } + if (m_pNewStubPrecodeHeap.IsValid()) + { + m_pNewStubPrecodeHeap->EnumMemoryRegions(flags); + } + if (m_pVirtualCallStubManager.IsValid()) + { + m_pVirtualCallStubManager->EnumMemoryRegions(flags); } } #endif //DACCESS_COMPILE diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 90311d3a01ca18..cbe6ce460f3fe8 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -240,7 +240,7 @@ class LoaderAllocator FatTokenSet *m_pFatTokenSet; #endif - VirtualCallStubManager *m_pVirtualCallStubManager; + PTR_VirtualCallStubManager m_pVirtualCallStubManager; private: LoaderAllocatorSet m_LoaderAllocatorReferences; @@ -599,7 +599,7 @@ class LoaderAllocator void InitVirtualCallStubManager(BaseDomain *pDomain); void UninitVirtualCallStubManager(); - inline VirtualCallStubManager *GetVirtualCallStubManager() + inline PTR_VirtualCallStubManager GetVirtualCallStubManager() { LIMITED_METHOD_CONTRACT; return m_pVirtualCallStubManager; diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index c4d8156fc939b8..e61162ac553c89 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -7904,7 +7904,7 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) pWriteableData.EnumMem(); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { DispatchMap * pMap = GetDispatchMap(); if (pMap != NULL) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 515cc554b4f6d8..07792d5fbf36d9 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1487,6 +1487,9 @@ class MethodTable inline BOOL IsAutoLayoutOrHasAutoLayoutField(); + // Only accurate on types which are not auto layout + inline BOOL IsInt128OrHasInt128Fields(); + UINT32 GetNativeSize(); DWORD GetBaseSize() diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index adcdbd33469858..8f8f8178e26023 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -948,6 +948,13 @@ inline BOOL MethodTable::IsAutoLayoutOrHasAutoLayoutField() return GetClass()->IsAutoLayoutOrHasAutoLayoutField(); } +//========================================================================================== +inline BOOL MethodTable::IsInt128OrHasInt128Fields() +{ + LIMITED_METHOD_CONTRACT; + return HasLayout() && GetClass()->IsInt128OrHasInt128Fields(); +} + //========================================================================================== inline DWORD MethodTable::GetPerInstInfoSize() { diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index ecd1e9d22916cb..b0c0c1ce749d37 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -9907,21 +9907,6 @@ void MethodTableBuilder::CheckForSystemTypes() return; } -#if defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64) - else if (strcmp(nameSpace, g_SystemNS) == 0) - { - EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); - - // These types correspond to fundamental data types in the underlying ABIs: - // * Int128: __int128 - // * UInt128: unsigned __int128 - - if ((strcmp(name, g_Int128Name) == 0) || (strcmp(name, g_UInt128Name) == 0)) - { - pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 16; // sizeof(__int128) - } - } -#endif // UNIX_AMD64_ABI || TARGET_ARM64 } if (g_pNullableClass != NULL) @@ -10005,6 +9990,30 @@ void MethodTableBuilder::CheckForSystemTypes() { pMT->SetInternalCorElementType (ELEMENT_TYPE_I); } + else if ((strcmp(name, g_Int128Name) == 0) || (strcmp(name, g_UInt128Name) == 0)) + { + EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); + pLayout->SetIsInt128OrHasInt128Fields(TRUE); +#ifdef TARGET_ARM + // No such type exists for the Procedure Call Standard for ARM. We will default + // to the same alignment as __m128, which is supported by the ABI. + + pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 8; +#elif defined(TARGET_64BIT) || defined(TARGET_X86) + + // These types correspond to fundamental data types in the underlying ABIs: + // * Int128: __int128 + // * UInt128: unsigned __int128 + // + // This behavior matches the ABI standard on various Unix platforms + // On Windows, no standard for Int128 has been established yet, + // although applying 16 byte alignment is consistent with treatment of 128 bit SSE types + // even on X86 + pLayout->m_ManagedLargestAlignmentRequirementOfAllMembers = 16; // sizeof(__int128) +#else +#error Unknown architecture +#endif // TARGET_64BIT + } } else { diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index 2665a46d12f0ab..2ca3596ac9236f 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -1131,6 +1131,11 @@ namespace *errorResIDOut = IDS_EE_BADMARSHAL_AUTOLAYOUT; return MarshalInfo::MARSHAL_TYPE_UNKNOWN; } + if (pMT->IsInt128OrHasInt128Fields()) + { + *errorResIDOut = IDS_EE_BADMARSHAL_INT128_RESTRICTION; + return MarshalInfo::MARSHAL_TYPE_UNKNOWN; + } *pMTOut = pMT; return MarshalInfo::MARSHAL_TYPE_BLITTABLEVALUECLASS; } @@ -2283,6 +2288,18 @@ MarshalInfo::MarshalInfo(Module* pModule, IfFailGoto(E_FAIL, lFail); } + // * Int128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling + // * UInt128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling + // The field layout is correct, so field scenarios work, but these should not be passed by value as parameters + if (!IsFieldScenario() && !m_byref) + { + if (m_pMT->IsInt128OrHasInt128Fields()) + { + m_resID = IDS_EE_BADMARSHAL_INT128_RESTRICTION; + IfFailGoto(E_FAIL, lFail); + } + } + if (!m_pMT->HasLayout()) { m_resID = IDS_EE_BADMARSHAL_AUTOLAYOUT; diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 5a8f1624ac5971..4c731c4a240f4b 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1153,7 +1153,6 @@ class ReflectFieldObject : public BaseObjectWithCachedData INT32 m_empty2; OBJECTREF m_empty3; OBJECTREF m_empty4; - OBJECTREF m_empty5; FieldDesc * m_pFD; public: diff --git a/src/coreclr/vm/peimagelayout.cpp b/src/coreclr/vm/peimagelayout.cpp index 2cf519425da2a9..792a3176eb129c 100644 --- a/src/coreclr/vm/peimagelayout.cpp +++ b/src/coreclr/vm/peimagelayout.cpp @@ -85,16 +85,18 @@ PEImageLayout* PEImageLayout::LoadConverted(PEImage* pOwner) // ConvertedImageLayout may be able to handle them, but the fact that we were unable to // load directly implies that MAPMapPEFile could not consume what crossgen produced. // that is suspicious, one or another might have a bug. - _ASSERTE(!pFlat->HasReadyToRunHeader()); + _ASSERTE(!pOwner->IsFile() || !pFlat->HasReadyToRunHeader()); #endif - if (!pFlat->HasReadyToRunHeader() && !pFlat->HasWriteableSections()) + // ignore R2R if the image is not a file. + if ((pFlat->HasReadyToRunHeader() && pOwner->IsFile()) || + pFlat->HasWriteableSections()) { - // we can use flat layout for this - return pFlat.Extract(); + return new ConvertedImageLayout(pFlat); } - return new ConvertedImageLayout(pFlat); + // we can use flat layout for this + return pFlat.Extract(); } PEImageLayout* PEImageLayout::Load(PEImage* pOwner, HRESULT* loadFailure) @@ -448,7 +450,7 @@ ConvertedImageLayout::ConvertedImageLayout(FlatImageLayout* source) IfFailThrow(Init(loadedImage)); - if (IsNativeMachineFormat() && g_fAllowNativeImages) + if (m_pOwner->IsFile() && IsNativeMachineFormat() && g_fAllowNativeImages) { // Do base relocation and exception hookup, if necessary. // otherwise R2R will be disabled for this image. diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 91b2f55d9ea33a..c86a6886515042 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -8383,7 +8383,7 @@ Thread::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) WRAPPER_NO_CONTRACT; DAC_ENUM_DTHIS(); - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { if (m_pDomain.IsValid()) { @@ -8467,7 +8467,7 @@ Thread::EnumMemoryRegionsWorker(CLRDataEnumMemoryFlags flags) DacGetThreadContext(this, &context); } - if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE) + if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { AppDomain::GetCurrentDomain()->EnumMemoryRegions(flags, true); } diff --git a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT index d15ea798c9519a..53f1569252ee83 100644 --- a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT +++ b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT @@ -1391,30 +1391,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -License notice for Newtonsoft.Json -=================================== - -The MIT License (MIT) - -Copyright (c) 2007 James Newton-King - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - License notice for NuGet.Client ------------------------------- @@ -1500,3 +1476,165 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +License for Jb Evain +--------------------- + +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +License for sse4-strstr (https://github.com/WojciechMula/sse4-strstr) +-------------------------------------- + + Copyright (c) 2008-2016, Wojciech Muła + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for amd/aocl-libm-ose +------------------------------- + +Copyright (C) 2008-2020 Advanced Micro Devices, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +License notice for code from The Practice of Programming +------------------------------- + +Copyright (C) 1999 Lucent Technologies + +Excerpted from 'The Practice of Programming +by Brian W. Kernighan and Rob Pike + +You may use this code for any purpose, as long as you leave the copyright notice and book citation attached. + +License notice for m-ou-se/floatconv +------------------------------- + +Copyright (c) 2020 Mara Bos +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License notice for MsQuic +-------------------------------------- + +Copyright (c) Microsoft Corporation. +Licensed under the MIT License. + +Available at +https://github.com/microsoft/msquic/blob/main/LICENSE + +--------------------------------------------------------- + +Newtonsoft.Json 13.0.1 - MIT + + +(c) 2008 VeriSign, Inc. +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Notice for Euclidean Affine Functions and Applications to Calendar +Algorithms +------------------------------- + +Aspects of Date/Time processing based on algorithm described in "Euclidean Affine Functions and Applications to Calendar +Algorithms", Cassio Neri and Lorenz Schneider. https://arxiv.org/pdf/2102.06959.pdf diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 0bb1a67b0320fb..07d77161002bcf 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -95,7 +95,6 @@ - @@ -206,13 +205,6 @@ - - - - - - - @@ -220,7 +212,6 @@ - diff --git a/src/installer/prepare-artifacts.proj b/src/installer/prepare-artifacts.proj index 595c8e49d575c1..f2b108bf4217a9 100644 --- a/src/installer/prepare-artifacts.proj +++ b/src/installer/prepare-artifacts.proj @@ -12,7 +12,7 @@ - + $(ArtifactsObjDir)TempWorkingDir\$([System.Guid]::NewGuid())\ - + $(ProductionVersion) $(ProductVersion) @@ -101,14 +101,14 @@ Lines="$(ProductVersionTxtContents)" Overwrite="true" Encoding="ASCII" /> - + - + @@ -133,7 +133,7 @@ - $(InstallersRelativePath)workloads/$(SdkBandVersion)/%(Filename)%(Extension) + $(InstallersRelativePath)workloads/%(Filename)%(Extension) true @@ -222,11 +222,13 @@ Include="$(DownloadDirectory)**\VS.Redist.Common.*.nupkg" Exclude="@(DownloadedSymbolNupkgFile)" /> - + + $(DownloadDirectory)*\workloads-vs\**\*.zip"/> @@ -276,7 +278,7 @@ - + + internal static bool HasSymmetricEncryption { get; } = #if NETCOREAPP !OperatingSystem.IsBrowser(); #else diff --git a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs index 185eb3fcf2b6b7..800b4f335e892c 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs @@ -74,20 +74,7 @@ internal static unsafe int Decrypt( { Debug.Assert(destination.Length >= encryptedData.Length); - // Don't check that algorithmIdentifier.Parameters is set here. - // Maybe some future PBES3 will have one with a default. - - if (algorithmIdentifier.Algorithm == Oids.PasswordBasedEncryptionScheme2) - { - return Pbes2Decrypt( - algorithmIdentifier.Parameters, - password, - passwordBytes, - encryptedData, - destination); - } - - if (!Helpers.HasNonAesSymmetricEncryption) + if (!Helpers.HasSymmetricEncryption) { throw new CryptographicException( SR.Format( @@ -95,8 +82,11 @@ internal static unsafe int Decrypt( algorithmIdentifier.Algorithm)); } + // Don't check that algorithmIdentifier.Parameters is set here. + // Maybe some future PBES3 will have one with a default. + HashAlgorithmName digestAlgorithmName; - SymmetricAlgorithm cipher; + SymmetricAlgorithm? cipher = null; bool pkcs12 = false; @@ -141,6 +131,13 @@ internal static unsafe int Decrypt( cipher.KeySize = 40; pkcs12 = true; break; + case Oids.PasswordBasedEncryptionScheme2: + return Pbes2Decrypt( + algorithmIdentifier.Parameters, + password, + passwordBytes, + encryptedData, + destination); default: throw new CryptographicException( SR.Format( @@ -149,6 +146,7 @@ internal static unsafe int Decrypt( } Debug.Assert(digestAlgorithmName.Name != null); + Debug.Assert(cipher != null); using (cipher) { @@ -239,6 +237,14 @@ internal static void InitiateEncryption( { Debug.Assert(pbeParameters != null); + if (!Helpers.HasSymmetricEncryption) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_UnknownAlgorithmIdentifier, + pbeParameters.EncryptionAlgorithm)); + } + isPkcs12 = false; switch (pbeParameters.EncryptionAlgorithm) @@ -258,7 +264,7 @@ internal static void InitiateEncryption( cipher.KeySize = 256; encryptionAlgorithmOid = Oids.Aes256Cbc; break; - case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12 when Helpers.HasNonAesSymmetricEncryption: + case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12: cipher = TripleDES.Create(); cipher.KeySize = 192; encryptionAlgorithmOid = Oids.Pkcs12PbeWithShaAnd3Key3Des; @@ -566,6 +572,12 @@ private static SymmetricAlgorithm OpenCipher( { string? algId = encryptionScheme.Algorithm; + if (!Helpers.HasSymmetricEncryption) + { + throw new CryptographicException( + SR.Format(SR.Cryptography_AlgorithmNotSupported, algId)); + } + if (algId == Oids.Aes128Cbc || algId == Oids.Aes192Cbc || algId == Oids.Aes256Cbc) @@ -604,12 +616,6 @@ private static SymmetricAlgorithm OpenCipher( return aes; } - if (!Helpers.HasNonAesSymmetricEncryption) - { - throw new CryptographicException( - SR.Format(SR.Cryptography_AlgorithmNotSupported, algId)); - } - if (algId == Oids.TripleDesCbc) { // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 diff --git a/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs b/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs index 6cb36e463da1f6..e034e82bd864a8 100644 --- a/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs +++ b/src/libraries/Common/tests/System/Net/Security/Kerberos/KerberosExecutor.cs @@ -29,7 +29,9 @@ public class KerberosExecutor : IDisposable private readonly ITestOutputHelper _testOutputHelper; public static bool IsSupported { get; } = - RemoteExecutor.IsSupported && (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()); + RemoteExecutor.IsSupported && + !PlatformDetection.IsLinuxBionic && + (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()); public const string DefaultAdminPassword = "PLACEHOLDERadmin."; public const string DefaultUserPassword = "PLACEHOLDERcorrect20"; diff --git a/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems b/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems index 6371a6a6b6fb70..013923faa3d662 100644 --- a/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems +++ b/src/libraries/Common/tests/System/Net/Security/Kerberos/System.Net.Security.Kerberos.Shared.projitems @@ -36,6 +36,6 @@ - + diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs index ec1a58e1ada7c3..c489db5197333f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherOneShotTests.cs @@ -9,6 +9,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesCipherOneShotTests : SymmetricOneShotBase { protected override byte[] Key => @@ -414,251 +415,519 @@ public static IEnumerable TestCases CipherMode.CBC, }; - if (PlatformDetection.IsNotBrowser) + // ECB test cases + // plaintext requires no padding + yield return new object[] { - // ECB test cases - // plaintext requires no padding - yield return new object[] + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, - 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, + 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, + }, - PaddingMode.PKCS7, - CipherMode.ECB, - }; + PaddingMode.PKCS7, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + }, - PaddingMode.None, - CipherMode.ECB, - }; + PaddingMode.None, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + }, - PaddingMode.Zeros, - CipherMode.ECB, - }; + PaddingMode.Zeros, + CipherMode.ECB, + }; - yield return new object[] + yield return new object[] + { + // plaintext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0xC1, 0xCA, 0x44, 0xE8, 0x05, 0xFF, 0xCB, 0x6F, - 0x4D, 0x7F, 0xE9, 0x17, 0x12, 0xFE, 0xBB, 0xAC, - }, + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0xC1, 0xCA, 0x44, 0xE8, 0x05, 0xFF, 0xCB, 0x6F, + 0x4D, 0x7F, 0xE9, 0x17, 0x12, 0xFE, 0xBB, 0xAC, + }, - PaddingMode.ANSIX923, - CipherMode.ECB, - }; + PaddingMode.ANSIX923, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, + 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, + 0xD3, 0xAA, 0x33, 0x5B, 0x93, 0xC2, 0x3D, 0x96, + 0xFD, 0x89, 0xB1, 0x8C, 0x47, 0x75, 0x65, 0xA8, + }, + + PaddingMode.ISO10126, + CipherMode.ECB, + }; + + // plaintext requires padding + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x82, 0x8D, 0x60, 0xDC, 0x44, 0x26, 0xCF, 0xDE, + 0xC9, 0x54, 0x33, 0x47, 0xE2, 0x9E, 0xF0, 0x8C, + }, + + PaddingMode.PKCS7, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x49, 0x39, 0x1B, 0x69, 0xA1, 0xF3, 0x66, 0xE4, + 0x3E, 0x40, 0x51, 0xB8, 0x05, 0x60, 0xDC, 0xFD, + }, + + PaddingMode.Zeros, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0xCD, 0x0D, 0xCD, 0xEA, 0xA2, 0x1F, 0xC1, 0xC3, + 0x81, 0xEE, 0x8A, 0x63, 0x94, 0x5F, 0x85, 0x43, + }, + + PaddingMode.ANSIX923, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, + + // ciphertext + new byte[] + { + 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, + 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, + 0x9C, 0xE4, 0x0D, 0x2F, 0xCD, 0x82, 0x25, 0x0E, + 0x13, 0xAB, 0x4B, 0x6B, 0xC0, 0x9A, 0x21, 0x2E, + }, + + PaddingMode.ISO10126, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, + 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, + }, + + PaddingMode.PKCS7, + CipherMode.ECB, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.None, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + }, + + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, + 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, + }, + + // ciphertext + new byte[] + { + 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, + 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0xD2, + }, + + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; + + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D, - 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9, - 0xD3, 0xAA, 0x33, 0x5B, 0x93, 0xC2, 0x3D, 0x96, - 0xFD, 0x89, 0xB1, 0x8C, 0x47, 0x75, 0x65, 0xA8, - }, + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; - PaddingMode.ISO10126, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - // plaintext requires padding - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x82, 0x8D, 0x60, 0xDC, 0x44, 0x26, 0xCF, 0xDE, - 0xC9, 0x54, 0x33, 0x47, 0xE2, 0x9E, 0xF0, 0x8C, - }, + PaddingMode.None, + CipherMode.CFB, + 8, + }; - PaddingMode.PKCS7, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x49, 0x39, 0x1B, 0x69, 0xA1, 0xF3, 0x66, 0xE4, - 0x3E, 0x40, 0x51, 0xB8, 0x05, 0x60, 0xDC, 0xFD, - }, + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; - PaddingMode.Zeros, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0xCD, 0x0D, 0xCD, 0xEA, 0xA2, 0x1F, 0xC1, 0xC3, - 0x81, 0xEE, 0x8A, 0x63, 0x94, 0x5F, 0x85, 0x43, - }, + PaddingMode.ANSIX923, + CipherMode.CFB, + 8, + }; - PaddingMode.ANSIX923, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + new byte[] + { + 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, + 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, + 0x59, + }, - yield return new object[] + // ciphertext + new byte[] { - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, + 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, + 0xF9, 0x97, + }, - // ciphertext - new byte[] - { - 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3, - 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F, - 0x9C, 0xE4, 0x0D, 0x2F, 0xCD, 0x82, 0x25, 0x0E, - 0x13, 0xAB, 0x4B, 0x6B, 0xC0, 0x9A, 0x21, 0x2E, - }, + PaddingMode.ISO10126, + CipherMode.CFB, + 8, + }; - PaddingMode.ISO10126, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] - { - // plaintext - Array.Empty(), + // ciphertext + Array.Empty(), - // ciphertext - Array.Empty(), + PaddingMode.Zeros, + CipherMode.CFB, + 8, + }; - PaddingMode.Zeros, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] - { - // plaintext - Array.Empty(), + // ciphertext + Array.Empty(), - // ciphertext - Array.Empty(), + PaddingMode.None, + CipherMode.CFB, + 8, + }; - PaddingMode.None, - CipherMode.ECB, - }; + yield return new object[] + { + // plaintext + Array.Empty(), - yield return new object[] + // ciphertext + new byte[] { - // plaintext - Array.Empty(), - - // ciphertext - new byte[] - { - 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, - 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, - }, + 0x02, + }, - PaddingMode.PKCS7, - CipherMode.ECB, - }; + PaddingMode.PKCS7, + CipherMode.CFB, + 8, + }; + // CFB128 is not supported on Windows 7. + if (PlatformDetection.IsNotWindows7) + { yield return new object[] { + // plaintext new byte[] { @@ -669,18 +938,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x2B, 0x63, 0xD4, 0x34, 0x86, 0x05, 0x9B, 0x52, + 0x20, 0x46, 0x65, 0xD5, 0xBC, 0xA1, 0xED, 0x11, }, PaddingMode.PKCS7, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -691,13 +962,13 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, }, PaddingMode.None, CipherMode.CFB, - 8, + 128, }; yield return new object[] @@ -713,17 +984,18 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, }, PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -734,18 +1006,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3B, 0x73, 0xC4, 0x24, 0x96, 0x15, 0x8B, 0x42, + 0x30, 0x56, 0x75, 0xC5, 0xAC, 0xB1, 0xFD, 0x11, }, PaddingMode.ANSIX923, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -756,18 +1030,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x53, 0x8B, 0x08, 0x3E, 0x07, 0xA4, 0x03, 0x16, - 0x0A, 0x75, 0x1A, 0x15, 0xF6, 0x1D, 0xAB, 0xD9, - 0xD2, + 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, + 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, + 0x3E, 0x5D, 0xED, 0x96, 0x51, 0x93, 0xF0, 0x12, + 0x95, 0x98, 0x51, 0x29, 0xB6, 0xF8, 0x84, 0x11, }, PaddingMode.ISO10126, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -779,18 +1055,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xD0, 0xD4, 0xF1, 0x60, 0x93, 0xD0, 0x20, + 0x91, 0x11, 0xD8, 0xF6, 0x27, 0xE3, 0xAF, 0x0F, }, PaddingMode.PKCS7, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -802,18 +1080,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x00, }, - PaddingMode.None, + PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -825,18 +1105,20 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, + 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x0F, }, - PaddingMode.Zeros, + PaddingMode.ANSIX923, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext new byte[] { @@ -848,41 +1130,38 @@ public static IEnumerable TestCases // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, + 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, + 0x0C, 0x0C, 0x39, 0x31, 0x1C, 0xAA, 0x41, 0x45, + 0x78, 0xD0, 0x9F, 0x0F, 0x44, 0xD9, 0x37, 0x0F, }, - PaddingMode.ANSIX923, + PaddingMode.ISO10126, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, + Array.Empty(), // ciphertext new byte[] { - 0x9A, 0x84, 0x44, 0xEB, 0x82, 0x11, 0xEA, 0x28, - 0x91, 0x8E, 0xA8, 0x40, 0xE4, 0x12, 0x3F, 0x72, - 0xF9, 0x97, + 0x13, 0x47, 0x4B, 0xA9, 0x1C, 0x31, 0xE1, 0xFE, + 0x23, 0x69, 0x61, 0xE6, 0x27, 0x01, 0xBE, 0xAA, }, - PaddingMode.ISO10126, + PaddingMode.PKCS7, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext Array.Empty(), @@ -891,11 +1170,12 @@ public static IEnumerable TestCases PaddingMode.Zeros, CipherMode.CFB, - 8, + 128, }; yield return new object[] { + // plaintext Array.Empty(), @@ -904,290 +1184,8 @@ public static IEnumerable TestCases PaddingMode.None, CipherMode.CFB, - 8, + 128, }; - - yield return new object[] - { - // plaintext - Array.Empty(), - - // ciphertext - new byte[] - { - 0x02, - }, - - PaddingMode.PKCS7, - CipherMode.CFB, - 8, - }; - - // CFB128 is not supported on Windows 7. - if (PlatformDetection.IsNotWindows7) - { - yield return new object[] - { - - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, - - // ciphertext - new byte[] - { - 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, - 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, - 0x2B, 0x63, 0xD4, 0x34, 0x86, 0x05, 0x9B, 0x52, - 0x20, 0x46, 0x65, 0xD5, 0xBC, 0xA1, 0xED, 0x11, - }, - - PaddingMode.PKCS7, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, - - // ciphertext - new byte[] - { - 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, - 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, - }, - - PaddingMode.None, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, - - // ciphertext - new byte[] - { - 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, - 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, - }, - - PaddingMode.Zeros, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, - - // ciphertext - new byte[] - { - 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, - 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, - 0x3B, 0x73, 0xC4, 0x24, 0x96, 0x15, 0x8B, 0x42, - 0x30, 0x56, 0x75, 0xC5, 0xAC, 0xB1, 0xFD, 0x11, - }, - - PaddingMode.ANSIX923, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, - 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59, - }, - - // ciphertext - new byte[] - { - 0x53, 0x3F, 0x49, 0x1D, 0x53, 0x29, 0x39, 0x67, - 0x8A, 0x06, 0x28, 0x76, 0x34, 0x9A, 0x2D, 0xE3, - 0x3E, 0x5D, 0xED, 0x96, 0x51, 0x93, 0xF0, 0x12, - 0x95, 0x98, 0x51, 0x29, 0xB6, 0xF8, 0x84, 0x11, - }, - - PaddingMode.ISO10126, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, - - // ciphertext - new byte[] - { - 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, - 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, - 0x0C, 0xD0, 0xD4, 0xF1, 0x60, 0x93, 0xD0, 0x20, - 0x91, 0x11, 0xD8, 0xF6, 0x27, 0xE3, 0xAF, 0x0F, - }, - - PaddingMode.PKCS7, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, - - // ciphertext - new byte[] - { - 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, - 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, - 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, - 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x00, - }, - - PaddingMode.Zeros, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, - - // ciphertext - new byte[] - { - 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, - 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, - 0x0C, 0xDF, 0xDB, 0xFE, 0x6F, 0x9C, 0xDF, 0x2F, - 0x9E, 0x1E, 0xD7, 0xF9, 0x28, 0xEC, 0xA0, 0x0F, - }, - - PaddingMode.ANSIX923, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - new byte[] - { - 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, - 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, - 0x59, - }, - - // ciphertext - new byte[] - { - 0x9A, 0x07, 0x33, 0xAB, 0xA8, 0x7E, 0xF9, 0x26, - 0xBA, 0xC0, 0x0E, 0xAF, 0xB7, 0x12, 0x25, 0x39, - 0x0C, 0x0C, 0x39, 0x31, 0x1C, 0xAA, 0x41, 0x45, - 0x78, 0xD0, 0x9F, 0x0F, 0x44, 0xD9, 0x37, 0x0F, - }, - - PaddingMode.ISO10126, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - Array.Empty(), - - // ciphertext - new byte[] - { - 0x13, 0x47, 0x4B, 0xA9, 0x1C, 0x31, 0xE1, 0xFE, - 0x23, 0x69, 0x61, 0xE6, 0x27, 0x01, 0xBE, 0xAA, - }, - - PaddingMode.PKCS7, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - Array.Empty(), - - // ciphertext - Array.Empty(), - - PaddingMode.Zeros, - CipherMode.CFB, - 128, - }; - - yield return new object[] - { - - // plaintext - Array.Empty(), - - // ciphertext - Array.Empty(), - - PaddingMode.None, - CipherMode.CFB, - 128, - }; - } } } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs index 95a8e740763174..a6206fc3e632b2 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs @@ -11,6 +11,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public partial class AesCipherTests { [Fact] @@ -34,7 +35,6 @@ public static void RandomKeyRoundtrip_128() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void RandomKeyRoundtrip_192() { using (Aes aes = AesFactory.Create()) @@ -79,7 +79,6 @@ public static void DecryptKnownCBC256() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_256() { byte[] encryptedBytes = new byte[] @@ -102,7 +101,6 @@ public static void DecryptKnownCFB128_256() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void DecryptKnownECB192() { byte[] encryptedBytes = new byte[] @@ -125,7 +123,6 @@ public static void DecryptKnownECB192() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_192() { byte[] encryptedBytes = new byte[] @@ -148,7 +145,6 @@ public static void DecryptKnownCFB128_192() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void DecryptKnownCFB128_128() { byte[] encryptedBytes = new byte[] @@ -276,7 +272,6 @@ public static void VerifyInPlaceDecryption() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding() { TestAesTransformDirectKey( @@ -289,7 +284,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB256_NoPadding() { TestAesTransformDirectKey( @@ -302,7 +296,6 @@ public static void VerifyKnownTransform_ECB256_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding_2() { TestAesTransformDirectKey( @@ -315,7 +308,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB128_NoPadding_3() { TestAesTransformDirectKey( @@ -328,7 +320,6 @@ public static void VerifyKnownTransform_ECB128_NoPadding_3() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB192_NoPadding() { TestAesTransformDirectKey( @@ -341,7 +332,6 @@ public static void VerifyKnownTransform_ECB192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void VerifyKnownTransform_ECB192_NoPadding_2() { TestAesTransformDirectKey( @@ -354,7 +344,6 @@ public static void VerifyKnownTransform_ECB192_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_8_NoPadding() { TestAesTransformDirectKey( @@ -368,7 +357,6 @@ public static void VerifyKnownTransform_CFB128_8_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "PaddingMode.None is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding() { TestAesTransformDirectKey( @@ -406,7 +394,6 @@ public static void VerifyKnownTransform_CBC256_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding() { TestAesTransformDirectKey( @@ -420,7 +407,6 @@ public static void VerifyKnownTransform_CFB128_256_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoPadding() { TestAesTransformDirectKey( @@ -446,7 +432,6 @@ public static void VerifyKnownTransform_CBC128_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_2() { TestAesTransformDirectKey( @@ -472,7 +457,6 @@ public static void VerifyKnownTransform_CBC128_NoPadding_3() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_3() { TestAesTransformDirectKey( @@ -486,7 +470,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_3() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKnownTransform_CBC192_NoPadding() { TestAesTransformDirectKey( @@ -499,7 +482,6 @@ public static void VerifyKnownTransform_CBC192_NoPadding() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding() { TestAesTransformDirectKey( @@ -513,7 +495,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoPadding() { TestAesTransformDirectKey( @@ -527,7 +508,6 @@ public static void VerifyKnownTransform_CFB8_192_NoPadding() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKnownTransform_CBC192_NoPadding_2() { TestAesTransformDirectKey( @@ -540,7 +520,6 @@ public static void VerifyKnownTransform_CBC192_NoPadding_2() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_2() { TestAesTransformDirectKey( @@ -554,7 +533,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void WrongKeyFailDecrypt() { // The test: @@ -601,7 +579,6 @@ public static void WrongKeyFailDecrypt() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void WrongKeyFailDecrypt_2() { // The test: @@ -652,7 +629,6 @@ public static void WrongKeyFailDecrypt_2() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoPadding_4() { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=4 @@ -668,7 +644,6 @@ public static void VerifyKnownTransform_CFB8_128_NoPadding_4() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_4_Fails() { Assert.Throws(() => @@ -684,7 +659,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_4_Fails() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_PKCS7_4() { TestAesTransformDirectKey( @@ -698,7 +672,6 @@ public static void VerifyKnownTransform_CFB128_128_PKCS7_4() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_PKCS7_4() { TestAesTransformDirectKey( @@ -714,7 +687,6 @@ public static void VerifyKnownTransform_CFB8_128_PKCS7_4() [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=0 @@ -733,7 +705,6 @@ public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT128.rsp, [ENCRYPT] COUNT=9 @@ -752,7 +723,6 @@ public static void VerifyKnownTransform_CFB8_128_NoOrZeroPadding_9_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=0 @@ -771,7 +741,6 @@ public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT192.rsp, [ENCRYPT] COUNT=9 @@ -790,7 +759,6 @@ public static void VerifyKnownTransform_CFB8_192_NoOrZeroPadding_9_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_0_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=0 @@ -809,7 +777,6 @@ public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_0_Extended(Padd [Theory] [InlineData(PaddingMode.None)] [InlineData(PaddingMode.Zeros)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_9_Extended(PaddingMode paddingMode) { // NIST CAVP AESMMT.ZIP CFB8MMT256.rsp, [ENCRYPT] COUNT=9 @@ -826,7 +793,6 @@ public static void VerifyKnownTransform_CFB8_256_NoOrZeroPadding_9_Extended(Padd } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_0() { // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=0 @@ -841,7 +807,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_0() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_128_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT128.rsp, [ENCRYPT] COUNT=1 @@ -856,7 +821,6 @@ public static void VerifyKnownTransform_CFB128_128_NoPadding_1_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_0_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=0 @@ -871,7 +835,6 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_0_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_192_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT192.rsp, [ENCRYPT] COUNT=1 @@ -885,23 +848,11 @@ public static void VerifyKnownTransform_CFB128_192_NoPadding_1_Extended() feedbackSize: 128); } - public static IEnumerable EncryptorReuse_LeadsToSameResultsData - { - get - { - yield return new object[] { CipherMode.CBC, 0 }; - - if (PlatformDetection.IsNotBrowser) - { - yield return new object[] { CipherMode.CFB, 128 }; - yield return new object[] { CipherMode.CFB, 8 }; - yield return new object[] { CipherMode.ECB, 0 }; - } - } - } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [MemberData(nameof(EncryptorReuse_LeadsToSameResultsData))] + [InlineData(CipherMode.CBC, 0)] + [InlineData(CipherMode.CFB, 128)] + [InlineData(CipherMode.CFB, 8)] + [InlineData(CipherMode.ECB, 0)] public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize) { // AppleCCCryptor does not allow calling Reset on CFB cipher. @@ -928,7 +879,10 @@ public static void EncryptorReuse_LeadsToSameResults(CipherMode cipherMode, int } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [MemberData(nameof(EncryptorReuse_LeadsToSameResultsData))] + [InlineData(CipherMode.CBC, 0)] + [InlineData(CipherMode.CFB, 128)] + [InlineData(CipherMode.CFB, 8)] + [InlineData(CipherMode.ECB, 0)] public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int feedbackSize) { // AppleCCCryptor does not allow calling Reset on CFB cipher. @@ -960,7 +914,6 @@ public static void DecryptorReuse_LeadsToSameResults(CipherMode cipherMode, int } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding_0_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=0 @@ -975,7 +928,6 @@ public static void VerifyKnownTransform_CFB128_256_NoPadding_0_Extended() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void VerifyKnownTransform_CFB128_256_NoPadding_1_Extended() { // NIST CAVP AESMMT.ZIP CFB128MMT256.rsp, [ENCRYPT] COUNT=1 @@ -995,9 +947,9 @@ public static IEnumerable AesZeroPadData { yield return new object[] { CipherMode.CBC }; - if (PlatformDetection.IsNotBrowser && !PlatformDetection.IsWindows7) + if (!PlatformDetection.IsWindows7) { - // Browser and Windows 7 do not support CFB128. + // Windows 7 does not support CFB128. yield return new object[] { CipherMode.CFB }; } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs index 9de09ff120325c..1b4f6032248356 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesContractTests.cs @@ -8,6 +8,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesContractTests { [Fact] @@ -55,10 +56,7 @@ public static void LegalKeySizes() Assert.Equal(128, keySizeLimits.MinSize); Assert.Equal(256, keySizeLimits.MaxSize); - - // Browser's SubtleCrypto doesn't support AES-192 - int expectedKeySkipSize = PlatformDetection.IsBrowser ? 128 : 64; - Assert.Equal(expectedKeySkipSize, keySizeLimits.SkipSize); + Assert.Equal(64, keySizeLimits.SkipSize); } } @@ -109,7 +107,6 @@ public static void InvalidKeySizes(int invalidKeySize, bool skipOnNetfx) [InlineData(64, false)] [InlineData(256, true)] [InlineData(127, true)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableInSetter) { using (Aes aes = AesFactory.Create()) @@ -142,7 +139,6 @@ public static void InvalidCFBFeedbackSizes(int feedbackSize, bool discoverableIn [Theory] [InlineData(8)] [InlineData(128)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void ValidCFBFeedbackSizes(int feedbackSize) { // Windows 7 only supports CFB8. @@ -217,7 +213,6 @@ public static void VerifyKeyGeneration_128() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "AES-192 is not supported on Browser")] public static void VerifyKeyGeneration_192() { using (Aes aes = AesFactory.Create()) @@ -309,28 +304,25 @@ public static void CreateTransformExceptions() Assert.Throws(() => aes.CreateDecryptor(key, null)); } - if (PlatformDetection.IsNotBrowser) + using (Aes aes = AesFactory.Create()) { - using (Aes aes = AesFactory.Create()) - { - aes.Mode = CipherMode.ECB; + aes.Mode = CipherMode.ECB; - Assert.Throws(() => aes.CreateEncryptor(null, iv)); - Assert.Throws(() => aes.CreateEncryptor(null, null)); + Assert.Throws(() => aes.CreateEncryptor(null, iv)); + Assert.Throws(() => aes.CreateEncryptor(null, null)); - Assert.Throws(() => aes.CreateDecryptor(null, iv)); - Assert.Throws(() => aes.CreateDecryptor(null, null)); + Assert.Throws(() => aes.CreateDecryptor(null, iv)); + Assert.Throws(() => aes.CreateDecryptor(null, null)); - // ECB will accept an IV (but ignore it), and doesn't require it. - using (ICryptoTransform didNotThrow = aes.CreateEncryptor(key, null)) - { - Assert.NotNull(didNotThrow); - } + // ECB will accept an IV (but ignore it), and doesn't require it. + using (ICryptoTransform didNotThrow = aes.CreateEncryptor(key, null)) + { + Assert.NotNull(didNotThrow); + } - using (ICryptoTransform didNotThrow = aes.CreateDecryptor(key, null)) - { - Assert.NotNull(didNotThrow); - } + using (ICryptoTransform didNotThrow = aes.CreateDecryptor(key, null)) + { + Assert.NotNull(didNotThrow); } } } @@ -392,7 +384,6 @@ public static void ValidateOffsetAndCount() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void Cfb8ModeCanDepadCfb128Padding() { using (Aes aes = AesFactory.Create()) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs index 94b7a9c7210f99..ed21b358b0987f 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCornerTests.cs @@ -12,6 +12,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class AesCornerTests { [Fact] diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs index 154ca9897156ba..1a496a505c6660 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesModeTests.cs @@ -7,6 +7,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesModeTests { [Fact] @@ -16,28 +17,24 @@ public static void SupportsCBC() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void SupportsECB() { SupportsMode(CipherMode.ECB); } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void SupportsCFB8() { SupportsMode(CipherMode.CFB, feedbackSize: 8); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void SupportsCFB128() { SupportsMode(CipherMode.CFB, feedbackSize: 128); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void Windows7DoesNotSupportCFB128() { DoesNotSupportMode(CipherMode.CFB, feedbackSize: 128); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs index 5665547eab4b74..f0761570a476bb 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/DecryptorReusability.cs @@ -7,6 +7,7 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { using Aes = System.Security.Cryptography.Aes; + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class DecryptorReusability { // See https://github.com/dotnet/runtime/issues/21354 for details diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs index 080f1888473391..7b56a7017c4bab 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs @@ -410,7 +410,6 @@ public void DerivedTypesDefineTest() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb8_ToleratesExtraPadding() { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -470,7 +469,6 @@ public void DecryptOneShot_Cbc_InvalidPadding_DoesNotContainPlaintext(PaddingMod [InlineData(PaddingMode.PKCS7, 2048)] [InlineData(PaddingMode.ANSIX923, 2048)] [InlineData(PaddingMode.ISO10126, 2048)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public void DecryptOneShot_Ecb_InvalidPadding_DoesNotContainPlaintext(PaddingMode paddingMode, int ciphertextSize) { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -497,7 +495,6 @@ public void DecryptOneShot_Ecb_InvalidPadding_DoesNotContainPlaintext(PaddingMod [InlineData(PaddingMode.PKCS7, 2048)] [InlineData(PaddingMode.ANSIX923, 2048)] [InlineData(PaddingMode.ISO10126, 2048)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb_InvalidPadding_DoesNotContainPlaintext(PaddingMode paddingMode, int ciphertextSize) { using (SymmetricAlgorithm alg = CreateAlgorithm()) @@ -550,7 +547,6 @@ public void DecryptOneShot_Cbc_TooShortDoesNotContainPlaintext(PaddingMode paddi [InlineData(PaddingMode.PKCS7)] [InlineData(PaddingMode.ANSIX923)] [InlineData(PaddingMode.ISO10126)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public void DecryptOneShot_Cfb8_TooShortDoesNotContainPlaintext(PaddingMode paddingMode) { using (SymmetricAlgorithm alg = CreateAlgorithm()) diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 7d2558a9593243..c16a31a1f60140 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -4697,14 +4697,22 @@ public static void Remove_Invalid() [InlineData("Aaaaaaaa", 'A', 'a', "aaaaaaaa")] // Single iteration of vectorised path; no remainders through non-vectorised path // Three leading 'a's before a match (copyLength > 0), Single iteration of vectorised path; no remainders through non-vectorised path [InlineData("aaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaa")] - // Single iteration of vectorised path; 3 remainders through non-vectorised path + // Single iteration of vectorised path; 3 remainders handled by vectorized path [InlineData("AaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaa")] + // Single iteration of vectorized path; 0 remainders handled by vectorized path + [InlineData("aaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaa")] + // Eight chars before a match (copyLength > 0), single iteration of vectorized path for the remainder + [InlineData("12345678AAAAAAA", 'A', 'a', "12345678aaaaaaa")] // ------------------------- For Vector.Count == 16 (AVX2) ------------------------- [InlineData("AaaaaaaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaaaaaaa")] // Single iteration of vectorised path; no remainders through non-vectorised path // Three leading 'a's before a match (copyLength > 0), Single iteration of vectorised path; no remainders through non-vectorised path [InlineData("aaaAaaaaaaaAaaaaaaa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] - // Single iteration of vectorised path; 3 remainders through non-vectorised path + // Single iteration of vectorised path; 3 remainders handled by vectorized path [InlineData("AaaaaaaaAaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] + // Single iteration of vectorized path; 0 remainders handled by vectorized path + [InlineData("aaaaaaaaaaaaaaaaaAa", 'A', 'a', "aaaaaaaaaaaaaaaaaaa")] + // Sixteen chars before a match (copyLength > 0), single iteration of vectorized path for the remainder + [InlineData("1234567890123456AAAAAAAAAAAAAAA", 'A', 'a', "1234567890123456aaaaaaaaaaaaaaa")] // ----------------------------------- General test data ----------------------------------- [InlineData("Hello", 'l', '!', "He!!o")] // 2 match, non-vectorised path [InlineData("Hello", 'e', 'e', "Hello")] // oldChar and newChar are same; nothing to replace diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 2e3613356868ee..2b1c22a5cf4ba0 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -131,13 +131,13 @@ - $(ArtifactsBinDir)sdk-no-workload\ + $(ArtifactsBinDir)dotnet-none\ $([MSBuild]::NormalizeDirectory($(SdkWithNoWorkloadForTestingPath))) $(SdkWithNoWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp $(SdkWithNoWorkloadForTestingPath)workload.stamp - $(ArtifactsBinDir)dotnet-workload\ + $(ArtifactsBinDir)dotnet-net7\ $([MSBuild]::NormalizeDirectory($(SdkWithWorkloadForTestingPath))) $(SdkWithWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs index 0cddf30f2a719c..d1af024a3e38e1 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs @@ -187,12 +187,20 @@ static async ValueTask Await(int i, ValueTask vt, List toDispose) // No further changes to _state.Disposables, are allowed. _disposed = true; - // ResolvedServices is never cleared for singletons because there might be a compilation running in background - // trying to get a cached singleton service. If it doesn't find it - // it will try to create a new one which will result in an ObjectDisposedException. + } - return _disposables; + if (IsRootScope && !RootProvider.IsDisposed()) + { + // If this ServiceProviderEngineScope instance is a root scope, disposing this instance will need to dispose the RootProvider too. + // Otherwise the RootProvider will never get disposed and will leak. + // Note, if the RootProvider get disposed first, it will automatically dispose all attached ServiceProviderEngineScope objects. + RootProvider.Dispose(); } + + // ResolvedServices is never cleared for singletons because there might be a compilation running in background + // trying to get a cached singleton service. If it doesn't find it + // it will try to create a new one which will result in an ObjectDisposedException. + return _disposables; } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs index f66f36b3cf6ed2..dd9b1af11a55a0 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs @@ -89,6 +89,8 @@ internal ServiceProvider(ICollection serviceDescriptors, Serv /// The service that was produced. public object? GetService(Type serviceType) => GetService(serviceType, Root); + internal bool IsDisposed() => _disposed; + /// public void Dispose() { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs index d43752db21eba7..e801497236f0b9 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.Extensions.DependencyInjection.Specification.Fakes; using Xunit; @@ -17,5 +18,16 @@ public void DoubleDisposeWorks() serviceProviderEngineScope.Dispose(); serviceProviderEngineScope.Dispose(); } + + [Fact] + public void RootEngineScopeDisposeTest() + { + var services = new ServiceCollection(); + ServiceProvider sp = services.BuildServiceProvider(); + var s = sp.GetRequiredService(); + ((IDisposable)s).Dispose(); + + Assert.Throws(() => sp.GetRequiredService()); + } } } diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json index 15c8c0a74777fd..5a39f5d7450448 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.compatibility.json @@ -3454,6 +3454,38 @@ "any", "base" ], + "fedora.38": [ + "fedora.38", + "fedora", + "linux", + "unix", + "any", + "base" + ], + "fedora.38-arm64": [ + "fedora.38-arm64", + "fedora.38", + "fedora-arm64", + "fedora", + "linux-arm64", + "linux", + "unix-arm64", + "unix", + "any", + "base" + ], + "fedora.38-x64": [ + "fedora.38-x64", + "fedora.38", + "fedora-x64", + "fedora", + "linux-x64", + "linux", + "unix-x64", + "unix", + "any", + "base" + ], "freebsd": [ "freebsd", "unix", @@ -4285,22 +4317,22 @@ "any", "base" ], - "linux-musl-s390x": [ - "linux-musl-s390x", + "linux-musl-ppc64le": [ + "linux-musl-ppc64le", "linux-musl", - "linux-s390x", + "linux-ppc64le", "linux", - "unix-s390x", + "unix-ppc64le", "unix", "any", "base" ], - "linux-musl-ppc64le": [ - "linux-musl-ppc64le", + "linux-musl-s390x": [ + "linux-musl-s390x", "linux-musl", - "linux-ppc64le", + "linux-s390x", "linux", - "unix-ppc64le", + "unix-s390x", "unix", "any", "base" @@ -4325,18 +4357,18 @@ "any", "base" ], - "linux-s390x": [ - "linux-s390x", + "linux-ppc64le": [ + "linux-ppc64le", "linux", - "unix-s390x", + "unix-ppc64le", "unix", "any", "base" ], - "linux-ppc64le": [ - "linux-ppc64le", + "linux-s390x": [ + "linux-s390x", "linux", - "unix-ppc64le", + "unix-s390x", "unix", "any", "base" @@ -8895,14 +8927,14 @@ "any", "base" ], - "unix-s390x": [ - "unix-s390x", + "unix-ppc64le": [ + "unix-ppc64le", "unix", "any", "base" ], - "unix-ppc64le": [ - "unix-ppc64le", + "unix-s390x": [ + "unix-s390x", "unix", "any", "base" @@ -9549,4 +9581,4 @@ "any", "base" ] -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json index 050ce1e4e8ce36..17a0a1c4db0b77 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json @@ -1215,6 +1215,23 @@ "fedora-x64" ] }, + "fedora.38": { + "#import": [ + "fedora" + ] + }, + "fedora.38-arm64": { + "#import": [ + "fedora.38", + "fedora-arm64" + ] + }, + "fedora.38-x64": { + "#import": [ + "fedora.38", + "fedora-x64" + ] + }, "freebsd": { "#import": [ "unix" @@ -1637,16 +1654,16 @@ "linux-armel" ] }, - "linux-musl-s390x": { + "linux-musl-ppc64le": { "#import": [ "linux-musl", - "linux-s390x" + "linux-ppc64le" ] }, - "linux-musl-ppc64le": { + "linux-musl-s390x": { "#import": [ "linux-musl", - "linux-ppc64le" + "linux-s390x" ] }, "linux-musl-x64": { @@ -1661,16 +1678,16 @@ "linux-x86" ] }, - "linux-s390x": { + "linux-ppc64le": { "#import": [ "linux", - "unix-s390x" + "unix-ppc64le" ] }, - "linux-ppc64le": { + "linux-s390x": { "#import": [ "linux", - "unix-ppc64le" + "unix-s390x" ] }, "linux-x64": { @@ -3636,12 +3653,12 @@ "unix" ] }, - "unix-s390x": { + "unix-ppc64le": { "#import": [ "unix" ] }, - "unix-ppc64le": { + "unix-s390x": { "#import": [ "unix" ] @@ -3980,4 +3997,4 @@ ] } } -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props index 1b10a2604af28a..38deb3f1863350 100644 --- a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props +++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props @@ -81,7 +81,7 @@ linux x64;arm64 - 23;24;25;26;27;28;29;30;31;32;33;34;35;36;37 + 23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38 false diff --git a/src/libraries/System.Console/src/System.Console.csproj b/src/libraries/System.Console/src/System.Console.csproj index c60368c33b8b50..1c45c5a8f932a4 100644 --- a/src/libraries/System.Console/src/System.Console.csproj +++ b/src/libraries/System.Console/src/System.Console.csproj @@ -238,7 +238,6 @@ - diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj index e9dd8a7adec528..90a660f236118d 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent) true + + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json deleted file mode 100644 index 1b600a96bff58a..00000000000000 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Diagnostics.DefaultActivityIdFormatIsHierarchial": true - } -} \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs index 0f0124469a9a13..797b35baf9b77d 100644 --- a/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs +++ b/src/libraries/System.Diagnostics.TraceSource/tests/System.Diagnostics.TraceSource.Config.Tests/TraceSourceWithConfigurationTests.cs @@ -35,6 +35,7 @@ private static void CreateAndLoadConfigFile(string filename) [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void RuntimeFilterChange() { CreateAndLoadConfigFile("testhost_ConfigWithRuntime.config"); @@ -95,6 +96,7 @@ public void RuntimeFilterChange() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_RemoveSwitch() { // Use a SourceSwitch that logs Error. @@ -135,6 +137,7 @@ void Log() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_ChangeSwitch() { // Use a SourceSwitch that logs Error. @@ -160,6 +163,7 @@ public void Refresh_ChangeSwitch() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Refresh_RemoveSource() { // Use a SourceSwitch that logs Error. @@ -193,6 +197,7 @@ public void Refresh_RemoveSource() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void ConfigWithEvents_RuntimeListener() { CreateAndLoadConfigFile("testhost_ConfigWithRuntime.config"); @@ -259,6 +264,7 @@ private void SubscribeToSwitch_Initializing(object? sender, InitializingSwitchEv [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void AllTypes() { CreateAndLoadConfigFile("testhost_AllTypes.config"); @@ -303,6 +309,7 @@ public void AllTypes() [Fact] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74244", TestPlatforms.tvOS)] public void Switch_MissingValue_Throws() { Exception e = Assert.Throws(() => diff --git a/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj b/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj index a08b302253bf84..ccbd35a5a34fce 100644 --- a/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj +++ b/src/libraries/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj @@ -6,10 +6,10 @@ - + - \ No newline at end of file + diff --git a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj index 75300d8460ba8d..35b1a833abb569 100644 --- a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj +++ b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj @@ -6,7 +6,7 @@ true - diagnostics_tracing;marshal-ilgen + diagnostics_tracing diff --git a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx index 79e3188410c3c9..5308e9153c9791 100644 --- a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx @@ -205,6 +205,9 @@ The entry is a symbolic link or a hard link but the LinkName field is null or empty. + Entry type '{0}' not supported. + + Entry type '{0}' not supported in format '{1}'. @@ -255,4 +258,7 @@ An attempt was made to move the position before the beginning of the stream. + + Unable to parse number. + diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs index eda97e2b8a26f0..11e3b77c5f8f2d 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs @@ -20,14 +20,16 @@ internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . /// In Unix platforms only: , and . /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public GnuTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Gnu, isGea: false) { @@ -38,6 +40,9 @@ public GnuTarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the GNU format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the GNU format. public GnuTarEntry(TarEntry other) : base(other, TarEntryFormat.Gnu) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs index db317f7a884112..555e4feaa27f73 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs @@ -25,8 +25,6 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . @@ -47,6 +45,10 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) /// File length, under the name size, as an , if the string representation of the number is larger than 12 bytes. /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public PaxTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Pax, isGea: false) { @@ -62,9 +64,6 @@ public PaxTarEntry(TarEntryType entryType, string entryName) /// The type of the entry. /// A string with the path and file name of this entry. /// An enumeration of string key-value pairs that represents the metadata to include in the Extended Attributes entry that precedes the current entry. - /// is . - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . @@ -85,6 +84,10 @@ public PaxTarEntry(TarEntryType entryType, string entryName) /// File length, under the name size, as an , if the string representation of the number is larger than 12 bytes. /// /// + /// or is . + /// is empty. + /// -or- + /// is not supported in the specified format. public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable> extendedAttributes) : base(entryType, entryName, TarEntryFormat.Pax, isGea: false) { @@ -100,6 +103,9 @@ public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable /// Initializes a new instance by converting the specified entry into the PAX format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the PAX format. public PaxTarEntry(TarEntry other) : base(other, TarEntryFormat.Pax) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs index e8fe9b7e01a16f..b35c958b4d53f0 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SeekableSubReadStream.cs @@ -18,7 +18,7 @@ public SeekableSubReadStream(Stream superStream, long startPosition, long maxLen { if (!superStream.CanSeek) { - throw new InvalidOperationException(SR.IO_NotSupported_UnseekableStream); + throw new ArgumentException(SR.IO_NotSupported_UnseekableStream, nameof(superStream)); } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs index c6b9b6afc8d6db..998e53ea6fc99f 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/SubReadStream.cs @@ -25,7 +25,7 @@ public SubReadStream(Stream superStream, long startPosition, long maxLength) { if (!superStream.CanRead) { - throw new InvalidOperationException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(superStream)); } _startInSuperStream = startPosition; _positionInSuperStream = startPosition; @@ -188,10 +188,7 @@ public override Task FlushAsync(CancellationToken cancellationToken) => // the substream is just 'a chunk' of the super-stream protected override void Dispose(bool disposing) { - if (disposing && !_isDisposed) - { - _isDisposed = true; - } + _isDisposed = true; base.Dispose(disposing); } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs index 00c790762e4b65..a27df41f4c1c6a 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs @@ -51,12 +51,12 @@ internal TarEntry(TarEntry other, TarEntryFormat format) { if (other is PaxGlobalExtendedAttributesTarEntry) { - throw new InvalidOperationException(SR.TarCannotConvertPaxGlobalExtendedAttributesEntry); + throw new ArgumentException(SR.TarCannotConvertPaxGlobalExtendedAttributesEntry, nameof(other)); } TarEntryType compatibleEntryType = TarHelpers.GetCorrectTypeFlagForFormat(format, other.EntryType); - TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format); + TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format, nameof(other)); _readerOfOrigin = other._readerOfOrigin; @@ -92,6 +92,7 @@ public int Gid /// A timestamps that represents the last time the contents of the file represented by this entry were modified. /// /// In Unix platforms, this timestamp is commonly known as mtime. + /// The specified value is larger than . public DateTimeOffset ModificationTime { get => _header._mTime; @@ -114,7 +115,9 @@ public DateTimeOffset ModificationTime /// /// When the indicates a or a , this property returns the link target path of such link. /// - /// Cannot set the link name if the entry type is not or . + /// The entry type is not or . + /// The specified value is . + /// The specified value is empty. public string LinkName { get => _header._linkName ?? string.Empty; @@ -124,6 +127,7 @@ public string LinkName { throw new InvalidOperationException(SR.TarEntryHardLinkOrSymLinkExpected); } + ArgumentException.ThrowIfNullOrEmpty(value); _header._linkName = value; } } @@ -177,7 +181,8 @@ public int Uid /// Elevation is required to extract a or to disk. /// Symbolic links can be recreated using , or . /// Hard links can only be extracted when using or . - /// is or empty. + /// is . + /// is empty. /// The parent directory of does not exist. /// -or- /// is and a file already exists in . @@ -206,7 +211,8 @@ public void ExtractToFile(string destinationFileName, bool overwrite) /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. - /// is or empty. + /// is . + /// is empty. /// The parent directory of does not exist. /// -or- /// is and a file already exists in . @@ -237,9 +243,8 @@ public Task ExtractToFileAsync(string destinationFileName, bool overwrite, Cance /// Sets a new stream that represents the data section, if it makes sense for the to contain data; if a stream already existed, the old stream gets disposed before substituting it with the new stream. Setting a stream is allowed. /// If you write data to this data stream, make sure to rewind it to the desired start position before writing this entry into an archive using . /// Setting a data section is not supported because the is not (or for an archive of format). - /// Cannot set an unreadable stream. - /// -or- - /// An I/O problem occurred. + /// Cannot set an unreadable stream. + /// An I/O problem occurred. public Stream? DataStream { get => _header._dataStream; @@ -252,7 +257,7 @@ public Stream? DataStream if (value != null && !value.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(value)); } if (_readerOfOrigin != null) @@ -287,12 +292,12 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o if (EntryType == TarEntryType.Directory) { - TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, pendingModes); } else { // If it is a file, create containing directory. - TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, pendingModes); ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite); } } @@ -309,13 +314,13 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b if (EntryType == TarEntryType.Directory) { - TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, pendingModes); return Task.CompletedTask; } else { // If it is a file, create containing directory. - TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, pendingModes); return ExtractToFileInternalAsync(fileDestinationPath, linkTargetPath, overwrite, cancellationToken); } } @@ -339,7 +344,7 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b { if (string.IsNullOrEmpty(LinkName)) { - throw new FormatException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); + throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, LinkName); @@ -465,7 +470,7 @@ private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bo // If the destination contains a directory segment, need to check that it exists if (!string.IsNullOrEmpty(directoryPath) && !Path.Exists(directoryPath)) { - throw new IOException(string.Format(SR.IO_PathNotFound_NoPathName, filePath)); + throw new IOException(string.Format(SR.IO_PathNotFound_Path, filePath)); } if (!Path.Exists(filePath)) @@ -511,7 +516,7 @@ private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bo } else { - throw new FormatException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); + throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); } } } @@ -529,7 +534,7 @@ private void ExtractAsRegularFile(string destinationFileName) DataStream?.CopyTo(fs); } - ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, ModificationTime); + AttemptSetLastWriteTime(destinationFileName, ModificationTime); } // Asynchronously extracts the current entry as a regular file into the specified destination. @@ -551,7 +556,19 @@ private async Task ExtractAsRegularFileAsync(string destinationFileName, Cancell } } - ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, ModificationTime); + AttemptSetLastWriteTime(destinationFileName, ModificationTime); + } + + private static void AttemptSetLastWriteTime(string destinationFileName, DateTimeOffset lastWriteTime) + { + try + { + File.SetLastWriteTime(destinationFileName, lastWriteTime.LocalDateTime); // SetLastWriteTime expects local time + } + catch + { + // Some OSes like Android might not support setting the last write time, the extraction should not fail because of that + } } private FileStreamOptions CreateFileStreamOptions(bool isAsync) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index a77cb2c36a65ea..2ed7865a2e13e8 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Enumeration; using System.Threading; using System.Threading.Tasks; @@ -15,17 +16,18 @@ namespace System.Formats.Tar /// public static class TarFile { - // Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely - // to be greater than the length of typical entry names from the file system, even - // on non-Windows platforms. The capacity will be increased, if needed. - private const int DefaultCapacity = 260; - /// /// Creates a tar stream that contains all the filesystem entries from the specified directory. /// /// The path of the directory to archive. /// The destination stream the archive. /// to include the base directory name as the first segment in all the names of the archive entries. to exclude the base directory name from the archive entry names. + /// or is . + /// is empty. + /// -or- + /// does not support writing. + /// The directory path was not found. + /// An I/O exception occurred. public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, bool includeBaseDirectory) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); @@ -33,7 +35,7 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin if (!destination.CanWrite) { - throw new IOException(SR.IO_NotSupported_UnwritableStream); + throw new ArgumentException(SR.IO_NotSupported_UnwritableStream, nameof(destination)); } if (!Directory.Exists(sourceDirectoryName)) @@ -55,6 +57,12 @@ public static void CreateFromDirectory(string sourceDirectoryName, Stream destin /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. + /// or is . + /// is empty. + /// -or- + /// does not support writing. + /// The directory path was not found. + /// An I/O exception occurred. public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -66,7 +74,7 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d if (!destination.CanWrite) { - return Task.FromException(new IOException(SR.IO_NotSupported_UnwritableStream)); + return Task.FromException(new ArgumentException(SR.IO_NotSupported_UnwritableStream, nameof(destination))); } if (!Directory.Exists(sourceDirectoryName)) @@ -86,6 +94,10 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, Stream d /// The path of the directory to archive. /// The path of the destination archive file. /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. + /// or is . + /// or is empty. + /// The directory path was not found. + /// An I/O exception occurred. public static void CreateFromDirectory(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectoryName); @@ -114,6 +126,10 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin /// to include the base directory name as the first path segment in all the names of the archive entries. to exclude the base directory name from the entry name paths. /// The token to monitor for cancellation requests. The default value is . /// A task that represents the asynchronous creation operation. + /// or is . + /// or is empty. + /// The directory path was not found. + /// An I/O exception occurred. public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string destinationFileName, bool includeBaseDirectory, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -143,8 +159,15 @@ public static Task CreateFromDirectoryAsync(string sourceDirectoryName, string d /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. /// Operation not permitted due to insufficient permissions. - /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// is empty. + /// -or- + /// does not support reading. + /// An I/O exception occurred. public static void ExtractToDirectory(Stream source, string destinationDirectoryName, bool overwriteFiles) { ArgumentNullException.ThrowIfNull(source); @@ -152,7 +175,7 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory if (!source.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source)); } if (!Directory.Exists(destinationDirectoryName)) @@ -176,7 +199,15 @@ public static void ExtractToDirectory(Stream source, string destinationDirectory /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// is empty. + /// -or- + /// does not support reading. + /// An I/O exception occurred. public static Task ExtractToDirectoryAsync(Stream source, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -188,7 +219,7 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire if (!source.CanRead) { - return Task.FromException(new IOException(SR.IO_NotSupported_UnreadableStream)); + return Task.FromException(new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(source))); } if (!Directory.Exists(destinationDirectoryName)) @@ -210,7 +241,14 @@ public static Task ExtractToDirectoryAsync(Stream source, string destinationDire /// to overwrite files and directories in ; to avoid overwriting, and throw if any files or directories are found with existing names. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. + /// The file path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// or is empty. + /// An I/O exception occurred. public static void ExtractToDirectory(string sourceFileName, string destinationDirectoryName, bool overwriteFiles) { ArgumentException.ThrowIfNullOrEmpty(sourceFileName); @@ -222,7 +260,7 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD if (!File.Exists(sourceFileName)) { - throw new FileNotFoundException(string.Format(SR.IO_FileNotFound, sourceFileName)); + throw new FileNotFoundException(string.Format(SR.IO_FileNotFound_FileName, sourceFileName)); } if (!Directory.Exists(destinationDirectoryName)) @@ -245,7 +283,14 @@ public static void ExtractToDirectory(string sourceFileName, string destinationD /// A task that represents the asynchronous extraction operation. /// Files of type , or can only be extracted in Unix platforms. /// Elevation is required to extract a or to disk. + /// or is . + /// The directory path was not found. + /// The file path was not found. /// Operation not permitted due to insufficient permissions. + /// Extracting tar entry would have resulted in a file outside the specified destination directory. + /// -or- + /// or is empty. + /// An I/O exception occurred. public static Task ExtractToDirectoryAsync(string sourceFileName, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) @@ -261,7 +306,7 @@ public static Task ExtractToDirectoryAsync(string sourceFileName, string destina if (!File.Exists(sourceFileName)) { - return Task.FromException(new FileNotFoundException(string.Format(SR.IO_FileNotFound, sourceFileName))); + return Task.FromException(new FileNotFoundException(string.Format(SR.IO_FileNotFound_FileName, sourceFileName))); } if (!Directory.Exists(destinationDirectoryName)) @@ -283,23 +328,22 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); - char[] entryNameBuffer = ArrayPool.Shared.Rent(DefaultCapacity); - - try + bool skipBaseDirRecursion = false; + if (includeBaseDirectory) { - if (includeBaseDirectory) - { - writer.WriteEntry(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer)); - } + writer.WriteEntry(di.FullName, GetEntryNameForBaseDirectory(di.Name)); + skipBaseDirRecursion = (di.Attributes & FileAttributes.ReparsePoint) != 0; + } - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) - { - writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer)); - } + if (skipBaseDirRecursion) + { + // The base directory is a symlink, do not recurse into it + return; } - finally + + foreach (FileSystemInfo file in GetFileSystemEnumerationForCreation(sourceDirectoryName)) { - ArrayPool.Shared.Return(entryNameBuffer); + writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length)); } } } @@ -339,44 +383,58 @@ private static async Task CreateFromDirectoryInternalAsync(string sourceDirector DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); - char[] entryNameBuffer = ArrayPool.Shared.Rent(DefaultCapacity); - - try + bool skipBaseDirRecursion = false; + if (includeBaseDirectory) { - if (includeBaseDirectory) - { - await writer.WriteEntryAsync(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); - } + await writer.WriteEntryAsync(di.FullName, GetEntryNameForBaseDirectory(di.Name), cancellationToken).ConfigureAwait(false); + skipBaseDirRecursion = (di.Attributes & FileAttributes.ReparsePoint) != 0; + } - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) - { - await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); - } + if (skipBaseDirRecursion) + { + // The base directory is a symlink, do not recurse into it + return; } - finally + + foreach (FileSystemInfo file in GetFileSystemEnumerationForCreation(sourceDirectoryName)) { - ArrayPool.Shared.Return(entryNameBuffer); + await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length), cancellationToken).ConfigureAwait(false); } } } + // Generates a recursive enumeration of the filesystem entries inside the specified source directory, while + // making sure that directory symlinks do not get recursed. + private static IEnumerable GetFileSystemEnumerationForCreation(string sourceDirectoryName) + { + return new FileSystemEnumerable( + directory: sourceDirectoryName, + transform: (ref FileSystemEntry entry) => entry.ToFileSystemInfo(), + options: new EnumerationOptions() + { + RecurseSubdirectories = true + }) + { + ShouldRecursePredicate = IsNotADirectorySymlink + }; + + static bool IsNotADirectorySymlink(ref FileSystemEntry entry) => entry.IsDirectory && (entry.Attributes & FileAttributes.ReparsePoint) == 0; + } + // Determines what should be the base path for all the entries when creating an archive. private static string GetBasePathForCreateFromDirectory(DirectoryInfo di, bool includeBaseDirectory) => includeBaseDirectory && di.Parent != null ? di.Parent.FullName : di.FullName; // Constructs the entry name used for a filesystem entry when creating an archive. - private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength, ref char[] entryNameBuffer) + private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength) { - int entryNameLength = file.FullName.Length - basePathLength; - Debug.Assert(entryNameLength > 0); - - bool isDirectory = file.Attributes.HasFlag(FileAttributes.Directory); - return ArchivingUtils.EntryFromPath(file.FullName, basePathLength, entryNameLength, ref entryNameBuffer, appendPathSeparator: isDirectory); + bool isDirectory = (file.Attributes & FileAttributes.Directory) != 0; + return TarHelpers.EntryFromPath(file.FullName.AsSpan(basePathLength), appendPathSeparator: isDirectory); } - private static string GetEntryNameForBaseDirectory(string name, ref char[] entryNameBuffer) + private static string GetEntryNameForBaseDirectory(string name) { - return ArchivingUtils.EntryFromPath(name, 0, name.Length, ref entryNameBuffer, appendPathSeparator: true); + return TarHelpers.EntryFromPath(name, appendPathSeparator: true); } // Extracts an archive into the specified directory. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs index e89c9d4579dd38..ce37a74c304c80 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs @@ -20,7 +20,7 @@ internal sealed partial class TarHeader // Attempts to retrieve the next header from the specified tar archive stream. // Throws if end of stream is reached or if any data type conversion fails. // Returns a valid TarHeader object if the attributes were read successfully, null otherwise. - internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat) + internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock) { // The four supported formats have a header that fits in the default record size Span buffer = stackalloc byte[TarHelpers.RecordSize]; @@ -28,7 +28,7 @@ internal sealed partial class TarHeader archiveStream.ReadExactly(buffer); TarHeader? header = TryReadAttributes(initialFormat, buffer); - if (header != null) + if (header != null && processDataBlock) { header.ProcessDataBlock(archiveStream, copyData); } @@ -39,7 +39,7 @@ internal sealed partial class TarHeader // Asynchronously attempts read all the fields of the next header. // Throws if end of stream is reached or if any data type conversion fails. // Returns true if all the attributes were read successfully, false otherwise. - internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, CancellationToken cancellationToken) + internal static async ValueTask TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -50,7 +50,7 @@ internal sealed partial class TarHeader await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); TarHeader? header = TryReadAttributes(initialFormat, buffer.Span); - if (header != null) + if (header != null && processDataBlock) { await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false); } @@ -180,7 +180,7 @@ internal void ReplaceNormalAttributesWithExtended(Dictionary? di // will get all the data section read and the stream pointer positioned at the beginning of the next header. // - Block, Character, Directory, Fifo, HardLink and SymbolicLink typeflag entries have no data section so the archive stream pointer will be positioned at the beginning of the next header. // - All other typeflag entries with a data section will generate a stream wrapping the data section: SeekableSubReadStream for seekable archive streams, and SubReadStream for unseekable archive streams. - private void ProcessDataBlock(Stream archiveStream, bool copyData) + internal void ProcessDataBlock(Stream archiveStream, bool copyData) { bool skipBlockAlignmentPadding = true; @@ -199,6 +199,10 @@ private void ProcessDataBlock(Stream archiveStream, bool copyData) case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section + if (_size > 0) + { + throw new InvalidDataException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); + } break; case TarEntryType.RegularFile: case TarEntryType.V7RegularFile: // Treated as regular file @@ -257,6 +261,10 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca case TarEntryType.HardLink: case TarEntryType.SymbolicLink: // No data section + if (_size > 0) + { + throw new InvalidDataException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); + } break; case TarEntryType.RegularFile: case TarEntryType.V7RegularFile: // Treated as regular file @@ -311,6 +319,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { MemoryStream copiedData = new MemoryStream(); TarHelpers.CopyBytes(archiveStream, copiedData, _size); + // Reset position pointer so the user can do the first DataStream read from the beginning + copiedData.Position = 0; return copiedData; } @@ -336,6 +346,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { MemoryStream copiedData = new MemoryStream(); await TarHelpers.CopyBytesAsync(archiveStream, copiedData, size, cancellationToken).ConfigureAwait(false); + // Reset position pointer so the user can do the first DataStream read from the beginning + copiedData.Position = 0; return copiedData; } @@ -357,30 +369,30 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca { return null; } - int checksum = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(spanChecksum); + int checksum = (int)TarHelpers.ParseOctal(spanChecksum); // Zero checksum means the whole header is empty if (checksum == 0) { return null; } - long size = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); + long size = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); if (size < 0) { - throw new FormatException(string.Format(SR.TarSizeFieldNegative)); + throw new InvalidDataException(string.Format(SR.TarSizeFieldNegative)); } // Continue with the rest of the fields that require no special checks TarHeader header = new(initialFormat, name: TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)), - mode: TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), - mTime: TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), + mode: (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), + mTime: TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch((long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), typeFlag: (TarEntryType)buffer[FieldLocations.TypeFlag]) { _checksum = checksum, _size = size, - _uid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), - _gid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), + _uid = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), + _gid = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)) }; @@ -396,12 +408,13 @@ TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or - TarEntryType.SparseFile or TarEntryType.TapeVolume => TarEntryFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. TarEntryType.V7RegularFile => TarEntryFormat.V7, + TarEntryType.SparseFile => throw new NotSupportedException(string.Format(SR.TarEntryTypeNotSupported, header._typeFlag)), + // We can quickly determine the *minimum* possible format if the entry type // is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU _ => (header._typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7 @@ -425,16 +438,23 @@ private void ReadMagicAttribute(Span buffer) } // When the magic field is set, the archive is newer than v7. - _magic = Encoding.ASCII.GetString(magic); - - if (_magic == GnuMagic) + if (magic.SequenceEqual(GnuMagicBytes)) { + _magic = GnuMagic; _format = TarEntryFormat.Gnu; } - else if (_format == TarEntryFormat.V7 && _magic == UstarMagic) + else if (magic.SequenceEqual(UstarMagicBytes)) { - // Important: Only change to ustar if we had not changed the format to pax already - _format = TarEntryFormat.Ustar; + _magic = UstarMagic; + if (_format == TarEntryFormat.V7) + { + // Important: Only change to ustar if we had not changed the format to pax already + _format = TarEntryFormat.Ustar; + } + } + else + { + _magic = Encoding.ASCII.GetString(magic); } } @@ -448,19 +468,47 @@ private void ReadVersionAttribute(Span buffer) } Span version = buffer.Slice(FieldLocations.Version, FieldLengths.Version); + switch (_format) + { + case TarEntryFormat.Ustar or TarEntryFormat.Pax: + // The POSIX formats have a 6 byte Magic "ustar\0", followed by a 2 byte Version "00" + if (!version.SequenceEqual(UstarVersionBytes)) + { + // Check for gnu version header for mixed case + if (!version.SequenceEqual(GnuVersionBytes)) + { + throw new InvalidDataException(string.Format(SR.TarPosixFormatExpected, _name)); + } - _version = Encoding.ASCII.GetString(version); + _version = GnuVersion; + } + else + { + _version = UstarVersion; + } + break; - // The POSIX formats have a 6 byte Magic "ustar\0", followed by a 2 byte Version "00" - if ((_format is TarEntryFormat.Ustar or TarEntryFormat.Pax) && _version != UstarVersion) - { - throw new FormatException(string.Format(SR.TarPosixFormatExpected, _name)); - } + case TarEntryFormat.Gnu: + // The GNU format has a Magic+Version 8 byte string "ustar \0" + if (!version.SequenceEqual(GnuVersionBytes)) + { + // Check for ustar or pax version header for mixed case + if (!version.SequenceEqual(UstarVersionBytes)) + { + throw new InvalidDataException(string.Format(SR.TarGnuFormatExpected, _name)); + } - // The GNU format has a Magic+Version 8 byte string "ustar \0" - if (_format == TarEntryFormat.Gnu && _version != GnuVersion) - { - throw new FormatException(string.Format(SR.TarGnuFormatExpected, _name)); + _version = UstarVersion; + } + else + { + _version = GnuVersion; + } + break; + + default: + _version = Encoding.ASCII.GetString(version); + break; } } @@ -477,10 +525,10 @@ private void ReadPosixAndGnuSharedAttributes(Span buffer) if (_typeFlag is TarEntryType.CharacterDevice or TarEntryType.BlockDevice) { // Major number for a character device or block device entry. - _devMajor = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); + _devMajor = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); // Minor number for a character device or block device entry. - _devMinor = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); + _devMinor = (int)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); } } @@ -489,10 +537,10 @@ private void ReadPosixAndGnuSharedAttributes(Span buffer) private void ReadGnuAttributes(Span buffer) { // Convert byte arrays - long aTime = TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); + long aTime = (long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); _aTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(aTime); - long cTime = TarHelpers.GetTenBaseLongFromOctalAsciiChars(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); + long cTime = (long)TarHelpers.ParseOctal(buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); _cTime = TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch(cTime); // TODO: Read the bytes of the currently unsupported GNU fields, in case user wants to write this entry into another GNU archive, they need to be preserved. https://github.com/dotnet/runtime/issues/68230 @@ -518,11 +566,23 @@ private void ReadUstarAttributes(Span buffer) // Throws if end of stream is reached or if an attribute is malformed. private void ReadExtendedAttributesBlock(Stream archiveStream) { - byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid(); - if (buffer != null) + if (_size != 0) { - archiveStream.ReadExactly(buffer); - ReadExtendedAttributesFromBuffer(buffer, _name); + ValidateSize(); + + byte[]? buffer = null; + Span span = _size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)_size)); + span = span.Slice(0, (int)_size); + + archiveStream.ReadExactly(span); + ReadExtendedAttributesFromBuffer(span, _name); + + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } @@ -531,48 +591,43 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) private async ValueTask ReadExtendedAttributesBlockAsync(Stream archiveStream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid(); - if (buffer != null) + + if (_size != 0) { - await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); - ReadExtendedAttributesFromBuffer(buffer, _name); - } - } + ValidateSize(); + byte[] buffer = ArrayPool.Shared.Rent((int)_size); + Memory memory = buffer.AsMemory(0, (int)_size); - // Return a byte array if the size field has a valid value for extended attributes. Otherwise, return null, or throw. - private byte[]? CreateExtendedAttributesBufferIfSizeIsValid() - { - Debug.Assert(_typeFlag is TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes); + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadExtendedAttributesFromBuffer(memory.Span, _name); - // It is not expected that the extended attributes data section will be longer than Array.MaxLength, considering - // the size field is 12 bytes long, which fits a number with a value under int.MaxValue. - if (_size > Array.MaxLength) - { - throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); + ArrayPool.Shared.Return(buffer); } + } - if (_size == 0) + private void ValidateSize() + { + if ((uint)_size > (uint)Array.MaxLength) { - return null; + ThrowSizeFieldTooLarge(); } - return new byte[(int)_size]; + [DoesNotReturn] + void ThrowSizeFieldTooLarge() => + throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); } // Returns a dictionary containing the extended attributes collected from the provided byte buffer. private void ReadExtendedAttributesFromBuffer(ReadOnlySpan buffer, string name) { - string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer); - - using StringReader reader = new(dataAsString); + buffer = TarHelpers.TrimEndingNullsAndSpaces(buffer); - while (TryGetNextExtendedAttribute(reader, out string? key, out string? value)) + while (TryGetNextExtendedAttribute(ref buffer, out string? key, out string? value)) { - if (ExtendedAttributes.ContainsKey(key)) + if (!ExtendedAttributes.TryAdd(key, value)) { - throw new FormatException(string.Format(SR.TarDuplicateExtendedAttribute, name)); + throw new InvalidDataException(string.Format(SR.TarDuplicateExtendedAttribute, name)); } - ExtendedAttributes.Add(key, value); } } @@ -581,11 +636,23 @@ private void ReadExtendedAttributesFromBuffer(ReadOnlySpan buffer, string // Throws if end of stream is reached. private void ReadGnuLongPathDataBlock(Stream archiveStream) { - byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid(); - if (buffer != null) + if (_size != 0) { - archiveStream.ReadExactly(buffer); - ReadGnuLongPathDataFromBuffer(buffer); + ValidateSize(); + + byte[]? buffer = null; + Span span = _size <= 256 ? + stackalloc byte[256] : + (buffer = ArrayPool.Shared.Rent((int)_size)); + span = span.Slice(0, (int)_size); + + archiveStream.ReadExactly(span); + ReadGnuLongPathDataFromBuffer(span); + + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } } } @@ -595,11 +662,17 @@ private void ReadGnuLongPathDataBlock(Stream archiveStream) private async ValueTask ReadGnuLongPathDataBlockAsync(Stream archiveStream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid(); - if (buffer != null) + + if (_size != 0) { - await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); - ReadGnuLongPathDataFromBuffer(buffer); + ValidateSize(); + byte[] buffer = ArrayPool.Shared.Rent((int)_size); + Memory memory = buffer.AsMemory(0, (int)_size); + + await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false); + ReadGnuLongPathDataFromBuffer(memory.Span); + + ArrayPool.Shared.Return(buffer); } } @@ -618,61 +691,64 @@ private void ReadGnuLongPathDataFromBuffer(ReadOnlySpan buffer) } } - // Return a byte array if the size field has a valid value for GNU long metadata entry data. Otherwise, return null, or throw. - private byte[]? CreateGnuLongDataBufferIfSizeIsValid() - { - Debug.Assert(_typeFlag is TarEntryType.LongLink or TarEntryType.LongPath); - - if (_size > Array.MaxLength) - { - throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString())); - } - - if (_size == 0) - { - return null; - } - - return new byte[(int)_size]; - } - - // Tries to collect the next extended attribute from the string wrapped by the specified reader. + // Tries to collect the next extended attribute from the string. // Extended attributes are saved in the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html // "LENGTH KEY=VALUE\n" // Where LENGTH is the total number of bytes of that line, from LENGTH itself to the endline, inclusive. // Throws if end of stream is reached or if an attribute is malformed. private static bool TryGetNextExtendedAttribute( - StringReader reader, + ref ReadOnlySpan buffer, [NotNullWhen(returnValue: true)] out string? key, [NotNullWhen(returnValue: true)] out string? value) { key = null; value = null; - string? nextLine = reader.ReadLine(); - if (string.IsNullOrWhiteSpace(nextLine)) + // Slice off the next line. + int newlinePos = buffer.IndexOf((byte)'\n'); + if (newlinePos < 0) { return false; } + ReadOnlySpan line = buffer.Slice(0, newlinePos); + + // Update buffer to point to the next line for the next call + buffer = buffer.Slice(newlinePos + 1); - StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries; + // Find the end of the length and remove everything up through it. + int spacePos = line.IndexOf((byte)' '); + if (spacePos < 0) + { + return false; + } + line = line.Slice(spacePos + 1).TrimStart((byte)' '); - string[] attributeArray = nextLine.Split(' ', 2, splitOptions); - if (attributeArray.Length != 2) + // If there are any more spaces, it's malformed. + if (line.IndexOf((byte)' ') >= 0) { return false; } - string[] keyAndValueArray = attributeArray[1].Split('=', 2, splitOptions); - if (keyAndValueArray.Length != 2) + // Find the equal separator. + int equalPos = line.IndexOf((byte)'='); + if (equalPos < 0) { return false; } - key = keyAndValueArray[0]; - value = keyAndValueArray[1]; + ReadOnlySpan keySlice = line.Slice(0, equalPos); + ReadOnlySpan valueSlice = line.Slice(equalPos + 1); + + // If the value contains an =, it's malformed. + if (valueSlice.IndexOf((byte)'=') >= 0) + { + return false; + } + // Return the parsed key and value. + key = Encoding.UTF8.GetString(keySlice); + value = Encoding.UTF8.GetString(valueSlice); return true; } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index ad5cdae22d0315..e1166a066814b3 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -1,10 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; +using System.Buffers.Text; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Numerics; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -14,16 +17,12 @@ namespace System.Formats.Tar // Writes header attributes of a tar archive entry. internal sealed partial class TarHeader { - private static ReadOnlySpan PaxMagicBytes => "ustar\0"u8; - private static ReadOnlySpan PaxVersionBytes => "00"u8; + private static ReadOnlySpan UstarMagicBytes => "ustar\0"u8; + private static ReadOnlySpan UstarVersionBytes => "00"u8; private static ReadOnlySpan GnuMagicBytes => "ustar "u8; private static ReadOnlySpan GnuVersionBytes => " \0"u8; - // Extended Attribute entries have a special format in the Name field: - // "{dirName}/PaxHeaders.{processId}/{fileName}{trailingSeparator}" - private const string PaxHeadersFormat = "{0}/PaxHeaders.{1}/{2}{3}"; - // Predefined text for the Name field of a GNU long metadata entry. Applies for both LongPath ('L') and LongLink ('K'). private const string GnuLongMetadataName = "././@LongLink"; @@ -61,7 +60,7 @@ private long WriteV7FieldsToBuffer(Span buffer) long actualLength = GetTotalDataBytesToWrite(); TarEntryType actualEntryType = TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.V7, _typeFlag); - int tmpChecksum = WriteName(buffer, out _); + int tmpChecksum = WriteName(buffer); tmpChecksum += WriteCommonFields(buffer, actualLength, actualEntryType); _checksum = WriteChecksum(tmpChecksum, buffer); @@ -215,7 +214,7 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C // Second, we determine if we need a preceding LongPath, and write it if needed if (_name.Length > FieldLengths.Name) { - TarHeader longPathHeader = await GetGnuLongMetadataHeaderAsync(TarEntryType.LongPath, _name, cancellationToken).ConfigureAwait(false); + TarHeader longPathHeader = GetGnuLongMetadataHeader(TarEntryType.LongPath, _name); await longPathHeader.WriteAsGnuInternalAsync(archiveStream, buffer, cancellationToken).ConfigureAwait(false); buffer.Span.Clear(); // Reset it to reuse it } @@ -227,34 +226,8 @@ internal async Task WriteAsGnuAsync(Stream archiveStream, Memory buffer, C // Creates and returns a GNU long metadata header, with the specified long text written into its data stream. private static TarHeader GetGnuLongMetadataHeader(TarEntryType entryType, string longText) { - TarHeader longMetadataHeader = GetDefaultGnuLongMetadataHeader(longText.Length, entryType); - Debug.Assert(longMetadataHeader._dataStream != null); - - longMetadataHeader._dataStream.Write(Encoding.UTF8.GetBytes(longText)); - longMetadataHeader._dataStream.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning - - return longMetadataHeader; - } - - // Asynchronously creates and returns a GNU long metadata header, with the specified long text written into its data stream. - private static async Task GetGnuLongMetadataHeaderAsync(TarEntryType entryType, string longText, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - TarHeader longMetadataHeader = GetDefaultGnuLongMetadataHeader(longText.Length, entryType); - Debug.Assert(longMetadataHeader._dataStream != null); - - await longMetadataHeader._dataStream.WriteAsync(Encoding.UTF8.GetBytes(longText), cancellationToken).ConfigureAwait(false); - longMetadataHeader._dataStream.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning - - return longMetadataHeader; - } - - // Constructs a GNU metadata header with default values for the specified entry type. - private static TarHeader GetDefaultGnuLongMetadataHeader(int longTextLength, TarEntryType entryType) - { - Debug.Assert((entryType is TarEntryType.LongPath && longTextLength > FieldLengths.Name) || - (entryType is TarEntryType.LongLink && longTextLength > FieldLengths.LinkName)); + Debug.Assert((entryType is TarEntryType.LongPath && longText.Length > FieldLengths.Name) || + (entryType is TarEntryType.LongLink && longText.Length > FieldLengths.LinkName)); TarHeader longMetadataHeader = new(TarEntryFormat.Gnu); @@ -264,7 +237,7 @@ private static TarHeader GetDefaultGnuLongMetadataHeader(int longTextLength, Tar longMetadataHeader._gid = 0; longMetadataHeader._mTime = DateTimeOffset.MinValue; // 0 longMetadataHeader._typeFlag = entryType; - longMetadataHeader._dataStream = new MemoryStream(); + longMetadataHeader._dataStream = new MemoryStream(Encoding.UTF8.GetBytes(longText)); return longMetadataHeader; } @@ -302,7 +275,7 @@ private void WriteAsGnuSharedInternal(Span buffer, out long actualLength) { actualLength = GetTotalDataBytesToWrite(); - int tmpChecksum = WriteName(buffer, out _); + int tmpChecksum = WriteName(buffer); tmpChecksum += WriteCommonFields(buffer, actualLength, TarHelpers.GetCorrectTypeFlagForFormat(TarEntryFormat.Gnu, _typeFlag)); tmpChecksum += WriteGnuMagicAndVersion(buffer); tmpChecksum += WritePosixAndGnuSharedFields(buffer); @@ -320,13 +293,13 @@ private void WriteAsPaxExtendedAttributes(Stream archiveStream, Span buffe } // Asynchronously writes the current header as a PAX Extended Attributes entry into the archive stream and returns the value of the final checksum. - private async Task WriteAsPaxExtendedAttributesAsync(Stream archiveStream, Memory buffer, Dictionary extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken) + private Task WriteAsPaxExtendedAttributesAsync(Stream archiveStream, Memory buffer, Dictionary extendedAttributes, bool isGea, int globalExtendedAttributesEntryNumber, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); WriteAsPaxExtendedAttributesShared(isGea, globalExtendedAttributesEntryNumber); - _dataStream = await GenerateExtendedAttributesDataStreamAsync(extendedAttributes, cancellationToken).ConfigureAwait(false); - await WriteAsPaxInternalAsync(archiveStream, buffer, cancellationToken).ConfigureAwait(false); + _dataStream = GenerateExtendedAttributesDataStream(extendedAttributes); + return WriteAsPaxInternalAsync(archiveStream, buffer, cancellationToken); } // Initializes the name, mode and type flag of a PAX extended attributes entry. @@ -385,55 +358,67 @@ private void WriteAsPaxSharedInternal(Span buffer, out long actualLength) _checksum = WriteChecksum(tmpChecksum, buffer); } - // All formats save in the name byte array only the ASCII bytes that fit. The full string is returned in the out byte array. - private int WriteName(Span buffer, out byte[] fullNameBytes) + // All formats save in the name byte array only the ASCII bytes that fit. + private int WriteName(Span buffer) { - fullNameBytes = Encoding.ASCII.GetBytes(_name); - int nameBytesLength = Math.Min(fullNameBytes.Length, FieldLengths.Name); - int checksum = WriteLeftAlignedBytesAndGetChecksum(fullNameBytes.AsSpan(0, nameBytesLength), buffer.Slice(FieldLocations.Name, FieldLengths.Name)); - return checksum; + ReadOnlySpan src = _name.AsSpan(0, Math.Min(_name.Length, FieldLengths.Name)); + Span dest = buffer.Slice(FieldLocations.Name, FieldLengths.Name); + int encoded = Encoding.ASCII.GetBytes(src, dest); + return Checksum(dest.Slice(0, encoded)); } // Ustar and PAX save in the name byte array only the ASCII bytes that fit, and the rest of that string is saved in the prefix field. private int WritePosixName(Span buffer) { - int checksum = WriteName(buffer, out byte[] fullNameBytes); - if (fullNameBytes.Length > FieldLengths.Name) + int checksum = WriteName(buffer); + + if (_name.Length > FieldLengths.Name) { - int prefixBytesLength = Math.Min(fullNameBytes.Length - FieldLengths.Name, FieldLengths.Name); - checksum += WriteLeftAlignedBytesAndGetChecksum(fullNameBytes.AsSpan(FieldLengths.Name, prefixBytesLength), buffer.Slice(FieldLocations.Prefix, FieldLengths.Prefix)); + int prefixBytesLength = Math.Min(_name.Length - FieldLengths.Name, FieldLengths.Name); + Span remaining = prefixBytesLength <= 256 ? + stackalloc byte[prefixBytesLength] : + new byte[prefixBytesLength]; + + int encoded = Encoding.ASCII.GetBytes(_name.AsSpan(FieldLengths.Name), remaining); + Debug.Assert(encoded == remaining.Length); + + checksum += WriteLeftAlignedBytesAndGetChecksum(remaining, buffer.Slice(FieldLocations.Prefix, FieldLengths.Prefix)); } + return checksum; } // Writes all the common fields shared by all formats into the specified spans. private int WriteCommonFields(Span buffer, long actualLength, TarEntryType actualEntryType) { + // Don't write an empty LinkName if the entry is a hardlink or symlink + Debug.Assert(!string.IsNullOrEmpty(_linkName) ^ (_typeFlag is not TarEntryType.SymbolicLink and not TarEntryType.HardLink)); + int checksum = 0; if (_mode > 0) { - checksum += WriteAsOctal(_mode, buffer, FieldLocations.Mode, FieldLengths.Mode); + checksum += FormatOctal(_mode, buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)); } if (_uid > 0) { - checksum += WriteAsOctal(_uid, buffer, FieldLocations.Uid, FieldLengths.Uid); + checksum += FormatOctal(_uid, buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)); } if (_gid > 0) { - checksum += WriteAsOctal(_gid, buffer, FieldLocations.Gid, FieldLengths.Gid); + checksum += FormatOctal(_gid, buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)); } _size = actualLength; if (_size > 0) { - checksum += WriteAsOctal(_size, buffer, FieldLocations.Size, FieldLengths.Size); + checksum += FormatOctal(_size, buffer.Slice(FieldLocations.Size, FieldLengths.Size)); } - checksum += WriteAsTimestamp(_mTime, buffer, FieldLocations.MTime, FieldLengths.MTime); + checksum += WriteAsTimestamp(_mTime, buffer.Slice(FieldLocations.MTime, FieldLengths.MTime)); char typeFlagChar = (char)actualEntryType; buffer[FieldLocations.TypeFlag] = (byte)typeFlagChar; @@ -441,7 +426,7 @@ private int WriteCommonFields(Span buffer, long actualLength, TarEntryType if (!string.IsNullOrEmpty(_linkName)) { - checksum += WriteAsAsciiString(_linkName, buffer, FieldLocations.LinkName, FieldLengths.LinkName); + checksum += WriteAsAsciiString(_linkName, buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)); } return checksum; @@ -465,8 +450,8 @@ private long GetTotalDataBytesToWrite() // Writes the magic and version fields of a ustar or pax entry into the specified spans. private static int WritePosixMagicAndVersion(Span buffer) { - int checksum = WriteLeftAlignedBytesAndGetChecksum(PaxMagicBytes, buffer.Slice(FieldLocations.Magic, FieldLengths.Magic)); - checksum += WriteLeftAlignedBytesAndGetChecksum(PaxVersionBytes, buffer.Slice(FieldLocations.Version, FieldLengths.Version)); + int checksum = WriteLeftAlignedBytesAndGetChecksum(UstarMagicBytes, buffer.Slice(FieldLocations.Magic, FieldLengths.Magic)); + checksum += WriteLeftAlignedBytesAndGetChecksum(UstarVersionBytes, buffer.Slice(FieldLocations.Version, FieldLengths.Version)); return checksum; } @@ -485,22 +470,22 @@ private int WritePosixAndGnuSharedFields(Span buffer) if (!string.IsNullOrEmpty(_uName)) { - checksum += WriteAsAsciiString(_uName, buffer, FieldLocations.UName, FieldLengths.UName); + checksum += WriteAsAsciiString(_uName, buffer.Slice(FieldLocations.UName, FieldLengths.UName)); } if (!string.IsNullOrEmpty(_gName)) { - checksum += WriteAsAsciiString(_gName, buffer, FieldLocations.GName, FieldLengths.GName); + checksum += WriteAsAsciiString(_gName, buffer.Slice(FieldLocations.GName, FieldLengths.GName)); } if (_devMajor > 0) { - checksum += WriteAsOctal(_devMajor, buffer, FieldLocations.DevMajor, FieldLengths.DevMajor); + checksum += FormatOctal(_devMajor, buffer.Slice(FieldLocations.DevMajor, FieldLengths.DevMajor)); } if (_devMinor > 0) { - checksum += WriteAsOctal(_devMinor, buffer, FieldLocations.DevMinor, FieldLengths.DevMinor); + checksum += FormatOctal(_devMinor, buffer.Slice(FieldLocations.DevMinor, FieldLengths.DevMinor)); } return checksum; @@ -509,8 +494,8 @@ private int WritePosixAndGnuSharedFields(Span buffer) // Saves the gnu-specific fields into the specified spans. private int WriteGnuFields(Span buffer) { - int checksum = WriteAsTimestamp(_aTime, buffer, FieldLocations.ATime, FieldLengths.ATime); - checksum += WriteAsTimestamp(_cTime, buffer, FieldLocations.CTime, FieldLengths.CTime); + int checksum = WriteAsTimestamp(_aTime, buffer.Slice(FieldLocations.ATime, FieldLengths.ATime)); + checksum += WriteAsTimestamp(_cTime, buffer.Slice(FieldLocations.CTime, FieldLengths.CTime)); if (_gnuUnusedBytes != null) { @@ -524,8 +509,18 @@ private int WriteGnuFields(Span buffer) private static void WriteData(Stream archiveStream, Stream dataStream, long actualLength) { dataStream.CopyTo(archiveStream); // The data gets copied from the current position + int paddingAfterData = TarHelpers.CalculatePadding(actualLength); - archiveStream.Write(new byte[paddingAfterData]); + if (paddingAfterData != 0) + { + Debug.Assert(paddingAfterData <= TarHelpers.RecordSize); + + Span padding = stackalloc byte[TarHelpers.RecordSize]; + padding = padding.Slice(0, paddingAfterData); + padding.Clear(); + + archiveStream.Write(padding); + } } // Asynchronously writes the current header's data stream into the archive stream. @@ -534,44 +529,95 @@ private static async Task WriteDataAsync(Stream archiveStream, Stream dataStream cancellationToken.ThrowIfCancellationRequested(); await dataStream.CopyToAsync(archiveStream, cancellationToken).ConfigureAwait(false); // The data gets copied from the current position + int paddingAfterData = TarHelpers.CalculatePadding(actualLength); - await archiveStream.WriteAsync(new byte[paddingAfterData], cancellationToken).ConfigureAwait(false); + if (paddingAfterData != 0) + { + byte[] buffer = ArrayPool.Shared.Rent(paddingAfterData); + Array.Clear(buffer, 0, paddingAfterData); + + await archiveStream.WriteAsync(buffer.AsMemory(0, paddingAfterData), cancellationToken).ConfigureAwait(false); + + ArrayPool.Shared.Return(buffer); + } } // Dumps into the archive stream an extended attribute entry containing metadata of the entry it precedes. private static Stream? GenerateExtendedAttributesDataStream(Dictionary extendedAttributes) { MemoryStream? dataStream = null; + + byte[]? buffer = null; + Span span = stackalloc byte[512]; + if (extendedAttributes.Count > 0) { dataStream = new MemoryStream(); + foreach ((string attribute, string value) in extendedAttributes) { - byte[] entryBytes = GenerateExtendedAttributeKeyValuePairAsByteArray(Encoding.UTF8.GetBytes(attribute), Encoding.UTF8.GetBytes(value)); - dataStream.Write(entryBytes); + // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html + + // The format is: + // "XX attribute=value\n" + // where "XX" is the number of characters in the entry, including those required for the count itself. + // If prepending the length digits increases the number of digits, we need to expand. + int length = 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value); + int originalDigitCount = CountDigits(length), newDigitCount; + length += originalDigitCount; + while ((newDigitCount = CountDigits(length)) != originalDigitCount) + { + length += newDigitCount - originalDigitCount; + originalDigitCount = newDigitCount; + } + Debug.Assert(length == CountDigits(length) + 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value)); + + // Get a large enough buffer if we don't already have one. + if (span.Length < length) + { + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } + span = buffer = ArrayPool.Shared.Rent(length); + } + + // Format the contents. + bool formatted = Utf8Formatter.TryFormat(length, span, out int bytesWritten); + Debug.Assert(formatted); + span[bytesWritten++] = (byte)' '; + bytesWritten += Encoding.UTF8.GetBytes(attribute, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'='; + bytesWritten += Encoding.UTF8.GetBytes(value, span.Slice(bytesWritten)); + span[bytesWritten++] = (byte)'\n'; + + // Write it to the stream. + dataStream.Write(span.Slice(0, bytesWritten)); } - dataStream?.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning + + dataStream.Position = 0; // Ensure it gets written into the archive from the beginning } - return dataStream; - } - // Asynchronously dumps into the archive stream an extended attribute entry containing metadata of the entry it precedes. - private static async Task GenerateExtendedAttributesDataStreamAsync(Dictionary extendedAttributes, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + } - MemoryStream? dataStream = null; - if (extendedAttributes.Count > 0) + return dataStream; + + static int CountDigits(int value) { - dataStream = new MemoryStream(); - foreach ((string attribute, string value) in extendedAttributes) + Debug.Assert(value >= 0); + int digits = 1; + while (true) { - byte[] entryBytes = GenerateExtendedAttributeKeyValuePairAsByteArray(Encoding.UTF8.GetBytes(attribute), Encoding.UTF8.GetBytes(value)); - await dataStream.WriteAsync(entryBytes, cancellationToken).ConfigureAwait(false); + value /= 10; + if (value == 0) break; + digits++; } - dataStream?.Seek(0, SeekOrigin.Begin); // Ensure it gets written into the archive from the beginning + return digits; } - return dataStream; } // Some fields that have a reserved spot in the header, may not fit in such field anymore, but they can fit in the @@ -584,10 +630,12 @@ private void CollectExtendedAttributesFromStandardFieldsIfNeeded() { ExtendedAttributes.Add(PaxEaMTime, TarHelpers.GetTimestampStringFromDateTimeOffset(_mTime)); } + if (!string.IsNullOrEmpty(_gName)) { TryAddStringField(ExtendedAttributes, PaxEaGName, _gName, FieldLengths.GName); } + if (!string.IsNullOrEmpty(_uName)) { TryAddStringField(ExtendedAttributes, PaxEaUName, _uName, FieldLengths.UName); @@ -603,7 +651,6 @@ private void CollectExtendedAttributesFromStandardFieldsIfNeeded() ExtendedAttributes.Add(PaxEaSize, _size.ToString()); } - // Adds the specified string to the dictionary if it's longer than the specified max byte length. static void TryAddStringField(Dictionary extendedAttributes, string key, string value, int maxLength) { @@ -614,63 +661,24 @@ static void TryAddStringField(Dictionary extendedAttributes, str } } - // Generates an extended attribute key value pair string saved into a byte array, following the ISO/IEC 10646-1:2000 standard UTF-8 encoding format. - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html - private static byte[] GenerateExtendedAttributeKeyValuePairAsByteArray(byte[] keyBytes, byte[] valueBytes) - { - // Assuming key="ab" and value="cdef" - - // The " ab=cdef\n" attribute string has a length of 9 chars - int suffixByteCount = 3 + // leading space, equals sign and trailing newline - keyBytes.Length + valueBytes.Length; - - // The count string "9" has a length of 1 char - string suffixByteCountString = suffixByteCount.ToString(); - int firstTotalByteCount = Encoding.ASCII.GetByteCount(suffixByteCountString); - - // If we prepend the count string length to the attribute string, - // the total length increases to 10, which has one more digit - // "9 abc=def\n" - int firstPrefixAndSuffixByteCount = firstTotalByteCount + suffixByteCount; - - // The new count string "10" has an increased length of 2 chars - string prefixAndSuffixByteCountString = firstPrefixAndSuffixByteCount.ToString(); - int realTotalCharCount = Encoding.ASCII.GetByteCount(prefixAndSuffixByteCountString); - - byte[] finalTotalCharCountBytes = Encoding.ASCII.GetBytes(prefixAndSuffixByteCountString); - - // The final string should contain the correct total length now - List bytesList = new(); - - bytesList.AddRange(finalTotalCharCountBytes); - bytesList.Add(TarHelpers.SpaceChar); - bytesList.AddRange(keyBytes); - bytesList.Add(TarHelpers.EqualsChar); - bytesList.AddRange(valueBytes); - bytesList.Add(TarHelpers.NewLineChar); - - Debug.Assert(bytesList.Count == (realTotalCharCount + suffixByteCount)); - - return bytesList.ToArray(); - } - // The checksum accumulator first adds up the byte values of eight space chars, then the final number // is written on top of those spaces on the specified span as ascii. // At the end, it's saved in the header field and the final value returned. - internal int WriteChecksum(int checksum, Span buffer) + internal static int WriteChecksum(int checksum, Span buffer) { // The checksum field is also counted towards the total sum // but as an array filled with spaces - checksum += TarHelpers.SpaceChar * 8; + checksum += (byte)' ' * 8; Span converted = stackalloc byte[FieldLengths.Checksum]; - WriteAsOctal(checksum, converted, 0, converted.Length); + converted.Clear(); + FormatOctal(checksum, converted); Span destination = buffer.Slice(FieldLocations.Checksum, FieldLengths.Checksum); // Checksum field ends with a null and a space - destination[^1] = TarHelpers.SpaceChar; // ' ' - destination[^2] = 0; // '\0' + destination[^1] = (byte)' '; + destination[^2] = (byte)'\0'; int i = destination.Length - 3; int j = converted.Length - 1; @@ -684,7 +692,7 @@ internal int WriteChecksum(int checksum, Span buffer) } else { - destination[i] = TarHelpers.ZeroChar; // Leading zero chars '0' + destination[i] = (byte)'0'; // Leading zero chars } i--; } @@ -697,67 +705,75 @@ private static int WriteLeftAlignedBytesAndGetChecksum(ReadOnlySpan bytesT { Debug.Assert(destination.Length > 1); - int checksum = 0; - - for (int i = 0, j = 0; i < destination.Length && j < bytesToWrite.Length; i++, j++) - { - destination[i] = bytesToWrite[j]; - checksum += destination[i]; - } + // Copy as many bytes as will fit + int numToCopy = Math.Min(bytesToWrite.Length, destination.Length); + bytesToWrite = bytesToWrite.Slice(0, numToCopy); + bytesToWrite.CopyTo(destination); - return checksum; + return Checksum(bytesToWrite); } // Writes the specified bytes aligned to the right, filling all the leading bytes with the zero char 0x30, // ensuring a null terminator is included at the end of the specified span. private static int WriteRightAlignedBytesAndGetChecksum(ReadOnlySpan bytesToWrite, Span destination) { - int checksum = 0; - int i = destination.Length - 1; - int j = bytesToWrite.Length - 1; + Debug.Assert(destination.Length > 1); - while (i >= 0) + // Null terminated + destination[^1] = (byte)'\0'; + + // Copy as many input bytes as will fit + int numToCopy = Math.Min(bytesToWrite.Length, destination.Length - 1); + bytesToWrite = bytesToWrite.Slice(0, numToCopy); + int copyPos = destination.Length - 1 - bytesToWrite.Length; + bytesToWrite.CopyTo(destination.Slice(copyPos)); + + // Fill all leading bytes with zeros + destination.Slice(0, copyPos).Fill((byte)'0'); + + return Checksum(destination); + } + + private static int Checksum(ReadOnlySpan bytes) + { + int checksum = 0; + foreach (byte b in bytes) { - if (i == destination.Length - 1) - { - destination[i] = 0; // null terminated - } - else if (j >= 0) - { - destination[i] = bytesToWrite[j]; - j--; - } - else - { - destination[i] = TarHelpers.ZeroChar; // leading zeros - } - checksum += destination[i]; - i--; + checksum += b; } - return checksum; } // Writes the specified decimal number as a right-aligned octal number and returns its checksum. - internal static int WriteAsOctal(long tenBaseNumber, Span destination, int location, int length) + internal static int FormatOctal(long value, Span destination) { - long octal = TarHelpers.ConvertDecimalToOctal(tenBaseNumber); - byte[] bytes = Encoding.ASCII.GetBytes(octal.ToString()); - return WriteRightAlignedBytesAndGetChecksum(bytes.AsSpan(), destination.Slice(location, length)); + ulong remaining = (ulong)value; + Span digits = stackalloc byte[32]; // longer than any possible octal formatting of a ulong + + int i = digits.Length - 1; + while (true) + { + digits[i] = (byte)('0' + (remaining % 8)); + remaining /= 8; + if (remaining == 0) break; + i--; + } + + return WriteRightAlignedBytesAndGetChecksum(digits.Slice(i), destination); } // Writes the specified DateTimeOffset's Unix time seconds as a right-aligned octal number, and returns its checksum. - private static int WriteAsTimestamp(DateTimeOffset timestamp, Span destination, int location, int length) + private static int WriteAsTimestamp(DateTimeOffset timestamp, Span destination) { long unixTimeSeconds = timestamp.ToUnixTimeSeconds(); - return WriteAsOctal(unixTimeSeconds, destination, location, length); + return FormatOctal(unixTimeSeconds, destination); } // Writes the specified text as an ASCII string aligned to the left, and returns its checksum. - private static int WriteAsAsciiString(string str, Span buffer, int location, int length) + private static int WriteAsAsciiString(string str, Span buffer) { byte[] bytes = Encoding.ASCII.GetBytes(str); - return WriteLeftAlignedBytesAndGetChecksum(bytes.AsSpan(), buffer.Slice(location, length)); + return WriteLeftAlignedBytesAndGetChecksum(bytes.AsSpan(), buffer); } // Gets the special name for the 'name' field in an extended attribute entry. @@ -767,18 +783,15 @@ private static int WriteAsAsciiString(string str, Span buffer, int locatio // - %f: The filename of the file, equivalent to the result of the basename utility on the translated pathname. private string GenerateExtendedAttributeName() { - string? dirName = Path.GetDirectoryName(_name); - dirName = string.IsNullOrEmpty(dirName) ? "." : dirName; - - int processId = Environment.ProcessId; - - string? fileName = Path.GetFileName(_name); - fileName = string.IsNullOrEmpty(fileName) ? "." : fileName; + ReadOnlySpan dirName = Path.GetDirectoryName(_name.AsSpan()); + dirName = dirName.IsEmpty ? "." : dirName; - string trailingSeparator = (_typeFlag is TarEntryType.Directory or TarEntryType.DirectoryList) ? - $"{Path.DirectorySeparatorChar}" : string.Empty; + ReadOnlySpan fileName = Path.GetFileName(_name.AsSpan()); + fileName = fileName.IsEmpty ? "." : fileName; - return string.Format(PaxHeadersFormat, dirName, processId, fileName, trailingSeparator); + return _typeFlag is TarEntryType.Directory or TarEntryType.DirectoryList ? + $"{dirName}/PaxHeaders.{Environment.ProcessId}/{fileName}{Path.DirectorySeparatorChar}" : + $"{dirName}/PaxHeaders.{Environment.ProcessId}/{fileName}"; } // Gets the special name for the 'name' field in a global extended attribute entry. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs index f684dd78fde1c6..613816904c9c4a 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs @@ -47,57 +47,41 @@ public int Compare (string? x, string? y) private static UnixFileMode UMask => s_umask.Value; - /* - Tar files are usually ordered: parent directories come before their child entries. - - They may be unordered. In that case we need to create parent directories before - we know the proper permissions for these directories. - - We create these directories with restrictive permissions. If we encounter an entry for - the directory later, we store the mode to apply it later. - - If the archive doesn't have an entry for the parent directory, we use the default mask. - - The pending modes to be applied are tracked through a reverse-sorted dictionary. - The reverse order is needed to apply permissions to children before their parent. - Otherwise we may apply a restrictive mask to the parent, that prevents us from - changing a child. - */ - + // Use a reverse-sorted dictionary to apply permission to children before their parents. + // Otherwise we may apply a restrictive mask to the parent, that prevents us from changing a child. internal static SortedDictionary? CreatePendingModesDictionary() => new SortedDictionary(s_reverseStringComparer); - internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, SortedDictionary? pendingModes) { - // Restrictive mask for creating the missing parent directories while extracting. + // Minimal permissions required for extracting. const UnixFileMode ExtractPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; Debug.Assert(pendingModes is not null); if (Directory.Exists(fullPath)) { - // Apply permissions to an existing directory when we're overwriting metadata - // or the directory was created as a missing parent (stored in pendingModes). + // Apply permissions to an existing directory. if (mode.HasValue) { + // Ensure we have sufficient permissions to extract in the directory. bool hasExtractPermissions = (mode.Value & ExtractPermissions) == ExtractPermissions; if (hasExtractPermissions) { - bool removed = pendingModes.Remove(fullPath); - if (overwriteMetadata || removed) - { - UnixFileMode umask = UMask; - File.SetUnixFileMode(fullPath, mode.Value & ~umask); - } + pendingModes.Remove(fullPath); + + UnixFileMode umask = UMask; + File.SetUnixFileMode(fullPath, mode.Value & ~umask); } - else if (overwriteMetadata || pendingModes.ContainsKey(fullPath)) + else { - pendingModes[fullPath] = mode.Value; + pendingModes[fullPath] = mode.Value; } } return; } + // If there are missing parents, Directory.CreateDirectory will create them using default permissions. if (mode.HasValue) { // Ensure we have sufficient permissions to extract in the directory. @@ -106,28 +90,13 @@ internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool o pendingModes[fullPath] = mode.Value; mode = ExtractPermissions; } - } - else - { - pendingModes.Add(fullPath, DefaultDirectoryMode); - mode = ExtractPermissions; - } - string parentDir = Path.GetDirectoryName(fullPath)!; - string rootDir = Path.GetPathRoot(parentDir)!; - bool hasMissingParents = false; - for (string dir = parentDir; dir != rootDir && !Directory.Exists(dir); dir = Path.GetDirectoryName(dir)!) - { - pendingModes.Add(dir, DefaultDirectoryMode); - hasMissingParents = true; + Directory.CreateDirectory(fullPath, mode.Value); } - - if (hasMissingParents) + else { - Directory.CreateDirectory(parentDir, ExtractPermissions); + Directory.CreateDirectory(fullPath); } - - Directory.CreateDirectory(fullPath, mode.Value); } internal static void SetPendingModes(SortedDictionary? pendingModes) @@ -145,5 +114,25 @@ internal static void SetPendingModes(SortedDictionary? pen File.SetUnixFileMode(dir.Key, dir.Value & ~umask); } } + + internal static unsafe string EntryFromPath(ReadOnlySpan path, bool appendPathSeparator = false) + { + // Remove leading separators. + int nonSlash = path.IndexOfAnyExcept('/'); + if (nonSlash == -1) + { + nonSlash = path.Length; + } + path = path.Slice(nonSlash); + + // Append a separator if necessary. + return (path.IsEmpty, appendPathSeparator) switch + { + (false, false) => path.ToString(), + (false, true) => string.Concat(path, "/"), + (true, false) => string.Empty, + (true, true) => "/", + }; + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs index e00f6476764aba..e1025b7bdece68 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.IO; -using System.Text; using System.Diagnostics; +using System.Runtime.InteropServices; namespace System.Formats.Tar { @@ -13,10 +13,52 @@ internal static partial class TarHelpers internal static SortedDictionary? CreatePendingModesDictionary() => null; - internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, SortedDictionary? pendingModes) => Directory.CreateDirectory(fullPath); internal static void SetPendingModes(SortedDictionary? pendingModes) => Debug.Assert(pendingModes is null); + + internal static unsafe string EntryFromPath(ReadOnlySpan path, bool appendPathSeparator = false) + { + // Remove leading separators. + int nonSlash = path.IndexOfAnyExcept('/', '\\'); + if (nonSlash == -1) + { + nonSlash = path.Length; + } + path = path.Slice(nonSlash); + + // Replace \ with /, and append a separator if necessary. + + if (path.IsEmpty) + { + return appendPathSeparator ? + "/" : + string.Empty; + } + + fixed (char* pathPtr = &MemoryMarshal.GetReference(path)) + { + return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, (IntPtr)pathPtr, path.Length), static (dest, state) => + { + ReadOnlySpan path = new ReadOnlySpan((char*)state.Item2, state.Length); + path.CopyTo(dest); + if (state.appendPathSeparator) + { + dest[^1] = '/'; + } + + // To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1, + // all slashes should be forward slashes. + int pos; + while ((pos = dest.IndexOf('\\')) >= 0) + { + dest[pos] = '/'; + dest = dest.Slice(pos + 1); + } + }); + } + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs index d4592eb85d4b5f..fb12a2d3faa74e 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs @@ -3,8 +3,11 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,11 +20,6 @@ internal static partial class TarHelpers internal const short RecordSize = 512; internal const int MaxBufferLength = 4096; - internal const int ZeroChar = 0x30; - internal const byte SpaceChar = 0x20; - internal const byte EqualsChar = 0x3d; - internal const byte NewLineChar = 0xa; - // Default mode for TarEntry created for a file-type. private const UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | @@ -117,36 +115,6 @@ internal static int CalculatePadding(long size) return padding; } - // Returns the specified 8-base number as a 10-base number. - internal static int ConvertDecimalToOctal(int value) - { - int multiplier = 1; - int accum = value; - int actual = 0; - while (accum != 0) - { - actual += (accum % 8) * multiplier; - accum /= 8; - multiplier *= 10; - } - return actual; - } - - // Returns the specified 10-base number as an 8-base number. - internal static long ConvertDecimalToOctal(long value) - { - long multiplier = 1; - long accum = value; - long actual = 0; - while (accum != 0) - { - actual += (accum % 8) * multiplier; - accum /= 8; - multiplier *= 10; - } - return actual; - } - // Returns true if all the bytes in the specified array are nulls, false otherwise. internal static bool IsAllNullBytes(Span buffer) => buffer.IndexOfAnyExcept((byte)0) < 0; @@ -191,9 +159,10 @@ internal static bool TryGetStringAsBaseTenInteger(IReadOnlyDictionary buffer) + /// Parses a byte span that represents an ASCII string containing a number in octal base. + internal static T ParseOctal(ReadOnlySpan buffer) where T : struct, INumber { - string str = GetTrimmedAsciiString(buffer); - return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt32(str, fromBase: 8); - } + buffer = TrimEndingNullsAndSpaces(buffer); + buffer = TrimLeadingNullsAndSpaces(buffer); - // Receives a byte array that represents an ASCII string containing a number in octal base. - // Converts the array to an octal base number, then transforms it to ten base and returns it. - internal static long GetTenBaseLongFromOctalAsciiChars(Span buffer) - { - string str = GetTrimmedAsciiString(buffer); - return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt64(str, fromBase: 8); + if (buffer.Length == 0) + { + return T.Zero; + } + + T octalFactor = T.CreateTruncating(8u); + T value = T.Zero; + foreach (byte b in buffer) + { + uint digit = (uint)(b - '0'); + if (digit >= 8) + { + ThrowInvalidNumber(); + } + + value = checked((value * octalFactor) + T.CreateTruncating(digit)); + } + + return value; } + [DoesNotReturn] + private static void ThrowInvalidNumber() => + throw new InvalidDataException(SR.Format(SR.TarInvalidNumber)); + // Returns the string contained in the specified buffer of bytes, // in the specified encoding, removing the trailing null or space chars. private static string GetTrimmedString(ReadOnlySpan buffer, Encoding encoding) + { + buffer = TrimEndingNullsAndSpaces(buffer); + return buffer.IsEmpty ? string.Empty : encoding.GetString(buffer); + } + + internal static ReadOnlySpan TrimEndingNullsAndSpaces(ReadOnlySpan buffer) { int trimmedLength = buffer.Length; - while (trimmedLength > 0 && IsByteNullOrSpace(buffer[trimmedLength - 1])) + while (trimmedLength > 0 && buffer[trimmedLength - 1] is 0 or 32) { trimmedLength--; } - return trimmedLength == 0 ? string.Empty : encoding.GetString(buffer.Slice(0, trimmedLength)); + return buffer.Slice(0, trimmedLength); + } + + private static ReadOnlySpan TrimLeadingNullsAndSpaces(ReadOnlySpan buffer) + { + int newStart = 0; + while (newStart < buffer.Length && buffer[newStart] is 0 or 32) + { + newStart++; + } - static bool IsByteNullOrSpace(byte c) => c is 0 or 32; + return buffer.Slice(newStart); } // Returns the ASCII string contained in the specified buffer of bytes, @@ -292,7 +292,7 @@ internal static async ValueTask SkipBlockAlignmentPaddingAsync(Stream archi } // Throws if the specified entry type is not supported for the specified format. - internal static void ThrowIfEntryTypeNotSupported(TarEntryType entryType, TarEntryFormat archiveFormat) + internal static void ThrowIfEntryTypeNotSupported(TarEntryType entryType, TarEntryFormat archiveFormat, [CallerArgumentExpression("entryType")] string? paramName = null) { switch (archiveFormat) { @@ -366,10 +366,10 @@ TarEntryType.RegularFile or case TarEntryFormat.Unknown: default: - throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat)); + throw new InvalidDataException(string.Format(SR.TarInvalidFormat, archiveFormat)); } - throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupported, entryType, archiveFormat)); + throw new ArgumentException(string.Format(SR.TarEntryTypeNotSupportedInFormat, entryType, archiveFormat), paramName); } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs index 7e951c5243e35c..ae815dc0073b46 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs @@ -19,7 +19,6 @@ public sealed class TarReader : IDisposable, IAsyncDisposable private readonly bool _leaveOpen; private TarEntry? _previouslyReadEntry; private List? _dataStreamsToDispose; - private bool _readFirstEntry; private bool _reachedEndMarkers; internal Stream _archiveStream; @@ -28,15 +27,16 @@ public sealed class TarReader : IDisposable, IAsyncDisposable /// Initializes a instance that can read tar entries from the specified stream, and can optionally leave the stream open upon disposal of this instance. /// /// The stream to read from. - /// to dispose the when this instance is disposed; to leave the stream open. - /// is unreadable. + /// to dispose the when this instance is disposed, as well as all the non-null instances from the entries that were visited by this reader; to leave all the streams open. + /// does not support reading. + /// is . public TarReader(Stream archiveStream, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(archiveStream); if (!archiveStream.CanRead) { - throw new IOException(SR.IO_NotSupported_UnreadableStream); + throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(archiveStream)); } _archiveStream = archiveStream; @@ -44,28 +44,47 @@ public TarReader(Stream archiveStream, bool leaveOpen = false) _previouslyReadEntry = null; _isDisposed = false; - _readFirstEntry = false; _reachedEndMarkers = false; } /// - /// Disposes the current instance, and disposes the streams of all the entries that were read from the archive. + /// Disposes the current instance, and disposes the non-null instances of all the entries that were read from the archive. /// /// The property of any entry can be replaced with a new stream. If the user decides to replace it on a instance that was obtained using a , the underlying stream gets disposed immediately, freeing the of origin from the responsibility of having to dispose it. public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) + { + foreach (Stream s in _dataStreamsToDispose) + { + s.Dispose(); + } + } + } } /// - /// Asynchronously disposes the current instance, and disposes the streams of all the entries that were read from the archive. + /// Asynchronously disposes the current instance, and disposes the non-null instances of all the entries that were read from the archive. /// /// The property of any entry can be replaced with a new stream. If the user decides to replace it on a instance that was obtained using a , the underlying stream gets disposed immediately, freeing the of origin from the responsibility of having to dispose it. public async ValueTask DisposeAsync() { - await DisposeAsync(disposing: true).ConfigureAwait(false); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) + { + foreach (Stream s in _dataStreamsToDispose) + { + await s.DisposeAsync().ConfigureAwait(false); + } + } + } } /// @@ -75,7 +94,7 @@ public async ValueTask DisposeAsync() /// Set it to if the data should not be copied into a new stream. If the underlying stream is unseekable, the user has the responsibility of reading and processing the immediately after calling this method. /// The default value is . /// A instance if a valid entry was found, or if the end of the archive has been reached. - /// The archive is malformed. + /// The entry's data is malformed. /// -or- /// The archive contains entries in different formats. /// -or- @@ -104,11 +123,6 @@ public async ValueTask DisposeAsync() TarHeader? header = TryGetNextEntryHeader(copyData); if (header != null) { - if (!_readFirstEntry) - { - _readFirstEntry = true; - } - TarEntry entry = header._format switch { TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ? @@ -118,6 +132,11 @@ public async ValueTask DisposeAsync() TarEntryFormat.V7 or TarEntryFormat.Unknown or _ => new V7TarEntry(header, this), }; + if (_archiveStream.CanSeek && _archiveStream.Length == _archiveStream.Position) + { + _reachedEndMarkers = true; + } + _previouslyReadEntry = entry; PreserveDataStreamForDisposalIfNeeded(entry); return entry; @@ -135,7 +154,7 @@ public async ValueTask DisposeAsync() /// The default value is . /// The token to monitor for cancellation requests. The default value is . /// A value task containing a instance if a valid entry was found, or if the end of the archive has been reached. - /// The archive is malformed. + /// The archive is malformed. /// -or- /// The archive contains entries in different formats. /// -or- @@ -200,10 +219,10 @@ internal void AdvanceDataStreamIfNeeded() { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; TarHelpers.AdvanceStream(_archiveStream, bytesToSkip); - TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } + TarHelpers.SkipBlockAlignmentPadding(_archiveStream, _previouslyReadEntry._header._size); } } @@ -242,56 +261,10 @@ internal async ValueTask AdvanceDataStreamIfNeededAsync(CancellationToken cancel { long bytesToSkip = _previouslyReadEntry._header._size - dataStream.Position; await TarHelpers.AdvanceStreamAsync(_archiveStream, bytesToSkip, cancellationToken).ConfigureAwait(false); - await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); dataStream.HasReachedEnd = true; // Now the pointer is beyond the limit, so any read attempts should throw } } - } - } - - // Disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private void Dispose(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) - { - foreach (Stream s in _dataStreamsToDispose) - { - s.Dispose(); - } - } - } - finally - { - _isDisposed = true; - } - } - } - - // Asynchronously disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (!_leaveOpen && _dataStreamsToDispose?.Count > 0) - { - foreach (Stream s in _dataStreamsToDispose) - { - await s.DisposeAsync().ConfigureAwait(false); - } - } - } - finally - { - _isDisposed = true; - } + await TarHelpers.SkipBlockAlignmentPaddingAsync(_archiveStream, _previouslyReadEntry._header._size, cancellationToken).ConfigureAwait(false); } } @@ -303,11 +276,6 @@ private async ValueTask DisposeAsync(bool disposing) TarHeader? header = await TryGetNextEntryHeaderAsync(copyData, cancellationToken).ConfigureAwait(false); if (header != null) { - if (!_readFirstEntry) - { - _readFirstEntry = true; - } - TarEntry entry = header._format switch { TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ? @@ -317,6 +285,11 @@ private async ValueTask DisposeAsync(bool disposing) TarEntryFormat.V7 or TarEntryFormat.Unknown or _ => new V7TarEntry(header, this), }; + if (_archiveStream.CanSeek && _archiveStream.Length == _archiveStream.Position) + { + _reachedEndMarkers = true; + } + _previouslyReadEntry = entry; PreserveDataStreamForDisposalIfNeeded(entry); return entry; @@ -335,7 +308,7 @@ private async ValueTask DisposeAsync(bool disposing) { Debug.Assert(!_reachedEndMarkers); - TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown); + TarHeader? header = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true); if (header == null) { @@ -377,7 +350,7 @@ private async ValueTask DisposeAsync(bool disposing) Debug.Assert(!_reachedEndMarkers); - TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, cancellationToken).ConfigureAwait(false); + TarHeader? header = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Unknown, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (header == null) { return null; @@ -413,9 +386,10 @@ private async ValueTask DisposeAsync(bool disposing) // and returns the actual entry with the processed extended attributes saved in the _extendedAttributes dictionary. private bool TryProcessExtendedAttributesHeader(TarHeader extendedAttributesHeader, bool copyData, [NotNullWhen(returnValue: true)] out TarHeader? actualHeader) { - actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax); + // Don't process the data block of the actual entry just yet, because there's a slim chance + // that the extended attributes contain a size that we need to override in the header + actualHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false); - // Now get the actual entry if (actualHeader == null) { return false; @@ -427,12 +401,15 @@ TarEntryType.ExtendedAttributes or TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); } // Replace all the attributes representing standard fields with the extended ones, if any actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes); + // We retrieved the extended attributes, now we can read the data, and always with the right size + actualHeader.ProcessDataBlock(_archiveStream, copyData); + return true; } @@ -442,8 +419,9 @@ TarEntryType.LongLink or { cancellationToken.ThrowIfCancellationRequested(); - // Now get the actual entry - TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, cancellationToken).ConfigureAwait(false); + // Don't process the data block of the actual entry just yet, because there's a slim chance + // that the extended attributes contain a size that we need to override in the header + TarHeader? actualHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Pax, processDataBlock: false, cancellationToken).ConfigureAwait(false); if (actualHeader == null) { return null; @@ -455,18 +433,21 @@ TarEntryType.ExtendedAttributes or TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, actualHeader._typeFlag, TarEntryType.ExtendedAttributes)); } // Can't have two extended attribute metadata entries in a row if (actualHeader._typeFlag is TarEntryType.ExtendedAttributes) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, TarEntryType.ExtendedAttributes, TarEntryType.ExtendedAttributes)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, TarEntryType.ExtendedAttributes, TarEntryType.ExtendedAttributes)); } // Replace all the attributes representing standard fields with the extended ones, if any actualHeader.ReplaceNormalAttributesWithExtended(extendedAttributesHeader.ExtendedAttributes); + // We retrieved the extended attributes, now we can read the data, and always with the right size + actualHeader.ProcessDataBlock(_archiveStream, copyData); + return actualHeader; } @@ -476,7 +457,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta { finalHeader = new(TarEntryFormat.Gnu); - TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu); + TarHeader? secondHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true); // Get the second entry, which is the actual entry if (secondHeader == null) @@ -487,14 +468,14 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have two identical metadata entries in a row if (secondHeader._typeFlag == header._typeFlag) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); } // It's possible to have the two different metadata entries in a row if ((header._typeFlag is TarEntryType.LongLink && secondHeader._typeFlag is TarEntryType.LongPath) || (header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink)) { - TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu); + TarHeader? thirdHeader = TarHeader.TryGetNextHeader(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true); // Get the third entry, which is the actual entry if (thirdHeader == null) @@ -505,7 +486,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have three GNU metadata entries in a row if (thirdHeader._typeFlag is TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); } if (header._typeFlag is TarEntryType.LongLink) @@ -553,7 +534,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta cancellationToken.ThrowIfCancellationRequested(); // Get the second entry, which is the actual entry - TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false); + TarHeader? secondHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (secondHeader == null) { return null; @@ -562,7 +543,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have two identical metadata entries in a row if (secondHeader._typeFlag == header._typeFlag) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, header._typeFlag)); } TarHeader finalHeader; @@ -572,7 +553,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta (header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink)) { // Get the third entry, which is the actual entry - TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, cancellationToken).ConfigureAwait(false); + TarHeader? thirdHeader = await TarHeader.TryGetNextHeaderAsync(_archiveStream, copyData, TarEntryFormat.Gnu, processDataBlock: true, cancellationToken).ConfigureAwait(false); if (thirdHeader == null) { return null; @@ -581,7 +562,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta // Can't have three GNU metadata entries in a row if (thirdHeader._typeFlag is TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); + throw new InvalidDataException(string.Format(SR.TarUnexpectedMetadataEntry, thirdHeader._typeFlag, secondHeader._typeFlag)); } if (header._typeFlag is TarEntryType.LongLink) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs index 7e678bcd918711..a691582178df6a 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs @@ -46,7 +46,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice) @@ -94,16 +94,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { Debug.Assert(entry._header._dataStream == null); - - FileStreamOptions options = new() - { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = fileOptions - }; - - entry._header._dataStream = new FileStream(fullPath, options); + entry._header._dataStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, fileOptions); } return entry; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs index bfb6cf17f11076..7452246f742ed6 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs @@ -22,15 +22,15 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil FileAttributes attributes = File.GetAttributes(fullPath); TarEntryType entryType; - if (attributes.HasFlag(FileAttributes.ReparsePoint)) + if ((attributes & FileAttributes.ReparsePoint) != 0) { entryType = TarEntryType.SymbolicLink; } - else if (attributes.HasFlag(FileAttributes.Directory)) + else if ((attributes & FileAttributes.Directory) != 0) { entryType = TarEntryType.Directory; } - else if (attributes.HasFlag(FileAttributes.Normal) || attributes.HasFlag(FileAttributes.Archive)) + else if ((attributes & (FileAttributes.Normal | FileAttributes.Archive)) != 0) { entryType = Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; } @@ -45,10 +45,10 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; - FileSystemInfo info = attributes.HasFlag(FileAttributes.Directory) ? new DirectoryInfo(fullPath) : new FileInfo(fullPath); + FileSystemInfo info = (attributes & FileAttributes.Directory) != 0 ? new DirectoryInfo(fullPath) : new FileInfo(fullPath); entry._header._mTime = info.LastWriteTimeUtc; entry._header._aTime = info.LastAccessTimeUtc; @@ -63,16 +63,8 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) { - FileStreamOptions options = new() - { - Mode = FileMode.Open, - Access = FileAccess.Read, - Share = FileShare.Read, - Options = fileOptions - }; - Debug.Assert(entry._header._dataStream == null); - entry._header._dataStream = new FileStream(fullPath, options); + entry._header._dataStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, fileOptions); } return entry; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs index 94692316840235..d7b7ceceac3464 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs @@ -25,6 +25,8 @@ public sealed partial class TarWriter : IDisposable, IAsyncDisposable /// /// The stream to write to. /// When using this constructor, is used as the default format of the entries written to the archive using the method. + /// is . + /// does not support writing. public TarWriter(Stream archiveStream) : this(archiveStream, TarEntryFormat.Pax, leaveOpen: false) { @@ -35,6 +37,8 @@ public TarWriter(Stream archiveStream) /// /// The stream to write to. /// to dispose the when this instance is disposed; to leave the stream open. + /// is . + /// is unwritable. public TarWriter(Stream archiveStream, bool leaveOpen = false) : this(archiveStream, TarEntryFormat.Pax, leaveOpen) { @@ -50,7 +54,7 @@ public TarWriter(Stream archiveStream, bool leaveOpen = false) /// to leave the stream open. The default is . /// The recommended format is for its flexibility. /// is . - /// is unwritable. + /// is unwritable. /// is either , or not one of the other enum values. public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pax, bool leaveOpen = false) { @@ -58,7 +62,7 @@ public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pa if (!archiveStream.CanWrite) { - throw new IOException(SR.IO_NotSupported_UnwritableStream); + throw new ArgumentException(SR.IO_NotSupported_UnwritableStream); } if (format is not TarEntryFormat.V7 and not TarEntryFormat.Ustar and not TarEntryFormat.Pax and not TarEntryFormat.Gnu) @@ -84,8 +88,20 @@ public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pa /// public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (_wroteEntries) + { + WriteFinalRecords(); + } + + if (!_leaveOpen) + { + _archiveStream.Dispose(); + } + } } /// @@ -93,8 +109,20 @@ public void Dispose() /// public async ValueTask DisposeAsync() { - await DisposeAsync(disposing: true).ConfigureAwait(false); - GC.SuppressFinalize(this); + if (!_isDisposed) + { + _isDisposed = true; + + if (_wroteEntries) + { + await WriteFinalRecordsAsync().ConfigureAwait(false); + } + + if (!_leaveOpen) + { + await _archiveStream.DisposeAsync().ConfigureAwait(false); + } + } } /// @@ -185,13 +213,15 @@ private async Task ReadFileFromDiskAndWriteToArchiveStreamAsEntryAsync(string fu /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. - /// The entry type of the is not supported for writing. + /// is . /// An I/O problem occurred. public void WriteEntry(TarEntry entry) { ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); WriteEntryInternal(entry); } @@ -226,8 +256,9 @@ public void WriteEntry(TarEntry entry) /// /// /// + /// The entry type is or and the is or empty. /// The archive stream is disposed. - /// The entry type of the is not supported for writing. + /// is . /// An I/O problem occurred. public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken = default) { @@ -238,98 +269,44 @@ public Task WriteEntryAsync(TarEntry entry, CancellationToken cancellationToken ObjectDisposedException.ThrowIf(_isDisposed, this); ArgumentNullException.ThrowIfNull(entry); + ValidateEntryLinkName(entry._header._typeFlag, entry._header._linkName); return WriteEntryAsyncInternal(entry, cancellationToken); } - // Disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private void Dispose(bool disposing) + // Portion of the WriteEntry(entry) method that rents a buffer and writes to the archive. + private void WriteEntryInternal(TarEntry entry) { - if (disposing && !_isDisposed) + Span buffer = stackalloc byte[TarHelpers.RecordSize]; + buffer.Clear(); + + switch (entry.Format) { - try - { - if (_wroteEntries) - { - WriteFinalRecords(); - } + case TarEntryFormat.V7: + entry._header.WriteAsV7(_archiveStream, buffer); + break; + case TarEntryFormat.Ustar: + entry._header.WriteAsUstar(_archiveStream, buffer); + break; - if (!_leaveOpen) + case TarEntryFormat.Pax: + if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes) { - _archiveStream.Dispose(); + entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++); } - } - finally - { - _isDisposed = true; - } - } - } - - // Asynchronously disposes the current instance. - // If 'disposing' is 'false', the method was called from the finalizer. - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing && !_isDisposed) - { - try - { - if (_wroteEntries) + else { - await WriteFinalRecordsAsync().ConfigureAwait(false); + entry._header.WriteAsPax(_archiveStream, buffer); } + break; + case TarEntryFormat.Gnu: + entry._header.WriteAsGnu(_archiveStream, buffer); + break; - if (!_leaveOpen) - { - await _archiveStream.DisposeAsync().ConfigureAwait(false); - } - } - finally - { - _isDisposed = true; - } - } - } - - // Portion of the WriteEntry(entry) method that rents a buffer and writes to the archive. - private void WriteEntryInternal(TarEntry entry) - { - byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); - Span buffer = rented.AsSpan(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger - buffer.Clear(); // Rented arrays aren't clean - try - { - switch (entry.Format) - { - case TarEntryFormat.V7: - entry._header.WriteAsV7(_archiveStream, buffer); - break; - case TarEntryFormat.Ustar: - entry._header.WriteAsUstar(_archiveStream, buffer); - break; - case TarEntryFormat.Pax: - if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes) - { - entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++); - } - else - { - entry._header.WriteAsPax(_archiveStream, buffer); - } - break; - case TarEntryFormat.Gnu: - entry._header.WriteAsGnu(_archiveStream, buffer); - break; - default: - Debug.Assert(entry.Format == TarEntryFormat.Unknown, "Missing format handler"); - throw new FormatException(string.Format(SR.TarInvalidFormat, Format)); - } - } - finally - { - ArrayPool.Shared.Return(rented); + default: + Debug.Assert(entry.Format == TarEntryFormat.Unknown, "Missing format handler"); + throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)); } _wroteEntries = true; @@ -351,7 +328,7 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can TarEntryFormat.Pax when entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes => entry._header.WriteAsPaxGlobalExtendedAttributesAsync(_archiveStream, buffer, _nextGlobalExtendedAttributesEntryNumber++, cancellationToken), TarEntryFormat.Pax => entry._header.WriteAsPaxAsync(_archiveStream, buffer, cancellationToken), TarEntryFormat.Gnu => entry._header.WriteAsGnuAsync(_archiveStream, buffer, cancellationToken), - _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), + _ => throw new InvalidDataException(string.Format(SR.TarInvalidFormat, Format)), }; await task.ConfigureAwait(false); @@ -364,7 +341,9 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can // by two records consisting entirely of zero bytes. private void WriteFinalRecords() { - byte[] emptyRecord = new byte[TarHelpers.RecordSize]; + Span emptyRecord = stackalloc byte[TarHelpers.RecordSize]; + emptyRecord.Clear(); + _archiveStream.Write(emptyRecord); _archiveStream.Write(emptyRecord); } @@ -374,9 +353,14 @@ private void WriteFinalRecords() // This method is called from DisposeAsync, so we don't want to propagate a cancelled CancellationToken. private async ValueTask WriteFinalRecordsAsync() { - byte[] emptyRecord = new byte[TarHelpers.RecordSize]; - await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false); - await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false); + const int TwoRecordSize = TarHelpers.RecordSize * 2; + + byte[] twoEmptyRecords = ArrayPool.Shared.Rent(TwoRecordSize); + Array.Clear(twoEmptyRecords, 0, TwoRecordSize); + + await _archiveStream.WriteAsync(twoEmptyRecords.AsMemory(0, TwoRecordSize), cancellationToken: default).ConfigureAwait(false); + + ArrayPool.Shared.Return(twoEmptyRecords); } private (string, string) ValidateWriteEntryArguments(string fileName, string? entryName) @@ -389,5 +373,16 @@ private async ValueTask WriteFinalRecordsAsync() return (fullPath, actualEntryName); } + + private static void ValidateEntryLinkName(TarEntryType entryType, string? linkName) + { + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + if (string.IsNullOrEmpty(linkName)) + { + throw new ArgumentException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty, "entry"); + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs index 10c5aad7325ade..9969520d96551c 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs @@ -19,14 +19,16 @@ internal UstarTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: /// /// In all platforms: , , , . /// In Unix platforms only: , and . /// /// + /// is . + /// is empty. + /// -or- + /// is not supported in the specified format. public UstarTarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.Ustar, isGea: false) { @@ -36,6 +38,9 @@ public UstarTarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the Ustar format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the Ustar format. public UstarTarEntry(TarEntry other) : base(other, TarEntryFormat.Ustar) { diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs index fd049c05d90c93..4b20f10faf4beb 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs @@ -19,9 +19,11 @@ internal V7TarEntry(TarHeader header, TarReader readerOfOrigin) /// /// The type of the entry. /// A string with the path and file name of this entry. - /// is null or empty. - /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: , , and . + /// is . + /// is empty. + /// -or- + /// is not supported for creating an entry. public V7TarEntry(TarEntryType entryType, string entryName) : base(entryType, entryName, TarEntryFormat.V7, isGea: false) { @@ -30,6 +32,9 @@ public V7TarEntry(TarEntryType entryType, string entryName) /// /// Initializes a new instance by converting the specified entry into the V7 format. /// + /// is a and cannot be converted. + /// -or- + /// The entry type of is not supported for conversion to the V7 format. public V7TarEntry(TarEntry other) : base(other, TarEntryFormat.V7) { diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index f06e639b74f564..c43c8dff343f23 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -40,6 +40,7 @@ + diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs index e9bb630dd3f873..4c13273a30ceaf 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/GnuTarEntry.Tests.cs @@ -19,23 +19,23 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); // These are specific to GNU, but currently the user cannot create them manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.TapeVolume, InitialEntryName)); // The user should not create these entries manually - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs index 8125e45708eb30..e42f1df0ea6ea1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Conversion.Tests.cs @@ -61,6 +61,15 @@ public class PaxTarEntry_Conversion_Tests : TarTestsConversionBase [Fact] public void Constructor_ConversionFromGnu_CharacterDevice() => TestConstructionConversion(TarEntryType.CharacterDevice, TarEntryFormat.Gnu, TarEntryFormat.Pax); + [Fact] + public void Constructor_ConversionFromPaxGEA_ToAny_Throw() + { + Assert.Throws(() => new V7TarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new UstarTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new PaxTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + Assert.Throws(() => new GnuTarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + } + [Theory] [InlineData(TarEntryFormat.V7)] [InlineData(TarEntryFormat.Ustar)] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs index 69e028ae883aa2..0e8bcc952cea5d 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/PaxTarEntry.Tests.cs @@ -19,21 +19,21 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new PaxTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.TapeVolume, InitialEntryName)); // The user should not be creating these entries manually in pax - Assert.Throws(() => new PaxTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new PaxTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs index 6626ca958f25d4..9fa5e57d460c19 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.Conversion.Tests.Base.cs @@ -184,7 +184,7 @@ protected TarEntry InvokeTarEntryConversionConstructor(TarEntryFormat targetForm TarEntryFormat.Ustar => new UstarTarEntry(other), TarEntryFormat.Pax => new PaxTarEntry(other), TarEntryFormat.Gnu => new GnuTarEntry(other), - _ => throw new FormatException($"Unexpected format: {targetFormat}") + _ => throw new InvalidDataException($"Unexpected format: {targetFormat}") }; } } diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs index 8087621f57ae1d..2fdcb34069e50b 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/UstarTarEntry.Tests.cs @@ -19,19 +19,19 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new UstarTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new UstarTarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.V7RegularFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new UstarTarEntry(TarEntryType.TapeVolume, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs index 3f5e41a8f3609f..8856ec4d54809a 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Conversion.Tests.cs @@ -16,25 +16,25 @@ public class V7TarEntry_Conversion_Tests : TarTestsConversionBase [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Ustar() { - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Pax() { - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] public void Constructor_Conversion_UnsupportedEntryTypes_Gnu() { - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs index 36dab66fb24e2f..2bf5471d4fa484 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/V7TarEntry.Tests.cs @@ -20,22 +20,22 @@ public void Constructor_InvalidEntryName() [Fact] public void Constructor_UnsupportedEntryTypes() { - Assert.Throws(() => new V7TarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); + Assert.Throws(() => new V7TarEntry((TarEntryType)byte.MaxValue, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.BlockDevice, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.CharacterDevice, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.ContiguousFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.DirectoryList, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.Fifo, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.LongLink, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.LongPath, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.MultiVolume, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.RegularFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.SparseFile, InitialEntryName)); - Assert.Throws(() => new V7TarEntry(TarEntryType.TapeVolume, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.BlockDevice, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.CharacterDevice, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.ContiguousFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.DirectoryList, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.Fifo, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.LongLink, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.LongPath, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.MultiVolume, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.RegularFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.RenamedOrSymlinked, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.SparseFile, InitialEntryName)); + Assert.Throws(() => new V7TarEntry(TarEntryType.TapeVolume, InitialEntryName)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs index 75a3495d94e5e5..308a9b68ba4def 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs @@ -193,5 +193,65 @@ public void IncludeAllSegmentsOfPath(bool includeBaseDirectory) Assert.Null(reader.GetNextEntry()); } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public void SkipRecursionIntoDirectorySymlinks() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + File.Create(Path.Join(externalDirectory, "file.txt")).Dispose(); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string subDirectory = Path.Join(sourceDirectoryName, "subDirectory"); + Directory.CreateSymbolicLink(subDirectory, externalDirectory); // Should not recurse here + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + using FileStream archiveStream = File.OpenRead(destinationArchive); + using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal("subDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(reader.GetNextEntry()); // file.txt should not be found + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public void SkipRecursionIntoBaseDirectorySymlink() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + string subDirectory = Path.Join(externalDirectory, "subDirectory"); + Directory.CreateDirectory(subDirectory); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateSymbolicLink(sourceDirectoryName, externalDirectory); + + TarFile.CreateFromDirectory(sourceDirectoryName, destinationArchive, includeBaseDirectory: true); // Base directory is a symlink, do not recurse + + using FileStream archiveStream = File.OpenRead(destinationArchive); + using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal("baseDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(reader.GetNextEntry()); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs index ca0cb57fab783b..33b550960ef563 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.Stream.Tests.cs @@ -28,7 +28,7 @@ public void UnwritableStream_Throws() { using MemoryStream archive = new MemoryStream(); using WrappedStream unwritable = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: true); - Assert.Throws(() => TarFile.CreateFromDirectory(sourceDirectoryName: "path",destination: unwritable, includeBaseDirectory: false)); + Assert.Throws(() => TarFile.CreateFromDirectory(sourceDirectoryName: "path",destination: unwritable, includeBaseDirectory: false)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs index c9b4377cd03285..78b051ae33b08e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs @@ -237,5 +237,65 @@ public async Task IncludeAllSegmentsOfPath_Async(bool includeBaseDirectory) } } } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task SkipRecursionIntoDirectorySymlinksAsync() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + File.Create(Path.Join(externalDirectory, "file.txt")).Dispose(); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateDirectory(sourceDirectoryName); + + string subDirectory = Path.Join(sourceDirectoryName, "subDirectory"); + Directory.CreateSymbolicLink(subDirectory, externalDirectory); // Should not recurse here + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: false); + + await using FileStream archiveStream = File.OpenRead(destinationArchive); + await using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal("subDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(await reader.GetNextEntryAsync()); // file.txt should not be found + } + + [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] + public async Task SkipRecursionIntoBaseDirectorySymlinkAsync() + { + using TempDirectory root = new TempDirectory(); + + string destinationArchive = Path.Join(root.Path, "destination.tar"); + + string externalDirectory = Path.Join(root.Path, "externalDirectory"); + Directory.CreateDirectory(externalDirectory); + + string subDirectory = Path.Join(externalDirectory, "subDirectory"); + Directory.CreateDirectory(subDirectory); + + string sourceDirectoryName = Path.Join(root.Path, "baseDirectory"); + Directory.CreateSymbolicLink(sourceDirectoryName, externalDirectory); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName, destinationArchive, includeBaseDirectory: true); // Base directory is a symlink, do not recurse + + await using FileStream archiveStream = File.OpenRead(destinationArchive); + await using TarReader reader = new(archiveStream, leaveOpen: false); + + TarEntry entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal("baseDirectory/", entry.Name); + Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType); + + Assert.Null(await reader.GetNextEntryAsync()); // subDirectory should not be found + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs index 7a67aac64508f7..8dbde4461290a6 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.Stream.Tests.cs @@ -48,7 +48,7 @@ public async Task UnwritableStream_Throws_Async() { await using (WrappedStream unwritable = new WrappedStream(archiveStream, canRead: true, canWrite: false, canSeek: true)) { - await Assert.ThrowsAsync(() => TarFile.CreateFromDirectoryAsync(sourceDirectoryName: "path", destination: unwritable, includeBaseDirectory: false)); + await Assert.ThrowsAsync(() => TarFile.CreateFromDirectoryAsync(sourceDirectoryName: "path", destination: unwritable, includeBaseDirectory: false)); } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs index f741926cdd8ef1..90c5eba6b9ff97 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs @@ -44,6 +44,33 @@ public void NonExistentDirectory_Throws() Assert.Throws(() => TarFile.ExtractToDirectory(sourceFileName: filePath, destinationDirectoryName: dirPath, overwriteFiles: false)); } + [Fact] + public void SetsLastModifiedTimeOnExtractedFiles() + { + using TempDirectory root = new TempDirectory(); + + string inDir = Path.Join(root.Path, "indir"); + string inFile = Path.Join(inDir, "file"); + + string tarFile = Path.Join(root.Path, "file.tar"); + + string outDir = Path.Join(root.Path, "outdir"); + string outFile = Path.Join(outDir, "file"); + + Directory.CreateDirectory(inDir); + File.Create(inFile).Dispose(); + var dt = new DateTime(2001, 1, 2, 3, 4, 5, DateTimeKind.Local); + File.SetLastWriteTime(inFile, dt); + + TarFile.CreateFromDirectory(sourceDirectoryName: inDir, destinationFileName: tarFile, includeBaseDirectory: false); + + Directory.CreateDirectory(outDir); + TarFile.ExtractToDirectory(sourceFileName: tarFile, destinationDirectoryName: outDir, overwriteFiles: false); + + Assert.True(File.Exists(outFile)); + Assert.InRange(File.GetLastWriteTime(outFile).Ticks, dt.AddSeconds(-3).Ticks, dt.AddSeconds(3).Ticks); // include some slop for filesystem granularity + } + [Theory] [InlineData(TestTarFormat.v7)] [InlineData(TestTarFormat.ustar)] @@ -216,13 +243,9 @@ public void UnixFileModes(bool overwrite) Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission2); - // Missing parents are created with DefaultDirectoryMode. - // The mode is not set when overwrite == true if there is no entry and the directory exists before extracting. + // Missing parents are created with CreateDirectoryDefaultMode. Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); - if (!overwrite) - { - AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); - } + AssertFileModeEquals(missingParentPath, CreateDirectoryDefaultMode); Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); AssertFileModeEquals(missingParentDirPath, TestPermission3); diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index 5a68d5a95d5d26..b62196bb5c5d6f 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using Xunit; @@ -29,7 +30,7 @@ public void UnreadableStream_Throws() { using MemoryStream archive = new MemoryStream(); using WrappedStream unreadable = new WrappedStream(archive, canRead: false, canWrite: true, canSeek: true); - Assert.Throws(() => TarFile.ExtractToDirectory(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); + Assert.Throws(() => TarFile.ExtractToDirectory(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); } [Fact] @@ -145,5 +146,36 @@ private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entry Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count()); } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public void Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + TarFile.ExtractToDirectory(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs index 01d2457018ce24..19a28d7f59ce39 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs @@ -56,6 +56,33 @@ public async Task NonExistentDirectory_Throws_Async() } } + [Fact] + public async Task SetsLastModifiedTimeOnExtractedFiles() + { + using TempDirectory root = new TempDirectory(); + + string inDir = Path.Join(root.Path, "indir"); + string inFile = Path.Join(inDir, "file"); + + string tarFile = Path.Join(root.Path, "file.tar"); + + string outDir = Path.Join(root.Path, "outdir"); + string outFile = Path.Join(outDir, "file"); + + Directory.CreateDirectory(inDir); + File.Create(inFile).Dispose(); + var dt = new DateTime(2001, 1, 2, 3, 4, 5, DateTimeKind.Local); + File.SetLastWriteTime(inFile, dt); + + await TarFile.CreateFromDirectoryAsync(sourceDirectoryName: inDir, destinationFileName: tarFile, includeBaseDirectory: false); + + Directory.CreateDirectory(outDir); + await TarFile.ExtractToDirectoryAsync(sourceFileName: tarFile, destinationDirectoryName: outDir, overwriteFiles: false); + + Assert.True(File.Exists(outFile)); + Assert.InRange(File.GetLastWriteTime(outFile).Ticks, dt.AddSeconds(-3).Ticks, dt.AddSeconds(3).Ticks); // include some slop for filesystem granularity + } + [Theory] [InlineData(TestTarFormat.v7)] [InlineData(TestTarFormat.ustar)] @@ -191,8 +218,10 @@ public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async() } } - [Fact] - public async Task UnixFileModes_Async() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task UnixFileModes_Async(bool overwrite) { using TempDirectory source = new TempDirectory(); using TempDirectory destination = new TempDirectory(); @@ -223,27 +252,36 @@ public async Task UnixFileModes_Async() writer.WriteEntry(outOfOrderDir); } - await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: false); - string dirPath = Path.Join(destination.Path, "dir"); + string filePath = Path.Join(destination.Path, "file"); + string missingParentPath = Path.Join(destination.Path, "missing_parent"); + string missingParentDirPath = Path.Join(missingParentPath, "dir"); + string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); + + if (overwrite) + { + File.OpenWrite(filePath).Dispose(); + Directory.CreateDirectory(dirPath); + Directory.CreateDirectory(missingParentDirPath); + Directory.CreateDirectory(outOfOrderDirPath); + } + + await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: overwrite); + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); AssertFileModeEquals(dirPath, TestPermission1); - string filePath = Path.Join(destination.Path, "file"); Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); AssertFileModeEquals(filePath, TestPermission2); - // Missing parents are created with DefaultDirectoryMode. - string missingParentPath = Path.Join(destination.Path, "missing_parent"); + // Missing parents are created with CreateDirectoryDefaultMode. Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); - AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); + AssertFileModeEquals(missingParentPath, CreateDirectoryDefaultMode); - string missingParentDirPath = Path.Join(missingParentPath, "dir"); Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); AssertFileModeEquals(missingParentDirPath, TestPermission3); // Directory modes that are out-of-order are still applied. - string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); Assert.True(Directory.Exists(outOfOrderDirPath), $"{outOfOrderDirPath}' does not exist."); AssertFileModeEquals(outOfOrderDirPath, TestPermission4); } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs index a35a22c1050f56..70c6fcbf8049d4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -44,7 +45,7 @@ public async Task UnreadableStream_Throws_Async() { using (WrappedStream unreadable = new WrappedStream(archive, canRead: false, canWrite: true, canSeek: true)) { - await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); + await Assert.ThrowsAsync(() => TarFile.ExtractToDirectoryAsync(unreadable, destinationDirectoryName: "path", overwriteFiles: false)); } } } @@ -174,5 +175,36 @@ private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEnt } } } + + [Theory] + [InlineData(512)] + [InlineData(512 + 1)] + [InlineData(512 + 512 - 1)] + public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true)) + { + using var writer = new TarWriter(compressor); + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var decompressor = new GZipStream(archive, CompressionMode.Decompress); + using var reader = new TarReader(decompressor); + + using TempDirectory destination = new TempDirectory(); + await TarFile.ExtractToDirectoryAsync(decompressor, destination.Path, overwriteFiles: true); + + Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count()); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs index 14087fb030d020..d86cfa4e34dd47 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -125,5 +126,245 @@ public Task Read_Archive_LongFileName_Over100_Under255_Async(TarEntryFormat form [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] public Task Read_Archive_LongPath_Over255_Async(TarEntryFormat format, TestTarFormat testFormat) => Read_Archive_LongPath_Over255_Async_Internal(format, testFormat); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public Task ReadDataStreamOfTarGzV7Async(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public Task ReadDataStreamOfTarGzUstarAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzPaxAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzPaxGeaAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzOldGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadDataStreamOfTarGzGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzV7Async(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.v7, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzUstarAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.ustar, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzPaxAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzPaxGeaAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.pax_gea, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzOldGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.oldgnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public Task ReadCopiedDataStreamOfTarGzGnuAsync(string testCaseName) => + VerifyDataStreamOfTarGzInternalAsync(TestTarFormat.gnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsGoLangAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsNodeAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public Task ReadDataStreamOfExternalAssetsRsAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsGoLangAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("golang_tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsNodeAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("node-tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public Task ReadCopiedDataStreamOfExternalAssetsRsAsync(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternalAsync("tar-rs", testCaseName, copyData: true); + + [Fact] + public async Task Throw_FifoContainsNonZeroDataSectionAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only"); + await using TarReader reader = new TarReader(archiveStream); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + Assert.NotNull(await reader.GetNextEntryAsync()); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task Throw_SingleExtendedAttributesEntryWithNoActualEntryAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr"); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Theory] + [InlineData("tar-rs", "spaces")] + [InlineData("golang_tar", "v7")] + public async Task AllowSpacesInOctalFieldsAsync(string folderName, string testCaseName) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName); + await using TarReader reader = new TarReader(archiveStream); + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) + { + AssertExtensions.GreaterThan(entry.Checksum, 0); + AssertExtensions.GreaterThan((int)entry.Mode, 0); + } + } + + [Theory] + [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries + [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries + [InlineData("neg-size")] // Garbage chars + [InlineData("invalid-go17")] // Many octal fields are all zero chars + [InlineData("issue11169")] // Checksum with null in the middle + [InlineData("issue10968")] // Garbage chars + [InlineData("writer-big")] // The size field contains an euro char + public async Task Throw_ArchivesWithRandomCharsAsync(string testCaseName) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task GarbageEntryChecksumZeroReturnNullAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435"); + await using TarReader reader = new TarReader(archiveStream); + Assert.Null(await reader.GetNextEntryAsync()); + } + + [Theory] + [InlineData("golang_tar", "gnu-nil-sparse-data")] + [InlineData("golang_tar", "gnu-nil-sparse-hole")] + [InlineData("golang_tar", "gnu-sparse-big")] + [InlineData("golang_tar", "sparse-formats")] + [InlineData("tar-rs", "sparse-1")] + [InlineData("tar-rs", "sparse")] + public async Task SparseEntryNotSupportedAsync(string testFolderName, string testCaseName) + { + // Currently sparse entries are not supported. + + // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file. + // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method: + // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big + + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + await using TarReader reader = new TarReader(archiveStream); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + [Fact] + public async Task DirectoryListRegularFileAndSparseAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental"); + await using TarReader reader = new TarReader(archiveStream); + TarEntry directoryList = await reader.GetNextEntryAsync(); + + Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType); + Assert.NotNull(directoryList.DataStream); + Assert.Equal(14, directoryList.Length); + + Assert.NotNull(await reader.GetNextEntryAsync()); // Just a regular file + + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); // Sparse + } + + [Fact] + public async Task PaxSizeLargerThanMaxAllowedByStreamAsync() + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long"); + await using TarReader reader = new TarReader(archiveStream); + // The extended attribute 'size' has the value 17179869184 + // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + } + + private static async Task VerifyDataStreamOfTarUncompressedInternalAsync(string testFolderName, string testCaseName, bool copyData) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + await VerifyDataStreamOfTarInternalAsync(archiveStream, copyData); + } + + private static async Task VerifyDataStreamOfTarGzInternalAsync(TestTarFormat testTarFormat, string testCaseName, bool copyData) + { + await using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName); + await using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress); + await VerifyDataStreamOfTarInternalAsync(decompressor, copyData); + } + + private static async Task VerifyDataStreamOfTarInternalAsync(Stream archiveStream, bool copyData) + { + await using TarReader reader = new TarReader(archiveStream); + + TarEntry entry; + + await using MemoryStream ms = new MemoryStream(); + while ((entry = await reader.GetNextEntryAsync(copyData)) != null) + { + if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile) + { + if (entry.Length == 0) + { + Assert.Null(entry.DataStream); + } + else + { + Assert.NotNull(entry.DataStream); + Assert.Equal(entry.DataStream.Length, entry.Length); + if (copyData) + { + Assert.True(entry.DataStream.CanSeek); + Assert.Equal(0, entry.DataStream.Position); + } + } + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs index ffc537917fc7af..17c67423c390b2 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using Xunit; +using static System.Formats.Tar.Tests.TarTestsBase; namespace System.Formats.Tar.Tests { @@ -124,5 +126,244 @@ public void Read_Archive_LongFileName_Over100_Under255(TarEntryFormat format, Te [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] public void Read_Archive_LongPath_Over255(TarEntryFormat format, TestTarFormat testFormat) => Read_Archive_LongPath_Over255_Internal(format, testFormat); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public void ReadDataStreamOfTarGzV7(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public void ReadDataStreamOfTarGzUstar(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzPax(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzPaxGea(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzOldGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadDataStreamOfTarGzGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetV7TestCaseNames))] + public void ReadCopiedDataStreamOfTarGzV7(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.v7, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetUstarTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzUstar(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.ustar, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzPax(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzPaxGea(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.pax_gea, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzOldGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.oldgnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetPaxAndGnuTestCaseNames))] + public void ReadCopiedDataStreamOfTarGzGnu(string testCaseName) => + VerifyDataStreamOfTarGzInternal(TestTarFormat.gnu, testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsGoLang(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsNode(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public void ReadDataStreamOfExternalAssetsRs(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: false); + + [Theory] + [MemberData(nameof(GetGoLangTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsGoLang(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("golang_tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetNodeTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsNode(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("node-tar", testCaseName, copyData: true); + + [Theory] + [MemberData(nameof(GetRsTarTestCaseNames))] + public void ReadCopiedDataStreamOfExternalAssetsRs(string testCaseName) => + VerifyDataStreamOfTarUncompressedInternal("tar-rs", testCaseName, copyData: true); + + [Fact] + public void Throw_FifoContainsNonZeroDataSection() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "hdr-only"); + using TarReader reader = new TarReader(archiveStream); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.NotNull(reader.GetNextEntry()); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void Throw_SingleExtendedAttributesEntryWithNoActualEntry() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "pax-path-hdr"); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Theory] + [InlineData("tar-rs", "spaces")] + [InlineData("golang_tar", "v7")] + public void AllowSpacesInOctalFields(string folderName, string testCaseName) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, folderName, testCaseName); + using TarReader reader = new TarReader(archiveStream); + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + AssertExtensions.GreaterThan(entry.Checksum, 0); + AssertExtensions.GreaterThan((int)entry.Mode, 0); + } + } + + [Theory] + [InlineData("pax-multi-hdrs")] // Multiple consecutive PAX metadata entries + [InlineData("gnu-multi-hdrs")] // Multiple consecutive GNU metadata entries + [InlineData("neg-size")] // Garbage chars + [InlineData("invalid-go17")] // Many octal fields are all zero chars + [InlineData("issue11169")] // Checksum with null in the middle + [InlineData("issue10968")] // Garbage chars + [InlineData("writer-big")] // The size field contains an euro char + public void Throw_ArchivesWithRandomChars(string testCaseName) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", testCaseName); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void GarbageEntryChecksumZeroReturnNull() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "issue12435"); + using TarReader reader = new TarReader(archiveStream); + Assert.Null(reader.GetNextEntry()); + } + + [Theory] + [InlineData("golang_tar", "gnu-nil-sparse-data")] + [InlineData("golang_tar", "gnu-nil-sparse-hole")] + [InlineData("golang_tar", "gnu-sparse-big")] + [InlineData("golang_tar", "sparse-formats")] + [InlineData("tar-rs", "sparse-1")] + [InlineData("tar-rs", "sparse")] + public void SparseEntryNotSupported(string testFolderName, string testCaseName) + { + // Currently sparse entries are not supported. + + // There are PAX archives archives in the golang folder that have extended attributes for treating a regular file as a sparse file. + // Sparse entries were created for the GNU format, so they are very rare entry types which are excluded from this test method: + // pax-nil-sparse-data, pax-nil-sparse-hole, pax-sparse-big + + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + using TarReader reader = new TarReader(archiveStream); + Assert.Throws(() => reader.GetNextEntry()); + } + + [Fact] + public void DirectoryListRegularFileAndSparse() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "gnu-incremental"); + using TarReader reader = new TarReader(archiveStream); + TarEntry directoryList = reader.GetNextEntry(); + + Assert.Equal(TarEntryType.DirectoryList, directoryList.EntryType); + Assert.NotNull(directoryList.DataStream); + Assert.Equal(14, directoryList.Length); + + Assert.NotNull(reader.GetNextEntry()); // Just a regular file + + Assert.Throws(() => reader.GetNextEntry()); // Sparse + } + + [Fact] + public void PaxSizeLargerThanMaxAllowedByStream() + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, "golang_tar", "writer-big-long"); + using TarReader reader = new TarReader(archiveStream); + // The extended attribute 'size' has the value 17179869184 + // Exception message: Stream length must be non-negative and less than 2^31 - 1 - origin + Assert.Throws(() => reader.GetNextEntry()); + } + + private static void VerifyDataStreamOfTarUncompressedInternal(string testFolderName, string testCaseName, bool copyData) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.Uncompressed, testFolderName, testCaseName); + VerifyDataStreamOfTarInternal(archiveStream, copyData); + } + + private static void VerifyDataStreamOfTarGzInternal(TestTarFormat testTarFormat, string testCaseName, bool copyData) + { + using MemoryStream archiveStream = GetTarMemoryStream(CompressionMethod.GZip, testTarFormat, testCaseName); + using GZipStream decompressor = new GZipStream(archiveStream, CompressionMode.Decompress); + VerifyDataStreamOfTarInternal(decompressor, copyData); + } + + private static void VerifyDataStreamOfTarInternal(Stream archiveStream, bool copyData) + { + using TarReader reader = new TarReader(archiveStream); + + TarEntry entry; + + while ((entry = reader.GetNextEntry(copyData)) != null) + { + if (entry.EntryType is TarEntryType.V7RegularFile or TarEntryType.RegularFile) + { + if (entry.Length == 0) + { + Assert.Null(entry.DataStream); + } + else + { + Assert.NotNull(entry.DataStream); + Assert.Equal(entry.DataStream.Length, entry.Length); + if (copyData) + { + Assert.True(entry.DataStream.CanSeek); + Assert.Equal(0, entry.DataStream.Position); + } + } + } + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs index bad9bf8fa179bf..2cede3a350c825 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs @@ -30,7 +30,7 @@ public void MalformedArchive_HeaderSize() malformed.Seek(0, SeekOrigin.Begin); using TarReader reader = new TarReader(malformed); - Assert.Throws(() => reader.GetNextEntry()); + Assert.Throws(() => reader.GetNextEntry()); } [Fact] @@ -250,5 +250,46 @@ public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposin Assert.Equal("Substituted", streamReader.ReadLine()); } } + + [Theory] + [InlineData(512, false)] + [InlineData(512, true)] + [InlineData(512 + 1, false)] + [InlineData(512 + 1, true)] + [InlineData(512 + 512 - 1, false)] + [InlineData(512 + 512 - 1, true)] + public void BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize, bool copyData) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + writer.WriteEntry(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + writer.WriteEntry(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = reader.GetNextEntry(copyData); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = reader.GetNextEntry(copyData); + Assert.Equal(0, e.Length); + + e = reader.GetNextEntry(copyData); + Assert.Null(e); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs index 77ddf1f1320621..f99e5853ebeaad 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntryAsync.Tests.cs @@ -51,7 +51,7 @@ public async Task MalformedArchive_HeaderSize_Async() await using (TarReader reader = new TarReader(malformed)) { - await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); + await Assert.ThrowsAsync(async () => await reader.GetNextEntryAsync()); } } } @@ -288,5 +288,46 @@ public async Task GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDi } } } + + [Theory] + [InlineData(512, false)] + [InlineData(512, true)] + [InlineData(512 + 1, false)] + [InlineData(512 + 1, true)] + [InlineData(512 + 512 - 1, false)] + [InlineData(512 + 512 - 1, true)] + public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize, bool copyData) + { + byte[] fileContents = new byte[contentSize]; + Array.Fill(fileContents, 0x1); + + using var archive = new MemoryStream(); + using (var writer = new TarWriter(archive, leaveOpen: true)) + { + var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file"); + entry1.DataStream = new MemoryStream(fileContents); + await writer.WriteEntryAsync(entry1); + + var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file"); + await writer.WriteEntryAsync(entry2); + } + + archive.Position = 0; + using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false); + using var reader = new TarReader(unseekable); + + TarEntry e = await reader.GetNextEntryAsync(copyData); + Assert.Equal(contentSize, e.Length); + + byte[] buffer = new byte[contentSize]; + while (e.DataStream.Read(buffer) > 0) ; + AssertExtensions.SequenceEqual(fileContents, buffer); + + e = await reader.GetNextEntryAsync(copyData); + Assert.Equal(0, e.Length); + + e = await reader.GetNextEntryAsync(copyData); + Assert.Null(e); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs new file mode 100644 index 00000000000000..8b41a5e9bb13bd --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.Tests.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarReader_Tests : TarTestsBase + { + [Fact] + public void TarReader_NullArchiveStream() => Assert.Throws(() => new TarReader(archiveStream: null)); + + [Fact] + public void TarReader_UnreadableStream() + { + using MemoryStream ms = new MemoryStream(); + using WrappedStream ws = new WrappedStream(ms, canRead: false, canWrite: true, canSeek: true); + Assert.Throws(() => new TarReader(ws)); + } + + [Fact] + public void TarReader_LeaveOpen_False() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: false)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + Assert.Throws(() => ds.ReadByte()); + } + } + + [Fact] + public void TarReader_LeaveOpen_True() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: true)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry()) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + ds.ReadByte(); // Should not throw + ds.Dispose(); + } + } + + [Fact] + public void TarReader_LeaveOpen_False_CopiedDataNotDisposed() + { + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax, "many_small_files"); + List dataStreams = new List(); + using (TarReader reader = new TarReader(ms, leaveOpen: false)) + { + TarEntry entry; + while ((entry = reader.GetNextEntry(copyData: true)) != null) + { + if (entry.DataStream != null) + { + dataStreams.Add(entry.DataStream); + } + } + } + + Assert.True(dataStreams.Any()); + foreach (Stream ds in dataStreams) + { + ds.ReadByte(); // Should not throw, copied streams, user should dispose + ds.Dispose(); + } + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs index b167a980acd2b0..ae01821d62b6d6 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -17,6 +19,9 @@ public abstract partial class TarTestsBase : FileCleanupTestBase protected const UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; // 644 in octal, internally used as default protected const UnixFileMode DefaultDirectoryMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default + protected readonly UnixFileMode CreateDirectoryDefaultMode; // Mode of directories created using Directory.CreateDirectory(string). + protected readonly UnixFileMode UMask; + // Mode assumed for files and directories on Windows. protected const UnixFileMode DefaultWindowsMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default @@ -85,6 +90,94 @@ public abstract partial class TarTestsBase : FileCleanupTestBase protected const string PaxEaDevMajor = "devmajor"; protected const string PaxEaDevMinor = "devminor"; + private static readonly string[] V7TestCaseNames = new[] + { + "file", + "file_hardlink", + "file_symlink", + "folder_file", + "folder_file_utf8", + "folder_subfolder_file", + "foldersymlink_folder_subfolder_file", + "many_small_files" + }; + + private static readonly string[] UstarTestCaseNames = new[] + { + "longpath_splitable_under255", + "specialfiles" }; + + private static readonly string[] PaxAndGnuTestCaseNames = new[] + { + "file_longsymlink", + "longfilename_over100_under255", + "longpath_over255" + }; + + private static readonly string[] GoLangTestCaseNames = new[] + { + "empty", + "file-and-dir", + "gnu-long-nul", + "gnu-not-utf8", + "gnu-utf8", + "gnu", + "hardlink", + "nil-uid", + "pax-bad-hdr-file", + "pax-bad-mtime-file", + "pax-global-records", + "pax-nul-path", + "pax-nul-xattrs", + "pax-pos-size-file", + "pax-records", + "pax", + "star", + "trailing-slash", + "ustar-file-devs", + "ustar-file-reg", + "ustar", + "writer", + "xattrs" + }; + + private static readonly string[] NodeTarTestCaseNames = new[] + { + "bad-cksum", + "body-byte-counts", + "dir", + "emptypax", + "file", + "global-header", + "links-invalid", + "links-strip", + "links", + "long-paths", + "long-pax", + "next-file-has-long", + "null-byte", + "path-missing", + "trailing-slash-corner-case", + "utf8" + }; + + private static readonly string[] RsTarTestCaseNames = new[] + { + "7z_long_path", + "directory", + "duplicate_dirs", + "empty_filename", + "file_times", + "link", + "pax_size", + "pax", + "pax2", + "reading_files", + "simple_missing_last_header", + "simple", + "xattrs" + }; + protected enum CompressionMethod { // Archiving only, no compression @@ -115,27 +208,44 @@ public enum TestTarFormat protected static bool IsNotLinuxBionic => !PlatformDetection.IsLinuxBionic; + protected TarTestsBase() + { + CreateDirectoryDefaultMode = Directory.CreateDirectory(GetRandomDirPath()).UnixFileMode; // '0777 & ~umask' + UMask = ~CreateDirectoryDefaultMode & (UnixFileMode)Convert.ToInt32("777", + 8); + } + protected static string GetTestCaseUnarchivedFolderPath(string testCaseName) => - Path.Join(Directory.GetCurrentDirectory(), "unarchived", testCaseName); + Path.Join(Directory.GetCurrentDirectory(), "unarchived", + testCaseName); protected static string GetTarFilePath(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) + => GetTarFilePath(compressionMethod, format.ToString(), testCaseName); + + protected static string GetTarFilePath(CompressionMethod compressionMethod, string testFolderName, string testCaseName) { (string compressionMethodFolder, string fileExtension) = compressionMethod switch { - CompressionMethod.Uncompressed => ("tar", ".tar"), - CompressionMethod.GZip => ("targz", ".tar.gz"), + CompressionMethod.Uncompressed => ("tar", + ".tar"), + CompressionMethod.GZip => ("targz", + ".tar.gz"), _ => throw new InvalidOperationException($"Unexpected compression method: {compressionMethod}"), }; - return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, format.ToString(), testCaseName + fileExtension); + return Path.Join(Directory.GetCurrentDirectory(), compressionMethodFolder, testFolderName, testCaseName + fileExtension); } // MemoryStream containing the copied contents of the specified file. Meant for reading and writing. protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, TestTarFormat format, string testCaseName) => - GetMemoryStream(GetTarFilePath(compressionMethod, format, testCaseName)); + GetTarMemoryStream(compressionMethod, format.ToString(), testCaseName); + + protected static MemoryStream GetTarMemoryStream(CompressionMethod compressionMethod, string testFolderName, string testCaseName) => + GetMemoryStream(GetTarFilePath(compressionMethod, testFolderName, testCaseName)); protected static string GetStrangeTarFilePath(string testCaseName) => - Path.Join(Directory.GetCurrentDirectory(), "strange", testCaseName + ".tar"); + Path.Join(Directory.GetCurrentDirectory(), "strange", + testCaseName + ".tar"); protected static MemoryStream GetStrangeTarMemoryStream(string testCaseName) => GetMemoryStream(GetStrangeTarFilePath(testCaseName)); @@ -178,6 +288,8 @@ protected void SetCommonHardLink(TarEntry hardLink) // LinkName Assert.Equal(DefaultLinkName, hardLink.LinkName); + Assert.Throws(() => hardLink.LinkName = null); + Assert.Throws(() => hardLink.LinkName = string.Empty); hardLink.LinkName = TestLinkName; } @@ -189,6 +301,8 @@ protected void SetCommonSymbolicLink(TarEntry symbolicLink) // LinkName Assert.Equal(DefaultLinkName, symbolicLink.LinkName); + Assert.Throws(() => symbolicLink.LinkName = null); + Assert.Throws(() => symbolicLink.LinkName = string.Empty); symbolicLink.LinkName = TestLinkName; } @@ -291,6 +405,13 @@ protected void VerifyDataStream(TarEntry entry, bool isFromWriter) if (isFromWriter) { Assert.Null(entry.DataStream); + + using (MemoryStream ms = new MemoryStream()) + using (WrappedStream ws = new WrappedStream(ms, canRead: false, canWrite: true, canSeek: true)) + { + Assert.Throws(() => entry.DataStream = ws); + } + entry.DataStream = new MemoryStream(); // Verify it is not modified or wrapped in any way Assert.True(entry.DataStream.CanRead); @@ -326,7 +447,7 @@ protected Type GetTypeForFormat(TarEntryFormat expectedFormat) TarEntryFormat.Ustar => typeof(UstarTarEntry), TarEntryFormat.Pax => typeof(PaxTarEntry), TarEntryFormat.Gnu => typeof(GnuTarEntry), - _ => throw new FormatException($"Unrecognized format: {expectedFormat}"), + _ => throw new InvalidDataException($"Unrecognized format: {expectedFormat}"), }; } @@ -362,7 +483,7 @@ protected TarEntry InvokeTarEntryCreationConstructor(TarEntryFormat targetFormat TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), - _ => throw new FormatException($"Unexpected format: {targetFormat}") + _ => throw new InvalidDataException($"Unexpected format: {targetFormat}") }; public static IEnumerable GetFormatsAndLinks() @@ -407,11 +528,20 @@ protected static void AssertEntryModeFromFileSystemEquals(TarEntry entry, UnixFi Assert.Equal(fileMode, entry.Mode); } - protected static void AssertFileModeEquals(string path, UnixFileMode mode) + protected void AssertFileModeEquals(string path, UnixFileMode archiveMode) { if (!PlatformDetection.IsWindows) { - Assert.Equal(mode, File.GetUnixFileMode(path)); + UnixFileMode expectedMode = archiveMode & ~UMask; + + UnixFileMode actualMode = File.GetUnixFileMode(path); + // Ignore SetGroup: it may have been added to preserve group ownership. + if ((expectedMode & UnixFileMode.SetGroup) == 0) + { + actualMode &= ~UnixFileMode.SetGroup; + } + + Assert.Equal(expectedMode, actualMode); } } @@ -444,5 +574,53 @@ protected void Verify_Extract(string destination, TarEntry entry, TarEntryType e AssertFileModeEquals(destination, TestPermission1); } + + public static IEnumerable GetNodeTarTestCaseNames() + { + foreach (string name in NodeTarTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetGoLangTarTestCaseNames() + { + foreach (string name in GoLangTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetRsTarTestCaseNames() + { + foreach (string name in RsTarTestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetV7TestCaseNames() + { + foreach (string name in V7TestCaseNames) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetUstarTestCaseNames() + { + foreach (string name in UstarTestCaseNames.Concat(V7TestCaseNames)) + { + yield return new object[] { name }; + } + } + + public static IEnumerable GetPaxAndGnuTestCaseNames() + { + foreach (string name in UstarTestCaseNames.Concat(V7TestCaseNames).Concat(PaxAndGnuTestCaseNames)) + { + yield return new object[] { name }; + } + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs index 894498934f0177..8d482af0b1dff1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs @@ -63,8 +63,8 @@ public void Constructors_UnwritableStream_Throws() { using MemoryStream archiveStream = new MemoryStream(); using WrappedStream wrappedStream = new WrappedStream(archiveStream, canRead: true, canWrite: false, canSeek: false); - Assert.Throws(() => new TarWriter(wrappedStream)); - Assert.Throws(() => new TarWriter(wrappedStream, TarEntryFormat.V7)); + Assert.Throws(() => new TarWriter(wrappedStream)); + Assert.Throws(() => new TarWriter(wrappedStream, TarEntryFormat.V7)); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs index d591ba9b6542a6..925daef99d5167 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs @@ -167,6 +167,10 @@ public void Write_Long_Name(TarEntryType entryType) using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } writer.WriteEntry(entry); } @@ -231,5 +235,15 @@ public void Write_LongName_And_LongLinkName(TarEntryType entryType) Assert.Equal(longLinkName, entry.LinkName); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs index 6c9e79d83a1eff..1e81fb7b1e8a01 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs @@ -485,5 +485,15 @@ public void WriteTimestampsBeyondOctalLimitInPax() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs index da3b69051ab347..c1f2d07562d388 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs @@ -152,5 +152,15 @@ public void WriteFifo() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs index fb0cbb980ee6e2..51be90c74b07c9 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs @@ -92,5 +92,15 @@ public void WriteDirectory() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public void Write_LinkEntry_EmptyLinkName_Throws(TarEntryType entryType) + { + using MemoryStream archiveStream = new MemoryStream(); + using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + Assert.Throws("entry", () => writer.WriteEntry(new V7TarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs index 6d19503c4fc25d..b0c78298f2bb01 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs @@ -101,7 +101,7 @@ public void Write_RegularFileEntry_In_V7Writer(TarEntryFormat entryFormat) TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), - _ => throw new FormatException($"Unexpected format: {entryFormat}") + _ => throw new InvalidDataException($"Unexpected format: {entryFormat}") }; // Should be written in the format of the entry diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs index b33e2bf7add22a..1e77e548d4a6f7 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Gnu.Tests.cs @@ -183,6 +183,10 @@ public async Task Write_Long_Name_Async(TarEntryType entryType) await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); + if (entryType is TarEntryType.HardLink or TarEntryType.SymbolicLink) + { + entry.LinkName = "linktarget"; + } await writer.WriteEntryAsync(entry); } @@ -252,5 +256,15 @@ public async Task Write_LongName_And_LongLinkName_Async(TarEntryType entryType) } } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new GnuTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs index 688c918baedf15..b0c9d636420b52 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Pax.Tests.cs @@ -505,5 +505,15 @@ public async Task WriteTimestampsBeyondOctalLimitInPax_Async() Assert.Equal(overLimitTimestamp, actualCTime); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new PaxTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs index 1266e7216a0126..b3e207d434f78e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.Ustar.Tests.cs @@ -153,5 +153,15 @@ public async Task WriteFifo_Async() VerifyFifo(fifo); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new UstarTarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs index 4408477ee00006..3e41f263ef02e4 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Entry.V7.Tests.cs @@ -93,5 +93,15 @@ public async Task WriteDirectory_Async() VerifyDirectory(directory); } } + + [Theory] + [InlineData(TarEntryType.HardLink)] + [InlineData(TarEntryType.SymbolicLink)] + public async Task Write_LinkEntry_EmptyLinkName_Throws_Async(TarEntryType entryType) + { + await using MemoryStream archiveStream = new MemoryStream(); + await using TarWriter writer = new TarWriter(archiveStream, leaveOpen: false); + await Assert.ThrowsAsync("entry", () => writer.WriteEntryAsync(new V7TarEntry(entryType, "link"))); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs index 8f6637408a8c9b..c2eb58a7f1f24a 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs @@ -125,7 +125,7 @@ public async Task Write_RegularFileEntry_In_V7Writer_Async(TarEntryFormat entryF TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), - _ => throw new FormatException($"Unexpected format: {entryFormat}") + _ => throw new InvalidDataException($"Unexpected format: {entryFormat}") }; // Should be written in the format of the entry diff --git a/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj b/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj index 573565c9decac6..61a409ffedc326 100644 --- a/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj +++ b/src/libraries/System.Globalization.Extensions/tests/NlsTests/System.Globalization.Extensions.Nls.Tests.csproj @@ -3,6 +3,9 @@ $(NetCoreAppCurrent)-windows true + + + diff --git a/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json b/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json deleted file mode 100644 index ec1e96166f3b3c..00000000000000 --- a/src/libraries/System.Globalization.Extensions/tests/NlsTests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Globalization.UseNls": true - } -} diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs index 96d601de841b8b..ebf8f5facb3d2f 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs @@ -158,7 +158,7 @@ private unsafe void Monitor(AsyncReadState state) continueExecuting = Interop.Kernel32.ReadDirectoryChangesW( state.DirectoryHandle, state.Buffer, // the buffer is kept pinned for the duration of the sync and async operation by the PreAllocatedOverlapped - _internalBufferSize, + (uint)state.Buffer.Length, _includeSubdirectories, (uint)_notifyFilters, null, diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs index 73c8f587bccede..5627974f47420a 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory_UnixFileMode.Unix.cs @@ -39,6 +39,21 @@ public void CreateDoesntChangeExistingMode() Assert.Equal(initialMode, sameDir.UnixFileMode); } + [Fact] + public void MissingParentsHaveDefaultPermissions() + { + string parent = GetRandomDirPath(); + string child = Path.Combine(parent, "child"); + + const UnixFileMode childMode = UnixFileMode.UserRead | UnixFileMode.UserExecute; + DirectoryInfo childDir = Directory.CreateDirectory(child, childMode); + + Assert.Equal(childMode, childDir.UnixFileMode); + + UnixFileMode defaultPermissions = Directory.CreateDirectory(GetRandomDirPath()).UnixFileMode; + Assert.Equal(defaultPermissions, File.GetUnixFileMode(parent)); + } + [Theory] [InlineData((UnixFileMode)(1 << 12), false)] [InlineData((UnixFileMode)(1 << 12), true)] diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs index 0bb405c487f02c..e4b482c675db41 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs @@ -41,8 +41,9 @@ public void ReturnsExactSizeForNonEmptyFiles(FileOptions options) [MemberData(nameof(GetSyncAsyncOptions))] public void ReturnsActualLengthForDevices(FileOptions options) { - // both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists - // that is why we just try and swallow the exception when it occurs + // Both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists + // that is why we just try and swallow the exception when it occurs. + // Exception can be also thrown when the file is in use (#73925). try { using (SafeFileHandle handle = File.OpenHandle(@"\\?\PhysicalDrive0", FileMode.Open, options: options)) @@ -51,7 +52,7 @@ public void ReturnsActualLengthForDevices(FileOptions options) Assert.True(length > 0); } } - catch (FileNotFoundException) { } + catch (IOException) { } } } } diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs index 67ec9825de3063..b5d182a28b55c5 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs @@ -12,6 +12,7 @@ public class NamedPipeTest_UnixDomainSockets [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); @@ -30,6 +31,7 @@ public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task NamedPipeClient_Connects_With_UnixDomainSocketEndPointServer() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); diff --git a/src/libraries/System.Memory/tests/Span/IndexOf.T.cs b/src/libraries/System.Memory/tests/Span/IndexOf.T.cs index 67b6e896bd59fa..e2fb8a0e64e48a 100644 --- a/src/libraries/System.Memory/tests/Span/IndexOf.T.cs +++ b/src/libraries/System.Memory/tests/Span/IndexOf.T.cs @@ -192,5 +192,60 @@ public static void IndexOfNull_String(string[] spanInput, int expected) Span theStrings = spanInput; Assert.Equal(expected, theStrings.IndexOf((string)null)); } + + [Fact] + public static void NotBitwiseEquatableUsesCustomIEquatableImplementationForActualComparison() + { + const byte Ten = 10, NotTen = 11; + for (int length = 1; length < 100; length++) + { + TwoBytes[] array = new TwoBytes[length]; + for (int i = 0; i < length; i++) + { + array[i] = new TwoBytes(Ten, (byte)i); + } + + Span span = new Span(array); + ReadOnlySpan ros = new ReadOnlySpan(array); + + ReadOnlySpan noMatch2 = new TwoBytes[2] { new TwoBytes(10, NotTen), new TwoBytes(10, NotTen) }; + Assert.Equal(-1, span.IndexOfAny(noMatch2)); + Assert.Equal(-1, ros.IndexOfAny(noMatch2)); + Assert.Equal(-1, span.LastIndexOfAny(noMatch2)); + Assert.Equal(-1, ros.LastIndexOfAny(noMatch2)); + + ReadOnlySpan noMatch3 = new TwoBytes[3] { new TwoBytes(10, NotTen), new TwoBytes(10, NotTen), new TwoBytes(10, NotTen) }; + Assert.Equal(-1, span.IndexOfAny(noMatch3)); + Assert.Equal(-1, ros.IndexOfAny(noMatch3)); + Assert.Equal(-1, span.LastIndexOfAny(noMatch3)); + Assert.Equal(-1, ros.LastIndexOfAny(noMatch3)); + + ReadOnlySpan match2 = new TwoBytes[2] { new TwoBytes(0, Ten), new TwoBytes(0, Ten) }; + Assert.Equal(0, span.IndexOfAny(match2)); + Assert.Equal(0, ros.IndexOfAny(match2)); + Assert.Equal(0, span.LastIndexOfAny(match2)); + Assert.Equal(0, ros.LastIndexOfAny(match2)); + + ReadOnlySpan match3 = new TwoBytes[3] { new TwoBytes(0, Ten), new TwoBytes(0, Ten), new TwoBytes(0, Ten) }; + Assert.Equal(0, span.IndexOfAny(match3)); + Assert.Equal(0, ros.IndexOfAny(match3)); + Assert.Equal(0, span.LastIndexOfAny(match3)); + Assert.Equal(0, ros.LastIndexOfAny(match3)); + } + } + + private readonly struct TwoBytes : IEquatable + { + private readonly byte _first, _second; + + public TwoBytes(byte first, byte second) + { + _first = first; + _second = second; + } + + // it compares different fields on purpose + public bool Equals(TwoBytes other) => _first == other._second && _second == other._first; + } } } diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index eb072b1d63cd4f..0f1758689cd87b 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -470,7 +470,6 @@ - diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs index ccca155d4bc8aa..c140c2ccb9e404 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/BrowserHttpHandler/BrowserHttpHandler.cs @@ -133,9 +133,6 @@ private static async Task CallFetch(HttpRequestMessage reques int headerCount = request.Headers.Count + request.Content?.Headers.Count ?? 0; List headerNames = new List(headerCount); List headerValues = new List(headerCount); - List optionNames = new List(); - List optionValues = new List(); - JSObject abortController = BrowserHttpInterop.CreateAbortController(); CancellationTokenRegistration? abortRegistration = cancellationToken.Register(() => { @@ -147,12 +144,27 @@ private static async Task CallFetch(HttpRequestMessage reques }); try { - optionNames.Add("method"); - optionValues.Add(request.Method.Method); + if (request.RequestUri == null) + { + throw new ArgumentNullException(nameof(request.RequestUri)); + } + + string uri = request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString(); + + bool hasFetchOptions = request.Options.TryGetValue(FetchOptions, out IDictionary? fetchOptions); + int optionCount = 1 + (allowAutoRedirect.HasValue ? 1 : 0) + (hasFetchOptions && fetchOptions != null ? fetchOptions.Count : 0); + int optionIndex = 0; + string[] optionNames = new string[optionCount]; + object?[] optionValues = new object?[optionCount]; + + optionNames[optionIndex] = "method"; + optionValues[optionIndex] = request.Method.Method; + optionIndex++; if (allowAutoRedirect.HasValue) { - optionNames.Add("redirect"); - optionValues.Add(allowAutoRedirect.Value ? "follow" : "manual"); + optionNames[optionIndex] = "redirect"; + optionValues[optionIndex] = allowAutoRedirect.Value ? "follow" : "manual"; + optionIndex++; } foreach (KeyValuePair> header in request.Headers) @@ -176,21 +188,16 @@ private static async Task CallFetch(HttpRequestMessage reques } } - if (request.Options.TryGetValue(FetchOptions, out IDictionary? fetchOptions)) + if (hasFetchOptions && fetchOptions != null) { foreach (KeyValuePair item in fetchOptions) { - optionNames.Add(item.Key); - optionValues.Add(item.Value); + optionNames[optionIndex] = item.Key; + optionValues[optionIndex] = item.Value; + optionIndex++; } } - if (request.RequestUri == null) - { - throw new ArgumentNullException(nameof(request.RequestUri)); - } - - string uri = request.RequestUri.IsAbsoluteUri ? request.RequestUri.AbsoluteUri : request.RequestUri.ToString(); Task? promise; cancellationToken.ThrowIfCancellationRequested(); if (request.Content != null) @@ -201,7 +208,7 @@ private static async Task CallFetch(HttpRequestMessage reques .ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, body); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController, body); } else { @@ -209,13 +216,14 @@ private static async Task CallFetch(HttpRequestMessage reques .ConfigureAwait(true); cancellationToken.ThrowIfCancellationRequested(); - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController, buffer); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController, buffer); } } else { - promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames.ToArray(), optionValues.ToArray(), abortController); + promise = BrowserHttpInterop.Fetch(uri, headerNames.ToArray(), headerValues.ToArray(), optionNames, optionValues, abortController); } + cancellationToken.ThrowIfCancellationRequested(); ValueTask wrappedTask = BrowserHttpInterop.CancelationHelper(promise, cancellationToken, abortController); JSObject fetchResponse = await wrappedTask.ConfigureAwait(true); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index ee9732416a38d7..f82e660f97de73 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs @@ -240,7 +240,7 @@ public async Task SendAsync(HttpRequestMessage request, lon catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted) { // This will happen if we aborted _connection somewhere and we have pending OpenOutboundStreamAsync call. - Debug.Assert(_abortException is not null); + // note that _abortException may be null if we closed the connection in response to a GOAWAY frame throw new HttpRequestException(SR.net_http_client_execution_error, _abortException, RequestRetryType.RetryOnConnectionFailure); } finally diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d2bc4873b6ad85..2d4368bd7e6b7f 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -3751,6 +3751,7 @@ public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBas public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_CustomTrust_Ok() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); @@ -3787,6 +3788,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_InvalidName_Throws() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); @@ -3817,6 +3819,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } [Fact] + [SkipOnPlatform(TestPlatforms.Android, "Self-signed certificates are rejected by Android before the .NET validation is reached")] public async Task SslOptions_CustomPolicy_IgnoresNameMismatch() { X509Certificate2Collection caCerts = new X509Certificate2Collection(); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs new file mode 100644 index 00000000000000..206eac76ac7878 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.NativeMethods.cs @@ -0,0 +1,378 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Quic; + +namespace System.Net.Quic; + +internal sealed unsafe partial class MsQuicApi +{ + public void SetContext(MsQuicSafeHandle handle, void* context) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + ApiTable->SetContext(handle.QuicHandle, context); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void* GetContext(MsQuicSafeHandle handle) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->GetContext(handle.QuicHandle); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void SetCallbackHandler(MsQuicSafeHandle handle, void* callback, void* context) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + ApiTable->SetCallbackHandler(handle.QuicHandle, callback, context); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public int SetParam(MsQuicSafeHandle handle, uint param, uint bufferLength, void* buffer) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->SetParam(handle.QuicHandle, param, bufferLength, buffer); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public int GetParam(MsQuicSafeHandle handle, uint param, uint* bufferLength, void* buffer) + { + bool success = false; + try + { + handle.DangerousAddRef(ref success); + return ApiTable->GetParam(handle.QuicHandle, param, bufferLength, buffer); + } + finally + { + if (success) + { + handle.DangerousRelease(); + } + } + } + + public void RegistrationShutdown(MsQuicSafeHandle registration, QUIC_CONNECTION_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + ApiTable->RegistrationShutdown(registration.QuicHandle, flags, code); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ConfigurationOpen(MsQuicSafeHandle registration, QUIC_BUFFER* alpnBuffers, uint alpnBuffersCount, QUIC_SETTINGS* settings, uint settingsSize, void* context, QUIC_HANDLE** configuration) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ConfigurationOpen(registration.QuicHandle, alpnBuffers, alpnBuffersCount, settings, settingsSize, context, configuration); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ConfigurationLoadCredential(MsQuicSafeHandle configuration, QUIC_CREDENTIAL_CONFIG* config) + { + bool success = false; + try + { + configuration.DangerousAddRef(ref success); + return ApiTable->ConfigurationLoadCredential(configuration.QuicHandle, config); + } + finally + { + if (success) + { + configuration.DangerousRelease(); + } + } + } + + public int ListenerOpen(MsQuicSafeHandle registration, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** listener) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ListenerOpen(registration.QuicHandle, callback, context, listener); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public int ListenerStart(MsQuicSafeHandle listener, QUIC_BUFFER* alpnBuffers, uint alpnBuffersCount, QuicAddr* localAddress) + { + bool success = false; + try + { + listener.DangerousAddRef(ref success); + return ApiTable->ListenerStart(listener.QuicHandle, alpnBuffers, alpnBuffersCount, localAddress); + } + finally + { + if (success) + { + listener.DangerousRelease(); + } + } + } + + public void ListenerStop(MsQuicSafeHandle listener) + { + bool success = false; + try + { + listener.DangerousAddRef(ref success); + ApiTable->ListenerStop(listener.QuicHandle); + } + finally + { + if (success) + { + listener.DangerousRelease(); + } + } + } + + public int ConnectionOpen(MsQuicSafeHandle registration, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** connection) + { + bool success = false; + try + { + registration.DangerousAddRef(ref success); + return ApiTable->ConnectionOpen(registration.QuicHandle, callback, context, connection); + } + finally + { + if (success) + { + registration.DangerousRelease(); + } + } + } + + public void ConnectionShutdown(MsQuicSafeHandle connection, QUIC_CONNECTION_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + connection.DangerousAddRef(ref success); + ApiTable->ConnectionShutdown(connection.QuicHandle, flags, code); + } + finally + { + if (success) + { + connection.DangerousRelease(); + } + } + } + + public int ConnectionStart(MsQuicSafeHandle connection, MsQuicSafeHandle configuration, ushort family, sbyte* serverName, ushort serverPort) + { + bool connectionSuccess = false; + bool configurationSuccess = false; + try + { + connection.DangerousAddRef(ref connectionSuccess); + configuration.DangerousAddRef(ref configurationSuccess); + return ApiTable->ConnectionStart(connection.QuicHandle, configuration.QuicHandle, family, serverName, serverPort); + } + finally + { + if (connectionSuccess) + { + connection.DangerousRelease(); + } + if (configurationSuccess) + { + configuration.DangerousRelease(); + } + } + } + + public int ConnectionSetConfiguration(MsQuicSafeHandle connection, MsQuicSafeHandle configuration) + { + bool connectionSuccess = false; + bool configurationSuccess = false; + try + { + connection.DangerousAddRef(ref connectionSuccess); + configuration.DangerousAddRef(ref configurationSuccess); + return ApiTable->ConnectionSetConfiguration(connection.QuicHandle, configuration.QuicHandle); + } + finally + { + if (connectionSuccess) + { + connection.DangerousRelease(); + } + if (configurationSuccess) + { + configuration.DangerousRelease(); + } + } + } + + public int StreamOpen(MsQuicSafeHandle connection, QUIC_STREAM_OPEN_FLAGS flags, delegate* unmanaged[Cdecl] callback, void* context, QUIC_HANDLE** stream) + { + bool success = false; + try + { + connection.DangerousAddRef(ref success); + return ApiTable->StreamOpen(connection.QuicHandle, flags, callback, context, stream); + } + finally + { + if (success) + { + connection.DangerousRelease(); + } + } + } + + public int StreamStart(MsQuicSafeHandle stream, QUIC_STREAM_START_FLAGS flags) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamStart(stream.QuicHandle, flags); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamShutdown(MsQuicSafeHandle stream, QUIC_STREAM_SHUTDOWN_FLAGS flags, ulong code) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamShutdown(stream.QuicHandle, flags, code); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamSend(MsQuicSafeHandle stream, QUIC_BUFFER* buffers, uint buffersCount, QUIC_SEND_FLAGS flags, void* context) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamSend(stream.QuicHandle, buffers, buffersCount, flags, context); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public void StreamReceiveComplete(MsQuicSafeHandle stream, ulong length) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + ApiTable->StreamReceiveComplete(stream.QuicHandle, length); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } + + public int StreamReceiveSetEnabled(MsQuicSafeHandle stream, byte enabled) + { + bool success = false; + try + { + stream.DangerousAddRef(ref success); + return ApiTable->StreamReceiveSetEnabled(stream.QuicHandle, enabled); + } + finally + { + if (success) + { + stream.DangerousRelease(); + } + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs index e2866454356dd2..e28134ea4b6f52 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs @@ -13,7 +13,7 @@ namespace System.Net.Quic; -internal sealed unsafe class MsQuicApi +internal sealed unsafe partial class MsQuicApi { private static readonly Version MinWindowsVersion = new Version(10, 0, 20145, 1000); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs index b84cb6cce4267a..ddec979aade1b5 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.cs @@ -131,8 +131,8 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI using MsQuicBuffers msquicBuffers = new MsQuicBuffers(); msquicBuffers.Initialize(alpnProtocols, alpnProtocol => alpnProtocol.Protocol); - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConfigurationOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConfigurationOpen( + MsQuicApi.Api.Registration, msquicBuffers.Buffers, (uint)alpnProtocols.Count, &settings, @@ -140,7 +140,7 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI (void*)IntPtr.Zero, &handle), "ConfigurationOpen failed"); - MsQuicSafeHandle configurationHandle = new MsQuicSafeHandle(handle, MsQuicApi.Api.ApiTable->ConfigurationClose, SafeHandleType.Configuration); + MsQuicSafeHandle configurationHandle = new MsQuicSafeHandle(handle, SafeHandleType.Configuration); try { @@ -157,13 +157,13 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI if (certificate is null) { config.Type = QUIC_CREDENTIAL_TYPE.NONE; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } else if (MsQuicApi.UsesSChannelBackend) { config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_CONTEXT; config.CertificateContext = (void*)certificate.Handle; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } else { @@ -192,7 +192,7 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI PrivateKeyPassword = (sbyte*)IntPtr.Zero }; config.CertificatePkcs12 = &pkcs12Certificate; - status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config); + status = MsQuicApi.Api.ConfigurationLoadCredential(configurationHandle, &config); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs index 04beebc6a2fc13..683e8bb62473e4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs @@ -61,8 +61,8 @@ internal static unsafe T GetMsQuicParameter(MsQuicSafeHandle handle, uint par T value; uint length = (uint)sizeof(T); - int status = MsQuicApi.Api.ApiTable->GetParam( - handle.QuicHandle, + int status = MsQuicApi.Api.GetParam( + handle, parameter, &length, (byte*)&value); @@ -78,8 +78,8 @@ internal static unsafe T GetMsQuicParameter(MsQuicSafeHandle handle, uint par internal static unsafe void SetMsQuicParameter(MsQuicSafeHandle handle, uint parameter, T value) where T : unmanaged { - int status = MsQuicApi.Api.ApiTable->SetParam( - handle.QuicHandle, + int status = MsQuicApi.Api.SetParam( + handle, parameter, (uint)sizeof(T), (byte*)&value); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs index 6e247ef9937376..58b41443694a1d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs @@ -39,10 +39,25 @@ public MsQuicSafeHandle(QUIC_HANDLE* handle, delegate* unmanaged[Cdecl] MsQuicApi.Api.ApiTable->RegistrationClose, + SafeHandleType.Configuration => MsQuicApi.Api.ApiTable->ConfigurationClose, + SafeHandleType.Listener => MsQuicApi.Api.ApiTable->ListenerClose, + SafeHandleType.Connection => MsQuicApi.Api.ApiTable->ConnectionClose, + SafeHandleType.Stream => MsQuicApi.Api.ApiTable->StreamClose, + _ => throw new ArgumentException($"Unexpected value: {safeHandleType}", nameof(safeHandleType)) + }, + safeHandleType) { } + protected override bool ReleaseHandle() { - _releaseAction(QuicHandle); + QUIC_HANDLE* quicHandle = QuicHandle; SetHandle(IntPtr.Zero); + _releaseAction(quicHandle); if (NetEventSource.Log.IsEnabled()) { @@ -77,8 +92,8 @@ internal sealed class MsQuicContextSafeHandle : MsQuicSafeHandle /// private readonly MsQuicSafeHandle? _parent; - public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, delegate* unmanaged[Cdecl] releaseAction, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null) - : base(handle, releaseAction, safeHandleType) + public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null) + : base(handle, safeHandleType) { _context = context; if (parent is not null) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 3aa439a25af03a..83073bcfab5022 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -179,13 +179,13 @@ private unsafe QuicConnection() try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionOpen( + MsQuicApi.Api.Registration, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "ConnectionOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ConnectionClose, SafeHandleType.Connection); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Connection); } catch { @@ -204,12 +204,12 @@ internal unsafe QuicConnection(QUIC_HANDLE* handle, QUIC_NEW_CONNECTION_INFO* in GCHandle context = GCHandle.Alloc(this, GCHandleType.Weak); try { + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Connection); delegate* unmanaged[Cdecl] nativeCallback = &NativeCallback; - MsQuicApi.Api.ApiTable->SetCallbackHandler( - handle, + MsQuicApi.Api.SetCallbackHandler( + _handle, nativeCallback, (void*)GCHandle.ToIntPtr(context)); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ConnectionClose, SafeHandleType.Connection); } catch { @@ -294,9 +294,9 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionStart( - _handle.QuicHandle, - _configuration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionStart( + _handle, + _configuration, (ushort)addressFamily, (sbyte*)targetHostPtr, (ushort)port), @@ -334,9 +334,9 @@ internal ValueTask FinishHandshakeAsync(QuicServerConnectionOptions options, str unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionSetConfiguration( - _handle.QuicHandle, - _configuration.QuicHandle), + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionSetConfiguration( + _handle, + _configuration), "ConnectionSetConfiguration failed"); } } @@ -392,6 +392,7 @@ public async ValueTask AcceptInboundStreamAsync(CancellationToken ca throw new InvalidOperationException(SR.net_quic_accept_not_allowed); } + GCHandle keepObject = GCHandle.Alloc(this); try { return await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); @@ -401,6 +402,10 @@ public async ValueTask AcceptInboundStreamAsync(CancellationToken ca ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } + finally + { + keepObject.Free(); + } } /// @@ -425,8 +430,8 @@ public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken { unsafe { - MsQuicApi.Api.ApiTable->ConnectionShutdown( - _handle.QuicHandle, + MsQuicApi.Api.ConnectionShutdown( + _handle, QUIC_CONNECTION_SHUTDOWN_FLAGS.NONE, (ulong)errorCode); } @@ -469,8 +474,8 @@ private unsafe int HandleEventShutdownInitiatedByPeer(ref SHUTDOWN_INITIATED_BY_ } private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE_DATA data) { - _shutdownTcs.TrySetResult(); _acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException())); + _shutdownTcs.TrySetResult(); return QUIC_STATUS_SUCCESS; } private unsafe int HandleEventLocalAddressChanged(ref LOCAL_ADDRESS_CHANGED_DATA data) @@ -577,8 +582,8 @@ public async ValueTask DisposeAsync() { unsafe { - MsQuicApi.Api.ApiTable->ConnectionShutdown( - _handle.QuicHandle, + MsQuicApi.Api.ConnectionShutdown( + _handle, QUIC_CONNECTION_SHUTDOWN_FLAGS.NONE, (ulong)_defaultCloseErrorCode); } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs index 11c4d77731e033..a99e82159eceb4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs @@ -106,13 +106,13 @@ private unsafe QuicListener(QuicListenerOptions options) try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerOpen( - MsQuicApi.Api.Registration.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ListenerOpen( + MsQuicApi.Api.Registration, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "ListenerOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ListenerClose, SafeHandleType.Listener); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Listener); } catch { @@ -135,8 +135,8 @@ private unsafe QuicListener(QuicListenerOptions options) // Using the Unspecified family makes MsQuic handle connections from all IP addresses. address.Family = QUIC_ADDRESS_FAMILY_UNSPEC; } - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerStart( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ListenerStart( + _handle, alpnBuffers.Buffers, (uint)alpnBuffers.Count, &address), @@ -162,6 +162,7 @@ public async ValueTask AcceptConnectionAsync(CancellationToken c { ObjectDisposedException.ThrowIf(_disposed == 1, this); + GCHandle keepObject = GCHandle.Alloc(this); try { PendingConnection pendingConnection = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); @@ -175,6 +176,10 @@ public async ValueTask AcceptConnectionAsync(CancellationToken c ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } + finally + { + keepObject.Free(); + } } private unsafe int HandleEventNewConnection(ref NEW_CONNECTION_DATA data) @@ -261,7 +266,7 @@ public async ValueTask DisposeAsync() { unsafe { - MsQuicApi.Api.ApiTable->ListenerStop(_handle.QuicHandle); + MsQuicApi.Api.ListenerStop(_handle); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs index c11f3029a2136c..4c2633963477e9 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs @@ -70,9 +70,18 @@ public sealed partial class QuicStream { CancellationAction = target => { - if (target is QuicStream stream) + try { - stream.Abort(QuicAbortDirection.Read, stream._defaultErrorCode); + if (target is QuicStream stream) + { + stream.Abort(QuicAbortDirection.Read, stream._defaultErrorCode); + } + } + catch (ObjectDisposedException) + { + // We collided with a Dispose in another thread. This can happen + // when using CancellationTokenSource.CancelAfter. + // Ignore the exception } } }; @@ -83,13 +92,23 @@ public sealed partial class QuicStream { CancellationAction = target => { - if (target is QuicStream stream) + try + { + if (target is QuicStream stream) + { + stream.Abort(QuicAbortDirection.Write, stream._defaultErrorCode); + } + } + catch (ObjectDisposedException) { - stream.Abort(QuicAbortDirection.Write, stream._defaultErrorCode); + // We collided with a Dispose in another thread. This can happen + // when using CancellationTokenSource.CancelAfter. + // Ignore the exception } } }; private MsQuicBuffers _sendBuffers = new MsQuicBuffers(); + private object _sendBuffersLock = new object(); private readonly long _defaultErrorCode; @@ -141,14 +160,14 @@ internal unsafe QuicStream(MsQuicContextSafeHandle connectionHandle, QuicStreamT try { QUIC_HANDLE* handle; - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamOpen( - connectionHandle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamOpen( + connectionHandle, type == QuicStreamType.Unidirectional ? QUIC_STREAM_OPEN_FLAGS.UNIDIRECTIONAL : QUIC_STREAM_OPEN_FLAGS.NONE, &NativeCallback, (void*)GCHandle.ToIntPtr(context), &handle), "StreamOpen failed"); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->StreamClose, SafeHandleType.Stream, connectionHandle); + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Stream, connectionHandle); } catch { @@ -179,12 +198,12 @@ internal unsafe QuicStream(MsQuicContextSafeHandle connectionHandle, QUIC_HANDLE GCHandle context = GCHandle.Alloc(this, GCHandleType.Weak); try { + _handle = new MsQuicContextSafeHandle(handle, context, SafeHandleType.Stream, connectionHandle); delegate* unmanaged[Cdecl] nativeCallback = &NativeCallback; - MsQuicApi.Api.ApiTable->SetCallbackHandler( - handle, + MsQuicApi.Api.SetCallbackHandler( + _handle, nativeCallback, (void*)GCHandle.ToIntPtr(context)); - _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->StreamClose, SafeHandleType.Stream, connectionHandle); } catch { @@ -220,8 +239,8 @@ internal ValueTask StartAsync(CancellationToken cancellationToken = default) { unsafe { - int status = MsQuicApi.Api.ApiTable->StreamStart( - _handle.QuicHandle, + int status = MsQuicApi.Api.StreamStart( + _handle, QUIC_STREAM_START_FLAGS.SHUTDOWN_ON_FAIL | QUIC_STREAM_START_FLAGS.INDICATE_PEER_ACCEPT); if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) { @@ -297,8 +316,8 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamReceiveSetEnabled( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamReceiveSetEnabled( + _handle, 1), "StreamReceivedSetEnabled failed"); } @@ -360,19 +379,34 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, bool completeWrites, Ca return valueTask; } - _sendBuffers.Initialize(buffer); - unsafe + lock (_sendBuffersLock) { - int status = MsQuicApi.Api.ApiTable->StreamSend( - _handle.QuicHandle, - _sendBuffers.Buffers, - (uint)_sendBuffers.Count, - completeWrites ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE, - null); - if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) + ObjectDisposedException.ThrowIf(_disposed == 1, this); // TODO: valueTask is left unobserved + unsafe { - _sendBuffers.Reset(); - _sendTcs.TrySetException(exception, final: true); + if (_sendBuffers.Count > 0 && _sendBuffers.Buffers[0].Buffer != null) + { + // _sendBuffers are not reset, meaning SendComplete for the previous WriteAsync call didn't arrive yet. + // In case of cancellation, the task from _sendTcs is finished before the aborting. It is technically possible for subsequent + // WriteAsync to grab the next task from _sendTcs and start executing before SendComplete event occurs for the previous (canceled) write. + // This is not an "invalid nested call", because the previous task has finished. Best guess is to mimic OperationAborted as it will be from Abort + // that would execute soon enough, if not already. Not final, because Abort should be the one to set final exception. + _sendTcs.TrySetException(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted), final: false); + return valueTask; + } + + _sendBuffers.Initialize(buffer); + int status = MsQuicApi.Api.StreamSend( + _handle, + _sendBuffers.Buffers, + (uint)_sendBuffers.Count, + completeWrites ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE, + null); + if (ThrowHelper.TryGetStreamExceptionForMsQuicStatus(status, out Exception? exception)) + { + _sendBuffers.Reset(); + _sendTcs.TrySetException(exception, final: true); + } } } @@ -419,8 +453,8 @@ public void Abort(QuicAbortDirection abortDirection, long errorCode) unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamShutdown( + _handle, flags, (ulong)errorCode), "StreamShutdown failed"); @@ -442,8 +476,8 @@ public void CompleteWrites() { unsafe { - ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.StreamShutdown( + _handle, QUIC_STREAM_SHUTDOWN_FLAGS.GRACEFUL, default), "StreamShutdown failed"); @@ -475,8 +509,8 @@ private unsafe int HandleEventStartComplete(ref START_COMPLETE data) private unsafe int HandleEventReceive(ref RECEIVE data) { ulong totalCopied = (ulong)_receiveBuffers.CopyFrom( - new ReadOnlySpan(data.Buffers, (int) data.BufferCount), - (int) data.TotalBufferLength, + new ReadOnlySpan(data.Buffers, (int)data.BufferCount), + (int)data.TotalBufferLength, data.Flags.HasFlag(QUIC_RECEIVE_FLAGS.FIN)); if (totalCopied < data.TotalBufferLength) { @@ -490,7 +524,12 @@ private unsafe int HandleEventReceive(ref RECEIVE data) } private unsafe int HandleEventSendComplete(ref SEND_COMPLETE data) { - _sendBuffers.Reset(); + // In case of cancellation, the task from _sendTcs is finished before the aborting. It is technically possible for subsequent WriteAsync to grab the next task + // from _sendTcs and start executing before SendComplete event occurs for the previous (canceled) write + lock (_sendBuffersLock) + { + _sendBuffers.Reset(); + } if (data.Canceled == 0) { _sendTcs.TrySetResult(); @@ -653,13 +692,16 @@ public override async ValueTask DisposeAsync() await valueTask.ConfigureAwait(false); _handle.Dispose(); - // TODO: memory leak if not disposed - _sendBuffers.Dispose(); + lock (_sendBuffersLock) + { + // TODO: memory leak if not disposed + _sendBuffers.Dispose(); + } unsafe void StreamShutdown(QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode) { - int status = MsQuicApi.Api.ApiTable->StreamShutdown( - _handle.QuicHandle, + int status = MsQuicApi.Api.StreamShutdown( + _handle, flags, (ulong)errorCode); if (StatusFailed(status)) diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs index 3831c8e6e9be9d..a12a14e8c9eb0f 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs @@ -336,5 +336,33 @@ public async Task Connect_PeerCertificateDisposed(bool useGetter) } peerCertificate.Dispose(); } + + [Fact] + public async Task Connection_AwaitsStream_ConnectionSurvivesGC() + { + const byte data = 0xDC; + + TaskCompletionSource listenerEndpointTcs = new TaskCompletionSource(); + await Task.WhenAll( + Task.Run(async () => + { + await using var listener = await CreateQuicListener(); + listenerEndpointTcs.SetResult(listener.LocalEndPoint); + await using var connection = await listener.AcceptConnectionAsync(); + await using var stream = await connection.AcceptInboundStreamAsync(); + var buffer = new byte[1]; + Assert.Equal(1, await stream.ReadAsync(buffer)); + Assert.Equal(data, buffer[0]); + }).WaitAsync(TimeSpan.FromSeconds(5)), + Task.Run(async () => + { + var endpoint = await listenerEndpointTcs.Task; + await using var connection = await CreateQuicConnection(endpoint); + await Task.Delay(TimeSpan.FromSeconds(0.5)); + GC.Collect(); + await using var stream = await connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional); + await stream.WriteAsync(new byte[1] { data }, completeWrites: true); + }).WaitAsync(TimeSpan.FromSeconds(5))); + } } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs index aa78c72c0b6e4b..643436a0d3f0f6 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs @@ -106,7 +106,7 @@ public async Task TwoListenersOnSamePort_DisjointAlpn_Success() QuicListenerOptions listenerOptions = CreateQuicListenerOptions(); listenerOptions.ListenEndPoint = listener1.LocalEndPoint; listenerOptions.ApplicationProtocols[0] = new SslApplicationProtocol("someprotocol"); - listenerOptions.ConnectionOptionsCallback = (_, _, _) => + listenerOptions.ConnectionOptionsCallback = (_, _, _) => { var options = CreateQuicServerOptions(); options.ServerAuthenticationOptions.ApplicationProtocols[0] = listenerOptions.ApplicationProtocols[0]; @@ -144,5 +144,27 @@ public async Task TwoListenersOnSamePort_SameAlpn_Throws() // await AssertThrowsQuicExceptionAsync(QuicError.InternalError, async () => await CreateQuicListener(listener.LocalEndPoint)); } + + [Fact] + public async Task Listener_AwaitsConnection_ListenerSurvivesGC() + { + TaskCompletionSource listenerEndpointTcs = new TaskCompletionSource(); + await Task.WhenAll( + Task.Run(async () => + { + await using var listener = await CreateQuicListener(); + listenerEndpointTcs.SetResult(listener.LocalEndPoint); + var connection = await listener.AcceptConnectionAsync(); + await connection.DisposeAsync(); + }).WaitAsync(TimeSpan.FromSeconds(5)), + Task.Run(async () => + { + var endpoint = await listenerEndpointTcs.Task; + await Task.Delay(TimeSpan.FromSeconds(0.5)); + GC.Collect(); + var connection = await CreateQuicConnection(endpoint); + await connection.DisposeAsync(); + }).WaitAsync(TimeSpan.FromSeconds(5))); + } } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index 5dafe8f6266a32..762fc4230abc0e 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -102,7 +102,7 @@ await WhenAllOrAnyFailed( } catch (Exception ex) { - _output?.WriteLine($"Failed to {ex.Message}"); + _output?.WriteLine($"Failed to connect: {ex.Message}"); throw; } })); @@ -153,14 +153,5 @@ public override void Dispose() } } } - - [OuterLoop("May take several seconds")] - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73377")] - public override Task Parallel_ReadWriteMultipleStreamsConcurrently() - { - return Task.CompletedTask; - } } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs index ade1bcb23a940f..1530d5a33b7b10 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationRemoteServer.cs @@ -96,6 +96,7 @@ public async Task DefaultConnect_EndToEnd_Ok(string host) [InlineData(true)] [InlineData(false)] [SkipOnPlatform(TestPlatforms.Android, "The invalid certificate is rejected by Android and the .NET validation code isn't reached")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", TestPlatforms.OSX)] public Task ConnectWithRevocation_WithCallback(bool checkRevocation) { X509RevocationMode mode = checkRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck; @@ -121,6 +122,7 @@ public Task ConnectWithRevocation_StapledOcsp(bool offlineContext) [Fact] [PlatformSpecific(TestPlatforms.Linux)] [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", typeof(PlatformDetection), nameof(PlatformDetection.IsDebian10))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/70981", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public Task ConnectWithRevocation_ServerCertWithoutContext_NoStapledOcsp() { // Offline will only work if diff --git a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs index e76ae87717ad42..94b7e980745566 100644 --- a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs +++ b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Runtime.Serialization; using System.Text.RegularExpressions; +using System.Threading; namespace System.Net { @@ -127,10 +128,9 @@ public bool UseDefaultCredentials private void UpdateRegexList() { - Regex[]? regexBypassList = null; if (_bypassList is ChangeTrackingArrayList bypassList) { - bypassList.IsChanged = false; + Regex[]? regexBypassList = null; if (bypassList.Count > 0) { regexBypassList = new Regex[bypassList.Count]; @@ -139,9 +139,14 @@ private void UpdateRegexList() regexBypassList[i] = new Regex((string)bypassList[i]!, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } } - } - _regexBypassList = regexBypassList; + _regexBypassList = regexBypassList; + bypassList.IsChanged = false; + } + else + { + _regexBypassList = null; + } } private bool IsMatchInBypassList(Uri input) @@ -219,7 +224,10 @@ public ChangeTrackingArrayList() { } public ChangeTrackingArrayList(ICollection c) : base(c) { } - public bool IsChanged { get; set; } + // While this type isn't intended to be mutated concurrently with reads, non-concurrent updates + // to the list might result in lazy initialization, and it's possible concurrent HTTP requests could race + // to trigger that initialization. + public volatile bool IsChanged; // Override the methods that can add, remove, or change the regexes in the bypass list. // Methods that only read (like CopyTo, BinarySearch, etc.) and methods that reorder diff --git a/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx b/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx index 401be8dc707616..e3d5079aafc0bc 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx +++ b/src/libraries/System.Net.WebSockets.Client/src/Resources/Strings.resx @@ -129,4 +129,10 @@ The WebSocket failed to negotiate max client window bits. The client requested {0} but the server responded with {1}. + + UseDefaultCredentials, Credentials, Proxy, ClientCertificates, RemoteCertificateValidationCallback and Cookies must not be set on ClientWebSocketOptions when an HttpMessageInvoker instance is also specified. These options should be set on the HttpMessageInvoker's underlying HttpMessageHandler instead. + + + An HttpMessageInvoker instance must be passed to ConnectAsync when using HTTP/2. + diff --git a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj index 28bc6b32221b13..b7da9d3e90becb 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj +++ b/src/libraries/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj @@ -45,7 +45,6 @@ - diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs index 6a8de0a712f581..c4069223db053f 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocketOptions.cs @@ -30,6 +30,14 @@ public sealed class ClientWebSocketOptions private HttpVersionPolicy _versionPolicy = HttpVersionPolicy.RequestVersionOrLower; private bool _collectHttpResponseDetails; + internal bool AreCompatibleWithCustomInvoker() => + !UseDefaultCredentials && + Credentials is null && + (_clientCertificates?.Count ?? 0) == 0 && + RemoteCertificateValidationCallback is null && + Cookies is null && + (Proxy is null || Proxy == WebSocketHandle.DefaultWebProxy.Instance); + internal ClientWebSocketOptions() { } // prevent external instantiation #region HTTP Settings diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index 6bb365012ef779..e27661ea29efff 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs @@ -48,9 +48,22 @@ public void Abort() public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, CancellationToken cancellationToken, ClientWebSocketOptions options) { bool disposeHandler = false; - invoker ??= new HttpMessageInvoker(SetupHandler(options, out disposeHandler)); - HttpResponseMessage? response = null; + if (invoker is null) + { + if (options.HttpVersion.Major >= 2 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher) + { + throw new ArgumentException(SR.net_WebSockets_CustomInvokerRequiredForHttp2, nameof(options)); + } + invoker = new HttpMessageInvoker(SetupHandler(options, out disposeHandler)); + } + else if (!options.AreCompatibleWithCustomInvoker()) + { + // This will not throw if the Proxy is a DefaultWebProxy. + throw new ArgumentException(SR.net_WebSockets_OptionsIncompatibleWithCustomInvoker, nameof(options)); + } + + HttpResponseMessage? response = null; bool disposeResponse = false; // force non-secure request to 1.1 whenever it is possible as HttpClient does @@ -237,12 +250,7 @@ private static SocketsHttpHandler SetupHandler(ClientWebSocketOptions options, o // Create the handler for this request and populate it with all of the options. // Try to use a shared handler rather than creating a new one just for this request, if // the options are compatible. - if (options.Credentials == null && - !options.UseDefaultCredentials && - options.Proxy == null && - options.Cookies == null && - options.RemoteCertificateValidationCallback == null && - (options._clientCertificates?.Count ?? 0) == 0) + if (options.AreCompatibleWithCustomInvoker() && options.Proxy is null) { disposeHandler = false; handler = s_defaultHandler; @@ -518,7 +526,7 @@ private static void ValidateHeader(HttpHeaders headers, string name, string expe } /// Used as a sentinel to indicate that ClientWebSocket should use the system's default proxy. - private sealed class DefaultWebProxy : IWebProxy + internal sealed class DefaultWebProxy : IWebProxy { public static DefaultWebProxy Instance { get; } = new DefaultWebProxy(); public ICredentials? Credentials { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs index 70cb6317f36635..d81baa53e4639b 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs @@ -16,14 +16,14 @@ public sealed class InvokerAbortTest : AbortTest { public InvokerAbortTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientAbortTest : AbortTest { public HttpClientAbortTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class AbortTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs index 5919f63157d705..534f62cca17d51 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs @@ -14,14 +14,14 @@ public sealed class InvokerCancelTest : CancelTest { public InvokerCancelTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientCancelTest : CancelTest { public HttpClientCancelTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class CancelTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs index a32587bc862fc9..0c8b94778a58ea 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Net.Test.Common; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,13 +9,10 @@ using Xunit; using Xunit.Abstractions; using System.Net.Http; -using System.Net.WebSockets.Client.Tests; +using System.Diagnostics; namespace System.Net.WebSockets.Client.Tests { - /// - /// ClientWebSocket tests that do require a remote server. - /// public class ClientWebSocketTestBase { public static readonly object[][] EchoServers = System.Net.Test.Common.Configuration.WebSockets.EchoServers; @@ -112,7 +108,38 @@ protected static async Task ReceiveEntireMessageAsync(We } } - protected virtual HttpMessageInvoker? GetInvoker() => null; + protected virtual bool UseCustomInvoker => false; + + protected virtual bool UseHttpClient => false; + + protected bool UseSharedHandler => !UseCustomInvoker && !UseHttpClient; + + protected Action? ConfigureCustomHandler; + + internal HttpMessageInvoker? GetInvoker() + { + var handler = new HttpClientHandler(); + + if (PlatformDetection.IsNotBrowser) + { + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + } + + ConfigureCustomHandler?.Invoke(handler); + + if (UseCustomInvoker) + { + Debug.Assert(!UseHttpClient); + return new HttpMessageInvoker(handler); + } + + if (UseHttpClient) + { + return new HttpClient(handler); + } + + return null; + } protected Task GetConnectedWebSocket(Uri uri, int TimeOutMilliseconds, ITestOutputHelper output) => WebSocketHelper.GetConnectedWebSocket(uri, TimeOutMilliseconds, output, invoker: GetInvoker()); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index c09df107c8538c..9034b38bd43f46 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -18,14 +18,14 @@ public sealed class InvokerCloseTest : CloseTest { public InvokerCloseTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientCloseTest : CloseTest { public HttpClientCloseTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class CloseTest : ClientWebSocketTestBase diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs index 1ec2f1ee39fa45..bdbbcb75222fa5 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.IO; using System.Net.Http; using System.Net.Test.Common; using System.Threading; @@ -11,34 +10,59 @@ using Xunit; using Xunit.Abstractions; -using static System.Net.Http.Functional.Tests.TestHelper; - namespace System.Net.WebSockets.Client.Tests { public sealed class InvokerConnectTest_Http2 : ConnectTest_Http2 { public InvokerConnectTest_Http2(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientConnectTest_Http2 : ConnectTest_Http2 { public HttpClientConnectTest_Http2(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } - public class ConnectTest_Http2 : ClientWebSocketTestBase + public sealed class HttpClientConnectTest_Http2_NoInvoker : ClientWebSocketTestBase { - public ConnectTest_Http2(ITestOutputHelper output) : base(output) { } + public HttpClientConnectTest_Http2_NoInvoker(ITestOutputHelper output) : base(output) { } + + public static IEnumerable ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException_MemberData() + { + yield return Options(options => options.HttpVersion = HttpVersion.Version20); + yield return Options(options => options.HttpVersion = HttpVersion.Version30); + yield return Options(options => options.HttpVersion = new Version(2, 1)); + yield return Options(options => options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher); + static object[] Options(Action configureOptions) => + new object[] { configureOptions }; + } [Theory] - [InlineData(false)] - [InlineData(true)] + [MemberData(nameof(ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException_MemberData))] + [SkipOnPlatform(TestPlatforms.Browser, "HTTP/2 WebSockets aren't supported on Browser")] + public async Task ConnectAsync_Http2WithNoInvoker_ThrowsArgumentException(Action configureOptions) + { + using var ws = new ClientWebSocket(); + configureOptions(ws.Options); + + Task connectTask = ws.ConnectAsync(new Uri("wss://dummy"), CancellationToken.None); + + Assert.Equal(TaskStatus.Faulted, connectTask.Status); + await Assert.ThrowsAsync("options", () => connectTask); + } + } + + public abstract class ConnectTest_Http2 : ClientWebSocketTestBase + { + public ConnectTest_Http2(ITestOutputHelper output) : base(output) { } + + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_VersionNotSupported_NoSsl_Throws(bool useHandler) + public async Task ConnectAsync_VersionNotSupported_NoSsl_Throws() { await Http2LoopbackServer.CreateClientAndServerAsync(async uri => { @@ -46,17 +70,10 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - t = cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - t = cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token); + var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); Assert.True(ex.InnerException.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL")); @@ -65,8 +82,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => async server => { Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 0 }); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -79,10 +95,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - t = cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token); var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); @@ -92,31 +107,22 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => async server => { Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 0 }); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); } [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] - [Theory] - [MemberData(nameof(SecureEchoServersAndBoolean))] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_Http11Server_DowngradeFail(Uri server, bool useHandler) + public async Task ConnectAsync_Http11Server_DowngradeFail() { using (var cws = new ClientWebSocket()) using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - Task t; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - t = cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); - } - else - { - t = cws.ConnectAsync(server, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + Task t = cws.ConnectAsync(Test.Common.Configuration.WebSockets.SecureRemoteEchoServer, GetInvoker(), cts.Token); + var ex = await Assert.ThrowsAnyAsync(() => t); Assert.IsType(ex.InnerException); Assert.True(ex.InnerException.Data.Contains("HTTP2_ENABLED")); @@ -126,34 +132,23 @@ public async Task ConnectAsync_Http11Server_DowngradeFail(Uri server, bool useHa [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [Theory] - [MemberData(nameof(EchoServersAndBoolean))] + [MemberData(nameof(EchoServers))] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_Http11Server_DowngradeSuccess(Uri server, bool useHandler) + public async Task ConnectAsync_Http11Server_DowngradeSuccess(Uri server) { using (var cws = new ClientWebSocket()) using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionOrLower; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(server, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + await cws.ConnectAsync(server, GetInvoker(), cts.Token); Assert.Equal(WebSocketState.Open, cws.State); } } - - [Theory] - [InlineData(false)] - [InlineData(true)] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ConnectAsync_VersionSupported_NoSsl_Success(bool useHandler) + public async Task ConnectAsync_VersionSupported_NoSsl_Success() { await Http2LoopbackServer.CreateClientAndServerAsync(async uri => { @@ -161,16 +156,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); } }, async server => @@ -178,8 +165,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -192,10 +178,8 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); } }, async server => @@ -203,8 +187,39 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); + } + + [Fact] + [SkipOnPlatform(TestPlatforms.Browser, "HTTP/2 WebSockets aren't supported on Browser")] + public async Task ConnectAsync_SameHttp2ConnectionUsedForMultipleWebSocketConnection() + { + await Http2LoopbackServer.CreateClientAndServerAsync(async uri => + { + using var cws1 = new ClientWebSocket(); + cws1.Options.HttpVersion = HttpVersion.Version20; + cws1.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + using var cws2 = new ClientWebSocket(); + cws2.Options.HttpVersion = HttpVersion.Version20; + cws2.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + HttpMessageInvoker? invoker = GetInvoker(); + + await cws1.ConnectAsync(uri, invoker, cts.Token); + await cws2.ConnectAsync(uri, invoker, cts.Token); + }, + async server => + { + await using Http2LoopbackConnection connection = await server.EstablishConnectionAsync(new SettingsEntry { SettingId = SettingId.EnableConnect, Value = 1 }); + + (int streamId1, _) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); + await connection.SendResponseHeadersAsync(streamId1, endStream: false, HttpStatusCode.OK); + + (int streamId2, _) = await connection.ReadAndParseRequestHeaderAsync(readBody: false); + await connection.SendResponseHeadersAsync(streamId2, endStream: false, HttpStatusCode.OK); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs index fd0eee1265cfa1..3fd63fbbb7c61a 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Http; using System.Net.Test.Common; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; - using Xunit; using Xunit.Abstractions; @@ -17,14 +16,78 @@ namespace System.Net.WebSockets.Client.Tests public sealed class InvokerConnectTest : ConnectTest { public InvokerConnectTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + + protected override bool UseCustomInvoker => true; + + public static IEnumerable ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException_MemberData() + { + yield return Throw(options => options.UseDefaultCredentials = true); + yield return NoThrow(options => options.UseDefaultCredentials = false); + yield return Throw(options => options.Credentials = new NetworkCredential()); + yield return Throw(options => options.Proxy = new WebProxy()); + yield return Throw(options => options.ClientCertificates.Add(Test.Common.Configuration.Certificates.GetClientCertificate())); + yield return NoThrow(options => options.ClientCertificates = new X509CertificateCollection()); + yield return Throw(options => options.RemoteCertificateValidationCallback = delegate { return true; }); + yield return Throw(options => options.Cookies = new CookieContainer()); + + // We allow no proxy or the default proxy to be used + yield return NoThrow(options => { }); + yield return NoThrow(options => options.Proxy = null); + + // These options don't conflict with the custom invoker + yield return NoThrow(options => options.HttpVersion = new Version(2, 0)); + yield return NoThrow(options => options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher); + yield return NoThrow(options => options.SetRequestHeader("foo", "bar")); + yield return NoThrow(options => options.AddSubProtocol("foo")); + yield return NoThrow(options => options.KeepAliveInterval = TimeSpan.FromSeconds(42)); + yield return NoThrow(options => options.DangerousDeflateOptions = new WebSocketDeflateOptions()); + yield return NoThrow(options => options.CollectHttpResponseDetails = true); + + static object[] Throw(Action configureOptions) => + new object[] { configureOptions, true }; + + static object[] NoThrow(Action configureOptions) => + new object[] { configureOptions, false }; + } + + [Theory] + [MemberData(nameof(ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException_MemberData))] + [SkipOnPlatform(TestPlatforms.Browser, "Custom invoker is ignored on Browser")] + public async Task ConnectAsync_CustomInvokerWithIncompatibleWebSocketOptions_ThrowsArgumentException(Action configureOptions, bool shouldThrow) + { + using var invoker = new HttpMessageInvoker(new SocketsHttpHandler + { + ConnectCallback = (_, _) => ValueTask.FromException(new Exception("ConnectCallback")) + }); + + using var ws = new ClientWebSocket(); + configureOptions(ws.Options); + + Task connectTask = ws.ConnectAsync(new Uri("wss://dummy"), invoker, CancellationToken.None); + if (shouldThrow) + { + Assert.Equal(TaskStatus.Faulted, connectTask.Status); + await Assert.ThrowsAsync("options", () => connectTask); + } + else + { + WebSocketException ex = await Assert.ThrowsAsync(() => connectTask); + Assert.NotNull(ex.InnerException); + Assert.Contains("ConnectCallback", ex.InnerException.Message); + } + + foreach (X509Certificate cert in ws.Options.ClientCertificates) + { + cert.Dispose(); + } + } } public sealed class HttpClientConnectTest : ConnectTest { public HttpClientConnectTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class ConnectTest : ClientWebSocketTestBase @@ -258,7 +321,13 @@ public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri se using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create()) { - cws.Options.Proxy = new WebProxy(proxyServer.Uri); + ConfigureCustomHandler = handler => handler.Proxy = new WebProxy(proxyServer.Uri); + + if (UseSharedHandler) + { + cws.Options.Proxy = new WebProxy(proxyServer.Uri); + } + await ConnectAsync(cws, server, cts.Token); string expectedCloseStatusDescription = "Client close status"; @@ -267,6 +336,7 @@ public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri se Assert.Equal(WebSocketState.Closed, cws.State); Assert.Equal(WebSocketCloseStatus.NormalClosure, cws.CloseStatus); Assert.Equal(expectedCloseStatusDescription, cws.CloseStatusDescription); + Assert.Equal(1, proxyServer.Connections); } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 262e45ae414db8..9836f31df16cb7 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -18,14 +18,14 @@ public sealed class InvokerDeflateTests : DeflateTests { public InvokerDeflateTests(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientDeflateTests : DeflateTests { public HttpClientDeflateTests(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } [PlatformSpecific(~TestPlatforms.Browser)] diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs index 5f3be83d5bfb79..ef21a36e44fa8f 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Http2.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Net.Http; -using System.Net.Sockets; using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; @@ -11,19 +10,29 @@ using Xunit; using Xunit.Abstractions; -using static System.Net.Http.Functional.Tests.TestHelper; - namespace System.Net.WebSockets.Client.Tests { - public class SendReceiveTest_Http2 : ClientWebSocketTestBase + public sealed class HttpClientSendReceiveTest_Http2 : SendReceiveTest_Http2 + { + public HttpClientSendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override bool UseHttpClient => true; + } + + public sealed class InvokerSendReceiveTest_Http2 : SendReceiveTest_Http2 + { + public InvokerSendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override bool UseCustomInvoker => true; + } + + public abstract class SendReceiveTest_Http2 : ClientWebSocketTestBase { public SendReceiveTest_Http2(ITestOutputHelper output) : base(output) { } - [Theory] - [InlineData(false)] - [InlineData(true)] + [Fact] [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] - public async Task ReceiveNoThrowAfterSend_NoSsl(bool useHandler) + public async Task ReceiveNoThrowAfterSend_NoSsl() { var serverMessage = new byte[] { 4, 5, 6 }; await Http2LoopbackServer.CreateClientAndServerAsync(async uri => @@ -32,16 +41,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; - if (useHandler) - { - var handler = new SocketsHttpHandler(); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); - } - else - { - await cws.ConnectAsync(uri, cts.Token); - } + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; + + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); await cws.SendAsync(new byte[] { 2, 3, 4 }, WebSocketMessageType.Binary, true, cts.Token); @@ -63,8 +65,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => byte[] constructMessage = prefix.Concat(serverMessage).ToArray(); await connection.SendResponseDataAsync(streamId, constructMessage, endStream: false); - }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false } - ); + }, new Http2Options() { WebSocketEndpoint = true, UseSsl = false }); } [Fact] @@ -78,10 +79,9 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.HttpVersion = HttpVersion.Version20; - cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; + cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact; - var handler = CreateSocketsHttpHandler(allowAllCertificates: true); - await cws.ConnectAsync(uri, new HttpMessageInvoker(handler), cts.Token); + await cws.ConnectAsync(uri, GetInvoker(), cts.Token); await cws.SendAsync(new byte[] { 2, 3, 4 }, WebSocketMessageType.Binary, true, cts.Token); @@ -103,8 +103,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => byte[] constructMessage = prefix.Concat(serverMessage).ToArray(); await connection.SendResponseDataAsync(streamId, constructMessage, endStream: false); - }, new Http2Options() { WebSocketEndpoint = true } - ); + }, new Http2Options() { WebSocketEndpoint = true }); } } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs index 6597b6f9ec6315..ec3913c02c16c9 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs @@ -17,28 +17,28 @@ public sealed class InvokerMemorySendReceiveTest : MemorySendReceiveTest { public InvokerMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientMemorySendReceiveTest : MemorySendReceiveTest { public HttpClientMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public sealed class InvokerArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest { public InvokerArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + protected override bool UseCustomInvoker => true; } public sealed class HttpClientArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest { public HttpClientArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } - protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + protected override bool UseHttpClient => true; } public class MemorySendReceiveTest : SendReceiveTest diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj index 413026393547ba..4cf77fe8bffa1c 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.csproj @@ -10,8 +10,8 @@ $(NoWarn);0809;0618;CS8614;CS3015 SilverlightPlatform true - true - true + true + true $(DefineConstants);FEATURE_WASM_PERFTRACING $(DefineConstants);FEATURE_WASM_THREADS $(DefineConstants);BUILDING_CORELIB_REFERENCE @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6f3a16e6136363..a908b752f45f48 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -617,7 +617,6 @@ - @@ -2456,4 +2455,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System/Array.cs b/src/libraries/System.Private.CoreLib/src/System/Array.cs index 32435ec4367ec8..b665d3094e1dc9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Array.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Array.cs @@ -1328,17 +1328,17 @@ public static int IndexOf(T[] array, T value, int startIndex, int count) { if (Unsafe.SizeOf() == sizeof(byte)) { - int result = SpanHelpers.IndexOf( + int result = SpanHelpers.IndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), Unsafe.As(ref value), count); return (result >= 0 ? startIndex : 0) + result; } - else if (Unsafe.SizeOf() == sizeof(char)) + else if (Unsafe.SizeOf() == sizeof(short)) { - int result = SpanHelpers.IndexOf( - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), - Unsafe.As(ref value), + int result = SpanHelpers.IndexOfValueType( + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), startIndex), + Unsafe.As(ref value), count); return (result >= 0 ? startIndex : 0) + result; } @@ -1586,19 +1586,19 @@ public static int LastIndexOf(T[] array, T value, int startIndex, int count) if (Unsafe.SizeOf() == sizeof(byte)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); return (result >= 0 ? endIndex : 0) + result; } - else if (Unsafe.SizeOf() == sizeof(char)) + else if (Unsafe.SizeOf() == sizeof(short)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( - ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), - Unsafe.As(ref value), + int result = SpanHelpers.LastIndexOfValueType( + ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), + Unsafe.As(ref value), count); return (result >= 0 ? endIndex : 0) + result; @@ -1606,7 +1606,7 @@ ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)) else if (Unsafe.SizeOf() == sizeof(int)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); @@ -1616,7 +1616,7 @@ ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), else if (Unsafe.SizeOf() == sizeof(long)) { int endIndex = startIndex - count + 1; - int result = SpanHelpers.LastIndexOf( + int result = SpanHelpers.LastIndexOfValueType( ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(array)), endIndex), Unsafe.As(ref value), count); diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs index fd6ce30c9e14b5..22eff2b8b16834 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -14,22 +14,37 @@ public static int CountDigits(UInt128 value) { ulong upper = value.Upper; - if (upper < 5) + // 1e19 is 8AC7_2304_89E8_0000 + // 1e20 is 5_6BC7_5E2D_6310_0000 + // 1e21 is 36_35C9_ADC5_DEA0_0000 + + if (upper == 0) { + // We have less than 64-bits, so just return the lower count return CountDigits(value.Lower); } - int digits = 19; + // We have more than 1e19, so we have at least 20 digits + int digits = 20; if (upper > 5) { - digits++; + // ((2^128) - 1) / 1e20 < 34_02_823_669_209_384_635 which + // is 18.5318 digits, meaning the result definitely fits + // into 64-bits and we only need to add the lower digit count + value /= new UInt128(0x5, 0x6BC7_5E2D_6310_0000); // value /= 1e20 + Debug.Assert(value.Upper == 0); + digits += CountDigits(value.Lower); } - else if (value.Lower >= 0x6BC75E2D63100000) + else if ((upper == 5) && (value.Lower >= 0x6BC75E2D63100000)) { + // We're greater than 1e20, but definitely less than 1e21 + // so we have exactly 21 digits + digits++; + Debug.Assert(digits == 21); } return digits; diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 6541d1a1f8cf0a..2a454153011960 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -920,7 +920,7 @@ private static bool TryConvertFromTruncating(TOther value, out byte resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -987,14 +987,14 @@ static bool INumberBase.TryConvertToChecked(byte value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1061,14 +1061,14 @@ static bool INumberBase.TryConvertToSaturating(byte value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(byte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(byte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1135,7 +1135,7 @@ static bool INumberBase.TryConvertToTruncating(byte value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index b93226bb9778e8..d506f503d48bee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1715,7 +1715,7 @@ static bool INumberBase.TryConvertFromTruncating(TOther value, out /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1782,14 +1782,14 @@ static bool INumberBase.TryConvertToChecked(char value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1856,14 +1856,14 @@ static bool INumberBase.TryConvertToSaturating(char value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(char value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(char value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1930,7 +1930,7 @@ static bool INumberBase.TryConvertToTruncating(char value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs index 7a49887d17c287..3fa2f71ade78fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs @@ -683,7 +683,7 @@ public bool TryDequeue([MaybeNullWhen(false)] out T result) // check and this check, another item could have arrived). if (head._nextSegment == null) { - result = default!; + result = default; return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs index e9f9f6c307f98d..7a793902166a02 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs @@ -231,7 +231,7 @@ public bool TryDequeue([MaybeNullWhen(false)] out T result) if (_size == 0) { - result = default!; + result = default; return false; } @@ -263,7 +263,7 @@ public bool TryPeek([MaybeNullWhen(false)] out T result) { if (_size == 0) { - result = default!; + result = default; return false; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 5436baebfdc572..8b469e385b13d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -1645,7 +1645,7 @@ private static bool TryConvertFrom(TOther value, out decimal result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(decimal value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1712,26 +1712,26 @@ static bool INumberBase.TryConvertToChecked(decimal value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(decimal value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(decimal value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(decimal value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(decimal value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(decimal value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1804,7 +1804,7 @@ private static bool TryConvertTo(decimal value, [NotNullWhen(true)] out } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 177eb912575ede..75580d6a88c8ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -1243,7 +1243,7 @@ private static bool TryConvertFrom(TOther value, out double result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(double value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1304,26 +1304,26 @@ static bool INumberBase.TryConvertToChecked(double value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(double value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(double value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(double value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(double value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(double value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1402,7 +1402,7 @@ private static bool TryConvertTo(double value, [NotNullWhen(true)] out T } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index 89404415b5e17d..cd4535867b0334 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -436,7 +436,7 @@ private static int FindDefinedIndex(ulong[] ulValues, ulong ulValue) int ulValuesLength = ulValues.Length; ref ulong start = ref MemoryMarshal.GetArrayDataReference(ulValues); return ulValuesLength <= NumberOfValuesThreshold ? - SpanHelpers.IndexOf(ref start, ulValue, ulValuesLength) : + SpanHelpers.IndexOfValueType(ref Unsafe.As(ref start), (long)ulValue, ulValuesLength) : SpanHelpers.BinarySearch(ref start, ulValuesLength, ulValue); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs index ef51de122f7179..76d2b06b20b903 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs @@ -264,8 +264,8 @@ internal static int IndexOfOrdinalIgnoreCase(ReadOnlySpan source, ReadOnly { // Do a quick search for the first element of "value". int relativeIndex = isLetter ? - SpanHelpers.IndexOfAny(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceLength) : - SpanHelpers.IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueChar, searchSpaceLength); + SpanHelpers.IndexOfAnyChar(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceLength) : + SpanHelpers.IndexOfChar(ref Unsafe.Add(ref searchSpace, offset), valueChar, searchSpaceLength); if (relativeIndex < 0) { break; diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index ba931c1f93cec9..ecab10dbc00d66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -1727,7 +1727,7 @@ private static bool TryConvertFrom(TOther value, out Half result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Half value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1788,26 +1788,26 @@ static bool INumberBase.TryConvertToChecked(Half value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Half value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Half value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Half value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(Half value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(Half value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1879,7 +1879,7 @@ private static bool TryConvertTo(Half value, [NotNullWhen(true)] out TOt } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index 67237805c3a744..f538524471a918 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs @@ -328,7 +328,7 @@ private static void CreateParentsAndDirectory(string fullPath, UnixFileMode unix } ReadOnlySpan mkdirPath = fullPath.AsSpan(0, i); - int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); + int result = Interop.Sys.MkDir(mkdirPath, (int)DefaultUnixCreateDirectoryMode); if (result == 0) { break; // Created parent. @@ -360,7 +360,8 @@ private static void CreateParentsAndDirectory(string fullPath, UnixFileMode unix for (i = stackDir.Length - 1; i >= 0; i--) { ReadOnlySpan mkdirPath = fullPath.AsSpan(0, stackDir[i]); - int result = Interop.Sys.MkDir(mkdirPath, (int)unixCreateMode); + UnixFileMode mode = i == 0 ? unixCreateMode : DefaultUnixCreateDirectoryMode; + int result = Interop.Sys.MkDir(mkdirPath, (int)mode); if (result < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); diff --git a/src/libraries/System.Private.CoreLib/src/System/IParsable.cs b/src/libraries/System.Private.CoreLib/src/System/IParsable.cs index af220483d640ef..e2891fbd689475 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IParsable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IParsable.cs @@ -8,7 +8,7 @@ namespace System /// Defines a mechanism for parsing a string to a value. /// The type that implements this interface. public interface IParsable - where TSelf : IParsable + where TSelf : IParsable? { /// Parses a string into a value. /// The string to parse. diff --git a/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs b/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs index 8ad117cfa4c6eb..2e24d5173e2fbd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ISpanParsable.cs @@ -8,7 +8,7 @@ namespace System /// Defines a mechanism for parsing a span of characters to a value. /// The type that implements this interface. public interface ISpanParsable : IParsable - where TSelf : ISpanParsable + where TSelf : ISpanParsable? { /// Parses a span of characters into a value. /// The span of characters to parse. diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index b67d6e89a7006b..d66655060f3e13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -1865,7 +1865,7 @@ private static bool TryConvertFromTruncating(TOther value, out Int128 re /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1926,14 +1926,14 @@ static bool INumberBase.TryConvertToChecked(Int128 value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2000,14 +2000,14 @@ static bool INumberBase.TryConvertToSaturating(Int128 value, [No } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Int128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Int128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2069,7 +2069,7 @@ static bool INumberBase.TryConvertToTruncating(Int128 value, [No } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index a357d272360fa9..94fe0e4844f084 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -1148,7 +1148,7 @@ private static bool TryConvertFromTruncating(TOther value, out short res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1209,14 +1209,14 @@ static bool INumberBase.TryConvertToChecked(short value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1278,14 +1278,14 @@ static bool INumberBase.TryConvertToSaturating(short value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(short value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(short value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1346,7 +1346,7 @@ static bool INumberBase.TryConvertToTruncating(short value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index 317c789c7d676a..7b7c3f2765fb10 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -1158,7 +1158,7 @@ private static bool TryConvertFromTruncating(TOther value, out int resul /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1219,14 +1219,14 @@ static bool INumberBase.TryConvertToChecked(int value, [NotNullWhen } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1290,14 +1290,14 @@ static bool INumberBase.TryConvertToSaturating(int value, [NotNullW } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(int value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(int value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1358,7 +1358,7 @@ static bool INumberBase.TryConvertToTruncating(int value, [NotNullW } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index 569240329da35c..8752d93c2f8372 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -1143,7 +1143,7 @@ private static bool TryConvertFromTruncating(TOther value, out long resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1204,14 +1204,14 @@ static bool INumberBase.TryConvertToChecked(long value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1283,14 +1283,14 @@ static bool INumberBase.TryConvertToSaturating(long value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(long value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(long value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1351,7 +1351,7 @@ static bool INumberBase.TryConvertToTruncating(long value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index 9f6b414343b15e..3fbed0cea35200 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -1117,7 +1117,7 @@ private static bool TryConvertFromTruncating(TOther value, out nint resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1178,14 +1178,14 @@ static bool INumberBase.TryConvertToChecked(nint value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1250,14 +1250,14 @@ static bool INumberBase.TryConvertToSaturating(nint value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(nint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(nint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1318,7 +1318,7 @@ static bool INumberBase.TryConvertToTruncating(nint value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 658a464d6d3d2d..5ae66529eb20db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace System { @@ -269,28 +268,33 @@ public static bool Contains(this Span span, T value) where T : IEquatable< if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.Contains( + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.Contains( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.ContainsValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(int)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(long)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); + } } return SpanHelpers.Contains(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -308,28 +312,33 @@ public static bool Contains(this ReadOnlySpan span, T value) where T : IEq if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.Contains( + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.Contains( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.ContainsValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(int)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(long)) - return 0 <= SpanHelpers.IndexOfValueType( + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.ContainsValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); + } } return SpanHelpers.Contains(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -346,15 +355,15 @@ public static int IndexOf(this Span span, T value) where T : IEquatable if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOf( + return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + if (Unsafe.SizeOf() == sizeof(short)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); if (Unsafe.SizeOf() == sizeof(int)) @@ -412,16 +421,33 @@ public static int LastIndexOf(this Span span, T value) where T : IEquatabl if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.LastIndexOf( + { + return SpanHelpers.LastIndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -503,7 +529,7 @@ public static int IndexOfAnyExcept(this Span span, ReadOnlySpan values) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value) where T : IEquatable? { - if (RuntimeHelpers.IsBitwiseEquatable()) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { if (Unsafe.SizeOf() == sizeof(byte)) { @@ -512,36 +538,34 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(short)) + else if (Unsafe.SizeOf() == sizeof(short)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(int)) + else if (Unsafe.SizeOf() == sizeof(int)) { return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } - - if (Unsafe.SizeOf() == sizeof(long)) + else { + Debug.Assert(Unsafe.SizeOf() == sizeof(long)); + return SpanHelpers.IndexOfAnyExceptValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); } } - - return SpanHelpers.IndexOfAnyExcept( - ref MemoryMarshal.GetReference(span), - value, - span.Length); + else + { + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value, span.Length); + } } /// Searches for the first index of any value other than the specified or . @@ -553,18 +577,30 @@ ref MemoryMarshal.GetReference(span), /// The index in the span of the first occurrence of any value other than and . /// If all of the values are or , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - for (int i = 0; i < span.Length; i++) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } /// Searches for the first index of any value other than the specified , , or . @@ -577,25 +613,38 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T val /// The index in the span of the first occurrence of any value other than , , and . /// If all of the values are , , and , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - for (int i = 0; i < span.Length; i++) + if (RuntimeHelpers.IsBitwiseEquatable()) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? + private static int IndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? { - if (RuntimeHelpers.IsBitwiseEquatable()) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { if (Unsafe.SizeOf() == sizeof(byte)) { @@ -617,40 +666,9 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value3), span.Length); } - else if (Unsafe.SizeOf() == sizeof(int)) - { - return SpanHelpers.IndexOfAnyExceptValueType( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), - span.Length); - } - else if (Unsafe.SizeOf() == sizeof(long)) - { - return SpanHelpers.IndexOfAnyExceptValueType( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - Unsafe.As(ref value3), - span.Length); - } - } - - for (int i = 0; i < span.Length; i++) - { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2) && - !EqualityComparer.Default.Equals(span[i], value3)) - { - return i; - } } - return -1; + return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, value3, span.Length); } /// Searches for the first index of any value other than the specified . @@ -680,7 +698,7 @@ public static int IndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpan case 3: return IndexOfAnyExcept(span, values[0], values[1], values[2]); - case 4: // common for searching whitespaces + case 4: return IndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); default: @@ -751,17 +769,46 @@ public static int LastIndexOfAnyExcept(this Span span, ReadOnlySpan val /// The index in the span of the last occurrence of any value other than . /// If all of the values are , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); } - } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else + { + Debug.Assert(Unsafe.SizeOf() == sizeof(long)); - return -1; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + } + else + { + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value, span.Length); + } } /// Searches for the last index of any value other than the specified or . @@ -773,18 +820,30 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value) wh /// The index in the span of the last occurrence of any value other than and . /// If all of the values are or , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); } } - return -1; + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } /// Searches for the last index of any value other than the specified , , or . @@ -797,19 +856,62 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T /// The index in the span of the last occurrence of any value other than , , and . /// If all of the values are , , and , returns -1. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - for (int i = span.Length - 1; i >= 0; i--) + if (RuntimeHelpers.IsBitwiseEquatable()) { - if (!EqualityComparer.Default.Equals(span[i], value0) && - !EqualityComparer.Default.Equals(span[i], value1) && - !EqualityComparer.Default.Equals(span[i], value2)) + if (Unsafe.SizeOf() == sizeof(byte)) { - return i; + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); } } - return -1; + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LastIndexOfAnyExcept(this ReadOnlySpan span, T value0, T value1, T value2, T value3) where T : IEquatable? + { + if (SpanHelpers.CanVectorizeAndBenefit(span.Length)) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyExceptValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + Unsafe.As(ref value3), + span.Length); + } + } + + return SpanHelpers.LastIndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, value2, value3, span.Length); } /// Searches for the last index of any value other than the specified . @@ -840,6 +942,9 @@ public static int LastIndexOfAnyExcept(this ReadOnlySpan span, ReadOnlySpa case 3: return LastIndexOfAnyExcept(span, values[0], values[1], values[2]); + case 4: + return LastIndexOfAnyExcept(span, values[0], values[1], values[2], values[3]); + default: for (int i = span.Length - 1; i >= 0; i--) { @@ -912,15 +1017,27 @@ public static int IndexOf(this ReadOnlySpan span, T value) where T : IEqua if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOf( + return SpanHelpers.IndexOfValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value), span.Length); - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + if (Unsafe.SizeOf() == sizeof(short)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + + if (Unsafe.SizeOf() == sizeof(int)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + + if (Unsafe.SizeOf() == sizeof(long)) + return SpanHelpers.IndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); } @@ -966,16 +1083,33 @@ public static int LastIndexOf(this ReadOnlySpan span, T value) where T : I if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), - span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.LastIndexOf( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value), + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.LastIndexOfValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } } return SpanHelpers.LastIndexOf(ref MemoryMarshal.GetReference(span), value, span.Length); @@ -1024,18 +1158,21 @@ public static int IndexOfAny(this Span span, T value0, T value1) where T : if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); @@ -1054,20 +1191,23 @@ public static int IndexOfAny(this Span span, T value0, T value1, T value2) if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), Unsafe.As(ref value2), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); @@ -1094,18 +1234,21 @@ public static int IndexOfAny(this ReadOnlySpan span, T value0, T value1) w if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); @@ -1124,20 +1267,23 @@ public static int IndexOfAny(this ReadOnlySpan span, T value0, T value1, T if (RuntimeHelpers.IsBitwiseEquatable()) { if (Unsafe.SizeOf() == sizeof(byte)) - return SpanHelpers.IndexOfAny( + { + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), Unsafe.As(ref value0), Unsafe.As(ref value1), Unsafe.As(ref value2), span.Length); - - if (Unsafe.SizeOf() == sizeof(char)) - return SpanHelpers.IndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.IndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), span.Length); + } } return SpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); @@ -1158,7 +1304,7 @@ public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan value ref byte valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); if (values.Length == 2) { - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), valueRef, Unsafe.Add(ref valueRef, 1), @@ -1166,7 +1312,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), } else if (values.Length == 3) { - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref Unsafe.As(ref MemoryMarshal.GetReference(span)), valueRef, Unsafe.Add(ref valueRef, 1), @@ -1175,30 +1321,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), } } - if (Unsafe.SizeOf() == sizeof(char)) + if (Unsafe.SizeOf() == sizeof(short)) { - ref char spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); - ref char valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + ref short spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); + ref short valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); switch (values.Length) { case 0: return -1; case 1: - return SpanHelpers.IndexOf( - ref spanRef, - valueRef, - span.Length); + return SpanHelpers.IndexOfValueType(ref spanRef, valueRef, span.Length); case 2: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), span.Length); case 3: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1206,7 +1349,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); case 4: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1215,7 +1358,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); case 5: - return SpanHelpers.IndexOfAny( + return SpanHelpers.IndexOfAnyValueType( ref spanRef, valueRef, Unsafe.Add(ref valueRef, 1), @@ -1225,7 +1368,7 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); default: - return IndexOfAnyProbabilistic(ref spanRef, span.Length, ref valueRef, values.Length); + return IndexOfAnyProbabilistic(ref Unsafe.As(ref spanRef), span.Length, ref Unsafe.As(ref valueRef), values.Length); } } } @@ -1272,12 +1415,25 @@ private static unsafe int IndexOfAnyProbabilistic(ref char searchSpace, int sear [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this Span span, T value0, T value1) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } @@ -1292,13 +1448,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this Span span, T value0, T value1, T value2) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } @@ -1330,12 +1500,25 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(values)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, span.Length); } @@ -1350,13 +1533,27 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(span)), [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable? { - if (Unsafe.SizeOf() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable()) - return SpanHelpers.LastIndexOfAny( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - Unsafe.As(ref value0), - Unsafe.As(ref value1), - Unsafe.As(ref value2), - span.Length); + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + } + } return SpanHelpers.LastIndexOfAny(ref MemoryMarshal.GetReference(span), value0, value1, value2, span.Length); } @@ -1371,28 +1568,66 @@ public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan v { if (RuntimeHelpers.IsBitwiseEquatable()) { - if (Unsafe.SizeOf() == sizeof(char)) + if (Unsafe.SizeOf() == sizeof(byte)) { + ref byte valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); + if (values.Length == 2) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valueRef, + Unsafe.Add(ref valueRef, 1), + span.Length); + } + else if (values.Length == 3) + { + return SpanHelpers.LastIndexOfAnyValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + span.Length); + } + } + + if (Unsafe.SizeOf() == sizeof(short)) + { + ref short spanRef = ref Unsafe.As(ref MemoryMarshal.GetReference(span)); + ref short valueRef = ref Unsafe.As(ref MemoryMarshal.GetReference(values)); switch (values.Length) { case 0: return -1; case 1: - return LastIndexOf(span, values[0]); + return SpanHelpers.LastIndexOfValueType(ref spanRef, valueRef, span.Length); case 2: - return LastIndexOfAny(span, values[0], values[1]); + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + span.Length); case 3: - return LastIndexOfAny(span, values[0], values[1], values[2]); + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + span.Length); + + case 4: + return SpanHelpers.LastIndexOfAnyValueType( + ref spanRef, + valueRef, + Unsafe.Add(ref valueRef, 1), + Unsafe.Add(ref valueRef, 2), + Unsafe.Add(ref valueRef, 3), + span.Length); default: - return LastIndexOfAnyProbabilistic( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - span.Length, - ref Unsafe.As(ref MemoryMarshal.GetReference(values)), - values.Length); + return LastIndexOfAnyProbabilistic(ref Unsafe.As(ref spanRef), span.Length, ref Unsafe.As(ref valueRef), values.Length); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs index ca11868381a295..ecdfe953a0c8ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will be added to . /// The type that contains the sum of and . public interface IAdditionOperators - where TSelf : IAdditionOperators + where TSelf : IAdditionOperators? { /// Adds two values together to compute their sum. /// The value to which is added. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs index 1e95b5fb35cc86..be11ca7e2fbf7a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IAdditiveIdentity.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the additive identify of . public interface IAdditiveIdentity - where TSelf : IAdditiveIdentity + where TSelf : IAdditiveIdentity? { /// Gets the additive identity of the current type. static abstract TResult AdditiveIdentity { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs index 7e6565579d4ff0..151ae4c11d0829 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryFloatingPointIeee754.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryFloatingPointIeee754 : IBinaryNumber, IFloatingPointIeee754 - where TSelf : IBinaryFloatingPointIeee754 + where TSelf : IBinaryFloatingPointIeee754? { } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs index e182d35b04246b..067d6c338384e1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryInteger : IBinaryNumber, IShiftOperators - where TSelf : IBinaryInteger + where TSelf : IBinaryInteger? { /// Computes the quotient and remainder of two values. /// The value which divides. @@ -25,7 +25,12 @@ static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) /// The number of leading zeros in . static virtual TSelf LeadingZeroCount(TSelf value) { - TSelf bitCount = TSelf.CreateChecked(value.GetByteCount() * 8L); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + TSelf bitCount = TSelf.CreateChecked(value!.GetByteCount() * 8L); if (value == TSelf.Zero) { @@ -132,7 +137,12 @@ static virtual TSelf ReadLittleEndian(ReadOnlySpan source, bool isUnsigned /// The result of rotating left by . static virtual TSelf RotateLeft(TSelf value, int rotateAmount) { - int bitCount = checked(value.GetByteCount() * 8); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + int bitCount = checked(value!.GetByteCount() * 8); return (value << rotateAmount) | (value >> (bitCount - rotateAmount)); } @@ -142,7 +152,12 @@ static virtual TSelf RotateLeft(TSelf value, int rotateAmount) /// The result of rotating right by . static virtual TSelf RotateRight(TSelf value, int rotateAmount) { - int bitCount = checked(value.GetByteCount() * 8); + if (!typeof(TSelf).IsValueType) + { + ArgumentNullException.ThrowIfNull(value); + } + + int bitCount = checked(value!.GetByteCount() * 8); return (value >> rotateAmount) | (value << (bitCount - rotateAmount)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs index 9b45fb59cc73b1..5dbe5bc9576098 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryNumber.cs @@ -8,7 +8,7 @@ namespace System.Numerics public interface IBinaryNumber : IBitwiseOperators, INumber - where TSelf : IBinaryNumber + where TSelf : IBinaryNumber? { /// Gets an instance of the binary type in which all bits are set. static virtual TSelf AllBitsSet => ~TSelf.Zero; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs index 15a7aa311f6427..36399a1b6d96c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBitwiseOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will is used in the operation with . /// The type that contains the result of op . public interface IBitwiseOperators - where TSelf : IBitwiseOperators + where TSelf : IBitwiseOperators? { /// Computes the bitwise-and of two values. /// The value to and with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs index d6b8883aa718c9..474ea46e7f656c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IComparisonOperators.cs @@ -9,7 +9,7 @@ namespace System.Numerics /// The type that is returned as a result of the comparison. public interface IComparisonOperators : IEqualityOperators - where TSelf : IComparisonOperators + where TSelf : IComparisonOperators? { /// Compares two values to determine which is less. /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs index 159ad6bc44e2dc..edc0022c584479 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDecrementOperators.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for decrementing a given value. /// The type that implements this interface. public interface IDecrementOperators - where TSelf : IDecrementOperators + where TSelf : IDecrementOperators? { /// Decrements a value. /// The value to decrement. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs index a8ed07a6335910..20535020efcd71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IDivisionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will divide . /// The type that contains the quotient of and . public interface IDivisionOperators - where TSelf : IDivisionOperators + where TSelf : IDivisionOperators? { /// Divides two values together to compute their quotient. /// The value which divides. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs index 397e4489aefbeb..ebf11d8576e4ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IEqualityOperators.cs @@ -8,18 +8,18 @@ namespace System.Numerics /// The type that will be compared with . /// The type that is returned as a result of the comparison. public interface IEqualityOperators - where TSelf : IEqualityOperators + where TSelf : IEqualityOperators? { /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . /// true if is equal to ; otherwise, false. - static abstract TResult operator ==(TSelf left, TOther right); + static abstract TResult operator ==(TSelf? left, TOther? right); /// Compares two values to determine inequality. /// The value to compare with . /// The value to compare with . /// true if is not equal to ; otherwise, false. - static abstract TResult operator !=(TSelf left, TOther right); + static abstract TResult operator !=(TSelf? left, TOther? right); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs index 85660ab3ba3c8b..b060924c8ceadb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IExponentialFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IExponentialFunctions : IFloatingPointConstants - where TSelf : IExponentialFunctions + where TSelf : IExponentialFunctions? { /// Computes E raised to a given power. /// The power to which E is raised. @@ -37,6 +37,6 @@ public interface IExponentialFunctions /// Computes 10 raised to a given power and subtracts one. /// The power to which 10 is raised. /// 10 - 1 - static virtual TSelf Exp10M1(TSelf x) => TSelf.Exp10M1(x) - TSelf.One; + static virtual TSelf Exp10M1(TSelf x) => TSelf.Exp10(x) - TSelf.One; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs index 9b5f9757543616..c8e58d8f8fb826 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPoint.cs @@ -9,7 +9,7 @@ public interface IFloatingPoint : IFloatingPointConstants, INumber, ISignedNumber - where TSelf : IFloatingPoint + where TSelf : IFloatingPoint? { /// Computes the ceiling of a value. /// The value whose ceiling is to be computed. @@ -36,7 +36,7 @@ public interface IFloatingPoint /// The value to round. /// The mode under which should be rounded. /// The result of rounding to the nearest integer using . - static virtual TSelf Round(TSelf x, MidpointRounding mode) => TSelf.Round(x, digits: 0, MidpointRounding.ToEven); + static virtual TSelf Round(TSelf x, MidpointRounding mode) => TSelf.Round(x, digits: 0, mode); /// Rounds a value to a specified number of fractional-digits using the default rounding mode (). /// The value to round. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs index 7bb8bbd229bd17..1ab2d6a4ed55f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointConstants.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface IFloatingPointConstants : INumberBase - where TSelf : IFloatingPointConstants + where TSelf : IFloatingPointConstants? { /// Gets the mathematical constant e. static abstract TSelf E { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs index 7d9e33c994957f..35d7b42cd79310 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs @@ -13,7 +13,7 @@ public interface IFloatingPointIeee754 IPowerFunctions, IRootFunctions, ITrigonometricFunctions - where TSelf : IFloatingPointIeee754 + where TSelf : IFloatingPointIeee754? { /// Gets the smallest value such that can be added to 0 that does not result in 0. static abstract TSelf Epsilon { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs index 3fe24e7ef6eba2..8929a539145d60 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IHyperbolicFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IHyperbolicFunctions : IFloatingPointConstants - where TSelf : IHyperbolicFunctions + where TSelf : IHyperbolicFunctions? { /// Computes the hyperbolic arc-cosine of a value. /// The value, in radians, whose hyperbolic arc-cosine is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs index 9024a4a6615ace..9dc434e4c42527 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IIncrementOperators.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for incrementing a given value. /// The type that implements this interface. public interface IIncrementOperators - where TSelf : IIncrementOperators + where TSelf : IIncrementOperators? { /// Increments a value. /// The value to increment. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs index 668a8b42d1d1f6..343327946e4233 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ILogarithmicFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface ILogarithmicFunctions : IFloatingPointConstants - where TSelf : ILogarithmicFunctions + where TSelf : ILogarithmicFunctions? { /// Computes the natural (base-E) logarithm of a value. /// The value whose natural logarithm is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs index fe424d1aa6733f..aabfdd10a9fba2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs @@ -6,7 +6,7 @@ namespace System.Numerics /// Defines a mechanism for getting the minimum and maximum value of a type. /// The type that implements this interface. public interface IMinMaxValue - where TSelf : IMinMaxValue + where TSelf : IMinMaxValue? { /// Gets the minimum value of the current type. static abstract TSelf MinValue { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs index fae966ef0be180..772241f9f16865 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IModulusOperators.cs @@ -9,7 +9,7 @@ namespace System.Numerics /// The type that contains the modulus or remainder of and . /// This type represents the % in C# which is often used to compute the remainder and may differ from an actual modulo operation depending on the type that implements the interface. public interface IModulusOperators - where TSelf : IModulusOperators + where TSelf : IModulusOperators? { /// Divides two values together to compute their modulus or remainder. /// The value which divides. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs index fa087d0d8a967b..32c7c40db8c915 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplicativeIdentity.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the multiplicative identify of . public interface IMultiplicativeIdentity - where TSelf : IMultiplicativeIdentity + where TSelf : IMultiplicativeIdentity? { /// Gets the multiplicative identity of the current type. static abstract TResult MultiplicativeIdentity { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs index a3e4d52ec42b81..e93e3d466823c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IMultiplyOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will multiply . /// The type that contains the product of and . public interface IMultiplyOperators - where TSelf : IMultiplyOperators + where TSelf : IMultiplyOperators? { /// Multiplies two values together to compute their product. /// The value which multiplies. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs index a3413ee78641e1..e41d0b999850b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumber.cs @@ -14,7 +14,7 @@ public interface INumber IComparisonOperators, IModulusOperators, INumberBase - where TSelf : INumber + where TSelf : INumber? { /// Clamps a value to an inclusive minimum and maximum value. /// The value to clamp. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs index ed159927d5d339..7bc025bf806ce0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -24,7 +24,7 @@ public interface INumberBase ISubtractionOperators, IUnaryPlusOperators, IUnaryNegationOperators - where TSelf : INumberBase + where TSelf : INumberBase? { /// Gets the value 1 for the type. static abstract TSelf One { get; } @@ -49,7 +49,9 @@ public interface INumberBase /// is not representable by . [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateChecked(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -57,7 +59,7 @@ static virtual TSelf CreateChecked(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) + else if (!TSelf.TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -72,7 +74,9 @@ static virtual TSelf CreateChecked(TOther value) /// is not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateSaturating(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -80,7 +84,7 @@ static virtual TSelf CreateSaturating(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) + else if (!TSelf.TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -95,7 +99,9 @@ static virtual TSelf CreateSaturating(TOther value) /// is not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual TSelf CreateTruncating(TOther value) +#nullable disable where TOther : INumberBase +#nullable restore { TSelf? result; @@ -103,7 +109,7 @@ static virtual TSelf CreateTruncating(TOther value) { result = (TSelf)(object)value; } - else if (!TSelf.TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) + else if (!TSelf.TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) { ThrowHelper.ThrowNotSupportedException(); } @@ -268,24 +274,30 @@ static virtual TSelf CreateTruncating(TOther value) /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. /// is not representable by . - protected static abstract bool TryConvertFromChecked(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromChecked(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert a value to an instance of the current type, saturating any values that fall outside the representable range of the current type. /// The type of . /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertFromSaturating(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromSaturating(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert a value to an instance of the current type, truncating any values that fall outside the representable range of the current type. /// The type of . /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertFromTruncating(TOther value, [NotNullWhen(true)] out TSelf? result) + protected static abstract bool TryConvertFromTruncating(TOther value, [MaybeNullWhen(false)] out TSelf result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, throwing an overflow exception for any values that fall outside the representable range of the current type. /// The type to which should be converted. @@ -293,24 +305,30 @@ protected static abstract bool TryConvertFromTruncating(TOther value, [N /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. /// is not representable by . - protected static abstract bool TryConvertToChecked(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToChecked(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, saturating any values that fall outside the representable range of the current type. /// The type to which should be converted. /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertToSaturating(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToSaturating(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to convert an instance of the current type to another type, truncating any values that fall outside the representable range of the current type. /// The type to which should be converted. /// The value which is used to create the instance of . /// On return, contains an instance of converted from . /// false if is not supported; otherwise, true. - protected static abstract bool TryConvertToTruncating(TSelf value, [NotNullWhen(true)] out TOther? result) + protected static abstract bool TryConvertToTruncating(TSelf value, [MaybeNullWhen(false)] out TOther result) +#nullable disable where TOther : INumberBase; +#nullable restore /// Tries to parses a string into a value. /// The string to parse. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs index 9c37729b6a6490..5f054364e4c75a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IPowerFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IPowerFunctions : INumberBase - where TSelf : IPowerFunctions + where TSelf : IPowerFunctions? { /// Computes a value raised to a given power. /// The value which is raised to the power of . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs index f8eef19b5f6fff..715ab83e5a5033 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IRootFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface IRootFunctions : IFloatingPointConstants - where TSelf : IRootFunctions + where TSelf : IRootFunctions? { /// Computes the cube-root of a value. /// The value whose cube-root is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs index 0ef088e5f5b447..45ccece20b9bdc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IShiftOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type used to specify the amount by which should be shifted. /// The type that contains the result of shifting by . public interface IShiftOperators - where TSelf : IShiftOperators + where TSelf : IShiftOperators? { /// Shifts a value left by a given amount. /// The value which is shifted left by . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs index df1ed28b195630..cc4e9651d5d305 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISignedNumber.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface ISignedNumber : INumberBase - where TSelf : ISignedNumber + where TSelf : ISignedNumber? { /// Gets the value -1 for the type. static abstract TSelf NegativeOne { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs index b79879bef3651e..e422900d604962 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ISubtractionOperators.cs @@ -8,7 +8,7 @@ namespace System.Numerics /// The type that will be subtracted from . /// The type that contains the difference of subtracted from . public interface ISubtractionOperators - where TSelf : ISubtractionOperators + where TSelf : ISubtractionOperators? { /// Subtracts two values to compute their difference. /// The value from which is subtracted. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs index 8539cd13c5f5e5..cac27fad008344 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. public interface ITrigonometricFunctions : IFloatingPointConstants - where TSelf : ITrigonometricFunctions + where TSelf : ITrigonometricFunctions? { /// Computes the arc-cosine of a value. /// The value whose arc-cosine is to be computed. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs index 6a8ee11fe07a49..0af4580635cb3f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryNegationOperators.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the result of negating . public interface IUnaryNegationOperators - where TSelf : IUnaryNegationOperators + where TSelf : IUnaryNegationOperators? { /// Computes the unary negation of a value. /// The value for which to compute its unary negation. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs index 916c5f13fc5b19..5ba2d43f6a94c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnaryPlusOperators.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements this interface. /// The type that contains the result of negating . public interface IUnaryPlusOperators - where TSelf : IUnaryPlusOperators + where TSelf : IUnaryPlusOperators? { /// Computes the unary plus of a value. /// The value for which to compute its unary plus. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs index 1cae84b36b7afc..d5de09443e2e52 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IUnsignedNumber.cs @@ -7,7 +7,7 @@ namespace System.Numerics /// The type that implements the interface. public interface IUnsignedNumber : INumberBase - where TSelf : IUnsignedNumber + where TSelf : IUnsignedNumber? { } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs index b9eb29598bb6e9..5dee06aa6adf00 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs @@ -895,6 +895,14 @@ public static bool LessThanOrEqualAll(Vector left, Vector right) public static bool LessThanOrEqualAny(Vector left, Vector right) where T : struct => LessThanOrEqual(left, right).As() != Vector.Zero; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector LoadUnsafe(ref T source, nuint elementOffset) + where T : struct + { + source = ref Unsafe.Add(ref source, elementOffset); + return Unsafe.ReadUnaligned>(ref Unsafe.As(ref source)); + } + /// Computes the maximum of two vectors on a per-element basis. /// The vector to compare with . /// The vector to compare with . @@ -1658,6 +1666,14 @@ public static Vector SquareRoot(Vector value) return result; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void StoreUnsafe(this Vector source, ref T destination, nuint elementOffset) + where T : struct + { + destination = ref Unsafe.Add(ref destination, elementOffset); + Unsafe.WriteUnaligned(ref Unsafe.As(ref destination), source); + } + /// Subtracts two vectors to compute their difference. /// The vector from which will be subtracted. /// The vector to subtract from . diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs deleted file mode 100644 index 530c5e767f7745..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Reflection -{ - internal sealed partial class FieldAccessor - { - private readonly RtFieldInfo _fieldInfo; - public InvocationFlags _invocationFlags; - - public FieldAccessor(RtFieldInfo fieldInfo) - { - _fieldInfo = fieldInfo; - } - - [DebuggerStepThrough] - [DebuggerHidden] - public object? GetValue(object? obj) - { - // Todo: add strategy for calling IL Emit-based version - return _fieldInfo.GetValueNonEmit(obj); - } - - [DebuggerStepThrough] - [DebuggerHidden] - public void SetValue(object? obj, object? value) - { - // Todo: add strategy for calling IL Emit-based version - _fieldInfo.SetValueNonEmit(obj, value); - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index f312049b82fcab..9aeef16dadb4dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs @@ -1492,14 +1492,14 @@ private static bool TryConvertFrom(TOther value, out NFloat result) } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(NFloat value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -1605,26 +1605,26 @@ static bool INumberBase.TryConvertToChecked(NFloat value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(NFloat value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(NFloat value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(NFloat value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(NFloat value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(NFloat value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { if (typeof(TOther) == typeof(byte)) @@ -1754,7 +1754,7 @@ private static bool TryConvertTo(NFloat value, [NotNullWhen(true)] out T } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index 81d6f994da54f9..d4b45f21bc9d74 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -459,8 +459,6 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c) string name, BindingFlags bindingFlags, Binder? binder, object? target, object?[]? providedArgs, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParams) { - ArgumentNullException.ThrowIfNull(name); - const BindingFlags MemberBindingMask = (BindingFlags)0x000000FF; const BindingFlags InvocationMask = (BindingFlags)0x0000FF00; const BindingFlags BinderGetSetField = BindingFlags.GetField | BindingFlags.SetField; @@ -567,10 +565,12 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c) // PutDispProperty and\or PutRefDispProperty ==> SetProperty. if ((bindingFlags & (BindingFlags.PutDispProperty | BindingFlags.PutRefDispProperty)) != 0) bindingFlags |= BindingFlags.SetProperty; + + ArgumentNullException.ThrowIfNull(name); if (name.Length == 0 || name.Equals("[DISPID=0]")) { // in InvokeMember we always pretend there is a default member if none is provided and we make it ToString - name = GetDefaultMemberName()! ?? "ToString"; + name = GetDefaultMemberName() ?? "ToString"; } // GetField or SetField diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index 8b9dc686c15a21..afd9b04f08e265 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -1114,7 +1114,7 @@ private static bool TryConvertFromTruncating(TOther value, out sbyte res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1175,14 +1175,14 @@ static bool INumberBase.TryConvertToChecked(sbyte value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1243,14 +1243,14 @@ static bool INumberBase.TryConvertToSaturating(sbyte value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(sbyte value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(sbyte value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1311,7 +1311,7 @@ static bool INumberBase.TryConvertToTruncating(sbyte value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index cd31393df23aea..54e1ef148e9f11 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -1223,7 +1223,7 @@ private static bool TryConvertFrom(TOther value, out float result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(float value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1284,26 +1284,26 @@ static bool INumberBase.TryConvertToChecked(float value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(float value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(float value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(float value, [MaybeNullWhen(false)] out TOther result) { return TryConvertTo(value, out result); } - private static bool TryConvertTo(float value, [NotNullWhen(true)] out TOther result) + private static bool TryConvertTo(float value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1382,7 +1382,7 @@ private static bool TryConvertTo(float value, [NotNullWhen(true)] out TO } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs index 5029e01a3161d7..61565cfd27db38 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System @@ -22,7 +22,7 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return IndexOf(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain IndexOf + return IndexOfValueType(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain IndexOf nint offset = 0; byte valueHead = value; @@ -38,7 +38,7 @@ public static int IndexOf(ref byte searchSpace, int searchSpaceLength, ref byte while (remainingSearchSpaceLength > 0) { // Do a quick search for the first element of "value". - int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); + int relativeIndex = IndexOfValueType(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -197,7 +197,7 @@ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref b int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return LastIndexOf(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain LastIndexOf + return LastIndexOfValueType(ref searchSpace, value, searchSpaceLength); // for single-byte values use plain LastIndexOf int offset = 0; byte valueHead = value; @@ -217,7 +217,7 @@ public static int LastIndexOf(ref byte searchSpace, int searchSpaceLength, ref b break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. // Do a quick search for the first element of "value". - int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength); + int relativeIndex = LastIndexOfValueType(ref searchSpace, valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -333,828 +333,52 @@ ref Unsafe.Add(ref searchSpace, offset + bitPos), } } - // Adapted from IndexOf(...) - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static bool Contains(ref byte searchSpace, byte value, int length) + [DoesNotReturn] + private static void ThrowMustBeNullTerminatedString() { - Debug.Assert(length >= 0); - - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } - - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - ref byte start = ref Unsafe.AddByteOffset(ref searchSpace, offset); - - if (uValue == Unsafe.AddByteOffset(ref start, 0) || - uValue == Unsafe.AddByteOffset(ref start, 1) || - uValue == Unsafe.AddByteOffset(ref start, 2) || - uValue == Unsafe.AddByteOffset(ref start, 3) || - uValue == Unsafe.AddByteOffset(ref start, 4) || - uValue == Unsafe.AddByteOffset(ref start, 5) || - uValue == Unsafe.AddByteOffset(ref start, 6) || - uValue == Unsafe.AddByteOffset(ref start, 7)) - { - goto Found; - } - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - ref byte start = ref Unsafe.AddByteOffset(ref searchSpace, offset); - - if (uValue == Unsafe.AddByteOffset(ref start, 0) || - uValue == Unsafe.AddByteOffset(ref start, 1) || - uValue == Unsafe.AddByteOffset(ref start, 2) || - uValue == Unsafe.AddByteOffset(ref start, 3)) - { - goto Found; - } - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine--; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - - offset++; - } - - if (Vector.IsHardwareAccelerated && (offset < (uint)length)) - { - lengthToExamine = ((uint)length - offset) & (nuint)~(Vector.Count - 1); - - Vector values = new(value); - Vector matches; - - while (offset < lengthToExamine) - { - matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (matches == Vector.Zero) - { - offset += (nuint)Vector.Count; - continue; - } - - goto Found; - } - - // The total length is at least Vector.Count, so instead of falling back to a - // sequential scan for the remainder, we check the vector read from the end -- note: unaligned read necessary. - // We do this only if at least one element is left. - if (offset < (uint)length) - { - offset = (uint)(length - Vector.Count); - matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (matches != Vector.Zero) - { - goto Found; - } - } - } - - return false; - - Found: - return true; + throw new ArgumentException(SR.Arg_MustBeNullTerminatedString); } + // IndexOfNullByte processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. + // This behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOf(ref byte searchSpace, byte value, int length) + internal static unsafe int IndexOfNullByte(ref byte searchSpace) { - Debug.Assert(length >= 0); + const int Length = int.MaxValue; - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions + const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; + nuint lengthToExamine = (nuint)(uint)Length; if (Vector128.IsHardwareAccelerated) { // Avx2 branch also operates on Sse2 sizes, so check is combined. - if (length >= Vector128.Count * 2) - { - lengthToExamine = UnalignedCountVector128(ref searchSpace); - } - } - else if (Vector.IsHardwareAccelerated) - { - if (length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } - } - SequentialScan: - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) - goto Found4; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) - goto Found5; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) - goto Found6; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) - goto Found7; - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - - offset += 1; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length. - // However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not - // have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining. - if (Vector256.IsHardwareAccelerated) - { - if (offset < (nuint)(uint)length) - { - if ((((nuint)(uint)Unsafe.AsPointer(ref searchSpace) + offset) & (nuint)(Vector256.Count - 1)) != 0) - { - // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches - // with no upper bound e.g. String.strlen. - // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. - // This ensures we do not fault across memory pages while searching for an end of string. - Vector128 values = Vector128.Create(value); - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as below - uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - } - else - { - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - } - - lengthToExamine = GetByteVector256SpanLength(offset, length); - if (lengthToExamine > offset) - { - Vector256 values = Vector256.Create(value); - do - { - Vector256 search = Vector256.LoadUnsafe(ref searchSpace, offset); - uint matches = Vector256.Equals(values, search).ExtractMostSignificantBits(); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector256.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } while (lengthToExamine > offset); - } - - lengthToExamine = GetByteVector128SpanLength(offset, length); - if (lengthToExamine > offset) - { - Vector128 values = Vector128.Create(value); - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as above - uint matches = Vector128.Equals(values, search).ExtractMostSignificantBits(); - if (matches == 0) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - } - else - { - // Find bitflag offset of first match and add to current offset - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } - } - else if (Vector128.IsHardwareAccelerated) - { - if (offset < (nuint)(uint)length) - { - lengthToExamine = GetByteVector128SpanLength(offset, length); - - Vector128 values = Vector128.Create(value); - while (lengthToExamine > offset) - { - Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - - // Same method as above - Vector128 compareResult = Vector128.Equals(values, search); - if (compareResult == Vector128.Zero) - { - // Zero flags set so no matches - offset += (nuint)Vector128.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - uint matches = compareResult.ExtractMostSignificantBits(); - return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } + lengthToExamine = UnalignedCountVector128(ref searchSpace); } else if (Vector.IsHardwareAccelerated) { - if (offset < (nuint)(uint)length) - { - lengthToExamine = GetByteVectorSpanLength(offset, length); - - Vector values = new Vector(value); - - while (lengthToExamine > offset) - { - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); - if (Vector.Zero.Equals(matches)) - { - offset += (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)offset + LocateFirstFoundByte(matches); - } - - if (offset < (nuint)(uint)length) - { - lengthToExamine = ((nuint)(uint)length - offset); - goto SequentialScan; - } - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int LastIndexOf(ref byte searchSpace, byte value, int length) - { - Debug.Assert(length >= 0); - - uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); + lengthToExamine = UnalignedCountVector(ref searchSpace); } SequentialScan: while (lengthToExamine >= 8) { lengthToExamine -= 8; - offset -= 8; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) - goto Found7; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) - goto Found6; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) - goto Found5; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) - goto Found4; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) - goto Found3; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) - goto Found2; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) - goto Found1; - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) - goto Found; - } - - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values = new Vector(value); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset - (nuint)Vector.Count)); - if (Vector.Zero.Equals(matches)) - { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported) - { - // Avx2 branch also operates on Sse2 sizes, so check is combined. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Sse2 intrinsics are supported, and length is enough to use them so use that path. - // We jump forward to the intrinsics at the end of the method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, as it is used later - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found7; - - offset += 8; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - - offset += 4; - } - - while (lengthToExamine > 0) - { - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Sse2.IsSupported) - { - int matches; - if (Avx2.IsSupported) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create(value0); - Vector256 values1 = Vector256.Create(value1); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector256(ref searchSpace, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = Avx2.MoveMask( - Avx2.Or( - Avx2.CompareEqual(values0, search), - Avx2.CompareEqual(values1, search))); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector256(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = Avx2.MoveMask( - Avx2.Or( - Avx2.CompareEqual(values0, search), - Avx2.CompareEqual(values1, search))); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector128(ref searchSpace, offset); - - matches = Sse2.MoveMask( - Sse2.Or( - Sse2.CompareEqual(values0, search), - Sse2.CompareEqual(values1, search)) - .AsByte()); - // Note that MoveMask has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = LoadVector128(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = Sse2.MoveMask( - Sse2.Or( - Sse2.CompareEqual(values0, search), - Sse2.CompareEqual(values1, search))); - if (matches == 0) - { - // None matched - goto NotFound; - } - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset - offset += (nuint)BitOperations.TrailingZeroCount(matches); - goto Found; - } - else if (AdvSimd.Arm64.IsSupported) - { - Vector128 search; - Vector128 matches; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector128(ref searchSpace, offset); - - matches = AdvSimd.Or( - AdvSimd.CompareEqual(values0, search), - AdvSimd.CompareEqual(values1, search)); - - if (matches == Vector128.Zero) - { - offset += (nuint)Vector128.Count; - continue; - } - - // Find bitflag offset of first match and add to current offset - offset += FindFirstMatchedLane(matches); - - goto Found; - } - - // Move to Vector length from end for final compare - search = LoadVector128(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = AdvSimd.Or( - AdvSimd.CompareEqual(values0, search), - AdvSimd.CompareEqual(values1, search)); - - if (matches == Vector128.Zero) - { - // None matched - goto NotFound; - } - - // Find bitflag offset of first match and add to current offset - offset += FindFirstMatchedLane(matches); - - goto Found; - } - else if (Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchSpace, offset); - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)LocateFirstFoundByte(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Avx2 branch also operates on Sse2 sizes, so check is combined. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated, and length is enough to use them so use that path. - // We jump forward to the intrinsics at the end of the method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, as it is used later - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 4)) goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 5)) goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 6)) goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 7)) goto Found7; offset += 8; @@ -1164,17 +388,13 @@ public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byt { lengthToExamine -= 4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 1)) goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 2)) goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset + 3)) goto Found3; offset += 4; @@ -1182,417 +402,149 @@ public static int IndexOfAny(ref byte searchSpace, byte value0, byte value1, byt while (lengthToExamine > 0) { - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + lengthToExamine -= 1; + + if (uValue == Unsafe.AddByteOffset(ref searchSpace, offset)) goto Found; offset += 1; - lengthToExamine -= 1; } - NotFound: - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) + // We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length. + // However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not + // have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining. + if (Vector256.IsHardwareAccelerated) { - uint matches; - if (Vector256.IsHardwareAccelerated) + if (offset < (nuint)(uint)Length) { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) + if ((((nuint)(uint)Unsafe.AsPointer(ref searchSpace) + offset) & (nuint)(Vector256.Count - 1)) != 0) + { + // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches + // with no upper bound e.g. String.strlen. + // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. + // This ensures we do not fault across memory pages while searching for an end of string. + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); + + // Same method as below + uint matches = Vector128.Equals(Vector128.Zero, search).ExtractMostSignificantBits(); + if (matches == 0) + { + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } + } + + lengthToExamine = GetByteVector256SpanLength(offset, Length); + if (lengthToExamine > offset) { - Vector256 values0 = Vector256.Create(value0); - Vector256 values1 = Vector256.Create(value1); - Vector256 values2 = Vector256.Create(value2); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) + do { - search = Vector256.LoadUnsafe(ref searchSpace, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)).ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, + Vector256 search = Vector256.LoadUnsafe(ref searchSpace, offset); + uint matches = Vector256.Equals(Vector256.Zero, search).ExtractMostSignificantBits(); + // Note that MoveMask has converted the equal vector elements into a set of bit flags, // So the bit position in 'matches' corresponds to the element offset. if (matches == 0) { - // None matched + // Zero flags set so no matches offset += (nuint)Vector256.Count; continue; } - goto IntrinsicsMatch; - } + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); + } while (lengthToExamine > offset); + } + + lengthToExamine = GetByteVector128SpanLength(offset, Length); + if (lengthToExamine > offset) + { + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)).ExtractMostSignificantBits(); + // Same method as above + uint matches = Vector128.Equals(Vector128.Zero, search).ExtractMostSignificantBits(); if (matches == 0) { - // None matched - goto NotFound; + // Zero flags set so no matches + offset += (nuint)Vector128.Count; + } + else + { + // Find bitflag offset of first match and add to current offset + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); } + } - goto IntrinsicsMatch; + if (offset < (nuint)(uint)Length) + { + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); + } + else if (Vector128.IsHardwareAccelerated) + { + if (offset < (nuint)(uint)Length) { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create(value0); - Vector128 values1 = Vector128.Create(value1); - Vector128 values2 = Vector128.Create(value2); - // First time this checks against 0 and we will move into final compare if it fails. + lengthToExamine = GetByteVector128SpanLength(offset, Length); + while (lengthToExamine > offset) { - search = Vector128.LoadUnsafe(ref searchSpace, offset); + Vector128 search = Vector128.LoadUnsafe(ref searchSpace, offset); - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); + // Same method as above + Vector128 compareResult = Vector128.Equals(Vector128.Zero, search); if (compareResult == Vector128.Zero) { - // None matched + // Zero flags set so no matches offset += (nuint)Vector128.Count; continue; } - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.ExtractMostSignificantBits(); - goto IntrinsicsMatch; + // Find bitflag offset of first match and add to current offset + uint matches = compareResult.ExtractMostSignificantBits(); + return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches)); } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) + + if (offset < (nuint)(uint)Length) { - // None matched - goto NotFound; + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } - matches = compareResult.ExtractMostSignificantBits(); } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset - offset += (nuint)BitOperations.TrailingZeroCount(matches); - goto Found; } else if (Vector.IsHardwareAccelerated) { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchSpace, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchSpace, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) + if (offset < (nuint)(uint)Length) { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)LocateFirstFoundByte(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - public static int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); - } - SequentialScan: - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - offset -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found7; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp) - goto Found; - } + lengthToExamine = GetByteVectorSpanLength(offset, Length); - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); - var matches = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(matches)) + while (lengthToExamine > offset) { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; - } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; - } - } - return -1; - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)offset; - Found1: - return (int)(offset + 1); - Found2: - return (int)(offset + 2); - Found3: - return (int)(offset + 3); - Found4: - return (int)(offset + 4); - Found5: - return (int)(offset + 5); - Found6: - return (int)(offset + 6); - Found7: - return (int)(offset + 7); - } - - public static int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) - { - Debug.Assert(length >= 0); - - uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions - uint uValue1 = value1; - uint uValue2 = value2; - nuint offset = (nuint)(uint)length; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVectorFromEnd(ref searchSpace, length); - } - SequentialScan: - uint lookUp; - while (lengthToExamine >= 8) - { - lengthToExamine -= 8; - offset -= 8; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 7); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found7; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 6); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found6; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 5); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found5; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 4); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found4; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - if (lengthToExamine >= 4) - { - lengthToExamine -= 4; - offset -= 4; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 3); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found3; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 2); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found2; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset + 1); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found1; - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - while (lengthToExamine > 0) - { - lengthToExamine -= 1; - offset -= 1; - - lookUp = Unsafe.AddByteOffset(ref searchSpace, offset); - if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) - goto Found; - } - - if (Vector.IsHardwareAccelerated && (offset > 0)) - { - lengthToExamine = (offset & (nuint)~(Vector.Count - 1)); - - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - while (lengthToExamine > (nuint)(Vector.Count - 1)) - { - Vector search = LoadVector(ref searchSpace, offset - (nuint)Vector.Count); + var matches = Vector.Equals(Vector.Zero, LoadVector(ref searchSpace, offset)); + if (Vector.Zero.Equals(matches)) + { + offset += (nuint)Vector.Count; + continue; + } - var matches = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); + // Find offset of first match and add to current offset + return (int)offset + LocateFirstFoundByte(matches); + } - if (Vector.Zero.Equals(matches)) + if (offset < (nuint)(uint)Length) { - offset -= (nuint)Vector.Count; - lengthToExamine -= (nuint)Vector.Count; - continue; + lengthToExamine = ((nuint)(uint)Length - offset); + goto SequentialScan; } - - // Find offset of first match and add to current offset - return (int)(offset) - Vector.Count + LocateLastFoundByte(matches); - } - - if (offset > 0) - { - lengthToExamine = offset; - goto SequentialScan; } } - return -1; + + ThrowMustBeNullTerminatedString(); Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 return (int)offset; Found1: @@ -2201,34 +1153,6 @@ private static unsafe nuint UnalignedCountVector128(ref byte searchSpace) return (nuint)(uint)((Vector128.Count - unaligned) & (Vector128.Count - 1)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe nuint UnalignedCountVectorFromEnd(ref byte searchSpace, int length) - { - nint unaligned = (nint)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); - return (nuint)(uint)(((length & (Vector.Count - 1)) + unaligned) & (Vector.Count - 1)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint FindFirstMatchedLane(Vector128 compareResult) - { - Debug.Assert(AdvSimd.Arm64.IsSupported); - - // Mask to help find the first lane in compareResult that is set. - // MSB 0x10 corresponds to 1st lane, 0x01 corresponds to 0th lane and so forth. - Vector128 mask = Vector128.Create((ushort)0x1001).AsByte(); - - // Find the first lane that is set inside compareResult. - Vector128 maskedSelectedLanes = AdvSimd.And(compareResult, mask); - Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(maskedSelectedLanes, maskedSelectedLanes); - ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); - - // It should be handled by compareResult != Vector.Zero - Debug.Assert(selectedLanes != 0); - - // Find the first lane that is set inside compareResult. - return (uint)BitOperations.TrailingZeroCount(selectedLanes) >> 2; - } - public static void Reverse(ref byte buf, nuint length) { if (Avx2.IsSupported && (nuint)Vector256.Count * 2 <= length) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs index 29a5906643a2a7..8a44db8cc3aed8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs @@ -5,7 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System @@ -24,7 +23,7 @@ public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char if (valueTailLength == 0) { // for single-char values use plain IndexOf - return IndexOf(ref searchSpace, value, searchSpaceLength); + return IndexOfChar(ref searchSpace, value, searchSpaceLength); } nint offset = 0; @@ -41,7 +40,7 @@ public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char while (remainingSearchSpaceLength > 0) { // Do a quick search for the first element of "value". - int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); + int relativeIndex = IndexOfChar(ref Unsafe.Add(ref searchSpace, offset), valueHead, remainingSearchSpaceLength); if (relativeIndex < 0) break; @@ -68,6 +67,7 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, offset + 1)), // Based on http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd "Algorithm 1: Generic SIMD" by Wojciech Muła // Some details about the implementation can also be found in https://github.com/dotnet/runtime/pull/63285 SEARCH_TWO_CHARS: + ref ushort ushortSearchSpace = ref Unsafe.As(ref searchSpace); if (Vector256.IsHardwareAccelerated && searchSpaceMinusValueTailLength - Vector256.Count >= 0) { // Find the last unique (which is not equal to ch1) character @@ -88,8 +88,8 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, offset + 1)), // Make sure we don't go out of bounds Debug.Assert(offset + ch1ch2Distance + Vector256.Count <= searchSpaceLength); - Vector256 cmpCh2 = Vector256.Equals(ch2, LoadVector256(ref searchSpace, offset + ch1ch2Distance)); - Vector256 cmpCh1 = Vector256.Equals(ch1, LoadVector256(ref searchSpace, offset)); + Vector256 cmpCh2 = Vector256.Equals(ch2, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); + Vector256 cmpCh1 = Vector256.Equals(ch1, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); Vector256 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -155,8 +155,8 @@ ref Unsafe.As(ref value), (nuint)(uint)valueLength * 2)) // Make sure we don't go out of bounds Debug.Assert(offset + ch1ch2Distance + Vector128.Count <= searchSpaceLength); - Vector128 cmpCh2 = Vector128.Equals(ch2, LoadVector128(ref searchSpace, offset + ch1ch2Distance)); - Vector128 cmpCh1 = Vector128.Equals(ch1, LoadVector128(ref searchSpace, offset)); + Vector128 cmpCh2 = Vector128.Equals(ch2, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); + Vector128 cmpCh1 = Vector128.Equals(ch1, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); Vector128 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -214,7 +214,7 @@ public static int LastIndexOf(ref char searchSpace, int searchSpaceLength, ref c int valueTailLength = valueLength - 1; if (valueTailLength == 0) - return LastIndexOf(ref searchSpace, value, searchSpaceLength); // for single-char values use plain LastIndexOf + return LastIndexOfValueType(ref Unsafe.As(ref searchSpace), (short)value, searchSpaceLength); // for single-char values use plain LastIndexOf int offset = 0; char valueHead = value; @@ -234,7 +234,7 @@ public static int LastIndexOf(ref char searchSpace, int searchSpaceLength, ref c break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there. // Do a quick search for the first element of "value". - int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength); + int relativeIndex = LastIndexOfValueType(ref Unsafe.As(ref searchSpace), (short)valueHead, remainingSearchSpaceLength); if (relativeIndex == -1) break; @@ -253,6 +253,7 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)), // Based on http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd "Algorithm 1: Generic SIMD" by Wojciech Muła // Some details about the implementation can also be found in https://github.com/dotnet/runtime/pull/63285 SEARCH_TWO_CHARS: + ref ushort ushortSearchSpace = ref Unsafe.As(ref searchSpace); if (Vector256.IsHardwareAccelerated && searchSpaceMinusValueTailLength >= Vector256.Count) { offset = searchSpaceMinusValueTailLength - Vector256.Count; @@ -270,8 +271,8 @@ ref Unsafe.As(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)), do { - Vector256 cmpCh1 = Vector256.Equals(ch1, LoadVector256(ref searchSpace, (nuint)offset)); - Vector256 cmpCh2 = Vector256.Equals(ch2, LoadVector256(ref searchSpace, (nuint)(offset + ch1ch2Distance))); + Vector256 cmpCh1 = Vector256.Equals(ch1, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); + Vector256 cmpCh2 = Vector256.Equals(ch2, Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); Vector256 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -319,8 +320,8 @@ ref Unsafe.As(ref value), (nuint)(uint)valueLength * 2)) do { - Vector128 cmpCh1 = Vector128.Equals(ch1, LoadVector128(ref searchSpace, (nuint)offset)); - Vector128 cmpCh2 = Vector128.Equals(ch2, LoadVector128(ref searchSpace, (nuint)(offset + ch1ch2Distance))); + Vector128 cmpCh1 = Vector128.Equals(ch1, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset)); + Vector128 cmpCh2 = Vector128.Equals(ch2, Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)(offset + ch1ch2Distance))); Vector128 cmpAnd = (cmpCh1 & cmpCh2).AsByte(); // Early out: cmpAnd is all zeros @@ -420,104 +421,13 @@ public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref return lengthDelta; } - // Adapted from IndexOf(...) + // IndexOfNullCharacter processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. + // This behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe bool Contains(ref char searchSpace, char value, int length) + public static unsafe int IndexOfNullCharacter(ref char searchSpace) { - Debug.Assert(length >= 0); - - fixed (char* pChars = &searchSpace) - { - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (uint)length; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - // Figure out how many characters to read sequentially until we are vector aligned - // This is equivalent to: - // unaligned = ((int)pCh % Unsafe.SizeOf>()) / ElementsPerByte - // length = (Vector.Count - unaligned) % Vector.Count - const int ElementsPerByte = sizeof(ushort) / sizeof(byte); - int unaligned = (int)((uint)((int)pChars & (Unsafe.SizeOf>() - 1)) / ElementsPerByte); - lengthToExamine = (uint)((Vector.Count - unaligned) & (Vector.Count - 1)); - } - - while (lengthToExamine >= 4) - { - lengthToExamine -= 4; - char* pStart = pChars + offset; - - if (value == pStart[0] || - value == pStart[1] || - value == pStart[2] || - value == pStart[3]) - { - goto Found; - } - - offset += 4; - } - - while (lengthToExamine > 0) - { - lengthToExamine--; - - if (value == pChars[offset]) - goto Found; - - offset++; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow - // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware acceleration. - if (Vector.IsHardwareAccelerated && (offset < (uint)length)) - { - // Get the highest multiple of Vector.Count that is within the search space. - // That will be how many times we iterate in the loop below. - // This is equivalent to: lengthToExamine = Vector.Count + ((uint)length - offset) / Vector.Count) - lengthToExamine = ((uint)length - offset) & (nuint)~(Vector.Count - 1); - - Vector values = new(value); - Vector matches; - - while (offset < lengthToExamine) - { - // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned - Debug.Assert(((int)(pChars + offset) % Unsafe.SizeOf>()) == 0); - matches = Vector.Equals(values, Unsafe.Read>(pChars + offset)); - if (matches == Vector.Zero) - { - offset += (nuint)Vector.Count; - continue; - } - - goto Found; - } - - // The total length is at least Vector.Count, so instead of falling back to a - // sequential scan for the remainder, we check the vector read from the end -- note: unaligned read necessary. - // We do this only if at least one element is left. - if (offset < (uint)length) - { - matches = Vector.Equals(values, Unsafe.ReadUnaligned>(pChars + (uint)length - (uint)Vector.Count)); - if (matches != Vector.Zero) - { - goto Found; - } - } - } - - return false; - - Found: - return true; - } - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOf(ref char searchSpace, char value, int length) - { - Debug.Assert(length >= 0); + const char value = '\0'; + const int length = int.MaxValue; nint offset = 0; nint lengthToExamine = length; @@ -530,18 +440,12 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) { // Avx2 branch also operates on Sse2 sizes, so check is combined. // Needs to be double length to allow us to align the data first. - if (length >= Vector128.Count * 2) - { - lengthToExamine = UnalignedCountVector128(ref searchSpace); - } + lengthToExamine = UnalignedCountVector128(ref searchSpace); } else if (Vector.IsHardwareAccelerated) { // Needs to be double length to allow us to align the data first. - if (length >= Vector.Count * 2) - { - lengthToExamine = UnalignedCountVector(ref searchSpace); - } + lengthToExamine = UnalignedCountVector(ref searchSpace); } SequentialScan: @@ -600,11 +504,10 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) // method, so the alignment only acts as best endeavour. The GC cost is likely to dominate over // the misalignment that may occur after; to we default to giving the GC a free hand to relocate and // its up to the caller whether they are operating over fixed data. - Vector128 values = Vector128.Create((ushort)value); Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); // Same method as below - uint matches = Vector128.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector128.Equals(Vector128.Zero, search).AsByte().ExtractMostSignificantBits(); if (matches == 0) { // Zero flags set so no matches @@ -620,13 +523,12 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) lengthToExamine = GetCharVector256SpanLength(offset, length); if (lengthToExamine > 0) { - Vector256 values = Vector256.Create((ushort)value); do { Debug.Assert(lengthToExamine >= Vector256.Count); Vector256 search = Vector256.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); - uint matches = Vector256.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector256.Equals(Vector256.Zero, search).AsByte().ExtractMostSignificantBits(); // Note that MoveMask has converted the equal vector elements into a set of bit flags, // So the bit position in 'matches' corresponds to the element offset. if (matches == 0) @@ -648,11 +550,10 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) { Debug.Assert(lengthToExamine >= Vector128.Count); - Vector128 values = Vector128.Create((ushort)value); Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (nuint)offset); // Same method as above - uint matches = Vector128.Equals(values, search).AsByte().ExtractMostSignificantBits(); + uint matches = Vector128.Equals(Vector128.Zero, search).AsByte().ExtractMostSignificantBits(); if (matches == 0) { // Zero flags set so no matches @@ -684,7 +585,6 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) lengthToExamine = GetCharVector128SpanLength(offset, length); if (lengthToExamine > 0) { - Vector128 values = Vector128.Create((ushort)value); do { Debug.Assert(lengthToExamine >= Vector128.Count); @@ -692,7 +592,7 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) Vector128 search = Vector128.LoadUnsafe(ref ushortSearchSpace, (uint)offset); // Same method as above - Vector128 compareResult = Vector128.Equals(values, search); + Vector128 compareResult = Vector128.Equals(Vector128.Zero, search); if (compareResult == Vector128.Zero) { // Zero flags set so no matches @@ -725,12 +625,11 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) if (lengthToExamine > 0) { - Vector values = new Vector((ushort)value); do { Debug.Assert(lengthToExamine >= Vector.Count); - var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset)); + var matches = Vector.Equals(Vector.Zero, LoadVector(ref searchSpace, offset)); if (Vector.Zero.Equals(matches)) { offset += Vector.Count; @@ -750,789 +649,8 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length) } } } - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)(offset); - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search)) - .AsByte().ExtractMostSignificantBits(); - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - // Bitwise Or to combine the flagged matches for the second value to our match flags - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, char value3, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - - NotFound: - return -1; - Found3: - return (int)(offset + 3); - Found2: - return (int)(offset + 2); - Found1: - return (int)(offset + 1); - Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - Vector256 values3 = Vector256.Create((ushort)value3); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) - | Vector256.Equals(values2, search) | Vector256.Equals(values3, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) - | Vector256.Equals(values2, search) | Vector256.Equals(values3, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - Vector128 values3 = Vector128.Create((ushort)value3); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) - | Vector128.Equals(values2, search) | Vector128.Equals(values3, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) - | Vector128.Equals(values2, search) | Vector128.Equals(values3, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - Vector values3 = new Vector(value3); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int IndexOfAny(ref char searchStart, char value0, char value1, char value2, char value3, char value4, int length) - { - Debug.Assert(length >= 0); - - nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations - nuint lengthToExamine = (nuint)(uint)length; - - if (Vector128.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector128.Count; - if (vectorDiff >= 0) - { - // >= Vector128 is accelerated and length is enough to use them, so use that path. - // We jump forward to the intrinsics at the end of them method so a naive branch predict - // will choose the non-intrinsic path so short lengths which don't gain anything aren't - // overly disadvantaged by having to jump over a lot of code. Whereas the longer lengths - // more than make this back from the intrinsics. - lengthToExamine = (nuint)vectorDiff; - goto IntrinsicsCompare; - } - } - else if (Vector.IsHardwareAccelerated) - { - // Calculate lengthToExamine here for test, rather than just testing as it used later, rather than doing it twice. - nint vectorDiff = (nint)length - Vector.Count; - if (vectorDiff >= 0) - { - // Similar as above for Vector version - lengthToExamine = (nuint)vectorDiff; - goto VectorCompare; - } - } - - int lookUp; - while (lengthToExamine >= 4) - { - ref char current = ref Add(ref searchStart, offset); - - lookUp = current; - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found; - lookUp = Unsafe.Add(ref current, 1); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found1; - lookUp = Unsafe.Add(ref current, 2); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found2; - lookUp = Unsafe.Add(ref current, 3); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found3; - - offset += 4; - lengthToExamine -= 4; - } - - while (lengthToExamine > 0) - { - lookUp = Add(ref searchStart, offset); - if (value0 == lookUp || value1 == lookUp || value2 == lookUp || value3 == lookUp || value4 == lookUp) - goto Found; - - offset += 1; - lengthToExamine -= 1; - } - NotFound: - return -1; + ThrowMustBeNullTerminatedString(); Found3: return (int)(offset + 3); Found2: @@ -1540,268 +658,7 @@ public static unsafe int IndexOfAny(ref char searchStart, char value0, char valu Found1: return (int)(offset + 1); Found: - return (int)offset; - - IntrinsicsCompare: - // When we move into a Vectorized block, we process everything of Vector size; - // and then for any remainder we do a final compare of Vector size but starting at - // the end and forwards, which may overlap on an earlier compare. - - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (Vector128.IsHardwareAccelerated) - { - uint matches; - ref ushort ushortSearchStart = ref Unsafe.As(ref searchStart); - if (Vector256.IsHardwareAccelerated) - { - Vector256 search; - // Guard as we may only have a valid size for Vector128; when we will move to the Sse2 - // We have already subtracted Vector128.Count from lengthToExamine so compare against that - // to see if we have double the size for Vector256.Count - if (lengthToExamine >= (nuint)Vector128.Count) - { - Vector256 values0 = Vector256.Create((ushort)value0); - Vector256 values1 = Vector256.Create((ushort)value1); - Vector256 values2 = Vector256.Create((ushort)value2); - Vector256 values3 = Vector256.Create((ushort)value3); - Vector256 values4 = Vector256.Create((ushort)value4); - - // Subtract Vector128.Count so we have now subtracted Vector256.Count - lengthToExamine -= (nuint)Vector128.Count; - // First time this checks again against 0, however we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector256.LoadUnsafe(ref ushortSearchStart, offset); - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search) - | Vector256.Equals(values3, search) | Vector256.Equals(values4, search)) - .AsByte().ExtractMostSignificantBits(); - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - if (matches == 0) - { - // None matched - offset += (nuint)Vector256.Count; - continue; - } - - goto IntrinsicsMatch; - } - - // Move to Vector length from end for final compare - search = Vector256.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - matches = (Vector256.Equals(values0, search) | Vector256.Equals(values1, search) | Vector256.Equals(values2, search) - | Vector256.Equals(values3, search) | Vector256.Equals(values4, search)) - .AsByte().ExtractMostSignificantBits(); - if (matches == 0) - { - // None matched - goto NotFound; - } - - goto IntrinsicsMatch; - } - } - - // Initial size check was done on method entry. - Debug.Assert(length >= Vector128.Count); - { - Vector128 search, compareResult; - Vector128 values0 = Vector128.Create((ushort)value0); - Vector128 values1 = Vector128.Create((ushort)value1); - Vector128 values2 = Vector128.Create((ushort)value2); - Vector128 values3 = Vector128.Create((ushort)value3); - Vector128 values4 = Vector128.Create((ushort)value4); - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = Vector128.LoadUnsafe(ref ushortSearchStart, offset); - - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search) - | Vector128.Equals(values3, search) | Vector128.Equals(values4, search); - if (compareResult == Vector128.Zero) - { - // None matched - offset += (nuint)Vector128.Count; - continue; - } - - // Note that ExtractMostSignificantBits has converted the equal vector elements into a set of bit flags, - // So the bit position in 'matches' corresponds to the element offset. - matches = compareResult.AsByte().ExtractMostSignificantBits(); - goto IntrinsicsMatch; - } - // Move to Vector length from end for final compare - search = Vector128.LoadUnsafe(ref ushortSearchStart, lengthToExamine); - offset = lengthToExamine; - // Same as method as above - compareResult = Vector128.Equals(values0, search) | Vector128.Equals(values1, search) | Vector128.Equals(values2, search) - | Vector128.Equals(values3, search) | Vector128.Equals(values4, search); - if (compareResult == Vector128.Zero) - { - // None matched - goto NotFound; - } - matches = compareResult.AsByte().ExtractMostSignificantBits(); - } - - IntrinsicsMatch: - // Find bitflag offset of first difference and add to current offset, - // flags are in bytes so divide by 2 for chars (shift right by 1) - offset += (nuint)(uint)BitOperations.TrailingZeroCount(matches) >> 1; - goto Found; - } - - VectorCompare: - // We include the Supported check again here even though path will not be taken, so the asm isn't generated if not supported. - if (!Vector128.IsHardwareAccelerated && Vector.IsHardwareAccelerated) - { - Vector values0 = new Vector(value0); - Vector values1 = new Vector(value1); - Vector values2 = new Vector(value2); - Vector values3 = new Vector(value3); - Vector values4 = new Vector(value4); - - Vector search; - // First time this checks against 0 and we will move into final compare if it fails. - while (lengthToExamine > offset) - { - search = LoadVector(ref searchStart, offset); - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)), - Vector.Equals(search, values4)); - if (Vector.Zero.Equals(search)) - { - // None matched - offset += (nuint)Vector.Count; - continue; - } - - goto VectorMatch; - } - - // Move to Vector length from end for final compare - search = LoadVector(ref searchStart, lengthToExamine); - offset = lengthToExamine; - search = Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.BitwiseOr( - Vector.Equals(search, values0), - Vector.Equals(search, values1)), - Vector.Equals(search, values2)), - Vector.Equals(search, values3)), - Vector.Equals(search, values4)); - if (Vector.Zero.Equals(search)) - { - // None matched - goto NotFound; - } - - VectorMatch: - offset += (nuint)(uint)LocateFirstFoundChar(search); - goto Found; - } - - Debug.Fail("Unreachable"); - goto NotFound; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static unsafe int LastIndexOf(ref char searchSpace, char value, int length) - { - Debug.Assert(length >= 0); - - fixed (char* pChars = &searchSpace) - { - char* pCh = pChars + length; - char* pEndCh = pChars; - - if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) - { - // Figure out how many characters to read sequentially from the end until we are vector aligned - // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf>()) / elementsPerByte - const int elementsPerByte = sizeof(ushort) / sizeof(byte); - length = ((int)pCh & (Unsafe.SizeOf>() - 1)) / elementsPerByte; - } - - SequentialScan: - while (length >= 4) - { - length -= 4; - pCh -= 4; - - if (*(pCh + 3) == value) - goto Found3; - if (*(pCh + 2) == value) - goto Found2; - if (*(pCh + 1) == value) - goto Found1; - if (*pCh == value) - goto Found; - } - - while (length > 0) - { - length--; - pCh--; - - if (*pCh == value) - goto Found; - } - - // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow - // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated. - if (Vector.IsHardwareAccelerated && pCh > pEndCh) - { - // Get the highest multiple of Vector.Count that is within the search space. - // That will be how many times we iterate in the loop below. - // This is equivalent to: length = Vector.Count * ((int)(pCh - pEndCh) / Vector.Count) - length = (int)((pCh - pEndCh) & ~(Vector.Count - 1)); - - // Get comparison Vector - Vector vComparison = new Vector(value); - - while (length > 0) - { - char* pStart = pCh - Vector.Count; - // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned - Debug.Assert(((int)pStart & (Unsafe.SizeOf>() - 1)) == 0); - Vector vMatches = Vector.Equals(vComparison, Unsafe.Read>(pStart)); - if (Vector.Zero.Equals(vMatches)) - { - pCh -= Vector.Count; - length -= Vector.Count; - continue; - } - // Find offset of last match - return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches); - } - - if (pCh > pEndCh) - { - length = (int)(pCh - pEndCh); - goto SequentialScan; - } - } - - return -1; - Found: - return (int)(pCh - pEndCh); - Found1: - return (int)(pCh - pEndCh) + 1; - Found2: - return (int)(pCh - pEndCh) + 2; - Found3: - return (int)(pCh - pEndCh) + 3; - } + return (int)(offset); } // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 @@ -1829,35 +686,6 @@ private static int LocateFirstFoundChar(Vector match) private static int LocateFirstFoundChar(ulong match) => BitOperations.TrailingZeroCount(match) >> 4; - // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateLastFoundChar(Vector match) - { - var vector64 = Vector.AsVectorUInt64(match); - ulong candidate = 0; - int i = Vector.Count - 1; - - // This pattern is only unrolled by the Jit if the limit is Vector.Count - // As such, we need a dummy iteration variable for that condition to be satisfied - for (int j = 0; j < Vector.Count; j++) - { - candidate = vector64[i]; - if (candidate != 0) - { - break; - } - - i--; - } - - // Single LEA instruction with jitted const (using function result) - return i * 4 + LocateLastFoundChar(candidate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LocateLastFoundChar(ulong match) - => BitOperations.Log2(match) >> 4; - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector LoadVector(ref char start, nint offset) => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); @@ -1866,25 +694,6 @@ private static Vector LoadVector(ref char start, nint offset) private static Vector LoadVector(ref char start, nuint offset) => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 LoadVector128(ref char start, nint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector128 LoadVector128(ref char start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 LoadVector256(ref char start, nint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 LoadVector256(ref char start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref start, (nint)offset))); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ref char Add(ref char start, nuint offset) => ref Unsafe.Add(ref start, (nint)offset); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nint GetCharVectorSpanLength(nint offset, nint length) => (length - offset) & ~(Vector.Count - 1); @@ -1922,21 +731,6 @@ private static unsafe nint UnalignedCountVector128(ref char searchSpace) return (nint)(uint)(-(int)Unsafe.AsPointer(ref searchSpace) / ElementsPerByte) & (Vector128.Count - 1); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FindFirstMatchedLane(Vector128 compareResult) - { - Debug.Assert(AdvSimd.Arm64.IsSupported); - - Vector128 pairwiseSelectedLane = AdvSimd.Arm64.AddPairwise(compareResult.AsByte(), compareResult.AsByte()); - ulong selectedLanes = pairwiseSelectedLane.AsUInt64().ToScalar(); - - // It should be handled by compareResult != Vector.Zero - Debug.Assert(selectedLanes != 0); - - return BitOperations.TrailingZeroCount(selectedLanes) >> 3; - } - public static void Reverse(ref char buf, nuint length) { if (Avx2.IsSupported && (nuint)Vector256.Count * 2 <= length) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index 709a423680af0f..877c21311876d5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -293,115 +293,6 @@ public static unsafe bool Contains(ref T searchSpace, T value, int length) wh return true; } - internal static unsafe int IndexOfValueType(ref T searchSpace, T value, int length) where T : struct, IEquatable - { - Debug.Assert(length >= 0); - - nint index = 0; // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations - if (Vector.IsHardwareAccelerated && Vector.IsSupported && (Vector.Count * 2) <= length) - { - Vector valueVector = new Vector(value); - Vector compareVector; - Vector matchVector; - if ((uint)length % (uint)Vector.Count != 0) - { - // Number of elements is not a multiple of Vector.Count, so do one - // check and shift only enough for the remaining set to be a multiple - // of Vector.Count. - compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); - matchVector = Vector.Equals(valueVector, compareVector); - if (matchVector != Vector.Zero) - { - goto VectorMatch; - } - index += length % Vector.Count; - length -= length % Vector.Count; - } - while (length > 0) - { - compareVector = Unsafe.As>(ref Unsafe.Add(ref searchSpace, index)); - matchVector = Vector.Equals(valueVector, compareVector); - if (matchVector != Vector.Zero) - { - goto VectorMatch; - } - index += Vector.Count; - length -= Vector.Count; - } - goto NotFound; - VectorMatch: - for (int i = 0; i < Vector.Count; i++) - if (compareVector[i].Equals(value)) - return (int)(index + i); - } - - while (length >= 8) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) - goto Found1; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) - goto Found2; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) - goto Found3; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 4))) - goto Found4; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 5))) - goto Found5; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 6))) - goto Found6; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 7))) - goto Found7; - - length -= 8; - index += 8; - } - - while (length >= 4) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) - goto Found1; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) - goto Found2; - if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) - goto Found3; - - length -= 4; - index += 4; - } - - while (length > 0) - { - if (value.Equals(Unsafe.Add(ref searchSpace, index))) - goto Found; - - index += 1; - length--; - } - NotFound: - return -1; - - Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 - return (int)index; - Found1: - return (int)(index + 1); - Found2: - return (int)(index + 2); - Found3: - return (int)(index + 3); - Found4: - return (int)(index + 4); - Found5: - return (int)(index + 5); - Found6: - return (int)(index + 6); - Found7: - return (int)(index + 7); - } - public static unsafe int IndexOf(ref T searchSpace, T value, int length) where T : IEquatable? { Debug.Assert(length >= 0); @@ -1162,7 +1053,7 @@ public static int LastIndexOfAny(ref T searchSpace, int searchSpaceLength, re return -1; // not found } - public static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); @@ -1177,113 +1068,125 @@ public static int IndexOfAnyExcept(ref T searchSpace, T value0, int length) return -1; } - public static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, int length) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); - Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + for (int i = length -1; i >= 0; i--) { - for (int i = 0; i < length; i++) + if (!EqualityComparer.Default.Equals(Unsafe.Add(ref searchSpace, i), value0)) { - if (!Unsafe.Add(ref searchSpace, i).Equals(value0)) - { - return i; - } + return i; } } - else - { - Vector128 notEquals, value0Vector = Vector128.Create(value0); - ref T current = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do - { - notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref current, notEquals); - } + return -1; + } - current = ref Unsafe.Add(ref current, Vector128.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref oneVectorAwayFromEnd)); + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); - // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector128.Count != 0) + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) && !EqualityComparer.Default.Equals(current, value1)) { - notEquals = ~Vector128.Equals(value0Vector, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); - } + return i; } } return -1; } - internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, int length) { Debug.Assert(length >= 0, "Expected non-negative length"); - Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + for (int i = length - 1; i >= 0; i--) { - for (int i = 0; i < length; i++) + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) && !EqualityComparer.Default.Equals(current, value1)) { - T current = Unsafe.Add(ref searchSpace, i); - if (!current.Equals(value0) && !current.Equals(value1) && !current.Equals(value2) && !current.Equals(value3)) - { - return i; - } + return i; } } - else - { - Vector128 notEquals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); - ref T currentSearchSpace = ref searchSpace; - ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - // Loop until either we've finished all elements or there's less than a vector's-worth remaining. - do + return -1; + } + + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2)) { - current = Vector128.LoadUnsafe(ref currentSearchSpace); - notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) - | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref currentSearchSpace, notEquals); - } + return i; + } + } + + return -1; + } + + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); - currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2)) + { + return i; } - while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + } - // If any elements remain, process the last vector in the search space. - if ((uint)length % Vector128.Count != 0) + return -1; + } + + internal static int IndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, T value3, int length) + { + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = 0; i < length; i++) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2) + && !EqualityComparer.Default.Equals(current, value3)) { - current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); - notEquals = ~(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) - | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); - if (notEquals != Vector128.Zero) - { - return ComputeIndex(ref searchSpace, ref oneVectorAwayFromEnd, notEquals); - } + return i; } } return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComputeIndex(ref T searchSpace, ref T current, Vector128 notEquals) where T : struct, IEquatable + internal static int LastIndexOfAnyExcept(ref T searchSpace, T value0, T value1, T value2, T value3, int length) { - uint notEqualsElements = notEquals.ExtractMostSignificantBits(); - int index = BitOperations.TrailingZeroCount(notEqualsElements); - return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + Debug.Assert(length >= 0, "Expected non-negative length"); + + for (int i = length - 1; i >= 0; i--) + { + ref T current = ref Unsafe.Add(ref searchSpace, i); + if (!EqualityComparer.Default.Equals(current, value0) + && !EqualityComparer.Default.Equals(current, value1) + && !EqualityComparer.Default.Equals(current, value2) + && !EqualityComparer.Default.Equals(current, value3)) + { + return i; + } + } + + return -1; } public static bool SequenceEqual(ref T first, ref T second, int length) where T : IEquatable? @@ -1395,5 +1298,1373 @@ public static int SequenceCompareTo(ref T first, int firstLength, ref T secon } return firstLength.CompareTo(secondLength); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool CanVectorizeAndBenefit(int length) where T : IEquatable? + { + if (Vector128.IsHardwareAccelerated && RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return length >= Vector128.Count; + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return length >= Vector128.Count; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static bool ContainsValueType(ref T searchSpace, T value, int length) where T : struct, INumber + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + + while (length >= 8) + { + length -= 8; + + if (Unsafe.Add(ref searchSpace, offset) == value + || Unsafe.Add(ref searchSpace, offset + 1) == value + || Unsafe.Add(ref searchSpace, offset + 2) == value + || Unsafe.Add(ref searchSpace, offset + 3) == value + || Unsafe.Add(ref searchSpace, offset + 4) == value + || Unsafe.Add(ref searchSpace, offset + 5) == value + || Unsafe.Add(ref searchSpace, offset + 6) == value + || Unsafe.Add(ref searchSpace, offset + 7) == value) + { + return true; + } + + offset += 8; + } + + if (length >= 4) + { + length -= 4; + + if (Unsafe.Add(ref searchSpace, offset) == value + || Unsafe.Add(ref searchSpace, offset + 1) == value + || Unsafe.Add(ref searchSpace, offset + 2) == value + || Unsafe.Add(ref searchSpace, offset + 3) == value) + { + return true; + } + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + if (Unsafe.Add(ref searchSpace, offset) == value) return true; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return true; + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + equals = Vector256.Equals(values, Vector256.LoadUnsafe(ref oneVectorAwayFromEnd)); + if (equals != Vector256.Zero) + { + return true; + } + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = Vector128.Equals(values, Vector128.LoadUnsafe(ref currentSearchSpace)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return true; + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + equals = Vector128.Equals(values, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd)); + if (equals != Vector128.Zero) + { + return true; + } + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfChar(ref char searchSpace, char value, int length) + => IndexOfValueType(ref Unsafe.As(ref searchSpace), (short)value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => IndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => IndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfValueType(ref TValue searchSpace, TValue value, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + + while (length >= 8) + { + length -= 8; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 1) == value)) return (int)offset + 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 2) == value)) return (int)offset + 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 3) == value)) return (int)offset + 3; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 4) == value)) return (int)offset + 4; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 5) == value)) return (int)offset + 5; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 6) == value)) return (int)offset + 6; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 7) == value)) return (int)offset + 7; + + offset += 8; + } + + if (length >= 4) + { + length -= 4; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 1) == value)) return (int)offset + 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 2) == value)) return (int)offset + 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset + 3) == value)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref oneVectorAwayFromEnd))); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref oneVectorAwayFromEnd))); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyChar(ref char searchSpace, char value0, char value1, int length) + => IndexOfAnyValueType(ref Unsafe.As(ref searchSpace), (short)value0, (short)value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + // having INumber constraint here allows to use == operator and get better perf compared to .Equals + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 3; + lookUp = Unsafe.Add(ref current, 4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 4; + lookUp = Unsafe.Add(ref current, 5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 5; + lookUp = Unsafe.Add(ref current, 6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 6; + lookUp = Unsafe.Add(ref current, 7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 7; + + offset += 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 3; + lookUp = Unsafe.Add(ref current, 4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 4; + lookUp = Unsafe.Add(ref current, 5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 5; + lookUp = Unsafe.Add(ref current, 6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 6; + lookUp = Unsafe.Add(ref current, 7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 7; + + offset += 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int IndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => IndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int IndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + TValue lookUp; + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2), values3 = Vector256.Create(value3); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) + | Vector256.Equals(values2, current) | Vector256.Equals(values3, current)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector256.Equals(values0, current) | Vector256.Equals(values1, current) + | Vector256.Equals(values2, current) | Vector256.Equals(values3, current)); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); + ref TValue currentSearchSpace = ref searchSpace; + ref TValue oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = TNegator.NegateIfNeeded(Vector128.Equals(values0, current) | Vector128.Equals(values1, current) + | Vector128.Equals(values2, current) | Vector128.Equals(values3, current)); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + internal static int IndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, T value4, int length) + where T : struct, INumber + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = 0; + T lookUp; + + while (length >= 4) + { + length -= 4; + + ref T current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + lookUp = Unsafe.Add(ref current, 1); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 1; + lookUp = Unsafe.Add(ref current, 2); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 2; + lookUp = Unsafe.Add(ref current, 3); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset + 3; + + offset += 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3 || lookUp == value4) return (int)offset; + + offset += 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), + values2 = Vector256.Create(value2), values3 = Vector256.Create(value3), values4 = Vector256.Create(value4); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the last vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = Vector256.Equals(values0, current) | Vector256.Equals(values1, current) | Vector256.Equals(values2, current) + | Vector256.Equals(values3, current) | Vector256.Equals(values4, current); + if (equals != Vector256.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), + values2 = Vector128.Create(value2), values3 = Vector128.Create(value3), values4 = Vector128.Create(value4); + ref T currentSearchSpace = ref searchSpace; + ref T oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeFirstIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd); + equals = Vector128.Equals(values0, current) | Vector128.Equals(values1, current) | Vector128.Equals(values2, current) + | Vector128.Equals(values3, current) | Vector128.Equals(values4, current); + if (equals != Vector128.Zero) + { + return ComputeFirstIndex(ref searchSpace, ref oneVectorAwayFromEnd, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => LastIndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value, int length) where T : struct, INumber + => LastIndexOfValueType>(ref searchSpace, value, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfValueType(ref TValue searchSpace, TValue value, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + + while (length >= 8) + { + length -= 8; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 1) == value)) return (int)offset - 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 2) == value)) return (int)offset - 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 3) == value)) return (int)offset - 3; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 4) == value)) return (int)offset - 4; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 5) == value)) return (int)offset - 5; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 6) == value)) return (int)offset - 6; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 7) == value)) return (int)offset - 7; + + offset -= 8; + } + + if (length >= 4) + { + length -= 4; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 1) == value)) return (int)offset - 1; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 2) == value)) return (int)offset - 2; + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset - 3) == value)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + if (TNegator.NegateIfNeeded(Unsafe.Add(ref searchSpace, offset) == value)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, values = Vector256.Create(value); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref searchSpace))); + if (equals != Vector256.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + else + { + Vector128 equals, values = Vector128.Create(value); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref currentSearchSpace))); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + equals = TNegator.NegateIfNeeded(Vector128.Equals(values, Vector128.LoadUnsafe(ref searchSpace))); + if (equals != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 3; + lookUp = Unsafe.Add(ref current, -4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 4; + lookUp = Unsafe.Add(ref current, -5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 5; + lookUp = Unsafe.Add(ref current, -6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 6; + lookUp = Unsafe.Add(ref current, -7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 7; + + offset -= 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1)); + if (equals != Vector256.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1)); + if (equals != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + if (typeof(TValue) == typeof(byte)) // this optimization is beneficial only to byte + { + while (length >= 8) + { + length -= 8; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 3; + lookUp = Unsafe.Add(ref current, -4); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 4; + lookUp = Unsafe.Add(ref current, -5); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 5; + lookUp = Unsafe.Add(ref current, -6); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 6; + lookUp = Unsafe.Add(ref current, -7); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 7; + + offset -= 8; + } + } + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) | Vector256.Equals(current, values2)); + if (equals != Vector256.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) | Vector128.Equals(current, values2)); + if (equals != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int LastIndexOfAnyExceptValueType(ref T searchSpace, T value0, T value1, T value2, T value3, int length) where T : struct, INumber + => LastIndexOfAnyValueType>(ref searchSpace, value0, value1, value2, value3, length); + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static int LastIndexOfAnyValueType(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, TValue value3, int length) + where TValue : struct, INumber + where TNegator : struct, INegator + { + Debug.Assert(length >= 0, "Expected non-negative length"); + Debug.Assert(value0 is byte or short or int or long, "Expected caller to normalize to one of these types"); + + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + nuint offset = (nuint)length - 1; + TValue lookUp; + + while (length >= 4) + { + length -= 4; + + ref TValue current = ref Unsafe.Add(ref searchSpace, offset); + lookUp = current; + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + lookUp = Unsafe.Add(ref current, -1); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 1; + lookUp = Unsafe.Add(ref current, -2); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 2; + lookUp = Unsafe.Add(ref current, -3); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset - 3; + + offset -= 4; + } + + while (length > 0) + { + length -= 1; + + lookUp = Unsafe.Add(ref searchSpace, offset); + if (TNegator.NegateIfNeeded(lookUp == value0 || lookUp == value1 || lookUp == value2 || lookUp == value3)) return (int)offset; + + offset -= 1; + } + } + else if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 equals, current, values0 = Vector256.Create(value0), values1 = Vector256.Create(value1), values2 = Vector256.Create(value2), values3 = Vector256.Create(value3); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector256.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) + | Vector256.Equals(current, values2) | Vector256.Equals(current, values3)); + if (equals == Vector256.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector256.Count != 0) + { + current = Vector256.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector256.Equals(current, values0) | Vector256.Equals(current, values1) + | Vector256.Equals(current, values2) | Vector256.Equals(current, values3)); + if (equals != Vector256.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + else + { + Vector128 equals, current, values0 = Vector128.Create(value0), values1 = Vector128.Create(value1), values2 = Vector128.Create(value2), values3 = Vector128.Create(value3); + ref TValue currentSearchSpace = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); + + // Loop until either we've finished all elements or there's less than a vector's-worth remaining. + do + { + current = Vector128.LoadUnsafe(ref currentSearchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) + | Vector128.Equals(current, values2) | Vector128.Equals(current, values3)); + if (equals == Vector128.Zero) + { + currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128.Count); + continue; + } + + return ComputeLastIndex(ref searchSpace, ref currentSearchSpace, equals); + } + while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace)); + + // If any elements remain, process the first vector in the search space. + if ((uint)length % Vector128.Count != 0) + { + current = Vector128.LoadUnsafe(ref searchSpace); + equals = TNegator.NegateIfNeeded(Vector128.Equals(current, values0) | Vector128.Equals(current, values1) + | Vector128.Equals(current, values2) | Vector128.Equals(current, values3)); + if (equals != Vector128.Zero) + { + return ComputeLastIndex(ref searchSpace, ref searchSpace, equals); + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector128 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeFirstIndex(ref T searchSpace, ref T current, Vector256 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = BitOperations.TrailingZeroCount(notEqualsElements); + return index + (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndex(ref T searchSpace, ref T current, Vector128 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = 31 - BitOperations.LeadingZeroCount(notEqualsElements); // 31 = 32 (bits in Int32) - 1 (indexing from zero) + return (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()) + index; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ComputeLastIndex(ref T searchSpace, ref T current, Vector256 equals) where T : struct + { + uint notEqualsElements = equals.ExtractMostSignificantBits(); + int index = 31 - BitOperations.LeadingZeroCount(notEqualsElements); // 31 = 32 (bits in Int32) - 1 (indexing from zero) + return (int)(Unsafe.ByteOffset(ref searchSpace, ref current) / Unsafe.SizeOf()) + index; + } + + private interface INegator where T : struct + { + static abstract bool NegateIfNeeded(bool equals); + static abstract Vector128 NegateIfNeeded(Vector128 equals); + static abstract Vector256 NegateIfNeeded(Vector256 equals); + } + + private readonly struct DontNegate : INegator where T : struct + { + public static bool NegateIfNeeded(bool equals) => equals; + public static Vector128 NegateIfNeeded(Vector128 equals) => equals; + public static Vector256 NegateIfNeeded(Vector256 equals) => equals; + } + + private readonly struct Negate : INegator where T : struct + { + public static bool NegateIfNeeded(bool equals) => !equals; + public static Vector128 NegateIfNeeded(Vector128 equals) => ~equals; + public static Vector256 NegateIfNeeded(Vector256 equals) => ~equals; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs index d35b5e2d2034ee..c46f5ec8ae4251 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.cs @@ -3,9 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace System diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index e9df30a7d78ec1..50b30cb5b01ec0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -994,7 +994,7 @@ public string Replace(char oldChar, char newChar) if (firstIndex < 0) return this; - int remainingLength = Length - firstIndex; + nuint remainingLength = (uint)(Length - firstIndex); string result = FastAllocateString(Length); int copyLength = firstIndex; @@ -1006,35 +1006,56 @@ public string Replace(char oldChar, char newChar) } // Copy the remaining characters, doing the replacement as we go. - ref ushort pSrc = ref Unsafe.Add(ref Unsafe.As(ref _firstChar), copyLength); - ref ushort pDst = ref Unsafe.Add(ref Unsafe.As(ref result._firstChar), copyLength); + ref ushort pSrc = ref Unsafe.Add(ref GetRawStringDataAsUInt16(), (uint)copyLength); + ref ushort pDst = ref Unsafe.Add(ref result.GetRawStringDataAsUInt16(), (uint)copyLength); + nuint i = 0; - if (Vector.IsHardwareAccelerated && remainingLength >= Vector.Count) + if (Vector.IsHardwareAccelerated && Length >= Vector.Count) { - Vector oldChars = new Vector(oldChar); - Vector newChars = new Vector(newChar); + Vector oldChars = new(oldChar); + Vector newChars = new(newChar); - do + Vector original; + Vector equals; + Vector results; + + if (remainingLength > (nuint)Vector.Count) { - Vector original = Unsafe.ReadUnaligned>(ref Unsafe.As(ref pSrc)); - Vector equals = Vector.Equals(original, oldChars); - Vector results = Vector.ConditionalSelect(equals, newChars, original); - Unsafe.WriteUnaligned(ref Unsafe.As(ref pDst), results); - - pSrc = ref Unsafe.Add(ref pSrc, Vector.Count); - pDst = ref Unsafe.Add(ref pDst, Vector.Count); - remainingLength -= Vector.Count; + nuint lengthToExamine = remainingLength - (nuint)Vector.Count; + + do + { + original = Vector.LoadUnsafe(ref pSrc, i); + equals = Vector.Equals(original, oldChars); + results = Vector.ConditionalSelect(equals, newChars, original); + results.StoreUnsafe(ref pDst, i); + + i += (nuint)Vector.Count; + } + while (i < lengthToExamine); } - while (remainingLength >= Vector.Count); - } - for (; remainingLength > 0; remainingLength--) - { - ushort currentChar = pSrc; - pDst = currentChar == oldChar ? newChar : currentChar; + // There are [0, Vector.Count) elements remaining now. + // As the operation is idempotent, and we know that in total there are at least Vector.Count + // elements available, we read a vector from the very end of the string, perform the replace + // and write to the destination at the very end. + // Thus we can eliminate the scalar processing of the remaining elements. + // We perform this operation even if there are 0 elements remaining, as it is cheaper than the + // additional check which would introduce a branch here. - pSrc = ref Unsafe.Add(ref pSrc, 1); - pDst = ref Unsafe.Add(ref pDst, 1); + i = (uint)(Length - Vector.Count); + original = Vector.LoadUnsafe(ref GetRawStringDataAsUInt16(), i); + equals = Vector.Equals(original, oldChars); + results = Vector.ConditionalSelect(equals, newChars, original); + results.StoreUnsafe(ref result.GetRawStringDataAsUInt16(), i); + } + else + { + for (; i < remainingLength; ++i) + { + ushort currentChar = Unsafe.Add(ref pSrc, i); + Unsafe.Add(ref pDst, i) = currentChar == oldChar ? newChar : currentChar; + } } return result; @@ -1067,7 +1088,7 @@ public string Replace(string oldValue, string? newValue) int i = 0; while (true) { - int pos = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, i), c, Length - i); + int pos = SpanHelpers.IndexOfChar(ref Unsafe.Add(ref _firstChar, i), c, Length - i); if (pos < 0) { break; diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs b/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs index 945959f104a7e3..df95295a116fa5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Searching.cs @@ -27,7 +27,8 @@ public bool Contains(string value, StringComparison comparisonType) #pragma warning restore CA2249 } - public bool Contains(char value) => SpanHelpers.Contains(ref _firstChar, value, Length); + public bool Contains(char value) + => SpanHelpers.ContainsValueType(ref Unsafe.As(ref _firstChar), (short)value, Length); public bool Contains(char value, StringComparison comparisonType) { @@ -36,8 +37,7 @@ public bool Contains(char value, StringComparison comparisonType) // Returns the index of the first occurrence of a specified character in the current instance. // The search starts at startIndex and runs thorough the next count characters. - // - public int IndexOf(char value) => SpanHelpers.IndexOf(ref _firstChar, value, Length); + public int IndexOf(char value) => SpanHelpers.IndexOfChar(ref _firstChar, value, Length); public int IndexOf(char value, int startIndex) { @@ -78,10 +78,10 @@ private int IndexOfCharOrdinalIgnoreCase(char value) { char valueUc = (char)(value | 0x20); char valueLc = (char)(value & ~0x20); - return SpanHelpers.IndexOfAny(ref _firstChar, valueLc, valueUc, Length); + return SpanHelpers.IndexOfAnyChar(ref _firstChar, valueLc, valueUc, Length); } - return SpanHelpers.IndexOf(ref _firstChar, value, Length); + return SpanHelpers.IndexOfChar(ref _firstChar, value, Length); } public unsafe int IndexOf(char value, int startIndex, int count) @@ -96,7 +96,7 @@ public unsafe int IndexOf(char value, int startIndex, int count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); } - int result = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, startIndex), value, count); + int result = SpanHelpers.IndexOfChar(ref Unsafe.Add(ref _firstChar, startIndex), value, count); return result < 0 ? result : result + startIndex; } @@ -280,8 +280,8 @@ public int IndexOf(string value, int startIndex, int count, StringComparison com // The search starts at startIndex and runs backwards to startIndex - count + 1. // The character at position startIndex is included in the search. startIndex is the larger // index within the string. - // - public int LastIndexOf(char value) => SpanHelpers.LastIndexOf(ref _firstChar, value, Length); + public int LastIndexOf(char value) + => SpanHelpers.LastIndexOfValueType(ref Unsafe.As(ref _firstChar), (short)value, Length); public int LastIndexOf(char value, int startIndex) { @@ -306,7 +306,7 @@ public unsafe int LastIndexOf(char value, int startIndex, int count) } int startSearchAt = startIndex + 1 - count; - int result = SpanHelpers.LastIndexOf(ref Unsafe.Add(ref _firstChar, startSearchAt), value, count); + int result = SpanHelpers.LastIndexOfValueType(ref Unsafe.As(ref Unsafe.Add(ref _firstChar, startSearchAt)), (short)value, count); return result < 0 ? result : result + startSearchAt; } diff --git a/src/libraries/System.Private.CoreLib/src/System/String.cs b/src/libraries/System.Private.CoreLib/src/System/String.cs index 77d2168b0d38b8..39d9153f1d1725 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.cs @@ -508,6 +508,7 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value) public ref readonly char GetPinnableReference() => ref _firstChar; internal ref char GetRawStringData() => ref _firstChar; + internal ref ushort GetRawStringDataAsUInt16() => ref Unsafe.As(ref _firstChar); // Helper for encodings so they can talk to our buffer directly // stringLength must be the exact size we'll expect @@ -593,39 +594,9 @@ public StringRuneEnumerator EnumerateRunes() return new StringRuneEnumerator(this); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int wcslen(char* ptr) - { - // IndexOf processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. - // This IndexOf behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. - int length = SpanHelpers.IndexOf(ref *ptr, '\0', int.MaxValue); - if (length < 0) - { - ThrowMustBeNullTerminatedString(); - } + internal static unsafe int wcslen(char* ptr) => SpanHelpers.IndexOfNullCharacter(ref *ptr); - return length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe int strlen(byte* ptr) - { - // IndexOf processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator. - // This IndexOf behavior is an implementation detail of the runtime and callers outside System.Private.CoreLib must not depend on it. - int length = SpanHelpers.IndexOf(ref *ptr, (byte)'\0', int.MaxValue); - if (length < 0) - { - ThrowMustBeNullTerminatedString(); - } - - return length; - } - - [DoesNotReturn] - private static void ThrowMustBeNullTerminatedString() - { - throw new ArgumentException(SR.Arg_MustBeNullTerminatedString); - } + internal static unsafe int strlen(byte* ptr) => SpanHelpers.IndexOfNullByte(ref *ptr); // // IConvertible implementation diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index ba233dc62aadee..1c0c6f493b033a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -1052,10 +1052,10 @@ public StringBuilder Append(char value) int nextCharIndex = m_ChunkLength; char[] chars = m_ChunkChars; - if ((uint)nextCharIndex < (uint)chars.Length) + if ((uint)chars.Length > (uint)nextCharIndex) { chars[nextCharIndex] = value; - m_ChunkLength = nextCharIndex + 1; + m_ChunkLength++; } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index e75cb7f0650afc..fb30f2c9996a21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -1793,7 +1793,7 @@ private static bool TryConvertFromTruncating(TOther value, out UInt128 r /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1860,14 +1860,14 @@ static bool INumberBase.TryConvertToChecked(UInt128 value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1940,14 +1940,14 @@ static bool INumberBase.TryConvertToSaturating(UInt128 value, [ } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(UInt128 value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(UInt128 value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -2014,7 +2014,7 @@ static bool INumberBase.TryConvertToTruncating(UInt128 value, [ } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 19069064d13697..5e50f54f64193d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -946,7 +946,7 @@ private static bool TryConvertFromTruncating(TOther value, out ushort re /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1013,14 +1013,14 @@ static bool INumberBase.TryConvertToChecked(ushort value, [NotNu } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1087,14 +1087,14 @@ static bool INumberBase.TryConvertToSaturating(ushort value, [No } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(ushort value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(ushort value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1161,7 +1161,7 @@ static bool INumberBase.TryConvertToTruncating(ushort value, [No } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index cce0a8b78a629d..4f137f25dd3037 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -949,7 +949,7 @@ private static bool TryConvertFromTruncating(TOther value, out uint resu /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1016,14 +1016,14 @@ static bool INumberBase.TryConvertToChecked(uint value, [NotNullWh } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1096,14 +1096,14 @@ static bool INumberBase.TryConvertToSaturating(uint value, [NotNul } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(uint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(uint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1170,7 +1170,7 @@ static bool INumberBase.TryConvertToTruncating(uint value, [NotNul } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index f3010971b12a26..c3f210e350dad1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -948,7 +948,7 @@ private static bool TryConvertFromTruncating(TOther value, out ulong res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1015,14 +1015,14 @@ static bool INumberBase.TryConvertToChecked(ulong value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1089,14 +1089,14 @@ static bool INumberBase.TryConvertToSaturating(ulong value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(ulong value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(ulong value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1163,7 +1163,7 @@ static bool INumberBase.TryConvertToTruncating(ulong value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 29403c4e09907c..04c8384c34957d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -933,7 +933,7 @@ private static bool TryConvertFromTruncating(TOther value, out nuint res /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1000,14 +1000,14 @@ static bool INumberBase.TryConvertToChecked(nuint value, [NotNull } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1074,14 +1074,14 @@ static bool INumberBase.TryConvertToSaturating(nuint value, [NotN } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(nuint value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(nuint value, [MaybeNullWhen(false)] out TOther result) { // In order to reduce overall code duplication and improve the inlinabilty of these // methods for the corelib types we have `ConvertFrom` handle the same sign and @@ -1148,7 +1148,7 @@ static bool INumberBase.TryConvertToTruncating(nuint value, [NotN } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs index aa3046d42f8c7b..3d909709a8fa5e 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryReader.cs @@ -1350,14 +1350,14 @@ private unsafe int ReadArray(float[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, float[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1373,14 +1373,14 @@ private unsafe int ReadArray(double[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, double[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } @@ -1396,14 +1396,14 @@ private unsafe int ReadArray(decimal[] array, int offset, int count) public override int ReadArray(string localName, string namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } public override int ReadArray(XmlDictionaryString localName, XmlDictionaryString namespaceUri, decimal[] array, int offset, int count) { - if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement)) + if (IsStartArray(localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement) && BitConverter.IsLittleEndian) return ReadArray(array, offset, count); return base.ReadArray(localName, namespaceUri, array, offset, count); } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs index be47e110a151bd..0277530923ef2b 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBinaryWriter.cs @@ -12,6 +12,7 @@ using System.Runtime.Serialization; using System.Globalization; using System.Collections.Generic; +using System.Buffers.Binary; namespace System.Xml { @@ -757,12 +758,8 @@ public override unsafe void WriteFloatText(float f) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(float), out offset); - byte* bytes = (byte*)&f; buffer[offset + 0] = (byte)XmlBinaryNodeType.FloatText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; + BinaryPrimitives.WriteSingleLittleEndian(buffer.AsSpan(offset + 1, sizeof(float)), f); Advance(1 + sizeof(float)); } } @@ -778,16 +775,8 @@ public override unsafe void WriteDoubleText(double d) { int offset; byte[] buffer = GetTextNodeBuffer(1 + sizeof(double), out offset); - byte* bytes = (byte*)&d; buffer[offset + 0] = (byte)XmlBinaryNodeType.DoubleText; - buffer[offset + 1] = bytes[0]; - buffer[offset + 2] = bytes[1]; - buffer[offset + 3] = bytes[2]; - buffer[offset + 4] = bytes[3]; - buffer[offset + 5] = bytes[4]; - buffer[offset + 6] = bytes[5]; - buffer[offset + 7] = bytes[6]; - buffer[offset + 8] = bytes[7]; + BinaryPrimitives.WriteDoubleLittleEndian(buffer.AsSpan(offset + 1, sizeof(double)), d); Advance(1 + sizeof(double)); } } @@ -798,9 +787,24 @@ public override unsafe void WriteDecimalText(decimal d) byte[] buffer = GetTextNodeBuffer(1 + sizeof(decimal), out offset); byte* bytes = (byte*)&d; buffer[offset++] = (byte)XmlBinaryNodeType.DecimalText; - for (int i = 0; i < sizeof(decimal); i++) + if (BitConverter.IsLittleEndian) { - buffer[offset++] = bytes[i]; + for (int i = 0; i < sizeof(decimal); i++) + { + buffer[offset++] = bytes[i]; + } + } + else + { + Span bits = stackalloc int[4]; + decimal.TryGetBits(d, bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = buffer.AsSpan(offset, sizeof(decimal)); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); } Advance(1 + sizeof(decimal)); } @@ -870,17 +874,147 @@ private void WriteArrayInfo(XmlBinaryNodeType nodeType, int count) WriteMultiByteInt32(count); } - public unsafe void UnsafeWriteArray(XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) + public unsafe void UnsafeWriteBoolArray(bool[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.BoolTextWithEndElement, count); + fixed (bool* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, count); + } + } + + public unsafe void UnsafeWriteInt16Array(short[] array, int offset, int count) { - WriteArrayInfo(nodeType, count); - UnsafeWriteArray(array, (int)(arrayMax - array)); + WriteArrayInfo(XmlBinaryNodeType.Int16TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (short* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(short) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(short), out int bufferOffset).AsSpan(bufferOffset, sizeof(short)); + BinaryPrimitives.WriteInt16LittleEndian(span, array[offset + i]); + Advance(sizeof(short)); + } + } } - private unsafe void UnsafeWriteArray(byte* array, int byteCount) + public unsafe void UnsafeWriteInt32Array(int[] array, int offset, int count) { - base.UnsafeWriteBytes(array, byteCount); + WriteArrayInfo(XmlBinaryNodeType.Int32TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (int* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(int) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(int), out int bufferOffset).AsSpan(bufferOffset, sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, array[offset + i]); + Advance(sizeof(int)); + } + } } + public unsafe void UnsafeWriteInt64Array(long[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.Int64TextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (long* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(long) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(long), out int bufferOffset).AsSpan(bufferOffset, sizeof(long)); + BinaryPrimitives.WriteInt64LittleEndian(span, array[offset + i]); + Advance(sizeof(long)); + } + } + } + + public unsafe void UnsafeWriteFloatArray(float[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.FloatTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (float* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(float) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(float), out int bufferOffset).AsSpan(bufferOffset, sizeof(float)); + BinaryPrimitives.WriteSingleLittleEndian(span, array[offset + i]); + Advance(sizeof(float)); + } + } + } + + public unsafe void UnsafeWriteDoubleArray(double[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.DoubleTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (double* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(double) * count); + } + } + else + { + for (int i = 0; i < count; i++) + { + Span span = GetBuffer(sizeof(double), out int bufferOffset).AsSpan(bufferOffset, sizeof(double)); + BinaryPrimitives.WriteDoubleLittleEndian(span, array[offset + i]); + Advance(sizeof(double)); + } + } + } + + public unsafe void UnsafeWriteDecimalArray(decimal[] array, int offset, int count) + { + WriteArrayInfo(XmlBinaryNodeType.DecimalTextWithEndElement, count); + if (BitConverter.IsLittleEndian) + { + fixed (decimal* items = &array[offset]) + { + base.UnsafeWriteBytes((byte*)items, sizeof(decimal) * count); + } + } + else + { + Span bits = stackalloc int[4]; + for (int i = 0; i < count; i++) + { + decimal.TryGetBits(array[offset + i], bits, out int intsWritten); + Debug.Assert(intsWritten == 4); + + Span span = GetBuffer(16, out int bufferOffset).AsSpan(bufferOffset, 16); + BinaryPrimitives.WriteInt32LittleEndian(span, bits[3]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(4), bits[2]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(8), bits[0]); + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(12), bits[1]); + Advance(16); + } + } + } public void WriteDateTimeArray(DateTime[] array, int offset, int count) { WriteArrayInfo(XmlBinaryNodeType.DateTimeTextWithEndElement, count); @@ -1160,20 +1294,6 @@ private void WriteStartArray(string? prefix, XmlDictionaryString localName, XmlD WriteEndElement(); } - private unsafe void UnsafeWriteArray(string? prefix, string localName, string? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - - private unsafe void UnsafeWriteArray(string? prefix, XmlDictionaryString localName, XmlDictionaryString? namespaceUri, - XmlBinaryNodeType nodeType, int count, byte* array, byte* arrayMax) - { - WriteStartArray(prefix, localName, namespaceUri, count); - _writer.UnsafeWriteArray(nodeType, count, array, arrayMax); - } - private static void CheckArray(Array array, int offset, int count) { ArgumentNullException.ThrowIfNull(array); @@ -1199,10 +1319,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1218,10 +1336,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (bool* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.BoolTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteBoolArray(array, offset, count); } } } @@ -1237,10 +1353,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1256,10 +1370,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (short* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int16TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt16Array(array, offset, count); } } } @@ -1275,10 +1387,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1294,10 +1404,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (int* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int32TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt32Array(array, offset, count); } } } @@ -1313,10 +1421,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1332,10 +1438,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (long* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.Int64TextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteInt64Array(array, offset, count); } } } @@ -1351,10 +1455,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1370,10 +1472,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (float* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.FloatTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteFloatArray(array, offset, count); } } } @@ -1389,10 +1489,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1408,10 +1506,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (double* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DoubleTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDoubleArray(array, offset, count); } } } @@ -1427,10 +1523,8 @@ public override unsafe void WriteArray(string? prefix, string localName, string? CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } @@ -1446,10 +1540,8 @@ public override unsafe void WriteArray(string? prefix, XmlDictionaryString local CheckArray(array, offset, count); if (count > 0) { - fixed (decimal* items = &array[offset]) - { - UnsafeWriteArray(prefix, localName, namespaceUri, XmlBinaryNodeType.DecimalTextWithEndElement, count, (byte*)items, (byte*)&items[count]); - } + WriteStartArray(prefix, localName, namespaceUri, count); + _writer.UnsafeWriteDecimalArray(array, offset, count); } } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs index f193d1815cd591..15b3f23b57708e 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -373,10 +373,10 @@ public long ReadInt64() => BitConverter.IsLittleEndian ? ReadRawBytes() : BinaryPrimitives.ReverseEndianness(ReadRawBytes()); public float ReadSingle() - => ReadRawBytes(); + => BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(float), out int offset).AsSpan(offset, sizeof(float))); public double ReadDouble() - => ReadRawBytes(); + => BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(double), out int offset).AsSpan(offset, sizeof(double))); public decimal ReadDecimal() { @@ -964,10 +964,10 @@ public ulong GetUInt64(int offset) => (ulong)GetInt64(offset); public float GetSingle(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(float))); public double GetDouble(int offset) - => ReadRawBytes(offset); + => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(double))); public decimal GetDecimal(int offset) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs index c2aa7970b546f6..d593a97e7d56ec 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextWriter.cs @@ -760,7 +760,7 @@ public override void WriteComment(string? text) { try { - if (null != text && (text.Contains("--") || text.StartsWith('-'))) + if (null != text && (text.Contains("--") || text.EndsWith('-'))) { throw new ArgumentException(SR.Xml_InvalidCommentChars); } diff --git a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs index f7b5e223303f53..e10326033c5dec 100644 --- a/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs +++ b/src/libraries/System.Private.Xml/tests/XmlSerializer/XmlSerializerTests.cs @@ -203,6 +203,7 @@ public static void Xml_ListRoot() // horizon that it's not worth the trouble. #if !XMLSERIALIZERGENERATORTESTS [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74247", TestPlatforms.tvOS)] public static void Xml_ReadOnlyCollection() { ReadOnlyCollection roc = new ReadOnlyCollection(new string[] { "one", "two" }); @@ -224,6 +225,7 @@ public static void Xml_ReadOnlyCollection() [Theory] [MemberData(nameof(Xml_ImmutableCollections_MemberData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74247", TestPlatforms.tvOS)] public static void Xml_ImmutableCollections(Type type, object collection, Type createException, Type addException, string expectedXml, string exMsg = null) { XmlSerializer serializer; diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj b/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj index c3e76cb1a30474..3e074e9a835e3f 100644 --- a/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/System.Xml.RW.XmlWriter.Tests.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs b/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs new file mode 100644 index 00000000000000..5b1063a1fc6060 --- /dev/null +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/XmlTextWriterTests.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace System.Xml.Tests +{ + public class XmlTextWriterTests + { + public static IEnumerable PositiveTestCases + { + get + { + yield return new string[] { null }; // will be normalized to empty string + yield return new string[] { "" }; + yield return new string[] { "This is some data." }; + yield return new string[] { " << brackets and whitespace >> " }; // brackets & surrounding whitespace are ok + yield return new string[] { "&" }; // entities are ok (treated opaquely) + yield return new string[] { "Hello\r\nthere." }; // newlines are ok + yield return new string[] { "\U0001F643 Upside-down smiley \U0001F643" }; // correctly paired surrogates are ok + yield return new string[] { "\uFFFD\uFFFE\uFFFF" }; // replacement char & private use are ok + } + } + + public static IEnumerable BadSurrogateTestCases + { + get + { + yield return new string[] { "\uD800 Unpaired high surrogate." }; + yield return new string[] { "\uDFFF Unpaired low surrogate." }; + yield return new string[] { "Unpaired high surrogate at end. \uD800" }; + yield return new string[] { "Unpaired low surrogate at end. \uDFFF" }; + yield return new string[] { "Unpaired surrogates \uDFFF\uD800 in middle." }; + } + } + + [Theory] + [MemberData(nameof(PositiveTestCases))] + [InlineData("]]")] // ]] without trailing > is ok + [InlineData("-->")] // end of comment marker ok (meaningless to cdata tag) + public void WriteCData_SuccessCases(string cdataText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + xw.WriteCData(cdataText); + + Assert.Equal($"", sw.ToString()); + } + + [Theory] + [MemberData(nameof(BadSurrogateTestCases), DisableDiscoveryEnumeration = true)] // disable enumeration to avoid test harness misinterpreting unpaired surrogates + [InlineData("]]>")] // end of cdata marker forbidden (ambiguous close tag) + public void WriteCData_FailureCases(string cdataText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + Assert.Throws(() => xw.WriteCData(cdataText)); + } + + [Theory] + [MemberData(nameof(PositiveTestCases))] + [InlineData("-12345")] // hyphen at beginning is ok + [InlineData("123- -45")] // single hyphens are ok in middle + [InlineData("]]>")] // end of cdata marker ok (meaningless to comment tag) + public void WriteComment_SuccessCases(string commentText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + xw.WriteComment(commentText); + + Assert.Equal($"", sw.ToString()); + } + + [Theory] + [MemberData(nameof(BadSurrogateTestCases), DisableDiscoveryEnumeration = true)] // disable enumeration to avoid test harness misinterpreting unpaired surrogates + [InlineData("123--45")] // double-hyphen in middle is forbidden (ambiguous comment close tag) + [InlineData("12345-")] // hyphen at end is forbidden (ambiguous comment close tag) + [InlineData("-->")] // end of comment marker forbidden (ambiguous close tag) + public void WriteComment_FailureCases(string commentText) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xw = new XmlTextWriter(sw); + + Assert.Throws(() => xw.WriteComment(commentText)); + } + } +} diff --git a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs index e6d59b787cc564..5e41d80f197c47 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -69,7 +69,6 @@ public void Invoke_StaticConstructor_NullObject_NullParameters() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsInvokingStaticConstructorsSupported))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/40351", TestRuntimes.Mono)] public void Invoke_StaticConstructorMultipleTimes() { ConstructorInfo[] constructors = GetConstructors(typeof(ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection)); diff --git a/src/libraries/System.Reflection/tests/DefaultBinderTests.cs b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs index 99018fed4b6a1c..874e1f3d5864c6 100644 --- a/src/libraries/System.Reflection/tests/DefaultBinderTests.cs +++ b/src/libraries/System.Reflection/tests/DefaultBinderTests.cs @@ -167,6 +167,14 @@ public static void InvokeWithNamedParametersOutOfOrder() Assert.Equal(8, result); } + [Theory] + [InlineData("")] + [InlineData(null)] + public static void InvokeWithCreateInstance(string name) + { + Assert.IsType(typeof(Sample).InvokeMember(name, BindingFlags.CreateInstance, null, null, null)); + } + public class Test { public void TestMethod(int param1) { } diff --git a/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json b/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json deleted file mode 100644 index e3ad204dd9e512..00000000000000 --- a/src/libraries/System.Resources.ResourceManager/tests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Drawing.EnableUnixSupport": true - } -} \ No newline at end of file diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs b/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs index bca195864c8354..255d03483505f2 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Environment.StackTrace.cs @@ -15,7 +15,6 @@ public class EnvironmentStackTrace static string s_stackTrace; [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73051", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] [ActiveIssue("https://github.com/mono/mono/issues/15315", TestRuntimes.Mono)] public void StackTraceTest() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs index 82fb93b871026b..f6590136448336 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs @@ -319,7 +319,7 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) // Verify the method has no generic types or defined implementation // and is marked static and partial. if (methodSyntax.TypeParameterList is not null - || methodSyntax.Body is null + || (methodSyntax.Body is null && methodSyntax.ExpressionBody is null) || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) || methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs index 055e81315e68e4..005e30581affea 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSImportStubContext.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; @@ -16,6 +18,11 @@ namespace Microsoft.Interop.JavaScript { internal sealed class JSSignatureContext : IEquatable { + private static SymbolDisplayFormat s_typeNameFormat { get; } = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes + ); + internal static readonly string GeneratorName = typeof(JSImportGenerator).Assembly.GetName().Name; internal static readonly string GeneratorVersion = typeof(JSImportGenerator).Assembly.GetName().Version.ToString(); @@ -118,14 +125,8 @@ public static JSSignatureContext Create( }; int typesHash = Math.Abs((int)hash); - - var fullName = $"{method.ContainingType.ToDisplayString()}.{method.Name}"; - string qualifiedName; - var ns = string.Join(".", method.ContainingType.ToDisplayParts().Where(p => p.Kind == SymbolDisplayPartKind.NamespaceName).Select(x => x.ToString()).ToArray()); - var cn = string.Join("/", method.ContainingType.ToDisplayParts().Where(p => p.Kind == SymbolDisplayPartKind.ClassName).Select(x => x.ToString()).ToArray()); - var qclasses = method.ContainingType.ContainingNamespace == null ? ns : ns + "." + cn; - qualifiedName = $"[{env.Compilation.AssemblyName}]{qclasses}:{method.Name}"; + string qualifiedName = GetFullyQualifiedMethodName(env, method); return new JSSignatureContext() { @@ -143,6 +144,17 @@ public static JSSignatureContext Create( }; } + private static string GetFullyQualifiedMethodName(StubEnvironment env, IMethodSymbol method) + { + // Mono style nested class name format. + string typeName = method.ContainingType.ToDisplayString(s_typeNameFormat).Replace(".", "/"); + + if (!method.ContainingType.ContainingNamespace.IsGlobalNamespace) + typeName = $"{method.ContainingType.ContainingNamespace.ToDisplayString()}.{typeName}"; + + return $"[{env.Compilation.AssemblyName}]{typeName}:{method.Name}"; + } + private static (ImmutableArray, IMarshallingGeneratorFactory) GenerateTypeInformation(IMethodSymbol method, GeneratorDiagnostics diagnostics, StubEnvironment env) { var jsMarshallingAttributeParser = new JSMarshallingAttributeInfoParser(env.Compilation, diagnostics, method); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 353071ad0c5a0c..82b95560ab2a94 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -5,6 +5,14 @@ enable + + true + + + + $(DefineConstants);FEATURE_WASM_THREADS + + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 3c7ba82f162641..5b932edb7965dc 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -85,6 +85,9 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } catch (Exception ex) { + if (ex is TargetInvocationException refEx && refEx.InnerException != null) + ex = refEx.InnerException; + arg_exc.ToJS(ex); } } @@ -192,6 +195,8 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) } } +#if FEATURE_WASM_THREADS + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 // the marshaled signature is: // void InstallSynchronizationContext() @@ -207,6 +212,8 @@ public static void InstallSynchronizationContext (JSMarshalerArgument* arguments } } +#endif + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void StopProfile() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs index 9c87e4ec351408..47d14676d064f9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if FEATURE_WASM_THREADS + using System; using System.Threading; using System.Threading.Channels; @@ -141,3 +143,5 @@ private void Pump () { } } } + +#endif diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs index 71a795545f3317..a8966dcaf521d0 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -1199,7 +1199,62 @@ public void JsExportString(string value) public void JsExportStringNoNs() { var actual = JavaScriptTestHelper.invoke2_String("test", nameof(JavaScriptTestHelperNoNamespace.EchoString)); - Assert.Equal("test!", actual); + Assert.Equal("test51", actual); + } + + [Fact] + public void JsExportStructClassRecords() + { + var actual = JavaScriptTestHelper.invokeStructClassRecords("test"); + Assert.Equal(48, actual.Length); + Assert.Equal("test11", actual[0]); + Assert.Equal("test12", actual[1]); + Assert.Equal("test13", actual[2]); + Assert.Equal("test14", actual[3]); + Assert.Equal("test15", actual[4]); + Assert.Equal("test16", actual[5]); + Assert.Equal("test17", actual[6]); + Assert.Equal("test18", actual[7]); + Assert.Equal("test19", actual[8]); + Assert.Equal("test21", actual[9]); + Assert.Equal("test22", actual[10]); + Assert.Equal("test23", actual[11]); + Assert.Equal("test24", actual[12]); + Assert.Equal("test25", actual[13]); + Assert.Equal("test31", actual[14]); + Assert.Equal("test32", actual[15]); + Assert.Equal("test33", actual[16]); + Assert.Equal("test34", actual[17]); + Assert.Equal("test35", actual[18]); + Assert.Equal("test41", actual[19]); + Assert.Equal("test42", actual[20]); + Assert.Equal("test43", actual[21]); + Assert.Equal("test44", actual[22]); + Assert.Equal("test45", actual[23]); + Assert.Equal("test51", actual[24]); + Assert.Equal("test52", actual[25]); + Assert.Equal("test53", actual[26]); + Assert.Equal("test54", actual[27]); + Assert.Equal("test55", actual[28]); + Assert.Equal("test56", actual[29]); + Assert.Equal("test57", actual[30]); + Assert.Equal("test58", actual[31]); + Assert.Equal("test59", actual[32]); + Assert.Equal("test61", actual[33]); + Assert.Equal("test62", actual[34]); + Assert.Equal("test63", actual[35]); + Assert.Equal("test64", actual[36]); + Assert.Equal("test65", actual[37]); + Assert.Equal("test71", actual[38]); + Assert.Equal("test72", actual[39]); + Assert.Equal("test73", actual[40]); + Assert.Equal("test74", actual[41]); + Assert.Equal("test75", actual[42]); + Assert.Equal("test81", actual[43]); + Assert.Equal("test82", actual[44]); + Assert.Equal("test83", actual[45]); + Assert.Equal("test84", actual[46]); + Assert.Equal("test85", actual[47]); } [Fact] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index aaa5d16c2b8cdb..b5a89b7f48fe98 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -250,6 +250,9 @@ public static int EchoInt32([JSMarshalAs] int arg1) [JSImport("invoke2", "JavaScriptTestHelper")] [return: JSMarshalAs] internal static partial string invoke2_String([JSMarshalAs] string value, [JSMarshalAs] string name); + [JSImport("invokeStructClassRecords", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial string[] invokeStructClassRecords([JSMarshalAs] string value); [JSExport] [return: JSMarshalAs] public static string EchoString([JSMarshalAs] string arg1) @@ -935,11 +938,302 @@ public static async Task InitializeAsync() } } +namespace JavaScriptTestHelperNamespace +{ + public partial class JavaScriptTestHelper + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "11"; + } + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "12"; + + public partial class DoubleNestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "13"; + } + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "14"; + + public partial record class DoubleNestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "15"; + } + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "16"; + + public partial struct DoubleNestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "17"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "18"; + + public partial record struct DoubleNestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "19"; + } + } + } + + public partial class JavaScriptTestHelperStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "21"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "22"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "23"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "24"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "25"; + } + } + + public partial record class JavaScriptTestHelperRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "31"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "32"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "33"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "34"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "35"; + } + } + + public partial record struct JavaScriptTestHelperRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "41"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "42"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "43"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "44"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) + { + return message + "45"; + } + } + } +} + public partial class JavaScriptTestHelperNoNamespace { [System.Runtime.InteropServices.JavaScript.JSExport] - public static string EchoString(string message) + public static string EchoString(string message) => message + "51"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "52"; + + public partial class DoubleNestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "53"; + } + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "54"; + + public partial record class DoubleNestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "55"; + } + } + + public partial struct NestedStruct { - return message + "!"; + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "56"; + + public partial struct DoubleNestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "57"; + } + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "58"; + + public partial record struct DoubleNestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "59"; + } } } + +public partial class JavaScriptTestHelperStructNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "61"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "62"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "63"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "64"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "65"; + } +} + +public partial record class JavaScriptTestHelperRecordClassNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "71"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "72"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "73"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "74"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "75"; + } +} + +public partial record struct JavaScriptTestHelperRecordStructNoNamespace +{ + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "81"; + + public partial class NestedClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "82"; + } + + public partial record class NestedRecordClass + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "83"; + } + + public partial struct NestedStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "84"; + } + + public partial record struct NestedRecordStruct + { + [System.Runtime.InteropServices.JavaScript.JSExport] + public static string EchoString(string message) => message + "85"; + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index c3cc24ed73979d..314e715e16b815 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -171,6 +171,59 @@ export function invoke2(arg1, name) { return res; } +export function invokeStructClassRecords(arg1) { + return [ + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedClass.DoubleNestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordClass.DoubleNestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedStruct.DoubleNestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.NestedRecordStruct.DoubleNestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperStruct.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordClass.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelperRecordStruct.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedClass.DoubleNestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordClass.DoubleNestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedStruct.DoubleNestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperNoNamespace.NestedRecordStruct.DoubleNestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperStructNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordClassNoNamespace.NestedRecordStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedRecordClass.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedStruct.EchoString(arg1), + dllExports.JavaScriptTestHelperRecordStructNoNamespace.NestedRecordStruct.EchoString(arg1), + ]; +} + export async function awaitvoid(arg1) { // console.log("awaitvoid:" + typeof arg1); await arg1; diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index b5a7b209486d9a..c1ace3807df844 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1314,9 +1314,9 @@ public static void Free(void* ptr) { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Runtime.InteropServices.NFloat result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Runtime.InteropServices.NFloat value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Runtime.InteropServices.NFloat System.Numerics.ISubtractionOperators.operator checked -(System.Runtime.InteropServices.NFloat left, System.Runtime.InteropServices.NFloat right) { throw null; } static System.Runtime.InteropServices.NFloat System.Numerics.IUnaryNegationOperators.operator checked -(System.Runtime.InteropServices.NFloat value) { throw null; } public static System.Runtime.InteropServices.NFloat Tan(System.Runtime.InteropServices.NFloat x) { throw null; } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 3009539cc05335..6c0187cf21ff84 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -7,6 +7,10 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser $(Features.Replace('nullablePublicOnly', '') + + wasm.helix.targets + $(WasmXHarnessArgs) --engine-arg=--experimental-wasm-simd diff --git a/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets b/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets new file mode 100644 index 00000000000000..65123620588e8d --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/tests/wasm.helix.targets @@ -0,0 +1,49 @@ + + + $(HelixExtensionTargets);_AddHelixRuntimeIntrinsicsItems + <_RuntimeIntrinsicsProjectName>System.Runtime.Intrinsics.Tests + + + + + + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">"WasmXHarnessArgs=$WasmXHarnessArgs --engine-arg=--experimental-wasm-simd" + <_AOTBuildArgsSIMD Condition="'$(OS)' == 'Windows_NT'">"WasmXHarnessArgs=%WasmXHarnessArgs% --engine-arg=--experimental-wasm-simd" + + + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">$(_AOTBuildArgsSIMD) "AOT_BUILD_ARGS=-p:WasmEnableSIMD=true" + + <_AOTBuildArgsSIMD Condition="'$(OS)' != 'Windows_NT'">export $(_AOTBuildArgsSIMD) + <_AOTBuildArgsSIMD Condition="'$(OS)' == 'Windows_NT'">set $(_AOTBuildArgsSIMD) + + + + + + + + $(TestArchiveTestsDir)$(_RuntimeIntrinsicsProjectName).zip + $(HelixCommand) + $(_workItemTimeout) + + + + $(TestArchiveTestsDir)$(_RuntimeIntrinsicsProjectName).zip + $(HelixCommand) + $(_workItemTimeout) + + $(_AOTBuildArgsSIMD) + + + <_RuntimeIntrinsicsHelixItem + Include="@(HelixWorkItem)" + Condition="$([System.String]::new('%(HelixWorkItem.Identity)').EndsWith('-$(_RuntimeIntrinsicsProjectName)'))" /> + + + + + diff --git a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs index 443aae6338ffcd..0d371c4d7886c6 100644 --- a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs +++ b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs @@ -218,9 +218,9 @@ namespace System.Numerics static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Numerics.BigInteger result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Numerics.BigInteger result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Numerics.BigInteger result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.BigInteger value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Numerics.BigInteger System.Numerics.INumber.MaxNumber(System.Numerics.BigInteger x, System.Numerics.BigInteger y) { throw null; } static System.Numerics.BigInteger System.Numerics.INumber.MinNumber(System.Numerics.BigInteger x, System.Numerics.BigInteger y) { throw null; } static int System.Numerics.INumber.Sign(System.Numerics.BigInteger value) { throw null; } @@ -367,9 +367,9 @@ namespace System.Numerics static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Numerics.Complex result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Numerics.Complex result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Numerics.Complex result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } public static System.Numerics.Complex Tan(System.Numerics.Complex value) { throw null; } public static System.Numerics.Complex Tanh(System.Numerics.Complex value) { throw null; } public override string ToString() { throw null; } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 4956fac177a403..1175591a97e005 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -4489,7 +4489,7 @@ private static bool TryConvertFromTruncating(TOther value, out BigIntege /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -4601,14 +4601,14 @@ static bool INumberBase.TryConvertToChecked(BigInteger value } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -4794,14 +4794,14 @@ static bool INumberBase.TryConvertToSaturating(BigInteger va } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(BigInteger value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(BigInteger value, [MaybeNullWhen(false)] out TOther result) { if (typeof(TOther) == typeof(byte)) { @@ -5190,7 +5190,7 @@ static bool INumberBase.TryConvertToTruncating(BigInteger va } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs index 7e59c6dd002603..d3edb7d44afb95 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs @@ -35,7 +35,9 @@ stackalloc uint[StackAllocThreshold] value.CopyTo(valueCopy); valueCopy.Slice(value.Length).Clear(); - PowCore(valueCopy, value.Length, temp, power, bits).CopyTo(bits); + Span result = PowCore(valueCopy, value.Length, temp, power, bits); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (tempFromPool != null) ArrayPool.Shared.Return(tempFromPool); @@ -50,19 +52,19 @@ private static Span PowCore(Span value, int valueLength, Span Debug.Assert(value.Length == temp.Length); result[0] = 1; - int bitsLength = 1; + int resultLength = 1; // The basic pow algorithm using square-and-multiply. while (power != 0) { if ((power & 1) == 1) - bitsLength = MultiplySelf(ref result, bitsLength, value.Slice(0, valueLength), ref temp); + resultLength = MultiplySelf(ref result, resultLength, value.Slice(0, valueLength), ref temp); if (power != 1) valueLength = SquareSelf(ref value, valueLength, ref temp); power >>= 1; } - return result; + return result.Slice(0, resultLength); } private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) @@ -217,7 +219,11 @@ public static void Pow(ReadOnlySpan value, uint power, Span valueCopy = (size <= StackAllocThreshold ? stackalloc uint[StackAllocThreshold] : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - valueCopy.Clear(); + + // smallish optimization here: + // subsequent operations will copy the elements to the beginning of the buffer, + // no need to clear everything + valueCopy.Slice(value.Length).Clear(); if (value.Length > modulus.Length) { @@ -262,7 +268,11 @@ public static void Pow(ReadOnlySpan value, ReadOnlySpan power, Span valueCopy = (size <= StackAllocThreshold ? stackalloc uint[StackAllocThreshold] : valueCopyFromPool = ArrayPool.Shared.Rent(size)).Slice(0, size); - valueCopy.Clear(); + + // smallish optimization here: + // subsequent operations will copy the elements to the beginning of the buffer, + // no need to clear everything + valueCopy.Slice(value.Length).Clear(); if (value.Length > modulus.Length) { @@ -305,7 +315,9 @@ private static void PowCore(Span value, int valueLength, if (modulus.Length < ReducerThreshold) { - PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); } else { @@ -341,7 +353,9 @@ stackalloc uint[StackAllocThreshold] if (rFromPool != null) ArrayPool.Shared.Return(rFromPool); - PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (muFromPool != null) ArrayPool.Shared.Return(muFromPool); @@ -361,7 +375,9 @@ private static void PowCore(Span value, int valueLength, if (modulus.Length < ReducerThreshold) { - PowCore(value, valueLength, power, modulus, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, modulus, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); } else { @@ -397,7 +413,9 @@ stackalloc uint[StackAllocThreshold] if (rFromPool != null) ArrayPool.Shared.Return(rFromPool); - PowCore(value, valueLength, power, reducer, bits, 1, temp).CopyTo(bits); + Span result = PowCore(value, valueLength, power, reducer, bits, 1, temp); + result.CopyTo(bits); + bits.Slice(result.Length).Clear(); if (muFromPool != null) ArrayPool.Shared.Return(muFromPool); @@ -523,7 +541,7 @@ private static Span PowCore(Span value, int valueLength, power >>= 1; } - return result; + return result.Slice(0, resultLength); } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 97c0a5558f5e13..f2e2220b6ba08d 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -1613,7 +1613,7 @@ private static bool TryConvertFrom(TOther value, out Complex result) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToChecked(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToChecked(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // so we'll throw an OverflowException for this scenario for integer types and @@ -1805,14 +1805,14 @@ static bool INumberBase.TryConvertToChecked(Complex value, [Not } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToSaturating(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToSaturating(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // and there isn't really a well-defined way to "saturate" to just a real value. @@ -1949,14 +1949,14 @@ static bool INumberBase.TryConvertToSaturating(Complex value, [ } else { - result = default!; + result = default; return false; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertToTruncating(Complex value, [NotNullWhen(true)] out TOther result) + static bool INumberBase.TryConvertToTruncating(Complex value, [MaybeNullWhen(false)] out TOther result) { // Complex numbers with an imaginary part can't be represented as a "real number" // so we'll only consider the real part for the purposes of truncation. @@ -2085,7 +2085,7 @@ static bool INumberBase.TryConvertToTruncating(Complex value, [ } else { - result = default!; + result = default; return false; } } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs index 54af6812151704..68f67892fcf76c 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs @@ -274,6 +274,31 @@ public static void ModPowAxiom() } } + [Fact] + public static void RegressionIssueRuntime70330() + { + byte[] tempByteArray1 = { 226, 32 }; + byte[] tempByteArray2 = { 113 }; + byte[] tempByteArray3 = { 15, 8, 201, 158, 96, 200, 233, 243, 184, 0, 33, 203, 210, 80, 174, 198, 244, 177, 223, 221, 168, 243, 233, 133, 103, 252, 219, 195, 187, 227, 215, 54, 66, 248, 37, 186, 232, 45, 227, 147, 100, 14, 121, 244, 56, 89, 181, 120, 205, 4, 59, 48, 65, 239, 221, 28, 30, 68, 55, 99, 237, 38, 56, 213, 40, 234, 136, 218, 42, 244, 222, 198, 205 }; + VerifyIdentityString( + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "tModPow", + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "bPow" + " bRemainder" + ); + } + + [Fact] + public static void RegressionIssuePerformance2575() + { + byte[] tempByteArray1 = { 0x93, 0x30, 0x70, 0xD8, 0x74, 0x0A, 0x70, 0x79, 0x18, 0x5A, 0xCD, 0x2D, 0x39, 0xBF, 0x36, 0xC6, 0x24, 0xDE, 0x4B, 0xD5, 0xC7, 0xB4, 0x56, 0x23, 0xB2, 0xB4, 0xB7, 0x43, 0xE5, 0x05, 0xDD, 0xAF, 0x97, 0x81, 0x67, 0xCB, 0x67, 0xE9, 0x53, 0x5A, 0x00, 0x42, 0x9B, 0x20, 0x56, 0xFA, 0xBE, 0x27, 0x6A, 0x14, 0x36, 0x17, 0x49, 0xC5, 0xAC, 0xDF, 0xDA, 0x4C, 0x26, 0xC0, 0x52, 0x2C, 0x93, 0x13, 0x7D, 0x1E, 0x96, 0xB8, 0x58, 0x6B, 0x5F, 0x3A, 0x8B, 0xF1, 0xD5, 0x84, 0x18, 0x36, 0xCC, 0xB7, 0xF5, 0x90, 0xD9, 0xD1, 0xCE, 0xC5, 0x65, 0xD2, 0xD5, 0x87, 0xF0, 0x1B, 0xC3, 0x92, 0x07, 0xD3, 0xAF, 0x88, 0xA2, 0x38, 0x64, 0x06, 0xCE, 0xFE, 0xB5, 0xFC, 0x8C, 0x58, 0xEF, 0x27, 0xC6, 0xA4, 0x7F, 0x6E, 0xCA, 0xC2, 0x53, 0xC2, 0x44, 0xB7, 0xB8, 0xC3, 0xE2, 0xD0, 0x7A, 0x43, 0x76, 0xF8, 0x00 }; + byte[] tempByteArray2 = { 0x02, 0xD4, 0x90, 0x93, 0x94, 0x52, 0xEF, 0xC1, 0xDA, 0x1B, 0xD2, 0x39, 0x0D, 0xE3, 0xAD, 0xC1, 0x4C, 0x9B, 0x54, 0xC8, 0x44, 0x7F, 0xED, 0x43, 0xEA, 0x7F, 0xA1, 0x23, 0xFE, 0x84, 0x71, 0x85, 0x93, 0x6E, 0xEC, 0x53, 0x35, 0x10, 0xEB, 0x1C, 0xDA, 0x01, 0x78, 0xA6, 0x71, 0x7E, 0xAB, 0xE0, 0x35, 0x0F, 0x2E, 0xA8, 0x21, 0x30, 0xA5, 0x83, 0xE7, 0x4C, 0xA9, 0x14, 0xB0, 0xCC, 0xC3, 0x56, 0xAA, 0x4C, 0xA5, 0x9E, 0xF6, 0x5A, 0x8B, 0x3C, 0xAF, 0x38, 0xED, 0x43, 0x06, 0x46, 0xD2, 0x6C, 0xD3, 0xC1, 0xED, 0xEE, 0x55, 0xC8, 0x63, 0x63, 0xC9, 0x69, 0x6B, 0xE8, 0x67, 0xD6, 0x6B, 0x0D, 0x3E, 0x22, 0xFC, 0x24, 0xD8, 0x0C, 0xEA, 0xDB, 0x83, 0xF2, 0xF9, 0xE9, 0x43, 0x61, 0x7C, 0xA0, 0x7A, 0x3D, 0x41, 0xEF, 0xDF, 0xD4, 0x00, 0xE1, 0xE9, 0x42, 0xD5, 0x8C, 0xEE, 0xA3, 0xD5, 0xD8, 0x00 }; + byte[] tempByteArray3 = { 0xDF, 0x05, 0xE4, 0x4A, 0xAD, 0x93, 0xC6, 0xD7, 0x00 }; + byte[] tempByteArray4 = { 0xC6, 0x57, 0x30, 0x3F, 0xCE, 0x21, 0xA0, 0x3D }; + VerifyIdentityString( + Print(tempByteArray3) + Print(tempByteArray2) + Print(tempByteArray1) + "tModPow", + Print(tempByteArray4) + ); + } + [Fact] public static void ModPowBoundary() { diff --git a/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj b/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj index d1d56008051099..68e20f52baae89 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj +++ b/src/libraries/System.Runtime.Serialization.Schema/src/System.Runtime.Serialization.Schema.csproj @@ -2,6 +2,7 @@ $(NetCoreAppCurrent) Microsoft + true true Provides support for importing and exporting xsd schemas for DataContractSerializer. @@ -25,4 +26,4 @@ System.Runtime.Serialization.Schema.XsdDataContractImporter - \ No newline at end of file + diff --git a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs index cea8d7333ccb7c..d0a4eda2369747 100644 --- a/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs +++ b/src/libraries/System.Runtime.Serialization.Schema/tests/System/Runtime/Serialization/Schema/SchemaUtils.cs @@ -14,8 +14,7 @@ internal class SchemaUtils static XmlWriterSettings writerSettings = new XmlWriterSettings() { Indent = true }; #region Test Data - internal static XmlSchemaSet PositiveSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _positiveSchemas = new string[] { @" @@ -30,10 +29,10 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet PositiveSchemas => ReadStringsIntoSchemaSet(_positiveSchemas); - internal static XmlSchemaSet IsReferenceSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _isReferenceSchemas = new string[] { @" @@ -53,10 +52,10 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet IsReferenceSchemas => ReadStringsIntoSchemaSet(_isReferenceSchemas); - internal static XmlSchemaSet MixedSchemas = SchemaUtils.ReadStringsIntoSchemaSet( - new string[] { + private static string[] _mixedSchemas = new string[] { @" @@ -67,7 +66,8 @@ internal class SchemaUtils ", - }); + }; + internal static XmlSchemaSet MixedSchemas => ReadStringsIntoSchemaSet(_mixedSchemas); internal static string[] NegativeSchemaStrings = new string[] { diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs index e15f9fc2df544b..6880fc48f02270 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryReaderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -193,8 +194,10 @@ public static void BinaryXml_ReadPrimitiveTypes() AssertReadContentFromBinary(8.20788039913184E-304, XmlBinaryNodeType.DoubleText, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }); AssertReadContentFromBinary(guid, XmlBinaryNodeType.GuidText, guid.ToByteArray()); AssertReadContentFromBinary(new TimeSpan(0x0807060504030201), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); - AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText - , new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertReadContentFromBinary(new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), XmlBinaryNodeType.DateTimeText, + new byte[] { 0x00, 0x18, 0xdf, 0x61, 0x5f, 0x87, 0xda, 0x48 }); // Double can be represented as float or inte as long as no detail is lost AssertReadContentFromBinary((double)0x0100, XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); @@ -205,6 +208,16 @@ public static void BinaryXml_ReadPrimitiveTypes() public static void BinaryXml_Array_RoundTrip() { int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + float[] floats = new float[] { 1.2345f, 2.3456f }; + double[] doubles = new double[] { 1.2345678901, 2.3456789012 }; + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; TimeSpan[] timespans = new[] { TimeSpan.FromTicks(0x0102030405060708), TimeSpan.FromTicks(0x1011121314151617) }; // Write more than 4 kb in a single call to ensure we hit path for reading (and writing happens on 512b) large arrays long[] longs = Enumerable.Range(0x01020304, 513).Select(i => (long)i | (long)(~i << 32)).ToArray(); @@ -217,6 +230,10 @@ public static void BinaryXml_Array_RoundTrip() using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); writer.WriteStartElement("root"); writer.WriteArray(null, "ints", null, ints, 1, 2); + writer.WriteArray(null, "floats", null, floats, 0, floats.Length); + writer.WriteArray(null, "doubles", null, doubles, 0, doubles.Length); + writer.WriteArray(null, "decimals", null, decimals, 0, decimals.Length); + writer.WriteArray(null, "datetimes", null, datetimes, 0, datetimes.Length); writer.WriteArray(null, "timespans", null, timespans, 0, timespans.Length); writer.WriteArray(null, "longs", null, longs, 0, longs.Length); writer.WriteArray(null, "guids", null, guids, 0, guids.Length); @@ -230,6 +247,10 @@ public static void BinaryXml_Array_RoundTrip() using var reader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max); reader.ReadStartElement("root"); int intsRead = reader.ReadArray("ints", string.Empty, actualInts, 1, 3); + float[] actualFloats = reader.ReadSingleArray("floats", string.Empty); + double[] actualDoubles = reader.ReadDoubleArray("doubles", string.Empty); + decimal[] actualDecimals = reader.ReadDecimalArray("decimals", string.Empty); + DateTime[] actualDateTimes = reader.ReadDateTimeArray("datetimes", string.Empty); TimeSpan[] actualTimeSpans = reader.ReadTimeSpanArray("timespans", string.Empty); long[] actualLongs = reader.ReadInt64Array("longs", string.Empty); Guid[] actualGuids = reader.ReadGuidArray("guids", string.Empty); @@ -240,6 +261,10 @@ public static void BinaryXml_Array_RoundTrip() Assert.Equal(2, intsRead); AssertExtensions.SequenceEqual(ints, actualInts); AssertExtensions.SequenceEqual(actualLongs, longs); + AssertExtensions.SequenceEqual(actualFloats, floats); + AssertExtensions.SequenceEqual(actualDoubles, doubles); + AssertExtensions.SequenceEqual(actualDecimals, decimals); + AssertExtensions.SequenceEqual(actualDateTimes, datetimes); AssertExtensions.SequenceEqual(actualTimeSpans, timespans); AssertExtensions.SequenceEqual(actualGuids, guids); } diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs index 719a06d6e48cf9..3ad4d32400e371 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -320,6 +321,179 @@ public static void FragmentTest() Assert.False(FragmentHelper.CanFragment(writer)); } + [Fact] + public static void BinaryWriter_PrimitiveTypes() + { + using MemoryStream ms = new(); + using XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + + AssertBytesWritten(x => x.WriteValue((byte)0x78), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue((short)0x1234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((short)0xf234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((int)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue((long)0x0102030412345678), XmlBinaryNodeType.Int64Text, new byte[] { 0x78, 0x56, 0x34, 0x12, 04, 03, 02, 01 }); + + // Integer values should be represented using smalles possible type + AssertBytesWritten(x => x.WriteValue((long)0), XmlBinaryNodeType.ZeroText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((long)1), XmlBinaryNodeType.OneText, Span.Empty); + AssertBytesWritten(x => x.WriteValue((int)0x00000078), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffffff0)), XmlBinaryNodeType.Int8Text, new byte[] { 0xf0 }); + AssertBytesWritten(x => x.WriteValue((int)0x00001234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffff234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 }); + AssertBytesWritten(x => x.WriteValue((long)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 }); + AssertBytesWritten(x => x.WriteValue(unchecked((long)0xfffffffff2345678)), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0xf2 }); + + float f = 1.23456788f; + ReadOnlySpan floatBytes = new byte[] { 0x52, 0x06, 0x9e, 0x3f }; + double d = 1.0 / 3.0; + ReadOnlySpan doubleBytes = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }; + Guid guid = new Guid(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + DateTime datetime = new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc); + Span datetimeBytes = stackalloc byte[8]; + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes, datetime.ToBinary()); + + AssertBytesWritten(x => x.WriteValue(f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b)), XmlBinaryNodeType.DecimalText, + new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 }); + AssertBytesWritten(x => x.WriteValue(guid), XmlBinaryNodeType.GuidText, guid.ToByteArray()); + AssertBytesWritten(x => x.WriteValue(new TimeSpan(0x0807060504030201)), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 }); + AssertBytesWritten(x => x.WriteValue(datetime), XmlBinaryNodeType.DateTimeText, datetimeBytes); + + // Double can be represented as float or int as long as no detail is lost + AssertBytesWritten(x => x.WriteValue((double)f), XmlBinaryNodeType.FloatText, floatBytes); + AssertBytesWritten(x => x.WriteValue((double)0x0100), XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 }); + AssertBytesWritten(x => x.WriteValue(d), XmlBinaryNodeType.DoubleText, doubleBytes); + + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, ReadOnlySpan expected) + { + writer.WriteStartElement("a"); + + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + Assert.Equal(nodeType, (XmlBinaryNodeType)segement[0]); + AssertExtensions.SequenceEqual(expected, segement.AsSpan(1)); + writer.WriteEndElement(); + } + } + + + [Fact] + public static void BinaryWriter_Arrays() + { + using var ms = new MemoryStream(); + using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms); + writer.WriteStartElement("root"); + int offset = 1; + int count = 2; + + bool[] bools = new bool[] { false, true, false, true }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, bools, offset, count), XmlBinaryNodeType.BoolTextWithEndElement, + count, new byte[] { 1, 0 }); + + short[] shorts = new short[] { -1, 0x0102, 0x1122, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, shorts, offset, count), XmlBinaryNodeType.Int16TextWithEndElement, + count, new byte[] { 2, 1, 0x22, 0x11 }); + + int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, ints, offset, count), XmlBinaryNodeType.Int32TextWithEndElement, + count, new byte[] { 4, 3, 2, 1, 0x44, 0x33, 0x22, 0x11 }); + + long[] longs = new long[] { -1, 0x0102030405060708, 0x1122334455667788, -1 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, longs, offset, count), XmlBinaryNodeType.Int64TextWithEndElement, + count, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }); + + float[] floats = new float[] { -1.0f, 1.23456788f, 1.23456788f, -1.0f }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, floats, offset, count), XmlBinaryNodeType.FloatTextWithEndElement, + count, new byte[] { 0x52, 0x06, 0x9e, 0x3f, 0x52, 0x06, 0x9e, 0x3f }); + + double[] doubles = new double[] { -1.0, 1.0 / 3.0, 1.0 / 3.0, -1.0 }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, doubles, offset, count), XmlBinaryNodeType.DoubleTextWithEndElement, + count, new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f }); + + decimal[] decimals = new[] { + new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b), + new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, decimals, 0, decimals.Length), XmlBinaryNodeType.DecimalTextWithEndElement, + decimals.Length, new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, + 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10, + 0x0, 0x0, 0x1c, 0x00, 0x34, 0x33, 0x32, 0x31, + 0x53, 0x52, 0x51, 0x50, 0x43, 0x42, 0x41, 0x40 }); + + DateTime[] datetimes = new[] { + new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local) + }; + Span datetimeBytes = stackalloc byte[8 * datetimes.Length]; + for (int i = 0; i < datetimes.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes.Slice(8 * i), datetimes[i].ToBinary()); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, datetimes, 0, datetimes.Length), XmlBinaryNodeType.DateTimeTextWithEndElement, + datetimes.Length, datetimeBytes); + + TimeSpan[] timespans = new[] { new TimeSpan(0x0807060504030201), new TimeSpan(0x1817161514131211) }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, timespans, 0, timespans.Length), XmlBinaryNodeType.TimeSpanTextWithEndElement, + timespans.Length, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 }); + + Guid[] guids = new Guid[] + { + new Guid(new ReadOnlySpan(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })), + new Guid(new ReadOnlySpan(new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 })) + }; + AssertBytesWritten(x => x.WriteArray(null, "a", null, guids, 0, guids.Length), XmlBinaryNodeType.GuidTextWithEndElement, + guids.Length, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 }); + + // Write more than 512 bytes in a single call to trigger different writing logic in XmlStreamNodeWriter.WriteBytes + long[] many_longs = Enumerable.Range(0x01020304, 127).Select(i => (long)i | (long)(~i << 32)).ToArray(); + Span many_longBytes = stackalloc byte[8 * many_longs.Length]; + for (int i = 0; i < many_longs.Length; i++) + { + BinaryPrimitives.WriteInt64LittleEndian(many_longBytes.Slice(8 * i), many_longs[i]); + } + AssertBytesWritten(x => x.WriteArray(null, "a", null, many_longs, 0, many_longs.Length), XmlBinaryNodeType.Int64TextWithEndElement, + many_longs.Length, many_longBytes); + + void AssertBytesWritten(Action action, XmlBinaryNodeType nodeType, int count, ReadOnlySpan expected) + { + // Reset stream so we only compare the actual value written (including end element) + writer.Flush(); + ms.Position = 0; + ms.SetLength(0); + + action(writer); + + writer.Flush(); + ms.TryGetBuffer(out ArraySegment segement); + + var actual = segement.AsSpan(); + Assert.Equal(XmlBinaryNodeType.Array, (XmlBinaryNodeType)actual[0]); + Assert.Equal(XmlBinaryNodeType.ShortElement, (XmlBinaryNodeType)actual[1]); + int elementLength = actual[2]; + Assert.InRange(elementLength, 0, 0x8f); // verify count is single byte + Assert.Equal(XmlBinaryNodeType.EndElement, (XmlBinaryNodeType)actual[3 + elementLength]); + + actual = actual.Slice(4 + elementLength); + // nodetype and count + Assert.Equal(nodeType, (XmlBinaryNodeType)actual[0]); + Assert.Equal(checked((sbyte)count), (sbyte)actual[1]); + + AssertExtensions.SequenceEqual(expected, actual.Slice(2)); + } + } + private static bool ReadTest(MemoryStream ms, Encoding encoding, ReaderWriterFactory.ReaderWriterType rwType, byte[] byteArray) { ms.Position = 0; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 5d04d15a81dc9b..6ad2debb088229 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -818,9 +818,9 @@ public static void SetByte(System.Array array, int index, byte value) { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out byte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out byte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out byte result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(byte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(byte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static byte System.Numerics.INumber.CopySign(byte value, byte sign) { throw null; } static byte System.Numerics.INumber.MaxNumber(byte x, byte y) { throw null; } static byte System.Numerics.INumber.MinNumber(byte x, byte y) { throw null; } @@ -1000,9 +1000,9 @@ public CannotUnloadAppDomainException(string? message, System.Exception? innerEx static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out char result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out char result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out char result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(char value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static bool System.Numerics.INumberBase.TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } static bool System.Numerics.INumberBase.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } static char System.Numerics.IShiftOperators.operator <<(char value, int shiftAmount) { throw null; } @@ -2016,9 +2016,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out decimal result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out decimal result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out decimal result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(decimal value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(decimal value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static decimal System.Numerics.INumber.MaxNumber(decimal x, decimal y) { throw null; } static decimal System.Numerics.INumber.MinNumber(decimal x, decimal y) { throw null; } void System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(object? sender) { } @@ -2266,9 +2266,9 @@ public DivideByZeroException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out double result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out double result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out double result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(double value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(double value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static double System.Numerics.ISubtractionOperators.operator -(double left, double right) { throw null; } static double System.Numerics.IUnaryNegationOperators.operator -(double value) { throw null; } static double System.Numerics.IUnaryPlusOperators.operator +(double value) { throw null; } @@ -2908,9 +2908,9 @@ public enum GCNotificationStatus static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Half result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Half result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Half result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Half value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Half value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } public static System.Half Tan(System.Half x) { throw null; } public static System.Half Tanh(System.Half x) { throw null; } public static System.Half TanPi(System.Half x) { throw null; } @@ -3209,9 +3209,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.Int128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.Int128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.Int128 result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Int128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.Int128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.Int128 System.Numerics.INumber.MaxNumber(System.Int128 x, System.Int128 y) { throw null; } static System.Int128 System.Numerics.INumber.MinNumber(System.Int128 x, System.Int128 y) { throw null; } public override string ToString() { throw null; } @@ -3334,9 +3334,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out short result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out short result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out short result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(short value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(short value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static short System.Numerics.INumber.MaxNumber(short x, short y) { throw null; } static short System.Numerics.INumber.MinNumber(short x, short y) { throw null; } static short System.Numerics.IShiftOperators.operator <<(short value, int shiftAmount) { throw null; } @@ -3467,9 +3467,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out int result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out int result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out int result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(int value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(int value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static int System.Numerics.INumber.MaxNumber(int x, int y) { throw null; } static int System.Numerics.INumber.MinNumber(int x, int y) { throw null; } static int System.Numerics.IShiftOperators.operator <<(int value, int shiftAmount) { throw null; } @@ -3600,9 +3600,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out long result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out long result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out long result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(long value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(long value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static long System.Numerics.INumber.MaxNumber(long x, long y) { throw null; } static long System.Numerics.INumber.MinNumber(long x, long y) { throw null; } static long System.Numerics.IShiftOperators.operator <<(long value, int shiftAmount) { throw null; } @@ -3735,9 +3735,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out nint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out nint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out nint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(nint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(nint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static nint System.Numerics.INumber.MaxNumber(nint x, nint y) { throw null; } static nint System.Numerics.INumber.MinNumber(nint x, nint y) { throw null; } static nint System.Numerics.IShiftOperators.operator <<(nint value, int shiftAmount) { throw null; } @@ -3804,7 +3804,7 @@ public partial interface IObserver void OnError(System.Exception error); void OnNext(T value); } - public partial interface IParsable where TSelf : System.IParsable + public partial interface IParsable where TSelf : System.IParsable? { static abstract TSelf Parse(string s, System.IFormatProvider? provider); static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result); @@ -3817,7 +3817,7 @@ public partial interface ISpanFormattable : System.IFormattable { bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider); } - public partial interface ISpanParsable : System.IParsable where TSelf : System.ISpanParsable + public partial interface ISpanParsable : System.IParsable where TSelf : System.ISpanParsable? { static abstract TSelf Parse(System.ReadOnlySpan s, System.IFormatProvider? provider); static abstract bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result); @@ -4664,9 +4664,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out sbyte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out sbyte result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out sbyte result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(sbyte value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(sbyte value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static sbyte System.Numerics.INumber.MaxNumber(sbyte x, sbyte y) { throw null; } static sbyte System.Numerics.INumber.MinNumber(sbyte x, sbyte y) { throw null; } static sbyte System.Numerics.IShiftOperators.operator <<(sbyte value, int shiftAmount) { throw null; } @@ -4863,9 +4863,9 @@ public SerializableAttribute() { } static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out float result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out float result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out float result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(float value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(float value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static float System.Numerics.ISubtractionOperators.operator -(float left, float right) { throw null; } static float System.Numerics.IUnaryNegationOperators.operator -(float value) { throw null; } static float System.Numerics.IUnaryPlusOperators.operator +(float value) { throw null; } @@ -6221,9 +6221,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out System.UInt128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out System.UInt128 result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out System.UInt128 result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(System.UInt128 value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static System.UInt128 System.Numerics.INumber.CopySign(System.UInt128 value, System.UInt128 sign) { throw null; } static System.UInt128 System.Numerics.INumber.MaxNumber(System.UInt128 x, System.UInt128 y) { throw null; } static System.UInt128 System.Numerics.INumber.MinNumber(System.UInt128 x, System.UInt128 y) { throw null; } @@ -6346,9 +6346,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out ushort result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out ushort result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out ushort result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(ushort value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(ushort value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static ushort System.Numerics.INumber.CopySign(ushort value, ushort sign) { throw null; } static ushort System.Numerics.INumber.MaxNumber(ushort x, ushort y) { throw null; } static ushort System.Numerics.INumber.MinNumber(ushort x, ushort y) { throw null; } @@ -6479,9 +6479,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out uint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out uint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out uint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(uint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(uint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static uint System.Numerics.INumber.CopySign(uint value, uint sign) { throw null; } static uint System.Numerics.INumber.MaxNumber(uint x, uint y) { throw null; } static uint System.Numerics.INumber.MinNumber(uint x, uint y) { throw null; } @@ -6612,9 +6612,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out ulong result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out ulong result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out ulong result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(ulong value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(ulong value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static ulong System.Numerics.INumber.CopySign(ulong value, ulong sign) { throw null; } static ulong System.Numerics.INumber.MaxNumber(ulong x, ulong y) { throw null; } static ulong System.Numerics.INumber.MinNumber(ulong x, ulong y) { throw null; } @@ -6744,9 +6744,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) static bool System.Numerics.INumberBase.TryConvertFromChecked(TOther value, out nuint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromSaturating(TOther value, out nuint result) { throw null; } static bool System.Numerics.INumberBase.TryConvertFromTruncating(TOther value, out nuint result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToChecked(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToSaturating(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } - static bool System.Numerics.INumberBase.TryConvertToTruncating(nuint value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToChecked(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToSaturating(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase.TryConvertToTruncating(nuint value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static nuint System.Numerics.INumber.CopySign(nuint value, nuint sign) { throw null; } static nuint System.Numerics.INumber.MaxNumber(nuint x, nuint y) { throw null; } static nuint System.Numerics.INumber.MinNumber(nuint x, nuint y) { throw null; } @@ -10218,19 +10218,19 @@ public static partial class BitOperations [System.CLSCompliantAttribute(false)] public static int TrailingZeroCount(nuint value) { throw null; } } - public partial interface IAdditionOperators where TSelf : System.Numerics.IAdditionOperators + public partial interface IAdditionOperators where TSelf : System.Numerics.IAdditionOperators? { static abstract TResult operator +(TSelf left, TOther right); static virtual TResult operator checked +(TSelf left, TOther right) { throw null; } } - public partial interface IAdditiveIdentity where TSelf : System.Numerics.IAdditiveIdentity + public partial interface IAdditiveIdentity where TSelf : System.Numerics.IAdditiveIdentity? { static abstract TResult AdditiveIdentity { get; } } - public partial interface IBinaryFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryFloatingPointIeee754 + public partial interface IBinaryFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryFloatingPointIeee754? { } - public partial interface IBinaryInteger : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryInteger + public partial interface IBinaryInteger : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryInteger? { static virtual (TSelf Quotient, TSelf Remainder) DivRem(TSelf left, TSelf right) { throw null; } int GetByteCount(); @@ -10257,42 +10257,42 @@ public partial interface IBinaryInteger : System.IComparable, System.ICom int WriteLittleEndian(byte[] destination, int startIndex) { throw null; } int WriteLittleEndian(System.Span destination) { throw null; } } - public partial interface IBinaryNumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryNumber + public partial interface IBinaryNumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IBinaryNumber? { static virtual TSelf AllBitsSet { get { throw null; } } static abstract bool IsPow2(TSelf value); static abstract TSelf Log2(TSelf value); } - public partial interface IBitwiseOperators where TSelf : System.Numerics.IBitwiseOperators + public partial interface IBitwiseOperators where TSelf : System.Numerics.IBitwiseOperators? { static abstract TResult operator &(TSelf left, TOther right); static abstract TResult operator |(TSelf left, TOther right); static abstract TResult operator ^(TSelf left, TOther right); static abstract TResult operator ~(TSelf value); } - public partial interface IComparisonOperators : System.Numerics.IEqualityOperators where TSelf : System.Numerics.IComparisonOperators + public partial interface IComparisonOperators : System.Numerics.IEqualityOperators where TSelf : System.Numerics.IComparisonOperators? { static abstract TResult operator >(TSelf left, TOther right); static abstract TResult operator >=(TSelf left, TOther right); static abstract TResult operator <(TSelf left, TOther right); static abstract TResult operator <=(TSelf left, TOther right); } - public partial interface IDecrementOperators where TSelf : System.Numerics.IDecrementOperators + public partial interface IDecrementOperators where TSelf : System.Numerics.IDecrementOperators? { static virtual TSelf operator checked --(TSelf value) { throw null; } static abstract TSelf operator --(TSelf value); } - public partial interface IDivisionOperators where TSelf : System.Numerics.IDivisionOperators + public partial interface IDivisionOperators where TSelf : System.Numerics.IDivisionOperators? { static virtual TResult operator checked /(TSelf left, TOther right) { throw null; } static abstract TResult operator /(TSelf left, TOther right); } - public partial interface IEqualityOperators where TSelf : System.Numerics.IEqualityOperators + public partial interface IEqualityOperators where TSelf : System.Numerics.IEqualityOperators? { - static abstract TResult operator ==(TSelf left, TOther right); - static abstract TResult operator !=(TSelf left, TOther right); + static abstract TResult operator ==(TSelf? left, TOther? right); + static abstract TResult operator !=(TSelf? left, TOther? right); } - public partial interface IExponentialFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IExponentialFunctions + public partial interface IExponentialFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IExponentialFunctions? { static abstract TSelf Exp(TSelf x); static abstract TSelf Exp10(TSelf x); @@ -10301,13 +10301,13 @@ public partial interface IExponentialFunctions : System.Numerics.IFloatin static virtual TSelf Exp2M1(TSelf x) { throw null; } static virtual TSelf ExpM1(TSelf x) { throw null; } } - public partial interface IFloatingPointConstants : System.Numerics.INumberBase where TSelf : System.Numerics.IFloatingPointConstants + public partial interface IFloatingPointConstants : System.Numerics.INumberBase where TSelf : System.Numerics.IFloatingPointConstants? { static abstract TSelf E { get; } static abstract TSelf Pi { get; } static abstract TSelf Tau { get; } } - public partial interface IFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPointIeee754 + public partial interface IFloatingPointIeee754 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPointIeee754? { static abstract TSelf Epsilon { get; } static abstract TSelf NaN { get; } @@ -10325,7 +10325,7 @@ public partial interface IFloatingPointIeee754 : System.IComparable, Syst static virtual TSelf ReciprocalSqrtEstimate(TSelf x) { throw null; } static abstract TSelf ScaleB(TSelf x, int n); } - public partial interface IFloatingPoint : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPoint + public partial interface IFloatingPoint : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.IFloatingPoint? { static virtual TSelf Ceiling(TSelf x) { throw null; } static virtual TSelf Floor(TSelf x) { throw null; } @@ -10355,7 +10355,7 @@ public partial interface IFloatingPoint : System.IComparable, System.ICom int WriteSignificandLittleEndian(byte[] destination, int startIndex) { throw null; } int WriteSignificandLittleEndian(System.Span destination) { throw null; } } - public partial interface IHyperbolicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IHyperbolicFunctions + public partial interface IHyperbolicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IHyperbolicFunctions? { static abstract TSelf Acosh(TSelf x); static abstract TSelf Asinh(TSelf x); @@ -10364,12 +10364,12 @@ public partial interface IHyperbolicFunctions : System.Numerics.IFloating static abstract TSelf Sinh(TSelf x); static abstract TSelf Tanh(TSelf x); } - public partial interface IIncrementOperators where TSelf : System.Numerics.IIncrementOperators + public partial interface IIncrementOperators where TSelf : System.Numerics.IIncrementOperators? { static virtual TSelf operator checked ++(TSelf value) { throw null; } static abstract TSelf operator ++(TSelf value); } - public partial interface ILogarithmicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ILogarithmicFunctions + public partial interface ILogarithmicFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ILogarithmicFunctions? { static abstract TSelf Log(TSelf x); static abstract TSelf Log(TSelf x, TSelf newBase); @@ -10379,33 +10379,42 @@ public partial interface ILogarithmicFunctions : System.Numerics.IFloatin static virtual TSelf Log2P1(TSelf x) { throw null; } static virtual TSelf LogP1(TSelf x) { throw null; } } - public partial interface IMinMaxValue where TSelf : System.Numerics.IMinMaxValue + public partial interface IMinMaxValue where TSelf : System.Numerics.IMinMaxValue? { static abstract TSelf MaxValue { get; } static abstract TSelf MinValue { get; } } - public partial interface IModulusOperators where TSelf : System.Numerics.IModulusOperators + public partial interface IModulusOperators where TSelf : System.Numerics.IModulusOperators? { static abstract TResult operator %(TSelf left, TOther right); } - public partial interface IMultiplicativeIdentity where TSelf : System.Numerics.IMultiplicativeIdentity + public partial interface IMultiplicativeIdentity where TSelf : System.Numerics.IMultiplicativeIdentity? { static abstract TResult MultiplicativeIdentity { get; } } - public partial interface IMultiplyOperators where TSelf : System.Numerics.IMultiplyOperators + public partial interface IMultiplyOperators where TSelf : System.Numerics.IMultiplyOperators? { static virtual TResult operator checked *(TSelf left, TOther right) { throw null; } static abstract TResult operator *(TSelf left, TOther right); } - public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumberBase + public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumberBase? { static abstract TSelf One { get; } static abstract int Radix { get; } static abstract TSelf Zero { get; } static abstract TSelf Abs(TSelf value); - static virtual TSelf CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } - static virtual TSelf CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } - static virtual TSelf CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } + static virtual TSelf CreateChecked(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore + static virtual TSelf CreateSaturating(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore + static virtual TSelf CreateTruncating(TOther value) +#nullable disable + where TOther : System.Numerics.INumberBase { throw null; } +#nullable restore static abstract bool IsCanonical(TSelf value); static abstract bool IsComplexNumber(TSelf value); static abstract bool IsEvenInteger(TSelf value); @@ -10429,16 +10438,34 @@ public partial interface INumberBase : System.IEquatable, System.I static abstract TSelf MinMagnitudeNumber(TSelf x, TSelf y); static abstract TSelf Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); - protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertFromSaturating(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertFromTruncating(TOther value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TSelf? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToChecked(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToSaturating(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; - protected static abstract bool TryConvertToTruncating(TSelf value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out TOther? result) where TOther : System.Numerics.INumberBase; + protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertFromSaturating(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertFromTruncating(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToChecked(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToSaturating(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore + protected static abstract bool TryConvertToTruncating(TSelf value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TOther result) +#nullable disable + where TOther : System.Numerics.INumberBase; +#nullable restore static abstract bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out TSelf result); static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out TSelf result); } - public partial interface INumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumber + public partial interface INumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumber? { static virtual TSelf Clamp(TSelf value, TSelf min, TSelf max) { throw null; } static virtual TSelf CopySign(TSelf value, TSelf sign) { throw null; } @@ -10448,33 +10475,33 @@ public partial interface INumber : System.IComparable, System.IComparable static virtual TSelf MinNumber(TSelf x, TSelf y) { throw null; } static virtual int Sign(TSelf value) { throw null; } } - public partial interface IPowerFunctions : System.Numerics.INumberBase where TSelf : System.Numerics.IPowerFunctions + public partial interface IPowerFunctions : System.Numerics.INumberBase where TSelf : System.Numerics.IPowerFunctions? { static abstract TSelf Pow(TSelf x, TSelf y); } - public partial interface IRootFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IRootFunctions + public partial interface IRootFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.IRootFunctions? { static abstract TSelf Cbrt(TSelf x); static abstract TSelf Hypot(TSelf x, TSelf y); static abstract TSelf RootN(TSelf x, int n); static abstract TSelf Sqrt(TSelf x); } - public partial interface IShiftOperators where TSelf : System.Numerics.IShiftOperators + public partial interface IShiftOperators where TSelf : System.Numerics.IShiftOperators? { static abstract TResult operator <<(TSelf value, TOther shiftAmount); static abstract TResult operator >>(TSelf value, TOther shiftAmount); static abstract TResult operator >>>(TSelf value, TOther shiftAmount); } - public partial interface ISignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.ISignedNumber + public partial interface ISignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.ISignedNumber? { static abstract TSelf NegativeOne { get; } } - public partial interface ISubtractionOperators where TSelf : System.Numerics.ISubtractionOperators + public partial interface ISubtractionOperators where TSelf : System.Numerics.ISubtractionOperators? { static virtual TResult operator checked -(TSelf left, TOther right) { throw null; } static abstract TResult operator -(TSelf left, TOther right); } - public partial interface ITrigonometricFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ITrigonometricFunctions + public partial interface ITrigonometricFunctions : System.Numerics.IFloatingPointConstants, System.Numerics.INumberBase where TSelf : System.Numerics.ITrigonometricFunctions? { static abstract TSelf Acos(TSelf x); static abstract TSelf AcosPi(TSelf x); @@ -10491,16 +10518,16 @@ public partial interface ITrigonometricFunctions : System.Numerics.IFloat static abstract TSelf Tan(TSelf x); static abstract TSelf TanPi(TSelf x); } - public partial interface IUnaryNegationOperators where TSelf : System.Numerics.IUnaryNegationOperators + public partial interface IUnaryNegationOperators where TSelf : System.Numerics.IUnaryNegationOperators? { static virtual TResult operator checked -(TSelf value) { throw null; } static abstract TResult operator -(TSelf value); } - public partial interface IUnaryPlusOperators where TSelf : System.Numerics.IUnaryPlusOperators + public partial interface IUnaryPlusOperators where TSelf : System.Numerics.IUnaryPlusOperators? { static abstract TResult operator +(TSelf value); } - public partial interface IUnsignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.IUnsignedNumber + public partial interface IUnsignedNumber : System.Numerics.INumberBase where TSelf : System.Numerics.IUnsignedNumber? { } } diff --git a/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj b/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj index b93223c66887c6..849edc8cd5c2c1 100644 --- a/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj +++ b/src/libraries/System.Runtime/tests/NlsTests/System.Runtime.Nls.Tests.csproj @@ -6,6 +6,9 @@ $(NetCoreAppCurrent)-windows $(NoWarn),SYSLIB0013 + + + diff --git a/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json b/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json deleted file mode 100644 index f93c6039127bd1..00000000000000 --- a/src/libraries/System.Runtime/tests/NlsTests/runtimeconfig.template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "configProperties": { - "System.Globalization.UseNls": true - } -} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index accc11d4277ca8..fc6859854dc2e6 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -111,6 +111,8 @@ + + diff --git a/src/libraries/System.Runtime/tests/System/Attributes.cs b/src/libraries/System.Runtime/tests/System/Attributes.cs index d0b474f3110821..0b75b48ea133e7 100644 --- a/src/libraries/System.Runtime/tests/System/Attributes.cs +++ b/src/libraries/System.Runtime/tests/System/Attributes.cs @@ -331,10 +331,11 @@ public static void customAttributeCount() { List customAttributes = typeof(GetCustomAttribute).Module.CustomAttributes.ToList(); // [System.Security.UnverifiableCodeAttribute()] + // [System.Runtime.CompilerServices.RefSafetyRulesAttribute((Int32)11)] // [TestAttributes.FooAttribute()] // [TestAttributes.ComplicatedAttribute((Int32)1, Stuff = 2)] // [System.Diagnostics.DebuggableAttribute((Boolean)True, (Boolean)False)] - Assert.Equal(4, customAttributes.Count); + Assert.Equal(5, customAttributes.Count); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System/Int128Tests.cs b/src/libraries/System.Runtime/tests/System/Int128Tests.cs index 15f1a39a02396a..036872f0ddc1a2 100644 --- a/src/libraries/System.Runtime/tests/System/Int128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int128Tests.cs @@ -116,6 +116,12 @@ public static IEnumerable ToString_TestData() yield return new object[] { (Int128)(-4567), defaultSpecifier, defaultFormat, "-4567" }; yield return new object[] { (Int128)0, defaultSpecifier, defaultFormat, "0" }; yield return new object[] { (Int128)4567, defaultSpecifier, defaultFormat, "4567" }; + yield return new object[] { new Int128(0x0000_0000_0000_0001, 0x0000_0000_0000_0003), defaultSpecifier, defaultFormat, "18446744073709551619" }; + yield return new object[] { new Int128(0x0000_0000_0000_0001, 0x0000_0000_0000_000A), defaultSpecifier, defaultFormat, "18446744073709551626" }; + yield return new object[] { new Int128(0x0000_0000_0000_0005, 0x0000_0000_0000_0001), defaultSpecifier, defaultFormat, "92233720368547758081" }; + yield return new object[] { new Int128(0x0000_0000_0000_0005, 0x6BC7_5E2D_6310_0000), defaultSpecifier, defaultFormat, "100000000000000000000" }; + yield return new object[] { new Int128(0x0000_0000_0000_0036, 0x35C9_ADC5_DEA0_0000), defaultSpecifier, defaultFormat, "1000000000000000000000" }; + yield return new object[] { new Int128(0x0013_4261_72C7_4D82, 0x2B87_8FE8_0000_0000), defaultSpecifier, defaultFormat, "100000000000000000000000000000000000" }; yield return new object[] { Int128.MaxValue, defaultSpecifier, defaultFormat, "170141183460469231731687303715884105727" }; } diff --git a/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs b/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs index d6b34be3114410..058bba1d255b5c 100644 --- a/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs +++ b/src/libraries/System.Runtime/tests/System/Numerics/GenericMathDimHelpers.cs @@ -60,9 +60,9 @@ private BinaryNumberDimHelper(int value) static BinaryNumberDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); static BinaryNumberDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); static BinaryNumberDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); - static bool INumberBase.TryConvertToChecked(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); - static bool INumberBase.TryConvertToSaturating(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); - static bool INumberBase.TryConvertToTruncating(BinaryNumberDimHelper value, out TOther? result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(BinaryNumberDimHelper value, out TOther result) where TOther : default => throw new NotImplementedException(); static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out BinaryNumberDimHelper result) => throw new NotImplementedException(); @@ -96,4 +96,176 @@ private BinaryNumberDimHelper(int value) static bool IComparisonOperators.operator >=(BinaryNumberDimHelper left, BinaryNumberDimHelper right) => throw new NotImplementedException(); } + public struct FloatingPointDimHelper : IFloatingPoint + { + public float Value; + + public FloatingPointDimHelper(float value) + { + Value = value; + } + + static FloatingPointDimHelper IFloatingPoint.Round(FloatingPointDimHelper x, int digits, MidpointRounding mode) + => new FloatingPointDimHelper(float.Round(x.Value, digits, mode)); + + // + // The below are all not used for existing Dim tests, so they stay unimplemented + // + + static FloatingPointDimHelper IFloatingPointConstants.E => throw new NotImplementedException(); + static FloatingPointDimHelper IFloatingPointConstants.Pi => throw new NotImplementedException(); + static FloatingPointDimHelper IFloatingPointConstants.Tau => throw new NotImplementedException(); + static FloatingPointDimHelper ISignedNumber.NegativeOne => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.One => throw new NotImplementedException(); + static int INumberBase.Radix => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Zero => throw new NotImplementedException(); + static FloatingPointDimHelper IAdditiveIdentity.AdditiveIdentity => throw new NotImplementedException(); + static FloatingPointDimHelper IMultiplicativeIdentity.MultiplicativeIdentity => throw new NotImplementedException(); + + static FloatingPointDimHelper INumberBase.Abs(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsCanonical(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsComplexNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsEvenInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsFinite(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsImaginaryNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNaN(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegative(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegativeInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNormal(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsOddInteger(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositive(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositiveInfinity(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsRealNumber(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsSubnormal(FloatingPointDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsZero(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MaxMagnitude(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MaxMagnitudeNumber(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MinMagnitude(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.MinMagnitudeNumber(FloatingPointDimHelper x, FloatingPointDimHelper y) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); + static FloatingPointDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); + + static bool INumberBase.TryConvertFromChecked(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromSaturating(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromTruncating(TOther value, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(FloatingPointDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + static bool IParsable.TryParse(string? s, IFormatProvider? provider, out FloatingPointDimHelper result) => throw new NotImplementedException(); + int IComparable.CompareTo(object? obj) => throw new NotImplementedException(); + int IComparable.CompareTo(FloatingPointDimHelper other) => throw new NotImplementedException(); + bool IEquatable.Equals(FloatingPointDimHelper other) => throw new NotImplementedException(); + int IFloatingPoint.GetExponentByteCount() => throw new NotImplementedException(); + int IFloatingPoint.GetExponentShortestBitLength() => throw new NotImplementedException(); + int IFloatingPoint.GetSignificandBitLength() => throw new NotImplementedException(); + int IFloatingPoint.GetSignificandByteCount() => throw new NotImplementedException(); + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => throw new NotImplementedException(); + bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten) => throw new NotImplementedException(); + + static FloatingPointDimHelper IUnaryPlusOperators.operator +(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IAdditionOperators.operator +(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IUnaryNegationOperators.operator -(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper ISubtractionOperators.operator -(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IIncrementOperators.operator ++(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IDecrementOperators.operator --(FloatingPointDimHelper value) => throw new NotImplementedException(); + static FloatingPointDimHelper IMultiplyOperators.operator *(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IDivisionOperators.operator /(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static FloatingPointDimHelper IModulusOperators.operator %(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator ==(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator !=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator <(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator >(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator <=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + static bool IComparisonOperators.operator >=(FloatingPointDimHelper left, FloatingPointDimHelper right) => throw new NotImplementedException(); + } + + public struct ExponentialFunctionsDimHelper : IExponentialFunctions + { + public float Value; + + public ExponentialFunctionsDimHelper(float value) + { + Value = value; + } + + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp10(ExponentialFunctionsDimHelper x) => new ExponentialFunctionsDimHelper(float.Exp10(x.Value)); + static ExponentialFunctionsDimHelper INumberBase.One => new ExponentialFunctionsDimHelper(1f); + static ExponentialFunctionsDimHelper ISubtractionOperators.operator -(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) + => new ExponentialFunctionsDimHelper(left.Value - right.Value); + + // + // The below are all not used for existing Dim tests, so they stay unimplemented + // + + static ExponentialFunctionsDimHelper IFloatingPointConstants.E => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IFloatingPointConstants.Pi => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IFloatingPointConstants.Tau => throw new NotImplementedException(); + static int INumberBase.Radix => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Zero => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IAdditiveIdentity.AdditiveIdentity => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IMultiplicativeIdentity.MultiplicativeIdentity => throw new NotImplementedException(); + + static ExponentialFunctionsDimHelper INumberBase.Abs(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp(ExponentialFunctionsDimHelper x) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IExponentialFunctions.Exp2(ExponentialFunctionsDimHelper x) => throw new NotImplementedException(); + static bool INumberBase.IsCanonical(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsComplexNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsEvenInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsFinite(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsImaginaryNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNaN(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegative(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNegativeInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsNormal(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsOddInteger(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositive(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsPositiveInfinity(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsRealNumber(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsSubnormal(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static bool INumberBase.IsZero(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MaxMagnitude(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MaxMagnitudeNumber(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MinMagnitude(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.MinMagnitudeNumber(ExponentialFunctionsDimHelper x, ExponentialFunctionsDimHelper y) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper INumberBase.Parse(string s, NumberStyles style, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper ISpanParsable.Parse(ReadOnlySpan s, IFormatProvider? provider) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IParsable.Parse(string s, IFormatProvider? provider) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromChecked(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromSaturating(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertFromTruncating(TOther value, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToChecked(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToSaturating(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryConvertToTruncating(ExponentialFunctionsDimHelper value, out TOther result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool INumberBase.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool ISpanParsable.TryParse(ReadOnlySpan s, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + static bool IParsable.TryParse(string? s, IFormatProvider? provider, out ExponentialFunctionsDimHelper result) => throw new NotImplementedException(); + bool IEquatable.Equals(ExponentialFunctionsDimHelper other) => throw new NotImplementedException(); + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => throw new NotImplementedException(); + bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => throw new NotImplementedException(); + + static ExponentialFunctionsDimHelper IUnaryPlusOperators.operator +(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IAdditionOperators.operator +(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IUnaryNegationOperators.operator -(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IIncrementOperators.operator ++(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IDecrementOperators.operator --(ExponentialFunctionsDimHelper value) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IMultiplyOperators.operator *(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static ExponentialFunctionsDimHelper IDivisionOperators.operator /(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator ==(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + static bool IEqualityOperators.operator !=(ExponentialFunctionsDimHelper left, ExponentialFunctionsDimHelper right) => throw new NotImplementedException(); + } } diff --git a/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs b/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs new file mode 100644 index 00000000000000..6c2096cf88d77c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Numerics/IExponentialFunctionsTests.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Numerics.Tests +{ + public sealed class IExponentialFunctionsTests + { + const float baseValue = 2.1f; + static readonly ExponentialFunctionsDimHelper helperValue = new ExponentialFunctionsDimHelper(baseValue); + + [Fact] + public static void Exp10M1Test() + { + Assert.Equal(float.Exp10M1(baseValue), ExponentialFunctionsHelper.Exp10M1(helperValue).Value); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs b/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs new file mode 100644 index 00000000000000..6085d5b29d0e5a --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Numerics/IFloatingPointTests.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Numerics.Tests +{ + public sealed class IFloatingPointTests + { + const float baseValue = 5.5f; + static readonly FloatingPointDimHelper helperValue = new FloatingPointDimHelper(baseValue); + + [Fact] + public static void AwayFromZeroRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.AwayFromZero), FloatingPointHelper.Round(helperValue, MidpointRounding.AwayFromZero).Value); + } + + [Fact] + public static void ToEvenRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToEven), FloatingPointHelper.Round(helperValue, MidpointRounding.ToEven).Value); + } + + [Fact] + public static void ToNegativeInfinityRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToNegativeInfinity), FloatingPointHelper.Round(helperValue, MidpointRounding.ToNegativeInfinity).Value); + } + + [Fact] + public static void ToPositiveRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToPositiveInfinity), FloatingPointHelper.Round(helperValue, MidpointRounding.ToPositiveInfinity).Value); + } + + [Fact] + public static void ToZeroRoundingTest() + { + Assert.Equal(float.Round(baseValue, MidpointRounding.ToZero), FloatingPointHelper.Round(helperValue, MidpointRounding.ToZero).Value); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs index 469e3cfd60b823..8aa28ad66522c3 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/ControlledExecutionTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; +using System.Threading.Tasks; using Xunit; // Disable warnings for ControlledExecution.Run @@ -9,208 +10,312 @@ namespace System.Runtime.Tests { - public class ControlledExecutionTests + public sealed class ControlledExecutionTests { - private bool _startedExecution, _caughtException, _finishedExecution; + private volatile bool _readyForCancellation; + private bool _caughtException, _finishedExecution; private Exception _exception; - private CancellationTokenSource _cts; private volatile int _counter; - // Tests cancellation on timeout. The ThreadAbortException must be mapped to OperationCanceledException. + // Tests that the Run method finishes normally if no cancellation is requested [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)] - public void CancelOnTimeout() + public void RunWithoutCancelling() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction, cts.Token); + RunTest(Test, cts.Token); + + Assert.True(_finishedExecution); + Assert.Null(_exception); + + void Test() + { + _finishedExecution = true; + } + } + + // Tests that a nested invocation of the Run method throws an InvalidOperationException + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void TestNestedRunInvocation() + { + bool nestedExecution = false; + + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); + + Assert.False(nestedExecution); + Assert.IsType(_exception); + + void Test() + { + ControlledExecution.Run(() => nestedExecution = true, cts.Token); + } + } + + // Tests that an infinite loop may be aborted and that the ThreadAbortException is translated + // to an OperationCanceledException. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelOutsideOfTryCatchFinally() + { + var cts = new CancellationTokenSource(); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); - Assert.True(_caughtException); Assert.False(_finishedExecution); Assert.IsType(_exception); + + void Test() + { + _readyForCancellation = true; + RunInfiniteLoop(); + _finishedExecution = true; + } } - // Tests that catch blocks are not aborted. The action catches the ThreadAbortException and throws an exception of a different type. + // Tests that an infinite loop may be aborted, that the ThreadAbortException is automatically rethrown, + // and that it is eventually translated to an OperationCanceledException. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_ThrowFromCatch() + public void CancelInTryAndExitCatchNormally() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_ThrowFromCatch, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.True(_caughtException); Assert.False(_finishedExecution); - Assert.IsType(_exception); + Assert.IsType(_exception); + + void Test() + { + try + { + _readyForCancellation = true; + RunInfiniteLoop(); + } + catch + { + // Swallow all exceptions to verify that the ThreadAbortException is automatically rethrown + _caughtException = true; + } + + if (!PlatformDetection.IsWindows) + { + // Rethrowing a ThreadAbortException at the end of catch blocks is not implemented, so force it + // here by calling native code (https://github.com/dotnet/runtime/issues/72703). + Thread.Sleep(0); + } + + _finishedExecution = true; + } } - // Tests that finally blocks are not aborted. The action throws an exception from a finally block. + // Tests that catch blocks are not aborted. The catch block swallows the ThreadAbortException and throws a different exception. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_ThrowFromFinally() + public void CancelInTryAndThrowFromCatch() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_ThrowFromFinally, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); + + Assert.True(_caughtException); + Assert.IsType(_exception); - Assert.True(_startedExecution); - Assert.IsType(_exception); + void Test() + { + try + { + _readyForCancellation = true; + RunInfiniteLoop(); + } + catch + { + _caughtException = true; + // The catch block must not be aborted + Thread.Sleep(200); + throw new TestException(); + } + } } - // Tests that finally blocks are not aborted. The action throws an exception from a try block. + // Tests that finally blocks are not aborted. The finally block exits normally. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelOnTimeout_Finally() + public void CancelInFinallyThatSleeps() { var cts = new CancellationTokenSource(); - cts.CancelAfter(200); - RunTest(LengthyAction_Finally, cts.Token); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.True(_finishedExecution); - Assert.IsType(_exception); + Assert.IsType(_exception); + + void Test() + { + try + { + // Make sure to run the non-inlined finally + throw new TestException(); + } + finally + { + _readyForCancellation = true; + WaitUntilAbortIsRequested(); + // The finally block must not be aborted + Thread.Sleep(200); + _finishedExecution = true; + } + } + } + + // Tests that finally blocks are not aborted. The finally block throws an exception. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelInFinallyThatSleepsAndThrows() + { + var cts = new CancellationTokenSource(); + Task.Run(() => CancelWhenTestIsReady(cts)); + RunTest(Test, cts.Token); + + Assert.IsType(_exception); + + void Test() + { + try + { + // Make sure to run the non-inlined finally + throw new Exception(); + } + finally + { + _readyForCancellation = true; + WaitUntilAbortIsRequested(); + // The finally block must not be aborted + Thread.Sleep(200); + throw new TestException(); + } + } } - // Tests cancellation before calling the Run method + // Tests cancellation before calling the Run method. The action must never start. [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] public void CancelBeforeRun() { var cts = new CancellationTokenSource(); cts.Cancel(); - Thread.Sleep(100); - RunTest(LengthyAction, cts.Token); + RunTest(Test, cts.Token); + Assert.False(_finishedExecution); Assert.IsType(_exception); + + void Test() + { + _finishedExecution = true; + } } // Tests cancellation by the action itself [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] - public void CancelItself() + public void CancelItselfOutsideOfTryCatchFinally() { - _cts = new CancellationTokenSource(); - RunTest(Action_CancelItself, _cts.Token); + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); - Assert.True(_startedExecution); Assert.False(_finishedExecution); - Assert.IsType(_exception); - Assert.IsType(_exception.InnerException); - } - - private void RunTest(Action action, CancellationToken cancellationToken) - { - _startedExecution = _caughtException = _finishedExecution = false; - _exception = null; + // CancellationTokenSource.Cancel catches the ThreadAbortException; however, it is rethrown at the end + // of the catch block. + Assert.IsType(_exception); - try + void Test() { - ControlledExecution.Run(action, cancellationToken); - } - catch (Exception e) - { - _exception = e; + cts.Cancel(); + _finishedExecution = true; } } - private void LengthyAction() + // Tests cancellation by the action itself. Finally blocks must be executed except the one that triggered cancellation. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))] + public void CancelItselfFromFinally() { - _startedExecution = true; - // Redirection via thread suspension is supported on Windows only. - // Make a call in the loop to allow redirection on other platforms. - bool sleep = !PlatformDetection.IsWindows; + bool finishedContainingFinally = false; - try + var cts = new CancellationTokenSource(); + RunTest(Test, cts.Token); + + Assert.False(finishedContainingFinally); + Assert.True(_finishedExecution); + // CancellationTokenSource.Cancel catches the ThreadAbortException and wraps it into an AggregateException + // at the end of the method's execution. The ThreadAbortException is not rethrown at the end of the catch + // block, because the Cancel method is called from a finally block. + Assert.IsType(_exception); + Assert.IsType(_exception.InnerException); + + void Test() { - for (_counter = 0; _counter < int.MaxValue; _counter++) + try { - if ((_counter & 0xfffff) == 0 && sleep) + try + { + // Make sure to run the non-inlined finally + throw new Exception(); + } + finally { - Thread.Sleep(0); + // When cancelling itself, the containing finally block must be aborted + cts.Cancel(); + finishedContainingFinally = true; } } + finally + { + _finishedExecution = true; + } } - catch - { - // Swallow all exceptions to verify that the exception is automatically rethrown - _caughtException = true; - } - - _finishedExecution = true; } - private void LengthyAction_ThrowFromCatch() + private void RunTest(Action action, CancellationToken cancellationToken) { - _startedExecution = true; - bool sleep = !PlatformDetection.IsWindows; + _readyForCancellation = _caughtException = _finishedExecution = false; + _exception = null; + _counter = 0; try { - for (_counter = 0; _counter < int.MaxValue; _counter++) - { - if ((_counter & 0xfffff) == 0 && sleep) - { - Thread.Sleep(0); - } - } + ControlledExecution.Run(action, cancellationToken); } - catch + catch (Exception e) { - _caughtException = true; - // The catch block must not be aborted - Thread.Sleep(100); - throw new TimeoutException(); + _exception = e; } - - _finishedExecution = true; } - private void LengthyAction_ThrowFromFinally() + private void CancelWhenTestIsReady(CancellationTokenSource cancellationTokenSource) { - _startedExecution = true; - - try + // Wait until the execution is ready to be canceled + while (!_readyForCancellation) { - // Make sure to run the non-inlined finally - throw new Exception(); - } - finally - { - // The finally block must not be aborted - Thread.Sleep(400); - throw new TimeoutException(); + Thread.Sleep(10); } + cancellationTokenSource.Cancel(); } - private void LengthyAction_Finally() + private static void WaitUntilAbortIsRequested() { - _startedExecution = true; - - try - { - // Make sure to run the non-inlined finally - throw new TimeoutException(); - } - finally + while ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) == 0) { - // The finally block must not be aborted - Thread.Sleep(400); - _finishedExecution = true; + Thread.Sleep(10); } } - private void Action_CancelItself() + private void RunInfiniteLoop() { - _startedExecution = true; - - try + while (true) { - // Make sure to run the non-inlined finally - throw new TimeoutException(); - } - finally - { - // The finally block must be aborted - _cts.Cancel(); - _finishedExecution = true; + if ((++_counter & 0xfffff) == 0) + { + Thread.Sleep(0); + } } } + + private sealed class TestException : Exception + { + } } } diff --git a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs index a02f205e72fa84..a255a0444960d8 100644 --- a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs @@ -101,6 +101,14 @@ public static IEnumerable ToString_TestData() { yield return new object[] { (UInt128)0, defaultSpecifier, defaultFormat, "0" }; yield return new object[] { (UInt128)4567, defaultSpecifier, defaultFormat, "4567" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0001, 0x0000_0000_0000_0003), defaultSpecifier, defaultFormat, "18446744073709551619" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0001, 0x0000_0000_0000_000A), defaultSpecifier, defaultFormat, "18446744073709551626" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0005, 0x0000_0000_0000_0001), defaultSpecifier, defaultFormat, "92233720368547758081" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0005, 0x6BC7_5E2D_6310_0000), defaultSpecifier, defaultFormat, "100000000000000000000" }; + yield return new object[] { new UInt128(0x0000_0000_0000_0036, 0x35C9_ADC5_DEA0_0000), defaultSpecifier, defaultFormat, "1000000000000000000000" }; + yield return new object[] { new UInt128(0x0013_4261_72C7_4D82, 0x2B87_8FE8_0000_0000), defaultSpecifier, defaultFormat, "100000000000000000000000000000000000" }; + yield return new object[] { new UInt128(0x7FFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF), defaultSpecifier, defaultFormat, "170141183460469231731687303715884105727" }; + yield return new object[] { new UInt128(0x8000_0000_0000_0000, 0x0000_0000_0000_0000), defaultSpecifier, defaultFormat, "170141183460469231731687303715884105728" }; yield return new object[] { UInt128.MaxValue, defaultSpecifier, defaultFormat, "340282366920938463463374607431768211455" }; } diff --git a/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs b/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs index 54f00d1154c754..8841cf2f821f00 100644 --- a/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs +++ b/src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs @@ -1593,6 +1593,7 @@ public void VerifyHMAC_HMACOutputLength_Invalid() [Theory] [InlineData(false)] [InlineData(true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/74115")] public void VerifyXmlResolver(bool provideResolver) { HttpListener listener; diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 8fa22de309915e..5be736fec669ef 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -47,6 +47,7 @@ namespace System.Security.Cryptography public abstract partial class Aes : System.Security.Cryptography.SymmetricAlgorithm { protected Aes() { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static new System.Security.Cryptography.Aes Create() { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.ObsoleteAttribute("Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.", DiagnosticId="SYSLIB0045", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] @@ -98,6 +99,7 @@ public override void GenerateKey() { } [System.ObsoleteAttribute("Derived cryptographic types are obsolete. Use the Create method on the base type instead.", DiagnosticId="SYSLIB0021", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] public sealed partial class AesCryptoServiceProvider : System.Security.Cryptography.Aes { + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public AesCryptoServiceProvider() { } public override int BlockSize { get { throw null; } set { } } public override int FeedbackSize { get { throw null; } set { } } @@ -134,6 +136,7 @@ public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] ta } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("Derived cryptographic types are obsolete. Use the Create method on the base type instead.", DiagnosticId="SYSLIB0021", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class AesManaged : System.Security.Cryptography.Aes { public AesManaged() { } @@ -1781,6 +1784,7 @@ public override void Reset() { } public abstract partial class Rijndael : System.Security.Cryptography.SymmetricAlgorithm { protected Rijndael() { } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static new System.Security.Cryptography.Rijndael Create() { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.ObsoleteAttribute("Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.", DiagnosticId="SYSLIB0045", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] @@ -1788,6 +1792,7 @@ protected Rijndael() { } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.", DiagnosticId="SYSLIB0022", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class RijndaelManaged : System.Security.Cryptography.Rijndael { public RijndaelManaged() { } diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index 3cc428cfbeed11..4dc40bba0b1b7a 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -822,10 +822,4 @@ Unknown error. - - SubtleCrypto returned an unknown error: '{0}'. - - - Only CipherMode.CBC is supported on this platform. - diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 986ea2c977f87d..cf25738f6e7bab 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -556,13 +556,9 @@ Link="Common\Interop\Browser\Interop.Libraries.cs" /> - - - - + @@ -576,7 +572,6 @@ - @@ -588,7 +583,6 @@ - @@ -597,9 +591,6 @@ - - - diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs index 767f97820fe267..e00abeeac2d7a8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Aes.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; using Internal.Cryptography; namespace System.Security.Cryptography @@ -11,7 +12,7 @@ public abstract class Aes : SymmetricAlgorithm protected Aes() { LegalBlockSizesValue = s_legalBlockSizes.CloneKeySizesArray(); - LegalKeySizesValue = AesImplementation.s_legalKeySizes.CloneKeySizesArray(); + LegalKeySizesValue = s_legalKeySizes.CloneKeySizesArray(); BlockSizeValue = 128; FeedbackSizeValue = 8; @@ -19,6 +20,7 @@ protected Aes() ModeValue = CipherMode.CBC; } + [UnsupportedOSPlatform("browser")] public static new Aes Create() { return new AesImplementation(); @@ -32,5 +34,6 @@ protected Aes() } private static readonly KeySizes[] s_legalBlockSizes = { new KeySizes(128, 128, 0) }; + private static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) }; } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesCryptoServiceProvider.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesCryptoServiceProvider.cs index c879a4f5460870..2e081e8a064939 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesCryptoServiceProvider.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesCryptoServiceProvider.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Runtime.Versioning; namespace System.Security.Cryptography { @@ -11,6 +12,7 @@ public sealed class AesCryptoServiceProvider : Aes { private readonly Aes _impl; + [UnsupportedOSPlatform("browser")] public AesCryptoServiceProvider() { // This class wraps Aes diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs deleted file mode 100644 index 3523b919f71e28..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Browser.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Security.Cryptography -{ - internal sealed partial class AesImplementation - { - internal const int BlockSizeBytes = 16; // 128 bits - - // SubtleCrypto doesn't support AES-192. http://crbug.com/533699 - internal static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 128) }; - - private static UniversalCryptoTransform CreateTransformCore( - CipherMode cipherMode, - PaddingMode paddingMode, - byte[] key, - byte[]? iv, - int blockSize, - int paddingSize, - int feedbackSize, - bool encrypting) - { - ValidateCipherMode(cipherMode); - if (iv is null) - throw new CryptographicException(SR.Cryptography_MissingIV); - - Debug.Assert(blockSize == BlockSizeBytes); - Debug.Assert(paddingSize == blockSize); - - BasicSymmetricCipher cipher = Interop.BrowserCrypto.CanUseSubtleCrypto ? - new AesSubtleCryptoTransform(key, iv, encrypting) : - new AesManagedTransform(key, iv, encrypting); - - return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting); - } - - private static ILiteSymmetricCipher CreateLiteCipher( - CipherMode cipherMode, - ReadOnlySpan key, - ReadOnlySpan iv, - int blockSize, - int paddingSize, - int feedbackSize, - bool encrypting) - { - ValidateCipherMode(cipherMode); - - Debug.Assert(blockSize == BlockSizeBytes); - Debug.Assert(paddingSize == blockSize); - - return Interop.BrowserCrypto.CanUseSubtleCrypto ? - new AesSubtleCryptoTransform(key, iv, encrypting) : - new AesManagedTransform(key, iv, encrypting); - } - - private static void ValidateCipherMode(CipherMode cipherMode) - { - if (cipherMode != CipherMode.CBC) - throw new PlatformNotSupportedException(SR.PlatformNotSupported_CipherModeBrowser); - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs deleted file mode 100644 index 78b74ac82abe75..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NonBrowser.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Security.Cryptography -{ - internal sealed partial class AesImplementation - { - internal static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) }; - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.cs new file mode 100644 index 00000000000000..86483ca199806c --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.NotSupported.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal sealed partial class AesImplementation : Aes + { + private static UniversalCryptoTransform CreateTransformCore( + CipherMode cipherMode, + PaddingMode paddingMode, + byte[] key, + byte[]? iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(Aes))); + } + + private static ILiteSymmetricCipher CreateLiteCipher( + CipherMode cipherMode, + ReadOnlySpan key, + ReadOnlySpan iv, + int blockSize, + int paddingSize, + int feedback, + bool encrypting) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(Aes))); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs index 3791b5a87208e8..dc623710517336 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManaged.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Runtime.Versioning; namespace System.Security.Cryptography { [Obsolete(Obsoletions.DerivedCryptographicTypesMessage, DiagnosticId = Obsoletions.DerivedCryptographicTypesDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] + [UnsupportedOSPlatform("browser")] public sealed class AesManaged : Aes { private readonly Aes _impl; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs deleted file mode 100644 index 0ae66acc18e571..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesManagedTransform.Browser.cs +++ /dev/null @@ -1,1015 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace System.Security.Cryptography -{ - internal sealed class AesManagedTransform : BasicSymmetricCipher, ILiteSymmetricCipher - { - private const int BlockSizeBytes = AesImplementation.BlockSizeBytes; - private const int BlockSizeInts = BlockSizeBytes / 4; - - private readonly bool _encrypting; - - private int[] _encryptKeyExpansion; - private int[] _decryptKeyExpansion; - - private readonly int _Nr; - private readonly int _Nk; - - private int[] _IV; - private int[] _lastBlockBuffer; - - public AesManagedTransform(ReadOnlySpan key, - ReadOnlySpan iv, - bool encrypting) - // AesManagedTransform doesn't use the base IV property, so just pass 'null'. - : base(iv: null, BlockSizeBytes, BlockSizeBytes) - { - Debug.Assert(BitConverter.IsLittleEndian, "The logic of casting Span to Span below assumes little endian"); - Debug.Assert(iv.Length == BlockSizeBytes); - - _encrypting = encrypting; - _Nr = GetNumberOfRounds(key); - _Nk = key.Length / 4; - - _IV = new int[BlockSizeInts]; - iv.CopyTo(MemoryMarshal.AsBytes(_IV.AsSpan())); - - GenerateKeyExpansion(key); - - _lastBlockBuffer = _IV.AsSpan().ToArray(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - // We need to always zeroize the following fields because they contain sensitive data. - // Note: Can't use CryptographicOperations.ZeroMemory since these are int[] and not byte[]. - if (_IV != null) - { - Array.Clear(_IV); - _IV = null!; - } - if (_lastBlockBuffer != null) - { - Array.Clear(_lastBlockBuffer); - _lastBlockBuffer = null!; - } - if (_encryptKeyExpansion != null) - { - Array.Clear(_encryptKeyExpansion); - _encryptKeyExpansion = null!; - } - if (_decryptKeyExpansion != null) - { - Array.Clear(_decryptKeyExpansion); - _decryptKeyExpansion = null!; - } - } - - base.Dispose(disposing); - } - - public override int Transform(ReadOnlySpan input, Span output) - { - Debug.Assert(input.Length % BlockSizeBytes == 0); - Debug.Assert(output.Length >= input.Length); - - // the below algorithm doesn't allow overlap, so rent a buffer to transform into - if (input.Overlaps(output, out int offset) && offset != 0) - { - byte[] rented = CryptoPool.Rent(input.Length); - int bytesWritten = 0; - - try - { - bytesWritten = _encrypting ? - EncryptData(input, rented) : - DecryptData(input, rented); - rented.AsSpan(0, bytesWritten).CopyTo(output); - return bytesWritten; - } - finally - { - CryptoPool.Return(rented, clearSize: bytesWritten); - } - } - else - { - // with no overlap, we can just write directly to the output - return _encrypting ? - EncryptData(input, output) : - DecryptData(input, output); - } - } - - public override int TransformFinal(ReadOnlySpan input, Span output) - { - int bytesWritten = Transform(input, output); - Reset(); - return bytesWritten; - } - - // - // resets the state of the transform - // - - void ILiteSymmetricCipher.Reset(ReadOnlySpan iv) => throw new NotImplementedException(); // never invoked - - private void Reset() - { - _IV.AsSpan().CopyTo(_lastBlockBuffer); - } - - // - // Encrypts input into output using the AES encryption routine. - // This method writes the encrypted data into the output buffer. - // - private int EncryptData(ReadOnlySpan input, Span output) - { - int inputCount = input.Length; - - Span work = stackalloc int[BlockSizeInts]; - Span temp = stackalloc int[BlockSizeInts]; - - int workBaseIndex = 0; - int iNumBlocks = inputCount / BlockSizeBytes; - int transformCount = 0; - for (int blockNum = 0; blockNum < iNumBlocks; ++blockNum) - { - input.Slice(workBaseIndex, BlockSizeBytes).CopyTo(MemoryMarshal.AsBytes(work)); - - for (int i = 0; i < BlockSizeInts; ++i) - { - // XOR with the last encrypted block - work[i] ^= _lastBlockBuffer[i]; - } - - Enc(work, temp); - - for (int i = 0; i < BlockSizeInts; ++i) - { - output[transformCount++] = (byte)(temp[i] & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 8 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 16 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 24 & 0xFF); - } - - Debug.Assert(_lastBlockBuffer.Length == BlockSizeInts); - temp.CopyTo(_lastBlockBuffer); - - workBaseIndex += BlockSizeBytes; - } - - return inputCount; - } - - // - // Decrypts intput into output using the AES encryption routine. - // This method writes the decrypted data into the output buffer. - // - private int DecryptData(ReadOnlySpan input, Span output) - { - int inputCount = input.Length; - - Span work = stackalloc int[BlockSizeInts]; - Span temp = stackalloc int[BlockSizeInts]; - - int iNumBlocks = inputCount / BlockSizeBytes; - int workBaseIndex = 0, index = 0, transformCount = 0; - for (int blockNum = 0; blockNum < iNumBlocks; ++blockNum) - { - index = workBaseIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - int i0 = input[index++]; - int i1 = input[index++]; - int i2 = input[index++]; - int i3 = input[index++]; - work[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - Dec(work, temp); - - index = workBaseIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] ^= _lastBlockBuffer[i]; - // save the input buffer - int i0 = input[index++]; - int i1 = input[index++]; - int i2 = input[index++]; - int i3 = input[index++]; - _lastBlockBuffer[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - for (int i = 0; i < BlockSizeInts; ++i) - { - output[transformCount++] = (byte)(temp[i] & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 8 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 16 & 0xFF); - output[transformCount++] = (byte)(temp[i] >> 24 & 0xFF); - } - - workBaseIndex += BlockSizeBytes; - } - - return inputCount; - } - - // - // AES encryption function. - // - private void Enc(Span work, Span temp) - { - for (int i = 0; i < BlockSizeInts; ++i) - { - work[i] ^= _encryptKeyExpansion[i]; - } - - ReadOnlySpan T = s_T; - ReadOnlySpan encryptindex = s_encryptindex; - int encryptindexIndex; - int encryptKeyExpansionIndex = BlockSizeInts; - for (int r = 1; r < _Nr; ++r) - { - encryptindexIndex = 0; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = T[0 + (work[i] & 0xFF)] ^ - T[256 + ((work[encryptindex[encryptindexIndex]] >> 8) & 0xFF)] ^ - T[512 + ((work[encryptindex[encryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - T[768 + ((work[encryptindex[encryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _encryptKeyExpansion[encryptKeyExpansionIndex]; - encryptindexIndex++; - encryptKeyExpansionIndex++; - } - - temp.CopyTo(work); - } - - ReadOnlySpan TF = s_TF; - encryptindexIndex = 0; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = TF[0 + (work[i] & 0xFF)] ^ - TF[256 + ((work[encryptindex[encryptindexIndex]] >> 8) & 0xFF)] ^ - TF[512 + ((work[encryptindex[encryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - TF[768 + ((work[encryptindex[encryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _encryptKeyExpansion[encryptKeyExpansionIndex]; - encryptindexIndex++; - encryptKeyExpansionIndex++; - } - } - - // - // AES decryption function. - // - - private void Dec(Span work, Span temp) - { - int keyIndex = BlockSizeInts * _Nr; - for (int i = 0; i < BlockSizeInts; ++i) - { - work[i] ^= _decryptKeyExpansion[keyIndex]; - keyIndex++; - } - - ReadOnlySpan iT = s_iT; - ReadOnlySpan decryptindex = s_decryptindex; - int decryptindexIndex; - int decryptKeyExpansionIndex; - for (int r = 1; r < _Nr; ++r) - { - keyIndex -= 2 * BlockSizeInts; - decryptindexIndex = 0; - decryptKeyExpansionIndex = keyIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = iT[0 + ((work[i]) & 0xFF)] ^ - iT[256 + ((work[decryptindex[decryptindexIndex]] >> 8) & 0xFF)] ^ - iT[512 + ((work[decryptindex[decryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - iT[768 + ((work[decryptindex[decryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _decryptKeyExpansion[decryptKeyExpansionIndex]; - keyIndex++; - decryptindexIndex++; - decryptKeyExpansionIndex++; - } - - temp.CopyTo(work); - } - - ReadOnlySpan iTF = s_iTF; - keyIndex = 0; - decryptindexIndex = 0; - decryptKeyExpansionIndex = keyIndex; - for (int i = 0; i < BlockSizeInts; ++i) - { - temp[i] = iTF[0 + ((work[i]) & 0xFF)] ^ - iTF[256 + ((work[decryptindex[decryptindexIndex]] >> 8) & 0xFF)] ^ - iTF[512 + ((work[decryptindex[decryptindexIndex + BlockSizeInts]] >> 16) & 0xFF)] ^ - iTF[768 + ((work[decryptindex[decryptindexIndex + (BlockSizeInts * 2)]] >> 24) & 0xFF)] ^ - _decryptKeyExpansion[decryptKeyExpansionIndex]; - decryptindexIndex++; - decryptKeyExpansionIndex++; - } - } - - private static int GetNumberOfRounds(ReadOnlySpan key) - { - return (BlockSizeBytes > key.Length ? BlockSizeBytes : key.Length) switch - { - 16 => 10, // 128 bits - // 24 => 12, // 192 bits is not supported by SubtleCrypto, so the managed implementation doesn't support it either - 32 => 14, // 256 bits - _ => throw new CryptographicException(SR.Cryptography_InvalidKeySize) - }; - } - - // - // Key expansion routine. - // - - [MemberNotNull(nameof(_encryptKeyExpansion))] - [MemberNotNull(nameof(_decryptKeyExpansion))] - private void GenerateKeyExpansion(ReadOnlySpan key) - { - _encryptKeyExpansion = new int[BlockSizeInts * (_Nr + 1)]; - _decryptKeyExpansion = new int[BlockSizeInts * (_Nr + 1)]; - int iTemp; - - int index = 0; - for (int i = 0; i < _Nk; ++i) - { - int i0 = key[index++]; - int i1 = key[index++]; - int i2 = key[index++]; - int i3 = key[index++]; - _encryptKeyExpansion[i] = i3 << 24 | i2 << 16 | i1 << 8 | i0; - } - - if (_Nk <= 6) - { - for (int i = _Nk; i < BlockSizeInts * (_Nr + 1); ++i) - { - iTemp = _encryptKeyExpansion[i - 1]; - - if (i % _Nk == 0) - { - iTemp = SubWord(rot3(iTemp)); - iTemp ^= s_Rcon[(i / _Nk) - 1]; - } - - _encryptKeyExpansion[i] = _encryptKeyExpansion[i - _Nk] ^ iTemp; - } - } - else - { - for (int i = _Nk; i < BlockSizeInts * (_Nr + 1); ++i) - { - iTemp = _encryptKeyExpansion[i - 1]; - - if (i % _Nk == 0) - { - iTemp = SubWord(rot3(iTemp)); - iTemp ^= s_Rcon[(i / _Nk) - 1]; - } - else if (i % _Nk == 4) - { - iTemp = SubWord(iTemp); - } - - _encryptKeyExpansion[i] = _encryptKeyExpansion[i - _Nk] ^ iTemp; - } - } - - for (int i = 0; i < BlockSizeInts; ++i) - { - _decryptKeyExpansion[i] = _encryptKeyExpansion[i]; - _decryptKeyExpansion[BlockSizeInts * _Nr + i] = _encryptKeyExpansion[BlockSizeInts * _Nr + i]; - } - - for (int i = BlockSizeInts; i < BlockSizeInts * _Nr; ++i) - { - int keyVal = _encryptKeyExpansion[i]; - int mul02 = MulX(keyVal); - int mul04 = MulX(mul02); - int mul08 = MulX(mul04); - int mul09 = keyVal ^ mul08; - _decryptKeyExpansion[i] = mul02 ^ mul04 ^ mul08 ^ rot3(mul02 ^ mul09) ^ rot2(mul04 ^ mul09) ^ rot1(mul09); - } - } - - private static int rot1(int val) => int.RotateLeft(val, 8); - private static int rot2(int val) => int.RotateLeft(val, 16); - private static int rot3(int val) => int.RotateLeft(val, 24); - - private static int SubWord(int a) - { - ReadOnlySpan sbox = Sbox; - return sbox[a & 0xFF] | - sbox[a >> 8 & 0xFF] << 8 | - sbox[a >> 16 & 0xFF] << 16 | - sbox[a >> 24 & 0xFF] << 24; - } - - private static int MulX(int x) - { - int u = x & unchecked((int)0x80808080); - return ((x & unchecked((int)0x7f7f7f7f)) << 1) ^ ((u - (u >> 7 & 0x01FFFFFF)) & 0x1b1b1b1b); - } - - private static ReadOnlySpan Sbox => new byte[] { - 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, - 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, - 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, - 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, - 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, - 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, - 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, - 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, - 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, - 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, - 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, - 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, - 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, - 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, - 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, - 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 }; - - // Precompute the modulus operations: these are performance killers when called frequently - private static readonly int[] s_encryptindex = new int[BlockSizeInts * 3] { - 1, 2, 3, 0, - 2, 3, 0, 1, - 3, 0, 1, 2, - }; - - private static readonly int[] s_decryptindex = new int[BlockSizeInts * 3] { - 3, 0, 1, 2, - 2, 3, 0, 1, - 1, 2, 3, 0, - }; - - private static readonly int[] s_Rcon = new int[] { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, - 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, - 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; - - private static readonly int[] s_T = new int[4 * 256] - { - // s_T1 - -1520213050, -2072216328, -1720223762, -1921287178, 234025727, -1117033514, -1318096930, 1422247313, - 1345335392, 50397442, -1452841010, 2099981142, 436141799, 1658312629, -424957107, -1703512340, - 1170918031, -1652391393, 1086966153, -2021818886, 368769775, -346465870, -918075506, 200339707, - -324162239, 1742001331, -39673249, -357585083, -1080255453, -140204973, -1770884380, 1539358875, - -1028147339, 486407649, -1366060227, 1780885068, 1513502316, 1094664062, 49805301, 1338821763, - 1546925160, -190470831, 887481809, 150073849, -1821281822, 1943591083, 1395732834, 1058346282, - 201589768, 1388824469, 1696801606, 1589887901, 672667696, -1583966665, 251987210, -1248159185, - 151455502, 907153956, -1686077413, 1038279391, 652995533, 1764173646, -843926913, -1619692054, - 453576978, -1635548387, 1949051992, 773462580, 756751158, -1301385508, -296068428, -73359269, - -162377052, 1295727478, 1641469623, -827083907, 2066295122, 1055122397, 1898917726, -1752923117, - -179088474, 1758581177, 0, 753790401, 1612718144, 536673507, -927878791, -312779850, - -1100322092, 1187761037, -641810841, 1262041458, -565556588, -733197160, -396863312, 1255133061, - 1808847035, 720367557, -441800113, 385612781, -985447546, -682799718, 1429418854, -1803188975, - -817543798, 284817897, 100794884, -2122350594, -263171936, 1144798328, -1163944155, -475486133, - -212774494, -22830243, -1069531008, -1970303227, -1382903233, -1130521311, 1211644016, 83228145, - -541279133, -1044990345, 1977277103, 1663115586, 806359072, 452984805, 250868733, 1842533055, - 1288555905, 336333848, 890442534, 804056259, -513843266, -1567123659, -867941240, 957814574, - 1472513171, -223893675, -2105639172, 1195195770, -1402706744, -413311558, 723065138, -1787595802, - -1604296512, -1736343271, -783331426, 2145180835, 1713513028, 2116692564, -1416589253, -2088204277, - -901364084, 703524551, -742868885, 1007948840, 2044649127, -497131844, 487262998, 1994120109, - 1004593371, 1446130276, 1312438900, 503974420, -615954030, 168166924, 1814307912, -463709000, - 1573044895, 1859376061, -273896381, -1503501628, -1466855111, -1533700815, 937747667, -1954973198, - 854058965, 1137232011, 1496790894, -1217565222, -1936880383, 1691735473, -766620004, -525751991, - -1267962664, -95005012, 133494003, 636152527, -1352309302, -1904575756, -374428089, 403179536, - -709182865, -2005370640, 1864705354, 1915629148, 605822008, -240736681, -944458637, 1371981463, - 602466507, 2094914977, -1670089496, 555687742, -582268010, -591544991, -2037675251, -2054518257, - -1871679264, 1111375484, -994724495, -1436129588, -666351472, 84083462, 32962295, 302911004, - -1553899070, 1597322602, -111716434, -793134743, -1853454825, 1489093017, 656219450, -1180787161, - 954327513, 335083755, -1281845205, 856756514, -1150719534, 1893325225, -1987146233, -1483434957, - -1231316179, 572399164, -1836611819, 552200649, 1238290055, -11184726, 2015897680, 2061492133, - -1886614525, -123625127, -2138470135, 386731290, -624967835, 837215959, -968736124, -1201116976, - -1019133566, -1332111063, 1999449434, 286199582, -877612933, -61582168, -692339859, 974525996, - - // s_T2 - 1667483301, 2088564868, 2004348569, 2071721613, -218956019, 1802229437, 1869602481, -976907948, - 808476752, 16843267, 1734856361, 724260477, -16849127, -673729182, -1414836762, 1987505306, - -892694715, -2105401443, -909539008, 2105408135, -84218091, 1499050731, 1195871945, -252642549, - -1381154324, -724257945, -1566416899, -1347467798, -1667488833, -1532734473, 1920132246, -1061119141, - -1212713534, -33693412, -1819066962, 640044138, 909536346, 1061125697, -134744830, -859012273, - 875849820, -1515892236, -437923532, -235800312, 1903288979, -656888973, 825320019, 353708607, - 67373068, -943221422, 589514341, -1010590370, 404238376, -1768540255, 84216335, -1701171275, - 117902857, 303178806, -2139087973, -488448195, -336868058, 656887401, -1296924723, 1970662047, - 151589403, -2088559202, 741103732, 437924910, 454768173, 1852759218, 1515893998, -1600103429, - 1381147894, 993752653, -690571423, -1280082482, 690573947, -471605954, 791633521, -2071719017, - 1397991157, -774784664, 0, -303185620, 538984544, -50535649, -1313769016, 1532737261, - 1785386174, -875852474, -1094817831, 960066123, 1246401758, 1280088276, 1482207464, -808483510, - -791626901, -269499094, -1431679003, -67375850, 1128498885, 1296931543, 859006549, -2054876780, - 1162185423, -101062384, 33686534, 2139094657, 1347461360, 1010595908, -1616960070, -1465365533, - 1364304627, -1549574658, 1077969088, -1886452342, -1835909203, -1650646596, 943222856, -168431356, - -1128504353, -1229555775, -623202443, 555827811, 269492272, -6886, -202113778, -757940371, - -842170036, 202119188, 320022069, -320027857, 1600110305, -1751698014, 1145342156, 387395129, - -993750185, -1482205710, 2122251394, 1027439175, 1684326572, 1566423783, 421081643, 1936975509, - 1616953504, -2122245736, 1330618065, -589520001, 572671078, 707417214, -1869595733, -2004350077, - 1179028682, -286341335, -1195873325, 336865340, -555833479, 1583267042, 185275933, -606360202, - -522134725, 842163286, 976909390, 168432670, 1229558491, 101059594, 606357612, 1549580516, - -1027432611, -741098130, -1397996561, 1650640038, -1852753496, -1785384540, -454765769, 2038035083, - -404237006, -926381245, 926379609, 1835915959, -1920138868, -707415708, 1313774802, -1448523296, - 1819072692, 1448520954, -185273593, -353710299, 1701169839, 2054878350, -1364310039, 134746136, - -1162186795, 2021191816, 623200879, 774790258, 471611428, -1499047951, -1263242297, -960063663, - -387396829, -572677764, 1953818780, 522141217, 1263245021, -1111662116, -1953821306, -1970663547, - 1886445712, 1044282434, -1246400060, 1718013098, 1212715224, 50529797, -151587071, 235805714, - 1633796771, 892693087, 1465364217, -1179031088, -2038032495, -1044276904, 488454695, -1633802311, - -505292488, -117904621, -1734857805, 286335539, 1768542907, -640046736, -1903294583, -1802226777, - -1684329034, 505297954, -2021190254, -370554592, -825325751, 1431677695, 673730680, -538991238, - -1936981105, -1583261192, -1987507840, 218962455, -1077975590, -421079247, 1111655622, 1751699640, - 1094812355, -1718015568, 757946999, 252648977, -1330611253, 1414834428, -1145344554, 370551866, - - // s_T3 - 1673962851, 2096661628, 2012125559, 2079755643, -218165774, 1809235307, 1876865391, -980331323, - 811618352, 16909057, 1741597031, 727088427, -18408962, -675978537, -1420958037, 1995217526, - -896580150, -2111857278, -913751863, 2113570685, -84994566, 1504897881, 1200539975, -251982864, - -1388188499, -726439980, -1570767454, -1354372433, -1675378788, -1538000988, 1927583346, -1063560256, - -1217019209, -35578627, -1824674157, 642542118, 913070646, 1065238847, -134937865, -863809588, - 879254580, -1521355611, -439274267, -235337487, 1910674289, -659852328, 828527409, 355090197, - 67636228, -946515257, 591815971, -1013096765, 405809176, -1774739050, 84545285, -1708149350, - 118360327, 304363026, -2145674368, -488686110, -338876693, 659450151, -1300247118, 1978310517, - 152181513, -2095210877, 743994412, 439627290, 456535323, 1859957358, 1521806938, -1604584544, - 1386542674, 997608763, -692624938, -1283600717, 693271337, -472039709, 794718511, -2079090812, - 1403450707, -776378159, 0, -306107155, 541089824, -52224004, -1317418831, 1538714971, - 1792327274, -879933749, -1100490306, 963791673, 1251270218, 1285084236, 1487988824, -813348145, - -793023536, -272291089, -1437604438, -68348165, 1132905795, 1301993293, 862344499, -2062445435, - 1166724933, -102166279, 33818114, 2147385727, 1352724560, 1014514748, -1624917345, -1471421528, - 1369633617, -1554121053, 1082179648, -1895462257, -1841320558, -1658733411, 946882616, -168753931, - -1134305348, -1233665610, -626035238, 557998881, 270544912, -1762561, -201519373, -759206446, - -847164211, 202904588, 321271059, -322752532, 1606345055, -1758092649, 1149815876, 388905239, - -996976700, -1487539545, 2130477694, 1031423805, 1690872932, 1572530013, 422718233, 1944491379, - 1623236704, -2129028991, 1335808335, -593264676, 574907938, 710180394, -1875137648, -2012511352, - 1183631942, -288937490, -1200893000, 338181140, -559449634, 1589437022, 185998603, -609388837, - -522503200, 845436466, 980700730, 169090570, 1234361161, 101452294, 608726052, 1555620956, - -1029743166, -742560045, -1404833876, 1657054818, -1858492271, -1791908715, -455919644, 2045938553, - -405458201, -930397240, 929978679, 1843050349, -1929278323, -709794603, 1318900302, -1454776151, - 1826141292, 1454176854, -185399308, -355523094, 1707781989, 2062847610, -1371018834, 135272456, - -1167075910, 2029029496, 625635109, 777810478, 473441308, -1504185946, -1267480652, -963161658, - -389340184, -576619299, 1961401460, 524165407, 1268178251, -1117659971, -1962047861, -1978694262, - 1893765232, 1048330814, -1250835275, 1724688998, 1217452104, 50726147, -151584266, 236720654, - 1640145761, 896163637, 1471084887, -1184247623, -2045275770, -1046914879, 490350365, -1641563746, - -505857823, -118811656, -1741966440, 287453969, 1775418217, -643206951, -1912108658, -1808554092, - -1691502949, 507257374, -2028629369, -372694807, -829994546, 1437269845, 676362280, -542803233, - -1945923700, -1587939167, -1995865975, 219813645, -1083843905, -422104602, 1115997762, 1758509160, - 1099088705, -1725321063, 760903469, 253628687, -1334064208, 1420360788, -1150429509, 371997206, - - // s_T4 - -962239645, -125535108, -291932297, -158499973, -15863054, -692229269, -558796945, -1856715323, - 1615867952, 33751297, -827758745, 1451043627, -417726722, -1251813417, 1306962859, -325421450, - -1891251510, 530416258, -1992242743, -91783811, -283772166, -1293199015, -1899411641, -83103504, - 1106029997, -1285040940, 1610457762, 1173008303, 599760028, 1408738468, -459902350, -1688485696, - 1975695287, -518193667, 1034851219, 1282024998, 1817851446, 2118205247, -184354825, -2091922228, - 1750873140, 1374987685, -785062427, -116854287, -493653647, -1418471208, 1649619249, 708777237, - 135005188, -1789737017, 1181033251, -1654733885, 807933976, 933336726, 168756485, 800430746, - 235472647, 607523346, 463175808, -549592350, -853087253, 1315514151, 2144187058, -358648459, - 303761673, 496927619, 1484008492, 875436570, 908925723, -592286098, -1259447718, 1543217312, - -1527360942, 1984772923, -1218324778, 2110698419, 1383803177, -583080989, 1584475951, 328696964, - -1493871789, -1184312879, 0, -1054020115, 1080041504, -484442884, 2043195825, -1225958565, - -725718422, -1924740149, 1742323390, 1917532473, -1797371318, -1730917300, -1326950312, -2058694705, - -1150562096, -987041809, 1340451498, -317260805, -2033892541, -1697166003, 1716859699, 294946181, - -1966127803, -384763399, 67502594, -25067649, -1594863536, 2017737788, 632987551, 1273211048, - -1561112239, 1576969123, -2134884288, 92966799, 1068339858, 566009245, 1883781176, -251333131, - 1675607228, 2009183926, -1351230758, 1113792801, 540020752, -451215361, -49351693, -1083321646, - -2125673011, 403966988, 641012499, -1020269332, -1092526241, 899848087, -1999879100, 775493399, - -1822964540, 1441965991, -58556802, 2051489085, -928226204, -1159242403, 841685273, -426413197, - -1063231392, 429425025, -1630449841, -1551901476, 1147544098, 1417554474, 1001099408, 193169544, - -1932900794, -953553170, 1809037496, 675025940, -1485185314, -1126015394, 371002123, -1384719397, - -616832800, 1683370546, 1951283770, 337512970, -1831122615, 201983494, 1215046692, -1192993700, - -1621245246, -1116810285, 1139780780, -995728798, 967348625, 832869781, -751311644, -225740423, - -718084121, -1958491960, 1851340599, -625513107, 25988493, -1318791723, -1663938994, 1239460265, - -659264404, -1392880042, -217582348, -819598614, -894474907, -191989126, 1206496942, 270010376, - 1876277946, -259491720, 1248797989, 1550986798, 941890588, 1475454630, 1942467764, -1756248378, - -886839064, -1585652259, -392399756, 1042358047, -1763882165, 1641856445, 226921355, 260409994, - -527404944, 2084716094, 1908716981, -861247898, -1864873912, 100991747, -150866186, 470945294, - -1029480095, 1784624437, -1359390889, 1775286713, 395413126, -1722236479, 975641885, 666476190, - -650583583, -351012616, 733190296, 573772049, -759469719, -1452221991, 126455438, 866620564, - 766942107, 1008868894, 361924487, -920589847, -2025206066, -1426107051, 1350051880, -1518673953, - 59739276, 1509466529, 159418761, 437718285, 1708834751, -684595482, -2067381694, -793221016, - -2101132991, 699439513, 1517759789, 504434447, 2076946608, -1459858348, 1842789307, 742004246 }; - - private static readonly int[] s_TF = new int[4 * 256] - { - // s_TF1 - 99, 124, 119, 123, 242, 107, 111, 197, - 48, 1, 103, 43, 254, 215, 171, 118, - 202, 130, 201, 125, 250, 89, 71, 240, - 173, 212, 162, 175, 156, 164, 114, 192, - 183, 253, 147, 38, 54, 63, 247, 204, - 52, 165, 229, 241, 113, 216, 49, 21, - 4, 199, 35, 195, 24, 150, 5, 154, - 7, 18, 128, 226, 235, 39, 178, 117, - 9, 131, 44, 26, 27, 110, 90, 160, - 82, 59, 214, 179, 41, 227, 47, 132, - 83, 209, 0, 237, 32, 252, 177, 91, - 106, 203, 190, 57, 74, 76, 88, 207, - 208, 239, 170, 251, 67, 77, 51, 133, - 69, 249, 2, 127, 80, 60, 159, 168, - 81, 163, 64, 143, 146, 157, 56, 245, - 188, 182, 218, 33, 16, 255, 243, 210, - 205, 12, 19, 236, 95, 151, 68, 23, - 196, 167, 126, 61, 100, 93, 25, 115, - 96, 129, 79, 220, 34, 42, 144, 136, - 70, 238, 184, 20, 222, 94, 11, 219, - 224, 50, 58, 10, 73, 6, 36, 92, - 194, 211, 172, 98, 145, 149, 228, 121, - 231, 200, 55, 109, 141, 213, 78, 169, - 108, 86, 244, 234, 101, 122, 174, 8, - 186, 120, 37, 46, 28, 166, 180, 198, - 232, 221, 116, 31, 75, 189, 139, 138, - 112, 62, 181, 102, 72, 3, 246, 14, - 97, 53, 87, 185, 134, 193, 29, 158, - 225, 248, 152, 17, 105, 217, 142, 148, - 155, 30, 135, 233, 206, 85, 40, 223, - 140, 161, 137, 13, 191, 230, 66, 104, - 65, 153, 45, 15, 176, 84, 187, 22, - - // s_TF2 - 25344, 31744, 30464, 31488, 61952, 27392, 28416, 50432, - 12288, 256, 26368, 11008, 65024, 55040, 43776, 30208, - 51712, 33280, 51456, 32000, 64000, 22784, 18176, 61440, - 44288, 54272, 41472, 44800, 39936, 41984, 29184, 49152, - 46848, 64768, 37632, 9728, 13824, 16128, 63232, 52224, - 13312, 42240, 58624, 61696, 28928, 55296, 12544, 5376, - 1024, 50944, 8960, 49920, 6144, 38400, 1280, 39424, - 1792, 4608, 32768, 57856, 60160, 9984, 45568, 29952, - 2304, 33536, 11264, 6656, 6912, 28160, 23040, 40960, - 20992, 15104, 54784, 45824, 10496, 58112, 12032, 33792, - 21248, 53504, 0, 60672, 8192, 64512, 45312, 23296, - 27136, 51968, 48640, 14592, 18944, 19456, 22528, 52992, - 53248, 61184, 43520, 64256, 17152, 19712, 13056, 34048, - 17664, 63744, 512, 32512, 20480, 15360, 40704, 43008, - 20736, 41728, 16384, 36608, 37376, 40192, 14336, 62720, - 48128, 46592, 55808, 8448, 4096, 65280, 62208, 53760, - 52480, 3072, 4864, 60416, 24320, 38656, 17408, 5888, - 50176, 42752, 32256, 15616, 25600, 23808, 6400, 29440, - 24576, 33024, 20224, 56320, 8704, 10752, 36864, 34816, - 17920, 60928, 47104, 5120, 56832, 24064, 2816, 56064, - 57344, 12800, 14848, 2560, 18688, 1536, 9216, 23552, - 49664, 54016, 44032, 25088, 37120, 38144, 58368, 30976, - 59136, 51200, 14080, 27904, 36096, 54528, 19968, 43264, - 27648, 22016, 62464, 59904, 25856, 31232, 44544, 2048, - 47616, 30720, 9472, 11776, 7168, 42496, 46080, 50688, - 59392, 56576, 29696, 7936, 19200, 48384, 35584, 35328, - 28672, 15872, 46336, 26112, 18432, 768, 62976, 3584, - 24832, 13568, 22272, 47360, 34304, 49408, 7424, 40448, - 57600, 63488, 38912, 4352, 26880, 55552, 36352, 37888, - 39680, 7680, 34560, 59648, 52736, 21760, 10240, 57088, - 35840, 41216, 35072, 3328, 48896, 58880, 16896, 26624, - 16640, 39168, 11520, 3840, 45056, 21504, 47872, 5632, - - // s_TF3 - 6488064, 8126464, 7798784, 8060928, 15859712, 7012352, 7274496, 12910592, - 3145728, 65536, 6750208, 2818048, 16646144, 14090240, 11206656, 7733248, - 13238272, 8519680, 13172736, 8192000, 16384000, 5832704, 4653056, 15728640, - 11337728, 13893632, 10616832, 11468800, 10223616, 10747904, 7471104, 12582912, - 11993088, 16580608, 9633792, 2490368, 3538944, 4128768, 16187392, 13369344, - 3407872, 10813440, 15007744, 15794176, 7405568, 14155776, 3211264, 1376256, - 262144, 13041664, 2293760, 12779520, 1572864, 9830400, 327680, 10092544, - 458752, 1179648, 8388608, 14811136, 15400960, 2555904, 11665408, 7667712, - 589824, 8585216, 2883584, 1703936, 1769472, 7208960, 5898240, 10485760, - 5373952, 3866624, 14024704, 11730944, 2686976, 14876672, 3080192, 8650752, - 5439488, 13697024, 0, 15532032, 2097152, 16515072, 11599872, 5963776, - 6946816, 13303808, 12451840, 3735552, 4849664, 4980736, 5767168, 13565952, - 13631488, 15663104, 11141120, 16449536, 4390912, 5046272, 3342336, 8716288, - 4521984, 16318464, 131072, 8323072, 5242880, 3932160, 10420224, 11010048, - 5308416, 10682368, 4194304, 9371648, 9568256, 10289152, 3670016, 16056320, - 12320768, 11927552, 14286848, 2162688, 1048576, 16711680, 15925248, 13762560, - 13434880, 786432, 1245184, 15466496, 6225920, 9895936, 4456448, 1507328, - 12845056, 10944512, 8257536, 3997696, 6553600, 6094848, 1638400, 7536640, - 6291456, 8454144, 5177344, 14417920, 2228224, 2752512, 9437184, 8912896, - 4587520, 15597568, 12058624, 1310720, 14548992, 6160384, 720896, 14352384, - 14680064, 3276800, 3801088, 655360, 4784128, 393216, 2359296, 6029312, - 12713984, 13828096, 11272192, 6422528, 9502720, 9764864, 14942208, 7929856, - 15138816, 13107200, 3604480, 7143424, 9240576, 13959168, 5111808, 11075584, - 7077888, 5636096, 15990784, 15335424, 6619136, 7995392, 11403264, 524288, - 12189696, 7864320, 2424832, 3014656, 1835008, 10878976, 11796480, 12976128, - 15204352, 14483456, 7602176, 2031616, 4915200, 12386304, 9109504, 9043968, - 7340032, 4063232, 11862016, 6684672, 4718592, 196608, 16121856, 917504, - 6356992, 3473408, 5701632, 12124160, 8781824, 12648448, 1900544, 10354688, - 14745600, 16252928, 9961472, 1114112, 6881280, 14221312, 9306112, 9699328, - 10158080, 1966080, 8847360, 15269888, 13500416, 5570560, 2621440, 14614528, - 9175040, 10551296, 8978432, 851968, 12517376, 15073280, 4325376, 6815744, - 4259840, 10027008, 2949120, 983040, 11534336, 5505024, 12255232, 1441792, - - // s_TF4 - 1660944384, 2080374784, 1996488704, 2063597568, -234881024, 1795162112, 1862270976, -989855744, - 805306368, 16777216, 1728053248, 721420288, -33554432, -687865856, -1426063360, 1979711488, - -905969664, -2113929216, -922746880, 2097152000, -100663296, 1493172224, 1191182336, -268435456, - -1392508928, -738197504, -1577058304, -1358954496, -1677721600, -1543503872, 1912602624, -1073741824, - -1224736768, -50331648, -1828716544, 637534208, 905969664, 1056964608, -150994944, -872415232, - 872415232, -1526726656, -452984832, -251658240, 1895825408, -671088640, 822083584, 352321536, - 67108864, -956301312, 587202560, -1023410176, 402653184, -1778384896, 83886080, -1711276032, - 117440512, 301989888, -2147483648, -503316480, -352321536, 654311424, -1308622848, 1962934272, - 150994944, -2097152000, 738197504, 436207616, 452984832, 1845493760, 1509949440, -1610612736, - 1375731712, 989855744, -704643072, -1291845632, 687865856, -486539264, 788529152, -2080374784, - 1392508928, -788529152, 0, -318767104, 536870912, -67108864, -1325400064, 1526726656, - 1778384896, -889192448, -1107296256, 956301312, 1241513984, 1275068416, 1476395008, -822083584, - -805306368, -285212672, -1442840576, -83886080, 1124073472, 1291845632, 855638016, -2063597568, - 1157627904, -117440512, 33554432, 2130706432, 1342177280, 1006632960, -1627389952, -1476395008, - 1358954496, -1560281088, 1073741824, -1895825408, -1845493760, -1660944384, 939524096, -184549376, - -1140850688, -1241513984, -637534208, 553648128, 268435456, -16777216, -218103808, -771751936, - -855638016, 201326592, 318767104, -335544320, 1593835520, -1761607680, 1140850688, 385875968, - -1006632960, -1493172224, 2113929216, 1023410176, 1677721600, 1560281088, 419430400, 1929379840, - 1610612736, -2130706432, 1325400064, -603979776, 570425344, 704643072, -1879048192, -2013265920, - 1174405120, -301989888, -1207959552, 335544320, -570425344, 1577058304, 184549376, -620756992, - -536870912, 838860800, 973078528, 167772160, 1224736768, 100663296, 603979776, 1543503872, - -1040187392, -754974720, -1409286144, 1644167168, -1862270976, -1795162112, -469762048, 2030043136, - -419430400, -939524096, 922746880, 1828716544, -1929379840, -721420288, 1308622848, -1459617792, - 1811939328, 1442840576, -201326592, -369098752, 1694498816, 2046820352, -1375731712, 134217728, - -1174405120, 2013265920, 620756992, 771751936, 469762048, -1509949440, -1275068416, -973078528, - -402653184, -587202560, 1946157056, 520093696, 1258291200, -1124073472, -1962934272, -1979711488, - 1879048192, 1040187392, -1258291200, 1711276032, 1207959552, 50331648, -167772160, 234881024, - 1627389952, 889192448, 1459617792, -1191182336, -2046820352, -1056964608, 486539264, -1644167168, - -520093696, -134217728, -1744830464, 285212672, 1761607680, -654311424, -1912602624, -1811939328, - -1694498816, 503316480, -2030043136, -385875968, -838860800, 1426063360, 671088640, -553648128, - -1946157056, -1593835520, -1996488704, 218103808, -1090519040, -436207616, 1107296256, 1744830464, - 1090519040, -1728053248, 754974720, 251658240, -1342177280, 1409286144, -1157627904, 369098752 }; - - private static readonly int[] s_iT = new int[4 * 256] - { - // s_iT1 - 1353184337, 1399144830, -1012656358, -1772214470, -882136261, -247096033, -1420232020, -1828461749, - 1442459680, -160598355, -1854485368, 625738485, -52959921, -674551099, -2143013594, -1885117771, - 1230680542, 1729870373, -1743852987, -507445667, 41234371, 317738113, -1550367091, -956705941, - -413167869, -1784901099, -344298049, -631680363, 763608788, -752782248, 694804553, 1154009486, - 1787413109, 2021232372, 1799248025, -579749593, -1236278850, 397248752, 1722556617, -1271214467, - 407560035, -2110711067, 1613975959, 1165972322, -529046351, -2068943941, 480281086, -1809118983, - 1483229296, 436028815, -2022908268, -1208452270, 601060267, -503166094, 1468997603, 715871590, - 120122290, 63092015, -1703164538, -1526188077, -226023376, -1297760477, -1167457534, 1552029421, - 723308426, -1833666137, -252573709, -1578997426, -839591323, -708967162, 526529745, -1963022652, - -1655493068, -1604979806, 853641733, 1978398372, 971801355, -1427152832, 111112542, 1360031421, - -108388034, 1023860118, -1375387939, 1186850381, -1249028975, 90031217, 1876166148, -15380384, - 620468249, -1746289194, -868007799, 2006899047, -1119688528, -2004121337, 945494503, -605108103, - 1191869601, -384875908, -920746760, 0, -2088337399, 1223502642, -1401941730, 1316117100, - -67170563, 1446544655, 517320253, 658058550, 1691946762, 564550760, -783000677, 976107044, - -1318647284, 266819475, -761860428, -1634624741, 1338359936, -1574904735, 1766553434, 370807324, - 179999714, -450191168, 1138762300, 488053522, 185403662, -1379431438, -1180125651, -928440812, - -2061897385, 1275557295, -1143105042, -44007517, -1624899081, -1124765092, -985962940, 880737115, - 1982415755, -590994485, 1761406390, 1676797112, -891538985, 277177154, 1076008723, 538035844, - 2099530373, -130171950, 288553390, 1839278535, 1261411869, -214912292, -330136051, -790380169, - 1813426987, -1715900247, -95906799, 577038663, -997393240, 440397984, -668172970, -275762398, - -951170681, -1043253031, -22885748, 906744984, -813566554, 685669029, 646887386, -1530942145, - -459458004, 227702864, -1681105046, 1648787028, -1038905866, -390539120, 1593260334, -173030526, - -1098883681, 2090061929, -1456614033, -1290656305, 999926984, -1484974064, 1852021992, 2075868123, - 158869197, -199730834, 28809964, -1466282109, 1701746150, 2129067946, 147831841, -420997649, - -644094022, -835293366, -737566742, -696471511, -1347247055, 824393514, 815048134, -1067015627, - 935087732, -1496677636, -1328508704, 366520115, 1251476721, -136647615, 240176511, 804688151, - -1915335306, 1303441219, 1414376140, -553347356, -474623586, 461924940, -1205916479, 2136040774, - 82468509, 1563790337, 1937016826, 776014843, 1511876531, 1389550482, 861278441, 323475053, - -1939744870, 2047648055, -1911228327, -1992551445, -299390514, 902390199, -303751967, 1018251130, - 1507840668, 1064563285, 2043548696, -1086863501, -355600557, 1537932639, 342834655, -2032450440, - -2114736182, 1053059257, 741614648, 1598071746, 1925389590, 203809468, -1958134744, 1100287487, - 1895934009, -558691320, -1662733096, -1866377628, 1636092795, 1890988757, 1952214088, 1113045200, - - // s_iT2 - -1477160624, 1698790995, -1541989693, 1579629206, 1806384075, 1167925233, 1492823211, 65227667, - -97509291, 1836494326, 1993115793, 1275262245, -672837636, -886389289, 1144333952, -1553812081, - 1521606217, 465184103, 250234264, -1057071647, 1966064386, -263421678, -1756983901, -103584826, - 1603208167, -1668147819, 2054012907, 1498584538, -2084645843, 561273043, 1776306473, -926314940, - -1983744662, 2039411832, 1045993835, 1907959773, 1340194486, -1383534569, -1407137434, 986611124, - 1256153880, 823846274, 860985184, 2136171077, 2003087840, -1368671356, -1602093540, 722008468, - 1749577816, -45773031, 1826526343, -126135625, -747394269, 38499042, -1893735593, -1420466646, - 686535175, -1028313341, 2076542618, 137876389, -2027409166, -1514200142, 1778582202, -2112426660, - 483363371, -1267095662, -234359824, -496415071, -187013683, -1106966827, 1647628575, -22625142, - 1395537053, 1442030240, -511048398, -336157579, -326956231, -278904662, -1619960314, 275692881, - -1977532679, 115185213, 88006062, -1108980410, -1923837515, 1573155077, -737803153, 357589247, - -73918172, -373434729, 1128303052, -1629919369, 1122545853, -1953953912, 1528424248, -288851493, - 175939911, 256015593, 512030921, 0, -2038429309, -315936184, 1880170156, 1918528590, - -15794693, 948244310, -710001378, 959264295, -653325724, -1503893471, 1415289809, 775300154, - 1728711857, -413691121, -1762741038, -1852105826, -977239985, 551313826, 1266113129, 437394454, - -1164713462, 715178213, -534627261, 387650077, 218697227, -947129683, -1464455751, -1457646392, - 435246981, 125153100, -577114437, 1618977789, 637663135, -177054532, 996558021, 2130402100, - 692292470, -970732580, -51530136, -236668829, -600713270, -2057092592, 580326208, 298222624, - 608863613, 1035719416, 855223825, -1591097491, 798891339, 817028339, 1384517100, -473860144, - 380840812, -1183798887, 1217663482, 1693009698, -1929598780, 1072734234, 746411736, -1875696913, - 1313441735, -784803391, -1563783938, 198481974, -2114607409, -562387672, -1900553690, -1079165020, - -1657131804, -1837608947, -866162021, 1182684258, 328070850, -1193766680, -147247522, -1346141451, - -2141347906, -1815058052, 768962473, 304467891, -1716729797, 2098729127, 1671227502, -1153705093, - 2015808777, 408514292, -1214583807, -1706064984, 1855317605, -419452290, -809754360, -401215514, - -1679312167, 913263310, 161475284, 2091919830, -1297862225, 591342129, -1801075152, 1721906624, - -1135709129, -897385306, -795811664, -660131051, -1744506550, -622050825, 1355644686, -158263505, - -699566451, -1326496947, 1303039060, 76997855, -1244553501, -2006299621, 523026872, 1365591679, - -362898172, 898367837, 1955068531, 1091304238, 493335386, -757362094, 1443948851, 1205234963, - 1641519756, 211892090, 351820174, 1007938441, 665439982, -916342987, -451091987, -1320715716, - -539845543, 1945261375, -837543815, 935818175, -839429142, -1426235557, 1866325780, -616269690, - -206583167, -999769794, 874788908, 1084473951, -1021503886, 635616268, 1228679307, -1794244799, - 27801969, -1291056930, -457910116, -1051302768, -2067039391, -1238182544, 1550600308, 1471729730, - - // s_iT3 - -195997529, 1098797925, 387629988, 658151006, -1422144661, -1658851003, -89347240, -481586429, - 807425530, 1991112301, -863465098, 49620300, -447742761, 717608907, 891715652, 1656065955, - -1310832294, -1171953893, -364537842, -27401792, 801309301, 1283527408, 1183687575, -747911431, - -1895569569, -1844079204, 1841294202, 1385552473, -1093390973, 1951978273, -532076183, -913423160, - -1032492407, -1896580999, 1486449470, -1188569743, -507595185, -1997531219, 550069932, -830622662, - -547153846, 451248689, 1368875059, 1398949247, 1689378935, 1807451310, -2114052960, 150574123, - 1215322216, 1167006205, -560691348, 2069018616, 1940595667, 1265820162, 534992783, 1432758955, - -340654296, -1255210046, -981034373, 936617224, 674296455, -1088179547, 50510442, 384654466, - -813028580, 2041025204, 133427442, 1766760930, -630862348, 84334014, 886120290, -1497068802, - 775200083, -207445931, -1979370783, -156994069, -2096416276, 1614850799, 1901987487, 1857900816, - 557775242, -577356538, 1054715397, -431143235, 1418835341, -999226019, 100954068, 1348534037, - -1743182597, -1110009879, 1082772547, -647530594, -391070398, -1995994997, 434583643, -931537938, - 2090944266, 1115482383, -2064070370, 0, -2146860154, 724715757, 287222896, 1517047410, - 251526143, -2062592456, -1371726123, 758523705, 252339417, 1550328230, 1536938324, 908343854, - 168604007, 1469255655, -290139498, -1692688751, -1065332795, -597581280, 2002413899, 303830554, - -1813902662, -1597971158, 574374880, 454171927, 151915277, -1947030073, -1238517336, 504678569, - -245922535, 1974422535, -1712407587, 2141453664, 33005350, 1918680309, 1715782971, -77908866, - 1133213225, 600562886, -306812676, -457677839, 836225756, 1665273989, -1760346078, -964419567, - 1250262308, -1143801795, -106032846, 700935585, -1642247377, -1294142672, -2045907886, -1049112349, - -1288999914, 1890163129, -1810761144, -381214108, -56048500, -257942977, 2102843436, 857927568, - 1233635150, 953795025, -896729438, -728222197, -173617279, 2057644254, -1210440050, -1388337985, - 976020637, 2018512274, 1600822220, 2119459398, -1913208301, -661591880, 959340279, -1014827601, - 1570750080, -798393197, -714102483, 634368786, -1396163687, 403744637, -1662488989, 1004239803, - 650971512, 1500443672, -1695809097, 1334028442, -1780062866, -5603610, -1138685745, 368043752, - -407184997, 1867173430, -1612000247, -1339435396, -1540247630, 1059729699, -1513738092, -1573535642, - 1316239292, -2097371446, -1864322864, -1489824296, 82922136, -331221030, -847311280, -1860751370, - 1299615190, -280801872, -1429449651, -1763385596, -778116171, 1783372680, 750893087, 1699118929, - 1587348714, -1946067659, -2013629580, 201010753, 1739807261, -611167534, 283718486, -697494713, - -677737375, -1590199796, -128348652, 334203196, -1446056409, 1639396809, 484568549, 1199193265, - -761505313, -229294221, 337148366, -948715721, -145495347, -44082262, 1038029935, 1148749531, - -1345682957, 1756970692, 607661108, -1547542720, 488010435, -490992603, 1009290057, 234832277, - -1472630527, 201907891, -1260872476, 1449431233, -881106556, 852848822, 1816687708, -1194311081, - - // s_iT4 - 1364240372, 2119394625, 449029143, 982933031, 1003187115, 535905693, -1398056710, 1267925987, - 542505520, -1376359050, -2003732788, -182105086, 1341970405, -975713494, 645940277, -1248877726, - -565617999, 627514298, 1167593194, 1575076094, -1023249105, -2129465268, -1918658746, 1808202195, - 65494927, 362126482, -1075086739, -1780852398, -735214658, 1490231668, 1227450848, -1908094775, - 1969916354, -193431154, -1721024936, 668823993, -1095348255, -266883704, -916018144, 2108963534, - 1662536415, -444452582, -1755303087, 1648721747, -1310689436, -1148932501, -31678335, -107730168, - 1884842056, -1894122171, -1803064098, 1387788411, -1423715469, 1927414347, -480800993, 1714072405, - -1308153621, 788775605, -2036696123, -744159177, 821200680, 598910399, 45771267, -312704490, - -1976886065, -1483557767, -202313209, 1319232105, 1707996378, 114671109, -786472396, -997523802, - 882725678, -1566550541, 87220618, -1535775754, 188345475, 1084944224, 1577492337, -1118760850, - 1056541217, -1774385443, -575797954, 1296481766, -1850372780, 1896177092, 74437638, 1627329872, - 421854104, -694687299, -1983102144, 1735892697, -1329773848, 126389129, -415737063, 2044456648, - -1589179780, 2095648578, -121037180, 0, 159614592, 843640107, 514617361, 1817080410, - -33816818, 257308805, 1025430958, 908540205, 174381327, 1747035740, -1680780197, 607792694, - 212952842, -1827674281, -1261267218, 463376795, -2142255680, 1638015196, 1516850039, 471210514, - -502613357, -1058723168, 1011081250, 303896347, 235605257, -223492213, 767142070, 348694814, - 1468340721, -1353971851, -289677927, -1543675777, -140564991, 1555887474, 1153776486, 1530167035, - -1955190461, -874723805, -1234633491, -1201409564, -674571215, 1108378979, 322970263, -2078273082, - -2055396278, -755483205, -1374604551, -949116631, 491466654, -588042062, 233591430, 2010178497, - 728503987, -1449543312, 301615252, 1193436393, -1463513860, -1608892432, 1457007741, 586125363, - -2016981431, -641609416, -1929469238, -1741288492, -1496350219, -1524048262, -635007305, 1067761581, - 753179962, 1343066744, 1788595295, 1415726718, -155053171, -1863796520, 777975609, -2097827901, - -1614905251, 1769771984, 1873358293, -810347995, -935618132, 279411992, -395418724, -612648133, - -855017434, 1861490777, -335431782, -2086102449, -429560171, -1434523905, 554225596, -270079979, - -1160143897, 1255028335, -355202657, 701922480, 833598116, 707863359, -969894747, 901801634, - 1949809742, -56178046, -525283184, 857069735, -246769660, 1106762476, 2131644621, 389019281, - 1989006925, 1129165039, -866890326, -455146346, -1629243951, 1276872810, -1044898004, 1182749029, - -1660622242, 22885772, -93096825, -80854773, -1285939865, -1840065829, -382511600, 1829980118, - -1702075945, 930745505, 1502483704, -343327725, -823253079, -1221211807, -504503012, 2050797895, - -1671831598, 1430221810, 410635796, 1941911495, 1407897079, 1599843069, -552308931, 2022103876, - -897453137, -1187068824, 942421028, -1033944925, 376619805, -1140054558, 680216892, -12479219, - 963707304, 148812556, -660806476, 1687208278, 2069988555, -714033614, 1215585388, -800958536 }; - - private static readonly int[] s_iTF = new int[4 * 256] - { - // s_iTF1 - 82, 9, 106, 213, 48, 54, 165, 56, - 191, 64, 163, 158, 129, 243, 215, 251, - 124, 227, 57, 130, 155, 47, 255, 135, - 52, 142, 67, 68, 196, 222, 233, 203, - 84, 123, 148, 50, 166, 194, 35, 61, - 238, 76, 149, 11, 66, 250, 195, 78, - 8, 46, 161, 102, 40, 217, 36, 178, - 118, 91, 162, 73, 109, 139, 209, 37, - 114, 248, 246, 100, 134, 104, 152, 22, - 212, 164, 92, 204, 93, 101, 182, 146, - 108, 112, 72, 80, 253, 237, 185, 218, - 94, 21, 70, 87, 167, 141, 157, 132, - 144, 216, 171, 0, 140, 188, 211, 10, - 247, 228, 88, 5, 184, 179, 69, 6, - 208, 44, 30, 143, 202, 63, 15, 2, - 193, 175, 189, 3, 1, 19, 138, 107, - 58, 145, 17, 65, 79, 103, 220, 234, - 151, 242, 207, 206, 240, 180, 230, 115, - 150, 172, 116, 34, 231, 173, 53, 133, - 226, 249, 55, 232, 28, 117, 223, 110, - 71, 241, 26, 113, 29, 41, 197, 137, - 111, 183, 98, 14, 170, 24, 190, 27, - 252, 86, 62, 75, 198, 210, 121, 32, - 154, 219, 192, 254, 120, 205, 90, 244, - 31, 221, 168, 51, 136, 7, 199, 49, - 177, 18, 16, 89, 39, 128, 236, 95, - 96, 81, 127, 169, 25, 181, 74, 13, - 45, 229, 122, 159, 147, 201, 156, 239, - 160, 224, 59, 77, 174, 42, 245, 176, - 200, 235, 187, 60, 131, 83, 153, 97, - 23, 43, 4, 126, 186, 119, 214, 38, - 225, 105, 20, 99, 85, 33, 12, 125, - - // s_iTF2 - 20992, 2304, 27136, 54528, 12288, 13824, 42240, 14336, - 48896, 16384, 41728, 40448, 33024, 62208, 55040, 64256, - 31744, 58112, 14592, 33280, 39680, 12032, 65280, 34560, - 13312, 36352, 17152, 17408, 50176, 56832, 59648, 51968, - 21504, 31488, 37888, 12800, 42496, 49664, 8960, 15616, - 60928, 19456, 38144, 2816, 16896, 64000, 49920, 19968, - 2048, 11776, 41216, 26112, 10240, 55552, 9216, 45568, - 30208, 23296, 41472, 18688, 27904, 35584, 53504, 9472, - 29184, 63488, 62976, 25600, 34304, 26624, 38912, 5632, - 54272, 41984, 23552, 52224, 23808, 25856, 46592, 37376, - 27648, 28672, 18432, 20480, 64768, 60672, 47360, 55808, - 24064, 5376, 17920, 22272, 42752, 36096, 40192, 33792, - 36864, 55296, 43776, 0, 35840, 48128, 54016, 2560, - 63232, 58368, 22528, 1280, 47104, 45824, 17664, 1536, - 53248, 11264, 7680, 36608, 51712, 16128, 3840, 512, - 49408, 44800, 48384, 768, 256, 4864, 35328, 27392, - 14848, 37120, 4352, 16640, 20224, 26368, 56320, 59904, - 38656, 61952, 52992, 52736, 61440, 46080, 58880, 29440, - 38400, 44032, 29696, 8704, 59136, 44288, 13568, 34048, - 57856, 63744, 14080, 59392, 7168, 29952, 57088, 28160, - 18176, 61696, 6656, 28928, 7424, 10496, 50432, 35072, - 28416, 46848, 25088, 3584, 43520, 6144, 48640, 6912, - 64512, 22016, 15872, 19200, 50688, 53760, 30976, 8192, - 39424, 56064, 49152, 65024, 30720, 52480, 23040, 62464, - 7936, 56576, 43008, 13056, 34816, 1792, 50944, 12544, - 45312, 4608, 4096, 22784, 9984, 32768, 60416, 24320, - 24576, 20736, 32512, 43264, 6400, 46336, 18944, 3328, - 11520, 58624, 31232, 40704, 37632, 51456, 39936, 61184, - 40960, 57344, 15104, 19712, 44544, 10752, 62720, 45056, - 51200, 60160, 47872, 15360, 33536, 21248, 39168, 24832, - 5888, 11008, 1024, 32256, 47616, 30464, 54784, 9728, - 57600, 26880, 5120, 25344, 21760, 8448, 3072, 32000, - - // s_iTF3 - 5373952, 589824, 6946816, 13959168, 3145728, 3538944, 10813440, 3670016, - 12517376, 4194304, 10682368, 10354688, 8454144, 15925248, 14090240, 16449536, - 8126464, 14876672, 3735552, 8519680, 10158080, 3080192, 16711680, 8847360, - 3407872, 9306112, 4390912, 4456448, 12845056, 14548992, 15269888, 13303808, - 5505024, 8060928, 9699328, 3276800, 10878976, 12713984, 2293760, 3997696, - 15597568, 4980736, 9764864, 720896, 4325376, 16384000, 12779520, 5111808, - 524288, 3014656, 10551296, 6684672, 2621440, 14221312, 2359296, 11665408, - 7733248, 5963776, 10616832, 4784128, 7143424, 9109504, 13697024, 2424832, - 7471104, 16252928, 16121856, 6553600, 8781824, 6815744, 9961472, 1441792, - 13893632, 10747904, 6029312, 13369344, 6094848, 6619136, 11927552, 9568256, - 7077888, 7340032, 4718592, 5242880, 16580608, 15532032, 12124160, 14286848, - 6160384, 1376256, 4587520, 5701632, 10944512, 9240576, 10289152, 8650752, - 9437184, 14155776, 11206656, 0, 9175040, 12320768, 13828096, 655360, - 16187392, 14942208, 5767168, 327680, 12058624, 11730944, 4521984, 393216, - 13631488, 2883584, 1966080, 9371648, 13238272, 4128768, 983040, 131072, - 12648448, 11468800, 12386304, 196608, 65536, 1245184, 9043968, 7012352, - 3801088, 9502720, 1114112, 4259840, 5177344, 6750208, 14417920, 15335424, - 9895936, 15859712, 13565952, 13500416, 15728640, 11796480, 15073280, 7536640, - 9830400, 11272192, 7602176, 2228224, 15138816, 11337728, 3473408, 8716288, - 14811136, 16318464, 3604480, 15204352, 1835008, 7667712, 14614528, 7208960, - 4653056, 15794176, 1703936, 7405568, 1900544, 2686976, 12910592, 8978432, - 7274496, 11993088, 6422528, 917504, 11141120, 1572864, 12451840, 1769472, - 16515072, 5636096, 4063232, 4915200, 12976128, 13762560, 7929856, 2097152, - 10092544, 14352384, 12582912, 16646144, 7864320, 13434880, 5898240, 15990784, - 2031616, 14483456, 11010048, 3342336, 8912896, 458752, 13041664, 3211264, - 11599872, 1179648, 1048576, 5832704, 2555904, 8388608, 15466496, 6225920, - 6291456, 5308416, 8323072, 11075584, 1638400, 11862016, 4849664, 851968, - 2949120, 15007744, 7995392, 10420224, 9633792, 13172736, 10223616, 15663104, - 10485760, 14680064, 3866624, 5046272, 11403264, 2752512, 16056320, 11534336, - 13107200, 15400960, 12255232, 3932160, 8585216, 5439488, 10027008, 6356992, - 1507328, 2818048, 262144, 8257536, 12189696, 7798784, 14024704, 2490368, - 14745600, 6881280, 1310720, 6488064, 5570560, 2162688, 786432, 8192000, - - // s_iTF4 - 1375731712, 150994944, 1778384896, -721420288, 805306368, 905969664, -1526726656, 939524096, - -1090519040, 1073741824, -1560281088, -1644167168, -2130706432, -218103808, -687865856, -83886080, - 2080374784, -486539264, 956301312, -2113929216, -1694498816, 788529152, -16777216, -2030043136, - 872415232, -1912602624, 1124073472, 1140850688, -1006632960, -570425344, -385875968, -889192448, - 1409286144, 2063597568, -1811939328, 838860800, -1509949440, -1040187392, 587202560, 1023410176, - -301989888, 1275068416, -1795162112, 184549376, 1107296256, -100663296, -1023410176, 1308622848, - 134217728, 771751936, -1593835520, 1711276032, 671088640, -654311424, 603979776, -1308622848, - 1979711488, 1526726656, -1577058304, 1224736768, 1828716544, -1962934272, -788529152, 620756992, - 1912602624, -134217728, -167772160, 1677721600, -2046820352, 1744830464, -1744830464, 369098752, - -738197504, -1543503872, 1543503872, -872415232, 1560281088, 1694498816, -1241513984, -1845493760, - 1811939328, 1879048192, 1207959552, 1342177280, -50331648, -318767104, -1191182336, -637534208, - 1577058304, 352321536, 1174405120, 1459617792, -1493172224, -1929379840, -1660944384, -2080374784, - -1879048192, -671088640, -1426063360, 0, -1946157056, -1140850688, -754974720, 167772160, - -150994944, -469762048, 1476395008, 83886080, -1207959552, -1291845632, 1157627904, 100663296, - -805306368, 738197504, 503316480, -1895825408, -905969664, 1056964608, 251658240, 33554432, - -1056964608, -1358954496, -1124073472, 50331648, 16777216, 318767104, -1979711488, 1795162112, - 973078528, -1862270976, 285212672, 1090519040, 1325400064, 1728053248, -603979776, -369098752, - -1761607680, -234881024, -822083584, -838860800, -268435456, -1275068416, -436207616, 1929379840, - -1778384896, -1409286144, 1946157056, 570425344, -419430400, -1392508928, 889192448, -2063597568, - -503316480, -117440512, 922746880, -402653184, 469762048, 1962934272, -553648128, 1845493760, - 1191182336, -251658240, 436207616, 1895825408, 486539264, 687865856, -989855744, -1996488704, - 1862270976, -1224736768, 1644167168, 234881024, -1442840576, 402653184, -1107296256, 452984832, - -67108864, 1442840576, 1040187392, 1258291200, -973078528, -771751936, 2030043136, 536870912, - -1711276032, -620756992, -1073741824, -33554432, 2013265920, -855638016, 1509949440, -201326592, - 520093696, -587202560, -1476395008, 855638016, -2013265920, 117440512, -956301312, 822083584, - -1325400064, 301989888, 268435456, 1493172224, 654311424, -2147483648, -335544320, 1593835520, - 1610612736, 1358954496, 2130706432, -1459617792, 419430400, -1258291200, 1241513984, 218103808, - 754974720, -452984832, 2046820352, -1627389952, -1828716544, -922746880, -1677721600, -285212672, - -1610612736, -536870912, 989855744, 1291845632, -1375731712, 704643072, -184549376, -1342177280, - -939524096, -352321536, -1157627904, 1006632960, -2097152000, 1392508928, -1728053248, 1627389952, - 385875968, 721420288, 67108864, 2113929216, -1174405120, 1996488704, -704643072, 637534208, - -520093696, 1761607680, 335544320, 1660944384, 1426063360, 553648128, 201326592, 2097152000 }; - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs deleted file mode 100644 index 08f46f5dad12a0..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesSubtleCryptoTransform.Browser.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using Internal.Cryptography; - -namespace System.Security.Cryptography -{ - internal sealed class AesSubtleCryptoTransform : BasicSymmetricCipher, ILiteSymmetricCipher - { - private const int BlockSizeBytes = AesImplementation.BlockSizeBytes; - - private readonly bool _encrypting; - - private readonly byte[] _key; - private byte[]? _lastBlockBuffer; - - public AesSubtleCryptoTransform(byte[] key, - byte[] iv, - bool encrypting) - : base(iv, BlockSizeBytes, BlockSizeBytes) - { - _encrypting = encrypting; - - // iv is guaranteed to be cloned before this method, but not key - _key = key.CloneByteArray(); - } - - public AesSubtleCryptoTransform(ReadOnlySpan key, - ReadOnlySpan iv, - bool encrypting) - : base(iv.ToArray(), BlockSizeBytes, BlockSizeBytes) - { - _encrypting = encrypting; - - _key = key.ToArray(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - // We need to always zeroize the following fields because they contain sensitive data - CryptographicOperations.ZeroMemory(_key); - CryptographicOperations.ZeroMemory(_lastBlockBuffer); - } - - base.Dispose(disposing); - } - - public override int Transform(ReadOnlySpan input, Span output) => - Transform(input, output, isFinal: false); - - public override int TransformFinal(ReadOnlySpan input, Span output) - { - int bytesWritten = Transform(input, output, isFinal: true); - Reset(); - return bytesWritten; - } - - private int Transform(ReadOnlySpan input, Span output, bool isFinal) - { - Debug.Assert(output.Length >= input.Length); - Debug.Assert(input.Length % BlockSizeInBytes == 0); - - if (input.IsEmpty) - { - return 0; - } - - // Note: SubtleCrypto always uses PKCS7 padding. - - // In order to implement streaming on top of SubtleCrypto's "one shot" API, we have to do the following: - // 1. Remember the last block of cipher text to pass as the "IV" of the next block. - // 2. When encrypting a complete block, PKCS7 padding will always add one block of '0x10' padding bytes. We - // need to strip this padding block off in between Transform calls. This is done by Interop.BrowserCrypto.EncryptDecrypt. - // 3. When decrypting, we need to do the inverse: append an encrypted block of '0x10' padding bytes, so - // SubtleCrypto will decrypt input as a complete message. This is done by Interop.BrowserCrypto.EncryptDecrypt. - - return _encrypting ? - EncryptBlock(input, output, isFinal) : - DecryptBlock(input, output, isFinal); - } - - private int EncryptBlock(ReadOnlySpan input, Span output, bool isFinal) - { - int bytesWritten = EncryptDecrypt(input, output); - - if (!isFinal) - { - SaveLastBlock(output.Slice(0, bytesWritten)); - } - - return bytesWritten; - } - - private int DecryptBlock(ReadOnlySpan input, Span output, bool isFinal) - { - Span lastInputBlockCopy = stackalloc byte[BlockSizeBytes]; - if (!isFinal) - { - // Save the lastInputBlock in a temp buffer first, in case input and output are overlapped - // and decrypting to the output overwrites the input. - ReadOnlySpan lastInputBlock = input.Slice(input.Length - BlockSizeBytes); - lastInputBlock.CopyTo(lastInputBlockCopy); - } - - int numBytesWritten = EncryptDecrypt(input, output); - - if (!isFinal) - { - SaveLastBlock(lastInputBlockCopy); - } - - return numBytesWritten; - } - - private void SaveLastBlock(ReadOnlySpan buffer) - { - Debug.Assert(buffer.Length > 0 && buffer.Length % BlockSizeBytes == 0); - - ReadOnlySpan lastBlock = buffer.Slice(buffer.Length - BlockSizeBytes); - if (_lastBlockBuffer is null) - { - _lastBlockBuffer = lastBlock.ToArray(); - } - else - { - Debug.Assert(_lastBlockBuffer.Length == BlockSizeBytes); - lastBlock.CopyTo(_lastBlockBuffer); - } - } - - private unsafe int EncryptDecrypt(ReadOnlySpan input, Span output) - { - byte[] iv = _lastBlockBuffer ?? IV!; - - fixed (byte* pKey = _key) - fixed (byte* pIV = iv) - fixed (byte* pInput = input) - fixed (byte* pOutput = output) - { - int bytesWritten = Interop.BrowserCrypto.EncryptDecrypt( - _encrypting ? 1 : 0, - pKey, _key.Length, - pIV, iv.Length, - pInput, input.Length, - pOutput, output.Length); - - if (bytesWritten < 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, bytesWritten)); - } - - return bytesWritten; - } - } - - // - // resets the state of the transform - // - - void ILiteSymmetricCipher.Reset(ReadOnlySpan iv) => throw new NotImplementedException(); // never invoked - - private void Reset() - { - CryptographicOperations.ZeroMemory(_lastBlockBuffer); - _lastBlockBuffer = null; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs index 6b52faa7a6d79f..d7397d28223b93 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs @@ -373,20 +373,6 @@ public static void AddAlgorithm(Type algorithm, params string[] names) case "HMACSHA512": case "System.Security.Cryptography.HMACSHA512": return new HMACSHA512(); - -#pragma warning disable SYSLIB0021 // Obsolete: derived cryptographic types - case "AES": - case "System.Security.Cryptography.AesCryptoServiceProvider": - return new AesCryptoServiceProvider(); - case "AesManaged": - case "System.Security.Cryptography.AesManaged": - return new AesManaged(); - case "Rijndael": - case "System.Security.Cryptography.Rijndael": -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - return new RijndaelManaged(); -#pragma warning restore SYSLIB0022 -#pragma warning restore SYSLIB0021 } return null; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs deleted file mode 100644 index 0ca7ccfc212c10..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACHashProvider.Browser.Native.cs +++ /dev/null @@ -1,94 +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.IO; -using System.Diagnostics; -using System.Security.Cryptography; - -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - -namespace System.Security.Cryptography -{ - internal sealed class HMACNativeHashProvider : HashProvider - { - private readonly int _hashSizeInBytes; - private readonly SimpleDigest _hashAlgorithm; - private readonly byte[] _key; - private MemoryStream? _buffer; - - public HMACNativeHashProvider(string hashAlgorithmId, ReadOnlySpan key) - { - Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto); - - (_hashAlgorithm, _hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId); - _key = key.ToArray(); - } - - public override void AppendHashData(ReadOnlySpan data) - { - _buffer ??= new MemoryStream(1000); - _buffer.Write(data); - } - - public override int FinalizeHashAndReset(Span destination) - { - int written = GetCurrentHash(destination); - _buffer = null; - - return written; - } - - public override int GetCurrentHash(Span destination) - { - Debug.Assert(destination.Length >= _hashSizeInBytes); - - ReadOnlySpan source = _buffer != null ? - new ReadOnlySpan(_buffer.GetBuffer(), 0, (int)_buffer.Length) : - default; - - Sign(_hashAlgorithm, _key, source, destination); - - return _hashSizeInBytes; - } - - public static int MacDataOneShot(string hashAlgorithmId, ReadOnlySpan key, ReadOnlySpan data, Span destination) - { - (SimpleDigest hashName, int hashSizeInBytes) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmId); - Debug.Assert(destination.Length >= hashSizeInBytes); - - Sign(hashName, key, data, destination); - - return hashSizeInBytes; - } - - private static unsafe void Sign(SimpleDigest hashName, ReadOnlySpan key, ReadOnlySpan data, Span destination) - { - fixed (byte* k = key) - fixed (byte* src = data) - fixed (byte* dest = destination) - { - int res = Interop.BrowserCrypto.Sign(hashName, k, key.Length, src, data.Length, dest, destination.Length); - if (res != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res)); - } - } - } - - public override int HashSizeInBytes => _hashSizeInBytes; - - public override void Dispose(bool disposing) - { - if (disposing) - { - CryptographicOperations.ZeroMemory(_key); - } - } - - public override void Reset() - { - _buffer = null; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs index 8c604bb32b122f..af0169bebd9d92 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashProviderDispenser.Browser.cs @@ -13,9 +13,7 @@ public static HashProvider CreateHashProvider(string hashAlgorithmId) case HashAlgorithmNames.SHA256: case HashAlgorithmNames.SHA384: case HashAlgorithmNames.SHA512: - return Interop.BrowserCrypto.CanUseSubtleCrypto - ? new SHANativeHashProvider(hashAlgorithmId) - : new SHAManagedHashProvider(hashAlgorithmId); + return new SHAManagedHashProvider(hashAlgorithmId); } throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); } @@ -28,30 +26,16 @@ public static unsafe int MacData( ReadOnlySpan source, Span destination) { - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - return HMACNativeHashProvider.MacDataOneShot(hashAlgorithmId, key, source, destination); - } - else - { - using HashProvider provider = CreateMacProvider(hashAlgorithmId, key); - provider.AppendHashData(source); - return provider.FinalizeHashAndReset(destination); - } + using HashProvider provider = CreateMacProvider(hashAlgorithmId, key); + provider.AppendHashData(source); + return provider.FinalizeHashAndReset(destination); } public static int HashData(string hashAlgorithmId, ReadOnlySpan source, Span destination) { - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - return SHANativeHashProvider.HashOneShot(hashAlgorithmId, source, destination); - } - else - { - HashProvider provider = CreateHashProvider(hashAlgorithmId); - provider.AppendHashData(source); - return provider.FinalizeHashAndReset(destination); - } + HashProvider provider = CreateHashProvider(hashAlgorithmId); + provider.AppendHashData(source); + return provider.FinalizeHashAndReset(destination); } } @@ -63,9 +47,7 @@ public static unsafe HashProvider CreateMacProvider(string hashAlgorithmId, Read case HashAlgorithmNames.SHA256: case HashAlgorithmNames.SHA384: case HashAlgorithmNames.SHA512: - return Interop.BrowserCrypto.CanUseSubtleCrypto - ? new HMACNativeHashProvider(hashAlgorithmId, key) - : new HMACManagedHashProvider(hashAlgorithmId, key); + return new HMACManagedHashProvider(hashAlgorithmId, key); } throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs index dc087adb84736c..b5a59ab8d81c16 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Pbkdf2Implementation.Browser.cs @@ -4,8 +4,6 @@ using System.Diagnostics; using Internal.Cryptography; -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - namespace System.Security.Cryptography { internal static partial class Pbkdf2Implementation @@ -20,41 +18,7 @@ public static void Fill( Debug.Assert(!destination.IsEmpty); Debug.Assert(hashAlgorithmName.Name is not null); - if (Interop.BrowserCrypto.CanUseSubtleCrypto) - { - FillSubtleCrypto(password, salt, iterations, hashAlgorithmName, destination); - } - else - { - FillManaged(password, salt, iterations, hashAlgorithmName, destination); - } - } - - private static unsafe void FillSubtleCrypto( - ReadOnlySpan password, - ReadOnlySpan salt, - int iterations, - HashAlgorithmName hashAlgorithmName, - Span destination) - { - (SimpleDigest hashName, _) = SHANativeHashProvider.HashAlgorithmToPal(hashAlgorithmName.Name!); - - fixed (byte* pPassword = password) - fixed (byte* pSalt = salt) - fixed (byte* pDestination = destination) - { - int result = Interop.BrowserCrypto.DeriveBits( - pPassword, password.Length, - pSalt, salt.Length, - iterations, - hashName, - pDestination, destination.Length); - - if (result != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, result)); - } - } + FillManaged(password, salt, iterations, hashAlgorithmName, destination); } private static void FillManaged( diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs index 1e21e1f00e156f..4c9de50ee6e2ca 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/Rijndael.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; using Internal.Cryptography; namespace System.Security.Cryptography @@ -11,6 +12,7 @@ namespace System.Security.Cryptography [EditorBrowsable(EditorBrowsableState.Never)] public abstract class Rijndael : SymmetricAlgorithm { + [UnsupportedOSPlatform("browser")] public static new Rijndael Create() { return new RijndaelImplementation(); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs index ed33c6554508ca..698926032dfdbb 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelImplementation.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.Versioning; namespace System.Security.Cryptography { @@ -16,6 +17,7 @@ internal sealed class RijndaelImplementation : Rijndael { private readonly Aes _impl; + [UnsupportedOSPlatform("browser")] internal RijndaelImplementation() { LegalBlockSizesValue = new KeySizes[] { new KeySizes(minSize: 128, maxSize: 128, skipSize: 0) }; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs index 6727bdc71bcada..2c866a2459f692 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RijndaelManaged.cs @@ -3,11 +3,13 @@ using System.ComponentModel; using System.Diagnostics; +using System.Runtime.Versioning; namespace System.Security.Cryptography { [Obsolete(Obsoletions.RijndaelMessage, DiagnosticId = Obsoletions.RijndaelDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] + [UnsupportedOSPlatform("browser")] public sealed class RijndaelManaged : Rijndael { private readonly Aes _impl; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs deleted file mode 100644 index d5d53c05e1a0f7..00000000000000 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHAHashProvider.Browser.Native.cs +++ /dev/null @@ -1,98 +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.IO; -using System.Diagnostics; -using System.Security.Cryptography; - -using SimpleDigest = Interop.BrowserCrypto.SimpleDigest; - -namespace System.Security.Cryptography -{ - internal sealed class SHANativeHashProvider : HashProvider - { - private readonly int _hashSizeInBytes; - private readonly SimpleDigest _impl; - private MemoryStream? _buffer; - - public SHANativeHashProvider(string hashAlgorithmId) - { - Debug.Assert(Interop.BrowserCrypto.CanUseSubtleCrypto); - (_impl, _hashSizeInBytes) = HashAlgorithmToPal(hashAlgorithmId); - } - - public override void AppendHashData(ReadOnlySpan data) - { - _buffer ??= new MemoryStream(1000); - _buffer.Write(data); - } - - public override int FinalizeHashAndReset(Span destination) - { - GetCurrentHash(destination); - _buffer = null; - - return _hashSizeInBytes; - } - - public override int GetCurrentHash(Span destination) - { - Debug.Assert(destination.Length >= _hashSizeInBytes); - - ReadOnlySpan source = _buffer != null ? - new ReadOnlySpan(_buffer.GetBuffer(), 0, (int)_buffer.Length) : - default; - - SimpleDigestHash(_impl, source, destination); - - return _hashSizeInBytes; - } - - public static int HashOneShot(string hashAlgorithmId, ReadOnlySpan data, Span destination) - { - (SimpleDigest impl, int hashSizeInBytes) = HashAlgorithmToPal(hashAlgorithmId); - Debug.Assert(destination.Length >= hashSizeInBytes); - - SimpleDigestHash(impl, data, destination); - - return hashSizeInBytes; - } - - private static unsafe void SimpleDigestHash(SimpleDigest hashName, ReadOnlySpan data, Span destination) - { - fixed (byte* src = data) - fixed (byte* dest = destination) - { - int res = Interop.BrowserCrypto.SimpleDigestHash(hashName, src, data.Length, dest, destination.Length); - if (res != 0) - { - throw new CryptographicException(SR.Format(SR.Unknown_SubtleCrypto_Error, res)); - } - } - } - - public override int HashSizeInBytes => _hashSizeInBytes; - - public override void Dispose(bool disposing) - { - } - - public override void Reset() - { - _buffer = null; - } - - internal static (SimpleDigest HashName, int HashSizeInBytes) HashAlgorithmToPal(string hashAlgorithmId) - { - return hashAlgorithmId switch - { - HashAlgorithmNames.SHA256 => (SimpleDigest.Sha256, SHA256.HashSizeInBytes), - HashAlgorithmNames.SHA1 => (SimpleDigest.Sha1, SHA1.HashSizeInBytes), - HashAlgorithmNames.SHA384 => (SimpleDigest.Sha384, SHA384.HashSizeInBytes), - HashAlgorithmNames.SHA512 => (SimpleDigest.Sha512, SHA512.HashSizeInBytes), - _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmId)), - }; - } - } -} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs index 5adab7b88abd37..c293c12374ff26 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs @@ -33,7 +33,7 @@ public static int GetCiphertextLength(int plaintextLength, int paddingSizeInByte } } - public static int PadBlock(ReadOnlySpan block, Span destination, int paddingSizeInBytes, PaddingMode paddingMode) + public static int PadBlock(ReadOnlySpan block, Span destination, int paddingSizeInBytes, PaddingMode paddingMode) { int count = block.Length; int paddingRemainder = count % paddingSizeInBytes; diff --git a/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs b/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs index e38c92d655416b..2db62616df8a5b 100644 --- a/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AesManagedTests.cs @@ -11,6 +11,7 @@ namespace System.Security.Cryptography.Tests /// /// Since AesManaged wraps Aes, we only test minimally here. /// + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class AesManagedTests { [Fact] @@ -26,7 +27,6 @@ public static void VerifyDefaults() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void EncryptDecryptKnownECB192() { byte[] plainTextBytes = diff --git a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs b/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs deleted file mode 100644 index 6e2d7322f53504..00000000000000 --- a/src/libraries/System.Security.Cryptography/tests/AesTests.Browser.cs +++ /dev/null @@ -1,75 +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.Reflection; -using Xunit; - -namespace System.Security.Cryptography.Tests -{ - public partial class AesTests - { - private static byte[] s_plainText = new byte[] { 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59 }; - private static byte[] s_iv = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - private static byte[] s_destination = new byte[s_plainText.Length]; - - [Fact] - public static void AesThrows_PlatformNotSupported_CipherMode_Browser() - { - using (Aes aes = Aes.Create()) - { - Assert.Throws(() => aes.EncryptEcb(s_plainText, PaddingMode.PKCS7)); - Assert.Throws(() => aes.EncryptEcb(s_plainText.AsSpan(), PaddingMode.PKCS7)); - Assert.Throws(() => aes.EncryptEcb(s_plainText.AsSpan(), s_destination, PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText, PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText.AsSpan(), PaddingMode.PKCS7)); - Assert.Throws(() => aes.DecryptEcb(s_plainText.AsSpan(), s_destination, PaddingMode.PKCS7)); - - Assert.Throws(() => aes.EncryptCfb(s_plainText, s_iv)); - Assert.Throws(() => aes.EncryptCfb(s_plainText.AsSpan(), s_iv.AsSpan())); - Assert.Throws(() => aes.EncryptCfb(s_plainText.AsSpan(), s_iv, s_destination)); - Assert.Throws(() => aes.DecryptCfb(s_plainText, s_iv)); - Assert.Throws(() => aes.DecryptCfb(s_plainText.AsSpan(), s_iv.AsSpan())); - Assert.Throws(() => aes.DecryptCfb(s_plainText.AsSpan(), s_iv, s_destination)); - - aes.Mode = CipherMode.ECB; - Assert.Throws(() => aes.CreateEncryptor()); - Assert.Throws(() => aes.CreateEncryptor(s_iv, s_iv)); - Assert.Throws(() => aes.CreateDecryptor()); - Assert.Throws(() => aes.CreateDecryptor(s_iv, s_iv)); - - aes.Mode = CipherMode.CFB; - Assert.Throws(() => aes.CreateEncryptor()); - Assert.Throws(() => aes.CreateEncryptor(s_iv, s_iv)); - Assert.Throws(() => aes.CreateDecryptor()); - Assert.Throws(() => aes.CreateDecryptor(s_iv, s_iv)); - } - } - - // Browser's SubtleCrypto doesn't support AES-192 - [Fact] - public static void Aes_InvalidKeySize_192_Browser() - { - byte[] key192 = new byte[192 / 8]; - using (Aes aes = Aes.Create()) - { - Assert.False(aes.ValidKeySize(192)); - Assert.Throws(() => aes.Key = key192); - Assert.Throws(() => aes.KeySize = 192); - Assert.Throws(() => aes.CreateEncryptor(key192, s_iv)); - Assert.Throws(() => aes.CreateDecryptor(key192, s_iv)); - } - } - - [Fact] - public static void EnsureSubtleCryptoIsUsed() - { - bool canUseSubtleCrypto = (bool)Type.GetType("Interop+BrowserCrypto, System.Security.Cryptography") - .GetField("CanUseSubtleCrypto", BindingFlags.NonPublic | BindingFlags.Static) - .GetValue(null); - - bool expectedCanUseSubtleCrypto = Environment.GetEnvironmentVariable("TEST_EXPECT_SUBTLE_CRYPTO") == "true"; - - Assert.Equal(expectedCanUseSubtleCrypto, canUseSubtleCrypto); - } - } -} diff --git a/src/libraries/System.Security.Cryptography/tests/AesTests.cs b/src/libraries/System.Security.Cryptography/tests/AesTests.cs index e9ee4b435818be..815014cc908b76 100644 --- a/src/libraries/System.Security.Cryptography/tests/AesTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AesTests.cs @@ -5,6 +5,7 @@ namespace System.Security.Cryptography.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public partial class AesTests { [Fact] diff --git a/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs index ca40479e6c2583..0a0011d1646c5e 100644 --- a/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/AsymmetricAlgorithmTests.cs @@ -370,7 +370,7 @@ public static void ExportPem_ExportEncryptedPkcs8PrivateKeyPem() PbeParameters expectedPbeParameters = new PbeParameters( PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA384, - RandomNumberGenerator.GetInt32(0, 100_000)); + RandomNumberGenerator.GetInt32(1, 100_000)); byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan password, PbeParameters pbeParameters) { @@ -407,7 +407,7 @@ public static void ExportPem_TryExportEncryptedPkcs8PrivateKeyPem() PbeParameters expectedPbeParameters = new PbeParameters( PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA384, - RandomNumberGenerator.GetInt32(0, 100_000)); + RandomNumberGenerator.GetInt32(1, 100_000)); bool TryExportEncryptedPkcs8PrivateKey( ReadOnlySpan password, diff --git a/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs b/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs index babea22f6cdaf0..964b4668a61a90 100644 --- a/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs @@ -106,36 +106,23 @@ public static void NamedKeyedHashAlgorithmCreate(string identifier, Type actualT } } - public static IEnumerable NamedSymmetricAlgorithmCreateData - { - get - { - yield return new object[] { "AES", typeof(Aes) }; -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "Rijndael", typeof(Rijndael) }; - yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(Rijndael) }; -#pragma warning restore SYSLIB0022 - - if (PlatformDetection.IsNotBrowser) - { + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/37669", TestPlatforms.Browser)] + [InlineData("AES", typeof(Aes))] #pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes128-cbc", typeof(Rijndael) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes192-cbc", typeof(Rijndael) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#aes256-cbc", typeof(Rijndael) }; + [InlineData("Rijndael", typeof(Rijndael))] + [InlineData("System.Security.Cryptography.Rijndael", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes128-cbc", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes192-cbc", typeof(Rijndael))] + [InlineData("http://www.w3.org/2001/04/xmlenc#aes256-cbc", typeof(Rijndael))] #pragma warning restore SYSLIB0022 - yield return new object[] { "3DES", typeof(TripleDES) }; - yield return new object[] { "TripleDES", typeof(TripleDES) }; - yield return new object[] { "System.Security.Cryptography.TripleDES", typeof(TripleDES) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#tripledes-cbc", typeof(TripleDES) }; - yield return new object[] { "DES", typeof(DES) }; - yield return new object[] { "System.Security.Cryptography.DES", typeof(DES) }; - yield return new object[] { "http://www.w3.org/2001/04/xmlenc#des-cbc", typeof(DES) }; - } - } - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBuiltWithAggressiveTrimming))] - [MemberData(nameof(NamedSymmetricAlgorithmCreateData))] + [InlineData("3DES", typeof(TripleDES))] + [InlineData("TripleDES", typeof(TripleDES))] + [InlineData("System.Security.Cryptography.TripleDES", typeof(TripleDES))] + [InlineData("http://www.w3.org/2001/04/xmlenc#tripledes-cbc", typeof(TripleDES))] + [InlineData("DES", typeof(DES))] + [InlineData("System.Security.Cryptography.DES", typeof(DES))] + [InlineData("http://www.w3.org/2001/04/xmlenc#des-cbc", typeof(DES))] public static void NamedSymmetricAlgorithmCreate(string identifier, Type baseType) { using (SymmetricAlgorithm created = SymmetricAlgorithm.Create(identifier)) @@ -371,15 +358,6 @@ public static IEnumerable AllValidNames yield return new object[] { "HMACSHA512", "System.Security.Cryptography.HMACSHA512", true }; yield return new object[] { "System.Security.Cryptography.HMACSHA512", null, true }; - yield return new object[] { "AES", "System.Security.Cryptography.AesCryptoServiceProvider", true }; - yield return new object[] { "System.Security.Cryptography.AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; - yield return new object[] { "AesManaged", typeof(AesManaged).FullName, true }; - yield return new object[] { "System.Security.Cryptography.AesManaged", typeof(AesManaged).FullName, true }; -#pragma warning disable SYSLIB0022 // Rijndael types are obsolete - yield return new object[] { "Rijndael", typeof(RijndaelManaged).FullName, true }; - yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(RijndaelManaged).FullName, true }; -#pragma warning restore SYSLIB0022 // Rijndael types are obsolete - if (PlatformDetection.IsBrowser) { // Hash functions @@ -444,9 +422,15 @@ public static IEnumerable AllValidNames yield return new object[] { "RC2", "System.Security.Cryptography.RC2CryptoServiceProvider", true }; yield return new object[] { "System.Security.Cryptography.RC2", "System.Security.Cryptography.RC2CryptoServiceProvider", true }; #pragma warning disable SYSLIB0022 // Rijndael types are obsolete + yield return new object[] { "Rijndael", typeof(RijndaelManaged).FullName, true }; + yield return new object[] { "System.Security.Cryptography.Rijndael", typeof(RijndaelManaged).FullName, true }; yield return new object[] { "System.Security.Cryptography.SymmetricAlgorithm", typeof(RijndaelManaged).FullName, true }; #pragma warning restore SYSLIB0022 // Rijndael types are obsolete + yield return new object[] { "AES", "System.Security.Cryptography.AesCryptoServiceProvider", true }; yield return new object[] { "AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; + yield return new object[] { "System.Security.Cryptography.AesCryptoServiceProvider", "System.Security.Cryptography.AesCryptoServiceProvider", true }; + yield return new object[] { "AesManaged", typeof(AesManaged).FullName, true }; + yield return new object[] { "System.Security.Cryptography.AesManaged", typeof(AesManaged).FullName, true }; // Xml Dsig/ Enc Hash algorithms yield return new object[] { "http://www.w3.org/2000/09/xmldsig#sha1", "System.Security.Cryptography.SHA1CryptoServiceProvider", true }; diff --git a/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs b/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs index 21541682781985..dbde519a416493 100644 --- a/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/PaddingModeTests.cs @@ -10,6 +10,7 @@ namespace System.Security.Cryptography.Tests { + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public static class PaddingModeTests { [Theory] diff --git a/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs b/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs index b35973aa377601..d8e9f975cae498 100644 --- a/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/RijndaelTests.cs @@ -13,6 +13,7 @@ namespace System.Security.Cryptography.Tests /// Since RijndaelImplementation (from Rijndael.Create()) and RijndaelManaged classes wrap Aes, /// we only test minimally here. /// + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] public class RijndaelTests { [Fact] @@ -89,7 +90,6 @@ public static void VerifyBlocksizeIVNulling() } [Fact] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.ECB is not supported on Browser")] public static void EncryptDecryptKnownECB192() { static void test(Rijndael alg) @@ -300,7 +300,6 @@ public static void MultipleBlockDecryptTransform(bool blockAlignedOutput) [InlineData(128)] [InlineData(8)] [InlineData(null)] - [SkipOnPlatform(TestPlatforms.Browser, "CipherMode.CFB is not supported on Browser")] public static void CfbFeedbackSizeIsRespected(int? feedbackSize) { // Windows 7 CFB only supports CFB8. diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 9b95e193b873a5..50e110c0445dba 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -13,10 +13,6 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - - $(WasmXHarnessMonoArgs) --setenv=TEST_EXPECT_SUBTLE_CRYPTO=true - $(WasmXHarnessArgs) --web-server-use-cop - true true @@ -313,9 +309,6 @@ - - - diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index d9698c71ce3f00..68b0bf76bf9757 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -152,7 +152,9 @@ private void AddSource(string fileName, string source, bool isRootContextDef = f bool isInGlobalNamespace = @namespace == JsonConstants.GlobalNamespaceValue; StringBuilder sb = new(@"// -#nullable enable + +#nullable enable annotations +#nullable disable warnings // Suppress warnings about [Obsolete] member usage in generated code. #pragma warning disable CS0618"); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 7e97443adfabc4..b89fcf34ed5ce0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -380,6 +380,37 @@ public static void SupportsGenericParameterWithCustomConverterFactory() Assert.Equal(@"[""Cee""]", json); } + // Regression test for https://github.com/dotnet/runtime/issues/74652 + [Fact] + public static void ClassWithStringValuesRoundtrips() + { + JsonSerializerOptions options = ClassWithStringValuesContext.Default.Options; + + ClassWithStringValues obj = new() + { + StringValuesProperty = new(new[] { "abc", "def" }) + }; + + string json = JsonSerializer.Serialize(obj, options); + Assert.Equal("""{"StringValuesProperty":["abc","def"]}""", json); + } + + // Regression test for https://github.com/dotnet/runtime/issues/61734 + [Fact] + public static void ClassWithDictionaryPropertyRoundtrips() + { + JsonSerializerOptions options = ClassWithDictionaryPropertyContext.Default.Options; + + ClassWithDictionaryProperty obj = new(new Dictionary() + { + ["foo"] = "bar", + ["test"] = "baz", + }); + + string json = JsonSerializer.Serialize(obj, options); + Assert.Equal("""{"DictionaryProperty":{"foo":"bar","test":"baz"}}""", json); + } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum TestEnum { @@ -394,7 +425,16 @@ internal partial class GenericParameterWithCustomConverterFactoryContext : JsonS [JsonSerializable(typeof(ClassWithPocoListDictionaryAndNullable))] internal partial class ClassWithPocoListDictionaryAndNullablePropertyContext : JsonSerializerContext { + } + [JsonSerializable(typeof(ClassWithStringValues))] + internal partial class ClassWithStringValuesContext : JsonSerializerContext + { + } + + [JsonSerializable(typeof(ClassWithDictionaryProperty))] + internal partial class ClassWithDictionaryPropertyContext : JsonSerializerContext + { } internal class ClassWithPocoListDictionaryAndNullable diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index a759efb7739842..1b9e0d17b199e8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -112,6 +112,10 @@ + + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index 4a52c8ce8179b0..eb4da1df79f20e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using Microsoft.Extensions.Primitives; namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes { @@ -275,4 +276,15 @@ public class PublicTestClass { internal class InternalNestedClass { } } + + public sealed class ClassWithStringValues + { + public StringValues StringValuesProperty { get; set; } + } + + public class ClassWithDictionaryProperty + { + public ClassWithDictionaryProperty(Dictionary property) => DictionaryProperty = property; + public Dictionary DictionaryProperty { get; } + } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs index ee8e487dd96ad9..3ed8102ed49d1f 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs @@ -1606,22 +1606,33 @@ static bool CanCombineCounts(int nodeMin, int nodeMax, int nextMin, int nextMax) // Coalescing a loop with its same type case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy when nextNode.Kind == currentNode.Kind && currentNode.Ch == nextNode.Ch: case RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy when nextNode.Kind == currentNode.Kind && currentNode.Str == nextNode.Str: - if (CanCombineCounts(currentNode.M, currentNode.N, nextNode.M, nextNode.N)) + if (nextNode.M > 0 && + currentNode.Kind is RegexNodeKind.Oneloopatomic or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Setloopatomic) { - currentNode.M += nextNode.M; - if (currentNode.N != int.MaxValue) - { - currentNode.N = nextNode.N == int.MaxValue ? int.MaxValue : currentNode.N + nextNode.N; - } - next++; - continue; + // Atomic loops can only be combined if the second loop has no lower bound, as if it has a lower bound, + // combining them changes behavior. Uncombined, the first loop can consume all matching items; + // the second loop might then not be able to meet its minimum and fail. But if they're combined, the combined + // minimum of the sole loop could now be met, introducing matches where there shouldn't have been any. + break; } - break; + + if (!CanCombineCounts(currentNode.M, currentNode.N, nextNode.M, nextNode.N)) + { + break; + } + + currentNode.M += nextNode.M; + if (currentNode.N != int.MaxValue) + { + currentNode.N = nextNode.N == int.MaxValue ? int.MaxValue : currentNode.N + nextNode.N; + } + next++; + continue; // Coalescing a loop with an additional item of the same type - case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.One && currentNode.Ch == nextNode.Ch: - case RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy when nextNode.Kind == RegexNodeKind.Notone && currentNode.Ch == nextNode.Ch: - case RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy when nextNode.Kind == RegexNodeKind.Set && currentNode.Str == nextNode.Str: + case RegexNodeKind.Oneloop or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.One && currentNode.Ch == nextNode.Ch: + case RegexNodeKind.Notoneloop or RegexNodeKind.Notonelazy when nextNode.Kind == RegexNodeKind.Notone && currentNode.Ch == nextNode.Ch: + case RegexNodeKind.Setloop or RegexNodeKind.Setlazy when nextNode.Kind == RegexNodeKind.Set && currentNode.Str == nextNode.Str: if (CanCombineCounts(currentNode.M, currentNode.N, 1, 1)) { currentNode.M++; @@ -1635,7 +1646,7 @@ static bool CanCombineCounts(int nodeMin, int nodeMax, int nextMin, int nextMax) break; // Coalescing a loop with a subsequent string - case RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.Multi && currentNode.Ch == nextNode.Str![0]: + case RegexNodeKind.Oneloop or RegexNodeKind.Onelazy when nextNode.Kind == RegexNodeKind.Multi && currentNode.Ch == nextNode.Str![0]: { // Determine how many of the multi's characters can be combined. // We already checked for the first, so we know it's at least one. @@ -2056,15 +2067,15 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i case RegexNodeKind.Multi when node.Ch != subsequent.Str![0]: case RegexNodeKind.End: case RegexNodeKind.EndZ or RegexNodeKind.Eol when node.Ch != '\n': - case RegexNodeKind.Boundary when RegexCharClass.IsBoundaryWordChar(node.Ch): - case RegexNodeKind.NonBoundary when !RegexCharClass.IsBoundaryWordChar(node.Ch): - case RegexNodeKind.ECMABoundary when RegexCharClass.IsECMAWordChar(node.Ch): - case RegexNodeKind.NonECMABoundary when !RegexCharClass.IsECMAWordChar(node.Ch): return true; case RegexNodeKind.Onelazy or RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic when subsequent.M == 0 && node.Ch != subsequent.Ch: case RegexNodeKind.Notonelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic when subsequent.M == 0 && node.Ch == subsequent.Ch: case RegexNodeKind.Setlazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic when subsequent.M == 0 && !RegexCharClass.CharInClass(node.Ch, subsequent.Str!): + case RegexNodeKind.Boundary when RegexCharClass.IsBoundaryWordChar(node.Ch): + case RegexNodeKind.NonBoundary when !RegexCharClass.IsBoundaryWordChar(node.Ch): + case RegexNodeKind.ECMABoundary when RegexCharClass.IsECMAWordChar(node.Ch): + case RegexNodeKind.NonECMABoundary when !RegexCharClass.IsECMAWordChar(node.Ch): // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. break; @@ -2103,14 +2114,14 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i case RegexNodeKind.Multi when !RegexCharClass.CharInClass(subsequent.Str![0], node.Str!): case RegexNodeKind.End: case RegexNodeKind.EndZ or RegexNodeKind.Eol when !RegexCharClass.CharInClass('\n', node.Str!): - case RegexNodeKind.Boundary when node.Str is RegexCharClass.WordClass or RegexCharClass.DigitClass: - case RegexNodeKind.NonBoundary when node.Str is RegexCharClass.NotWordClass or RegexCharClass.NotDigitClass: - case RegexNodeKind.ECMABoundary when node.Str is RegexCharClass.ECMAWordClass or RegexCharClass.ECMADigitClass: - case RegexNodeKind.NonECMABoundary when node.Str is RegexCharClass.NotECMAWordClass or RegexCharClass.NotDigitClass: return true; case RegexNodeKind.Onelazy or RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic when subsequent.M == 0 && !RegexCharClass.CharInClass(subsequent.Ch, node.Str!): case RegexNodeKind.Setlazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic when subsequent.M == 0 && !RegexCharClass.MayOverlap(node.Str!, subsequent.Str!): + case RegexNodeKind.Boundary when node.Str is RegexCharClass.WordClass or RegexCharClass.DigitClass: + case RegexNodeKind.NonBoundary when node.Str is RegexCharClass.NotWordClass or RegexCharClass.NotDigitClass: + case RegexNodeKind.ECMABoundary when node.Str is RegexCharClass.ECMAWordClass or RegexCharClass.ECMADigitClass: + case RegexNodeKind.NonECMABoundary when node.Str is RegexCharClass.NotECMAWordClass or RegexCharClass.NotDigitClass: // The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well. break; @@ -2125,8 +2136,6 @@ private static bool CanBeMadeAtomic(RegexNode node, RegexNode subsequent, bool i // We only get here if the node could be made atomic based on subsequent but subsequent has a lower bound of zero // and thus we need to move subsequent to be the next node in sequence and loop around to try again. - Debug.Assert(subsequent.Kind is RegexNodeKind.Oneloop or RegexNodeKind.Oneloopatomic or RegexNodeKind.Onelazy or RegexNodeKind.Notoneloop or RegexNodeKind.Notoneloopatomic or RegexNodeKind.Notonelazy or RegexNodeKind.Setloop or RegexNodeKind.Setloopatomic or RegexNodeKind.Setlazy); - Debug.Assert(subsequent.M == 0); if (!iterateNullableSubsequent) { return false; diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs index 70390343c3405c..89650f3e27a69a 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs @@ -448,13 +448,13 @@ private int FindEndPosition inputForInnerLoop = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? - input.Slice(0, pos + CharsPerTimeoutCheck) : - input; + int innerLoopLength = _checkTimeout && input.Length - pos > CharsPerTimeoutCheck ? + pos + CharsPerTimeoutCheck : + input.Length; bool done = currentState.NfaState is not null ? - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : - FindEndPositionDeltas(inputForInnerLoop, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate) : + FindEndPositionDeltas(input, innerLoopLength, mode, ref pos, ref currentState, ref endPos, ref endStateId, ref initialStatePos, ref initialStatePosCandidate); // If the inner loop indicates that the search finished (for example due to reaching a deadend state) or // there is no more input available, then the whole search is done. @@ -466,7 +466,7 @@ private int FindEndPosition - private bool FindEndPositionDeltas(ReadOnlySpan input, RegexRunnerMode mode, + private bool FindEndPositionDeltas(ReadOnlySpan input, int length, RegexRunnerMode mode, ref int posRef, ref CurrentState state, ref int endPosRef, ref int endStateIdRef, ref int initialStatePosRef, ref int initialStatePosCandidateRef) where TStateHandler : struct, IStateHandler where TInputReader : struct, IInputReader @@ -561,7 +561,7 @@ private bool FindEndPositionDeltas= length || !TStateHandler.TryTakeTransition(this, ref state, positionId)) { return false; } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs index 02182bd87ede6e..b5b27236a56a7d 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs @@ -149,6 +149,15 @@ public static IEnumerable Match_MemberData() yield return (Case("(?>[^z]+)z"), "zzzzxyxyxyz123", options, 4, 9, true, "xyxyxyz"); yield return (Case("(?>(?>[^z]+))z"), "zzzzxyxyxyz123", options, 4, 9, true, "xyxyxyz"); yield return (Case("(?>[^z]*)z123"), "zzzzxyxyxyz123", options, 4, 10, true, "xyxyxyz123"); + yield return (Case("(?>a*)a"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>a*)a+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>a+)a+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.*)."), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.*).+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>.+).+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w*)\\w"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w*)\\w+"), "aaa", options, 0, 3, false, ""); + yield return (Case("(?>\\w+)\\w+"), "aaa", options, 0, 3, false, ""); yield return (Case("(?>[^12]+)1"), "121231", options, 0, 6, true, "31"); yield return (Case("(?>[^123]+)1"), "12312341", options, 0, 8, true, "41"); @@ -1269,6 +1278,22 @@ public void Match_CachedPattern_NewTimeoutApplies(RegexOptions options) Assert.InRange(sw.Elapsed.TotalSeconds, 0, 10); // arbitrary upper bound that should be well above what's needed with a 1ms timeout } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + public void NonBacktracking_NoEndAnchorMatchAtTimeoutCheck() + { + // This constant must be at least as large as the one in the implementation that sets the maximum number + // of innermost loop iterations between timeout checks. + const int CharsToTriggerTimeoutCheck = 10000; + // Check that it is indeed large enough to trigger timeouts. If this fails the constant above needs to be larger. + Assert.Throws(() => new Regex("a*", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromTicks(1)) + .Match(new string('a', CharsToTriggerTimeoutCheck))); + + // The actual test: ^a*$ shouldn't match in a string ending in 'b' + Regex testPattern = new Regex("^a*$", RegexHelpers.RegexOptionNonBacktracking, TimeSpan.FromHours(1)); + string input = string.Concat(new string('a', CharsToTriggerTimeoutCheck), 'b'); + Assert.False(testPattern.IsMatch(input)); + } + public static IEnumerable Match_Advanced_TestData() { foreach (RegexEngine engine in RegexHelpers.AvailableEngines) diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs index 744fd174351171..bd0b1eab45ce37 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.MultipleMatches.Tests.cs @@ -276,6 +276,22 @@ public static IEnumerable Matches_TestData() } }; + yield return new object[] + { + engine, + @"\w*\b\w+", "abc def ghij kl m nop qrstuv", RegexOptions.None, + new[] + { + new CaptureData("abc", 0, 3), + new CaptureData("def", 4, 3), + new CaptureData("ghij", 8, 4), + new CaptureData("kl", 13, 2), + new CaptureData("m", 16, 1), + new CaptureData("nop", 18, 3), + new CaptureData("qrstuv", 22, 6), + } + }; + if (!PlatformDetection.IsNetFramework) { // .NET Framework missing fix in https://github.com/dotnet/runtime/pull/1075 @@ -294,6 +310,20 @@ public static IEnumerable Matches_TestData() if (!RegexHelpers.IsNonBacktracking(engine)) { + yield return new object[] + { + engine, + @"(\b(?!ab|nop)\w*\b)\w+", "abc def ghij kl m nop qrstuv", RegexOptions.None, + new[] + { + new CaptureData("def", 4, 3), + new CaptureData("ghij", 8, 4), + new CaptureData("kl", 13, 2), + new CaptureData("m", 16, 1), + new CaptureData("qrstuv", 22, 6), + } + }; + yield return new object[] { engine, diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs index b21b26071bc2e6..dbb22cf5026485 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs @@ -50,26 +50,14 @@ public class RegexReductionTests // Two atomic one loops [InlineData("(?>a*)(?>a*)", "(?>a*)")] [InlineData("(?>a*)(?>(?:a*))", "(?>a*)")] - [InlineData("(?>a*)(?>a+)", "(?>a+)")] [InlineData("(?>a*)(?>a?)", "(?>a*)")] - [InlineData("(?>a*)(?>a{1,3})", "(?>a+)")] [InlineData("(?>a+)(?>a*)", "(?>a+)")] - [InlineData("(?>a+)(?>a+)", "(?>a{2,})")] [InlineData("(?>a+)(?>a?)", "(?>a+)")] - [InlineData("(?>a+)(?>a{1,3})", "(?>a{2,})")] [InlineData("(?>a?)(?>a*)", "(?>a*)")] - [InlineData("(?>a?)(?>a+)", "(?>a+)")] [InlineData("(?>a?)(?>a?)", "(?>a{0,2})")] - [InlineData("(?>a?)(?>a{1,3})", "(?>a{1,4})")] [InlineData("(?>a{1,3})(?>a*)", "(?>a+)")] - [InlineData("(?>a{1,3})(?>a+)", "(?>a{2,})")] [InlineData("(?>a{1,3})(?>a?)", "(?>a{1,4})")] - [InlineData("(?>a{1,3})(?>a{1,3})", "(?>a{2,6})")] - // Atomic one loop and one - [InlineData("(?>a*)a", "(?>a+)")] - [InlineData("(?>a+)a", "(?>a{2,})")] - [InlineData("(?>a?)a", "(?>a{1,2})")] - [InlineData("(?>a{1,3})a", "(?>a{2,4})")] + // One and atomic one loop [InlineData("a(?>a*)", "(?>a+)")] [InlineData("a(?>a+)", "(?>a{2,})")] [InlineData("a(?>a?)", "(?>a{1,2})")] @@ -136,21 +124,13 @@ public class RegexReductionTests [InlineData("[^a]{1,3}?[^a]{1,3}?", "[^a]{2,6}?")] // Two atomic notone loops [InlineData("(?>[^a]*)(?>[^a]*)", "(?>[^a]*)")] - [InlineData("(?>[^a]*)(?>[^a]+)", "(?>[^a]+)")] [InlineData("(?>[^a]*)(?>[^a]?)", "(?>[^a]*)")] - [InlineData("(?>[^a]*)(?>[^a]{1,3})", "(?>[^a]+)")] [InlineData("(?>[^a]+)(?>[^a]*)", "(?>[^a]+)")] - [InlineData("(?>[^a]+)(?>[^a]+)", "(?>[^a]{2,})")] [InlineData("(?>[^a]+)(?>[^a]?)", "(?>[^a]+)")] - [InlineData("(?>[^a]+)(?>[^a]{1,3})", "(?>[^a]{2,})")] [InlineData("(?>[^a]?)(?>[^a]*)", "(?>[^a]*)")] - [InlineData("(?>[^a]?)(?>[^a]+)", "(?>[^a]+)")] [InlineData("(?>[^a]?)(?>[^a]?)", "(?>[^a]{0,2})")] - [InlineData("(?>[^a]?)(?>[^a]{1,3})", "(?>[^a]{1,4})")] [InlineData("(?>[^a]{1,3})(?>[^a]*)", "(?>[^a]+)")] - [InlineData("(?>[^a]{1,3})(?>[^a]+)", "(?>[^a]{2,})")] [InlineData("(?>[^a]{1,3})(?>[^a]?)", "(?>[^a]{1,4})")] - [InlineData("(?>[^a]{1,3})(?>[^a]{1,3})", "(?>[^a]{2,6})")] // Greedy notone loop and notone [InlineData("[^a]*[^a]", "[^a]+")] [InlineData("[^a]+[^a]", "[^a]{2,}")] @@ -169,11 +149,7 @@ public class RegexReductionTests [InlineData("[^a][^a]+?", "[^a]{2,}?")] [InlineData("[^a][^a]??", "[^a]{1,2}?")] [InlineData("[^a][^a]{1,3}?", "[^a]{2,4}?")] - // Atomic notone loop and notone - [InlineData("(?>[^a]*)[^a]", "(?>[^a]+)")] - [InlineData("(?>[^a]+)[^a]", "(?>[^a]{2,})")] - [InlineData("(?>[^a]?)[^a]", "(?>[^a]{1,2})")] - [InlineData("(?>[^a]{1,3})[^a]", "(?>[^a]{2,4})")] + // Notone and atomic notone loop [InlineData("[^a](?>[^a]*)", "(?>[^a]+)")] [InlineData("[^a](?>[^a]+)", "(?>[^a]{2,})")] [InlineData("[^a](?>[^a]?)", "(?>[^a]{1,2})")] @@ -206,11 +182,7 @@ public class RegexReductionTests [InlineData("[0-9][0-9]+", "[0-9]{2,}")] [InlineData("[0-9][0-9]?", "[0-9]{1,2}")] [InlineData("[0-9][0-9]{1,3}", "[0-9]{2,4}")] - // Atomic set loop and set - [InlineData("(?>[0-9]*)[0-9]", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)[0-9]", "(?>[0-9]{2,})")] - [InlineData("(?>[0-9]?)[0-9]", "(?>[0-9]{1,2})")] - [InlineData("(?>[0-9]{1,3})[0-9]", "(?>[0-9]{2,4})")] + // Set and atomic set loop [InlineData("[0-9](?>[0-9]*)", "(?>[0-9]+)")] [InlineData("[0-9](?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("[0-9](?>[0-9]?)", "(?>[0-9]{1,2})")] @@ -234,21 +206,13 @@ public class RegexReductionTests [InlineData("[0-9]{1,3}?[0-9]{1,3}?", "[0-9]{2,6}?")] // Two atomic set loops [InlineData("(?>[0-9]*)(?>[0-9]*)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]*)(?>[0-9]+)", "(?>[0-9]+)")] [InlineData("(?>[0-9]*)(?>[0-9]?)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]*)(?>[0-9]{1,3})", "(?>[0-9]+)")] [InlineData("(?>[0-9]+)(?>[0-9]*)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)(?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]+)(?>[0-9]?)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]+)(?>[0-9]{1,3})", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]?)(?>[0-9]*)", "(?>[0-9]*)")] - [InlineData("(?>[0-9]?)(?>[0-9]+)", "(?>[0-9]+)")] [InlineData("(?>[0-9]?)(?>[0-9]?)", "(?>[0-9]{0,2})")] - [InlineData("(?>[0-9]?)(?>[0-9]{1,3})", "(?>[0-9]{1,4})")] [InlineData("(?>[0-9]{1,3})(?>[0-9]*)", "(?>[0-9]+)")] - [InlineData("(?>[0-9]{1,3})(?>[0-9]+)", "(?>[0-9]{2,})")] [InlineData("(?>[0-9]{1,3})(?>[0-9]?)", "(?>[0-9]{1,4})")] - [InlineData("(?>[0-9]{1,3})(?>[0-9]{1,3})", "(?>[0-9]{2,6})")] // Lazy set loop and set [InlineData("[0-9]*?[0-9]", "[0-9]+?")] [InlineData("[0-9]+?[0-9]", "[0-9]{2,}?")] @@ -381,6 +345,8 @@ public class RegexReductionTests [InlineData("(?:w*)+\\.", "(?>w*)+\\.")] [InlineData("(a[bcd]e*)*fg", "(a[bcd](?>e*))*fg")] [InlineData("(\\w[bcd]\\s*)*fg", "(\\w[bcd](?>\\s*))*fg")] + [InlineData(@"\b(\w+)\b", @"\b((?>\w+))\b")] + [InlineData(@"\b(?:\w+)\b ", @"\b(?>\w+)\b ")] // Nothing handling [InlineData(@"\wabc(?!)def", "(?!)")] [InlineData(@"\wabc(?!)def|ghi(?!)", "(?!)")] @@ -423,6 +389,42 @@ public void PatternsReduceIdentically(string actual, string expected) [InlineData("a*?a*", "a*")] [InlineData("a*[^a]*", "a*")] [InlineData("[^a]*a*", "a*")] + [InlineData("(?>a*)(?>a+)", "(?>a+)")] + [InlineData("(?>a*)(?>a{1,3})", "(?>a+)")] + [InlineData("(?>a+)(?>a+)", "(?>a{2,})")] + [InlineData("(?>a+)(?>a{1,3})", "(?>a{2,})")] + [InlineData("(?>a?)(?>a+)", "(?>a+)")] + [InlineData("(?>a?)(?>a{1,3})", "(?>a{1,4})")] + [InlineData("(?>a{1,3})(?>a+)", "(?>a{2,})")] + [InlineData("(?>a{1,3})(?>a{1,3})", "(?>a{2,6})")] + [InlineData("(?>[^a]*)(?>[^a]+)", "(?>[^a]+)")] + [InlineData("(?>[^a]*)(?>[^a]{1,3})", "(?>[^a]+)")] + [InlineData("(?>[^a]+)(?>[^a]+)", "(?>[^a]{2,})")] + [InlineData("(?>[^a]+)(?>[^a]{1,3})", "(?>[^a]{2,})")] + [InlineData("(?>[^a]?)(?>[^a]+)", "(?>[^a]+)")] + [InlineData("(?>[^a]?)(?>[^a]{1,3})", "(?>[^a]{1,4})")] + [InlineData("(?>[^a]{1,3})(?>[^a]+)", "(?>[^a]{2,})")] + [InlineData("(?>[^a]{1,3})(?>[^a]{1,3})", "(?>[^a]{2,6})")] + [InlineData("(?>[0-9]*)(?>[0-9]+)", "(?>[0-9]+)")] + [InlineData("(?>[0-9]*)(?>[0-9]{1,3})", "(?>[0-9]+)")] + [InlineData("(?>[0-9]+)(?>[0-9]+)", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]+)(?>[0-9]{1,3})", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]?)(?>[0-9]+)", "(?>[0-9]+)")] + [InlineData("(?>[0-9]?)(?>[0-9]{1,3})", "(?>[0-9]{1,4})")] + [InlineData("(?>[0-9]{1,3})(?>[0-9]+)", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]{1,3})(?>[0-9]{1,3})", "(?>[0-9]{2,6})")] + [InlineData("(?>a*)a", "(?>a+)")] + [InlineData("(?>a+)a", "(?>a{2,})")] + [InlineData("(?>a?)a", "(?>a{1,2})")] + [InlineData("(?>a{1,3})a", "(?>a{2,4})")] + [InlineData("(?>[^a]*)[^a]", "(?>[^a]+)")] + [InlineData("(?>[^a]+)[^a]", "(?>[^a]{2,})")] + [InlineData("(?>[^a]?)[^a]", "(?>[^a]{1,2})")] + [InlineData("(?>[^a]{1,3})[^a]", "(?>[^a]{2,4})")] + [InlineData("(?>[0-9]*)[0-9]", "(?>[0-9]+)")] + [InlineData("(?>[0-9]+)[0-9]", "(?>[0-9]{2,})")] + [InlineData("(?>[0-9]?)[0-9]", "(?>[0-9]{1,2})")] + [InlineData("(?>[0-9]{1,3})[0-9]", "(?>[0-9]{2,4})")] [InlineData("a{2147483646}a", "a{2147483647}")] [InlineData("a{2147483647}a", "a{2147483647}")] [InlineData("a{0,2147483646}a", "a{0,2147483647}")] @@ -470,6 +472,10 @@ public void PatternsReduceIdentically(string actual, string expected) [InlineData("(?:ab??){2}", "(?:a(?>b??)){2}")] [InlineData("(?:ab??){2, 3}", "(?:a(?>b??)){2, 3}")] [InlineData("ab??(b)", "a(?>b??)(b)")] + [InlineData(@"\w+\b\w+", @"(?>\w+)\b\w")] + [InlineData(@"\w*\b\w+", @"(?>\w*)\b\w+")] + [InlineData(@"\W+\B\W+", @"(?>\W+)\B\W")] + [InlineData(@"\W*\B\W+", @"(?>\W*)\B\W")] // Loops inside alternation constructs [InlineData("(abc*|def)chi", "(ab(?>c*)|def)chi")] [InlineData("(abc|def*)fhi", "(abc|de(?>f*))fhi")] @@ -505,6 +511,9 @@ public void PatternsReduceDifferently(string actual, string expected) [InlineData(@"a??", RegexOptions.None, 0, 1)] [InlineData(@"a+", RegexOptions.None, 1, null)] [InlineData(@"a+?", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a+", RegexOptions.None, 1, null)] + [InlineData(@"(?>a*)a*", RegexOptions.None, 0, null)] [InlineData(@"a{2}", RegexOptions.None, 2, 2)] [InlineData(@"a{2}?", RegexOptions.None, 2, 2)] [InlineData(@"a{3,17}", RegexOptions.None, 3, 17)] diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs index fe4b0c29c3a627..780eddd2cee2e8 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs @@ -59,9 +59,9 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.Window < TimeSpan.Zero) + if (options.Window <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new FixedWindowRateLimiterOptions @@ -287,7 +287,7 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks) + if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks && !_options.AutoReplenishment) { return; } @@ -295,21 +295,14 @@ private void ReplenishInternal(long nowTicks) _lastReplenishmentTick = nowTicks; int availableRequestCounters = _requestCount; - int maxPermits = _options.PermitLimit; - int resourcesToAdd; - if (availableRequestCounters < maxPermits) - { - resourcesToAdd = maxPermits - availableRequestCounters; - } - else + if (availableRequestCounters >= _options.PermitLimit) { // All counters available, nothing to do return; } - _requestCount += resourcesToAdd; - Debug.Assert(_requestCount == _options.PermitLimit); + _requestCount = _options.PermitLimit; // Process queued requests while (_queue.Count > 0) diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs index 92cac84012c064..8f7dbaa344beba 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class FixedWindowRateLimiterOptions { /// /// Specifies the time window that takes in the requests. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan Window { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs index 1ccf40775e2d87..5dfc36914487e1 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs @@ -26,6 +26,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter private readonly Timer? _renewTimer; private readonly SlidingWindowRateLimiterOptions _options; + private readonly TimeSpan _replenishmentPeriod; private readonly Deque _queue = new Deque(); // Use the queue as the lock field so we don't need to allocate another object for a lock and have another field in the object @@ -42,7 +43,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter public override bool IsAutoReplenishing => _options.AutoReplenishment; /// - public override TimeSpan ReplenishmentPeriod => new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow); + public override TimeSpan ReplenishmentPeriod => _replenishmentPeriod; /// /// Initializes the . @@ -62,9 +63,9 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.Window < TimeSpan.Zero) + if (options.Window <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new SlidingWindowRateLimiterOptions @@ -78,6 +79,7 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options) }; _requestCount = options.PermitLimit; + _replenishmentPeriod = new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow); // _requestsPerSegment holds the no. of acquired requests in each window segment _requestsPerSegment = new int[options.SegmentsPerWindow]; @@ -287,7 +289,7 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks) + if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks && !_options.AutoReplenishment) { return; } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs index 8e1d397a57f11c..93f7ba933b464f 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class SlidingWindowRateLimiterOptions { /// /// Specifies the minimum period between replenishments. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan Window { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs index 7baf91ea590804..f1fbcb4433c4d8 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs @@ -13,7 +13,7 @@ namespace System.Threading.RateLimiting /// public sealed class TokenBucketRateLimiter : ReplenishingRateLimiter { - private int _tokenCount; + private double _tokenCount; private int _queueCount; private long _lastReplenishmentTick; private long? _idleSince; @@ -22,6 +22,7 @@ public sealed class TokenBucketRateLimiter : ReplenishingRateLimiter private long _failedLeasesCount; private long _successfulLeasesCount; + private readonly double _fillRate; private readonly Timer? _renewTimer; private readonly TokenBucketRateLimiterOptions _options; private readonly Deque _queue = new Deque(); @@ -60,9 +61,9 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) { throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options)); } - if (options.ReplenishmentPeriod < TimeSpan.Zero) + if (options.ReplenishmentPeriod <= TimeSpan.Zero) { - throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options)); + throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than TimeSpan.Zero.", nameof(options)); } _options = new TokenBucketRateLimiterOptions @@ -76,6 +77,7 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) }; _tokenCount = options.TokenLimit; + _fillRate = (double)options.TokensPerPeriod / options.ReplenishmentPeriod.Ticks; _idleSince = _lastReplenishmentTick = Stopwatch.GetTimestamp(); @@ -91,7 +93,7 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options) ThrowIfDisposed(); return new RateLimiterStatistics() { - CurrentAvailablePermits = _tokenCount, + CurrentAvailablePermits = (long)_tokenCount, CurrentQueuedCount = _queueCount, TotalFailedLeases = Interlocked.Read(ref _failedLeasesCount), TotalSuccessfulLeases = Interlocked.Read(ref _successfulLeasesCount), @@ -210,7 +212,7 @@ protected override ValueTask AcquireAsyncCore(int tokenCount, Ca private RateLimitLease CreateFailedTokenLease(int tokenCount) { - int replenishAmount = tokenCount - _tokenCount + _queueCount; + int replenishAmount = tokenCount - (int)_tokenCount + _queueCount; // can't have 0 replenish periods, that would mean it should be a successful lease // if TokensPerPeriod is larger than the replenishAmount needed then it would be 0 Debug.Assert(_options.TokensPerPeriod > 0); @@ -278,7 +280,7 @@ private static void Replenish(object? state) limiter!.ReplenishInternal(nowTicks); } - // Used in tests that test behavior with specific time intervals + // Used in tests to avoid dealing with real time private void ReplenishInternal(long nowTicks) { // method is re-entrant (from Timer), lock to avoid multiple simultaneous replenishes @@ -289,37 +291,35 @@ private void ReplenishInternal(long nowTicks) return; } - if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.ReplenishmentPeriod.Ticks) + if (_tokenCount == _options.TokenLimit) { return; } - _lastReplenishmentTick = nowTicks; - - int availablePermits = _tokenCount; - TokenBucketRateLimiterOptions options = _options; - int maxPermits = options.TokenLimit; - int resourcesToAdd; + double add; - if (availablePermits < maxPermits) + // Trust the timer to be close enough to when we want to replenish, this avoids issues with Timer jitter where it might be .99 seconds instead of 1, and 1.1 seconds the next time etc. + if (_options.AutoReplenishment) { - resourcesToAdd = Math.Min(options.TokensPerPeriod, maxPermits - availablePermits); + add = _options.TokensPerPeriod; } else { - // All tokens available, nothing to do - return; + add = _fillRate * (nowTicks - _lastReplenishmentTick) * TickFrequency; } + _tokenCount = Math.Min(_options.TokenLimit, _tokenCount + add); + + _lastReplenishmentTick = nowTicks; + // Process queued requests Deque queue = _queue; - _tokenCount += resourcesToAdd; Debug.Assert(_tokenCount <= _options.TokenLimit); while (queue.Count > 0) { RequestRegistration nextPendingRequest = - options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst + _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst ? queue.PeekHead() : queue.PeekTail(); @@ -327,7 +327,7 @@ private void ReplenishInternal(long nowTicks) { // Request can be fulfilled nextPendingRequest = - options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst + _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst ? queue.DequeueHead() : queue.DequeueTail(); diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs index 55b63f65d36bc5..2c065d9432e67c 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs @@ -10,7 +10,7 @@ public sealed class TokenBucketRateLimiterOptions { /// /// Specifies the minimum period between replenishments. - /// Must be set to a value >= by the time these options are passed to the constructor of . + /// Must be set to a value greater than by the time these options are passed to the constructor of . /// public TimeSpan ReplenishmentPeriod { get; set; } = TimeSpan.Zero; diff --git a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs index 6830a1ce742816..1f597748d67f35 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(); @@ -27,7 +27,7 @@ public override void CanAcquireResource() lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -37,31 +37,49 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - AutoReplenishment = false - })); + { + PermitLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + AutoReplenishment = false + })); Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - Window = TimeSpan.FromMinutes(2), - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + Window = TimeSpan.FromMinutes(2), + AutoReplenishment = false + })); Assert.Throws( () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.MinValue, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.MinValue, + AutoReplenishment = false + })); + Assert.Throws( + () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(-2), + AutoReplenishment = false, + })); + Assert.Throws( + () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.Zero, + AutoReplenishment = false, + })); } [Fact] @@ -72,7 +90,7 @@ public override async Task CanAcquireResourceAsync() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -82,7 +100,7 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait).IsAcquired); } @@ -95,7 +113,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = await limiter.AcquireAsync(); @@ -107,7 +125,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -115,7 +133,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -129,7 +147,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -142,7 +160,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; @@ -151,7 +169,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -165,7 +183,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -174,7 +192,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan)); - Assert.Equal(TimeSpan.Zero, timeSpan); + Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan); } [Fact] @@ -185,7 +203,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -197,7 +215,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -211,7 +229,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -229,7 +247,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -243,7 +261,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -256,7 +274,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -270,7 +288,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -279,14 +297,14 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -299,7 +317,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() PermitLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(int.MaxValue); @@ -315,7 +333,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -328,7 +346,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); Assert.Throws(() => limiter.AttemptAcquire(2)); @@ -342,7 +360,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); await Assert.ThrowsAsync(async () => await limiter.AcquireAsync(2)); @@ -356,7 +374,7 @@ public override void ThrowsWhenAcquiringLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); Assert.Throws(() => limiter.AttemptAcquire(-1)); @@ -370,7 +388,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); await Assert.ThrowsAsync(async () => await limiter.AcquireAsync(-1)); @@ -384,7 +402,7 @@ public override void AcquireZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -400,7 +418,7 @@ public override void AcquireZero_WithoutAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -419,7 +437,7 @@ public override async Task AcquireAsyncZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -435,7 +453,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = await limiter.AcquireAsync(1); @@ -445,7 +463,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -458,7 +476,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = await limiter.AcquireAsync(2); @@ -470,7 +488,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -486,7 +504,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -500,7 +518,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -513,7 +531,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -526,7 +544,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -539,7 +557,7 @@ public override async Task CancelUpdatesQueueLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -555,7 +573,7 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -568,7 +586,7 @@ public override void NoMetadataOnAcquiredLease() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -583,7 +601,7 @@ public override void MetadataNamesContainsAllMetadata() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); using var lease = limiter.AttemptAcquire(1); @@ -598,7 +616,7 @@ public override async Task DisposeReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -631,7 +649,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -770,7 +788,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -785,7 +803,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -799,7 +817,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -811,13 +829,13 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -831,7 +849,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -845,7 +863,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -859,7 +877,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -872,7 +890,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -918,11 +936,11 @@ public override void IdleDurationUpdatesWhenChangingFromActive() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -962,7 +980,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(2); @@ -988,7 +1006,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -1001,7 +1019,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(1); @@ -1026,7 +1044,7 @@ public override void GetStatisticsReturnsNewInstances() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -1049,7 +1067,7 @@ public override async Task GetStatisticsHasCorrectValues() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); @@ -1093,7 +1111,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; // success from wait + available + queue @@ -1112,7 +1130,7 @@ public override async Task GetStatisticsWithZeroPermitCount() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); var lease = limiter.AttemptAcquire(0); @@ -1145,11 +1163,45 @@ public override void GetStatisticsThrowsAfterDispose() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions + { + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + Window = replenishmentPeriod, + AutoReplenishment = true, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(10, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(FixedWindowRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(FixedWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(FixedWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs index 7241a39e0f1f4d..66e6cb2d5f228a 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -28,8 +28,8 @@ public override void CanAcquireResource() lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -39,44 +39,64 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.FromMinutes(2), - SegmentsPerWindow = -1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(2), + SegmentsPerWindow = -1, + AutoReplenishment = false + })); Assert.Throws( () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions - { - PermitLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - Window = TimeSpan.MinValue, - SegmentsPerWindow = 1, - AutoReplenishment = false - })); + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.MinValue, + SegmentsPerWindow = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.FromMinutes(-2), + SegmentsPerWindow = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + Window = TimeSpan.Zero, + SegmentsPerWindow = 1, + AutoReplenishment = false + })); } [Fact] @@ -87,7 +107,7 @@ public override async Task CanAcquireResourceAsync() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -98,14 +118,14 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(2); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); var wait2 = limiter.AcquireAsync(2); Assert.False(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait2).IsAcquired); } @@ -121,7 +141,7 @@ public async Task CanAcquireMultipleRequestsAsync() PermitLimit = 4, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -132,19 +152,19 @@ public async Task CanAcquireMultipleRequestsAsync() var wait = limiter.AcquireAsync(3); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); var wait2 = limiter.AcquireAsync(2); Assert.True(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var wait3 = limiter.AcquireAsync(2); Assert.False(wait3.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait3).IsAcquired); Assert.False((await wait).IsAcquired); @@ -159,7 +179,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -172,10 +192,10 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait1.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -183,8 +203,8 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -198,7 +218,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.FromMinutes(0), + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -212,10 +232,10 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.False(wait2.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; Assert.True(lease.IsAcquired); @@ -223,8 +243,8 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -238,7 +258,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -257,7 +277,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -270,8 +290,8 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -285,7 +305,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -304,8 +324,8 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -319,7 +339,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -333,8 +353,8 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -348,7 +368,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv PermitLimit = 3, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -358,20 +378,20 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(2); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(2); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -385,7 +405,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() PermitLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -402,8 +422,8 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -416,7 +436,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -431,7 +451,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -446,7 +466,7 @@ public override void ThrowsWhenAcquiringLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -461,7 +481,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -476,7 +496,7 @@ public override void AcquireZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -493,7 +513,7 @@ public override void AcquireZero_WithoutAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -513,7 +533,7 @@ public override async Task AcquireAsyncZero_WithAvailability() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -530,7 +550,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -541,8 +561,8 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -555,7 +575,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 4, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -568,8 +588,8 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -585,7 +605,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -600,7 +620,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); } @@ -613,7 +633,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -627,7 +647,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); } @@ -640,7 +660,7 @@ public override async Task CancelUpdatesQueueLimit() PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -657,8 +677,8 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -673,7 +693,7 @@ public override void NoMetadataOnAcquiredLease() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -689,7 +709,7 @@ public override void MetadataNamesContainsAllMetadata() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -705,7 +725,7 @@ public override async Task DisposeReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 1, AutoReplenishment = false }); @@ -739,7 +759,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -809,7 +829,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -825,12 +845,12 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -843,7 +863,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems PermitLimit = 3, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 5, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -856,18 +876,18 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -881,7 +901,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -896,8 +916,8 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -911,7 +931,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -925,8 +945,8 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -974,13 +994,13 @@ public override void IdleDurationUpdatesWhenChangingFromActive() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -1022,7 +1042,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1041,7 +1061,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - limiter.TryReplenish(); + Replenish(limiter, 1L); var wait3 = limiter.AcquireAsync(2); Assert.False(wait3.IsCompleted); @@ -1050,7 +1070,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -1063,7 +1083,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(1), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1089,7 +1109,7 @@ public override void GetStatisticsReturnsNewInstances() PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1113,7 +1133,7 @@ public override async Task GetStatisticsHasCorrectValues() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(2), SegmentsPerWindow = 2, AutoReplenishment = false }); @@ -1138,7 +1158,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(0, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); var lease3 = await limiter.AcquireAsync(1); Assert.False(lease3.IsAcquired); @@ -1156,7 +1176,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; stats = limiter.GetStatistics(); @@ -1174,7 +1194,7 @@ public override async Task GetStatisticsWithZeroPermitCount() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); @@ -1208,12 +1228,57 @@ public override void GetStatisticsThrowsAfterDispose() PermitLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - Window = TimeSpan.Zero, + Window = TimeSpan.FromMilliseconds(3), SegmentsPerWindow = 3, AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions + { + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + Window = replenishmentPeriod, + SegmentsPerWindow = 2, + AutoReplenishment = true, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 - 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(4, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 + 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(SlidingWindowRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(SlidingWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(SlidingWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs index 272c294a09b345..79c368e2b6d34a 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs @@ -17,7 +17,7 @@ public override void CanAcquireResource() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -26,9 +26,10 @@ public override void CanAcquireResource() Assert.True(lease.IsAcquired); Assert.False(limiter.AttemptAcquire().IsAcquired); + // Dispose doesn't change token count lease.Dispose(); Assert.False(limiter.AttemptAcquire().IsAcquired); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True(limiter.AttemptAcquire().IsAcquired); } @@ -38,44 +39,64 @@ public override void InvalidOptionsThrows() { Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = -1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = -1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = 1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = -1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = -1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = 1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.FromMinutes(2), - TokensPerPeriod = -1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMinutes(2), + TokensPerPeriod = -1, + AutoReplenishment = false + })); Assert.Throws( () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions - { - TokenLimit = 1, - QueueProcessingOrder = QueueProcessingOrder.NewestFirst, - QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.MinValue, - TokensPerPeriod = 1, - AutoReplenishment = false - })); + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.MinValue, + TokensPerPeriod = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(-1), + TokensPerPeriod = 1, + AutoReplenishment = false + })); + Assert.Throws( + () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1, + ReplenishmentPeriod = TimeSpan.Zero, + TokensPerPeriod = 1, + AutoReplenishment = false + })); } [Fact] @@ -86,7 +107,7 @@ public override async Task CanAcquireResourceAsync() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -97,7 +118,7 @@ public override async Task CanAcquireResourceAsync() var wait = limiter.AcquireAsync(); Assert.False(wait.IsCompleted); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.True((await wait).IsAcquired); } @@ -110,7 +131,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -123,7 +144,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -131,7 +152,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsOldest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -145,7 +166,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.FromMinutes(0), + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -159,7 +180,7 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); // second queued item completes first with NewestFirst lease = await wait2; @@ -168,8 +189,9 @@ public override async Task CanAcquireResourceAsync_QueuesAndGrabsNewest() lease.Dispose(); Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); + Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); + Replenish(limiter, 1L); lease = await wait1; Assert.True(lease.IsAcquired); @@ -183,7 +205,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -193,7 +215,7 @@ public override async Task FailsWhenQueuingMoreThanLimit_OldestFirst() var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan)); - Assert.Equal(TimeSpan.Zero, timeSpan); + Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan); } [Fact] @@ -204,7 +226,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -217,7 +239,7 @@ public override async Task DropsOldestWhenQueuingMoreThanLimit_NewestFirst() Assert.False(lease1.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -231,7 +253,7 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -250,8 +272,8 @@ public override async Task DropsMultipleOldestWhenQueuingMoreThanLimit_NewestFir Assert.False(lease2.IsAcquired); Assert.False(wait3.IsCompleted); - limiter.TryReplenish(); - limiter.TryReplenish(); + Replenish(limiter, 1L); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); @@ -265,7 +287,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -279,7 +301,7 @@ public override async Task DropsRequestedLeaseIfPermitCountGreaterThanQueueLimit var lease1 = await limiter.AcquireAsync(2); Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -293,7 +315,7 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -303,14 +325,14 @@ public override async Task QueueAvailableAfterQueueLimitHitAndResources_BecomeAv var failedLease = await limiter.AcquireAsync(1); Assert.False(failedLease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -323,7 +345,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() TokenLimit = int.MaxValue, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = int.MaxValue, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = int.MaxValue, AutoReplenishment = false }); @@ -340,7 +362,7 @@ public override async Task LargeAcquiresAndQueuesDoNotIntegerOverflow() var lease1 = await wait; Assert.False(lease1.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); var lease2 = await wait2; Assert.True(lease2.IsAcquired); } @@ -353,7 +375,7 @@ public override void ThrowsWhenAcquiringMoreThanLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -368,7 +390,7 @@ public override async Task ThrowsWhenWaitingForMoreThanLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -383,7 +405,7 @@ public override void ThrowsWhenAcquiringLessThanZero() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -398,7 +420,7 @@ public override async Task ThrowsWhenWaitingForLessThanZero() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -413,7 +435,7 @@ public override void AcquireZero_WithAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -430,7 +452,7 @@ public override void AcquireZero_WithoutAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -450,7 +472,7 @@ public override async Task AcquireAsyncZero_WithAvailability() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -467,7 +489,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -478,7 +500,7 @@ public override async Task AcquireAsyncZero_WithoutAvailabilityWaitsForAvailabil Assert.False(wait.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); using var lease2 = await wait; Assert.True(lease2.IsAcquired); } @@ -491,7 +513,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -504,7 +526,7 @@ public override async Task CanDequeueMultipleResourcesAtOnce() Assert.False(wait2.IsCompleted); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); var lease1 = await wait1; var lease2 = await wait2; @@ -520,7 +542,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -535,7 +557,7 @@ public override async Task CanCancelAcquireAsyncAfterQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -548,7 +570,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -575,7 +597,7 @@ public override async Task CanFillQueueWithNewestFirstAfterCancelingQueuedReques lease = await wait2; Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait3; Assert.True(lease.IsAcquired); } @@ -588,7 +610,7 @@ public override async Task CanDisposeAfterCancelingQueuedRequest() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -614,7 +636,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -628,7 +650,7 @@ public override async Task CanCancelAcquireAsyncBeforeQueuing() Assert.Equal(cts.Token, ex.CancellationToken); lease.Dispose(); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits); } @@ -641,7 +663,7 @@ public override async Task CancelUpdatesQueueLimit() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -658,7 +680,7 @@ public override async Task CancelUpdatesQueueLimit() wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -671,7 +693,7 @@ public override void NoMetadataOnAcquiredLease() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -687,7 +709,7 @@ public override void MetadataNamesContainsAllMetadata() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -703,7 +725,7 @@ public override async Task DisposeReleasesQueuedAcquires() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -737,7 +759,7 @@ public override async Task DisposeAsyncReleasesQueuedAcquires() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); @@ -888,14 +910,14 @@ public async Task CorrectRetryMetadataWithNonZeroAvailableItems() } [Fact] - public void TryReplenishHonorsTokensPerPeriod() + public void ReplenishHonorsTokensPerPeriod() { var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 7, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 3, AutoReplenishment = false }); @@ -903,27 +925,28 @@ public void TryReplenishHonorsTokensPerPeriod() Assert.False(limiter.AttemptAcquire(3).IsAcquired); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(5, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + Replenish(limiter, 1L); Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); } [Fact] - public void TryReplenishWithAllTokensAvailable_Noops() + public async void TryReplenishWithAllTokensAvailable_Noops() { var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(30), TokensPerPeriod = 1, AutoReplenishment = false }); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); - Assert.True(limiter.TryReplenish()); + await Task.Delay(100); + limiter.TryReplenish(); Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits); } @@ -971,7 +994,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -987,7 +1010,7 @@ public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfN Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1001,7 +1024,7 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1014,13 +1037,13 @@ public override async Task CannotAcquireResourcesWithAcquireAsyncWithQueuedItems Assert.False(wait.IsCompleted); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); Assert.False(wait2.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait2; Assert.True(lease.IsAcquired); @@ -1034,7 +1057,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1049,7 +1072,7 @@ public override async Task CanAcquireResourcesWithAcquireWithQueuedItemsIfNewest Assert.True(lease.IsAcquired); Assert.False(wait.IsCompleted); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1063,7 +1086,7 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld TokenLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 3, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1077,14 +1100,12 @@ public override async Task CannotAcquireResourcesWithAcquireWithQueuedItemsIfOld lease = limiter.AttemptAcquire(1); Assert.False(lease.IsAcquired); - limiter.TryReplenish(); + Replenish(limiter, 1L); lease = await wait; Assert.True(lease.IsAcquired); } - private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; - [Fact] public async Task ReplenishWorksWithTicksOverInt32Max() { @@ -1098,16 +1119,16 @@ public async Task ReplenishWorksWithTicksOverInt32Max() AutoReplenishment = false }); + // Ensure next tick is over uint.MaxValue + Replenish(limiter, uint.MaxValue); + var lease = limiter.AttemptAcquire(10); Assert.True(lease.IsAcquired); var wait = limiter.AcquireAsync(1); Assert.False(wait.IsCompleted); - var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; - // Ensure next tick is over uint.MaxValue - var tick = Stopwatch.GetTimestamp() + uint.MaxValue; - replenishInternalMethod.Invoke(limiter, new object[] { tick }); + Replenish(limiter, 2L); lease = await wait; Assert.True(lease.IsAcquired); @@ -1116,11 +1137,11 @@ public async Task ReplenishWorksWithTicksOverInt32Max() Assert.False(wait.IsCompleted); // Tick 1 millisecond too soon and verify that the queued item wasn't completed - replenishInternalMethod.Invoke(limiter, new object[] { tick + 1L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + Replenish(limiter, 1L); Assert.False(wait.IsCompleted); // ticks would wrap if using uint - replenishInternalMethod.Invoke(limiter, new object[] { tick + 2L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + Replenish(limiter, 2L); lease = await wait; Assert.True(lease.IsAcquired); } @@ -1167,12 +1188,12 @@ public override void IdleDurationUpdatesWhenChangingFromActive() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 2, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 1, AutoReplenishment = false }); limiter.AttemptAcquire(1); - limiter.TryReplenish(); + Replenish(limiter, 1L); Assert.NotNull(limiter.IdleDuration); } @@ -1214,7 +1235,7 @@ public override void GetStatisticsReturnsNewInstances() TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 2, AutoReplenishment = false }); @@ -1238,7 +1259,7 @@ public override async Task GetStatisticsHasCorrectValues() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); @@ -1279,7 +1300,7 @@ public override async Task GetStatisticsHasCorrectValues() Assert.Equal(2, stats.TotalFailedLeases); Assert.Equal(1, stats.TotalSuccessfulLeases); - limiter.TryReplenish(); + Replenish(limiter, 1); await lease2Task; stats = limiter.GetStatistics(); @@ -1297,7 +1318,7 @@ public override async Task GetStatisticsWithZeroPermitCount() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); @@ -1331,12 +1352,82 @@ public override void GetStatisticsThrowsAfterDispose() TokenLimit = 100, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 50, - ReplenishmentPeriod = TimeSpan.Zero, + ReplenishmentPeriod = TimeSpan.FromMilliseconds(1), TokensPerPeriod = 30, AutoReplenishment = false }); limiter.Dispose(); Assert.Throws(limiter.GetStatistics); } + + [Fact] + public void AutoReplenishIgnoresTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + ReplenishmentPeriod = replenishmentPeriod, + AutoReplenishment = true, + TokensPerPeriod = 1, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(8, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1); + + Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits); + } + + [Fact] + public void ManualReplenishPreservesTimeWithTimerJitter() + { + var replenishmentPeriod = TimeSpan.FromMinutes(10); + using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions + { + TokenLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + ReplenishmentPeriod = replenishmentPeriod, + AutoReplenishment = false, + TokensPerPeriod = 1, + }); + + var lease = limiter.AttemptAcquire(permitCount: 3); + Assert.True(lease.IsAcquired); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1); + + Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits); + + // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled + Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1); + + Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits); + } + + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + + static internal void Replenish(TokenBucketRateLimiter limiter, long addMilliseconds) + { + var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var internalTick = typeof(TokenBucketRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var currentTick = (long)internalTick.GetValue(limiter); + replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) }); + } } } diff --git a/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj b/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj index 57a6f7f3c4ddb0..4d133621e598d9 100644 --- a/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj +++ b/src/libraries/System.Threading.Thread/src/System.Threading.Thread.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj b/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj index bda556178b9e6c..051ec503dc2450 100644 --- a/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj +++ b/src/libraries/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj @@ -6,10 +6,10 @@ - + - \ No newline at end of file + diff --git a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj index 86a68905ab6ed6..034f32afcf5070 100644 --- a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj +++ b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj @@ -5,7 +5,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent) CA1805;IDE0059;CS1591 $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - false + false diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs index e516d2c0d038fc..a06acb7668a582 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs @@ -11,9 +11,15 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid(Guids.IID_ITransaction), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransaction { - void Commit([MarshalAs(UnmanagedType.Bool)] bool fRetainingt, [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, uint grfRM); + void Commit( + [MarshalAs(UnmanagedType.Bool)] bool fRetainingt, + [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, + uint grfRM); - void Abort(IntPtr reason, [MarshalAs(UnmanagedType.Bool)] bool retaining, [MarshalAs(UnmanagedType.Bool)] bool async); + void Abort( + IntPtr reason, + [MarshalAs(UnmanagedType.Bool)] bool retaining, + [MarshalAs(UnmanagedType.Bool)] bool async); void GetTransactionInfo(out OletxXactTransInfo xactInfo); } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs index 7ce0b361bdf5ff..731e72214f45a5 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs @@ -10,5 +10,17 @@ namespace System.Transactions.DtcProxyShim.DtcInterfaces; [ComImport, Guid("02656950-2152-11d0-944C-00A0C905416E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ITransactionCloner { + void Commit( + [MarshalAs(UnmanagedType.Bool)] bool fRetainingt, + [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, + uint grfRM); + + void Abort( + IntPtr reason, + [MarshalAs(UnmanagedType.Bool)] bool retaining, + [MarshalAs(UnmanagedType.Bool)] bool async); + + void GetTransactionInfo(out OletxXactTransInfo xactInfo); + void CloneWithCommitDisabled([MarshalAs(UnmanagedType.Interface)] out ITransaction ppITransaction); } diff --git a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs index dc2ba01f63bcbb..6313200e46ada0 100644 --- a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs +++ b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs @@ -21,7 +21,6 @@ public class NonMsdtcPromoterTests : IDisposable private static MethodInfo s_setDistributedTransactionIdentifierMethodInfo; private static MethodInfo s_getPromotedTokenMethodInfo; private static PropertyInfo s_promoterTypePropertyInfo; - private static FieldInfo s_promoterTypeDtcFieldInfo; public NonMsdtcPromoterTests() { @@ -51,17 +50,12 @@ private static void VerifySoftDependencies() // And the PropertyInfo objects for PromoterType s_promoterTypePropertyInfo = typeof(Transaction).GetTypeInfo().GetProperty("PromoterType", typeof(Guid)); - - // And the FieldInfo for TransactionInterop.PromoterTypeDtc - s_promoterTypeDtcFieldInfo = typeof(TransactionInterop).GetTypeInfo().GetField("PromoterTypeDtc", BindingFlags.Public | BindingFlags.Static); } bool allMethodsAreThere = ((s_enlistPromotableSinglePhaseMethodInfo != null) && (s_setDistributedTransactionIdentifierMethodInfo != null) && (s_getPromotedTokenMethodInfo != null) && - (s_promoterTypePropertyInfo != null) && - (s_promoterTypeDtcFieldInfo != null) - ); + (s_promoterTypePropertyInfo != null)); Assert.True(allMethodsAreThere, "At least one of the expected new methods or properties is not implemented by the available System.Transactions."); } @@ -339,14 +333,6 @@ private static byte[] TxPromotedToken(Transaction txToGet) return (byte[])s_getPromotedTokenMethodInfo.Invoke(txToGet, null); } - private static Guid PromoterTypeDtc - { - get - { - return (Guid)s_promoterTypeDtcFieldInfo.GetValue(null); - } - } - #endregion #region NonMSDTCPromoterEnlistment @@ -1706,45 +1692,6 @@ private static void TestCase_PromoterType() TestPassed(); } - private static void TestCase_PromoterTypeMSDTC() - { - string testCaseDescription = "TestCase_PromoterTypeMSDTC"; - - Trace("**** " + testCaseDescription + " ****"); - - AutoResetEvent volCompleted = new AutoResetEvent(false); - MyEnlistment vol = null; - - try - { - using (TransactionScope ts = new TransactionScope()) - { - Assert.Equal(Guid.Empty, TxPromoterType(Transaction.Current)); - - vol = CreateVolatileEnlistment(volCompleted); - - // Force MSDTC promotion. - TransactionInterop.GetDtcTransaction(Transaction.Current); - - // TransactionInterop.PromoterTypeDtc - Assert.Equal(PromoterTypeDtc, TxPromoterType(Transaction.Current)); - - ts.Complete(); - } - } - catch (Exception ex) - { - Trace(string.Format("Caught unexpected exception {0}:{1}", ex.GetType().ToString(), ex.ToString())); - return; - } - - Assert.True(volCompleted.WaitOne(TimeSpan.FromSeconds(5))); - - Assert.True(vol.CommittedOutcome); - - TestPassed(); - } - private static void TestCase_FailPromotableSinglePhaseNotificationCalls() { string testCaseDescription = "TestCase_FailPromotableSinglePhaseNotificationCalls"; @@ -2133,16 +2080,6 @@ public void PSPENonMsdtcGetPromoterType() TestCase_PromoterType(); } - /// - /// PSPE Non-MSDTC Get PromoterType. - /// - [Fact] - public void PSPENonMsdtcGetPromoterTypeMSDTC() - { - // get_PromoterType - TestCase_PromoterTypeMSDTC(); - } - /// /// PSPE Non-MSDTC Fail PromotableSinglePhaseNotification Calls. /// diff --git a/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs index fc753cef0e2d0a..bcf50d06c31738 100644 --- a/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs +++ b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs @@ -55,10 +55,15 @@ public void TransmitterPropagationToken() [Fact] public void GetWhereabouts() - => Assert.Throws(() => TransactionInterop.GetWhereabouts()); + => Assert.Throws(TransactionInterop.GetWhereabouts); [Fact] public void GetExportCookie() - => Assert.Throws(() => TransactionInterop.GetExportCookie( - new CommittableTransaction(), new byte[200])); + => Assert.Throws(() => + TransactionInterop.GetExportCookie(new CommittableTransaction(), new byte[200])); + + [Fact] + public void GetDtcTransaction() + => Assert.Throws(() => + TransactionInterop.GetDtcTransaction(new CommittableTransaction())); } diff --git a/src/libraries/System.Transactions.Local/tests/OleTxTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs index 48b2792ddef05f..eaf2d1b972bc26 100644 --- a/src/libraries/System.Transactions.Local/tests/OleTxTests.cs +++ b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs @@ -13,6 +13,7 @@ namespace System.Transactions.Tests; #nullable enable [PlatformSpecific(TestPlatforms.Windows)] +[SkipOnMono("COM Interop not supported on Mono")] public class OleTxTests : IClassFixture { private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(1); @@ -26,242 +27,230 @@ public OleTxTests(OleTxFixture fixture) [InlineData(Phase1Vote.Prepared, Phase1Vote.ForceRollback, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] [InlineData(Phase1Vote.ForceRollback, Phase1Vote.Prepared, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] public void Two_durable_enlistments_commit(Phase1Vote vote1, Phase1Vote vote2, EnlistmentOutcome expectedOutcome1, EnlistmentOutcome expectedOutcome2, TransactionStatus expectedTxStatus) - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - try - { - var enlistment1 = new TestEnlistment(vote1, expectedOutcome1); - var enlistment2 = new TestEnlistment(vote2, expectedOutcome2); + try + { + var enlistment1 = new TestEnlistment(vote1, expectedOutcome1); + var enlistment2 = new TestEnlistment(vote2, expectedOutcome2); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); - tx.Commit(); - } - catch (TransactionInDoubtException) - { - Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); - } - catch (TransactionAbortedException) - { - Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); - } + Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); + tx.Commit(); + } + catch (TransactionInDoubtException) + { + Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); + } + catch (TransactionAbortedException) + { + Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); + } - Retry(() => Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status)); - } + Retry(() => Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status)); + }); [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public void Two_durable_enlistments_rollback() - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); - var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - tx.Rollback(); + tx.Rollback(); - Assert.False(enlistment1.WasPreparedCalled); - Assert.False(enlistment2.WasPreparedCalled); + Assert.False(enlistment1.WasPreparedCalled); + Assert.False(enlistment2.WasPreparedCalled); - // This matches the .NET Framework behavior - Retry(() => Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status)); - } + // This matches the .NET Framework behavior + Retry(() => Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status)); + }); [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] [InlineData(0)] [InlineData(1)] [InlineData(2)] public void Volatile_and_durable_enlistments(int volatileCount) - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - if (volatileCount > 0) - { - TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; - for (int i = 0; i < volatileCount; i++) + if (volatileCount > 0) { - // It doesn't matter what we specify for SinglePhaseVote. - volatiles[i] = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - tx.EnlistVolatile(volatiles[i], EnlistmentOptions.None); + TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; + for (int i = 0; i < volatileCount; i++) + { + // It doesn't matter what we specify for SinglePhaseVote. + volatiles[i] = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + tx.EnlistVolatile(volatiles[i], EnlistmentOptions.None); + } } - } - var durable = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + var durable = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - // Creation of two phase durable enlistment attempts to promote to MSDTC - tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); + // Creation of two phase durable enlistment attempts to promote to MSDTC + tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); - tx.Commit(); + tx.Commit(); - Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); - } + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + }); protected static bool IsRemoteExecutorSupportedAndNotNano => RemoteExecutor.IsSupported && PlatformDetection.IsNotWindowsNanoServer; [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] public void Promotion() { - if (!Environment.Is64BitProcess) + Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - // This simulates the full promotable flow, as implemented for SQL Server. - - // We are going to spin up two external processes. - // 1. The 1st external process will create the transaction and save its propagation token to disk. - // 2. The main process will read that, and propagate the transaction to the 2nd external process. - // 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it). - // 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified - // to commit, and the transaction status will reflect the committed status in the main process. - using var tx = new CommittableTransaction(); - - string propagationTokenFilePath = Path.GetTempFileName(); - string exportCookieFilePath = Path.GetTempFileName(); - using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); - using var waitHandle2 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); - using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + // This simulates the full promotable flow, as implemented for SQL Server. + + // We are going to spin up two external processes. + // 1. The 1st external process will create the transaction and save its propagation token to disk. + // 2. The main process will read that, and propagate the transaction to the 2nd external process. + // 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it). + // 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified + // to commit, and the transaction status will reflect the committed status in the main process. + using var tx = new CommittableTransaction(); - RemoteInvokeHandle? remote1 = null, remote2 = null; + string propagationTokenFilePath = Path.GetTempFileName(); + string exportCookieFilePath = Path.GetTempFileName(); + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + using var waitHandle2 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); - try - { - remote1 = RemoteExecutor.Invoke(Remote1, propagationTokenFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); - - // Wait for the external process to start a transaction and save its propagation token - Assert.True(waitHandle1.WaitOne(Timeout)); - - // Enlist the first PSPE. No escalation happens yet, since its the only enlistment. - var pspe1 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); - Assert.True(tx.EnlistPromotableSinglePhase(pspe1)); - Assert.True(pspe1.WasInitializedCalled); - Assert.False(pspe1.WasPromoteCalled); - Assert.False(pspe1.WasRollbackCalled); - Assert.False(pspe1.WasSinglePhaseCommitCalled); - - // Enlist the second PSPE. This returns false and does nothing, since there's already an enlistment. - var pspe2 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); - Assert.False(tx.EnlistPromotableSinglePhase(pspe2)); - Assert.False(pspe2.WasInitializedCalled); - Assert.False(pspe2.WasPromoteCalled); - Assert.False(pspe2.WasRollbackCalled); - Assert.False(pspe2.WasSinglePhaseCommitCalled); - - // Now generate an export cookie for the 2nd external process. This causes escalation and promotion. - byte[] whereabouts = TransactionInterop.GetWhereabouts(); - byte[] exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); - - Assert.True(pspe1.WasPromoteCalled); - Assert.False(pspe1.WasRollbackCalled); - Assert.False(pspe1.WasSinglePhaseCommitCalled); - - // Write the export cookie and start the 2nd external process, which will read the cookie and enlist in the transaction. - // Wait for it to complete. - File.WriteAllBytes(exportCookieFilePath, exportCookie); - remote2 = RemoteExecutor.Invoke(Remote2, exportCookieFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); - Assert.True(waitHandle2.WaitOne(Timeout)); - - // We now have two external processes with enlistments to our distributed transaction. Commit. - // Since our transaction is delegated to the 1st PSPE enlistment, Sys.Tx will call SinglePhaseCommit on it. - // In SQL Server this contacts the 1st DB to actually commit the transaction with MSDTC. In this simulation we'll just use a wait handle to trigger this. - tx.Commit(); - Assert.True(pspe1.WasSinglePhaseCommitCalled); - waitHandle3.Set(); + RemoteInvokeHandle? remote1 = null, remote2 = null; - Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); - } - catch - { try { - remote1?.Process.Kill(); - remote2?.Process.Kill(); + remote1 = RemoteExecutor.Invoke(Remote1, propagationTokenFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + + // Wait for the external process to start a transaction and save its propagation token + Assert.True(waitHandle1.WaitOne(Timeout)); + + // Enlist the first PSPE. No escalation happens yet, since its the only enlistment. + var pspe1 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.True(tx.EnlistPromotableSinglePhase(pspe1)); + Assert.True(pspe1.WasInitializedCalled); + Assert.False(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Enlist the second PSPE. This returns false and does nothing, since there's already an enlistment. + var pspe2 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.False(tx.EnlistPromotableSinglePhase(pspe2)); + Assert.False(pspe2.WasInitializedCalled); + Assert.False(pspe2.WasPromoteCalled); + Assert.False(pspe2.WasRollbackCalled); + Assert.False(pspe2.WasSinglePhaseCommitCalled); + + // Now generate an export cookie for the 2nd external process. This causes escalation and promotion. + byte[] whereabouts = TransactionInterop.GetWhereabouts(); + byte[] exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.True(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Write the export cookie and start the 2nd external process, which will read the cookie and enlist in the transaction. + // Wait for it to complete. + File.WriteAllBytes(exportCookieFilePath, exportCookie); + remote2 = RemoteExecutor.Invoke(Remote2, exportCookieFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + Assert.True(waitHandle2.WaitOne(Timeout)); + + // We now have two external processes with enlistments to our distributed transaction. Commit. + // Since our transaction is delegated to the 1st PSPE enlistment, Sys.Tx will call SinglePhaseCommit on it. + // In SQL Server this contacts the 1st DB to actually commit the transaction with MSDTC. In this simulation we'll just use a wait handle to trigger this. + tx.Commit(); + Assert.True(pspe1.WasSinglePhaseCommitCalled); + waitHandle3.Set(); + + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); } catch { - } + try + { + remote1?.Process.Kill(); + remote2?.Process.Kill(); + } + catch + { + } - throw; - } - finally - { - File.Delete(propagationTokenFilePath); - } + throw; + } + finally + { + File.Delete(propagationTokenFilePath); + } - // Disposal of the RemoteExecutor handles will wait for the external processes to exit with the right exit code, - // which will happen when their enlistments receive the commit. - remote1?.Dispose(); - remote2?.Dispose(); + // Disposal of the RemoteExecutor handles will wait for the external processes to exit with the right exit code, + // which will happen when their enlistments receive the commit. + remote1?.Dispose(); + remote2?.Dispose(); + }); static void Remote1(string propagationTokenFilePath) - { - using var tx = new CommittableTransaction(); + => Test(() => + { + using var tx = new CommittableTransaction(); - var outcomeEvent = new AutoResetEvent(false); - var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); - tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + var outcomeEvent = new AutoResetEvent(false); + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - // We now have an OleTx transaction. Save its propagation token to disk so that the main process can read it when promoting. - byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); - File.WriteAllBytes(propagationTokenFilePath, propagationToken); + // We now have an OleTx transaction. Save its propagation token to disk so that the main process can read it when promoting. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + File.WriteAllBytes(propagationTokenFilePath, propagationToken); - // Signal to the main process that the propagation token is ready to be read - using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); - waitHandle1.Set(); + // Signal to the main process that the propagation token is ready to be read + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + waitHandle1.Set(); - // The main process will now import our transaction via the propagation token, and propagate it to a 2nd process. - // In the main process the transaction is delegated; we're the one who started it, and so we're the one who need to Commit. - // When Commit() is called in the main process, that will trigger a SinglePhaseCommit on the PSPE which represents us. In SQL Server this - // contacts the DB to actually commit the transaction with MSDTC. In this simulation we'll just use the wait handle again to trigger this. - using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); - Assert.True(waitHandle3.WaitOne(Timeout)); + // The main process will now import our transaction via the propagation token, and propagate it to a 2nd process. + // In the main process the transaction is delegated; we're the one who started it, and so we're the one who need to Commit. + // When Commit() is called in the main process, that will trigger a SinglePhaseCommit on the PSPE which represents us. In SQL Server this + // contacts the DB to actually commit the transaction with MSDTC. In this simulation we'll just use the wait handle again to trigger this. + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + Assert.True(waitHandle3.WaitOne(Timeout)); - tx.Commit(); + tx.Commit(); - // Wait for the commit to occur on our enlistment, then exit successfully. - Assert.True(outcomeEvent.WaitOne(Timeout)); - Environment.Exit(42); // 42 is error code expected by RemoteExecutor - } + // Wait for the commit to occur on our enlistment, then exit successfully. + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + }); static void Remote2(string exportCookieFilePath) - { - // Load the export cookie and enlist durably - byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath); - using var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie); - - // Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token. - var outcomeEvent = new AutoResetEvent(false); - var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); - tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - - // Signal to the main process that we're enlisted and ready to commit - using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); - waitHandle.Set(); - - // Wait for the main process to commit the transaction - Assert.True(outcomeEvent.WaitOne(Timeout)); - Environment.Exit(42); // 42 is error code expected by RemoteExecutor - } + => Test(() => + { + // Load the export cookie and enlist durably + byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath); + using var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + + // Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token. + var outcomeEvent = new AutoResetEvent(false); + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + + // Signal to the main process that we're enlisted and ready to commit + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + waitHandle.Set(); + + // Wait for the main process to commit the transaction + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + }); } public class TestPromotableSinglePhaseNotification : IPromotableSinglePhaseNotification @@ -300,88 +289,87 @@ public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] public void Recovery() { - if (!Environment.Is64BitProcess) + Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - // We are going to spin up an external process to also enlist in the transaction, and then to crash when it - // receives the commit notification. We will then initiate the recovery flow. + // We are going to spin up an external process to also enlist in the transaction, and then to crash when it + // receives the commit notification. We will then initiate the recovery flow. - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - var outcomeEvent1 = new AutoResetEvent(false); - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1); - var guid1 = Guid.NewGuid(); - tx.EnlistDurable(guid1, enlistment1, EnlistmentOptions.None); + var outcomeEvent1 = new AutoResetEvent(false); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1); + var guid1 = Guid.NewGuid(); + tx.EnlistDurable(guid1, enlistment1, EnlistmentOptions.None); - // The propagation token is used to propagate the transaction to that process so it can enlist to our - // transaction. We also provide the resource manager identifier GUID, and a path where the external process will - // write the recovery information it will receive from the MSDTC when preparing. - // We'll need these two elements later in order to Reenlist and trigger recovery. - byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); - string propagationTokenText = Convert.ToBase64String(propagationToken); - var guid2 = Guid.NewGuid(); - string secondEnlistmentRecoveryFilePath = Path.GetTempFileName(); + // The propagation token is used to propagate the transaction to that process so it can enlist to our + // transaction. We also provide the resource manager identifier GUID, and a path where the external process will + // write the recovery information it will receive from the MSDTC when preparing. + // We'll need these two elements later in order to Reenlist and trigger recovery. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + string propagationTokenText = Convert.ToBase64String(propagationToken); + var guid2 = Guid.NewGuid(); + string secondEnlistmentRecoveryFilePath = Path.GetTempFileName(); - using var waitHandle = new EventWaitHandle( - initialState: false, - EventResetMode.ManualReset, - "System.Transactions.Tests.OleTxTests.Recovery"); + using var waitHandle = new EventWaitHandle( + initialState: false, + EventResetMode.ManualReset, + "System.Transactions.Tests.OleTxTests.Recovery"); - try - { - using (RemoteExecutor.Invoke( - EnlistAndCrash, - propagationTokenText, guid2.ToString(), secondEnlistmentRecoveryFilePath, - new RemoteInvokeOptions { ExpectedExitCode = 42 })) + try { - // Wait for the external process to enlist in the transaction, it will signal this EventWaitHandle. - Assert.True(waitHandle.WaitOne(Timeout)); + using (RemoteExecutor.Invoke( + EnlistAndCrash, + propagationTokenText, guid2.ToString(), secondEnlistmentRecoveryFilePath, + new RemoteInvokeOptions { ExpectedExitCode = 42 })) + { + // Wait for the external process to enlist in the transaction, it will signal this EventWaitHandle. + Assert.True(waitHandle.WaitOne(Timeout)); - tx.Commit(); - } + tx.Commit(); + } - // The other has crashed when the MSDTC notified it to commit. - // Load the recovery information the other process has written to disk for us and reenlist with - // the failed RM's Guid to commit. - var outcomeEvent3 = new AutoResetEvent(false); - var enlistment3 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent3); - byte[] secondRecoveryInformation = File.ReadAllBytes(secondEnlistmentRecoveryFilePath); - _ = TransactionManager.Reenlist(guid2, secondRecoveryInformation, enlistment3); - TransactionManager.RecoveryComplete(guid2); - - Assert.True(outcomeEvent1.WaitOne(Timeout)); - Assert.True(outcomeEvent3.WaitOne(Timeout)); - Assert.Equal(EnlistmentOutcome.Committed, enlistment1.Outcome); - Assert.Equal(EnlistmentOutcome.Committed, enlistment3.Outcome); - Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status); - - // Note: verify manually in the MSDTC console that the distributed transaction is gone - // (i.e. successfully committed), - // (Start -> Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> - // Local DTC -> Transaction List) - } - finally - { - File.Delete(secondEnlistmentRecoveryFilePath); - } + // The other has crashed when the MSDTC notified it to commit. + // Load the recovery information the other process has written to disk for us and reenlist with + // the failed RM's Guid to commit. + var outcomeEvent3 = new AutoResetEvent(false); + var enlistment3 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent3); + byte[] secondRecoveryInformation = File.ReadAllBytes(secondEnlistmentRecoveryFilePath); + _ = TransactionManager.Reenlist(guid2, secondRecoveryInformation, enlistment3); + TransactionManager.RecoveryComplete(guid2); + + Assert.True(outcomeEvent1.WaitOne(Timeout)); + Assert.True(outcomeEvent3.WaitOne(Timeout)); + Assert.Equal(EnlistmentOutcome.Committed, enlistment1.Outcome); + Assert.Equal(EnlistmentOutcome.Committed, enlistment3.Outcome); + Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status); + + // Note: verify manually in the MSDTC console that the distributed transaction is gone + // (i.e. successfully committed), + // (Start -> Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> + // Local DTC -> Transaction List) + } + finally + { + File.Delete(secondEnlistmentRecoveryFilePath); + } + }); static void EnlistAndCrash(string propagationTokenText, string resourceManagerIdentifierGuid, string recoveryInformationFilePath) - { - byte[] propagationToken = Convert.FromBase64String(propagationTokenText); - using var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + => Test(() => + { + byte[] propagationToken = Convert.FromBase64String(propagationTokenText); + using var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); - var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath); - tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None); + var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath); + tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None); - // Signal to the main process that we've enlisted and are ready to accept prepare/commit. - using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Recovery"); - waitHandle.Set(); + // Signal to the main process that we've enlisted and are ready to accept prepare/commit. + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Recovery"); + waitHandle.Set(); - // We've enlisted, and set it up so that when the MSDTC tells us to commit, the process will crash. - Thread.Sleep(Timeout); - } + // We've enlisted, and set it up so that when the MSDTC tells us to commit, the process will crash. + Thread.Sleep(Timeout); + }); } public class CrashingEnlistment : IEnlistmentNotification @@ -411,46 +399,99 @@ public void InDoubt(Enlistment enlistment) [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public void TransmitterPropagationToken() - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } - - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); - var propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + var propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); - Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); - var tx2 = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + var tx2 = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); - Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); - } + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + }); [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public void GetExportCookie() - { - if (!Environment.Is64BitProcess) + => Test(() => { - return; // Temporarily skip on 32-bit where we have an issue - } + using var tx = new CommittableTransaction(); + + var whereabouts = TransactionInterop.GetWhereabouts(); + + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var tx2 = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + }); + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void GetDtcTransaction() + => Test(() => + { + using var tx = new CommittableTransaction(); - using var tx = new CommittableTransaction(); + var outcomeReceived = new AutoResetEvent(false); - var whereabouts = TransactionInterop.GetWhereabouts(); + var enlistment = new TestEnlistment( + Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeReceived); - Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + Assert.Equal(Guid.Empty, tx.PromoterType); - var exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + tx.EnlistVolatile(enlistment, EnlistmentOptions.None); - Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + // Forces promotion to MSDTC, returns an ITransaction for use only with System.EnterpriseServices. + _ = TransactionInterop.GetDtcTransaction(tx); - var tx2 = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); + Assert.Equal(TransactionInterop.PromoterTypeDtc, tx.PromoterType); + + tx.Commit(); + + Assert.True(outcomeReceived.WaitOne(Timeout)); + Assert.Equal(EnlistmentOutcome.Committed, enlistment.Outcome); + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + }); + + private static void Test(Action action) + { + // Temporarily skip on 32-bit where we have an issue. + // ARM64 issue: https://github.com/dotnet/runtime/issues/74170 + if (!Environment.Is64BitProcess || PlatformDetection.IsArm64Process) + { + return; + } + + // In CI, we sometimes get XACT_E_TMNOTAVAILABLE; when it happens, it's typically on the very first + // attempt to connect to MSDTC (flaky/slow on-demand startup of MSDTC), though not only. + // This catches that error and retries. + int nRetries = 5; - Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + while (true) + { + try + { + action(); + return; + } + catch (TransactionException e) when (e.InnerException is TransactionManagerCommunicationException) + { + if (--nRetries == 0) + { + throw; + } + + Thread.Sleep(500); + } + } } // MSDTC is aynchronous, i.e. Commit/Rollback may return before the transaction has actually completed; @@ -480,44 +521,21 @@ private static void Retry(Action action) public class OleTxFixture { + // In CI, we sometimes get XACT_E_TMNOTAVAILABLE on the very first attempt to connect to MSDTC; + // this is likely due to on-demand slow startup of MSDTC. Perform pre-test connecting with retry + // to ensure that MSDTC is properly up when the first test runs. public OleTxFixture() - { - if (!Environment.Is64BitProcess) - { - return; // Temporarily skip on 32-bit where we have an issue - } - - // In CI, we sometimes get XACT_E_TMNOTAVAILABLE on the very first attempt to connect to MSDTC; - // this is likely due to on-demand slow startup of MSDTC. Perform pre-test connecting with retry - // to ensure that MSDTC is properly up when the first test runs. - int nRetries = 5; - - while (true) + => Test(() => { - try - { - using var tx = new CommittableTransaction(); + using var tx = new CommittableTransaction(); - var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); - tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); - tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); - tx.Commit(); - - return; - } - catch (TransactionException e) when (e.InnerException is TransactionManagerCommunicationException) - { - if (--nRetries == 0) - { - throw; - } - - Thread.Sleep(100); - } - } - } + tx.Commit(); + }); } } diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt index 93dd3423e9b827..1361a3c323a21c 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.txt @@ -28,7 +28,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -61,7 +60,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAtt CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Numerics.Vector.TryCopyTo(System.Span)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.RequiresPreviewFeaturesAttribute' exists on 'System.String System.Runtime.CompilerServices.RuntimeFeature.VirtualStaticsInInterfaces' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeDeformatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeFormatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricSignatureDeformatter' in the contract but not the implementation. @@ -93,7 +91,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -112,7 +109,6 @@ Compat issues with assembly System.Core: CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'System.Linq.EnumerableQuery..ctor(System.Collections.Generic.IEnumerable)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'System.Linq.EnumerableQuery..ctor(System.Linq.Expressions.Expression)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECCurve' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECDiffieHellman' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.ECDsa' in the contract but not the implementation. @@ -156,7 +152,6 @@ MembersMustExist : Member 'public System.Runtime.Intrinsics.Vector256 System. MembersMustExist : Member 'public System.Runtime.Intrinsics.Vector64 System.Runtime.Intrinsics.Vector64.As(System.Runtime.Intrinsics.Vector64)' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Security.Cryptography.Algorithms: CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Aes' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AesManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeDeformatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricKeyExchangeFormatter' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.AsymmetricSignatureDeformatter' in the contract but not the implementation. @@ -189,7 +184,6 @@ CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatfo CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.RC2.Create()' changed from '[UnsupportedOSPlatformAttribute("android")]' in the contract to '[UnsupportedOSPlatformAttribute("android")]' in the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rfc2898DeriveBytes' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.Rijndael' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RijndaelManaged' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSA' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAEncryptionPadding' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' exists on 'System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter' in the contract but not the implementation. @@ -203,4 +197,4 @@ Compat issues with assembly System.Security.Cryptography.X509Certificates: CannotChangeAttribute : Attribute 'System.Runtime.Versioning.UnsupportedOSPlatformAttribute' on 'System.Security.Cryptography.X509Certificates.PublicKey.GetDSAPublicKey()' changed from '[UnsupportedOSPlatformAttribute("ios")]' in the contract to '[UnsupportedOSPlatformAttribute("browser")]' in the implementation. Compat issues with assembly System.Text.Json: CannotMakeTypeAbstract : Type 'System.Text.Json.Serialization.Metadata.JsonTypeInfo' is abstract in the implementation but is not abstract in the contract. -Total Issues: 190 +Total Issues: 184 diff --git a/src/libraries/sendtohelix-wasm.targets b/src/libraries/sendtohelix-wasm.targets index acf037e988b272..ed76264134f306 100644 --- a/src/libraries/sendtohelix-wasm.targets +++ b/src/libraries/sendtohelix-wasm.targets @@ -26,6 +26,7 @@ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasm', 'build')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'NetCoreServer', '$(Configuration)', '$(AspNetCoreAppCurrent)')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'bin', 'RemoteLoopServer', '$(Configuration)', '$(AspNetCoreAppCurrent)')) + <_ShippingPackagesPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir), 'packages', $(Configuration), 'Shipping')) $(DebuggerHost)- $(Scenario)- @@ -56,6 +57,7 @@ true true true + true true false @@ -121,6 +123,9 @@ + + + @@ -187,6 +192,10 @@ + + + + - + diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index 59cd95650c13e6..0f92fdde638e3f 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -60,8 +60,8 @@ $(WaitForWorkItemCompletion) - dotnet-workload - sdk-no-workload + dotnet-net7 + dotnet-none diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index ecffaaf6e0a462..07349a0d3d4497 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -19,6 +19,7 @@ false false + true false @@ -333,7 +334,8 @@ - + + @@ -544,7 +546,12 @@ - + + + + diff --git a/src/mono/CMakeLists.txt b/src/mono/CMakeLists.txt index 1629fe4dbcec93..6dbf542183bcdc 100644 --- a/src/mono/CMakeLists.txt +++ b/src/mono/CMakeLists.txt @@ -215,6 +215,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(HOST_LINUX 1) add_definitions(-D_GNU_SOURCE -D_REENTRANT) add_definitions(-D_THREAD_SAFE) + set(HAVE_CGROUP_SUPPORT 1) # Enable the "full RELRO" options (RELRO & BIND_NOW) at link time add_link_options(-Wl,-z,relro) add_link_options(-Wl,-z,now) diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 7eb9314c1ce629..b39c4504440463 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -125,8 +125,8 @@ $(DefineConstants);MONO_FEATURE_SRE true - true - true + true + true true true true diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index 1fcb29907ae2aa..40265d7b9a2702 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -38,8 +38,6 @@ internal abstract class RtFieldInfo : FieldInfo internal abstract object UnsafeGetValue(object obj); internal abstract void UnsafeSetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture); internal abstract void CheckConsistency(object target); - internal abstract object? GetValueNonEmit(object? obj); - internal abstract void SetValueNonEmit(object? obj, object? value); } [StructLayout(LayoutKind.Sequential)] @@ -51,19 +49,8 @@ internal sealed class RuntimeFieldInfo : RtFieldInfo private string? name; private Type? type; private FieldAttributes attrs; - private FieldAccessor? invoker; #pragma warning restore 649 - private FieldAccessor Invoker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - invoker ??= new FieldAccessor(this); - return invoker; - } - } - public override Module Module { get @@ -128,13 +115,6 @@ public override void SetValueDirect(TypedReference obj, object value) } } - [DebuggerStepThrough] - [DebuggerHidden] - internal override void SetValueNonEmit(object? obj, object? value) - { - SetValueInternal(this, obj, value); - } - [DebuggerStepThrough] [DebuggerHidden] public override object GetValueDirect(TypedReference obj) @@ -149,13 +129,6 @@ public override object GetValueDirect(TypedReference obj) } } - [DebuggerStepThrough] - [DebuggerHidden] - internal override object? GetValueNonEmit(object? obj) - { - return GetValueInternal(obj); - } - public override FieldAttributes Attributes { get @@ -239,7 +212,7 @@ public override object[] GetCustomAttributes(Type attributeType, bool inherit) if (!IsLiteral) CheckGeneric(); - return Invoker.GetValue(obj); + return GetValueInternal(obj); } public override string ToString() @@ -278,7 +251,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, } } - Invoker.SetValue(obj, val); + SetValueInternal(this, obj, val); } internal RuntimeFieldInfo Clone(string newName) diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 146739163199be..08f30f04fca829 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs @@ -802,10 +802,13 @@ internal RuntimeType[] ArgumentTypes } } - private static void InvokeClassConstructor() + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void InvokeClassConstructor(QCallTypeHandle type); + + private void InvokeClassConstructor() { - // [TODO] Mechanism for invoking class constructor - // See https://github.com/dotnet/runtime/issues/40351 + RuntimeType type = (RuntimeType)DeclaringType; + InvokeClassConstructor(new QCallTypeHandle(ref type)); } /* diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index 81ff6697c2a272..4ac722c9d928c1 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -888,6 +888,9 @@ /* Define to 1 if you have /usr/include/malloc.h. */ #cmakedefine HAVE_USR_INCLUDE_MALLOC_H 1 +/* Define to 1 if you have linux cgroups */ +#cmakedefine HAVE_CGROUP_SUPPORT 1 + /* The architecture this is running on */ #define MONO_ARCHITECTURE @MONO_ARCHITECTURE@ diff --git a/src/mono/mono.proj b/src/mono/mono.proj index 26ca5545d761bb..38731e968660a5 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -733,8 +733,7 @@ - - + diff --git a/src/mono/mono/arch/amd64/amd64-codegen.h b/src/mono/mono/arch/amd64/amd64-codegen.h index ccd82d1ac172d8..9ac73b9853c466 100644 --- a/src/mono/mono/arch/amd64/amd64-codegen.h +++ b/src/mono/mono/arch/amd64/amd64-codegen.h @@ -1206,6 +1206,7 @@ typedef union { #define amd64_alu_membase8_imm_size(inst,opc,basereg,disp,imm,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(basereg)); x86_alu_membase8_imm((inst),(opc),((basereg)&0x7),(disp),(imm)); amd64_codegen_post(inst); } while (0) #define amd64_alu_mem_reg_size(inst,opc,mem,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_alu_mem_reg((inst),(opc),(mem),((reg)&0x7)); amd64_codegen_post(inst); } while (0) #define amd64_alu_membase_reg_size(inst,opc,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_alu_membase_reg((inst),(opc),((basereg)&0x7),(disp),((reg)&0x7)); amd64_codegen_post(inst); } while (0) +#define amd64_alu_membase8_reg_size(inst,opc,basereg,disp,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(reg),0,(basereg)); x86_alu_membase8_reg((inst),(opc),((basereg)&0x7),(disp),((reg)&0x7)); amd64_codegen_post(inst); } while (0) //#define amd64_alu_reg_reg_size(inst,opc,dreg,reg,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_alu_reg_reg((inst),(opc),((dreg)&0x7),((reg)&0x7)); amd64_codegen_post(inst); } while (0) #define amd64_alu_reg8_reg8_size(inst,opc,dreg,reg,is_dreg_h,is_reg_h,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),(dreg),0,(reg)); x86_alu_reg8_reg8((inst),(opc),((dreg)&0x7),((reg)&0x7),(is_dreg_h),(is_reg_h)); amd64_codegen_post(inst); } while (0) #define amd64_alu_reg_mem_size(inst,opc,reg,mem,size) do { amd64_codegen_pre(inst); amd64_emit_rex ((inst),(size),0,0,(reg)); x86_alu_reg_mem((inst),(opc),((reg)&0x7),(mem)); amd64_codegen_post(inst); } while (0) diff --git a/src/mono/mono/arch/x86/x86-codegen.h b/src/mono/mono/arch/x86/x86-codegen.h index 74f96f81af4077..aca2b659ca058b 100644 --- a/src/mono/mono/arch/x86/x86-codegen.h +++ b/src/mono/mono/arch/x86/x86-codegen.h @@ -682,6 +682,13 @@ mono_x86_patch_inline (guchar* code, gpointer target) x86_membase_emit ((inst), (reg), (basereg), (disp)); \ } while (0) +#define x86_alu_membase8_reg(inst,opc,basereg,disp,reg) \ + do { \ + x86_codegen_pre(&(inst), 1 + kMaxMembaseEmitPadding); \ + x86_byte (inst, (((unsigned char)(opc)) << 3)); \ + x86_membase_emit ((inst), (reg), (basereg), (disp)); \ + } while (0) + #define x86_alu_reg_reg(inst,opc,dreg,reg) \ do { \ x86_codegen_pre(&(inst), 2); \ diff --git a/src/mono/mono/component/CMakeLists.txt b/src/mono/mono/component/CMakeLists.txt index d83c9144af07f9..7c864322f5f377 100644 --- a/src/mono/mono/component/CMakeLists.txt +++ b/src/mono/mono/component/CMakeLists.txt @@ -6,12 +6,9 @@ set(MONO_EVENTPIPE_GEN_INCLUDE_PATH "${CMAKE_CURRENT_BINARY_DIR}/eventpipe") set(MONO_HOT_RELOAD_COMPONENT_NAME "hot_reload") set(MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME "diagnostics_tracing") set(MONO_DEBUGGER_COMPONENT_NAME "debugger") -set(MONO_MARSHAL_ILGEN_COMPONENT_NAME "marshal-ilgen") # a list of every component. set(components "") -# a list of components needed by the AOT compiler -set(components_for_aot "") # the sources for each individiable component define a new # component_name-sources list for each component, and a @@ -82,53 +79,17 @@ set(${MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME}-dependencies ${MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME}-gen-sources ) -# marshal-ilgen -list(APPEND components - ${MONO_MARSHAL_ILGEN_COMPONENT_NAME} -) -list(APPEND components_for_aot - ${MONO_MARSHAL_ILGEN_COMPONENT_NAME} -) - -set(${MONO_MARSHAL_ILGEN_COMPONENT_NAME}-sources - ${MONO_COMPONENT_PATH}/marshal-ilgen.c - ${MONO_COMPONENT_PATH}/marshal-ilgen.h - ${MONO_COMPONENT_PATH}/marshal-ilgen-noilgen.c - ${MONO_COMPONENT_PATH}/marshal-ilgen-noilgen.h -) - -# For every component not build into the AOT compiler, build the stub instead -set(stubs_for_aot "") -foreach (component IN LISTS components) - if (NOT (component IN_LIST components_for_aot)) - list(APPEND stubs_for_aot "${component}") - endif() -endforeach() - - -set(${MONO_MARSHAL_ILGEN_COMPONENT_NAME}-stub-sources - ${MONO_COMPONENT_PATH}/marshal-ilgen-stub.c -) - -if (AOT_COMPONENTS) - set(components_to_build ${components_for_aot}) - set(stubs_to_build ${stubs_for_aot}) -else() - set(components_to_build ${components}) - set(stubs_to_build ${components}) -endif() - # from here down, all the components are treated in the same way #define a library for each component and component stub function(define_component_libs) # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets - if (AOT_COMPONENTS OR NOT DISABLE_LIBS ) - foreach(component IN LISTS components_to_build) + if (NOT DISABLE_LIBS) + foreach(component IN LISTS components) add_library("mono-component-${component}-static" STATIC $) install(TARGETS "mono-component-${component}-static" LIBRARY) endforeach() - foreach(component IN LISTS stubs_to_build) + foreach(component IN LISTS components) add_library("mono-component-${component}-stub-static" STATIC $) install(TARGETS "mono-component-${component}-stub-static" LIBRARY) endforeach() @@ -142,7 +103,7 @@ target_sources(component_base INTERFACE ) target_link_libraries(component_base INTERFACE monoapi) -if(NOT AOT_COMPONENTS AND (DISABLE_COMPONENTS OR DISABLE_LIBS)) +if(DISABLE_COMPONENTS OR DISABLE_LIBS) set(DISABLE_COMPONENT_OBJECTS 1) endif() @@ -162,7 +123,7 @@ endforeach() if(NOT DISABLE_COMPONENTS AND NOT STATIC_COMPONENTS) # define a shared library for each component - foreach(component IN LISTS components_to_build) + foreach(component IN LISTS components) # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets if(HOST_WIN32) add_library("mono-component-${component}" SHARED "${${component}-sources}") @@ -194,14 +155,14 @@ if(NOT DISABLE_COMPONENTS AND NOT STATIC_COMPONENTS) #define a library for each component and component stub define_component_libs() -elseif(AOT_COMPONENTS OR (NOT DISABLE_COMPONENTS AND STATIC_COMPONENTS)) +elseif(NOT DISABLE_COMPONENTS AND STATIC_COMPONENTS) #define a library for each component and component stub define_component_libs() # define a list of mono-components objects for mini if building a shared libmono with static-linked components set(mono-components-objects "") - foreach(component IN LISTS components_to_build) + foreach(component IN LISTS components) list(APPEND mono-components-objects $) endforeach() diff --git a/src/mono/mono/component/marshal-ilgen-noilgen.c b/src/mono/mono/component/marshal-ilgen-noilgen.c deleted file mode 100644 index 6cf9dfd5ac628a..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-noilgen.c +++ /dev/null @@ -1,186 +0,0 @@ -#include "mono/component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen-noilgen.h" - -#ifndef ENABLE_ILGEN -static int -emit_marshal_array_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - MonoType *object_type = mono_get_object_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = object_type; - break; - case MARSHAL_ACTION_MANAGED_CONV_IN: - *conv_arg_type = int_type; - break; - } - return conv_arg; -} - -static int -emit_marshal_ptr_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - return conv_arg; -} -#endif - -#if !defined(ENABLE_ILGEN) || defined(DISABLE_NONBLITTABLE) -static int -emit_marshal_vtype_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_string_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - break; - case MARSHAL_ACTION_MANAGED_CONV_IN: - *conv_arg_type = int_type; - break; - } - return conv_arg; -} - -static int -emit_marshal_safehandle_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_handleref_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_object_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN) - *conv_arg_type = int_type; - return conv_arg; -} - -static int -emit_marshal_variant_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - g_assert_not_reached (); -} - -static int -emit_marshal_asany_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_boolean_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - if (m_type_is_byref (t)) - *conv_arg_type = int_type; - else - *conv_arg_type = mono_marshal_boolean_conv_in_get_local_type (spec, NULL); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - MonoClass* conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, NULL); - if (m_type_is_byref (t)) - *conv_arg_type = m_class_get_this_arg (conv_arg_class); - else - *conv_arg_type = m_class_get_byval_arg (conv_arg_class); - break; - } - - } - return conv_arg; -} - -static int -emit_marshal_char_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - return conv_arg; -} - -static int -emit_marshal_custom_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoType *int_type = mono_get_int_type (); - if (action == MARSHAL_ACTION_CONV_IN && t->type == MONO_TYPE_VALUETYPE) - *conv_arg_type = int_type; - return conv_arg; -} -#endif - -#ifndef ENABLE_ILGEN - -void -mono_marshal_noilgen_init_heavyweight (void) -{ - MonoMarshalILgenCallbacks ilgen_cb; - - ilgen_cb.version = MONO_MARSHAL_CALLBACKS_VERSION; - ilgen_cb.emit_marshal_array = emit_marshal_array_noilgen; - ilgen_cb.emit_marshal_vtype = emit_marshal_vtype_noilgen; - ilgen_cb.emit_marshal_string = emit_marshal_string_noilgen; - ilgen_cb.emit_marshal_safehandle = emit_marshal_safehandle_noilgen; - ilgen_cb.emit_marshal_handleref = emit_marshal_handleref_noilgen; - ilgen_cb.emit_marshal_object = emit_marshal_object_noilgen; - ilgen_cb.emit_marshal_variant = emit_marshal_variant_noilgen; - ilgen_cb.emit_marshal_asany = emit_marshal_asany_noilgen; - ilgen_cb.emit_marshal_boolean = emit_marshal_boolean_noilgen; - ilgen_cb.emit_marshal_custom = emit_marshal_custom_noilgen; - ilgen_cb.emit_marshal_ptr = emit_marshal_ptr_noilgen; - - ilgen_cb.emit_marshal_char = emit_marshal_char_noilgen; - mono_install_marshal_callbacks_ilgen(&ilgen_cb); -} - -#endif \ No newline at end of file diff --git a/src/mono/mono/component/marshal-ilgen-noilgen.h b/src/mono/mono/component/marshal-ilgen-noilgen.h deleted file mode 100644 index 5e877c223833c7..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-noilgen.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ -#ifndef __MARSHAL_ILGEN_NOILGEN_H__ -#define __MARSHAL_ILGEN_NOILGEN_H__ - -void mono_marshal_noilgen_init_heavyweight (void); - -#endif // __MARSHAL_ILGEN_NOILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/component/marshal-ilgen-stub.c b/src/mono/mono/component/marshal-ilgen-stub.c deleted file mode 100644 index 8182c95b7e286d..00000000000000 --- a/src/mono/mono/component/marshal-ilgen-stub.c +++ /dev/null @@ -1,41 +0,0 @@ - -#include -#include -#include - -static bool -marshal_ilgen_available (void) -{ - return false; -} - -static int -stub_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) -{ - return 0; -} - -static void -mono_component_marshal_ilgen_stub_init(void) -{ -} - -static void -stub_mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks) -{ -} - -static MonoComponentMarshalILgen component_func_table = { - { MONO_COMPONENT_ITF_VERSION, &marshal_ilgen_available }, - mono_component_marshal_ilgen_stub_init, - stub_emit_marshal_ilgen, - stub_mono_marshal_ilgen_install_callbacks_mono -}; - -MonoComponentMarshalILgen* -mono_component_marshal_ilgen_init (void) -{ - return &component_func_table; -} diff --git a/src/mono/mono/component/marshal-ilgen.c b/src/mono/mono/component/marshal-ilgen.c deleted file mode 100644 index e6deeea5564097..00000000000000 --- a/src/mono/mono/component/marshal-ilgen.c +++ /dev/null @@ -1,2861 +0,0 @@ - -#include "mono/metadata/debug-helpers.h" -#include "metadata/marshal.h" -#include "component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen.h" -#include "mono/component/marshal-ilgen-noilgen.h" -#include "metadata/marshal-lightweight.h" -#include "metadata/marshal-shared.h" -#include "metadata/method-builder-ilgen.h" -#include "metadata/custom-attrs-internals.h" -#include "metadata/class-init.h" -#include "mono/metadata/class-internals.h" -#include "metadata/reflection-internals.h" -#include "mono/metadata/handle.h" -#include "mono/component/component.h" - -#define OPDEF(a,b,c,d,e,f,g,h,i,j) \ - a = i, - -enum { -#include "mono/cil/opcode.def" - LAST = 0xff -}; -#undef OPDEF - -#define mono_mb_emit_jit_icall(mb, name) (cb_to_mono->mb_emit_icall_id ((mb), MONO_JIT_ICALL_ ## name)) - -static GENERATE_GET_CLASS_WITH_CACHE (date_time, "System", "DateTime"); -static GENERATE_TRY_GET_CLASS_WITH_CACHE (icustom_marshaler, "System.Runtime.InteropServices", "ICustomMarshaler"); - -static void emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv); - -static void mono_marshal_ilgen_legacy_init (void); - -static gboolean ilgen_cb_inited = FALSE; -static MonoMarshalILgenCallbacks ilgen_marshal_cb; - -static IlgenCallbacksToMono *cb_to_mono; - -static bool -marshal_ilgen_available (void) -{ - return true; -} - -static MonoComponentMarshalILgen component_func_table = { - { MONO_COMPONENT_ITF_VERSION, &marshal_ilgen_available }, - &mono_marshal_ilgen_init, - &mono_emit_marshal_ilgen, - &mono_marshal_ilgen_install_callbacks_mono -}; - - -MonoComponentMarshalILgen* -mono_component_marshal_ilgen_init (void) -{ - return &component_func_table; -} - -void -mono_install_marshal_callbacks_ilgen (MonoMarshalILgenCallbacks *cb) -{ - g_assert (!ilgen_cb_inited); - g_assert (cb->version == MONO_MARSHAL_CALLBACKS_VERSION); - memcpy (&ilgen_marshal_cb, cb, sizeof (MonoMarshalILgenCallbacks)); - ilgen_cb_inited = TRUE; -} - -void -mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks) -{ - cb_to_mono = callbacks; -} - -static void -emit_struct_free (MonoMethodBuilder *mb, MonoClass *klass, int struct_var) -{ - /* Call DestroyStructure */ - /* FIXME: Only do this if needed */ - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, struct_var); - mono_mb_emit_jit_icall (mb, mono_struct_delete_old); -} - -static int -emit_marshal_array_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass = mono_class_from_mono_type_internal (t); - MonoMarshalNative encoding; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - - MonoClass *eklass = m_class_get_element_class (klass); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = object_type; - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - if (m_class_is_blittable (eklass)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_ARRAY_LPARRAY, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { -#ifdef DISABLE_NONBLITTABLE - char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); -#else - guint32 label1, label2, label3; - int index_var, src_var, dest_ptr, esize; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - dest_ptr = cb_to_mono->mb_add_local (mb, int_type); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - } - else - conv = MONO_MARSHAL_CONV_INVALID; - - if (is_string && conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string/stringbuilder marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - src_var = cb_to_mono->mb_add_local (mb, object_type); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, src_var); - - /* Check null */ - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, src_var); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) /*can't call mono_marshal_type_size since it causes all sorts of asserts*/ - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - /* allocate space for the native struct and store the address */ - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Make the array bigger for the terminating null */ - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, dest_ptr); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - - if (is_string) { - int stind_op; - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } else { - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, src_var); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_op (mb, CEE_LDELEMA, eklass); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv_full (mb, eklass, FALSE, 0, eklass == cb_to_mono->mono_defaults->char_class ? encoding : (MonoMarshalNative)-1); - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label3); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Null terminate */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - } - - cb_to_mono->mb_patch_branch (mb, label1); -#endif - } - - break; - - case MARSHAL_ACTION_CONV_OUT: { -#ifndef DISABLE_NONBLITTABLE - gboolean need_convert, need_free; - /* Unicode character arrays are implicitly marshalled as [Out] under MS.NET */ - need_convert = ((eklass == cb_to_mono->mono_defaults->char_class) && (encoding == MONO_NATIVE_LPWSTR)) || (eklass == cb_to_mono->try_get_stringbuilder_class ()) || (t->attrs & PARAM_ATTRIBUTE_OUT); - need_free = cb_to_mono->need_free (m_class_get_byval_arg (eklass), m->piinfo, spec); - - if ((t->attrs & PARAM_ATTRIBUTE_OUT) && spec && spec->native == MONO_NATIVE_LPARRAY && spec->data.array_data.param_num != -1) { - int param_num = spec->data.array_data.param_num; - MonoType *param_type; - - param_type = m->sig->params [param_num]; - - if (m_type_is_byref (param_type) && param_type->type != MONO_TYPE_I4) { - char *msg = g_strdup ("Not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (m_type_is_byref (t) ) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - /* Create the managed array */ - cb_to_mono->mb_emit_ldarg (mb, param_num); - if (m_type_is_byref (m->sig->params [param_num])) - // FIXME: Support other types - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I4); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_OVF_I); - cb_to_mono->mb_emit_op (mb, CEE_NEWARR, eklass); - /* Store into argument */ - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - } - - if (need_convert || need_free) { - /* FIXME: Optimize blittable case */ - guint32 label1, label2, label3; - int index_var, src_ptr, loc, esize; - - if ((eklass == cb_to_mono->try_get_stringbuilder_class ()) || (eklass == cb_to_mono->mono_defaults->string_class)) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - src_ptr = cb_to_mono->mb_add_local (mb, int_type); - loc = cb_to_mono->mb_add_local (mb, int_type); - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, src_ptr); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - - if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - gboolean need_free2; - MonoMarshalConv conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free2); - - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - - if (need_free) { - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - } - else if (eklass == cb_to_mono->mono_defaults->string_class) { - if (need_free) { - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - } - else { - if (need_convert) { - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_op (mb, CEE_LDELEMA, eklass); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv_full (mb, eklass, TRUE, 0, eklass == cb_to_mono->mono_defaults->char_class ? encoding : (MonoMarshalNative)-1); - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_stloc (mb, loc); - - emit_struct_free (mb, eklass, loc); - } - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); - } -#endif - - if (m_class_is_blittable (eklass)) { - /* free memory allocated (if any) by MONO_MARSHAL_CONV_ARRAY_LPARRAY */ - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_FREE_LPARRAY, NULL)); - } - - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: { - cb_to_mono->mb_emit_byte (mb, CEE_POP); - char *msg = g_strdup_printf ("Cannot marshal 'return value': Invalid managed/unmanaged type combination."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - guint32 label1, label2, label3; - int index_var, src_ptr, esize, param_num, num_elem; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - char *msg = g_strdup ("Byref array marshalling to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - if (!spec) { - char *msg = g_strdup ("[MarshalAs] attribute required to marshal arrays to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - switch (spec->native) { - case MONO_NATIVE_LPARRAY: - break; - case MONO_NATIVE_SAFEARRAY: -#ifndef DISABLE_COM - if (spec->data.safearray_data.elem_type != MONO_VARIANT_VARIANT) { - char *msg = g_strdup ("Only SAFEARRAY(VARIANT) marshalling to managed code is implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - default: { - char *msg = g_strdup ("Unsupported array type marshalling to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - - /* FIXME: t is from the method which is wrapped, not the delegate type */ - /* g_assert (t->attrs & PARAM_ATTRIBUTE_IN); */ - - param_num = spec->data.array_data.param_num; - num_elem = spec->data.array_data.num_elem; - if (spec->data.array_data.elem_mult == 0) - /* param_num is not specified */ - param_num = -1; - - if (param_num == -1) { - if (num_elem <= 0) { - char *msg = g_strdup ("Either SizeConst or SizeParamIndex should be specified when marshalling arrays to managed code."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - - /* FIXME: Optimize blittable case */ - -#ifndef DISABLE_NONBLITTABLE - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - gboolean need_free; - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - gboolean need_free; - conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); - } - else - conv = MONO_MARSHAL_CONV_INVALID; -#endif - - cb_to_mono->load_type_info (eklass); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - src_ptr = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - /* Check param index */ - if (param_num != -1) { - if (param_num >= m->sig->param_count) { - char *msg = g_strdup ("Array size control parameter index is out of range."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - switch (m->sig->params [param_num]->type) { - case MONO_TYPE_I1: - case MONO_TYPE_U1: - case MONO_TYPE_I2: - case MONO_TYPE_U2: - case MONO_TYPE_I4: - case MONO_TYPE_U4: - case MONO_TYPE_I: - case MONO_TYPE_U: - case MONO_TYPE_I8: - case MONO_TYPE_U8: - break; - default: { - char *msg = g_strdup ("Array size control parameter must be an integral type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - } - } - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, src_ptr); - - /* Create managed array */ - /* - * The LPArray marshalling spec says that sometimes param_num starts - * from 1, sometimes it starts from 0. But MS seems to allways start - * from 0. - */ - - if (param_num == -1) { - cb_to_mono->mb_emit_icon (mb, num_elem); - } else { - cb_to_mono->mb_emit_ldarg (mb, param_num); - if (num_elem > 0) { - cb_to_mono->mb_emit_icon (mb, num_elem); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_CONV_OVF_I); - } - - cb_to_mono->mb_emit_op (mb, CEE_NEWARR, eklass); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_class_is_blittable (eklass)) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_CPBLK); - cb_to_mono->mb_patch_branch (mb, label1); - break; - } -#ifdef DISABLE_NONBLITTABLE - else { - char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } -#else - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_ldloc (mb, src_ptr); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_STELEM_REF); - } - else { - char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); -#endif - - break; - } - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - guint32 label1, label2, label3; - int index_var, dest_ptr, esize, param_num, num_elem; - MonoMarshalConv conv; - gboolean is_string = FALSE; - - if (!spec) - /* Already handled in CONV_IN */ - break; - - /* These are already checked in CONV_IN */ - g_assert (!m_type_is_byref (t)); - g_assert (spec->native == MONO_NATIVE_LPARRAY); - g_assert (t->attrs & PARAM_ATTRIBUTE_OUT); - - param_num = spec->data.array_data.param_num; - num_elem = spec->data.array_data.num_elem; - - if (spec->data.array_data.elem_mult == 0) - /* param_num is not specified */ - param_num = -1; - - if (param_num == -1) { - if (num_elem <= 0) { - g_assert_not_reached (); - } - } - - /* FIXME: Optimize blittable case */ - -#ifndef DISABLE_NONBLITTABLE - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else if (eklass == cb_to_mono->try_get_stringbuilder_class ()) { - is_string = TRUE; - conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - } - else - conv = MONO_MARSHAL_CONV_INVALID; -#endif - - cb_to_mono->load_type_info (eklass); - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - dest_ptr = cb_to_mono->mb_add_local (mb, int_type); - - /* Check null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, dest_ptr); - - if (m_class_is_blittable (eklass)) { - /* dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - /* src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - /* length */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_CPBLK); - cb_to_mono->mb_patch_branch (mb, label1); - break; - } - -#ifndef DISABLE_NONBLITTABLE - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - int stind_op; - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldloc (mb, dest_ptr); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - else { - char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label1); - cb_to_mono->mb_patch_branch (mb, label3); -#endif - - break; - } - case MARSHAL_ACTION_MANAGED_CONV_RESULT: { -#ifndef DISABLE_NONBLITTABLE - guint32 label1, label2, label3; - int index_var, src, dest, esize; - MonoMarshalConv conv = MONO_MARSHAL_CONV_INVALID; - gboolean is_string = FALSE; - - g_assert (!m_type_is_byref (t)); - - cb_to_mono->load_type_info (eklass); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - is_string = TRUE; - conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - } - else { - g_assert_not_reached (); - } - - if (is_string) - esize = TARGET_SIZEOF_VOID_P; - else if (eklass == cb_to_mono->mono_defaults->char_class) - esize = cb_to_mono->pinvoke_is_unicode (m->piinfo) ? 2 : 1; - else - esize = cb_to_mono->class_native_size (eklass, NULL); - - src = cb_to_mono->mb_add_local (mb, object_type); - dest = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_stloc (mb, src); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, src); - label1 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Allocate native array */ - cb_to_mono->mb_emit_icon (mb, esize); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - - if (eklass == cb_to_mono->mono_defaults->string_class) { - /* Make the array bigger for the terminating null */ - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - } - cb_to_mono->mb_emit_byte (mb, CEE_MUL); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, dest); - cb_to_mono->mb_emit_ldloc (mb, dest); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Emit marshalling loop */ - index_var = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, index_var); - label2 = cb_to_mono->mb_get_label (mb); - cb_to_mono->mb_emit_ldloc (mb, index_var); - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_byte (mb, CEE_LDLEN); - label3 = cb_to_mono->mb_emit_branch (mb, CEE_BGE); - - /* Emit marshalling code */ - if (is_string) { - int stind_op; - g_assert (conv != MONO_MARSHAL_CONV_INVALID); - - /* dest */ - cb_to_mono->mb_emit_ldloc (mb, dest); - - /* src */ - cb_to_mono->mb_emit_ldloc (mb, src); - cb_to_mono->mb_emit_ldloc (mb, index_var); - - cb_to_mono->mb_emit_byte (mb, CEE_LDELEM_REF); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - else { - char *msg = g_strdup ("Marshalling of non-string arrays to managed code is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - return conv_arg; - } - - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); - cb_to_mono->mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest), esize); - - cb_to_mono->mb_emit_branch_label (mb, CEE_BR, label2); - - cb_to_mono->mb_patch_branch (mb, label3); - cb_to_mono->mb_patch_branch (mb, label1); -#endif - break; - } - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static gboolean -emit_native_wrapper_validate_signature (MonoMethodBuilder *mb, MonoMethodSignature* sig, MonoMarshalSpec** mspecs) -{ - if (mspecs) { - for (int i = 0; i < sig->param_count; i ++) { - if (mspecs [i + 1] && mspecs [i + 1]->native == MONO_NATIVE_CUSTOM) { - if (!mspecs [i + 1]->data.custom_data.custom_name || *mspecs [i + 1]->data.custom_data.custom_name == '\0') { - cb_to_mono->mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Missing ICustomMarshaler type")); - return FALSE; - } - - switch (sig->params[i]->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - break; - - default: - cb_to_mono->mb_emit_exception_full (mb, "System.Runtime.InteropServices", "MarshalDirectiveException", g_strdup_printf ("custom marshalling of type %x is currently not supported", sig->params[i]->type)); - return FALSE; - } - } - else if (sig->params[i]->type == MONO_TYPE_VALUETYPE) { - MonoMarshalType *marshal_type = mono_marshal_load_type_info (mono_class_from_mono_type_internal (sig->params [i])); - for (guint32 field_idx = 0; field_idx < marshal_type->num_fields; ++field_idx) { - if (marshal_type->fields [field_idx].mspec && marshal_type->fields [field_idx].mspec->native == MONO_NATIVE_CUSTOM) { - cb_to_mono->mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Value type includes custom marshaled fields")); - return FALSE; - } - } - } - } - } - - return TRUE; -} - -static int -emit_marshal_ptr_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - switch (action) { - case MARSHAL_ACTION_CONV_IN: - /* MS seems to allow this in some cases, ie. bxc #158 */ - /* - if (MONO_TYPE_ISSTRUCT (t->data.type) && !mono_class_from_mono_type_internal (t->data.type)->blittable) { - char *msg = g_strdup_printf ("Can not marshal 'parameter #%d': Pointers can not reference marshaled structures. Use byref instead.", argnum + 1); - cb_to_mono->mb_emit_exception_marshal_directive (m->mb, msg); - } - */ - break; - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* no conversions necessary */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - break; - } - return conv_arg; -} - -static int -emit_marshal_boolean_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *boolean_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->boolean_class); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - MonoType *local_type; - int label_false; - guint8 ldc_op = CEE_LDC_I4_1; - - local_type = cb_to_mono->boolean_conv_in_get_local_type (spec, &ldc_op); - if (m_type_is_byref (t)) - *conv_arg_type = int_type; - else - *conv_arg_type = local_type; - conv_arg = cb_to_mono->mb_add_local (mb, local_type); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I1); - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, ldc_op); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_patch_branch (mb, label_false); - - break; - } - - case MARSHAL_ACTION_CONV_OUT: - { - int label_false, label_end; - if (!m_type_is_byref (t)) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - - label_end = cb_to_mono->mb_emit_branch (mb, CEE_BR); - cb_to_mono->mb_patch_branch (mb, label_false); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_patch_branch (mb, label_end); - - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I1); - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else if (conv_arg) - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - else - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* maybe we need to make sure that it fits within 8 bits */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - MonoClass* conv_arg_class = cb_to_mono->mono_defaults->int32_class; - guint8 ldop = CEE_LDIND_I4; - int label_null, label_false; - - conv_arg_class = cb_to_mono->boolean_managed_conv_in_get_conv_arg_class (spec, &ldop); - conv_arg = cb_to_mono->mb_add_local (mb, boolean_type); - - if (m_type_is_byref (t)) - *conv_arg_type = m_class_get_this_arg (conv_arg_class); - else - *conv_arg_type = m_class_get_byval_arg (conv_arg_class); - - - cb_to_mono->mb_emit_ldarg (mb, argnum); - - /* Check null */ - if (m_type_is_byref (t)) { - label_null = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, ldop); - } else - label_null = 0; - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_1); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_patch_branch (mb, label_false); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, label_null); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - guint8 stop = CEE_STIND_I4; - guint8 ldc_op = CEE_LDC_I4_1; - int label_null,label_false, label_end; - - if (!m_type_is_byref (t)) - break; - if (spec) { - switch (spec->native) { - case MONO_NATIVE_I1: - case MONO_NATIVE_U1: - stop = CEE_STIND_I1; - break; - case MONO_NATIVE_VARIANTBOOL: - stop = CEE_STIND_I2; - ldc_op = CEE_LDC_I4_M1; - break; - default: - break; - } - } - - /* Check null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - label_null = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - label_false = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_byte (mb, ldc_op); - label_end = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, label_false); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_patch_branch (mb, label_end); - - cb_to_mono->mb_emit_byte (mb, stop); - cb_to_mono->mb_patch_branch (mb, label_null); - break; - } - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_char_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - switch (action) { - case MARSHAL_ACTION_PUSH: - /* fixme: dont know how to marshal that. We cant simply - * convert it to a one byte UTF8 character, because an - * unicode character may need more that one byte in UTF8 */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - - case MARSHAL_ACTION_CONV_RESULT: - /* fixme: we need conversions here */ - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - break; - } - return conv_arg; -} - -static int -emit_marshal_custom_ilgen_throw_exception (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg, MarshalAction action) -{ - /* Throw exception and emit compensation code, if neccesary */ - switch (action) { - case MARSHAL_ACTION_CONV_IN: - case MARSHAL_ACTION_MANAGED_CONV_IN: - case MARSHAL_ACTION_CONV_RESULT: - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if ((action == MARSHAL_ACTION_CONV_RESULT) || (action == MARSHAL_ACTION_MANAGED_CONV_RESULT)) - cb_to_mono->mb_emit_byte (mb, CEE_POP); - - cb_to_mono->mb_emit_exception_full (mb, exc_nspace, exc_name, msg); - - break; - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - break; - default: - break; - } - - return 0; -} - -static int -emit_marshal_custom_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - ERROR_DECL (error); - MonoType *mtype; - MonoClass *mklass; - static MonoClass *ICustomMarshaler = NULL; - static MonoMethod *cleanup_native, *cleanup_managed; - static MonoMethod *marshal_managed_to_native, *marshal_native_to_managed; - MonoMethodBuilder *mb = m->mb; - MonoAssemblyLoadContext *alc = mono_alc_get_ambient (); - guint32 loc1; - int pos2; - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - - if (!ICustomMarshaler) { - MonoClass *klass = mono_class_try_get_icustom_marshaler_class (); - if (!klass) - return emit_marshal_custom_ilgen_throw_exception (mb, "System", "ApplicationException", g_strdup ("Current profile doesn't support ICustomMarshaler"), action); - - cleanup_native = cb_to_mono->get_method_nofail (klass, "CleanUpNativeData", 1, 0); - g_assert (cleanup_native); - - cleanup_managed = cb_to_mono->get_method_nofail (klass, "CleanUpManagedData", 1, 0); - g_assert (cleanup_managed); - - marshal_managed_to_native = cb_to_mono->get_method_nofail (klass, "MarshalManagedToNative", 1, 0); - g_assert (marshal_managed_to_native); - - marshal_native_to_managed = cb_to_mono->get_method_nofail (klass, "MarshalNativeToManaged", 1, 0); - g_assert (marshal_native_to_managed); - - cb_to_mono->memory_barrier (); - ICustomMarshaler = klass; - } - - if (spec->data.custom_data.image) - mtype = cb_to_mono->reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, spec->data.custom_data.image, error); - else - mtype = cb_to_mono->reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, m->image, error); - - if (!mtype) - return emit_marshal_custom_ilgen_throw_exception (mb, "System", "TypeLoadException", g_strdup ("Failed to load ICustomMarshaler type"), action); - - mklass = mono_class_from_mono_type_internal (mtype); - g_assert (mklass != NULL); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - switch (t->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - break; - - default: - g_warning ("custom marshalling of type %x is currently not supported", t->type); - g_assert_not_reached (); - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - /* Minic MS.NET behavior */ - if (!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN)) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - - if (t->type == MONO_TYPE_VALUETYPE) { - /* - * Since we can't determine the type of the argument, we - * will assume the unmanaged function takes a pointer. - */ - *conv_arg_type = int_type; - - cb_to_mono->mb_emit_op (mb, CEE_BOX, mono_class_from_mono_type_internal (t)); - } - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_CONV_OUT: - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (t->attrs & PARAM_ATTRIBUTE_OUT) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - /* We have nowhere to store the result */ - cb_to_mono->mb_emit_byte (mb, CEE_POP); - } - - // Only call cleanup_native if MARSHAL_ACTION_CONV_IN called marshal_managed_to_native. - if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) && - !(!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_native); - } - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - switch (t->type) { - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: - case MONO_TYPE_STRING: - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_VALUETYPE: - case MONO_TYPE_BOOLEAN: - break; - - default: - g_warning ("custom marshalling of type %x is currently not supported", t->type); - g_assert_not_reached (); - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - g_assert (!m_type_is_byref (t)); - - loc1 = cb_to_mono->mb_add_local (mb, object_type); - - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_stloc (mb, loc1); - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, loc1); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - } - - // Only call cleanup_managed if MARSHAL_ACTION_MANAGED_CONV_IN called marshal_native_to_managed. - if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - cb_to_mono->emit_marshal_custom_get_instance (mb, mklass, spec); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); - } - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_asany_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, NULL); - - g_assert (t->type == MONO_TYPE_OBJECT); - g_assert (!m_type_is_byref (t)); - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_icon (mb, encoding); - cb_to_mono->mb_emit_icon (mb, t->attrs); - mono_mb_emit_jit_icall (mb, mono_marshal_asany); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, NULL); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icon (mb, encoding); - cb_to_mono->mb_emit_icon (mb, t->attrs); - mono_mb_emit_jit_icall (mb, mono_marshal_free_asany); - break; - } - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_vtype_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass, *date_time_class; - int pos = 0, pos2; - - klass = mono_class_from_mono_type_internal (t); - - date_time_class = mono_class_get_date_time_class (); - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *double_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->double_class); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: - if (klass == date_time_class) { - /* Convert it to an OLE DATE type */ - - conv_arg = cb_to_mono->mb_add_local (mb, double_type); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - if (!m_type_is_byref (t)) - m->csig->params [argnum - m->csig->hasthis] = double_type; - - MONO_STATIC_POINTER_INIT (MonoMethod, to_oadate) - to_oadate = cb_to_mono->get_method_nofail (date_time_class, "ToOADate", 0, 0); - g_assert (to_oadate); - MONO_STATIC_POINTER_INIT_END (MonoMethod, to_oadate) - - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, to_oadate, NULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - /* store the address of the source into local variable 0 */ - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - - cb_to_mono->mb_emit_stloc (mb, 0); - - /* allocate space for the native struct and - * store the address into local variable 1 (dest) */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - } - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_PUSH: - if (spec && spec->native == MONO_NATIVE_LPSTRUCT) { - /* FIXME: */ - g_assert (!m_type_is_byref (t)); - - /* Have to change the signature since the vtype is passed byref */ - m->csig->params [argnum - m->csig->hasthis] = int_type; - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - } - - if (klass == date_time_class) { - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - break; - } - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - if (!m_type_is_byref (t)) { - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_LDNATIVEOBJ, klass); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - if (klass == date_time_class) { - /* Convert from an OLE DATE type */ - - if (!m_type_is_byref (t)) - break; - - if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { - - MONO_STATIC_POINTER_INIT (MonoMethod, from_oadate) - from_oadate = cb_to_mono->get_method_nofail (date_time_class, "FromOADate", 1, 0); - MONO_STATIC_POINTER_INIT_END (MonoMethod, from_oadate) - - g_assert (from_oadate); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, from_oadate, NULL); - cb_to_mono->mb_emit_op (mb, CEE_STOBJ, date_time_class); - } - break; - } - - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - - if (m_type_is_byref (t)) { - /* dst = argument */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - cb_to_mono->mb_emit_ldloc (mb, 1); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { - /* src = tmp_locals [i] */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - } - } - - emit_struct_free (mb, klass, conv_arg); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_CONV_RESULT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass)) { - cb_to_mono->mb_emit_stloc (mb, 3); - break; - } - - /* load pointer to returned value type */ - g_assert (m->vtaddr_var); - cb_to_mono->mb_emit_ldloc (mb, m->vtaddr_var); - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc_addr (mb, 3); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - conv_arg = 0; - break; - } - - conv_arg = cb_to_mono->mb_add_local (mb, m_class_get_byval_arg (klass)); - - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 0); - - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - } - - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - if (m_type_is_byref (t)) - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) - break; - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - /* Check for null */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Set src */ - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Set dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { - cb_to_mono->mb_emit_stloc (mb, 3); - m->retobj_var = 0; - break; - } - - /* load pointer to returned value type */ - g_assert (m->vtaddr_var); - cb_to_mono->mb_emit_ldloc (mb, m->vtaddr_var); - - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - /* allocate space for the native struct and - * store the address into dst_ptr */ - m->retobj_var = cb_to_mono->mb_add_local (mb, int_type); - m->retobj_class = klass; - g_assert (m->retobj_var); - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, 1); - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_stloc (mb, m->retobj_var); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static void -emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv) -{ - if (conv == MONO_MARSHAL_CONV_BSTR_STR || conv == MONO_MARSHAL_CONV_ANSIBSTR_STR || conv == MONO_MARSHAL_CONV_TBSTR_STR) - mono_mb_emit_jit_icall (mb, mono_free_bstr); - else - mono_mb_emit_jit_icall (mb, mono_marshal_free); -} - -static int -emit_marshal_string_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoMarshalConv conv = cb_to_mono->get_string_to_ptr_conv (m->piinfo, spec); - gboolean need_free; - - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *object_type = cb_to_mono->get_object_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - if (m_type_is_byref (t)) { - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - } - - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } else { - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (encoding == MONO_NATIVE_VBBYREFSTR) { - - if (!m_type_is_byref (t)) { - char *msg = g_strdup ("VBByRefStr marshalling requires a ref parameter."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - MONO_STATIC_POINTER_INIT (MonoMethod, method) - - method = cb_to_mono->get_method_nofail (cb_to_mono->mono_defaults->string_class, "get_Length", -1, 0); - - MONO_STATIC_POINTER_INIT_END (MonoMethod, method) - - /* - * Have to allocate a new string with the same length as the original, and - * copy the contents of the buffer pointed to by CONV_ARG into it. - */ - g_assert (m_type_is_byref (t)); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_managed_call (mb, method, NULL); - mono_mb_emit_jit_icall (mb, mono_string_new_len_wrapper); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - need_free = TRUE; - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - emit_string_free_icall (mb, conv); - } - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t) && encoding != MONO_NATIVE_VBBYREFSTR) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - cb_to_mono->mb_emit_stloc (mb, 0); - - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* free the string */ - cb_to_mono->mb_emit_ldloc (mb, 0); - emit_string_free_icall (mb, conv); - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - } - - conv = cb_to_mono->get_ptr_to_string_conv (m->piinfo, spec, &need_free); - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (m_type_is_byref (t)) { - if (conv_arg) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - } - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (cb_to_mono->conv_to_icall (conv, NULL) == MONO_JIT_ICALL_mono_marshal_string_to_utf16) - /* We need to make a copy so the caller is able to free it */ - mono_mb_emit_jit_icall (mb, mono_marshal_string_to_utf16_copy); - else - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_safehandle_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoType *int_type = cb_to_mono->get_int_type (); - MonoType *boolean_type = m_class_get_byval_arg (cb_to_mono->mono_defaults->boolean_class); - - switch (action){ - case MARSHAL_ACTION_CONV_IN: { - int dar_release_slot, pos; - - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - *conv_arg_type = int_type; - - if (!*cb_to_mono->get_sh_dangerous_add_ref()) - cb_to_mono->init_safe_handle (); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_exception (mb, "ArgumentNullException", NULL); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Create local to hold the ref parameter to DangerousAddRef */ - dar_release_slot = cb_to_mono->mb_add_local (mb, boolean_type); - - /* set release = false; */ - cb_to_mono->mb_emit_icon (mb, 0); - cb_to_mono->mb_emit_stloc (mb, dar_release_slot); - - if (m_type_is_byref (t)) { - int old_handle_value_slot = cb_to_mono->mb_add_local (mb, int_type); - - if (!cb_to_mono->is_in (t)) { - cb_to_mono->mb_emit_icon (mb, 0); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { - /* safehandle.DangerousAddRef (ref release) */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc_addr (mb, dar_release_slot); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_add_ref(), NULL); - - /* Pull the handle field from SafeHandle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, old_handle_value_slot); - } - } else { - /* safehandle.DangerousAddRef (ref release) */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc_addr (mb, dar_release_slot); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_add_ref(), NULL); - - /* Pull the handle field from SafeHandle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - /* The slot for the boolean is the next temporary created after conv_arg, see the CONV_IN code */ - int dar_release_slot = conv_arg + 1; - int label_next = 0; - - if (!*cb_to_mono->get_sh_dangerous_release()) - cb_to_mono->init_safe_handle (); - - if (m_type_is_byref (t)) { - /* If there was SafeHandle on input we have to release the reference to it */ - if (cb_to_mono->is_in (t)) { - cb_to_mono->mb_emit_ldloc (mb, dar_release_slot); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_release (), NULL); - cb_to_mono->mb_patch_branch (mb, label_next); - } - - if (cb_to_mono->is_out (t)) { - ERROR_DECL (local_error); - MonoMethod *ctor; - - /* - * If the SafeHandle was marshalled on input we can skip the marshalling on - * output if the handle value is identical. - */ - if (cb_to_mono->is_in (t)) { - int old_handle_value_slot = dar_release_slot + 1; - cb_to_mono->mb_emit_ldloc (mb, old_handle_value_slot); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BEQ); - } - - /* - * Create an empty SafeHandle (of correct derived type). - * - * FIXME: If an out-of-memory situation or exception happens here we will - * leak the handle. We should move the allocation of the SafeHandle to the - * input marshalling code to prevent that. - */ - ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, local_error); - if (ctor == NULL || !is_ok (local_error)){ - cb_to_mono->mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); - mono_error_cleanup (local_error); - break; - } - - /* refval = new SafeHandleDerived ()*/ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_op (mb, CEE_NEWOBJ, ctor); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - - /* refval.handle = returned_handle */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - - if (cb_to_mono->is_in (t) && label_next) { - cb_to_mono->mb_patch_branch (mb, label_next); - } - } - } else { - cb_to_mono->mb_emit_ldloc (mb, dar_release_slot); - label_next = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, *cb_to_mono->get_sh_dangerous_release (), NULL); - cb_to_mono->mb_patch_branch (mb, label_next); - } - break; - } - - case MARSHAL_ACTION_CONV_RESULT: { - ERROR_DECL (error); - MonoMethod *ctor = NULL; - int intptr_handle_slot; - - if (mono_class_is_abstract (t->data.klass)) { - cb_to_mono->mb_emit_byte (mb, CEE_POP); - cb_to_mono->mb_emit_exception_marshal_directive (mb, g_strdup ("Returned SafeHandles should not be abstract")); - break; - } - - ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, error); - if (ctor == NULL || !is_ok (error)){ - mono_error_cleanup (error); - cb_to_mono->mb_emit_byte (mb, CEE_POP); - cb_to_mono->mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); - break; - } - /* Store the IntPtr results into a local */ - intptr_handle_slot = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_stloc (mb, intptr_handle_slot); - - /* Create return value */ - cb_to_mono->mb_emit_op (mb, CEE_NEWOBJ, ctor); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* Set the return.handle to the value, am using ldflda, not sure if thats a good idea */ - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); - cb_to_mono->mb_emit_ldloc (mb, intptr_handle_slot); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); - break; - default: - printf ("Unhandled case for MarshalAction: %d\n", action); - } - return conv_arg; -} - -static int -emit_marshal_handleref_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action){ - case MARSHAL_ACTION_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - *conv_arg_type = int_type; - - if (m_type_is_byref (t)) { - char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoHandleRef, handle)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_PUSH: - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_OUT: { - /* no resource release required */ - break; - } - - case MARSHAL_ACTION_CONV_RESULT: { - char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); - break; - default: - fprintf (stderr, "Unhandled case for MarshalAction: %d\n", action); - } - return conv_arg; -} - -static int -emit_marshal_object_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ - MonoMethodBuilder *mb = m->mb; - MonoClass *klass = mono_class_from_mono_type_internal (t); - int pos, pos2, loc; - - MonoType *int_type = cb_to_mono->get_int_type (); - switch (action) { - case MARSHAL_ACTION_CONV_IN: - *conv_arg_type = int_type; - conv_arg = cb_to_mono->mb_add_local (mb, int_type); - - m->orig_conv_args [argnum] = 0; - - if (mono_class_from_mono_type_internal (t) == cb_to_mono->mono_defaults->object_class) { - char *msg = g_strdup_printf ("Marshalling of type object is not implemented"); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - char *msg = g_strdup_printf ("Byref marshalling of delegates is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - MonoMarshalNative encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - MonoMarshalConv conv = cb_to_mono->get_stringbuilder_to_ptr_conv (m->piinfo, spec); - -#if 0 - if (m_type_is_byref (t)) { - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - char *msg = g_strdup_printf ("Byref marshalling of stringbuilders is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } - break; - } -#endif - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT)) - break; - - if (conv == MONO_MARSHAL_CONV_INVALID) { - char *msg = g_strdup_printf ("stringbuilder marshalling conversion %d not implemented", encoding); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - } else if (m_class_is_blittable (klass)) { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - } else { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - /* we dont need any conversions for out parameters */ - if (t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - } else { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_byte (mb, CEE_MONO_OBJADDR); - } - - /* store the address of the source into local variable 0 */ - cb_to_mono->mb_emit_stloc (mb, 0); - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* allocate space for the native struct and store the address */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_PREFIX1); - cb_to_mono->mb_emit_byte (mb, CEE_LOCALLOC); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - if (m_type_is_byref (t)) { - /* Need to store the original buffer so we can free it later */ - m->orig_conv_args [argnum] = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, m->orig_conv_args [argnum]); - } - - /* set the src_ptr */ - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* set dst_ptr */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos); - } - break; - - case MARSHAL_ACTION_CONV_OUT: - if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - gboolean need_free; - MonoMarshalNative encoding; - MonoMarshalConv conv; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - conv = cb_to_mono->get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); - - g_assert (encoding != -1); - - if (m_type_is_byref (t)) { - //g_assert (!(t->attrs & PARAM_ATTRIBUTE_OUT)); - - need_free = TRUE; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - switch (encoding) { - case MONO_NATIVE_LPWSTR: - mono_mb_emit_jit_icall (mb, mono_string_utf16_to_builder2); - break; - case MONO_NATIVE_LPSTR: - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - break; - case MONO_NATIVE_UTF8STR: - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - break; - default: - g_assert_not_reached (); - } - - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } else if (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (conv, NULL)); - } - - if (need_free) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - } - break; - } - - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - break; - } - - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { - /* allocate a new object */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - - /* dst = *argument */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - - cb_to_mono->mb_emit_stloc (mb, 1); - - cb_to_mono->mb_emit_ldloc (mb, 1); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - if (m_type_is_byref (t) || (t->attrs & PARAM_ATTRIBUTE_OUT)) { - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_icon (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_byte (mb, CEE_ADD); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* src = tmp_locals [i] */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - /* Free the structure returned by the native code */ - emit_struct_free (mb, klass, conv_arg); - - if (m->orig_conv_args [argnum]) { - /* - * If the native function changed the pointer, then free - * the original structure plus the new pointer. - */ - cb_to_mono->mb_emit_ldloc (mb, m->orig_conv_args [argnum]); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BEQ); - - if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { - g_assert (m->orig_conv_args [argnum]); - - emit_struct_free (mb, klass, m->orig_conv_args [argnum]); - } - - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - - cb_to_mono->mb_patch_branch (mb, pos2); - } - } - else - /* Free the original structure passed to native code */ - emit_struct_free (mb, klass, conv_arg); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: - if (m_class_is_delegate (klass)) { - g_assert (!m_type_is_byref (t)); - cb_to_mono->mb_emit_stloc (mb, 0); - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - // FIXME: - char *msg = g_strdup_printf ("Return marshalling of stringbuilders is not implemented."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - } else { - /* set src */ - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Make a copy since emit_conv modifies local 0 */ - loc = cb_to_mono->mb_add_local (mb, int_type); - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_stloc (mb, loc); - - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* allocate result object */ - - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_stloc (mb, 3); - - /* set dst */ - - cb_to_mono->mb_emit_ldloc (mb, 3); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - emit_struct_free (mb, klass, loc); - - /* Free the pointer allocated by unmanaged code */ - cb_to_mono->mb_emit_ldloc (mb, loc); - mono_mb_emit_jit_icall (mb, mono_marshal_free); - cb_to_mono->mb_patch_branch (mb, pos); - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_IN: - conv_arg = cb_to_mono->mb_add_local (mb, m_class_get_byval_arg (klass)); - - if (m_class_is_delegate (klass)) { - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - MonoMarshalNative encoding; - - encoding = cb_to_mono->get_string_encoding (m->piinfo, spec); - - // FIXME: - g_assert (encoding == MONO_NATIVE_LPSTR || encoding == MONO_NATIVE_UTF8STR); - - g_assert (!m_type_is_byref (t)); - g_assert (encoding != -1); - - cb_to_mono->mb_emit_ldarg (mb, argnum); - mono_mb_emit_jit_icall (mb, mono_string_utf8_to_builder2); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - /* The class can not have an automatic layout */ - if (mono_class_is_auto_layout (klass)) { - cb_to_mono->mb_emit_auto_layout_exception (mb, klass); - break; - } - - if (t->attrs & PARAM_ATTRIBUTE_OUT) { - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - /* Set src */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) { - /* Check for NULL and raise an exception */ - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - - cb_to_mono->mb_emit_exception (mb, "ArgumentNullException", NULL); - - cb_to_mono->mb_patch_branch (mb, pos2); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDIND_I); - } - - cb_to_mono->mb_emit_stloc (mb, 0); - - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRFALSE); - - /* Create and set dst */ - cb_to_mono->mb_emit_byte (mb, MONO_CUSTOM_PREFIX); - cb_to_mono->mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, TRUE); - - cb_to_mono->mb_patch_branch (mb, pos); - break; - - case MARSHAL_ACTION_MANAGED_CONV_OUT: - if (m_class_is_delegate (klass)) { - if (m_type_is_byref (t)) { - int stind_op; - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, &stind_op)); - cb_to_mono->mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); - break; - } - } - - if (m_type_is_byref (t)) { - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_byte (mb, CEE_LDC_I4_0); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Allocate and set dest */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* Update argument pointer */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc (mb, 1); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_I); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - } else if (klass == cb_to_mono->try_get_stringbuilder_class ()) { - // FIXME: What to do here ? - } else { - /* byval [Out] marshalling */ - - /* FIXME: Handle null */ - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Set dest */ - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_stloc (mb, 1); - - /* emit valuetype conversion code */ - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - } - break; - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: - if (m_class_is_delegate (klass)) { - cb_to_mono->mb_emit_icall_id (mb, cb_to_mono->conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); - cb_to_mono->mb_emit_stloc (mb, 3); - break; - } - - /* The class can not have an automatic layout */ - if (mono_class_is_auto_layout (klass)) { - cb_to_mono->mb_emit_auto_layout_exception (mb, klass); - break; - } - - cb_to_mono->mb_emit_stloc (mb, 0); - /* Check for null */ - cb_to_mono->mb_emit_ldloc (mb, 0); - pos = cb_to_mono->mb_emit_branch (mb, CEE_BRTRUE); - cb_to_mono->mb_emit_byte (mb, CEE_LDNULL); - cb_to_mono->mb_emit_stloc (mb, 3); - pos2 = cb_to_mono->mb_emit_branch (mb, CEE_BR); - - cb_to_mono->mb_patch_branch (mb, pos); - - /* Set src */ - cb_to_mono->mb_emit_ldloc (mb, 0); - cb_to_mono->mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); - cb_to_mono->mb_emit_stloc (mb, 0); - - /* Allocate and set dest */ - cb_to_mono->mb_emit_icon (mb, cb_to_mono->class_native_size (klass, NULL)); - cb_to_mono->mb_emit_byte (mb, CEE_CONV_I); - mono_mb_emit_jit_icall (mb, ves_icall_marshal_alloc); - cb_to_mono->mb_emit_byte (mb, CEE_DUP); - cb_to_mono->mb_emit_stloc (mb, 1); - cb_to_mono->mb_emit_stloc (mb, 3); - - cb_to_mono->emit_struct_conv (mb, klass, FALSE); - - cb_to_mono->mb_patch_branch (mb, pos2); - break; - - default: - g_assert_not_reached (); - } - return conv_arg; -} - -static int -emit_marshal_variant_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, - int conv_arg, MonoType **conv_arg_type, - MarshalAction action) -{ -#ifndef DISABLE_COM - MonoMethodBuilder *mb = m->mb; - MonoType *variant_type = m_class_get_byval_arg (mono_class_get_variant_class ()); - MonoType *variant_type_byref = mono_class_get_byref_type (mono_class_get_variant_class ()); - MonoType *object_type = cb_to_mono->get_object_type (); - - switch (action) { - case MARSHAL_ACTION_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, variant_type); - - if (m_type_is_byref (t)) - *conv_arg_type = variant_type_byref; - else - *conv_arg_type = variant_type; - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - cb_to_mono->mb_emit_ldarg (mb, argnum); - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_byte(mb, CEE_LDIND_REF); - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); - break; - } - - case MARSHAL_ACTION_CONV_OUT: { - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); - cb_to_mono->mb_emit_byte (mb, CEE_STIND_REF); - } - - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Variant_Clear (), NULL); - break; - } - - case MARSHAL_ACTION_PUSH: - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldloc_addr (mb, conv_arg); - else - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - break; - - case MARSHAL_ACTION_CONV_RESULT: { - char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_IN: { - conv_arg = cb_to_mono->mb_add_local (mb, object_type); - - if (m_type_is_byref (t)) - *conv_arg_type = variant_type_byref; - else - *conv_arg_type = variant_type; - - if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) - break; - - if (m_type_is_byref (t)) - cb_to_mono->mb_emit_ldarg (mb, argnum); - else - cb_to_mono->mb_emit_ldarg_addr (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); - cb_to_mono->mb_emit_stloc (mb, conv_arg); - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_OUT: { - if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { - cb_to_mono->mb_emit_ldloc (mb, conv_arg); - cb_to_mono->mb_emit_ldarg (mb, argnum); - cb_to_mono->mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); - } - break; - } - - case MARSHAL_ACTION_MANAGED_CONV_RESULT: { - char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); - cb_to_mono->mb_emit_exception_marshal_directive (mb, msg); - break; - } - - default: - g_assert_not_reached (); - } -#endif /* DISABLE_COM */ - - return conv_arg; -} - - -static MonoMarshalILgenCallbacks * -get_marshal_cb (void) -{ - if (G_UNLIKELY (!ilgen_cb_inited)) { -#ifdef ENABLE_ILGEN - mono_marshal_ilgen_init (); -#else - mono_marshal_noilgen_init_heavyweight (); -#endif - } - return &ilgen_marshal_cb; -} - -int -mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) -{ - if (spec && spec->native == MONO_NATIVE_CUSTOM) - return get_marshal_cb ()->emit_marshal_custom (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - if (spec && spec->native == MONO_NATIVE_ASANY) - return get_marshal_cb ()->emit_marshal_asany (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - switch (t->type) { - case MONO_TYPE_VALUETYPE: - if (t->data.klass == cb_to_mono->class_try_get_handleref_class ()) - return get_marshal_cb ()->emit_marshal_handleref (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_STRING: - return get_marshal_cb ()->emit_marshal_string (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_CLASS: - case MONO_TYPE_OBJECT: -#if !defined(DISABLE_COM) - if (spec && spec->native == MONO_NATIVE_STRUCT) - return get_marshal_cb ()->emit_marshal_variant (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - -#if !defined(DISABLE_COM) - if ((spec && (spec->native == MONO_NATIVE_IUNKNOWN || - spec->native == MONO_NATIVE_IDISPATCH || - spec->native == MONO_NATIVE_INTERFACE)) || - (t->type == MONO_TYPE_CLASS && mono_cominterop_is_interface(t->data.klass))) - return mono_cominterop_emit_marshal_com_interface (m, argnum, t, spec, conv_arg, conv_arg_type, action); - if (spec && (spec->native == MONO_NATIVE_SAFEARRAY) && - (spec->data.safearray_data.elem_type == MONO_VARIANT_VARIANT) && - ((action == MARSHAL_ACTION_CONV_OUT) || (action == MARSHAL_ACTION_CONV_IN) || (action == MARSHAL_ACTION_PUSH))) - return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); -#endif - - if (cb_to_mono->try_get_safehandle_class () != NULL && t->data.klass && - cb_to_mono->is_subclass_of_internal (t->data.klass, cb_to_mono->try_get_safehandle_class (), FALSE)) - return get_marshal_cb ()->emit_marshal_safehandle (m, argnum, t, spec, conv_arg, conv_arg_type, action); - - return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return get_marshal_cb ()->emit_marshal_array (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_BOOLEAN: - return get_marshal_cb ()->emit_marshal_boolean (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_PTR: - return get_marshal_cb ()->emit_marshal_ptr (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_CHAR: - return get_marshal_cb ()->emit_marshal_char (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_I1: - case MONO_TYPE_U1: - case MONO_TYPE_I2: - case MONO_TYPE_U2: - case MONO_TYPE_I4: - case MONO_TYPE_U4: - case MONO_TYPE_I: - case MONO_TYPE_U: - case MONO_TYPE_R4: - case MONO_TYPE_R8: - case MONO_TYPE_I8: - case MONO_TYPE_U8: - case MONO_TYPE_FNPTR: - return lightweigth_cb->emit_marshal_scalar (m, argnum, t, spec, conv_arg, conv_arg_type, action); - case MONO_TYPE_GENERICINST: - if (mono_type_generic_inst_is_valuetype (t)) - return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); - else - return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); - default: - return conv_arg; - } -} - -void -mono_marshal_ilgen_init (void) -{ - MonoMarshalILgenCallbacks cb; - cb.version = MONO_MARSHAL_CALLBACKS_VERSION; - cb.emit_marshal_array = emit_marshal_array_ilgen; - cb.emit_marshal_ptr = emit_marshal_ptr_ilgen; - cb.emit_marshal_char = emit_marshal_char_ilgen; - cb.emit_marshal_vtype = emit_marshal_vtype_ilgen; - cb.emit_marshal_string = emit_marshal_string_ilgen; - cb.emit_marshal_variant = emit_marshal_variant_ilgen; - cb.emit_marshal_safehandle = emit_marshal_safehandle_ilgen; - cb.emit_marshal_object = emit_marshal_object_ilgen; - cb.emit_marshal_boolean = emit_marshal_boolean_ilgen; - cb.emit_marshal_custom = emit_marshal_custom_ilgen; - cb.emit_marshal_asany = emit_marshal_asany_ilgen; - cb.emit_marshal_handleref = emit_marshal_handleref_ilgen; - -#ifdef DISABLE_NONBLITTABLE - mono_marshal_noilgen_init_blittable (&cb); -#endif - mono_install_marshal_callbacks_ilgen (&cb); -} - - diff --git a/src/mono/mono/metadata/CMakeLists.txt b/src/mono/mono/metadata/CMakeLists.txt index 69394f0aba1d08..3eb15313804f32 100644 --- a/src/mono/mono/metadata/CMakeLists.txt +++ b/src/mono/mono/metadata/CMakeLists.txt @@ -16,6 +16,8 @@ set(ilgen_base_sources method-builder-ilgen.c method-builder-ilgen.h method-builder-ilgen-internals.h + marshal-ilgen.c + marshal-ilgen.h marshal-lightweight.c marshal-lightweight.h marshal-shared.c @@ -95,7 +97,6 @@ set(metadata_common_sources marshal.h marshal-internals.h marshal-noilgen.c - marshal-noilgen.h mempool.c mempool.h mempool-internals.h diff --git a/src/mono/mono/metadata/class-setup-vtable.c b/src/mono/mono/metadata/class-setup-vtable.c index 2c29e30d05a05b..bc949189a3c125 100644 --- a/src/mono/mono/metadata/class-setup-vtable.c +++ b/src/mono/mono/metadata/class-setup-vtable.c @@ -932,7 +932,8 @@ mono_class_setup_vtable_full (MonoClass *klass, GList *in_setup) context = mono_class_get_context (klass); type_token = mono_class_get_generic_class (klass)->container_class->type_token; } else { - context = (MonoGenericContext *) mono_class_try_get_generic_container (klass); //FIXME is this a case of a try? + MonoGenericContainer *container = mono_class_try_get_generic_container (klass); //FIXME is this a case of a try? + context = container ? &container->context : NULL; type_token = klass->type_token; } @@ -1010,30 +1011,14 @@ apply_override (MonoClass *klass, MonoClass *override_class, MonoMethod **vtable MonoMethod *prev_override = (MonoMethod*)g_hash_table_lookup (map, decl); MonoClass *prev_override_class = (MonoClass*)g_hash_table_lookup (class_map, decl); + g_assert (override_class == override->klass); + g_hash_table_insert (map, decl, override); g_hash_table_insert (class_map, decl, override_class); /* Collect potentially conflicting overrides which are introduced by default interface methods */ if (prev_override) { - ERROR_DECL (error); - - /* - * The override methods are part of the generic definition, need to inflate them so their - * parent class becomes the actual interface/class containing the override, i.e. - * IFace in: - * class Foo : IFace - * This is needed so the mono_class_is_assignable_from_internal () calls in the - * conflict resolution work. - */ - if (mono_class_is_ginst (override_class)) { - override = mono_class_inflate_generic_method_checked (override, &mono_class_get_generic_class (override_class)->context, error); - mono_error_assert_ok (error); - } - - if (mono_class_is_ginst (prev_override_class)) { - prev_override = mono_class_inflate_generic_method_checked (prev_override, &mono_class_get_generic_class (prev_override_class)->context, error); - mono_error_assert_ok (error); - } + g_assert (prev_override->klass == prev_override_class); if (!*conflict_map) *conflict_map = g_hash_table_new (mono_aligned_addr_hash, NULL); @@ -1771,10 +1756,9 @@ mono_class_setup_vtable_general (MonoClass *klass, MonoMethod **overrides, int o MonoMethod *override = iface_overrides [i*2 + 1]; if (mono_class_is_gtd (override->klass)) { override = mono_class_inflate_generic_method_full_checked (override, ic, mono_class_get_context (ic), error); - } else if (decl->is_inflated) { - override = mono_class_inflate_generic_method_checked (override, mono_method_get_context (decl), error); - mono_error_assert_ok (error); - } + } + // there used to be code here to inflate decl if decl->is_inflated, but in https://github.com/dotnet/runtime/pull/64102#discussion_r790019545 we + // think that this does not correspond to any real code. if (!apply_override (klass, ic, vtable, decl, override, &override_map, &override_class_map, &conflict_map)) goto fail; } @@ -1786,6 +1770,18 @@ mono_class_setup_vtable_general (MonoClass *klass, MonoMethod **overrides, int o MonoMethod *decl = overrides [i*2]; MonoMethod *override = overrides [i*2 + 1]; if (MONO_CLASS_IS_INTERFACE_INTERNAL (decl->klass)) { + /* + * We expect override methods that are part of a generic definition, to have + * their parent class be the actual interface/class containing the override, + * i.e. + * + * IFace in: + * class Foo : IFace + * + * This is needed so the mono_class_is_assignable_from_internal () calls in the + * conflict resolution work. + */ + g_assert (override->klass == klass); if (!apply_override (klass, klass, vtable, decl, override, &override_map, &override_class_map, &conflict_map)) goto fail; } diff --git a/src/mono/mono/metadata/components.c b/src/mono/mono/metadata/components.c index 204c3d9c462f8b..f6f94a0b507c9e 100644 --- a/src/mono/mono/metadata/components.c +++ b/src/mono/mono/metadata/components.c @@ -43,9 +43,6 @@ typedef struct _MonoComponentEntry { #define DEBUGGER_LIBRARY_NAME "debugger" #define DEBUGGER_COMPONENT_NAME DEBUGGER_LIBRARY_NAME -#define MARSHAL_ILGEN_LIBRARY_NAME "marshal-ilgen" -#define MARSHAL_ILGEN_COMPONENT_NAME "marshal_ilgen" - MonoComponentHotReload *mono_component_hot_reload_private_ptr = NULL; MonoComponentDebugger *mono_component_debugger_private_ptr = NULL; @@ -53,8 +50,6 @@ MonoComponentDebugger *mono_component_debugger_private_ptr = NULL; MonoComponentEventPipe *mono_component_event_pipe_private_ptr = NULL; MonoComponentDiagnosticsServer *mono_component_diagnostics_server_private_ptr = NULL; -MonoComponentMarshalILgen* mono_component_marshal_ilgen_private_ptr = NULL; - // DiagnosticsServer/EventPipe components currently hosted by diagnostics_tracing library. #define DIAGNOSTICS_TRACING_LIBRARY_NAME "diagnostics_tracing" #define EVENT_PIPE_COMPONENT_NAME "event_pipe" @@ -66,7 +61,6 @@ MonoComponentEntry components[] = { { HOT_RELOAD_LIBRARY_NAME, HOT_RELOAD_COMPONENT_NAME, COMPONENT_INIT_FUNC (hot_reload), (MonoComponent**)&mono_component_hot_reload_private_ptr, NULL }, { DIAGNOSTICS_TRACING_LIBRARY_NAME, EVENT_PIPE_COMPONENT_NAME, COMPONENT_INIT_FUNC (event_pipe), (MonoComponent**)&mono_component_event_pipe_private_ptr, NULL }, { DIAGNOSTICS_TRACING_LIBRARY_NAME, DIAGNOSTICS_SERVER_COMPONENT_NAME, COMPONENT_INIT_FUNC (diagnostics_server), (MonoComponent**)&mono_component_diagnostics_server_private_ptr, NULL }, - { MARSHAL_ILGEN_LIBRARY_NAME, MARSHAL_ILGEN_COMPONENT_NAME, COMPONENT_INIT_FUNC (marshal_ilgen), (MonoComponent**)&mono_component_marshal_ilgen_private_ptr, NULL } }; #ifndef STATIC_COMPONENTS diff --git a/src/mono/mono/metadata/components.h b/src/mono/mono/metadata/components.h index f6b8696d194f05..aba03174589432 100644 --- a/src/mono/mono/metadata/components.h +++ b/src/mono/mono/metadata/components.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -25,7 +24,6 @@ extern MonoComponentHotReload *mono_component_hot_reload_private_ptr; extern MonoComponentEventPipe *mono_component_event_pipe_private_ptr; extern MonoComponentDiagnosticsServer *mono_component_diagnostics_server_private_ptr; extern MonoComponentDebugger *mono_component_debugger_private_ptr; -extern MonoComponentMarshalILgen *mono_component_marshal_ilgen_private_ptr; /* Declare each component's getter function here */ static inline @@ -56,11 +54,4 @@ mono_component_debugger (void) return mono_component_debugger_private_ptr; } -static inline -MonoComponentMarshalILgen* -mono_component_marshal_ilgen (void) -{ - return mono_component_marshal_ilgen_private_ptr; -} - -#endif/*_MONO_METADATA_COMPONENTS_H*/ \ No newline at end of file +#endif/*_MONO_METADATA_COMPONENTS_H*/ diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 5f47166ab1a646..073590ef5a9f11 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -357,6 +357,7 @@ HANDLES(RASSEM_7, "InternalGetReferencedAssemblies", ves_icall_System_Reflection ICALL_TYPE(MCMETH, "System.Reflection.RuntimeConstructorInfo", MCMETH_1) HANDLES(MCMETH_1, "GetGenericMethodDefinition_impl", ves_icall_RuntimeMethodInfo_GetGenericMethodDefinition, MonoReflectionMethod, 1, (MonoReflectionMethod)) HANDLES(MCMETH_2, "InternalInvoke", ves_icall_InternalInvoke, MonoObject, 4, (MonoReflectionMethod, MonoObject, MonoSpanOfObjects_ref, MonoExceptionOut)) +HANDLES(MCMETH_5, "InvokeClassConstructor", ves_icall_InvokeClassConstructor, void, 1, (MonoQCallTypeHandle)) HANDLES_REUSE_WRAPPER(MCMETH_4, "get_metadata_token", ves_icall_reflection_get_token) ICALL_TYPE(CATTR_DATA, "System.Reflection.RuntimeCustomAttributeData", CATTR_DATA_1) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 2fbdb8f330fe70..c8b16f7f400a68 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -2809,6 +2809,18 @@ ves_icall_RuntimeTypeHandle_IsComObject (MonoQCallTypeHandle type_handle, MonoEr return mono_class_is_com_object (klass); } +void +ves_icall_InvokeClassConstructor (MonoQCallTypeHandle type_handle, MonoError *error) +{ + MonoType *type = type_handle.type; + MonoClass *klass = mono_class_from_mono_type_internal (type); + + MonoVTable *vtable = mono_class_vtable_checked (klass, error); + return_if_nok (error); + + mono_runtime_class_init_full (vtable, error); +} + guint32 ves_icall_reflection_get_token (MonoObjectHandle obj, MonoError *error) { @@ -7177,7 +7189,7 @@ ves_icall_System_Threading_Thread_YieldInternal (void) gint32 ves_icall_System_Environment_get_ProcessorCount (void) { - return mono_cpu_count (); + return mono_cpu_limit (); } // Generate wrappers. diff --git a/src/mono/mono/metadata/marshal-ilgen.c b/src/mono/mono/metadata/marshal-ilgen.c new file mode 100644 index 00000000000000..ceceb70149f21b --- /dev/null +++ b/src/mono/mono/metadata/marshal-ilgen.c @@ -0,0 +1,2830 @@ +#include "mono/metadata/debug-helpers.h" +#include "metadata/marshal.h" +#include "metadata/marshal-ilgen.h" +#include "metadata/marshal-lightweight.h" +#include "metadata/marshal-shared.h" +#include "metadata/method-builder-ilgen.h" +#include "metadata/custom-attrs-internals.h" +#include "metadata/class-init.h" +#include "mono/metadata/class-internals.h" +#include "metadata/reflection-internals.h" +#include "mono/metadata/handle.h" + + + +#define OPDEF(a,b,c,d,e,f,g,h,i,j) \ + a = i, + +enum { +#include "mono/cil/opcode.def" + LAST = 0xff +}; +#undef OPDEF + +static GENERATE_GET_CLASS_WITH_CACHE (date_time, "System", "DateTime"); +static GENERATE_TRY_GET_CLASS_WITH_CACHE (icustom_marshaler, "System.Runtime.InteropServices", "ICustomMarshaler"); + +static void emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv); + +// TODO: Does this need to loose the mono_ prefix? +static void mono_marshal_ilgen_legacy_init (void); + +static gboolean ilgen_cb_inited = FALSE; +static MonoMarshalIlgenCallbacks ilgen_marshal_cb; + +void +mono_install_marshal_callbacks_ilgen (MonoMarshalIlgenCallbacks *cb) +{ + g_assert (!ilgen_cb_inited); + g_assert (cb->version == MONO_MARSHAL_CALLBACKS_VERSION); + memcpy (&ilgen_marshal_cb, cb, sizeof (MonoMarshalIlgenCallbacks)); + ilgen_cb_inited = TRUE; +} + + +static void +emit_struct_free (MonoMethodBuilder *mb, MonoClass *klass, int struct_var) +{ + /* Call DestroyStructure */ + /* FIXME: Only do this if needed */ + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, struct_var); + mono_mb_emit_icall (mb, mono_struct_delete_old); +} + +static int +emit_marshal_array_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass = mono_class_from_mono_type_internal (t); + MonoMarshalNative encoding; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + + MonoClass *eklass = m_class_get_element_class (klass); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = object_type; + conv_arg = mono_mb_add_local (mb, object_type); + + if (m_class_is_blittable (eklass)) { + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_ARRAY_LPARRAY, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } else { +#ifdef DISABLE_NONBLITTABLE + char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); +#else + guint32 label1, label2, label3; + int index_var, src_var, dest_ptr, esize; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + dest_ptr = mono_mb_add_local (mb, int_type); + + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + } + else + conv = MONO_MARSHAL_CONV_INVALID; + + if (is_string && conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string/stringbuilder marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + src_var = mono_mb_add_local (mb, object_type); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, src_var); + + /* Check null */ + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, src_var); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) /*can't call mono_marshal_type_size since it causes all sorts of asserts*/ + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + + /* allocate space for the native struct and store the address */ + mono_mb_emit_icon (mb, esize); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_byte (mb, CEE_LDLEN); + + if (eklass == mono_defaults.string_class) { + /* Make the array bigger for the terminating null */ + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, dest_ptr); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + + if (is_string) { + int stind_op; + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } else { + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, src_var); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_op (mb, CEE_LDELEMA, eklass); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv_full (mb, eklass, FALSE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1); + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label3); + + if (eklass == mono_defaults.string_class) { + /* Null terminate */ + mono_mb_emit_ldloc (mb, dest_ptr); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_byte (mb, CEE_STIND_I); + } + + mono_mb_patch_branch (mb, label1); +#endif + } + + break; + + case MARSHAL_ACTION_CONV_OUT: { +#ifndef DISABLE_NONBLITTABLE + gboolean need_convert, need_free; + /* Unicode character arrays are implicitly marshalled as [Out] under MS.NET */ + need_convert = ((eklass == mono_defaults.char_class) && (encoding == MONO_NATIVE_LPWSTR)) || (eklass == mono_class_try_get_stringbuilder_class ()) || (t->attrs & PARAM_ATTRIBUTE_OUT); + need_free = mono_marshal_need_free (m_class_get_byval_arg (eklass), m->piinfo, spec); + + if ((t->attrs & PARAM_ATTRIBUTE_OUT) && spec && spec->native == MONO_NATIVE_LPARRAY && spec->data.array_data.param_num != -1) { + int param_num = spec->data.array_data.param_num; + MonoType *param_type; + + param_type = m->sig->params [param_num]; + + if (m_type_is_byref (param_type) && param_type->type != MONO_TYPE_I4) { + char *msg = g_strdup ("Not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (m_type_is_byref (t) ) { + mono_mb_emit_ldarg (mb, argnum); + + /* Create the managed array */ + mono_mb_emit_ldarg (mb, param_num); + if (m_type_is_byref (m->sig->params [param_num])) + // FIXME: Support other types + mono_mb_emit_byte (mb, CEE_LDIND_I4); + mono_mb_emit_byte (mb, CEE_CONV_OVF_I); + mono_mb_emit_op (mb, CEE_NEWARR, eklass); + /* Store into argument */ + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + } + + if (need_convert || need_free) { + /* FIXME: Optimize blittable case */ + guint32 label1, label2, label3; + int index_var, src_ptr, loc, esize; + + if ((eklass == mono_class_try_get_stringbuilder_class ()) || (eklass == mono_defaults.string_class)) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + src_ptr = mono_mb_add_local (mb, int_type); + loc = mono_mb_add_local (mb, int_type); + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, src_ptr); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + + if (eklass == mono_class_try_get_stringbuilder_class ()) { + gboolean need_free2; + MonoMarshalConv conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free2); + + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + + if (need_free) { + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall (mb, mono_marshal_free); + } + } + else if (eklass == mono_defaults.string_class) { + if (need_free) { + /* src */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall (mb, mono_marshal_free); + } + } + else { + if (need_convert) { + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_op (mb, CEE_LDELEMA, eklass); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv_full (mb, eklass, TRUE, 0, eklass == mono_defaults.char_class ? encoding : (MonoMarshalNative)-1); + } + + if (need_free) { + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_stloc (mb, loc); + + emit_struct_free (mb, eklass, loc); + } + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); + } +#endif + + if (m_class_is_blittable (eklass)) { + /* free memory allocated (if any) by MONO_MARSHAL_CONV_ARRAY_LPARRAY */ + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_FREE_LPARRAY, NULL)); + } + + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: { + mono_mb_emit_byte (mb, CEE_POP); + char *msg = g_strdup_printf ("Cannot marshal 'return value': Invalid managed/unmanaged type combination."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + guint32 label1, label2, label3; + int index_var, src_ptr, esize, param_num, num_elem; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + conv_arg = mono_mb_add_local (mb, object_type); + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + char *msg = g_strdup ("Byref array marshalling to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + if (!spec) { + char *msg = g_strdup ("[MarshalAs] attribute required to marshal arrays to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + switch (spec->native) { + case MONO_NATIVE_LPARRAY: + break; + case MONO_NATIVE_SAFEARRAY: +#ifndef DISABLE_COM + if (spec->data.safearray_data.elem_type != MONO_VARIANT_VARIANT) { + char *msg = g_strdup ("Only SAFEARRAY(VARIANT) marshalling to managed code is implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + default: { + char *msg = g_strdup ("Unsupported array type marshalling to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + + /* FIXME: t is from the method which is wrapped, not the delegate type */ + /* g_assert (t->attrs & PARAM_ATTRIBUTE_IN); */ + + param_num = spec->data.array_data.param_num; + num_elem = spec->data.array_data.num_elem; + if (spec->data.array_data.elem_mult == 0) + /* param_num is not specified */ + param_num = -1; + + if (param_num == -1) { + if (num_elem <= 0) { + char *msg = g_strdup ("Either SizeConst or SizeParamIndex should be specified when marshalling arrays to managed code."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + + /* FIXME: Optimize blittable case */ + +#ifndef DISABLE_NONBLITTABLE + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + gboolean need_free; + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + gboolean need_free; + conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); + } + else + conv = MONO_MARSHAL_CONV_INVALID; +#endif + + mono_marshal_load_type_info (eklass); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else + esize = mono_class_native_size (eklass, NULL); + src_ptr = mono_mb_add_local (mb, int_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + /* Check param index */ + if (param_num != -1) { + if (param_num >= m->sig->param_count) { + char *msg = g_strdup ("Array size control parameter index is out of range."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + switch (m->sig->params [param_num]->type) { + case MONO_TYPE_I1: + case MONO_TYPE_U1: + case MONO_TYPE_I2: + case MONO_TYPE_U2: + case MONO_TYPE_I4: + case MONO_TYPE_U4: + case MONO_TYPE_I: + case MONO_TYPE_U: + case MONO_TYPE_I8: + case MONO_TYPE_U8: + break; + default: { + char *msg = g_strdup ("Array size control parameter must be an integral type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + } + } + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, src_ptr); + + /* Create managed array */ + /* + * The LPArray marshalling spec says that sometimes param_num starts + * from 1, sometimes it starts from 0. But MS seems to always start + * from 0. + */ + + if (param_num == -1) { + mono_mb_emit_icon (mb, num_elem); + } else { + mono_mb_emit_ldarg (mb, param_num); + if (num_elem > 0) { + mono_mb_emit_icon (mb, num_elem); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_CONV_OVF_I); + } + + mono_mb_emit_op (mb, CEE_NEWARR, eklass); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_class_is_blittable (eklass)) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + mono_mb_emit_icon (mb, esize); + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_CPBLK); + mono_mb_patch_branch (mb, label1); + break; + } +#ifdef DISABLE_NONBLITTABLE + else { + char *msg = g_strdup ("Non-blittable marshalling conversion is disabled"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } +#else + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_ldloc (mb, src_ptr); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_byte (mb, CEE_STELEM_REF); + } + else { + char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (src_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); +#endif + + break; + } + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + guint32 label1, label2, label3; + int index_var, dest_ptr, esize, param_num, num_elem; + MonoMarshalConv conv; + gboolean is_string = FALSE; + + if (!spec) + /* Already handled in CONV_IN */ + break; + + /* These are already checked in CONV_IN */ + g_assert (!m_type_is_byref (t)); + g_assert (spec->native == MONO_NATIVE_LPARRAY); + g_assert (t->attrs & PARAM_ATTRIBUTE_OUT); + + param_num = spec->data.array_data.param_num; + num_elem = spec->data.array_data.num_elem; + + if (spec->data.array_data.elem_mult == 0) + /* param_num is not specified */ + param_num = -1; + + if (param_num == -1) { + if (num_elem <= 0) { + g_assert_not_reached (); + } + } + + /* FIXME: Optimize blittable case */ + +#ifndef DISABLE_NONBLITTABLE + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else if (eklass == mono_class_try_get_stringbuilder_class ()) { + is_string = TRUE; + conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + } + else + conv = MONO_MARSHAL_CONV_INVALID; +#endif + + mono_marshal_load_type_info (eklass); + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else + esize = mono_class_native_size (eklass, NULL); + + dest_ptr = mono_mb_add_local (mb, int_type); + + /* Check null */ + mono_mb_emit_ldloc (mb, conv_arg); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, dest_ptr); + + if (m_class_is_blittable (eklass)) { + /* dest */ + mono_mb_emit_ldarg (mb, argnum); + /* src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoArray, vector)); + mono_mb_emit_byte (mb, CEE_ADD); + /* length */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + mono_mb_emit_icon (mb, esize); + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_CPBLK); + mono_mb_patch_branch (mb, label1); + break; + } + +#ifndef DISABLE_NONBLITTABLE + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + int stind_op; + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldloc (mb, dest_ptr); + + /* src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + else { + char *msg = g_strdup ("Marshalling of non-string and non-blittable arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest_ptr), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label1); + mono_mb_patch_branch (mb, label3); +#endif + + break; + } + case MARSHAL_ACTION_MANAGED_CONV_RESULT: { +#ifndef DISABLE_NONBLITTABLE + guint32 label1, label2, label3; + int index_var, src, dest, esize; + MonoMarshalConv conv = MONO_MARSHAL_CONV_INVALID; + gboolean is_string = FALSE; + + g_assert (!m_type_is_byref (t)); + + mono_marshal_load_type_info (eklass); + + if (eklass == mono_defaults.string_class) { + is_string = TRUE; + conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + } + else { + g_assert_not_reached (); + } + + if (is_string) + esize = TARGET_SIZEOF_VOID_P; + else if (eklass == mono_defaults.char_class) + esize = mono_pinvoke_is_unicode (m->piinfo) ? 2 : 1; + else + esize = mono_class_native_size (eklass, NULL); + + src = mono_mb_add_local (mb, object_type); + dest = mono_mb_add_local (mb, int_type); + + mono_mb_emit_stloc (mb, src); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_stloc (mb, 3); + + /* Check for null */ + mono_mb_emit_ldloc (mb, src); + label1 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Allocate native array */ + mono_mb_emit_icon (mb, esize); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_byte (mb, CEE_LDLEN); + + if (eklass == mono_defaults.string_class) { + /* Make the array bigger for the terminating null */ + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_byte (mb, CEE_ADD); + } + mono_mb_emit_byte (mb, CEE_MUL); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, dest); + mono_mb_emit_ldloc (mb, dest); + mono_mb_emit_stloc (mb, 3); + + /* Emit marshalling loop */ + index_var = mono_mb_add_local (mb, int_type); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, index_var); + label2 = mono_mb_get_label (mb); + mono_mb_emit_ldloc (mb, index_var); + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_byte (mb, CEE_LDLEN); + label3 = mono_mb_emit_branch (mb, CEE_BGE); + + /* Emit marshalling code */ + if (is_string) { + int stind_op; + g_assert (conv != MONO_MARSHAL_CONV_INVALID); + + /* dest */ + mono_mb_emit_ldloc (mb, dest); + + /* src */ + mono_mb_emit_ldloc (mb, src); + mono_mb_emit_ldloc (mb, index_var); + + mono_mb_emit_byte (mb, CEE_LDELEM_REF); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + else { + char *msg = g_strdup ("Marshalling of non-string arrays to managed code is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + return conv_arg; + } + + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (index_var), 1); + mono_mb_emit_add_to_local (mb, GINT_TO_UINT16 (dest), esize); + + mono_mb_emit_branch_label (mb, CEE_BR, label2); + + mono_mb_patch_branch (mb, label3); + mono_mb_patch_branch (mb, label1); +#endif + break; + } + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static gboolean +emit_native_wrapper_validate_signature (MonoMethodBuilder *mb, MonoMethodSignature* sig, MonoMarshalSpec** mspecs) +{ + if (mspecs) { + for (int i = 0; i < sig->param_count; i ++) { + if (mspecs [i + 1] && mspecs [i + 1]->native == MONO_NATIVE_CUSTOM) { + if (!mspecs [i + 1]->data.custom_data.custom_name || *mspecs [i + 1]->data.custom_data.custom_name == '\0') { + mono_mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Missing ICustomMarshaler type")); + return FALSE; + } + + switch (sig->params[i]->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + break; + + default: + mono_mb_emit_exception_full (mb, "System.Runtime.InteropServices", "MarshalDirectiveException", g_strdup_printf ("custom marshalling of type %x is currently not supported", sig->params[i]->type)); + return FALSE; + } + } + else if (sig->params[i]->type == MONO_TYPE_VALUETYPE) { + MonoMarshalType *marshal_type = mono_marshal_load_type_info (mono_class_from_mono_type_internal (sig->params [i])); + for (guint32 field_idx = 0; field_idx < marshal_type->num_fields; ++field_idx) { + if (marshal_type->fields [field_idx].mspec && marshal_type->fields [field_idx].mspec->native == MONO_NATIVE_CUSTOM) { + mono_mb_emit_exception_full (mb, "System", "TypeLoadException", g_strdup ("Value type includes custom marshaled fields")); + return FALSE; + } + } + } + } + } + + return TRUE; +} + +static int +emit_marshal_ptr_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + switch (action) { + case MARSHAL_ACTION_CONV_IN: + /* MS seems to allow this in some cases, ie. bxc #158 */ + /* + if (MONO_TYPE_ISSTRUCT (t->data.type) && !mono_class_from_mono_type_internal (t->data.type)->blittable) { + char *msg = g_strdup_printf ("Can not marshal 'parameter #%d': Pointers can not reference marshaled structures. Use byref instead.", argnum + 1); + mono_marshal_shared_mb_emit_exception_marshal_directive (m->mb, msg); + } + */ + break; + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* no conversions necessary */ + mono_mb_emit_stloc (mb, 3); + break; + + default: + break; + } + return conv_arg; +} + +static int +emit_marshal_boolean_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoType *int_type = mono_get_int_type (); + MonoType *boolean_type = m_class_get_byval_arg (mono_defaults.boolean_class); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + MonoType *local_type; + int label_false; + guint8 ldc_op = CEE_LDC_I4_1; + + local_type = mono_marshal_boolean_conv_in_get_local_type (spec, &ldc_op); + if (m_type_is_byref (t)) + *conv_arg_type = int_type; + else + *conv_arg_type = local_type; + conv_arg = mono_mb_add_local (mb, local_type); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I1); + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, ldc_op); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_patch_branch (mb, label_false); + + break; + } + + case MARSHAL_ACTION_CONV_OUT: + { + int label_false, label_end; + if (!m_type_is_byref (t)) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + + label_end = mono_mb_emit_branch (mb, CEE_BR); + mono_mb_patch_branch (mb, label_false); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_patch_branch (mb, label_end); + + mono_mb_emit_byte (mb, CEE_STIND_I1); + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else if (conv_arg) + mono_mb_emit_ldloc (mb, conv_arg); + else + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* maybe we need to make sure that it fits within 8 bits */ + mono_mb_emit_stloc (mb, 3); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + MonoClass* conv_arg_class = mono_defaults.int32_class; + guint8 ldop = CEE_LDIND_I4; + int label_null, label_false; + + conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, &ldop); + conv_arg = mono_mb_add_local (mb, boolean_type); + + if (m_type_is_byref (t)) + *conv_arg_type = m_class_get_this_arg (conv_arg_class); + else + *conv_arg_type = m_class_get_byval_arg (conv_arg_class); + + + mono_mb_emit_ldarg (mb, argnum); + + /* Check null */ + if (m_type_is_byref (t)) { + label_null = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, ldop); + } else + label_null = 0; + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, CEE_LDC_I4_1); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_patch_branch (mb, label_false); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, label_null); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + guint8 stop = CEE_STIND_I4; + guint8 ldc_op = CEE_LDC_I4_1; + int label_null,label_false, label_end; + + if (!m_type_is_byref (t)) + break; + if (spec) { + switch (spec->native) { + case MONO_NATIVE_I1: + case MONO_NATIVE_U1: + stop = CEE_STIND_I1; + break; + case MONO_NATIVE_VARIANTBOOL: + stop = CEE_STIND_I2; + ldc_op = CEE_LDC_I4_M1; + break; + default: + break; + } + } + + /* Check null */ + mono_mb_emit_ldarg (mb, argnum); + label_null = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + label_false = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_byte (mb, ldc_op); + label_end = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, label_false); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_patch_branch (mb, label_end); + + mono_mb_emit_byte (mb, stop); + mono_mb_patch_branch (mb, label_null); + break; + } + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_char_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + switch (action) { + case MARSHAL_ACTION_PUSH: + /* fixme: dont know how to marshal that. We cant simply + * convert it to a one byte UTF8 character, because an + * unicode character may need more that one byte in UTF8 */ + mono_mb_emit_ldarg (mb, argnum); + break; + + case MARSHAL_ACTION_CONV_RESULT: + /* fixme: we need conversions here */ + mono_mb_emit_stloc (mb, 3); + break; + + default: + break; + } + return conv_arg; +} + +static int +emit_marshal_custom_ilgen_throw_exception (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg, MarshalAction action) +{ + /* Throw exception and emit compensation code, if necessary */ + switch (action) { + case MARSHAL_ACTION_CONV_IN: + case MARSHAL_ACTION_MANAGED_CONV_IN: + case MARSHAL_ACTION_CONV_RESULT: + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if ((action == MARSHAL_ACTION_CONV_RESULT) || (action == MARSHAL_ACTION_MANAGED_CONV_RESULT)) + mono_mb_emit_byte (mb, CEE_POP); + + mono_mb_emit_exception_full (mb, exc_nspace, exc_name, msg); + + break; + case MARSHAL_ACTION_PUSH: + mono_mb_emit_byte (mb, CEE_LDNULL); + break; + default: + break; + } + + return 0; +} + +static int +emit_marshal_custom_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + ERROR_DECL (error); + MonoType *mtype; + MonoClass *mklass; + static MonoClass *ICustomMarshaler = NULL; + static MonoMethod *cleanup_native, *cleanup_managed; + static MonoMethod *marshal_managed_to_native, *marshal_native_to_managed; + MonoMethodBuilder *mb = m->mb; + MonoAssemblyLoadContext *alc = mono_alc_get_ambient (); + guint32 loc1; + int pos2; + + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + + if (!ICustomMarshaler) { + MonoClass *klass = mono_class_try_get_icustom_marshaler_class (); + if (!klass) + return emit_marshal_custom_ilgen_throw_exception (mb, "System", "ApplicationException", g_strdup ("Current profile doesn't support ICustomMarshaler"), action); + + cleanup_native = mono_marshal_shared_get_method_nofail (klass, "CleanUpNativeData", 1, 0); + g_assert (cleanup_native); + + cleanup_managed = mono_marshal_shared_get_method_nofail (klass, "CleanUpManagedData", 1, 0); + g_assert (cleanup_managed); + + marshal_managed_to_native = mono_marshal_shared_get_method_nofail (klass, "MarshalManagedToNative", 1, 0); + g_assert (marshal_managed_to_native); + + marshal_native_to_managed = mono_marshal_shared_get_method_nofail (klass, "MarshalNativeToManaged", 1, 0); + g_assert (marshal_native_to_managed); + + mono_memory_barrier (); + ICustomMarshaler = klass; + } + + if (spec->data.custom_data.image) + mtype = mono_reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, spec->data.custom_data.image, error); + else + mtype = mono_reflection_type_from_name_checked (spec->data.custom_data.custom_name, alc, m->image, error); + + if (!mtype) + return emit_marshal_custom_ilgen_throw_exception (mb, "System", "TypeLoadException", g_strdup ("Failed to load ICustomMarshaler type"), action); + + mklass = mono_class_from_mono_type_internal (mtype); + g_assert (mklass != NULL); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + switch (t->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + break; + + default: + g_warning ("custom marshalling of type %x is currently not supported", t->type); + g_assert_not_reached (); + break; + } + + conv_arg = mono_mb_add_local (mb, int_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + /* Minic MS.NET behavior */ + if (!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN)) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_REF); + + if (t->type == MONO_TYPE_VALUETYPE) { + /* + * Since we can't determine the type of the argument, we + * will assume the unmanaged function takes a pointer. + */ + *conv_arg_type = int_type; + + mono_mb_emit_op (mb, CEE_BOX, mono_class_from_mono_type_internal (t)); + } + + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_CONV_OUT: + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_byte (mb, CEE_DUP); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (t->attrs & PARAM_ATTRIBUTE_OUT) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + /* We have nowhere to store the result */ + mono_mb_emit_byte (mb, CEE_POP); + } + + // Only call cleanup_native if MARSHAL_ACTION_CONV_IN called marshal_managed_to_native. + if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) && + !(!m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT) && !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_native); + } + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + mono_mb_emit_stloc (mb, 3); + + /* Check for null */ + mono_mb_emit_ldloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_stloc (mb, 3); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + switch (t->type) { + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: + case MONO_TYPE_STRING: + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + case MONO_TYPE_VALUETYPE: + case MONO_TYPE_BOOLEAN: + break; + + default: + g_warning ("custom marshalling of type %x is currently not supported", t->type); + g_assert_not_reached (); + break; + } + + conv_arg = mono_mb_add_local (mb, object_type); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_native_to_managed); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + g_assert (!m_type_is_byref (t)); + + loc1 = mono_mb_add_local (mb, object_type); + + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_stloc (mb, loc1); + + /* Check for null */ + mono_mb_emit_ldloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_byte (mb, CEE_DUP); + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, loc1); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, marshal_managed_to_native); + mono_mb_emit_byte (mb, CEE_STIND_I); + } + + // Only call cleanup_managed if MARSHAL_ACTION_MANAGED_CONV_IN called marshal_native_to_managed. + if (!(m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + mono_marshal_shared_emit_marshal_custom_get_instance (mb, mklass, spec); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_op (mb, CEE_CALLVIRT, cleanup_managed); + } + + mono_mb_patch_branch (mb, pos2); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_asany_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, NULL); + + g_assert (t->type == MONO_TYPE_OBJECT); + g_assert (!m_type_is_byref (t)); + + conv_arg = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icon (mb, encoding); + mono_mb_emit_icon (mb, t->attrs); + mono_mb_emit_icall (mb, mono_marshal_asany); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, NULL); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icon (mb, encoding); + mono_mb_emit_icon (mb, t->attrs); + mono_mb_emit_icall (mb, mono_marshal_free_asany); + break; + } + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_vtype_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass, *date_time_class; + int pos = 0, pos2; + + klass = mono_class_from_mono_type_internal (t); + + date_time_class = mono_class_get_date_time_class (); + + MonoType *int_type = mono_get_int_type (); + MonoType *double_type = m_class_get_byval_arg (mono_defaults.double_class); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: + if (klass == date_time_class) { + /* Convert it to an OLE DATE type */ + + conv_arg = mono_mb_add_local (mb, double_type); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + if (!m_type_is_byref (t)) + m->csig->params [argnum - m->csig->hasthis] = double_type; + + MONO_STATIC_POINTER_INIT (MonoMethod, to_oadate) + to_oadate = mono_marshal_shared_get_method_nofail (date_time_class, "ToOADate", 0, 0); + g_assert (to_oadate); + MONO_STATIC_POINTER_INIT_END (MonoMethod, to_oadate) + + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_managed_call (mb, to_oadate, NULL); + mono_mb_emit_stloc (mb, conv_arg); + } + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + + conv_arg = mono_mb_add_local (mb, int_type); + + /* store the address of the source into local variable 0 */ + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + + mono_mb_emit_stloc (mb, 0); + + /* allocate space for the native struct and + * store the address into local variable 1 (dest) */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + if (!(m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT))) { + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + } + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_PUSH: + if (spec && spec->native == MONO_NATIVE_LPSTRUCT) { + /* FIXME: */ + g_assert (!m_type_is_byref (t)); + + /* Have to change the signature since the vtype is passed byref */ + m->csig->params [argnum - m->csig->hasthis] = int_type; + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + mono_mb_emit_ldarg_addr (mb, argnum); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + } + + if (klass == date_time_class) { + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + mono_mb_emit_ldarg (mb, argnum); + break; + } + mono_mb_emit_ldloc (mb, conv_arg); + if (!m_type_is_byref (t)) { + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_LDNATIVEOBJ, klass); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + if (klass == date_time_class) { + /* Convert from an OLE DATE type */ + + if (!m_type_is_byref (t)) + break; + + if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { + + MONO_STATIC_POINTER_INIT (MonoMethod, from_oadate) + from_oadate = mono_marshal_shared_get_method_nofail (date_time_class, "FromOADate", 1, 0); + MONO_STATIC_POINTER_INIT_END (MonoMethod, from_oadate) + + g_assert (from_oadate); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_managed_call (mb, from_oadate, NULL); + mono_mb_emit_op (mb, CEE_STOBJ, date_time_class); + } + break; + } + + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + + if (m_type_is_byref (t)) { + /* dst = argument */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + mono_mb_emit_ldloc (mb, 1); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (!((t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT))) { + /* src = tmp_locals [i] */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + } + } + + emit_struct_free (mb, klass, conv_arg); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_CONV_RESULT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass)) { + mono_mb_emit_stloc (mb, 3); + break; + } + + /* load pointer to returned value type */ + g_assert (m->vtaddr_var); + mono_mb_emit_ldloc (mb, m->vtaddr_var); + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + /* set dst_ptr */ + mono_mb_emit_ldloc_addr (mb, 3); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + conv_arg = 0; + break; + } + + conv_arg = mono_mb_add_local (mb, m_class_get_byval_arg (klass)); + + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_stloc (mb, 0); + + if (m_type_is_byref (t)) { + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + } + + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + if (m_type_is_byref (t)) + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) + break; + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_IN) && !(t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + /* Check for null */ + mono_mb_emit_ldarg (mb, argnum); + pos2 = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Set src */ + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* Set dest */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (mono_class_is_explicit_layout (klass) || m_class_is_blittable (klass) || m_class_is_enumtype (klass)) { + mono_mb_emit_stloc (mb, 3); + m->retobj_var = 0; + break; + } + + /* load pointer to returned value type */ + g_assert (m->vtaddr_var); + mono_mb_emit_ldloc (mb, m->vtaddr_var); + + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + /* allocate space for the native struct and + * store the address into dst_ptr */ + m->retobj_var = mono_mb_add_local (mb, int_type); + m->retobj_class = klass; + g_assert (m->retobj_var); + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, 1); + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_stloc (mb, m->retobj_var); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static void +emit_string_free_icall (MonoMethodBuilder *mb, MonoMarshalConv conv) +{ + if (conv == MONO_MARSHAL_CONV_BSTR_STR || conv == MONO_MARSHAL_CONV_ANSIBSTR_STR || conv == MONO_MARSHAL_CONV_TBSTR_STR) + mono_mb_emit_icall (mb, mono_free_bstr); + else + mono_mb_emit_icall (mb, mono_marshal_free); +} + +static int +emit_marshal_string_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoMarshalConv conv = mono_marshal_get_string_to_ptr_conv (m->piinfo, spec); + gboolean need_free; + + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + conv_arg = mono_mb_add_local (mb, int_type); + + if (m_type_is_byref (t)) { + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + } else { + mono_mb_emit_ldarg (mb, argnum); + } + + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } else { + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + + mono_mb_emit_stloc (mb, conv_arg); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (encoding == MONO_NATIVE_VBBYREFSTR) { + + if (!m_type_is_byref (t)) { + char *msg = g_strdup ("VBByRefStr marshalling requires a ref parameter."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + MONO_STATIC_POINTER_INIT (MonoMethod, method) + + method = mono_marshal_shared_get_method_nofail (mono_defaults.string_class, "get_Length", -1, 0); + + MONO_STATIC_POINTER_INIT_END (MonoMethod, method) + + /* + * Have to allocate a new string with the same length as the original, and + * copy the contents of the buffer pointed to by CONV_ARG into it. + */ + g_assert (m_type_is_byref (t)); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_managed_call (mb, method, NULL); + mono_mb_emit_icall (mb, mono_string_new_len_wrapper); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + need_free = TRUE; + } + + if (need_free) { + mono_mb_emit_ldloc (mb, conv_arg); + emit_string_free_icall (mb, conv); + } + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t) && encoding != MONO_NATIVE_VBBYREFSTR) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + mono_mb_emit_stloc (mb, 0); + + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, 3); + + /* free the string */ + mono_mb_emit_ldloc (mb, 0); + emit_string_free_icall (mb, conv); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + conv_arg = mono_mb_add_local (mb, object_type); + + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + } + + conv = mono_marshal_get_ptr_to_string_conv (m->piinfo, spec, &need_free); + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("string marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (m_type_is_byref (t)) { + if (conv_arg) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + } + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (mono_marshal_shared_conv_to_icall (conv, NULL) == MONO_JIT_ICALL_mono_marshal_string_to_utf16) + /* We need to make a copy so the caller is able to free it */ + mono_mb_emit_icall (mb, mono_marshal_string_to_utf16_copy); + else + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, 3); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_safehandle_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoType *int_type = mono_get_int_type (); + MonoType *boolean_type = m_class_get_byval_arg (mono_defaults.boolean_class); + + switch (action){ + case MARSHAL_ACTION_CONV_IN: { + int dar_release_slot, pos; + + conv_arg = mono_mb_add_local (mb, int_type); + *conv_arg_type = int_type; + + if (!*mono_marshal_shared_get_sh_dangerous_add_ref()) + mono_marshal_shared_init_safe_handle (); + + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_exception (mb, "ArgumentNullException", NULL); + + mono_mb_patch_branch (mb, pos); + + /* Create local to hold the ref parameter to DangerousAddRef */ + dar_release_slot = mono_mb_add_local (mb, boolean_type); + + /* set release = false; */ + mono_mb_emit_icon (mb, 0); + mono_mb_emit_stloc (mb, dar_release_slot); + + if (m_type_is_byref (t)) { + int old_handle_value_slot = mono_mb_add_local (mb, int_type); + + if (!mono_marshal_shared_is_in (t)) { + mono_mb_emit_icon (mb, 0); + mono_mb_emit_stloc (mb, conv_arg); + } else { + /* safehandle.DangerousAddRef (ref release) */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldloc_addr (mb, dar_release_slot); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_add_ref(), NULL); + + /* Pull the handle field from SafeHandle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_byte (mb, CEE_DUP); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_stloc (mb, old_handle_value_slot); + } + } else { + /* safehandle.DangerousAddRef (ref release) */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc_addr (mb, dar_release_slot); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_add_ref(), NULL); + + /* Pull the handle field from SafeHandle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, conv_arg); + } + + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + /* The slot for the boolean is the next temporary created after conv_arg, see the CONV_IN code */ + int dar_release_slot = conv_arg + 1; + int label_next = 0; + + if (!*mono_marshal_shared_get_sh_dangerous_release()) + mono_marshal_shared_init_safe_handle (); + + if (m_type_is_byref (t)) { + /* If there was SafeHandle on input we have to release the reference to it */ + if (mono_marshal_shared_is_in (t)) { + mono_mb_emit_ldloc (mb, dar_release_slot); + label_next = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_release (), NULL); + mono_mb_patch_branch (mb, label_next); + } + + if (mono_marshal_shared_is_out (t)) { + ERROR_DECL (local_error); + MonoMethod *ctor; + + /* + * If the SafeHandle was marshalled on input we can skip the marshalling on + * output if the handle value is identical. + */ + if (mono_marshal_shared_is_in (t)) { + int old_handle_value_slot = dar_release_slot + 1; + mono_mb_emit_ldloc (mb, old_handle_value_slot); + mono_mb_emit_ldloc (mb, conv_arg); + label_next = mono_mb_emit_branch (mb, CEE_BEQ); + } + + /* + * Create an empty SafeHandle (of correct derived type). + * + * FIXME: If an out-of-memory situation or exception happens here we will + * leak the handle. We should move the allocation of the SafeHandle to the + * input marshalling code to prevent that. + */ + ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, local_error); + if (ctor == NULL || !is_ok (local_error)){ + mono_mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); + mono_error_cleanup (local_error); + break; + } + + /* refval = new SafeHandleDerived ()*/ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_op (mb, CEE_NEWOBJ, ctor); + mono_mb_emit_byte (mb, CEE_STIND_REF); + + /* refval.handle = returned_handle */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_REF); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_byte (mb, CEE_STIND_I); + + if (mono_marshal_shared_is_in (t) && label_next) { + mono_mb_patch_branch (mb, label_next); + } + } + } else { + mono_mb_emit_ldloc (mb, dar_release_slot); + label_next = mono_mb_emit_branch (mb, CEE_BRFALSE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_managed_call (mb, *mono_marshal_shared_get_sh_dangerous_release (), NULL); + mono_mb_patch_branch (mb, label_next); + } + break; + } + + case MARSHAL_ACTION_CONV_RESULT: { + ERROR_DECL (error); + MonoMethod *ctor = NULL; + int intptr_handle_slot; + + if (mono_class_is_abstract (t->data.klass)) { + mono_mb_emit_byte (mb, CEE_POP); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, g_strdup ("Returned SafeHandles should not be abstract")); + break; + } + + ctor = mono_class_get_method_from_name_checked (t->data.klass, ".ctor", 0, 0, error); + if (ctor == NULL || !is_ok (error)){ + mono_error_cleanup (error); + mono_mb_emit_byte (mb, CEE_POP); + mono_mb_emit_exception (mb, "MissingMethodException", "parameterless constructor required"); + break; + } + /* Store the IntPtr results into a local */ + intptr_handle_slot = mono_mb_add_local (mb, int_type); + mono_mb_emit_stloc (mb, intptr_handle_slot); + + /* Create return value */ + mono_mb_emit_op (mb, CEE_NEWOBJ, ctor); + mono_mb_emit_stloc (mb, 3); + + /* Set the return.handle to the value, am using ldflda, not sure if thats a good idea */ + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle)); + mono_mb_emit_ldloc (mb, intptr_handle_slot); + mono_mb_emit_byte (mb, CEE_STIND_I); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); + break; + default: + printf ("Unhandled case for MarshalAction: %d\n", action); + } + return conv_arg; +} + +static int +emit_marshal_handleref_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + + MonoType *int_type = mono_get_int_type (); + switch (action){ + case MARSHAL_ACTION_CONV_IN: { + conv_arg = mono_mb_add_local (mb, int_type); + *conv_arg_type = int_type; + + if (m_type_is_byref (t)) { + char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoHandleRef, handle)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_PUSH: + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_OUT: { + /* no resource release required */ + break; + } + + case MARSHAL_ACTION_CONV_RESULT: { + char *msg = g_strdup ("HandleRefs can not be returned from unmanaged code (or passed by ref)"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n"); + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n"); + break; + default: + fprintf (stderr, "Unhandled case for MarshalAction: %d\n", action); + } + return conv_arg; +} + +static int +emit_marshal_object_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoMethodBuilder *mb = m->mb; + MonoClass *klass = mono_class_from_mono_type_internal (t); + int pos, pos2, loc; + + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + conv_arg = mono_mb_add_local (mb, int_type); + + m->orig_conv_args [argnum] = 0; + + if (mono_class_from_mono_type_internal (t) == mono_defaults.object_class) { + char *msg = g_strdup_printf ("Marshalling of type object is not implemented"); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + char *msg = g_strdup_printf ("Byref marshalling of delegates is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + } else { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + MonoMarshalNative encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + MonoMarshalConv conv = mono_marshal_get_stringbuilder_to_ptr_conv (m->piinfo, spec); + +#if 0 + if (m_type_is_byref (t)) { + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + char *msg = g_strdup_printf ("Byref marshalling of stringbuilders is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } + break; + } +#endif + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && (t->attrs & PARAM_ATTRIBUTE_OUT)) + break; + + if (conv == MONO_MARSHAL_CONV_INVALID) { + char *msg = g_strdup_printf ("stringbuilder marshalling conversion %d not implemented", encoding); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + } else if (m_class_is_blittable (klass)) { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldarg (mb, argnum); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_patch_branch (mb, pos); + break; + } else { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + /* we dont need any conversions for out parameters */ + if (t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + + } else { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_byte (mb, CEE_MONO_OBJADDR); + } + + /* store the address of the source into local variable 0 */ + mono_mb_emit_stloc (mb, 0); + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* allocate space for the native struct and store the address */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_PREFIX1); + mono_mb_emit_byte (mb, CEE_LOCALLOC); + mono_mb_emit_stloc (mb, conv_arg); + + if (m_type_is_byref (t)) { + /* Need to store the original buffer so we can free it later */ + m->orig_conv_args [argnum] = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, m->orig_conv_args [argnum]); + } + + /* set the src_ptr */ + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* set dst_ptr */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos); + } + break; + + case MARSHAL_ACTION_CONV_OUT: + if (klass == mono_class_try_get_stringbuilder_class ()) { + gboolean need_free; + MonoMarshalNative encoding; + MonoMarshalConv conv; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + conv = mono_marshal_get_ptr_to_stringbuilder_conv (m->piinfo, spec, &need_free); + + g_assert (encoding != -1); + + if (m_type_is_byref (t)) { + //g_assert (!(t->attrs & PARAM_ATTRIBUTE_OUT)); + + need_free = TRUE; + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + switch (encoding) { + case MONO_NATIVE_LPWSTR: + mono_mb_emit_icall (mb, mono_string_utf16_to_builder2); + break; + case MONO_NATIVE_LPSTR: + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + break; + case MONO_NATIVE_UTF8STR: + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + break; + default: + g_assert_not_reached (); + } + + mono_mb_emit_byte (mb, CEE_STIND_REF); + } else if (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN)) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (conv, NULL)); + } + + if (need_free) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall (mb, mono_marshal_free); + } + break; + } + + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + break; + } + + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT)) { + /* allocate a new object */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + + /* dst = *argument */ + mono_mb_emit_ldarg (mb, argnum); + + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + + mono_mb_emit_stloc (mb, 1); + + mono_mb_emit_ldloc (mb, 1); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + if (m_type_is_byref (t) || (t->attrs & PARAM_ATTRIBUTE_OUT)) { + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_icon (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_byte (mb, CEE_ADD); + mono_mb_emit_stloc (mb, 1); + + /* src = tmp_locals [i] */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_stloc (mb, 0); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + /* Free the structure returned by the native code */ + emit_struct_free (mb, klass, conv_arg); + + if (m->orig_conv_args [argnum]) { + /* + * If the native function changed the pointer, then free + * the original structure plus the new pointer. + */ + mono_mb_emit_ldloc (mb, m->orig_conv_args [argnum]); + mono_mb_emit_ldloc (mb, conv_arg); + pos2 = mono_mb_emit_branch (mb, CEE_BEQ); + + if (!(t->attrs & PARAM_ATTRIBUTE_OUT)) { + g_assert (m->orig_conv_args [argnum]); + + emit_struct_free (mb, klass, m->orig_conv_args [argnum]); + } + + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall (mb, mono_marshal_free); + + mono_mb_patch_branch (mb, pos2); + } + } + else + /* Free the original structure passed to native code */ + emit_struct_free (mb, klass, conv_arg); + + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: + if (m_class_is_delegate (klass)) { + g_assert (!m_type_is_byref (t)); + mono_mb_emit_stloc (mb, 0); + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_stloc (mb, 3); + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + // FIXME: + char *msg = g_strdup_printf ("Return marshalling of stringbuilders is not implemented."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + } else { + /* set src */ + mono_mb_emit_stloc (mb, 0); + + /* Make a copy since emit_conv modifies local 0 */ + loc = mono_mb_add_local (mb, int_type); + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_stloc (mb, loc); + + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, 3); + + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* allocate result object */ + + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_stloc (mb, 3); + + /* set dst */ + + mono_mb_emit_ldloc (mb, 3); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 1); + + /* emit conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + emit_struct_free (mb, klass, loc); + + /* Free the pointer allocated by unmanaged code */ + mono_mb_emit_ldloc (mb, loc); + mono_mb_emit_icall (mb, mono_marshal_free); + mono_mb_patch_branch (mb, pos); + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: + conv_arg = mono_mb_add_local (mb, m_class_get_byval_arg (klass)); + + if (m_class_is_delegate (klass)) { + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_CLASSCONST, klass); + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte (mb, CEE_LDIND_I); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_FTN_DEL, NULL)); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + if (klass == mono_class_try_get_stringbuilder_class ()) { + MonoMarshalNative encoding; + + encoding = mono_marshal_get_string_encoding (m->piinfo, spec); + + // FIXME: + g_assert (encoding == MONO_NATIVE_LPSTR || encoding == MONO_NATIVE_UTF8STR); + + g_assert (!m_type_is_byref (t)); + g_assert (encoding != -1); + + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_icall (mb, mono_string_utf8_to_builder2); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + /* The class can not have an automatic layout */ + if (mono_class_is_auto_layout (klass)) { + mono_mb_emit_auto_layout_exception (mb, klass); + break; + } + + if (t->attrs & PARAM_ATTRIBUTE_OUT) { + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + /* Set src */ + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) { + /* Check for NULL and raise an exception */ + pos2 = mono_mb_emit_branch (mb, CEE_BRTRUE); + + mono_mb_emit_exception (mb, "ArgumentNullException", NULL); + + mono_mb_patch_branch (mb, pos2); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDIND_I); + } + + mono_mb_emit_stloc (mb, 0); + + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_stloc (mb, conv_arg); + + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRFALSE); + + /* Create and set dst */ + mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); + mono_mb_emit_op (mb, CEE_MONO_NEWOBJ, klass); + mono_mb_emit_stloc (mb, conv_arg); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, TRUE); + + mono_mb_patch_branch (mb, pos); + break; + + case MARSHAL_ACTION_MANAGED_CONV_OUT: + if (m_class_is_delegate (klass)) { + if (m_type_is_byref (t)) { + int stind_op; + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, &stind_op)); + mono_mb_emit_byte (mb, GINT_TO_UINT8 (stind_op)); + break; + } + } + + if (m_type_is_byref (t)) { + /* Check for null */ + mono_mb_emit_ldloc (mb, conv_arg); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_byte (mb, CEE_LDC_I4_0); + mono_mb_emit_byte (mb, CEE_STIND_I); + pos2 = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, pos); + + /* Set src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Allocate and set dest */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_stloc (mb, 1); + + /* Update argument pointer */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc (mb, 1); + mono_mb_emit_byte (mb, CEE_STIND_I); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + } else if (klass == mono_class_try_get_stringbuilder_class ()) { + // FIXME: What to do here ? + } else { + /* byval [Out] marshalling */ + + /* FIXME: Handle null */ + + /* Set src */ + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Set dest */ + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_stloc (mb, 1); + + /* emit valuetype conversion code */ + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + } + break; + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: + if (m_class_is_delegate (klass)) { + mono_mb_emit_icall_id (mb, mono_marshal_shared_conv_to_icall (MONO_MARSHAL_CONV_DEL_FTN, NULL)); + mono_mb_emit_stloc (mb, 3); + break; + } + + /* The class can not have an automatic layout */ + if (mono_class_is_auto_layout (klass)) { + mono_mb_emit_auto_layout_exception (mb, klass); + break; + } + + mono_mb_emit_stloc (mb, 0); + /* Check for null */ + mono_mb_emit_ldloc (mb, 0); + pos = mono_mb_emit_branch (mb, CEE_BRTRUE); + mono_mb_emit_byte (mb, CEE_LDNULL); + mono_mb_emit_stloc (mb, 3); + pos2 = mono_mb_emit_branch (mb, CEE_BR); + + mono_mb_patch_branch (mb, pos); + + /* Set src */ + mono_mb_emit_ldloc (mb, 0); + mono_mb_emit_ldflda (mb, MONO_ABI_SIZEOF (MonoObject)); + mono_mb_emit_stloc (mb, 0); + + /* Allocate and set dest */ + mono_mb_emit_icon (mb, mono_class_native_size (klass, NULL)); + mono_mb_emit_byte (mb, CEE_CONV_I); + mono_mb_emit_icall (mb, ves_icall_marshal_alloc); + mono_mb_emit_byte (mb, CEE_DUP); + mono_mb_emit_stloc (mb, 1); + mono_mb_emit_stloc (mb, 3); + + mono_marshal_shared_emit_struct_conv (mb, klass, FALSE); + + mono_mb_patch_branch (mb, pos2); + break; + + default: + g_assert_not_reached (); + } + return conv_arg; +} + +static int +emit_marshal_variant_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ +#ifndef DISABLE_COM + MonoMethodBuilder *mb = m->mb; + MonoType *variant_type = m_class_get_byval_arg (mono_class_get_variant_class ()); + MonoType *variant_type_byref = mono_class_get_byref_type (mono_class_get_variant_class ()); + MonoType *object_type = mono_get_object_type (); + + switch (action) { + case MARSHAL_ACTION_CONV_IN: { + conv_arg = mono_mb_add_local (mb, variant_type); + + if (m_type_is_byref (t)) + *conv_arg_type = variant_type_byref; + else + *conv_arg_type = variant_type; + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + mono_mb_emit_ldarg (mb, argnum); + if (m_type_is_byref (t)) + mono_mb_emit_byte(mb, CEE_LDIND_REF); + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); + break; + } + + case MARSHAL_ACTION_CONV_OUT: { + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); + mono_mb_emit_byte (mb, CEE_STIND_REF); + } + + mono_mb_emit_ldloc_addr (mb, conv_arg); + mono_mb_emit_managed_call (mb, mono_get_Variant_Clear (), NULL); + break; + } + + case MARSHAL_ACTION_PUSH: + if (m_type_is_byref (t)) + mono_mb_emit_ldloc_addr (mb, conv_arg); + else + mono_mb_emit_ldloc (mb, conv_arg); + break; + + case MARSHAL_ACTION_CONV_RESULT: { + char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + conv_arg = mono_mb_add_local (mb, object_type); + + if (m_type_is_byref (t)) + *conv_arg_type = variant_type_byref; + else + *conv_arg_type = variant_type; + + if (m_type_is_byref (t) && !(t->attrs & PARAM_ATTRIBUTE_IN) && t->attrs & PARAM_ATTRIBUTE_OUT) + break; + + if (m_type_is_byref (t)) + mono_mb_emit_ldarg (mb, argnum); + else + mono_mb_emit_ldarg_addr (mb, argnum); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetObjectForNativeVariant (), NULL); + mono_mb_emit_stloc (mb, conv_arg); + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_OUT: { + if (m_type_is_byref (t) && (t->attrs & PARAM_ATTRIBUTE_OUT || !(t->attrs & PARAM_ATTRIBUTE_IN))) { + mono_mb_emit_ldloc (mb, conv_arg); + mono_mb_emit_ldarg (mb, argnum); + mono_mb_emit_managed_call (mb, mono_get_Marshal_GetNativeVariantForObject (), NULL); + } + break; + } + + case MARSHAL_ACTION_MANAGED_CONV_RESULT: { + char *msg = g_strdup ("Marshalling of VARIANT not supported as a return type."); + mono_marshal_shared_mb_emit_exception_marshal_directive (mb, msg); + break; + } + + default: + g_assert_not_reached (); + } +#endif /* DISABLE_COM */ + + return conv_arg; +} + +static MonoMarshalIlgenCallbacks * +get_marshal_cb (void) +{ + if (G_UNLIKELY (!ilgen_cb_inited)) { +#ifdef ENABLE_ILGEN + mono_marshal_ilgen_init (); +#else + mono_marshal_noilgen_init_heavyweight (); +#endif + } + return &ilgen_marshal_cb; +} + +int +mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb) +{ + if (spec && spec->native == MONO_NATIVE_CUSTOM) + return get_marshal_cb ()->emit_marshal_custom (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + if (spec && spec->native == MONO_NATIVE_ASANY) + return get_marshal_cb ()->emit_marshal_asany (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + switch (t->type) { + case MONO_TYPE_VALUETYPE: + if (t->data.klass == mono_class_try_get_handleref_class ()) + return get_marshal_cb ()->emit_marshal_handleref (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_STRING: + return get_marshal_cb ()->emit_marshal_string (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_CLASS: + case MONO_TYPE_OBJECT: +#if !defined(DISABLE_COM) + if (spec && spec->native == MONO_NATIVE_STRUCT) + return get_marshal_cb ()->emit_marshal_variant (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + +#if !defined(DISABLE_COM) + if ((spec && (spec->native == MONO_NATIVE_IUNKNOWN || + spec->native == MONO_NATIVE_IDISPATCH || + spec->native == MONO_NATIVE_INTERFACE)) || + (t->type == MONO_TYPE_CLASS && mono_cominterop_is_interface(t->data.klass))) + return mono_cominterop_emit_marshal_com_interface (m, argnum, t, spec, conv_arg, conv_arg_type, action); + if (spec && (spec->native == MONO_NATIVE_SAFEARRAY) && + (spec->data.safearray_data.elem_type == MONO_VARIANT_VARIANT) && + ((action == MARSHAL_ACTION_CONV_OUT) || (action == MARSHAL_ACTION_CONV_IN) || (action == MARSHAL_ACTION_PUSH))) + return mono_cominterop_emit_marshal_safearray (m, argnum, t, spec, conv_arg, conv_arg_type, action); +#endif + + if (mono_class_try_get_safehandle_class () != NULL && t->data.klass && + mono_class_is_subclass_of_internal (t->data.klass, mono_class_try_get_safehandle_class (), FALSE)) + return get_marshal_cb ()->emit_marshal_safehandle (m, argnum, t, spec, conv_arg, conv_arg_type, action); + + return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: + return get_marshal_cb ()->emit_marshal_array (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_BOOLEAN: + return get_marshal_cb ()->emit_marshal_boolean (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_PTR: + return get_marshal_cb ()->emit_marshal_ptr (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_CHAR: + return get_marshal_cb ()->emit_marshal_char (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_I1: + case MONO_TYPE_U1: + case MONO_TYPE_I2: + case MONO_TYPE_U2: + case MONO_TYPE_I4: + case MONO_TYPE_U4: + case MONO_TYPE_I: + case MONO_TYPE_U: + case MONO_TYPE_R4: + case MONO_TYPE_R8: + case MONO_TYPE_I8: + case MONO_TYPE_U8: + case MONO_TYPE_FNPTR: + return lightweigth_cb->emit_marshal_scalar (m, argnum, t, spec, conv_arg, conv_arg_type, action); + case MONO_TYPE_GENERICINST: + if (mono_type_generic_inst_is_valuetype (t)) + return get_marshal_cb ()->emit_marshal_vtype (m, argnum, t, spec, conv_arg, conv_arg_type, action); + else + return get_marshal_cb ()->emit_marshal_object (m, argnum, t, spec, conv_arg, conv_arg_type, action); + default: + return conv_arg; + } +} + +void +mono_marshal_ilgen_init (void) +{ + MonoMarshalIlgenCallbacks cb; + cb.version = MONO_MARSHAL_CALLBACKS_VERSION; + cb.emit_marshal_array = emit_marshal_array_ilgen; + cb.emit_marshal_ptr = emit_marshal_ptr_ilgen; + cb.emit_marshal_char = emit_marshal_char_ilgen; + cb.emit_marshal_vtype = emit_marshal_vtype_ilgen; + cb.emit_marshal_string = emit_marshal_string_ilgen; + cb.emit_marshal_variant = emit_marshal_variant_ilgen; + cb.emit_marshal_safehandle = emit_marshal_safehandle_ilgen; + cb.emit_marshal_object = emit_marshal_object_ilgen; + cb.emit_marshal_boolean = emit_marshal_boolean_ilgen; + cb.emit_marshal_custom = emit_marshal_custom_ilgen; + cb.emit_marshal_asany = emit_marshal_asany_ilgen; + cb.emit_marshal_handleref = emit_marshal_handleref_ilgen; + +#ifdef DISABLE_NONBLITTABLE + mono_marshal_noilgen_init_blittable (&cb); +#endif + mono_install_marshal_callbacks_ilgen (&cb); +} + + diff --git a/src/mono/mono/component/marshal-ilgen.h b/src/mono/mono/metadata/marshal-ilgen.h similarity index 73% rename from src/mono/mono/component/marshal-ilgen.h rename to src/mono/mono/metadata/marshal-ilgen.h index 08768a374483a7..1b3914588fb2e4 100644 --- a/src/mono/mono/component/marshal-ilgen.h +++ b/src/mono/mono/metadata/marshal-ilgen.h @@ -1,24 +1,9 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ + #ifndef __MARSHAL_ILGEN_H__ #define __MARSHAL_ILGEN_H__ #include "metadata/marshal-lightweight.h" #include "metadata/marshal.h" -#include "mono/component/component.h" - -typedef struct MonoComponentMarshalILgen { - MonoComponent component; - void (*ilgen_init) (void); - int (*emit_marshal_ilgen) (EmitMarshalContext *m, int argnum, MonoType *t, - MonoMarshalSpec *spec, int conv_arg, - MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb); - void (*install_callbacks_mono) (IlgenCallbacksToMono *callbacks); - -} MonoComponentMarshalILgen; typedef struct { int version; @@ -34,23 +19,24 @@ typedef struct { int (*emit_marshal_custom) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); int (*emit_marshal_asany) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); int (*emit_marshal_handleref) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); -} MonoMarshalILgenCallbacks; - -MONO_COMPONENT_EXPORT_ENTRYPOINT -MonoComponentMarshalILgen* mono_component_marshal_ilgen_init (void); +} MonoMarshalIlgenCallbacks; void -mono_install_marshal_callbacks_ilgen (MonoMarshalILgenCallbacks *cb); +mono_install_marshal_callbacks_ilgen (MonoMarshalIlgenCallbacks *cb); + MONO_API void mono_marshal_ilgen_init (void); +void +mono_marshal_noilgen_init_heavyweight (void); + +void +mono_marshal_noilgen_init_lightweight (void); + int mono_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweigth_cb); -void -mono_marshal_ilgen_install_callbacks_mono (IlgenCallbacksToMono *callbacks); - #endif // __MARSHAL_ILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/metadata/marshal-lightweight.c b/src/mono/mono/metadata/marshal-lightweight.c index 0c9a5258063caa..70a8ee0ede2947 100644 --- a/src/mono/mono/metadata/marshal-lightweight.c +++ b/src/mono/mono/metadata/marshal-lightweight.c @@ -8,13 +8,14 @@ #include #endif -#include "mono/metadata/method-builder-ilgen.h" -#include "mono/metadata/method-builder-ilgen-internals.h" +#include "metadata/method-builder-ilgen.h" +#include "metadata/method-builder-ilgen-internals.h" #include #include #include "cil-coff.h" #include "metadata/marshal.h" #include "metadata/marshal-internals.h" +#include "metadata/marshal-ilgen.h" #include "metadata/marshal-lightweight.h" #include "metadata/marshal-shared.h" #include "metadata/tabledefs.h" @@ -23,7 +24,6 @@ #include "mono/metadata/abi-details.h" #include "mono/metadata/class-abi-details.h" #include "mono/metadata/class-init.h" -#include "mono/metadata/components.h" #include "mono/metadata/debug-helpers.h" #include "mono/metadata/threads.h" #include "mono/metadata/monitor.h" diff --git a/src/mono/mono/metadata/marshal-lightweight.h b/src/mono/mono/metadata/marshal-lightweight.h index b25d9cc9f2aba5..8dfd4803a96496 100644 --- a/src/mono/mono/metadata/marshal-lightweight.h +++ b/src/mono/mono/metadata/marshal-lightweight.h @@ -5,7 +5,6 @@ */ #ifndef __MONO_MARSHAL_LIGHTWEIGHT_H__ #define __MONO_MARSHAL_LIGHTWEIGHT_H__ -#include MONO_API void mono_marshal_lightweight_init (void); diff --git a/src/mono/mono/metadata/marshal-noilgen.c b/src/mono/mono/metadata/marshal-noilgen.c index 8d6f75981cd87d..ae073fcaf7f8ec 100644 --- a/src/mono/mono/metadata/marshal-noilgen.c +++ b/src/mono/mono/metadata/marshal-noilgen.c @@ -1,13 +1,38 @@ #include "config.h" + #include -#include -#include -#include +#include "metadata/marshal-internals.h" +#include "metadata/marshal.h" +#include "metadata/marshal-ilgen.h" #include "utils/mono-compiler.h" #ifndef ENABLE_ILGEN +static int +emit_marshal_array_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + MonoType *object_type = mono_get_object_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = object_type; + break; + case MARSHAL_ACTION_MANAGED_CONV_IN: + *conv_arg_type = int_type; + break; + } + return conv_arg; +} - +static int +emit_marshal_ptr_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + return conv_arg; +} static int emit_marshal_scalar_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, @@ -18,6 +43,136 @@ emit_marshal_scalar_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, } #endif +#if !defined(ENABLE_ILGEN) || defined(DISABLE_NONBLITTABLE) +static int +emit_marshal_boolean_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + if (m_type_is_byref (t)) + *conv_arg_type = int_type; + else + *conv_arg_type = mono_marshal_boolean_conv_in_get_local_type (spec, NULL); + break; + + case MARSHAL_ACTION_MANAGED_CONV_IN: { + MonoClass* conv_arg_class = mono_marshal_boolean_managed_conv_in_get_conv_arg_class (spec, NULL); + if (m_type_is_byref (t)) + *conv_arg_type = m_class_get_this_arg (conv_arg_class); + else + *conv_arg_type = m_class_get_byval_arg (conv_arg_class); + break; + } + + } + return conv_arg; +} + +static int +emit_marshal_char_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_custom_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN && t->type == MONO_TYPE_VALUETYPE) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_asany_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_vtype_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + return conv_arg; +} + +static int +emit_marshal_string_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + switch (action) { + case MARSHAL_ACTION_CONV_IN: + *conv_arg_type = int_type; + break; + case MARSHAL_ACTION_MANAGED_CONV_IN: + *conv_arg_type = int_type; + break; + } + return conv_arg; +} + +static int +emit_marshal_safehandle_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + + +static int +emit_marshal_handleref_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, int conv_arg, + MonoType **conv_arg_type, MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_object_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + MonoType *int_type = mono_get_int_type (); + if (action == MARSHAL_ACTION_CONV_IN) + *conv_arg_type = int_type; + return conv_arg; +} + +static int +emit_marshal_variant_noilgen (EmitMarshalContext *m, int argnum, MonoType *t, + MonoMarshalSpec *spec, + int conv_arg, MonoType **conv_arg_type, + MarshalAction action) +{ + g_assert_not_reached (); +} +#endif + #ifndef ENABLE_ILGEN static void emit_managed_wrapper_noilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, MonoError *error) @@ -251,13 +406,38 @@ mono_marshal_noilgen_init_lightweight (void) } +void +mono_marshal_noilgen_init_heavyweight (void) +{ + MonoMarshalIlgenCallbacks ilgen_cb; + + ilgen_cb.version = MONO_MARSHAL_CALLBACKS_VERSION; + ilgen_cb.emit_marshal_array = emit_marshal_array_noilgen; + ilgen_cb.emit_marshal_vtype = emit_marshal_vtype_noilgen; + ilgen_cb.emit_marshal_string = emit_marshal_string_noilgen; + ilgen_cb.emit_marshal_safehandle = emit_marshal_safehandle_noilgen; + ilgen_cb.emit_marshal_handleref = emit_marshal_handleref_noilgen; + ilgen_cb.emit_marshal_object = emit_marshal_object_noilgen; + ilgen_cb.emit_marshal_variant = emit_marshal_variant_noilgen; + ilgen_cb.emit_marshal_asany = emit_marshal_asany_noilgen; + ilgen_cb.emit_marshal_boolean = emit_marshal_boolean_noilgen; + ilgen_cb.emit_marshal_custom = emit_marshal_custom_noilgen; + ilgen_cb.emit_marshal_ptr = emit_marshal_ptr_noilgen; + + ilgen_cb.emit_marshal_char = emit_marshal_char_noilgen; + mono_install_marshal_callbacks_ilgen(&ilgen_cb); +} + #else void mono_marshal_noilgen_init_lightweight (void) { } - +void +mono_marshal_noilgen_init_heavyweight (void) +{ +} #endif #ifdef DISABLE_NONBLITTABLE diff --git a/src/mono/mono/metadata/marshal-noilgen.h b/src/mono/mono/metadata/marshal-noilgen.h deleted file mode 100644 index c9cafee09fbbd7..00000000000000 --- a/src/mono/mono/metadata/marshal-noilgen.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * \file - * Copyright 2022 Microsoft - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - */ -#ifndef __MARSHAL_NOILGEN_H__ -#define __MARSHAL_NOILGEN_H__ - -void -mono_marshal_noilgen_init_lightweight (void); - -void -mono_marshal_noilgen_init_heavyweight (void); - -#endif // __MARSHAL_NOILGEN_H__ \ No newline at end of file diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 47b1a66a979cab..6caac8e640b333 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -32,7 +32,7 @@ MONO_PRAGMA_WARNING_POP() #include "cil-coff.h" #include "metadata/marshal.h" #include "metadata/marshal-internals.h" -#include "metadata/marshal-shared.h" +#include "metadata/marshal-ilgen.h" #include "metadata/marshal-lightweight.h" #include "metadata/method-builder.h" #include "metadata/method-builder-internals.h" @@ -41,10 +41,8 @@ MONO_PRAGMA_WARNING_POP() #include #include "mono/metadata/abi-details.h" #include "mono/metadata/class-abi-details.h" -#include "mono/metadata/components.h" #include "mono/metadata/debug-helpers.h" #include "mono/metadata/threads.h" -#include "mono/metadata/marshal-noilgen.h" #include "mono/metadata/monitor.h" #include "mono/metadata/class-init.h" #include "mono/metadata/class-internals.h" @@ -129,66 +127,6 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callconv_attribute, "System. static gboolean type_is_blittable (MonoType *type); -static IlgenCallbacksToMono ilgenCallbacksToMono = { - &mono_get_object_type, - &mono_marshal_get_ptr_to_string_conv, - &mono_class_is_subclass_of_internal, - &mono_class_native_size, - &mono_class_try_get_handleref_class, - &mono_class_try_get_safehandle_class, - &mono_class_try_get_stringbuilder_class, - &mono_defaults, - &mono_marshal_boolean_conv_in_get_local_type, - &mono_marshal_boolean_managed_conv_in_get_conv_arg_class, - &mono_marshal_get_ptr_to_stringbuilder_conv, - &mono_marshal_get_string_encoding, - &mono_marshal_get_string_to_ptr_conv, - &mono_marshal_get_stringbuilder_to_ptr_conv, - &mono_marshal_load_type_info, - &mono_marshal_shared_conv_to_icall, - &mono_marshal_shared_emit_marshal_custom_get_instance, - &mono_marshal_shared_emit_struct_conv, - &mono_marshal_shared_emit_struct_conv_full, - &mono_marshal_shared_get_method_nofail, - &mono_marshal_shared_get_sh_dangerous_add_ref, - &mono_marshal_shared_get_sh_dangerous_release, - &mono_marshal_shared_init_safe_handle, - &mono_marshal_shared_is_in, - &mono_marshal_shared_is_out, - &mono_marshal_shared_mb_emit_exception_marshal_directive, - &mono_mb_add_local, - &mono_mb_emit_add_to_local, - &mono_mb_emit_auto_layout_exception, - &mono_mb_emit_branch, - &mono_mb_emit_branch_label, - &mono_mb_emit_byte, - &mono_mb_emit_exception, - &mono_mb_emit_exception_full, - &mono_mb_emit_icall_id, - &mono_mb_emit_icon, - &mono_mb_emit_ldarg, - &mono_mb_emit_ldarg_addr, - &mono_mb_emit_ldflda, - &mono_mb_emit_ldloc, - &mono_mb_emit_ldloc_addr, - &mono_mb_emit_managed_call, - &mono_mb_emit_op, - &mono_mb_emit_stloc, - &mono_mb_get_label, - &mono_mb_patch_branch, - &mono_pinvoke_is_unicode, - &mono_reflection_type_from_name_checked, - &mono_memory_barrier, - &mono_marshal_need_free, - &mono_get_int_type -}; - -IlgenCallbacksToMono* -mono_marshal_get_mono_callbacks_for_ilgen (void) -{ - return &ilgenCallbacksToMono; -} - static MonoImage* get_method_image (MonoMethod *method) { @@ -3222,9 +3160,8 @@ mono_emit_marshal (EmitMarshalContext *m, int argnum, MonoType *t, if (!m->runtime_marshalling_enabled) return mono_emit_disabled_marshal (m, argnum, t, spec, conv_arg, conv_arg_type, action); - mono_component_marshal_ilgen()->install_callbacks_mono(mono_marshal_get_mono_callbacks_for_ilgen()); - return mono_component_marshal_ilgen()->emit_marshal_ilgen(m, argnum, t, spec, conv_arg, conv_arg_type, action, get_marshal_cb()); -} + return mono_emit_marshal_ilgen(m, argnum, t, spec, conv_arg, conv_arg_type, action, get_marshal_cb()); +} static void mono_marshal_set_callconv_for_type(MonoType *type, MonoMethodSignature *csig, gboolean *skip_gc_trans /*out*/) @@ -6323,6 +6260,7 @@ get_marshal_cb (void) mono_marshal_noilgen_init_lightweight (); #endif } + return &marshal_lightweight_cb; } diff --git a/src/mono/mono/metadata/marshal.h b/src/mono/mono/metadata/marshal.h index e4244ad07c7b47..93aeb28667de56 100644 --- a/src/mono/mono/metadata/marshal.h +++ b/src/mono/mono/metadata/marshal.h @@ -348,60 +348,6 @@ typedef struct { int (*emit_marshal_scalar) (EmitMarshalContext *m, int argnum, MonoType *t, MonoMarshalSpec *spec, int conv_arg, MonoType **conv_arg_type, MarshalAction action); } MonoMarshalLightweightCallbacks; -typedef struct { - MonoType* (*get_object_type) (void); - MonoMarshalConv (*get_ptr_to_string_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec, gboolean *need_free); - gboolean (*is_subclass_of_internal) (MonoClass *klass, MonoClass *klassc, gboolean check_interfaces); - gint32 (*class_native_size) (MonoClass *klass, guint32 *align); - MonoClass* (*class_try_get_handleref_class) (void); - MonoClass* (*try_get_safehandle_class) (void); - MonoClass* (*try_get_stringbuilder_class) (void); - MonoDefaults* mono_defaults; - MonoType* (*boolean_conv_in_get_local_type) (MonoMarshalSpec *spec, guint8 *ldc_op /*out*/); - MonoClass* (*boolean_managed_conv_in_get_conv_arg_class) (MonoMarshalSpec *spec, guint8 *ldop/*out*/); - MonoMarshalConv (*get_ptr_to_stringbuilder_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec, gboolean *need_free); - MonoMarshalNative (*get_string_encoding) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalConv (*get_string_to_ptr_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalConv (*get_stringbuilder_to_ptr_conv) (MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoMarshalType* (*load_type_info) (MonoClass* klass); - MonoJitICallId (*conv_to_icall) (MonoMarshalConv conv, int *ind_store_type); - void (*emit_marshal_custom_get_instance) (MonoMethodBuilder *mb, MonoClass *klass, MonoMarshalSpec *spec); - void (*emit_struct_conv) (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object); - void (*emit_struct_conv_full) (MonoMethodBuilder *mb, MonoClass *klass, gboolean to_object, int offset_of_first_child_field, MonoMarshalNative string_encoding); - MonoMethod* (*get_method_nofail) (MonoClass *klass, const char *method_name, int num_params, int flags); - MonoMethod** (*get_sh_dangerous_add_ref) (void); - MonoMethod** (*get_sh_dangerous_release) (void); - void (*init_safe_handle) (void); - gboolean (*is_in) (const MonoType *t); - gboolean (*is_out) (const MonoType *t); - void (*mb_emit_exception_marshal_directive) (MonoMethodBuilder *mb, char *msg); - int (*mb_add_local) (MonoMethodBuilder *mb, MonoType *type); - void (*mb_emit_add_to_local) (MonoMethodBuilder *mb, guint16 local, gint32 incr); - void (*mb_emit_auto_layout_exception) (MonoMethodBuilder *mb, MonoClass *klass); - guint32 (*mb_emit_branch) (MonoMethodBuilder *mb, guint8 op); - void (*mb_emit_branch_label) (MonoMethodBuilder *mb, guint8 op, guint32 label); - void (*mb_emit_byte) (MonoMethodBuilder *mb, guint8 op); - void (*mb_emit_exception) (MonoMethodBuilder *mb, const char *exc_name, const char *msg); - void (*mb_emit_exception_full) (MonoMethodBuilder *mb, const char *exc_nspace, const char *exc_name, const char *msg); - void (*mb_emit_icall_id) (MonoMethodBuilder *mb, MonoJitICallId jit_icall_id); - void (*mb_emit_icon) (MonoMethodBuilder *mb, gint32 value); - void (*mb_emit_ldarg) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_ldarg_addr) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_ldflda) (MonoMethodBuilder *mb, gint32 offset); - void (*mb_emit_ldloc) (MonoMethodBuilder *mb, guint num); - void (*mb_emit_ldloc_addr) (MonoMethodBuilder *mb, guint argnum); - void (*mb_emit_managed_call) (MonoMethodBuilder *mb, MonoMethod *method, MonoMethodSignature *opt_sig); - void (*mb_emit_op) (MonoMethodBuilder *mb, guint8 op, gpointer data); - void (*mb_emit_stloc) (MonoMethodBuilder *mb, guint num); - int (*mb_get_label) (MonoMethodBuilder *mb); - void (*mb_patch_branch) (MonoMethodBuilder *mb, guint32 pos); - gboolean (*pinvoke_is_unicode) (MonoMethodPInvoke *piinfo); - MonoType* (*reflection_type_from_name_checked) (char *name, MonoAssemblyLoadContext *alc, MonoImage *image, MonoError *error); - void (*memory_barrier) (void); - gboolean (*need_free) (MonoType *t, MonoMethodPInvoke *piinfo, MonoMarshalSpec *spec); - MonoType* (*get_int_type) (void); -} IlgenCallbacksToMono; - /*type of the function pointer of methods returned by mono_marshal_get_runtime_invoke*/ typedef MonoObject *(*RuntimeInvokeFunction) (MonoObject *this_obj, void **params, MonoObject **exc, void* compiled_method); @@ -756,7 +702,4 @@ mono_mb_create_and_cache_full (GHashTable *cache, gpointer key, MonoMethodBuilder *mb, MonoMethodSignature *sig, int max_stack, WrapperInfo *info, gboolean *out_found); -IlgenCallbacksToMono* -mono_marshal_get_mono_callbacks_for_ilgen (void); - #endif /* __MONO_MARSHAL_H__ */ diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 6ef0ebf57b4ea2..9e74fd9e16fb43 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -349,7 +349,7 @@ add_library(monosgen-static STATIC $;$aot_opts.tool_prefix) + g_string_append_printf (str, "\"%s%s\" %s", tool_prefix, ld_binary_name, LD_OPTIONS); + else if (acfg->aot_opts.llvm_only) + g_string_append_printf (str, "%s", acfg->aot_opts.clangxx); + else + g_string_append_printf (str, "\"%s%s\" %s", tool_prefix, ld_binary_name, LD_OPTIONS); #else if (ld_binary_name == NULL) { ld_binary_name = "ld"; diff --git a/src/mono/mono/mini/cpu-ppc64.mdesc b/src/mono/mono/mini/cpu-ppc64.mdesc index d2437a34fda45e..77d99656d72151 100644 --- a/src/mono/mono/mini/cpu-ppc64.mdesc +++ b/src/mono/mono/mini/cpu-ppc64.mdesc @@ -45,18 +45,18 @@ # # See the code in mini-x86.c for more details on how the specifiers are used. # -tailcall: len:124 clob:c +tailcall: len:152 clob:c tailcall_parameter: len:0 # PowerPC outputs a nice fixed size memcpy loop for larger stack_usage, so 0. memory_barrier: len:4 nop: len:4 relaxed_nop: len:4 -break: len:40 +break: len:44 seq_point: len:48 il_seq_point: len:0 -call: dest:a clob:c len:36 +call: dest:a clob:c len:40 br: len:4 -throw: src1:i len:40 -rethrow: src1:i len:40 +throw: src1:i len:44 +rethrow: src1:i len:44 ckfinite: dest:f src1:f ppc_check_finite: src1:i len:16 add_ovf_carry: dest:i src1:i src2:i len:16 @@ -77,16 +77,16 @@ fcompare: src1:f src2:f len:12 arglist: src1:i len:12 setlret: src1:i src2:i len:12 check_this: src1:b len:4 -voidcall: len:36 clob:c +voidcall: len:40 clob:c voidcall_reg: src1:i len:16 clob:c voidcall_membase: src1:b len:16 clob:c -fcall: dest:g len:36 clob:c +fcall: dest:g len:40 clob:c fcall_reg: dest:g src1:i len:16 clob:c fcall_membase: dest:g src1:b len:16 clob:c -lcall: dest:a len:36 clob:c +lcall: dest:a len:40 clob:c lcall_reg: dest:a src1:i len:16 clob:c lcall_membase: dest:a src1:b len:16 clob:c -vcall: len:16 clob:c +vcall: len:20 clob:c vcall_reg: src1:i len:16 clob:c vcall_membase: src1:b len:12 clob:c call_reg: dest:a src1:i len:16 clob:c @@ -404,7 +404,7 @@ int_max_un: dest:i src1:i src2:i len:8 clob:1 #long_conv_to_ovf_i4_2: dest:i src1:i src2:i len:30 -vcall2: len:36 clob:c +vcall2: len:40 clob:c vcall2_reg: src1:i len:16 clob:c vcall2_membase: src1:b len:16 clob:c diff --git a/src/mono/mono/mini/exceptions-ppc.c b/src/mono/mono/mini/exceptions-ppc.c index 99dc4b447d2128..69f6c490c13cb5 100644 --- a/src/mono/mono/mini/exceptions-ppc.c +++ b/src/mono/mono/mini/exceptions-ppc.c @@ -564,9 +564,9 @@ mono_arch_unwind_frame (MonoJitTlsData *jit_tls, unwind_info = mono_jinfo_get_unwind_info (ji, &unwind_info_len); - sframe = (MonoPPCStackFrame*)MONO_CONTEXT_GET_SP (ctx); - MONO_CONTEXT_SET_BP (new_ctx, sframe->sp); if (!ji->is_trampoline && jinfo_get_method (ji)->save_lmf) { + sframe = (MonoPPCStackFrame*)MONO_CONTEXT_GET_SP (ctx); + MONO_CONTEXT_SET_BP (new_ctx, sframe->sp); /* sframe->sp points just past the end of the LMF */ guint8 *lmf_addr = (guint8*)sframe->sp - sizeof (MonoLMF); memcpy (&new_ctx->fregs [MONO_PPC_FIRST_SAVED_FREG], lmf_addr + G_STRUCT_OFFSET (MonoLMF, fregs), sizeof (double) * MONO_SAVED_FREGS); diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index cfb8f7cc1ff888..41bece492d72db 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -2232,7 +2232,16 @@ static MonoInst* mono_emit_widen_call_res (MonoCompile *cfg, MonoInst *ins, MonoMethodSignature *fsig) { if (!MONO_TYPE_IS_VOID (fsig->ret)) { - if ((fsig->pinvoke || LLVM_ENABLED) && !m_type_is_byref (fsig->ret)) { + // FIXME + // LLVM code doesn't uses zero extend the full word while jit expects it. + // A proper fix would be to detect if we are actually using llvm code from aot images + // or make sure llvm code actually zero extends the return. +#ifdef MONO_ARCH_LLVM_SUPPORTED + gboolean might_use_llvm = TRUE; +#else + gboolean might_use_llvm = FALSE; +#endif + if ((fsig->pinvoke || might_use_llvm) && !m_type_is_byref (fsig->ret)) { int widen_op = -1; /* diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index 16f6f06db84706..bc4eedd8f86c5e 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -2165,6 +2165,12 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) return linfo; } +#if 0 + /* FIXME: the non-LLVM codegen should also pass arguments in registers or + * else there could a mismatch when LLVM code calls non-LLVM code + * + * See https://github.com/dotnet/runtime/issues/73454 + */ if ((t->type == MONO_TYPE_GENERICINST) && !cfg->full_aot && !sig->pinvoke) { MonoClass *klass = mono_class_from_mono_type_internal (t); if (MONO_CLASS_IS_SIMD (cfg, klass)) { @@ -2172,6 +2178,7 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } } +#endif linfo->args [i].storage = LLVMArgVtypeInReg; for (j = 0; j < 2; ++j) @@ -5504,7 +5511,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) } case OP_CHECK_THIS: /* ensure ins->sreg1 is not NULL */ - amd64_alu_membase_imm_size (code, X86_CMP, ins->sreg1, 0, 0, 4); + amd64_alu_membase8_reg_size (code, X86_CMP, ins->sreg1, 0, ins->sreg1, 1); break; case OP_ARGLIST: { amd64_lea_membase (code, AMD64_R11, cfg->frame_reg, cfg->sig_cookie); diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index 081e5bc151aeca..65a6bb42e1fc7e 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -2521,6 +2521,12 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } case ArgVtypeInIRegs: +#if 0 + /* FIXME: the non-LLVM codegen should also pass arguments in registers or + * else there could a mismatch when LLVM code calls non-LLVM code + * + * See https://github.com/dotnet/runtime/issues/73454 + */ if ((t->type == MONO_TYPE_GENERICINST) && !cfg->full_aot && !sig->pinvoke) { MonoClass *klass = mono_class_from_mono_type_internal (t); if (MONO_CLASS_IS_SIMD (cfg, klass)) { @@ -2528,6 +2534,7 @@ mono_arch_get_llvm_call_info (MonoCompile *cfg, MonoMethodSignature *sig) break; } } +#endif lainfo->storage = LLVMArgAsIArgs; lainfo->nslots = ainfo->nregs; diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 3d7a809c2173a8..e6224e598f6701 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -6856,7 +6856,7 @@ MONO_RESTORE_WARNING } case OP_CHECK_THIS: - LLVMBuildLoad2 (builder, IntPtrType (), convert (ctx, lhs, pointer_type (IntPtrType ())), ""); + LLVMBuildLoad2 (builder, LLVMInt8Type (), convert (ctx, lhs, pointer_type (LLVMInt8Type ())), ""); break; case OP_OUTARG_VTRETADDR: break; diff --git a/src/mono/mono/mini/mini-ppc.c b/src/mono/mono/mini/mini-ppc.c index 93abd63562ff53..bc97b497af93a2 100644 --- a/src/mono/mono/mini/mini-ppc.c +++ b/src/mono/mono/mini/mini-ppc.c @@ -1891,7 +1891,7 @@ typedef struct { if (0 && ins->inst_true_bb->native_offset) { \ ppc_bc (code, (b0), (b1), (code - cfg->native_code + ins->inst_true_bb->native_offset) & 0xffff); \ } else { \ - int br_disp = ins->inst_true_bb->max_offset - offset; \ + int br_disp = ins->inst_true_bb->max_offset - cpos; \ if (!ppc_is_imm16 (br_disp + 8 * 1024) || !ppc_is_imm16 (br_disp - 8 * 1024)) { \ MonoOvfJump *ovfj = mono_mempool_alloc (cfg->mempool, sizeof (MonoOvfJump)); \ ovfj->data.bb = ins->inst_true_bb; \ @@ -1915,7 +1915,7 @@ if (0 && ins->inst_true_bb->native_offset) { \ */ #define EMIT_COND_SYSTEM_EXCEPTION_FLAGS(b0,b1,exc_name) \ do { \ - int br_disp = cfg->bb_exit->max_offset - offset; \ + int br_disp = cfg->bb_exit->max_offset - cpos; \ if (!ppc_is_imm16 (br_disp + 1024) || ! ppc_is_imm16 (ppc_is_imm16 (br_disp - 1024))) { \ MonoOvfJump *ovfj = mono_mempool_alloc (cfg->mempool, sizeof (MonoOvfJump)); \ ovfj->data.exception = (exc_name); \ @@ -2732,6 +2732,9 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) if (!cfg->arch.thunks) { cfg->arch.thunks = cfg->thunks; cfg->arch.thunks_size = cfg->thunk_area; +#ifdef THUNK_ADDR_ALIGNMENT + cfg->arch.thunks = ALIGN_TO(cfg->arch.thunks, THUNK_ADDR_ALIGNMENT); +#endif } thunks = cfg->arch.thunks; thunks_size = cfg->arch.thunks_size; @@ -3907,11 +3910,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) if (cfg->compile_aot && ins->sreg1 == ppc_r12) { /* The trampolines clobber this */ ppc_mr (code, ppc_r29, ins->sreg1); - ppc_ldptr (code, ppc_r0, ins->inst_offset, ppc_r29); + ppc_ldptr (code, ppc_r12, ins->inst_offset, ppc_r29); } else { - ppc_ldptr (code, ppc_r0, ins->inst_offset, ins->sreg1); + ppc_ldptr (code, ppc_r12, ins->inst_offset, ins->sreg1); } - ppc_mtlr (code, ppc_r0); + ppc_mtlr (code, ppc_r12); ppc_blrl (code); /* FIXME: this should be handled somewhere else in the new jit */ code = emit_move_return_value (cfg, ins, code); @@ -5142,8 +5145,8 @@ mono_arch_emit_prolog (MonoCompile *cfg) int soffset = 0; int cur_reg; int size = 0; - g_assert (ppc_is_imm16 (inst->inst_offset)); - g_assert (ppc_is_imm16 (inst->inst_offset + ainfo->vtregs * sizeof (target_mgreg_t))); + g_assert (ppc_is_imm32 (inst->inst_offset)); + g_assert (ppc_is_imm32 (inst->inst_offset + ainfo->vtregs * sizeof (target_mgreg_t))); /* FIXME: what if there is no class? */ if (sig->pinvoke && !sig->marshalling_disabled && mono_class_from_mono_type_internal (inst->inst_vtype)) size = mono_class_native_size (mono_class_from_mono_type_internal (inst->inst_vtype), NULL); @@ -5171,21 +5174,39 @@ mono_arch_emit_prolog (MonoCompile *cfg) (sizeof (target_mgreg_t) - ainfo->bytes) * 8); ppc_stptr (code, ppc_r0, doffset, inst->inst_basereg); #else - if (mono_class_native_size (inst->klass, NULL) == 1) { - ppc_stb (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else if (mono_class_native_size (inst->klass, NULL) == 2) { - ppc_sth (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else if (mono_class_native_size (inst->klass, NULL) == 4) { // WDS -- maybe <=4? - ppc_stw (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); - } else { - ppc_stptr (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); // WDS -- Better way? + if (ppc_is_imm16 (inst->inst_offset)) { + if (mono_class_native_size (inst->klass, NULL) == 1) { + ppc_stb (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else if (mono_class_native_size (inst->klass, NULL) == 2) { + ppc_sth (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else if (mono_class_native_size (inst->klass, NULL) == 4) { // WDS -- maybe <=4? + ppc_stw (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); + } else { + ppc_stptr (code, ainfo->reg + cur_reg, doffset, inst->inst_basereg); // WDS -- Better way? + } + } + else if (ppc_is_imm32 (inst->inst_offset)) { + ppc_addis (code, ppc_r12, inst->inst_basereg, ppc_ha(doffset)); + ppc_stptr (code, ainfo->reg + cur_reg, doffset, ppc_r12); + } + else { + g_assert_not_reached(); } #endif } else #endif { - ppc_stptr (code, ainfo->reg + cur_reg, doffset, - inst->inst_basereg); + if (ppc_is_imm16 (inst->inst_offset)) { + ppc_stptr (code, ainfo->reg + cur_reg, doffset, + inst->inst_basereg); + } + else if (ppc_is_imm32 (inst->inst_offset)) { + ppc_addis (code, ppc_r12, inst->inst_basereg, ppc_ha(doffset)); + ppc_stptr (code, ainfo->reg + cur_reg, doffset, ppc_r12); + } + else { + g_assert_not_reached(); + } } } soffset += sizeof (target_mgreg_t); @@ -5538,6 +5559,14 @@ mono_arch_emit_exceptions (MonoCompile *cfg) } set_code_cursor (cfg, code); + +#ifdef THUNK_ADDR_ALIGNMENT + /* We need to align thunks_offset to 8 byte boundary, hence allocating first 8 bytes + for padding purpose */ + if (cfg->thunk_area != 0) { + cfg->thunk_area += THUNK_ADDR_ALIGNMENT; + } +#endif } #endif diff --git a/src/mono/mono/mini/mini-ppc.h b/src/mono/mono/mini/mini-ppc.h index 0b962aac233d24..e872c4b99c5387 100644 --- a/src/mono/mono/mini/mini-ppc.h +++ b/src/mono/mono/mini/mini-ppc.h @@ -35,6 +35,7 @@ #ifdef TARGET_POWERPC64 #if !defined(PPC_USES_FUNCTION_DESCRIPTOR) #define THUNK_SIZE 8 +#define THUNK_ADDR_ALIGNMENT 8 #define GET_MEMORY_SLOT_THUNK_ADDRESS(c) \ ((guint64)(((c)) [0] & 0x0000ffff) << 48) \ + ((guint64)(((c)) [1] & 0x0000ffff) << 32) \ diff --git a/src/mono/mono/mini/mini-x86.c b/src/mono/mono/mini/mini-x86.c index 6680b08f72d287..1006eabf989e54 100644 --- a/src/mono/mono/mini/mini-x86.c +++ b/src/mono/mono/mini/mini-x86.c @@ -3192,11 +3192,8 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) break; } case OP_CHECK_THIS: - /* ensure ins->sreg1 is not NULL - * note that cmp DWORD PTR [eax], eax is one byte shorter than - * cmp DWORD PTR [eax], 0 - */ - x86_alu_membase_reg (code, X86_CMP, ins->sreg1, 0, ins->sreg1); + /* ensure ins->sreg1 is not NULL */ + x86_alu_membase8_reg (code, X86_CMP, ins->sreg1, 0, ins->sreg1); break; case OP_ARGLIST: { int hreg = ins->sreg1 == X86_EAX? X86_ECX: X86_EAX; diff --git a/src/mono/mono/mini/simd-intrinsics.c b/src/mono/mono/mini/simd-intrinsics.c index 4ce3b622a47679..376f2b69386ceb 100644 --- a/src/mono/mono/mini/simd-intrinsics.c +++ b/src/mono/mono/mini/simd-intrinsics.c @@ -718,6 +718,13 @@ emit_hardware_intrinsics ( if (!info) goto support_probe_complete; id = info->id; + +#ifdef TARGET_ARM64 + if (!(cfg->compile_aot && cfg->full_aot && !cfg->interp) && !intrin_group->jit_supported) { + goto support_probe_complete; + } +#endif + // Hardware intrinsics are LLVM-only. if (!COMPILE_LLVM (cfg) && !intrin_group->jit_supported) goto support_probe_complete; @@ -986,7 +993,7 @@ is_element_type_primitive (MonoType *vector_type) static MonoInst* emit_sri_vector (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) -{ +{ if (!COMPILE_LLVM (cfg)) return NULL; diff --git a/src/mono/mono/sgen/sgen-marksweep.c b/src/mono/mono/sgen/sgen-marksweep.c index 49840611b5b54d..be5da41863dfc2 100644 --- a/src/mono/mono/sgen/sgen-marksweep.c +++ b/src/mono/mono/sgen/sgen-marksweep.c @@ -2861,7 +2861,7 @@ sgen_marksweep_init_internal (SgenMajorCollector *collector, gboolean is_concurr sgen_register_fixed_internal_mem_type (INTERNAL_MEM_MS_BLOCK_INFO, SIZEOF_MS_BLOCK_INFO); - if (mono_cpu_count () <= 1) + if (mono_cpu_limit () <= 1) is_parallel = FALSE; num_block_obj_sizes = ms_calculate_block_obj_sizes (MS_BLOCK_OBJ_SIZE_FACTOR, NULL); @@ -3027,7 +3027,7 @@ sgen_marksweep_init_internal (SgenMajorCollector *collector, gboolean is_concurr #ifndef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC if (is_concurrent && is_parallel) - sgen_workers_create_context (GENERATION_OLD, mono_cpu_count ()); + sgen_workers_create_context (GENERATION_OLD, mono_cpu_limit ()); else if (is_concurrent) sgen_workers_create_context (GENERATION_OLD, 1); diff --git a/src/mono/mono/sgen/sgen-simple-nursery.c b/src/mono/mono/sgen/sgen-simple-nursery.c index 18a771d0abafce..8a63d8f66942e5 100644 --- a/src/mono/mono/sgen/sgen-simple-nursery.c +++ b/src/mono/mono/sgen/sgen-simple-nursery.c @@ -148,7 +148,7 @@ fill_parallel_with_concurrent_major_ops (SgenObjectOperations *ops) void sgen_simple_nursery_init (SgenMinorCollector *collector, gboolean parallel) { - if (mono_cpu_count () <= 1) + if (mono_cpu_limit () <= 1) parallel = FALSE; #ifdef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC diff --git a/src/mono/mono/utils/CMakeLists.txt b/src/mono/mono/utils/CMakeLists.txt index 7ceb82e8eed230..4d552b6e033dd9 100644 --- a/src/mono/mono/utils/CMakeLists.txt +++ b/src/mono/mono/utils/CMakeLists.txt @@ -32,6 +32,7 @@ set(utils_common_sources mono-sha1.c mono-logger.c mono-logger-internals.h + mono-cgroup.c mono-codeman.c mono-counters.c mono-compiler.h diff --git a/src/mono/mono/utils/memfuncs.c b/src/mono/mono/utils/memfuncs.c index 4fc78079626abc..885c21ae3d8fb9 100644 --- a/src/mono/mono/utils/memfuncs.c +++ b/src/mono/mono/utils/memfuncs.c @@ -27,6 +27,7 @@ #include #include #include +#include #if defined (__APPLE__) #include @@ -69,6 +70,7 @@ __d [__i] = NULL; \ } while (0) +#define MINMEMSZ 209715200 /* Minimum restricted memory size */ /** * mono_gc_bzero_aligned: @@ -273,24 +275,59 @@ mono_determine_physical_ram_size (void) return (guint64)value; #elif defined (HAVE_SYSCONF) - gint64 page_size = -1, num_pages = -1; + guint64 page_size = 0, num_pages = 0, memsize; /* sysconf works on most *NIX operating systems, if your system doesn't have it or if it * reports invalid values, please add your OS specific code below. */ #ifdef _SC_PAGESIZE - page_size = (gint64)sysconf (_SC_PAGESIZE); + page_size = (guint64)sysconf (_SC_PAGESIZE); #endif #ifdef _SC_PHYS_PAGES - num_pages = (gint64)sysconf (_SC_PHYS_PAGES); + num_pages = (guint64)sysconf (_SC_PHYS_PAGES); #endif - if (page_size == -1 || num_pages == -1) { + if (!page_size || !num_pages) { g_warning ("Your operating system's sysconf (3) function doesn't correctly report physical memory size!"); return _DEFAULT_MEM_SIZE; } - return (guint64)page_size * (guint64)num_pages; +#if defined(_SC_AVPHYS_PAGES) + memsize = sysconf(_SC_AVPHYS_PAGES) * page_size; +#else + memsize = page_size * num_pages; /* Calculate physical memory size */ +#endif + +#if HAVE_CGROUP_SUPPORT + gint64 restricted_limit = mono_get_restricted_memory_limit(); /* Check for any cgroup limit */ + if (restricted_limit != 0) { + gchar *heapHardLimit = getenv("DOTNET_GCHeapHardLimit"); /* See if user has set a limit */ + if (heapHardLimit == NULL) + heapHardLimit = getenv("COMPlus_GCHeapHardLimit"); /* Check old envvar name */ + errno = 0; + if (heapHardLimit != NULL) { + guint64 gcLimit = strtoull(heapHardLimit, NULL, 16); + if ((errno == 0) && (gcLimit != 0)) + restricted_limit = (restricted_limit < gcLimit ? restricted_limit : (gint64) gcLimit); + } else { + gchar *heapHardLimitPct = getenv("DOTNET_GCHeapHardLimitPercent"); /* User % limit? */ + if (heapHardLimitPct == NULL) + heapHardLimitPct = getenv("COMPlus_GCHeapHardLimitPercent"); /* Check old envvar name */ + if (heapHardLimitPct != NULL) { + int gcLimit = strtoll(heapHardLimitPct, NULL, 16); + if ((gcLimit > 0) && (gcLimit <= 100)) + restricted_limit = (gcLimit * restricted_limit) / 100; + else + restricted_limit = (3 * restricted_limit) / 4; /* Use 75% limit of container */ + } else { + restricted_limit = (3 * restricted_limit) / 4; /* Use 75% limit of container */ + } + } + return (restricted_limit < MINMEMSZ ? MINMEMSZ : /* Use at least 20MB */ + (restricted_limit < memsize ? restricted_limit : memsize)); + } +#endif + return memsize; #else return _DEFAULT_MEM_SIZE; #endif @@ -343,25 +380,28 @@ mono_determine_physical_ram_available_size (void) host_page_size (host, &page_size); return (guint64) vmstat.free_count * page_size; +#elif HAVE_CGROUP_SUPPORT + return (mono_get_memory_avail()); + #elif defined (HAVE_SYSCONF) - gint64 page_size = -1, num_pages = -1; + guint64 page_size = 0, num_pages = 0; /* sysconf works on most *NIX operating systems, if your system doesn't have it or if it * reports invalid values, please add your OS specific code below. */ #ifdef _SC_PAGESIZE - page_size = (gint64)sysconf (_SC_PAGESIZE); + page_size = (guint64)sysconf (_SC_PAGESIZE); #endif #ifdef _SC_AVPHYS_PAGES - num_pages = (gint64)sysconf (_SC_AVPHYS_PAGES); + num_pages = (guint64)sysconf (_SC_AVPHYS_PAGES); #endif - if (page_size == -1 || num_pages == -1) { + if (!page_size || !num_pages) { g_warning ("Your operating system's sysconf (3) function doesn't correctly report physical memory size!"); return _DEFAULT_MEM_SIZE; } - return (guint64)page_size * (guint64)num_pages; + return page_size * num_pages; #else return _DEFAULT_MEM_SIZE; #endif diff --git a/src/mono/mono/utils/memfuncs.h b/src/mono/mono/utils/memfuncs.h index 904b28c96562c4..c8b934e3f7aacb 100644 --- a/src/mono/mono/utils/memfuncs.h +++ b/src/mono/mono/utils/memfuncs.h @@ -24,5 +24,10 @@ MONO_COMPONENT_API void mono_gc_memmove_atomic (void *dest, const void *src, siz void mono_gc_memmove_aligned (void *dest, const void *src, size_t size); guint64 mono_determine_physical_ram_size (void); guint64 mono_determine_physical_ram_available_size (void); +#if HAVE_CGROUP_SUPPORT +size_t mono_get_restricted_memory_limit(void); +gboolean mono_get_memory_used(size_t *); +size_t mono_get_memory_avail(void); +#endif #endif diff --git a/src/mono/mono/utils/mono-cgroup.c b/src/mono/mono/utils/mono-cgroup.c new file mode 100644 index 00000000000000..337abbe38b2350 --- /dev/null +++ b/src/mono/mono/utils/mono-cgroup.c @@ -0,0 +1,972 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/*++ + +Module Name: + + mono-cgroup.c + +Abstract: + Read the memory limit for the current process + + Adapted from runtime src/coreclr/gc/unix/cgroup.cpp + - commit 28ec20194010c2a3d06f2217998cfcb8e8b8fb5e +--*/ +#ifdef __FreeBSD__ +#define _WITH_GETLINE +#endif + +#include +#include + +#if HAVE_CGROUP_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#include +#else +#include +#endif +#include +#include + +#include + +#ifndef SIZE_T_MAX +# define SIZE_T_MAX (~(size_t)0) +#endif + +#define CGROUP2_SUPER_MAGIC 0x63677270 +#define TMPFS_MAGIC 0x01021994 + +#define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" +#define PROC_CGROUP_FILENAME "/proc/self/cgroup" +#define PROC_STATM_FILENAME "/proc/self/statm" +#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" +#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" +#define CGROUP_MEMORY_STAT_FILENAME "/memory.stat" +#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" +#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" +#define CGROUP1_MEMORY_STAT_INACTIVE_FIELD "total_inactive_file " +#define CGROUP2_MEMORY_STAT_INACTIVE_FIELD "inactive_file " +#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" +#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us" +#define CGROUP2_CPU_MAX_FILENAME "/cpu.max" + +static void initialize(void); +static gboolean readMemoryValueFromFile(const char *, size_t *); +static gboolean getPhysicalMemoryLimit(size_t *); +static gboolean getPhysicalMemoryUsage(size_t *); +static int findCGroupVersion(void); +static gboolean isCGroup1MemorySubsystem(const char *); +static gboolean isCGroup1CpuSubsystem(const char *); +static char *findCGroupPath(gboolean (*is_subsystem)(const char *)); +static void findHierarchyMount(gboolean (*is_subsystem)(const char *), char **, char **); +static char *findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *)); +static gboolean getCGroupMemoryLimit(size_t *, const char *); +static gboolean getCGroupMemoryUsage(size_t *, const char *, const char *); +static size_t getPhysicalMemoryTotal(size_t); +static long long readCpuCGroupValue(const char *); +static void computeCpuLimit(long long, long long, guint32 *); + +size_t mono_get_restricted_memory_limit(void); +gboolean mono_get_memory_used(size_t *); +size_t mono_get_memory_avail(void); +gboolean mono_get_cpu_limit(guint *); +static gboolean readLongLongValueFromFile(const char *, long long *); + +// the cgroup version number or 0 to indicate cgroups are not found or not enabled +static int s_cgroup_version = -1; + +static char *s_memory_cgroup_path = NULL; +static char *s_cpu_cgroup_path = NULL; + +static long pageSize; + +/** + * @brief Initialize variables used by the calculation routines. + * + */ +static void +initialize() +{ + s_cgroup_version = findCGroupVersion (); + s_memory_cgroup_path = findCGroupPath (s_cgroup_version == 1 ? &isCGroup1MemorySubsystem : NULL); + s_cpu_cgroup_path = findCGroupPath (s_cgroup_version == 1 ? &isCGroup1CpuSubsystem : NULL); + + if (s_cgroup_version == 0) + return; + + pageSize = sysconf (_SC_PAGE_SIZE); +} + +/** + * + * @brief Read a value from a specified /sys/fs/cgroup/memory file + * + * @param[in] filename - name of file containing value + * @param[out] val - pointer to the result area + * @returns True or False depending if value was found + * + */ +static gboolean +readMemoryValueFromFile(const char* filename, size_t* val) +{ + gboolean result = FALSE; + char *line = NULL; + size_t lineLen = 0; + char *endptr = NULL; + FILE *file = NULL; + + if (val != NULL) { + file = fopen (filename, "r"); + if (file != NULL) { + if (getline (&line, &lineLen, file) != -1) { + errno = 0; + *val = strtoull (line, &endptr, 0); + result = TRUE; + } + } + } + + if (file) + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Interrogate the cgroup memory values to determine if there's + * a limit on physical memory. + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a limit was found + * + */ +static gboolean +getPhysicalMemoryLimit(size_t *val) +{ + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroupMemoryLimit (val, CGROUP1_MEMORY_LIMIT_FILENAME); + else if (s_cgroup_version == 2) + return getCGroupMemoryLimit (val, CGROUP2_MEMORY_LIMIT_FILENAME); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} + +/** + * + * @brief Interrogate the cgroup memory values to determine how much + * memory is in use. + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a usage value was found + * + */ +static gboolean +getPhysicalMemoryUsage(size_t *val) +{ + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroupMemoryUsage (val, CGROUP1_MEMORY_USAGE_FILENAME, CGROUP1_MEMORY_STAT_INACTIVE_FIELD); + else if (s_cgroup_version == 2) + return getCGroupMemoryUsage (val, CGROUP2_MEMORY_USAGE_FILENAME, CGROUP2_MEMORY_STAT_INACTIVE_FIELD); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} + +/** + * + * @brief Inspect the /sys/fs/cgroup hierachy to determine what version of + * group we are using + * + * @returns cgroup version + * + */ +static int +findCGroupVersion() +{ + // It is possible to have both cgroup v1 and v2 enabled on a system. + // Most non-bleeding-edge Linux distributions fall in this group. We + // look at the file system type of /sys/fs/cgroup to determine which + // one is the default. For more details, see: + // https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups- + // We dont care about the difference between the "legacy" and "hybrid" + // modes because both of those involve cgroup v1 controllers managing + // resources. + + + struct statfs stats; + int result = statfs ("/sys/fs/cgroup", &stats); + if (result != 0) + return 0; + + switch (stats.f_type) { + case TMPFS_MAGIC: return 1; + case CGROUP2_SUPER_MAGIC: return 2; + default: return 0; + } +} + +/** + * + * @brief Check if we've found the memory component of /sys/fs/cgroup + * + * @param[in] strTok - Token for comparison + * @returns True if token matches "memory" + * + */ +static gboolean +isCGroup1MemorySubsystem(const char *strTok) +{ + return strcmp ("memory", strTok) == 0; +} + +/** + * + * @brief Check if we've found the CPU component of /sys/fs/cgroup + * + * @param[in] strTok - Token for comparison + * @returns True if token matches "cpu" + * + */ +static gboolean +isCGroup1CpuSubsystem(const char *strTok) +{ + return strcmp ("cpu", strTok) == 0; +} + +/** + * + * @brief Navigate the /sys/fs/cgroup to try and find the correct cgroup path + * + * @param[in] is_subsystem - Function used to compare tokens + * @returns Path to cgroup + * + */ +static char * +findCGroupPath(gboolean (*is_subsystem)(const char *)) +{ + char *cgroup_path = NULL; + char *hierarchy_mount = NULL; + char *hierarchy_root = NULL; + char *cgroup_path_relative_to_mount = NULL; + size_t common_path_prefix_len; + + findHierarchyMount (is_subsystem, &hierarchy_mount, &hierarchy_root); + if (hierarchy_mount != NULL && hierarchy_root != NULL) { + + cgroup_path_relative_to_mount = findCGroupPathForSubsystem (is_subsystem); + if (cgroup_path_relative_to_mount != NULL) { + + cgroup_path = (char*)malloc (strlen (hierarchy_mount) + strlen (cgroup_path_relative_to_mount) + 1); + if (cgroup_path != NULL) { + + strcpy (cgroup_path, hierarchy_mount); + // For a host cgroup, we need to append the relative path. + // The root and cgroup path can share a common prefix of the path that should not be appended. + // Example 1 (docker): + // hierarchy_mount: /sys/fs/cgroup/cpu + // hierarchy_root: /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578 + // cgroup_path_relative_to_mount: /docker/87ee2de57e51bc75175a4d2e81b71d162811b179d549d6601ed70b58cad83578/my_named_cgroup + // append do the cgroup_path: /my_named_cgroup + // final cgroup_path: /sys/fs/cgroup/cpu/my_named_cgroup + // + // Example 2 (out of docker) + // hierarchy_mount: /sys/fs/cgroup/cpu + // hierarchy_root: / + // cgroup_path_relative_to_mount: /my_named_cgroup + // append do the cgroup_path: /my_named_cgroup + // final cgroup_path: /sys/fs/cgroup/cpu/my_named_cgroup + common_path_prefix_len = strlen (hierarchy_root); + if ((common_path_prefix_len == 1) || + (strncmp (hierarchy_root, cgroup_path_relative_to_mount, common_path_prefix_len) != 0)) + common_path_prefix_len = 0; + + g_assert((cgroup_path_relative_to_mount[common_path_prefix_len] == '/') || + (cgroup_path_relative_to_mount[common_path_prefix_len] == '\0')); + + strcat (cgroup_path, cgroup_path_relative_to_mount + common_path_prefix_len); + } + } + } + + free (hierarchy_mount); + free (hierarchy_root); + free (cgroup_path_relative_to_mount); + return cgroup_path; +} + +/** + * + * @brief Check the /proc filesystem to determine the root and mount + * path of /sys/fs/cgroup data + * + * @param[in] is_subsystem - Comparison function + * @param[out] pmountpath - + * @param[out] pmountroot - + * + */ +static void +findHierarchyMount(gboolean (*is_subsystem)(const char *), char** pmountpath, char** pmountroot) +{ + char *line = NULL; + size_t lineLen = 0, maxLineLen = 0; + char *filesystemType = NULL; + char *options = NULL; + char *mountpath = NULL; + char *mountroot = NULL; + + FILE *mountinfofile = fopen (PROC_MOUNTINFO_FILENAME, "r"); + if (mountinfofile == NULL) + goto done; + + while (getline (&line, &lineLen, mountinfofile) != -1) { + if (filesystemType == NULL || lineLen > maxLineLen) { + free (filesystemType); + filesystemType = NULL; + free (options); + options = NULL; + filesystemType = (char*)malloc (lineLen+1); + if (filesystemType == NULL) + goto done; + options = (char*)malloc (lineLen+1); + if (options == NULL) + goto done; + maxLineLen = lineLen; + } + + char *separatorChar = strstr (line, " - "); + + // See man page of proc to get format for /proc/self/mountinfo file + int sscanfRet = sscanf (separatorChar, + " - %s %*s %s", + filesystemType, + options); + if (sscanfRet != 2) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, "Failed to parse mount info file contents with sscanf."); + goto done; + } + + if (strncmp(filesystemType, "cgroup", 6) == 0) { + gboolean isSubsystemMatch = is_subsystem == NULL; + if (!isSubsystemMatch) { + char *context = NULL; + char *strTok = strtok_r (options, ",", &context); + while (!isSubsystemMatch && strTok != NULL) + { + isSubsystemMatch = is_subsystem (strTok); + strTok = strtok_r (NULL, ",", &context); + } + } + if (isSubsystemMatch) { + mountpath = (char*)malloc (lineLen+1); + if (mountpath == NULL) + goto done; + mountroot = (char*)malloc (lineLen+1); + if (mountroot == NULL) + goto done; + + sscanfRet = sscanf (line, + "%*s %*s %*s %s %s ", + mountroot, + mountpath); + if (sscanfRet != 2) + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Failed to parse mount info file contents with sscanf."); + + // assign the output arguments and clear the locals so we don't free them. + *pmountpath = mountpath; + *pmountroot = mountroot; + mountpath = mountroot = NULL; + } + } + } +done: + free (mountpath); + free (mountroot); + free (filesystemType); + free (options); + free (line); + if (mountinfofile) + fclose (mountinfofile); +} + +/** + * + * @brief + * Check the /proc filesystem to determine the root and mount path + * of /sys/fs/cgroup data + * + * @param[in] is_subsystem - Comparison function + * @returns cgroup path for the memory subsystem + * + */ +static char * +findCGroupPathForSubsystem(gboolean (*is_subsystem)(const char *)) +{ + char *line = NULL; + size_t lineLen = 0; + size_t maxLineLen = 0; + char *subsystem_list = NULL; + char *cgroup_path = NULL; + gboolean result = FALSE; + + FILE *cgroupfile = fopen (PROC_CGROUP_FILENAME, "r"); + if (cgroupfile == NULL) + goto done; + + while (!result && getline (&line, &lineLen, cgroupfile) != -1) { + if (subsystem_list == NULL || lineLen > maxLineLen) { + free (subsystem_list); + subsystem_list = NULL; + free (cgroup_path); + cgroup_path = NULL; + subsystem_list = (char*)malloc (lineLen+1); + if (subsystem_list == NULL) + goto done; + cgroup_path = (char*)malloc (lineLen+1); + if (cgroup_path == NULL) + goto done; + maxLineLen = lineLen; + } + + if (s_cgroup_version == 1) { + // See man page of proc to get format for /proc/self/cgroup file + int sscanfRet = sscanf (line, + "%*[^:]:%[^:]:%s", + subsystem_list, + cgroup_path); + if (sscanfRet != 2) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Failed to parse cgroup info file contents with sscanf."); + goto done; + } + + char* context = NULL; + char* strTok = strtok_r (subsystem_list, ",", &context); + while (strTok != NULL) { + if (is_subsystem (strTok)) { + result = TRUE; + break; + } + strTok = strtok_r (NULL, ",", &context); + } + } else if (s_cgroup_version == 2) { + // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt + // Look for a "0::/some/path" + int sscanfRet = sscanf (line, + "0::%s", + cgroup_path); + if (sscanfRet == 1) + { + result = TRUE; + } + } else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version in mountinfo."); + goto done; + } + } +done: + free (subsystem_list); + if (!result) { + free (cgroup_path); + cgroup_path = NULL; + } + free (line); + if (cgroupfile) + fclose (cgroupfile); + return cgroup_path; +} + +/** + * + * @brief Extract memory limit from specified /sys/fs/cgroup/memory file + * + * @param[out] val - Memory limit + * @param[in] filename - name of file from which to extract limit + * @returns True if value found + * + */ +static gboolean +getCGroupMemoryLimit(size_t *val, const char *filename) +{ + if (s_memory_cgroup_path == NULL) + return FALSE; + + char* mem_limit_filename = NULL; + if (asprintf (&mem_limit_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return FALSE; + + gboolean result = readMemoryValueFromFile (mem_limit_filename, val); + free (mem_limit_filename); + return result; +} + +/** + * + * @brief Extract memory usage from /sys/fs/cgroup/memory.stat file + * + * @param[out] val - Memory limit + * @returns True if value found + * + */ +static gboolean +getCGroupMemoryUsage(size_t *val, const char *filename, const char *inactiveFileFieldName) +{ + /* + * Use the same way to calculate memory load as popular container tools (Docker, Kubernetes, Containerd etc.) + * For cgroup v1: value of 'memory.usage_in_bytes' minus 'total_inactive_file' value of 'memory.stat' + * For cgroup v2: value of 'memory.current' minus 'inactive_file' value of 'memory.stat' + */ + + char *mem_usage_filename = NULL; + if (asprintf (&mem_usage_filename, "%s%s", s_memory_cgroup_path, filename) < 0) + return FALSE; + + size_t temp = 0; + size_t usage = 0; + + gboolean result = readMemoryValueFromFile (mem_usage_filename, &temp); + if (result) { + if (temp > SIZE_T_MAX) + usage = SIZE_T_MAX; + else + usage = temp; + } + + free (mem_usage_filename); + + if (!result) + return result; + + if (s_memory_cgroup_path == NULL) + return FALSE; + + char *stat_filename = NULL; + if (asprintf (&stat_filename, "%s%s", s_memory_cgroup_path, CGROUP_MEMORY_STAT_FILENAME) < 0) + return FALSE; + + FILE *stat_file = fopen (stat_filename, "r"); + free (stat_filename); + if (stat_file == NULL) + return FALSE; + + char *line = NULL; + size_t lineLen = 0; + gboolean foundInactiveFileValue = FALSE; + char *endptr; + + size_t inactiveFileFieldNameLength = strlen (inactiveFileFieldName); + + while (getline (&line, &lineLen, stat_file) != -1) { + if (strncmp (line, inactiveFileFieldName, inactiveFileFieldNameLength) == 0) { + errno = 0; + const char *startptr = line + inactiveFileFieldNameLength; + size_t inactiveFileValue = strtoll (startptr, &endptr, 10); + if (endptr != startptr && errno == 0) { + foundInactiveFileValue = TRUE; + *val = usage - inactiveFileValue; + } + break; + } + } + + fclose (stat_file); + free (line); + + return foundInactiveFileValue; +} + +/** + * + * @brief Determine if there are any limits on memory and return the value + * + * @returns Physical memory limit + * + * Zero represents no limit. + */ +size_t +mono_get_restricted_memory_limit() +{ + size_t physical_memory_limit = 0; + + if (s_cgroup_version == -1) + initialize(); + + if (s_cgroup_version == 0) + return 0; + + if (!getPhysicalMemoryLimit (&physical_memory_limit)) + return 0; + + // If there's no memory limit specified on the container this + // actually returns 0x7FFFFFFFFFFFF000 (2^63-1 rounded down to + // 4k which is a common page size). So we know we are not + // running in a memory restricted environment. + if (physical_memory_limit > 0x7FFFFFFF00000000) + return 0; + + return (getPhysicalMemoryTotal (physical_memory_limit)); +} + +/** + * + * @brief Check the input limit against any system limits or actual memory on system + * + * @param[in] physical_memory_limit - The max memory on the system + * @returns Physical memory total + * + */ +static size_t +getPhysicalMemoryTotal(size_t physical_memory_limit) +{ + struct rlimit curr_rlimit; + size_t rlimit_soft_limit = (size_t)RLIM_INFINITY; + if (getrlimit (RLIMIT_AS, &curr_rlimit) == 0) + rlimit_soft_limit = curr_rlimit.rlim_cur; + physical_memory_limit = (physical_memory_limit < rlimit_soft_limit) ? + physical_memory_limit : rlimit_soft_limit; + + // Ensure that limit is not greater than real memory size + long pages = sysconf (_SC_PHYS_PAGES); + if (pages != -1) { + if (pageSize != -1) { + physical_memory_limit = (physical_memory_limit < (size_t)pages * pageSize) ? + physical_memory_limit : (size_t)pages * pageSize; + } + } + + if (physical_memory_limit > ULONG_MAX) { + // It is observed in practice when the memory is unrestricted, Linux control + // group returns a physical limit that is bigger than the address space + return ULONG_MAX; + } else + return physical_memory_limit; +} + +/** + * + * @brief Determine the amount of memory in use + * + * @param[out] val - pointer to the memory usage value + * @returns True if we are able to determine usage + * + */ +gboolean +mono_get_memory_used(size_t *val) +{ + gboolean result = FALSE; + size_t linelen; + char *line = NULL; + + if (val == NULL) + return FALSE; + + // Linux uses cgroup usage to trigger oom kills. + if (getPhysicalMemoryUsage (val)) + return TRUE; + + // process resident set size. + FILE* file = fopen (PROC_STATM_FILENAME, "r"); + if (file != NULL && getline (&line, &linelen, file) != -1) { + char* context = NULL; + char* strTok = strtok_r (line, " ", &context); + strTok = strtok_r (NULL, " ", &context); + + errno = 0; + *val = strtoull (strTok, NULL, 0); + if (errno == 0) { + if (pageSize != -1) { + *val = *val * pageSize; + result = TRUE; + } + } + } + + if (file) + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Determine the amount of memory available by examininig any + * limits and checking what memory is in use. + * + * @returns Amount of memory available + * + */ +size_t +mono_get_memory_avail() +{ + size_t max, used, avail, sysAvail; +#ifdef _SC_AVPHYS_PAGES // If this isn't defined then we don't get called + + max = mono_get_restricted_memory_limit (); + + if (max == 0) + max = getPhysicalMemoryTotal (ULONG_MAX); + + if (mono_get_memory_used (&used)) + avail = max - used; + else + avail = max; + + sysAvail = sysconf (_SC_AVPHYS_PAGES) * pageSize; + return (avail < sysAvail ? avail : sysAvail); +#else + return (0); +#endif +} + +/** + * + * @brief Return any limits on CPU use + * + * @returns Number of CPU usable + * + */ +static gboolean +getCGroup1CpuLimit(guint32 *val) +{ + long long quota; + long long period; + + quota = readCpuCGroupValue (CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return FALSE; + + period = readCpuCGroupValue (CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return FALSE; + + computeCpuLimit (period, quota, val); + + return TRUE; +} + +/** + * + * @brief Return any limits on CPU use + * + * @returns Number of CPU usable + * + */ +static gboolean +getCGroup2CpuLimit(guint32 *val) +{ + char *filename = NULL; + FILE *file = NULL; + char *endptr = NULL; + char *max_quota_string = NULL; + char *period_string = NULL; + char *context = NULL; + char *line = NULL; + size_t lineLen = 0; + + long long quota = 0; + long long period = 0; + + + gboolean result = FALSE; + + if (s_cpu_cgroup_path == NULL) + return FALSE; + + if (asprintf (&filename, "%s%s", s_cpu_cgroup_path, CGROUP2_CPU_MAX_FILENAME) < 0) + return FALSE; + + file = fopen (filename, "r"); + if (file == NULL) + goto done; + + if (getline (&line, &lineLen, file) == -1) + goto done; + + // The expected format is: + // $MAX $PERIOD + // Where "$MAX" may be the string literal "max" + + max_quota_string = strtok_r (line, " ", &context); + if (max_quota_string == NULL) + { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + period_string = strtok_r (NULL, " ", &context); + if (period_string == NULL) + { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents."); + goto done; + } + + // "max" means no cpu limit + if (strcmp ("max", max_quota_string) == 0) + goto done; + + errno = 0; + quota = strtoll (max_quota_string, &endptr, 10); + if (max_quota_string == endptr || errno != 0) + goto done; + + period = strtoll (period_string, &endptr, 10); + if (period_string == endptr || errno != 0) + goto done; + + computeCpuLimit (period, quota, val); + result = TRUE; + +done: + if (file) + fclose (file); + free (filename); + free (line); + + return result; +} + +/** + * + * @brief Compute the CPU limit based on the CGroup data + * + * @param[in] period + * @param[in] quota - Limit found in sysfs + * @param[out] Number of CPU usable + * + */ +static void +computeCpuLimit(long long period, long long quota, uint32_t *val) +{ + // Cannot have less than 1 CPU + if (quota <= period) { + *val = 1; + return; + } + + // Calculate cpu count based on quota and round it up + double cpu_count = (double) quota / period + 0.999999999; + *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; +} + +/** + * + * @brief Read the CGroup CPU data from sysfs + * + * @param[in] subsystemFileName - sysfs File containing data + * @returns CPU CGroup value + * + */ +static long long +readCpuCGroupValue(const char *subsystemFilename) +{ + char *filename = NULL; + gboolean result = FALSE; + long long val = -1; + + if (s_cpu_cgroup_path == NULL) + return -1; + + if (asprintf (&filename, "%s%s", s_cpu_cgroup_path, subsystemFilename) < 0) + return -1; + + result = readLongLongValueFromFile (filename, &val); + free (filename); + if (!result) + return -1; + + return val; +} + +/** + * + * @brief Read a long long value from a file + * + * @param[in] fileName - sysfs File containing data + * @param[out] val - Value read + * @returns Success indicator + * + */ +static gboolean +readLongLongValueFromFile(const char *filename, long long *val) +{ + gboolean result = FALSE; + char *line = NULL; + size_t lineLen = 0; + char *endptr = NULL; + + if (val == NULL) + return FALSE; + + FILE *file = fopen (filename, "r"); + if (file == NULL) + return FALSE; + + if (getline (&line, &lineLen, file) != -1) { + errno = 0; + *val = strtoll (line, &endptr, 10); + if (line != endptr && errno == 0) + result = TRUE; + } + + fclose (file); + free (line); + return result; +} + +/** + * + * @brief Interrogate the cgroup CPU values to determine if there's + * a limit on CPUs + * + * @param[out] val - pointer to the result area + * @returns True or False depending if a limit was found + * + * Interrogate the cgroup CPU values to determine if there's + * a limit on CPUs + */ +gboolean +mono_get_cpu_limit(guint *val) +{ + if (s_cgroup_version == -1) + initialize(); + + if (s_cgroup_version == 0) + return FALSE; + else if (s_cgroup_version == 1) + return getCGroup1CpuLimit ((guint32 *)val); + else if (s_cgroup_version == 2) + return getCGroup2CpuLimit ((guint32 *)val); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_CONFIG, + "Unknown cgroup version."); + return FALSE; + } +} +#else + +MONO_EMPTY_SOURCE_FILE (mono_cgroup); + +#endif diff --git a/src/mono/mono/utils/mono-proclib.c b/src/mono/mono/utils/mono-proclib.c index 40404b44f85e3b..a683e3d90295cc 100644 --- a/src/mono/mono/utils/mono-proclib.c +++ b/src/mono/mono/utils/mono-proclib.c @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef HOST_WIN32 #include @@ -190,3 +191,44 @@ mono_cpu_count (void) return 1; #endif } + +/** + * mono_cpu_limit: + * \returns the number of processors available to this process + */ +int +mono_cpu_limit (void) +{ +#if defined(HOST_WIN32) + return mono_cpu_count (); +#else + static int limit = -1; /* Value will be cached for future calls */ + + /* + * If 1st time through then check if user has mandated a value and use it, + * otherwise we check for any cgroup limit and use the min of actual number + * and that limit + */ + if (limit == -1) { + char *dotnetProcCnt = getenv ("DOTNET_PROCESSOR_COUNT"); + if (dotnetProcCnt != NULL) { + errno = 0; + limit = (int) strtol (dotnetProcCnt, NULL, 0); + if ((errno == 0) && (limit > 0)) /* If it's in range and positive */ + return limit; + } + limit = mono_cpu_count (); +#if HAVE_CGROUP_SUPPORT + int count = 0; + if (mono_get_cpu_limit (&count)) + limit = (limit < count ? limit : count); +#endif + } + + /* + * Just return the cached value + */ + return limit; +#endif +} + diff --git a/src/mono/mono/utils/mono-proclib.h b/src/mono/mono/utils/mono-proclib.h index f14be3b9f3375b..9c72b47b239004 100644 --- a/src/mono/mono/utils/mono-proclib.h +++ b/src/mono/mono/utils/mono-proclib.h @@ -19,5 +19,12 @@ MONO_API int mono_cpu_count (void); +MONO_API +int +mono_cpu_limit (void); + +gboolean +mono_get_cpu_limit(int *limit); + #endif /* __MONO_PROC_LIB_H__ */ diff --git a/src/mono/msbuild/android/build/AndroidApp.targets b/src/mono/msbuild/android/build/AndroidApp.targets index 3d6024763b0f95..6fd26641cd1e10 100644 --- a/src/mono/msbuild/android/build/AndroidApp.targets +++ b/src/mono/msbuild/android/build/AndroidApp.targets @@ -119,13 +119,8 @@ - - - marshal-ilgen - - - - - - marshal-ilgen - - true - - - 7.0.0 - $(PackageVersion) diff --git a/src/mono/nuget/Directory.Build.targets b/src/mono/nuget/Directory.Build.targets index 32e2e4251e792f..c3af51a7f58b05 100644 --- a/src/mono/nuget/Directory.Build.targets +++ b/src/mono/nuget/Directory.Build.targets @@ -1,5 +1,13 @@ + + + + $(ProductVersion) + $(PackageVersion) + + + diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props index 9831506418661e..93712989d412ba 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props +++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/AutoImport.props @@ -1,5 +1,6 @@ - true + net7.0 + true diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in deleted file mode 100644 index a07122acd7b25a..00000000000000 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in +++ /dev/null @@ -1,120 +0,0 @@ - - - ${PackageVersion} - true - - - - - true - $(WasmNativeWorkload) - - - - false - false - - - - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_MonoWorkloadTargetsMobile>true - <_MonoWorkloadRuntimePackPackageVersion>$(RuntimePackInWorkloadVersion) - - - - - **FromWorkload** - - Microsoft.NETCore.App.Runtime.Mono.multithread.**RID** - Microsoft.NETCore.App.Runtime.Mono.perftrace.**RID** - - - - - - - - - - - - - - - - - - - diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj new file mode 100644 index 00000000000000..7ce452fe6229c4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest.pkgproj @@ -0,0 +1,55 @@ + + + + + Internal toolchain package not meant for direct consumption. Please do not reference directly. + + + + + + Microsoft.NET.Workload.Mono.ToolChain.net6.Manifest-$(SdkBandVersion) + + + + $(IntermediateOutputPath)WorkloadManifest.json + $(IntermediateOutputPath)WorkloadManifest.targets + + + + + + + + + + data/localize + + + + + + + + PackageVersion=$(PackageVersion); + + + + <_WorkloadManifestValues Include="WorkloadVersion" Value="$(PackageVersion)" /> + <_WorkloadManifestValues Include="PackageVersionNet6" Value="$(PackageVersionNet6)" /> + <_WorkloadManifestValues Include="EmscriptenVersion" Value="$(MicrosoftNETRuntimeEmscriptenVersion)" /> + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in new file mode 100644 index 00000000000000..716cd56d9a607f --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.json.in @@ -0,0 +1,453 @@ +{ + "version": "${WorkloadVersion}", + "depends-on": { + "Microsoft.NET.Workload.Emscripten.net6": "${EmscriptenVersion}" + }, + "workloads": { + "wasm-tools-net6": { + "description": ".NET WebAssembly build tools for net6.0", + "packs": [ + "Microsoft.NET.Runtime.WebAssembly.Sdk.net6", + "Microsoft.NETCore.App.Runtime.Mono.net6.browser-wasm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.browser-wasm" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6", "microsoft-net-sdk-emscripten-net6" ], + "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android-net6": { + "abstract": true, + "description": "Android Mono Runtime", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x64", + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-android-aot-net6": { + "abstract": true, + "description": "Android Mono AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x86", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm64" + ], + "extends": [ "microsoft-net-runtime-android-net6" ], + "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] + }, + "microsoft-net-runtime-ios-net6": { + "abstract": true, + "description": "iOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x86" + ], + "extends": [ "runtimes-ios-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "runtimes-ios-net6": { + "abstract": true, + "description": "iOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm", + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x86" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-maccatalyst-net6": { + "abstract": true, + "description": "MacCatalyst Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-x64" + ], + "extends": [ "runtimes-maccatalyst-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "runtimes-maccatalyst-net6": { + "abstract": true, + "description": "MacCatalyst Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-macos-net6": { + "abstract": true, + "description": "MacOS CoreCLR and Mono Runtime Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-x64", + "Microsoft.NETCore.App.Runtime.net6.osx-arm64", + "Microsoft.NETCore.App.Runtime.net6.osx-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "osx-arm64", "osx-x64" ] + }, + "microsoft-net-runtime-tvos-net6": { + "abstract": true, + "description": "tvOS Mono Runtime and AOT Workload", + "packs": [ + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvos-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-x64" + ], + "extends": [ "runtimes-tvos-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "runtimes-tvos-net6": { + "abstract": true, + "description": "tvOS Mono Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.Mono.net6.tvos-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-x64" + ], + "extends": [ "microsoft-net-runtime-mono-tooling-net6" ], + "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] + }, + "runtimes-windows-net6": { + "description": "Windows Runtime Packs", + "packs": [ + "Microsoft.NETCore.App.Runtime.net6.win-x64", + "Microsoft.NETCore.App.Runtime.net6.win-x86", + "Microsoft.NETCore.App.Runtime.net6.win-arm", + "Microsoft.NETCore.App.Runtime.net6.win-arm64" + ] + }, + "microsoft-net-runtime-mono-tooling-net6": { + "abstract": true, + "description": "Shared native build tooling for Mono runtime", + "packs": [ + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net6", + "Microsoft.NET.Runtime.MonoTargets.Sdk.net6" + ] + } + }, + "packs": { + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoAOTCompiler.Task" + } + }, + "Microsoft.NET.Runtime.MonoTargets.Sdk.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoTargets.Sdk" + } + }, + "Microsoft.NET.Runtime.WebAssembly.Sdk.net6": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Sdk" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.android-x86": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x86": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x86", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x86", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-x64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.android-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.android-arm64", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.android-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.maccatalyst-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.osx-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.osx-arm64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.osx-x64": { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.osx-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.ios-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.iossimulator-x86" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvos-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvos-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.tvossimulator-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.maccatalyst-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.tvossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.ios-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-arm64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-arm64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-arm64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x64": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x64", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x64" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.iossimulator-x86": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x86", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x86" + } + }, + "Microsoft.NETCore.App.Runtime.AOT.Cross.net6.browser-wasm": { + "kind": "Sdk", + "version": "${PackageVersionNet6}", + "alias-to": { + "win-x64": "Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm", + "linux-x64": "Microsoft.NETCore.App.Runtime.AOT.linux-x64.Cross.browser-wasm", + "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.Mono.net6.browser-wasm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-x64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x64" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-x86" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x86" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-arm" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm" + } + }, + "Microsoft.NETCore.App.Runtime.net6.win-arm64" : { + "kind": "framework", + "version": "${PackageVersionNet6}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm64" + } + } + } +} diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in new file mode 100644 index 00000000000000..6f13276c2e7dd5 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/WorkloadManifest.targets.in @@ -0,0 +1,129 @@ + + + <_RuntimePackInWorkloadVersion6>${PackageVersionNet6} + true + true + + + + + false + + + + + true + $(WasmNativeWorkload) + + + + false + false + + + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_MonoWorkloadTargetsMobile>true + <_MonoWorkloadRuntimePackPackageVersion>$(_RuntimePackInWorkloadVersion6) + + + + + $(_MonoWorkloadRuntimePackPackageVersion) + + + + + + + + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json new file mode 100644 index 00000000000000..63f42875d04eb8 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.cs.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Nástroje pro sestavení .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json new file mode 100644 index 00000000000000..8e47a8ed518fe2 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.de.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly-Buildtools" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json new file mode 100644 index 00000000000000..2b693d54c3fb53 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.en.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly build tools" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json new file mode 100644 index 00000000000000..5244945464f4a3 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.es.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Herramientas de compilación de WebAssembly de .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json new file mode 100644 index 00000000000000..7492e913330dd7 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.fr.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Outils de construction .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json new file mode 100644 index 00000000000000..fbf5627f4c5802 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.it.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Strumenti di compilazione WebAssembly .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json new file mode 100644 index 00000000000000..e9e8b38a9ebb67 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ja.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly ビルド ツール" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json new file mode 100644 index 00000000000000..788574ae8536d4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ko.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 빌드 도구" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json new file mode 100644 index 00000000000000..90a04e1e219650 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pl.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Narzędzia kompilacji zestawu WebAssembly platformy .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json new file mode 100644 index 00000000000000..9869a7e82489bb --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.pt-BR.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Ferramentas de build do .NET WebAssembly" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json new file mode 100644 index 00000000000000..dae1bab0be634c --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.ru.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": "Средства сборки WebAssembly .NET" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json new file mode 100644 index 00000000000000..ec5dbdf0f5ead4 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.tr.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly derleme araçları" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json new file mode 100644 index 00000000000000..849ab454aca627 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hans.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 生成工具" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json new file mode 100644 index 00000000000000..da192170661373 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net6.Manifest/localize/WorkloadManifest.zh-Hant.json @@ -0,0 +1,3 @@ +{ + "workloads/wasm-tools-net6/description": ".NET WebAssembly 組建工具" +} \ No newline at end of file diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj similarity index 96% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj index 940dd5cd4f3bfc..55d801786d53ec 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest.pkgproj @@ -8,7 +8,7 @@ - Microsoft.NET.Workload.Mono.ToolChain.Manifest-$(SdkBandVersion) + Microsoft.NET.Workload.Mono.ToolChain.net7.Manifest-$(SdkBandVersion) diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in similarity index 50% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in index 64d946adca32e7..cbd3cacb453e55 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in @@ -1,25 +1,25 @@ { "version": "${WorkloadVersion}", "depends-on": { - "Microsoft.NET.Workload.Emscripten": "${EmscriptenVersion}" + "Microsoft.NET.Workload.Emscripten.net7": "${EmscriptenVersion}" }, "workloads": { "wasm-tools": { "description": ".NET WebAssembly build tools", "packs": [ - "Microsoft.NET.Runtime.WebAssembly.Sdk", - "Microsoft.NETCore.App.Runtime.Mono.browser-wasm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.browser-wasm" + "Microsoft.NET.Runtime.WebAssembly.Sdk.net7", + "Microsoft.NETCore.App.Runtime.Mono.net7.browser-wasm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.browser-wasm" ], - "extends": [ "microsoft-net-runtime-mono-tooling", "microsoft-net-sdk-emscripten" ], + "extends": [ "microsoft-net-runtime-mono-tooling", "microsoft-net-sdk-emscripten-net7" ], "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] }, "wasm-experimental": { - "description": ".NET WebAssembly experimental", + "description": ".NET WebAssembly experimental tooling", "packs": [ - "Microsoft.NET.Runtime.WebAssembly.Templates", - "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm", - "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" + "Microsoft.NET.Runtime.WebAssembly.Templates.net7", + "Microsoft.NETCore.App.Runtime.Mono.multithread.net7.browser-wasm", + "Microsoft.NETCore.App.Runtime.Mono.perftrace.net7.browser-wasm" ], "extends": [ "wasm-tools" ], "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] @@ -28,10 +28,10 @@ "abstract": true, "description": "Android Mono Runtime", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.android-arm", - "Microsoft.NETCore.App.Runtime.Mono.android-arm64", - "Microsoft.NETCore.App.Runtime.Mono.android-x64", - "Microsoft.NETCore.App.Runtime.Mono.android-x86" + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x64", + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x86" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] @@ -40,10 +40,10 @@ "abstract": true, "description": "Android Mono AOT Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x86", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64" + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x86", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm64" ], "extends": [ "microsoft-net-runtime-android" ], "platforms": [ "win-x64", "linux-x64", "osx-x64", "osx-arm64" ] @@ -52,11 +52,11 @@ "abstract": true, "description": "iOS Mono Runtime and AOT Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm", - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x86" + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x86" ], "extends": [ "runtimes-ios" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -65,11 +65,11 @@ "abstract": true, "description": "iOS Mono Runtime Packs", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.ios-arm", - "Microsoft.NETCore.App.Runtime.Mono.ios-arm64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64", - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm", + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x64", + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x86" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -78,8 +78,8 @@ "abstract": true, "description": "MacCatalyst Mono Runtime and AOT Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-x64" + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-x64" ], "extends": [ "runtimes-maccatalyst" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -88,8 +88,8 @@ "abstract": true, "description": "MacCatalyst Mono Runtime Packs", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64", - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-x64" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -98,8 +98,8 @@ "abstract": true, "description": "MacOS CoreCLR and Mono Runtime Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.osx-arm64", - "Microsoft.NETCore.App.Runtime.Mono.osx-x64", + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-x64", "Microsoft.NETCore.App.Runtime.osx-arm64", "Microsoft.NETCore.App.Runtime.osx-x64" ], @@ -110,9 +110,9 @@ "abstract": true, "description": "tvOS Mono Runtime and AOT Workload", "packs": [ - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-arm64", - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-x64" + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvos-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-x64" ], "extends": [ "runtimes-tvos" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -121,9 +121,9 @@ "abstract": true, "description": "tvOS Mono Runtime Packs", "packs": [ - "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64", - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64", - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + "Microsoft.NETCore.App.Runtime.Mono.net7.tvos-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-arm64", + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-x64" ], "extends": [ "microsoft-net-runtime-mono-tooling" ], "platforms": [ "win-x64", "osx-arm64", "osx-x64" ] @@ -131,55 +131,79 @@ "runtimes-windows": { "description": "Windows Runtime Packs", "packs": [ - "Microsoft.NETCore.App.Runtime.win-x64", - "Microsoft.NETCore.App.Runtime.win-x86", - "Microsoft.NETCore.App.Runtime.win-arm", - "Microsoft.NETCore.App.Runtime.win-arm64" + "Microsoft.NETCore.App.Runtime.net7.win-x64", + "Microsoft.NETCore.App.Runtime.net7.win-x86", + "Microsoft.NETCore.App.Runtime.net7.win-arm", + "Microsoft.NETCore.App.Runtime.net7.win-arm64" ] }, "microsoft-net-runtime-mono-tooling": { "abstract": true, "description": "Shared native build tooling for Mono runtime", "packs": [ - "Microsoft.NET.Runtime.MonoAOTCompiler.Task", - "Microsoft.NET.Runtime.MonoTargets.Sdk", - ], + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net7", + "Microsoft.NET.Runtime.MonoTargets.Sdk.net7" + ] } }, "packs": { - "Microsoft.NET.Runtime.MonoAOTCompiler.Task": { + "Microsoft.NET.Runtime.MonoAOTCompiler.Task.net7": { "kind": "Sdk", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoAOTCompiler.Task" + } }, - "Microsoft.NET.Runtime.MonoTargets.Sdk": { + "Microsoft.NET.Runtime.MonoTargets.Sdk.net7": { "kind": "Sdk", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.MonoTargets.Sdk" + } }, - "Microsoft.NET.Runtime.WebAssembly.Sdk": { + "Microsoft.NET.Runtime.WebAssembly.Sdk.net7": { "kind": "Sdk", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Sdk" + } }, - "Microsoft.NET.Runtime.WebAssembly.Templates": { + "Microsoft.NET.Runtime.WebAssembly.Templates.net7": { "kind": "template", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NET.Runtime.WebAssembly.Templates" + } }, - "Microsoft.NETCore.App.Runtime.Mono.android-arm": { + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm": { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm" + } }, - "Microsoft.NETCore.App.Runtime.Mono.android-arm64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.android-arm64": { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.android-x64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x64": { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.android-x86": { + "Microsoft.NETCore.App.Runtime.Mono.net7.android-x86": { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.android-x86" + } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x86": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x86": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -189,7 +213,7 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x86" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-x64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-x64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -199,7 +223,7 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-x64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -209,7 +233,7 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.android-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -219,71 +243,113 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.android-arm64" } }, - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-arm64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.maccatalyst-x64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.maccatalyst-x64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.osx-arm64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-arm64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.osx-x64": { + "Microsoft.NETCore.App.Runtime.Mono.net7.osx-x64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-x64" + } }, - "Microsoft.NETCore.App.Runtime.osx-arm64": { + "Microsoft.NETCore.App.Runtime.net7.osx-arm64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.osx-arm64" + } }, - "Microsoft.NETCore.App.Runtime.osx-x64": { + "Microsoft.NETCore.App.Runtime.net7.osx-x64": { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.osx-x64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.ios-arm" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm" + } }, - "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.ios-arm64" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.ios-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-arm64" : { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x64" : { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.iossimulator-x86" : { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.iossimulator-x86" + } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvos-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvos-arm64" } }, - "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.tvos-arm64" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvos-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-arm64" : { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-arm64" + } }, - "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.tvossimulator-x64" : { "kind": "framework", "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.tvossimulator-x64" + } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -291,7 +357,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-arm64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst-x64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.maccatalyst-x64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -299,7 +365,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.maccatalyst-x64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -307,7 +373,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-arm64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator-x64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.tvossimulator-x64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -315,23 +381,23 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.tvossimulator-x64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.ios-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", - "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64", + "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-arm64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-arm64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -339,7 +405,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-arm64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x64": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x64": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -347,7 +413,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x64" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator-x86": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.iossimulator-x86": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -355,7 +421,7 @@ "osx-x64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.iossimulator-x86" } }, - "Microsoft.NETCore.App.Runtime.AOT.Cross.browser-wasm": { + "Microsoft.NETCore.App.Runtime.AOT.Cross.net7.browser-wasm": { "kind": "Sdk", "version": "${PackageVersion}", "alias-to": { @@ -365,33 +431,54 @@ "osx-arm64": "Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.browser-wasm" } }, - "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" : { + "Microsoft.NETCore.App.Runtime.Mono.net7.browser-wasm" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.browser-wasm" + } }, - "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm" : { + "Microsoft.NETCore.App.Runtime.Mono.multithread.net7.browser-wasm" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.multithread.browser-wasm" + } }, - "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" : { + "Microsoft.NETCore.App.Runtime.Mono.perftrace.net7.browser-wasm" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.Mono.perftrace.browser-wasm" + } }, - "Microsoft.NETCore.App.Runtime.win-x64" : { + "Microsoft.NETCore.App.Runtime.net7.win-x64" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x64" + } }, - "Microsoft.NETCore.App.Runtime.win-x86" : { + "Microsoft.NETCore.App.Runtime.net7.win-x86" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-x86" + } }, - "Microsoft.NETCore.App.Runtime.win-arm" : { + "Microsoft.NETCore.App.Runtime.net7.win-arm" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm" + } }, - "Microsoft.NETCore.App.Runtime.win-arm64" : { + "Microsoft.NETCore.App.Runtime.net7.win-arm64" : { "kind": "framework", - "version": "${PackageVersion}" + "version": "${PackageVersion}", + "alias-to": { + "any": "Microsoft.NETCore.App.Runtime.win-arm64" + } } } } diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in new file mode 100644 index 00000000000000..f592c49a74dee3 --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.targets.in @@ -0,0 +1,135 @@ + + + <_RuntimePackInWorkloadVersion7>${PackageVersion} + <_BrowserWorkloadDisabled7>$(BrowserWorkloadDisabled) + <_BrowserWorkloadDisabled7 Condition="'$(_BrowserWorkloadDisabled7)' == '' and + '$(RuntimeIdentifier)' == 'browser-wasm' and + '$(TargetFrameworkIdentifier)' == '.NETCoreApp' and + !$([MSBuild]::VersionEquals('$(TargetFrameworkVersion)', '7.0'))">true + true + + + + + true + + + + + true + $(WasmNativeWorkload7) + + + + false + false + false + + + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_MonoWorkloadTargetsMobile>true + <_MonoWorkloadRuntimePackPackageVersion>$(_RuntimePackInWorkloadVersion7) + + + + + $(_MonoWorkloadRuntimePackPackageVersion) + + Microsoft.NETCore.App.Runtime.Mono.multithread.**RID** + Microsoft.NETCore.App.Runtime.Mono.perftrace.**RID** + + + + + + + + + + + + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.cs.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.cs.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.cs.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.cs.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.de.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.de.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.de.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.de.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.en.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.en.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.en.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.en.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.es.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.es.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.es.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.es.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.fr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.fr.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.fr.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.fr.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.it.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.it.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.it.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.it.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ja.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ja.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ja.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ja.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ko.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ko.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ko.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ko.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pl.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pl.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pl.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pl.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pt-BR.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pt-BR.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.pt-BR.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.pt-BR.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ru.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ru.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.ru.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.ru.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.tr.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.tr.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.tr.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.tr.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hans.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hans.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hans.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hans.json diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hant.json b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hant.json similarity index 100% rename from src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/localize/WorkloadManifest.zh-Hant.json rename to src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/localize/WorkloadManifest.zh-Hant.json diff --git a/src/mono/nuget/mono-packages.proj b/src/mono/nuget/mono-packages.proj index 1c1c7c0bff9e3f..319028efb91b6b 100644 --- a/src/mono/nuget/mono-packages.proj +++ b/src/mono/nuget/mono-packages.proj @@ -15,7 +15,8 @@ - + + diff --git a/src/mono/sample/iOS/Program.csproj b/src/mono/sample/iOS/Program.csproj index 206d8f02fe863c..ec1f84c354d9cc 100644 --- a/src/mono/sample/iOS/Program.csproj +++ b/src/mono/sample/iOS/Program.csproj @@ -67,10 +67,6 @@ - - marshal-ilgen - - .Node.win-x64` package name, version and sha hash in https://github.com/dotnet/runtime/blob/main/eng/Version.Details.xml and in https://github.com/dotnet/runtime/blob/main/eng/Versions.props. the sha is the commit hash in https://github.com/dotnet/emsdk and the package version can be found at https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet6 -* update packages in the workload manifest https://github.com/dotnet/runtime/blob/main/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.json.in +* update packages in the workload manifest https://github.com/dotnet/runtime/blob/main/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.net7.Manifest/WorkloadManifest.json.in ## Upgrading NPM packages In folder `src\mono\wasm\runtime\` diff --git a/src/mono/wasm/build/WasmApp.InTree.props b/src/mono/wasm/build/WasmApp.InTree.props index 38e99c6a6cbc33..4949caa40e6eda 100644 --- a/src/mono/wasm/build/WasmApp.InTree.props +++ b/src/mono/wasm/build/WasmApp.InTree.props @@ -18,6 +18,5 @@ <_MonoRuntimeComponentDontLink Include="libmono-component-diagnostics_tracing-static.a" Condition="'$(FeatureWasmPerfTracing)' != 'true' and $(FeatureWasmThreads) != 'true'"/> <_MonoRuntimeComponentDontLink Include="libmono-component-hot_reload-stub-static.a" /> - <_MonoRuntimeComponentDontLink Include="libmono-component-marshal-ilgen-stub-static.a" /> diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index f45230c3346e45..4c624331712115 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -31,9 +31,6 @@ <_MonoComponent Include="hot_reload;debugger" /> - - <_MonoComponent Include="marshal-ilgen" /> - @@ -111,6 +108,8 @@ true + true + true false @@ -121,6 +120,7 @@ true true + true false @@ -198,7 +198,7 @@ <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" Condition="'$(WasmExceptionHandling)' == 'false'" /> <_EmccCommonFlags Include="-fwasm-exceptions" Condition="'$(WasmExceptionHandling)' == 'true'" /> - <_EmccCommonFlags Include="-msimd128" Condition="'$(WasmSIMD)' == 'true'" /> + <_EmccCommonFlags Include="-msimd128" Condition="'$(WasmEnableSIMD)' == 'true'" /> <_EmccIncludePaths Include="$(_WasmIntermediateOutputPath.TrimEnd('\/'))" /> <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)mono-2.0" /> @@ -272,7 +272,6 @@ <_WasmPInvokeModules Include="libSystem.Native" /> <_WasmPInvokeModules Include="libSystem.IO.Compression.Native" /> <_WasmPInvokeModules Include="libSystem.Globalization.Native" /> - <_WasmPInvokeModules Include="libSystem.Security.Cryptography.Native.Browser" /> @@ -528,7 +527,7 @@ - + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 602896b55da0f0..b324a4da7d2659 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -66,7 +66,7 @@ Defaults to false. - $(WasmAotProfilePath) - Path to an AOT profile file. - $(WasmExceptionHandling) - Enable support for the WASM Exception Handling feature. - - $(WasmSIMD) - Enable support for the WASM SIMD feature. + - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -87,7 +87,7 @@ false false - false + false @@ -286,7 +286,6 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true - <_HasDotnetJsCryptoWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet-crypto-worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true @@ -296,7 +295,6 @@ - "internal" }; + if (field.IsBackingField) { fieldValue["__isBackingField"] = true; @@ -567,13 +568,20 @@ public static async Task GetObjectMemberValues( for (int i = 0; i < typeIdsCnt; i++) { int typeId = typeIdsIncludingParents[i]; + var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); + + if (typeInfo.Info.IsNonUserCode && getCommandType.HasFlag(GetObjectCommandOptions.JustMyCode)) + continue; + int parentTypeId = i + 1 < typeIdsCnt ? typeIdsIncludingParents[i + 1] : -1; string typeName = await sdbHelper.GetTypeName(typeId, token); // 0th id is for the object itself, and then its ancestors bool isOwn = i == 0; + IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); if (!includeStatic) thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); + if (thisTypeFields.Count > 0) { var allFields = await ExpandFieldValues( diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 1626bb2f9e9d4e..0aa7650c42c2bd 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -745,6 +745,8 @@ internal async Task> RuntimeGetObjectMembers(Sess if (args["forDebuggerDisplayAttribute"]?.Value() == true) getObjectOptions |= GetObjectCommandOptions.ForDebuggerDisplayAttribute; } + if (JustMyCode) + getObjectOptions |= GetObjectCommandOptions.JustMyCode; try { switch (objectId.Scheme) @@ -1537,8 +1539,9 @@ protected async Task RuntimeReady(SessionId sessionId, CancellationT ExecutionContext context = GetContext(sessionId); if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) return await context.ready.Task; - - await context.SdbAgent.SendDebuggerAgentCommand(CmdEventRequest.ClearAllBreakpoints, null, token); + var res = await context.SdbAgent.SendDebuggerAgentCommand(CmdEventRequest.ClearAllBreakpoints, null, token, false); + if (res.HasError) //it's not a wasm page then the command returns an error + return null; if (context.PauseOnExceptions != PauseOnExceptionsKind.None && context.PauseOnExceptions != PauseOnExceptionsKind.Unset) await context.SdbAgent.EnableExceptions(context.PauseOnExceptions, token); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 3b3de88f56a9c7..3a25a7fac07929 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -59,7 +59,8 @@ internal enum GetObjectCommandOptions OwnProperties = 4, ForDebuggerProxyAttribute = 8, ForDebuggerDisplayAttribute = 16, - WithProperties = 32 + WithProperties = 32, + JustMyCode = 64 } internal enum CommandSet { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index cb21ab4e9f2257..7949ebec263fa6 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs @@ -470,8 +470,10 @@ await SendCommandAndCheck(null, "Debugger.resume", }); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("load_non_wasm_page")] + [InlineData("load_non_wasm_page_forcing_runtime_ready")] //to simulate the same behavior that has when debugging from VS and OnDefaultContextCreated is called + public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain(string func_name) { var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); var pause_location = await EvaluateAndCheck( @@ -500,7 +502,7 @@ public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain() var run_method = JObject.FromObject(new { - expression = "window.setTimeout(function() { load_non_wasm_page(); }, 1);" + expression = "window.setTimeout(function() { " + func_name + "(); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); await Task.Delay(1000, token); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs index 146ecaebac862c..7e5f5fa3cd8102 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs @@ -51,35 +51,37 @@ public async Task DebugHotReloadMethodUnchanged() CheckNumber(locals, "a", 10); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugHotReloadMethodAddBreakpoint() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ApplyUpdateReferencedAssembly")] + [InlineData("ApplyUpdateReferencedAssemblyChineseCharInPathㄨ")] + public async Task DebugHotReloadMethodAddBreakpoint(string assembly_name) { int line = 30; await SetBreakpoint(".*/MethodBody1.cs$", line, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReload( - Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), - Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), - Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"), + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb"), + Path.Combine(DebuggerTestAppPath, $"../wasm/{assembly_name}.dll"), "MethodBody3", "StaticMethod3", expectBpResolvedEvent: true); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); - pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "b", 15); - pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); + pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", $"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3"); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); await CheckBool(locals, "c", true); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -87,7 +89,7 @@ await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/Me await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -217,12 +219,14 @@ public async Task DebugHotReloadMethodUnchangedUsingSDB() CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 21, 12, scripts, top_frame["location"]); } - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugHotReloadMethodAddBreakpointUsingSDB() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ApplyUpdateReferencedAssembly")] + [InlineData("ApplyUpdateReferencedAssemblyChineseCharInPathㄨ")] + public async Task DebugHotReloadMethodAddBreakpointUsingSDB(string assembly_name) { - string asm_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"); - string pdb_file = Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"); - string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); + string asm_file = Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"); + string pdb_file = Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb"); + string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, $"../wasm/{assembly_name}.dll"); int line = 30; await SetBreakpoint(".*/MethodBody1.cs$", line, 12, use_regex: true); @@ -238,7 +242,7 @@ public async Task DebugHotReloadMethodAddBreakpointUsingSDB() JToken top_frame = pause_location["callFrames"]?[0]; AssertEqual("ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", top_frame?["functionName"]?.Value(), top_frame?.ToString()); - CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); + CheckLocation($"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "b", 15); @@ -249,19 +253,19 @@ public async Task DebugHotReloadMethodAddBreakpointUsingSDB() top_frame = pause_location["callFrames"]?[0]; AssertEqual("ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", top_frame?["functionName"]?.Value(), top_frame?.ToString()); - CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); + CheckLocation($"dotnet://{assembly_name}.dll/MethodBody1.cs", 30, 12, scripts, top_frame["location"]); locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); await CheckBool(locals, "c", true); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 31, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 32, 12, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); @@ -269,7 +273,7 @@ await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/Me await Task.CompletedTask; } ); - await StepAndCheck(StepKind.Over, "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", + await StepAndCheck(StepKind.Over, $"dotnet://{assembly_name}.dll/MethodBody1.cs", 33, 8, "ApplyUpdateReferencedAssembly.MethodBody3.StaticMethod3", locals_fn: async (locals) => { CheckNumber(locals, "d", 10); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 5e0f0d6802c5a9..9973811a56e499 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -735,23 +735,25 @@ JObject FindFrame(JObject pause_location, string function_name) ?.Where(f => f["functionName"]?.Value() == function_name) ?.FirstOrDefault(); - [ConditionalFact(nameof(RunningOnChrome))] - public async Task DebugLazyLoadedAssemblyWithPdb() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("lazy-debugger-test")] + [InlineData("lazy-debugger-test-chinese-char-in-path-ㄨ")] + public async Task DebugLazyLoadedAssemblyWithPdb(string assembly_name) { Task bpResolved = WaitForBreakpointResolvedEvent(); int line = 9; await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb")); + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.dll"), + Path.Combine(DebuggerTestAppPath, $"{assembly_name}.pdb")); - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + var source_location = $"dotnet://{assembly_name}.dll/lazy-debugger-test.cs"; Assert.Contains(source_location, scripts.Values); await bpResolved; var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", + "window.setTimeout(function () { invoke_static_method('[" + assembly_name + "] LazyMath:IntAdd', 5, 10); }, 1);", source_location, line, 8, "LazyMath.IntAdd"); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); @@ -1039,5 +1041,64 @@ await EvaluateAndCheck( } ); } + + [Theory] + [InlineData("ClassInheritsFromClassWithoutDebugSymbols", 1287, true)] + [InlineData("ClassInheritsFromClassWithoutDebugSymbols", 1287, false)] + [InlineData("ClassInheritsFromNonUserCodeClass", 1335, true)] + [InlineData("ClassInheritsFromNonUserCodeClass", 1335, false)] + [InlineData("ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1352, true)] + [InlineData("ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass", 1352, false)] + public async Task InspectThisThatInheritsFromClassNonUserCode(string class_name, int line, bool jmc) + { + await SetJustMyCode(jmc); + var expression = "{{ invoke_static_method('[debugger-test] " + class_name + ":Run'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", line, 8, + $"{class_name}.CallMethod", + locals_fn: async (locals) => + { + var this_props = await GetObjectOnLocals(locals, "this"); + if (jmc) + { + await CheckProps(this_props, new + { + myField = TNumber(0), + myField2 = TNumber(0), + }, "this_props", num_fields: 2); + } + else + { + await CheckProps(this_props, new + { + propA = TNumber(10), + propB = TNumber(20), + propC = TNumber(30), + d = TNumber(40), + e = TNumber(50), + f = TNumber(60), + G = TGetter("G"), + H = TGetter("H"), + myField = TNumber(0), + myField2 = TNumber(0), + }, "this_props", num_fields: 10); + } + } + ); + } + + [ConditionalFact(nameof(RunningOnChrome))] + public async Task SetBreakpointInProjectWithChineseCharactereInPath() + { + var bp = await SetBreakpointInMethod("debugger-test-chinese-char-in-path-ㄨ.dll", "DebuggerTests.CheckChineseCharacterInPath", "Evaluate", 1); + await EvaluateAndCheck( + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test-chinese-char-in-path-ㄨ] DebuggerTests.CheckChineseCharacterInPath:Evaluate'); }}, 1);", + "dotnet://debugger-test-chinese-char-in-path-ㄨ.dll/test.cs", + bp.Value["locations"][0]["lineNumber"].Value(), + bp.Value["locations"][0]["columnNumber"].Value(), + $"DebuggerTests.CheckChineseCharacterInPath.Evaluate"); + } } } diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" new file mode 100644 index 00000000000000..9b4c7a12d15077 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250.csproj" @@ -0,0 +1,36 @@ + + + true + deltascript.json + library + false + true + + false + true + + false + false + false + true + + + true + + + + + + + + + + + + diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" new file mode 100644 index 00000000000000..833ac95a642f02 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody0.cs" @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; + +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBodyUnchangedAssembly { + public static string StaticMethod1 () { + Console.WriteLine("original"); + return "ok"; + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.cs" new file mode 100644 index 00000000000000..3abc1d4b538bb7 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1.cs" @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + int a = 10; + Console.WriteLine("original"); + return "OLD STRING"; + } + } + + + + public class MethodBody4 { + public static void StaticMethod4 () { + } + } + + + + + + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + } + + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" new file mode 100644 index 00000000000000..cef3214d9c89e9 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v1.cs" @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () { + Console.WriteLine("v1"); + double b = 15; + Debugger.Break(); + return "NEW STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + float b = 15; + Console.WriteLine("v1"); + return "NEW STRING"; + } + } + + + + public class MethodBody4 { + public static void StaticMethod4 () { + int a = 10; + int b = 20; + Console.WriteLine(a + b); + Console.WriteLine(a + b); + Console.WriteLine(a + b); + } + } + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("beforeoriginal"); + Console.WriteLine("original"); + } + } + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + public static void NewMethodStatic () { + int i = 20; + newStaticField = 10; + Console.WriteLine($"add a breakpoint in the new static method, look at locals {newStaticField}"); + /*var newvar = new MethodBody6(); + newvar.NewMethodInstance (10);*/ + } + public static int newStaticField; + } + + public class MethodBody7 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody7 newvar = new MethodBody7(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" new file mode 100644 index 00000000000000..48c3b9911e8059 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/MethodBody1_v2.cs" @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System; +//keep the same line number for class in the original file and the updates ones +namespace ApplyUpdateReferencedAssembly +{ + public class MethodBody1 { + public static string StaticMethod1 () + { + Console.WriteLine("v2"); + bool c = true; + Debugger.Break(); + return "NEWEST STRING"; + } + } + + public class MethodBody2 { + public static string StaticMethod1 () { + Console.WriteLine("original"); + int a = 10; + Debugger.Break(); + return "OLD STRING"; + } + } + + public class MethodBody3 { + public static string StaticMethod3 () { + bool c = true; + int d = 10; + int e = 20; + int f = 50; + return "NEWEST STRING"; + } + } + + public class MethodBody4 { + public static void StaticMethod4 () { + } + } + + + + + + + public class MethodBody5 { + public static void StaticMethod1 () { + Console.WriteLine("beforeoriginal"); + Console.WriteLine("original"); + } + } + + public class MethodBody6 { + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a line that will not be changed"); + Console.WriteLine("original"); + } + public static void NewMethodStatic () { + int i = 20; + newStaticField = 10; + Console.WriteLine($"add a breakpoint in the new static method, look at locals {newStaticField}"); + /*var newvar = new MethodBody6(); + newvar.NewMethodInstance (10);*/ + } + public static int newStaticField; + } + + public class MethodBody7 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody7 newvar = new MethodBody7(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } + + public class MethodBody8 { + public static int staticField; + int attr1; + string attr2; + public static void StaticMethod1 () { + Console.WriteLine("breakpoint in a method in a new class"); + Console.WriteLine("original"); + MethodBody8 newvar = new MethodBody8(); + staticField = 80; + newvar.InstanceMethod(); + } + public void InstanceMethod () { + int aLocal = 50; + attr1 = 15; + attr2 = "20"; + Console.WriteLine($"add a breakpoint the instance method of the new class"); + } + } +} diff --git "a/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/deltascript.json" "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/deltascript.json" new file mode 100644 index 00000000000000..8e738364bc7475 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/ApplyUpdateReferencedAssemblyChineseCharInPath\343\204\250/deltascript.json" @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "MethodBody1.cs", "update": "MethodBody1_v1.cs"}, + {"document": "MethodBody1.cs", "update": "MethodBody1_v2.cs"}, + ] +} + diff --git "a/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" new file mode 100644 index 00000000000000..1463b4a398f1f7 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/debugger-test-chinese-char-in-path-\343\204\250.csproj" @@ -0,0 +1,6 @@ + + + + false + + diff --git "a/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" new file mode 100644 index 00000000000000..6902b401317936 --- /dev/null +++ "b/src/mono/wasm/debugger/tests/debugger-test-chinese-char-in-path-\343\204\250/test.cs" @@ -0,0 +1,10 @@ +namespace DebuggerTests +{ + public class CheckChineseCharacterInPath + { + public static void Evaluate() + { + var a = 123; + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj new file mode 100644 index 00000000000000..c0d42d7f25cde5 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/debugger-test-with-non-user-code-class.csproj @@ -0,0 +1,4 @@ + + + + diff --git a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs new file mode 100644 index 00000000000000..af11a6329d0d26 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs @@ -0,0 +1,41 @@ +using System; + +namespace DebuggerTests +{ + public class NormalClass + { + public int myField2; + } + + [System.Diagnostics.DebuggerNonUserCode] + public class ClassNonUserCodeToInheritThatInheritsFromNormalClass : NormalClass + { + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + public int G + { + get {return f + 1;} + } + public int H => f; + + public ClassNonUserCodeToInheritThatInheritsFromNormalClass() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj new file mode 100644 index 00000000000000..34367db0bff2da --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/debugger-test-without-debug-symbols-to-load.csproj @@ -0,0 +1,6 @@ + + + none + false + + diff --git a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs new file mode 100644 index 00000000000000..5dcdc29f93f2d0 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs @@ -0,0 +1,35 @@ +using System; + +namespace DebuggerTests +{ + public class ClassWithoutDebugSymbolsToInherit + { + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + public int G + { + get {return f + 1;} + } + public int H => f; + + public ClassWithoutDebugSymbolsToInherit() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html index fdbbc640696ac3..b6522cf3cc79a8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html @@ -92,6 +92,10 @@ console.log("load_wasm_page_without_assets") window.location.replace("http://localhost:9400/wasm-page-without-assets.html"); } + function load_non_wasm_page_forcing_runtime_ready () { + console.log("load_non_wasm_page_forcing_runtime_ready") + window.location.replace("http://localhost:9400/non-wasm-page-forcing-runtime-ready.html"); + } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs index 6424ca3d5cdd93..6b5f84561beb9e 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -1275,3 +1275,83 @@ public static void MethodWithHiddenLinesAtTheEnd3() #line default } +public class ClassInheritsFromClassWithoutDebugSymbols : DebuggerTests.ClassWithoutDebugSymbolsToInherit +{ + public static void Run() + { + var myVar = new ClassInheritsFromClassWithoutDebugSymbols(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + public int myField2; + public int myField; +} + +[System.Diagnostics.DebuggerNonUserCode] +public class ClassNonUserCodeToInherit +{ + private int propA {get;} + public int propB {get;} + protected int propC {get;} + private int d; + public int e; + protected int f; + public int G + { + get {return f + 1;} + } + public int H => f; + + public ClassNonUserCodeToInherit() + { + propA = 10; + propB = 20; + propC = 30; + d = 40; + e = 50; + f = 60; + Console.WriteLine(propA); + Console.WriteLine(propB); + Console.WriteLine(propC); + Console.WriteLine(d); + Console.WriteLine(e); + Console.WriteLine(f); + } +} + +public class ClassInheritsFromNonUserCodeClass : ClassNonUserCodeToInherit +{ + public static void Run() + { + var myVar = new ClassInheritsFromNonUserCodeClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField2; + public int myField; +} + +public class ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass : DebuggerTests.ClassNonUserCodeToInheritThatInheritsFromNormalClass +{ + public static void Run() + { + var myVar = new ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass(); + myVar.CallMethod(); + } + + public void CallMethod() + { + System.Diagnostics.Debugger.Break(); + } + + public int myField; +} \ No newline at end of file diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 42edcb663f906a..c6977c5f2d0407 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -13,19 +13,25 @@ + + + + + <_AssemblyForDynamicLoading Include="lazy-debugger-test" /> + <_AssemblyForDynamicLoading Include="lazy-debugger-test-chinese-char-in-path-ㄨ" /> <_AssemblyForDynamicLoading Include="debugger-test-with-full-debug-type" /> <_AssemblyForDynamicLoading Include="debugger-test-with-pdb-deleted" /> <_AssemblyForDynamicLoading Include="debugger-test-without-debug-symbols" /> @@ -44,14 +50,16 @@ debugger-main.js -1 - true + + + @@ -63,6 +71,7 @@ + + + + + + + + + diff --git "a/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" new file mode 100644 index 00000000000000..35e3d8428b7cfc --- /dev/null +++ "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test-chinese-char-in-path-\343\204\250.csproj" @@ -0,0 +1,2 @@ + + diff --git "a/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" new file mode 100644 index 00000000000000..2da3c708e85acb --- /dev/null +++ "b/src/mono/wasm/debugger/tests/lazy-debugger-test-chinese-char-in-path-\343\204\250/lazy-debugger-test.cs" @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +public partial class LazyMath +{ + public static int IntAdd(int a, int b) + { + int c = a + b; + return c; + } +} + +namespace DebuggerTests +{ + public class ClassToCheckFieldValue + { + public int valueToCheck; + public ClassToCheckFieldValue() + { + valueToCheck = 20; + } + } +} diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index 2eeb255fb41945..c852a7df1df614 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -19,7 +19,6 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-component-hot_reload-static.a ${MONO_ARTIFACTS_DIR}/libmono-component-debugger-static.a ${MONO_ARTIFACTS_DIR}/libmono-component-diagnostics_tracing-stub-static.a - ${MONO_ARTIFACTS_DIR}/libmono-component-marshal-ilgen-static.a ${MONO_ARTIFACTS_DIR}/libmono-ee-interp.a ${MONO_ARTIFACTS_DIR}/libmonosgen-2.0.a ${MONO_ARTIFACTS_DIR}/libmono-ilgen.a @@ -27,8 +26,7 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-wasm-eh-js.a ${MONO_ARTIFACTS_DIR}/libmono-profiler-aot.a ${NATIVE_BIN_DIR}/libSystem.Native.a - ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a - ${NATIVE_BIN_DIR}/libSystem.Security.Cryptography.Native.Browser.a) + ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) set_target_properties(dotnet PROPERTIES LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.pre.js;${NATIVE_BIN_DIR}/src/es6/runtime.es6.iffe.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.post.js;${NATIVE_BIN_DIR}/src/es6/dotnet.es6.extpost.js;" diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 8d44101617b9c8..80d63429102938 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -28,7 +28,6 @@ let throttlingPromise: PromiseAndController | undefined; const skipDownloadsByAssetTypes: { [k: string]: boolean } = { - "js-module-crypto": true, "js-module-threads": true, }; @@ -43,7 +42,6 @@ const skipBufferByAssetTypes: { const skipInstantiateByAssetTypes: { [k: string]: boolean } = { - "js-module-crypto": true, "js-module-threads": true, "dotnetwasm": true, }; @@ -356,7 +354,6 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { switch (asset.behavior) { case "dotnetwasm": - case "js-module-crypto": case "js-module-threads": // do nothing break; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 704b8ab94e230c..a65ce52c7de712 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -166,7 +166,7 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". "invariant" | // operate in invariant globalization mode. "auto"; @@ -236,83 +236,11 @@ declare type ModuleAPI = { declare function createDotnetRuntime(moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)): Promise; declare type CreateDotnetRuntimeType = typeof createDotnetRuntime; -interface IDisposable { - dispose(): void; - get isDisposed(): boolean; -} -declare const enum MemoryViewType { - Byte = 0, - Int32 = 1, - Double = 2 -} -interface IMemoryView { - /** - * copies elements from provided source to the wasm memory. - * target has to have the elements of the same type as the underlying C# array. - * same as TypedArray.set() - */ - set(source: TypedArray, targetOffset?: number): void; - /** - * copies elements from wasm memory to provided target. - * target has to have the elements of the same type as the underlying C# array. - */ - copyTo(target: TypedArray, sourceOffset?: number): void; - /** - * same as TypedArray.slice() - */ - slice(start?: number, end?: number): TypedArray; - get length(): number; - get byteLength(): number; -} - declare global { function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; } -/** - * Span class is JS wrapper for System.Span. This view doesn't own the memory, nor pin the underlying array. - * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. - * It is disposed at the end of the call to JS. - */ -declare class Span implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} -/** - * ArraySegment class is JS wrapper for System.ArraySegment. - * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. - * User could dispose it manually. - */ -declare class ArraySegment implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} -/** - * Represents proxy to the System.Exception - */ -declare class ManagedError extends Error implements IDisposable { - get stack(): string | undefined; - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} -/** - * Represents proxy to the System.Object - */ -declare class ManagedObject implements IDisposable { - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} +declare const dotnet: ModuleAPI["dotnet"]; +declare const exit: ModuleAPI["exit"]; -export { ArraySegment, AssetBehaviours, AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, LoadingResource, ManagedError, ManagedObject, MemoryViewType, ModuleAPI, MonoConfig, NativePointer, ResourceRequest, RuntimeAPI, Span, createDotnetRuntime as default }; +export { CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 74aed3030e8a86..bde650aea36eca 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -95,13 +95,6 @@ const linked_functions = [ "mono_wasm_load_icu_data", "mono_wasm_get_icudt_name", - // pal_crypto_webworker.c - "dotnet_browser_can_use_subtle_crypto_impl", - "dotnet_browser_simple_digest_hash", - "dotnet_browser_sign", - "dotnet_browser_encrypt_decrypt", - "dotnet_browser_derive_bits", - #if USE_PTHREADS /// mono-threads-wasm.c "mono_wasm_pthread_on_pthread_attached", diff --git a/src/mono/wasm/runtime/export-types.ts b/src/mono/wasm/runtime/export-types.ts index bc368975c17520..ea27e07e62acc8 100644 --- a/src/mono/wasm/runtime/export-types.ts +++ b/src/mono/wasm/runtime/export-types.ts @@ -1,9 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { IDisposable, IMemoryView, MemoryViewType } from "./marshal"; -import { AssetBehaviours, AssetEntry, createDotnetRuntime, CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, LoadingResource, MonoConfig, ResourceRequest, ModuleAPI } from "./types"; -import { EmscriptenModule, NativePointer, TypedArray } from "./types/emscripten"; +import { createDotnetRuntime, CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI } from "./types"; +import { EmscriptenModule } from "./types/emscripten"; // ----------------------------------------------------------- // this files has all public exports from the dotnet.js module @@ -17,60 +16,11 @@ declare global { export default createDotnetRuntime; - -/** - * Span class is JS wrapper for System.Span. This view doesn't own the memory, nor pin the underlying array. - * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. - * It is disposed at the end of the call to JS. - */ -declare class Span implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} - -/** - * ArraySegment class is JS wrapper for System.ArraySegment. - * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. - * User could dispose it manually. - */ -declare class ArraySegment implements IMemoryView, IDisposable { - dispose(): void; - get isDisposed(): boolean; - set(source: TypedArray, targetOffset?: number | undefined): void; - copyTo(target: TypedArray, sourceOffset?: number | undefined): void; - slice(start?: number | undefined, end?: number | undefined): TypedArray; - get length(): number; - get byteLength(): number; -} - -/** - * Represents proxy to the System.Exception - */ -declare class ManagedError extends Error implements IDisposable { - get stack(): string | undefined; - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} - -/** - * Represents proxy to the System.Object - */ -declare class ManagedObject implements IDisposable { - dispose(): void; - get isDisposed(): boolean; - toString(): string; -} +declare const dotnet: ModuleAPI["dotnet"]; +declare const exit: ModuleAPI["exit"]; export { - EmscriptenModule, NativePointer, + EmscriptenModule, RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, - AssetEntry, ResourceRequest, LoadingResource, AssetBehaviours, - IMemoryView, MemoryViewType, ManagedObject, ManagedError, Span, ArraySegment + dotnet, exit }; - diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 89dfc822b99d82..22f8a66cdee89f 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. import MonoWasmThreads from "consts:monoWasmThreads"; -import { dotnet_browser_can_use_subtle_crypto_impl, dotnet_browser_simple_digest_hash, dotnet_browser_sign, dotnet_browser_encrypt_decrypt, dotnet_browser_derive_bits } from "./subtle-crypto"; import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; @@ -78,14 +77,7 @@ export function export_linker(): any { mono_wasm_load_icu_data, mono_wasm_get_icudt_name, - // pal_crypto_webworker.c - dotnet_browser_can_use_subtle_crypto_impl, - dotnet_browser_simple_digest_hash, - dotnet_browser_sign, - dotnet_browser_encrypt_decrypt, - dotnet_browser_derive_bits, - // threading exports, if threading is enabled ...mono_wasm_threads_exports, }; -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index bbd48aa9e30140..09f6d9a33d0241 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -22,8 +22,8 @@ export function init_managed_exports(): void { if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; - const install_sync_context = get_method("InstallSynchronizationContext"); - mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); + const install_sync_context = cwraps.mono_wasm_assembly_find_method(runtimeHelpers.runtime_interop_exports_class, "InstallSynchronizationContext", -1); + // mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); const call_entry_point = get_method("CallEntrypoint"); mono_assert(call_entry_point, "Can't find CallEntrypoint method"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); @@ -134,19 +134,22 @@ export function init_managed_exports(): void { anyModule.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.install_synchronization_context = () => { - const sp = anyModule.stackSave(); - try { - const args = alloc_stack_frame(2); - invoke_method_and_handle_exception(install_sync_context, args); - } finally { - anyModule.stackRestore(sp); - } - }; - if (!ENVIRONMENT_IS_PTHREAD) - // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically - runtimeHelpers.javaScriptExports.install_synchronization_context(); + if (install_sync_context) { + runtimeHelpers.javaScriptExports.install_synchronization_context = () => { + const sp = anyModule.stackSave(); + try { + const args = alloc_stack_frame(2); + invoke_method_and_handle_exception(install_sync_context, args); + } finally { + anyModule.stackRestore(sp); + } + }; + + if (!ENVIRONMENT_IS_PTHREAD) + // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically + runtimeHelpers.javaScriptExports.install_synchronization_context(); + } } export function get_method(method_name: string): MonoMethod { diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index 021bc6c70a666d..b7c62e9599303c 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -135,12 +135,14 @@ export function preAllocatePThreadWorkerPool(defaultPthreadPoolSize: number, con export async function instantiateWasmPThreadWorkerPool(): Promise { // this is largely copied from emscripten's "receiveInstance" in "createWasm" in "src/preamble.js" const workers = Internals.getUnusedWorkerPool(); - const allLoaded = createPromiseController(); - let leftToLoad = workers.length; - workers.forEach((w) => { - Internals.loadWasmModuleToWorker(w, function () { - if (!--leftToLoad) allLoaded.promise_control.resolve(); + if (workers.length > 0) { + const allLoaded = createPromiseController(); + let leftToLoad = workers.length; + workers.forEach((w) => { + Internals.loadWasmModuleToWorker(w, function () { + if (!--leftToLoad) allLoaded.promise_control.resolve(); + }); }); - }); - await allLoaded.promise; + await allLoaded.promise; + } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 68996b266d28ee..c576927f17725a 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -13,7 +13,6 @@ import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from ". import { mono_on_abort, mono_exit } from "./run"; import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; -import { init_crypto } from "./subtle-crypto"; import { init_polyfills_async } from "./polyfills"; import * as pthreads_worker from "./pthreads/worker"; import { createPromiseController } from "./promise-controller"; @@ -255,7 +254,6 @@ async function mono_wasm_pre_init_essential_async(): Promise { await init_polyfills_async(); await mono_wasm_load_config(Module.configSrc); - init_crypto(); if (MonoWasmThreads) { preAllocatePThreadWorkerPool(MONO_PTHREAD_POOL_SIZE, config); diff --git a/src/mono/wasm/runtime/subtle-crypto.ts b/src/mono/wasm/runtime/subtle-crypto.ts deleted file mode 100644 index 8a60cc5ef93865..00000000000000 --- a/src/mono/wasm/runtime/subtle-crypto.ts +++ /dev/null @@ -1,412 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { resolve_asset_path } from "./assets"; -import { Module, runtimeHelpers } from "./imports"; -import { mono_assert } from "./types"; - -class OperationFailedError extends Error { } - -const ERR_ARGS = -1; -const ERR_WORKER_FAILED = -2; -const ERR_OP_FAILED = -3; -const ERR_UNKNOWN = -100; - -let mono_wasm_crypto: { - channel: LibraryChannel - worker: Worker -} | null = null; - -export function dotnet_browser_can_use_subtle_crypto_impl(): number { - return mono_wasm_crypto === null ? 0 : 1; -} - -export function dotnet_browser_simple_digest_hash(ver: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - const msg = { - func: "digest", - type: ver, - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - return _send_simple_msg(msg, "DIGEST HASH", output_buffer, output_len); -} - -export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, key_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - const msg = { - func: "sign", - type: hashAlgorithm, - key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)), - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - return _send_simple_msg(msg, "SIGN HASH", output_buffer, output_len); -} - -const AesBlockSizeBytes = 16; // 128 bits - -export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer: number, key_len: number, iv_buffer: number, iv_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number { - if (input_len <= 0 || input_len % AesBlockSizeBytes !== 0) { - throw "ENCRYPT DECRYPT: data was not a full block: " + input_len; - } - - const msg = { - func: "encrypt_decrypt", - isEncrypting: isEncrypting, - key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)), - iv: Array.from(Module.HEAPU8.subarray(iv_buffer, iv_buffer + iv_len)), - data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len)) - }; - - const result = _send_msg_worker(msg); - if (typeof result === "number") { - return result; - } - - if (result.length > output_len) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: Encrypt/Decrypt length exceeds output length: ${result.length} > ${output_len}`); - return ERR_ARGS; - } - - Module.HEAPU8.set(result, output_buffer); - return result.length; -} - -export function dotnet_browser_derive_bits(password_buffer: number, password_len: number, salt_buffer: number, salt_len: number, iterations: number, hashAlgorithm: number, output_buffer: number, output_len: number): number { - const msg = { - func: "derive_bits", - password: Array.from(Module.HEAPU8.subarray(password_buffer, password_buffer + password_len)), - salt: Array.from(Module.HEAPU8.subarray(salt_buffer, salt_buffer + salt_len)), - iterations: iterations, - hashAlgorithm: hashAlgorithm, - lengthInBytes: output_len - }; - - return _send_simple_msg(msg, "DERIVE BITS", output_buffer, output_len); -} - -function _send_simple_msg(msg: any, prefix: string, output_buffer: number, output_len: number): number { - const result = _send_msg_worker(msg); - - if (typeof result === "number") { - return result; - } - - if (result.length > output_len) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: ${prefix}: Result length exceeds output length: ${result.length} > ${output_len}`); - return ERR_ARGS; - } - - Module.HEAPU8.set(result, output_buffer); - return 0; -} - -export function init_crypto(): void { - if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" - && typeof SharedArrayBuffer !== "undefined" - && typeof Worker !== "undefined" - ) { - console.debug("MONO_WASM: Initializing Crypto WebWorker"); - - const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const asset = resolve_asset_path("js-module-crypto"); - mono_assert(asset && asset.resolvedUrl, "Can't find js-module-crypto"); - const worker = new Worker(asset.resolvedUrl); - mono_wasm_crypto = { - channel: chan, - worker: worker, - }; - const messageData: InitCryptoMessageData = { - config: JSON.stringify(runtimeHelpers.config),// there could be things in config which could not be cloned to worker - comm_buf: chan.get_comm_buffer(), - msg_buf: chan.get_msg_buffer(), - msg_char_len: chan.get_msg_len() - }; - worker.onerror = event => { - console.warn(`MONO_WASM: Error in Crypto WebWorker. Cryptography digest calls will fallback to managed implementation. Error: ${event.message}`); - mono_wasm_crypto = null; - }; - worker.postMessage(messageData); - } -} - -function _send_msg_worker(msg: any): number | any { - mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized"); - - try { - const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg)); - const responseJson = JSON.parse(response); - - if (responseJson.error !== undefined) { - console.error(`MONO_WASM_ENCRYPT_DECRYPT: Worker failed with: ${responseJson.error}`); - if (responseJson.error_type == "ArgumentsError") - return ERR_ARGS; - if (responseJson.error_type == "WorkerFailedError") - return ERR_WORKER_FAILED; - - return ERR_UNKNOWN; - } - - return responseJson.result; - } catch (err) { - if (err instanceof Error && err.stack !== undefined) - console.error(`MONO_WASM_ENCRYPT_DECRYPT: ${err.stack}`); - else - console.error(`MONO_WASM_ENCRYPT_DECRYPT: _send_msg_worker failed: ${err}`); - return ERR_OP_FAILED; - } -} - -class LibraryChannel { - private msg_char_len: number; - private comm_buf: SharedArrayBuffer; - private msg_buf: SharedArrayBuffer; - private comm: Int32Array; - private msg: Uint16Array; - - // LOCK states - private get LOCK_UNLOCKED(): number { return 0; } // 0 means the lock is unlocked - private get LOCK_OWNED(): number { return 1; } // 1 means the LibraryChannel owns the lock - - // Index constants for the communication buffer. - private get STATE_IDX(): number { return 0; } - private get MSG_SIZE_IDX(): number { return 1; } - private get LOCK_IDX(): number { return 2; } - private get COMM_LAST_IDX(): number { return this.LOCK_IDX; } - - // Communication states. - private get STATE_SHUTDOWN(): number { return -1; } // Shutdown - private get STATE_IDLE(): number { return 0; } - private get STATE_REQ(): number { return 1; } - private get STATE_RESP(): number { return 2; } - private get STATE_REQ_P(): number { return 3; } // Request has multiple parts - private get STATE_RESP_P(): number { return 4; } // Response has multiple parts - private get STATE_AWAIT(): number { return 5; } // Awaiting the next part - private get STATE_REQ_FAILED(): number { return 6; } // The Request failed - private get STATE_RESET(): number { return 7; } // Reset to a known state - - private constructor(msg_char_len: number) { - this.msg_char_len = msg_char_len; - - const int_bytes = 4; - const comm_byte_len = int_bytes * (this.COMM_LAST_IDX + 1); - this.comm_buf = new SharedArrayBuffer(comm_byte_len); - - // JavaScript character encoding is UTF-16. - const char_bytes = 2; - const msg_byte_len = char_bytes * this.msg_char_len; - this.msg_buf = new SharedArrayBuffer(msg_byte_len); - - // Create the local arrays to use. - this.comm = new Int32Array(this.comm_buf); - this.msg = new Uint16Array(this.msg_buf); - } - - public get_msg_len(): number { return this.msg_char_len; } - public get_msg_buffer(): SharedArrayBuffer { return this.msg_buf; } - public get_comm_buffer(): SharedArrayBuffer { return this.comm_buf; } - - public send_msg(msg: string): string { - try { - this.wait_for_state_change_to(pstate => pstate == this.STATE_IDLE, "waiting"); - this.send_request(msg); - return this.read_response(); - } catch (err) { - this.reset(LibraryChannel.stringify_err(err)); - throw err; - } - } - - public shutdown(): void { - console.debug("MONO_WASM_ENCRYPT_DECRYPT: Shutting down crypto"); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state !== this.STATE_IDLE) - throw new Error(`OWNER: Invalid sync communication channel state: ${state}`); - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_SHUTDOWN); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - } - - private reset(reason: string): void { - console.debug(`MONO_WASM_ENCRYPT_DECRYPT: reset: ${reason}`); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_SHUTDOWN) - return; - - if (state === this.STATE_RESET || state === this.STATE_IDLE) { - console.debug(`MONO_WASM_ENCRYPT_DECRYPT: state is already RESET or idle: ${state}`); - return; - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_RESET); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - } - - private send_request(msg: string): void { - let state; - const msg_len = msg.length; - let msg_written = 0; - - for (; ;) { - this.using_lock(() => { - // Write the message and return how much was written. - const wrote = this.write_to_msg(msg, msg_written, msg_len); - msg_written += wrote; - - // Indicate how much was written to the this.msg buffer. - Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote); - - // Indicate if this was the whole message or part of it. - state = msg_written === msg_len ? this.STATE_REQ : this.STATE_REQ_P; - - this.change_state_locked(state); - }); - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - - // The send message is complete. - if (state === this.STATE_REQ) { - break; - } - else if (state !== this.STATE_REQ_P) { - throw new Error(`Unexpected state ${state}`); - } - - this.wait_for_state_change_to(state => state == this.STATE_AWAIT, "send_request"); - } - } - - private write_to_msg(input: string, start: number, input_len: number): number { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - private read_response(): string { - let response = ""; - for (; ;) { - this.wait_for_state_change_to(state => state == this.STATE_RESP || state == this.STATE_RESP_P, "read_response"); - const done = this.using_lock(() => { - const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX); - - // Append the latest part of the message. - response += this.read_from_msg(0, size_to_read); - - // The response is complete. - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_RESP) { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - return true; - } else if (state !== this.STATE_RESP_P) { - throw new Error(`Unexpected state ${state}`); - } - - // Reset the size and transition to await state. - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_AWAIT); - return false; - }); - - // Notify webworker - Atomics.notify(this.comm, this.STATE_IDX); - - if (done) { - break; - } - } - - // Reset the communication channel's state and let the - // webworker know we are done. - this.using_lock(() => { - this.change_state_locked(this.STATE_IDLE); - }); - Atomics.notify(this.comm, this.STATE_IDX); - - return response; - } - - private change_state_locked(newState: number): void { - Atomics.store(this.comm, this.STATE_IDX, newState); - } - - private wait_for_state_change_to(is_ready: (state: number) => boolean, msg: string): void { - // Wait for webworker - // - Atomics.wait() is not permissible on the main thread. - for (; ;) { - const done = this.using_lock(() => { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state == this.STATE_REQ_FAILED) - throw new OperationFailedError(`Worker failed during ${msg} with state=${state}`); - - if (is_ready(state)) - return true; - }); - if (done) return; - } - } - - private read_from_msg(begin: number, end: number): string { - const slicedMessage: number[] = []; - this.msg.slice(begin, end).forEach((value, index) => slicedMessage[index] = value); - return String.fromCharCode.apply(null, slicedMessage); - } - - private using_lock(callback: Function) { - try { - this.acquire_lock(); - return callback(); - } finally { - this.release_lock(); - } - } - - private acquire_lock() { - for (; ;) { - const lock_state = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED); - - if (lock_state === this.LOCK_UNLOCKED) { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_REQ_FAILED) - throw new OperationFailedError("Worker failed"); - return; - } - } - } - - private release_lock() { - const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED); - if (result !== this.LOCK_OWNED) { - throw new Error("CRYPTO: LibraryChannel tried to release a lock that wasn't acquired: " + result); - } - } - - private static stringify_err(err: any) { - return (err instanceof Error && err.stack !== undefined) ? err.stack : err; - } - - public static create(msg_char_len: number): LibraryChannel { - if (msg_char_len === undefined) { - msg_char_len = 1024; // Default size is arbitrary but is in 'char' units (i.e. UTF-16 code points). - } - return new LibraryChannel(msg_char_len); - } -} - -export type InitCryptoMessageData = { - config: string,// serialized to avoid passing non-clonable objects - comm_buf: SharedArrayBuffer, - msg_buf: SharedArrayBuffer, - msg_char_len: number -} diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index ac6256b56799be..ea01f16a9e5c85 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -195,7 +195,6 @@ export type AssetBehaviours = | "icu" // load asset as an ICU data archive | "vfs" // load asset into the virtual filesystem (for fopen, File.Open, etc) | "dotnetwasm" // the binary of the dotnet runtime - | "js-module-crypto" // the javascript module for subtle crypto | "js-module-threads" // the javascript module for threads export type RuntimeHelpers = { diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts deleted file mode 100644 index 47aeba44882bf4..00000000000000 --- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts +++ /dev/null @@ -1,404 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { setup_proxy_console } from "../logging"; -import type { InitCryptoMessageData } from "../subtle-crypto"; -import type { MonoConfig } from "../types"; - -class FailedOrStoppedLoopError extends Error { } -class ArgumentsError extends Error { } -class WorkerFailedError extends Error { } - -class ChannelWorker { - // LOCK states - get LOCK_UNLOCKED() { return 0; } // 0 means the lock is unlocked - get LOCK_OWNED() { return 2; } // 2 means the ChannelWorker owns the lock - - // BEGIN ChannelOwner contract - shared constants. - get STATE_IDX() { return 0; } - get MSG_SIZE_IDX() { return 1; } - get LOCK_IDX() { return 2; } - - // Communication states. - get STATE_SHUTDOWN() { return -1; } // Shutdown - get STATE_IDLE() { return 0; } - get STATE_REQ() { return 1; } - get STATE_RESP() { return 2; } - get STATE_REQ_P() { return 3; } // Request has multiple parts - get STATE_RESP_P() { return 4; } // Response has multiple parts - get STATE_AWAIT() { return 5; } // Awaiting the next part - get STATE_REQ_FAILED() { return 6; } // The Request failed - get STATE_RESET() { return 7; } // Reset to a known state - // END ChannelOwner contract - shared constants. - - private comm: Int32Array; - private msg: Uint16Array; - private msg_char_len: number; - - constructor(comm_buf: SharedArrayBuffer, msg_buf: SharedArrayBuffer, msg_char_len: number) { - this.comm = new Int32Array(comm_buf); - this.msg = new Uint16Array(msg_buf); - this.msg_char_len = msg_char_len; - } - - async run_message_loop(async_op: (jsonRequest: string) => Promise) { - for (; ;) { - try { - // Wait for signal to perform operation - let state; - do { - this.wait_for_state_to_change_from(this.STATE_IDLE); - state = Atomics.load(this.comm, this.STATE_IDX); - } while (state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_SHUTDOWN && state !== this.STATE_REQ_FAILED && state !== this.STATE_RESET); - - this.throw_if_reset_or_shutdown(); - - // Read in request - const request_json = this.read_request(); - const response: any = {}; - try { - // Perform async action based on request - response.result = await async_op(request_json); - } - catch (err) { - response.error_type = typeof err; - response.error = _stringify_err(err); - console.error(`MONO_WASM: Request error: ${response.error}. req was: ${request_json}`); - } - - // Send response - this.send_response(JSON.stringify(response)); - } catch (err) { - if (err instanceof FailedOrStoppedLoopError) { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_SHUTDOWN) - break; - if (state === this.STATE_RESET) - console.debug("MONO_WASM: caller failed, resetting worker"); - } else { - console.error(`MONO_WASM: Worker failed to handle the request: ${_stringify_err(err)}`); - this.using_lock(() => { - this.change_state_locked(this.STATE_REQ_FAILED); - }); - - console.debug("MONO_WASM: set state to failed, now waiting to get RESET"); - Atomics.wait(this.comm, this.STATE_IDX, this.STATE_REQ_FAILED); - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state !== this.STATE_RESET) { - throw new WorkerFailedError(`expected to RESET, but got ${state}`); - } - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - Atomics.store(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED); - this.change_state_locked(this.STATE_IDLE); - }); - } - - const state = Atomics.load(this.comm, this.STATE_IDX); - const lock_state = Atomics.load(this.comm, this.LOCK_IDX); - - if (state !== this.STATE_IDLE && state !== this.STATE_REQ && state !== this.STATE_REQ_P) - console.error(`MONO_WASM: -- state is not idle at the top of the loop: ${state}, and lock_state: ${lock_state}`); - if (lock_state !== this.LOCK_UNLOCKED && state !== this.STATE_REQ && state !== this.STATE_REQ_P && state !== this.STATE_IDLE) - console.error(`MONO_WASM: -- lock is not unlocked at the top of the loop: ${lock_state}, and state: ${state}`); - } - - this.using_lock(() => { - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_SHUTDOWN); - }); - console.debug("MONO_WASM: ******* run_message_loop ending"); - } - - private read_request(): string { - let request = ""; - for (; ;) { - const done = this.using_lock(() => { - this.throw_if_reset_or_shutdown(); - - // Get the current state and message size - const state = Atomics.load(this.comm, this.STATE_IDX); - const size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX); - - const view = this.msg.subarray(0, size_to_read); - const part = String.fromCharCode(...view); - // Append the latest part of the message. - request += part; - - // The request is complete. - if (state === this.STATE_REQ) { - return true; - } else if (state !== this.STATE_REQ_P) { - throw new Error(`Unexpected state ${state}`); - } - - // Shutdown the worker. - this.throw_if_reset_or_shutdown(); - - // Reset the size and transition to await state. - Atomics.store(this.comm, this.MSG_SIZE_IDX, 0); - this.change_state_locked(this.STATE_AWAIT); - }); - if (done) { - break; - } - - this.wait_for_state_to_change_from(this.STATE_AWAIT); - } - - return request; - } - - private send_response(msg: string) { - if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ) - throw new WorkerFailedError("WORKER: Invalid sync communication channel state."); - - const msg_len = msg.length; - let msg_written = 0; - - for (; ;) { - const state = this.using_lock(() => { - // Write the message and return how much was written. - const wrote = this.write_to_msg(msg, msg_written, msg_len); - msg_written += wrote; - - // Indicate how much was written to the this.msg buffer. - Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote); - - // Indicate if this was the whole message or part of it. - const state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P; - - // Update the state - this.change_state_locked(state); - - return state; - }); - - // Wait for the transition to know the main thread has - // received the response by moving onto a new state. - this.wait_for_state_to_change_from(state); - - // Done sending response. - if (state === this.STATE_RESP) { - break; - } else if (state !== this.STATE_RESP_P) { - throw new Error(`Unexpected state ${state}`); - } - } - } - - private write_to_msg(input: string, start: number, input_len: number) { - let mi = 0; - let ii = start; - while (mi < this.msg_char_len && ii < input_len) { - this.msg[mi] = input.charCodeAt(ii); - ii++; // Next character - mi++; // Next buffer index - } - return ii - start; - } - - private change_state_locked(newState: number) { - Atomics.store(this.comm, this.STATE_IDX, newState); - } - - private using_lock(callback: Function) { - try { - this.acquire_lock(); - return callback(); - } finally { - this.release_lock(); - } - } - - private acquire_lock() { - for (; ;) { - const lockState = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED); - this.throw_if_reset_or_shutdown(); - - if (lockState === this.LOCK_UNLOCKED) - return; - } - } - - private release_lock() { - const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED); - if (result !== this.LOCK_OWNED) { - throw new WorkerFailedError("CRYPTO: ChannelWorker tried to release a lock that wasn't acquired: " + result); - } - } - - private wait_for_state_to_change_from(expected_state: number) { - Atomics.wait(this.comm, this.STATE_IDX, expected_state); - this.throw_if_reset_or_shutdown(); - } - - private throw_if_reset_or_shutdown() { - const state = Atomics.load(this.comm, this.STATE_IDX); - if (state === this.STATE_RESET || state === this.STATE_SHUTDOWN) - throw new FailedOrStoppedLoopError(); - } -} - -async function call_digest(type: number, data: Uint8Array) { - const digest_type = get_hash_name(type); - - // The 'crypto' API is not available in non-browser - // environments (for example, v8 server). - const digest = await crypto.subtle.digest(digest_type, data); - return Array.from(new Uint8Array(digest)); -} - -async function sign(type: number, key: Uint8Array, data: Uint8Array) { - const hash_name = get_hash_name(type); - - if (key.length === 0) { - // crypto.subtle.importKey will raise an error for an empty key. - // To prevent an error, reset it to a key with just a `0x00` byte. This is equivalent - // since HMAC keys get zero-extended up to the block size of the algorithm. - key = new Uint8Array([0]); - } - - const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: hash_name }, false /* extractable */, ["sign"]); - const signResult = await crypto.subtle.sign("HMAC", cryptoKey, data); - return Array.from(new Uint8Array(signResult)); -} - -async function derive_bits(password: Uint8Array, salt: Uint8Array, iterations: number, hashAlgorithm: number, lengthInBytes: number) { - const hash_name = get_hash_name(hashAlgorithm); - - const passwordKey = await importKey(password, "PBKDF2", ["deriveBits"]); - const result = await crypto.subtle.deriveBits( - { - name: "PBKDF2", - salt: salt, - iterations: iterations, - hash: hash_name - }, - passwordKey, - lengthInBytes * 8 // deriveBits takes number of bits - ); - - return Array.from(new Uint8Array(result)); -} - -function get_hash_name(type: number) { - switch (type) { - case 0: return "SHA-1"; - case 1: return "SHA-256"; - case 2: return "SHA-384"; - case 3: return "SHA-512"; - default: - throw new ArgumentsError("CRYPTO: Unknown digest: " + type); - } -} - -const AesBlockSizeBytes = 16; // 128 bits - -async function encrypt_decrypt(isEncrypting: boolean, key: number[], iv: number[], data: number[]) { - const algorithmName = "AES-CBC"; - const keyUsage: KeyUsage[] = isEncrypting ? ["encrypt"] : ["encrypt", "decrypt"]; - const cryptoKey = await importKey(new Uint8Array(key), algorithmName, keyUsage); - const algorithm = { - name: algorithmName, - iv: new Uint8Array(iv) - }; - - const result = await (isEncrypting ? - crypto.subtle.encrypt( - algorithm, - cryptoKey, - new Uint8Array(data)) : - decrypt( - algorithm, - cryptoKey, - data)); - - let resultByteArray = new Uint8Array(result); - if (isEncrypting) { - // trim off the last block, which is always a padding block. - resultByteArray = resultByteArray.slice(0, resultByteArray.length - AesBlockSizeBytes); - } - return Array.from(resultByteArray); -} - -async function decrypt(algorithm: Algorithm, cryptoKey: CryptoKey, data: number[]) { - // crypto.subtle AES-CBC will only allow a PaddingMode of PKCS7, but we need to use - // PaddingMode None. To simulate this, we only decrypt full blocks of data, with an extra full - // padding block of 0x10 (16) bytes appended to data. crypto.subtle will see that padding block and return - // the fully decrypted message. To create the encrypted padding block, we encrypt an empty array using the - // last block of the cipher text as the IV. This will create a full block of padding bytes. - - const paddingBlockIV = new Uint8Array(data).slice(data.length - AesBlockSizeBytes); - const empty = new Uint8Array(); - const encryptedPaddingBlockResult = await crypto.subtle.encrypt( - { - name: algorithm.name, - iv: paddingBlockIV - }, - cryptoKey, - empty - ); - - const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult); - for (let i = 0; i < encryptedPaddingBlock.length; i++) { - data.push(encryptedPaddingBlock[i]); - } - - return await crypto.subtle.decrypt( - algorithm, - cryptoKey, - new Uint8Array(data)); -} - -function importKey(key: ArrayBuffer, algorithmName: string, keyUsage: KeyUsage[]) { - return crypto.subtle.importKey( - "raw", - key, - { - name: algorithmName - }, - false /* extractable */, - keyUsage); -} - -// Operation to perform. -async function handle_req_async(jsonRequest: string): Promise { - const req = JSON.parse(jsonRequest); - - if (req.func === "digest") { - return await call_digest(req.type, new Uint8Array(req.data)); - } - else if (req.func === "sign") { - return await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data)); - } - else if (req.func === "encrypt_decrypt") { - return await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data); - } - else if (req.func === "derive_bits") { - return await derive_bits(new Uint8Array(req.password), new Uint8Array(req.salt), req.iterations, req.hashAlgorithm, req.lengthInBytes); - } - else { - throw new ArgumentsError("CRYPTO: Unknown request: " + req.func); - } -} - -function _stringify_err(err: any) { - return (err instanceof Error && err.stack !== undefined) ? err.stack : err; -} - -let s_channel; -let config: MonoConfig = null; - -// Initialize WebWorker -self.addEventListener("message", (event: MessageEvent) => { - const data = event.data as InitCryptoMessageData; - config = data && data.config ? JSON.parse(data.config) : {}; - if (config.diagnosticTracing) { - setup_proxy_console("crypto-worker", console, self.location.origin); - } - s_channel = new ChannelWorker(data.comm_buf, data.msg_buf, data.msg_char_len); - s_channel.run_message_loop(handle_req_async); -}); diff --git a/src/mono/wasm/templates/templates/browser/.template.config/template.json b/src/mono/wasm/templates/templates/browser/.template.config/template.json index 4209d083adbcc5..8051e4c6aab777 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -6,6 +6,7 @@ "name": "WebAssembly Browser App", "shortName": "wasmbrowser", "sourceName": "browser.0", + "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" diff --git a/src/mono/wasm/templates/templates/console/.template.config/template.json b/src/mono/wasm/templates/templates/console/.template.config/template.json index 15f0c9041de09f..8ead39edc0f8c2 100644 --- a/src/mono/wasm/templates/templates/console/.template.config/template.json +++ b/src/mono/wasm/templates/templates/console/.template.config/template.json @@ -6,6 +6,7 @@ "name": "WebAssembly Console App", "shortName": "wasmconsole", "sourceName": "console.0", + "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 8ba45094fb98e2..cac849c58181ef 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -26,7 +26,6 @@ <_EmccCompileRspPath>$(NativeBinDir)src\emcc-compile.rsp <_EmccLinkRspPath>$(NativeBinDir)src\emcc-link.rsp false - $(RepoRoot)\src\native\libs\System.Security.Cryptography.Native.Browser @@ -62,7 +61,6 @@ - @@ -295,7 +293,6 @@ $(NativeBinDir)dotnet-legacy.d.ts; $(NativeBinDir)package.json; $(NativeBinDir)dotnet.wasm; - $(NativeBinDir)\src\dotnet-crypto-worker.js; $(NativeBinDir)dotnet.timezones.blat" DestinationFolder="$(MicrosoftNetCoreAppRuntimePackNativeDir)" SkipUnchangedFiles="true" /> diff --git a/src/native/libs/CMakeLists.txt b/src/native/libs/CMakeLists.txt index cbb208d0a7ffb4..11d3799129bedd 100644 --- a/src/native/libs/CMakeLists.txt +++ b/src/native/libs/CMakeLists.txt @@ -156,7 +156,7 @@ if (CLR_CMAKE_TARGET_UNIX OR CLR_CMAKE_TARGET_BROWSER) add_subdirectory(System.Native) if (CLR_CMAKE_TARGET_BROWSER) - add_subdirectory(System.Security.Cryptography.Native.Browser) + # skip for now elseif (CLR_CMAKE_TARGET_MACCATALYST) add_subdirectory(System.Net.Security.Native) # System.Security.Cryptography.Native is intentionally disabled on iOS diff --git a/src/native/libs/System.Native/pal_io.c b/src/native/libs/System.Native/pal_io.c index 94dca73ccb58d6..7f775ff88697c7 100644 --- a/src/native/libs/System.Native/pal_io.c +++ b/src/native/libs/System.Native/pal_io.c @@ -1479,6 +1479,141 @@ static int16_t ConvertLockType(int16_t managedLockType) } } +#if !HAVE_NON_LEGACY_STATFS || defined(__APPLE__) +static uint32_t MapFileSystemNameToEnum(const char* fileSystemName) +{ + uint32_t result = 0; + + if (strcmp(fileSystemName, "adfs") == 0) result = 0xADF5; + else if (strcmp(fileSystemName, "affs") == 0) result = 0xADFF; + else if (strcmp(fileSystemName, "afs") == 0) result = 0x5346414F; + else if (strcmp(fileSystemName, "anoninode") == 0) result = 0x09041934; + else if (strcmp(fileSystemName, "apfs") == 0) result = 0x1A; + else if (strcmp(fileSystemName, "aufs") == 0) result = 0x61756673; + else if (strcmp(fileSystemName, "autofs") == 0) result = 0x0187; + else if (strcmp(fileSystemName, "autofs4") == 0) result = 0x6D4A556D; + else if (strcmp(fileSystemName, "befs") == 0) result = 0x42465331; + else if (strcmp(fileSystemName, "bdevfs") == 0) result = 0x62646576; + else if (strcmp(fileSystemName, "bfs") == 0) result = 0x1BADFACE; + else if (strcmp(fileSystemName, "bpf_fs") == 0) result = 0xCAFE4A11; + else if (strcmp(fileSystemName, "binfmt_misc") == 0) result = 0x42494E4D; + else if (strcmp(fileSystemName, "bootfs") == 0) result = 0xA56D3FF9; + else if (strcmp(fileSystemName, "btrfs") == 0) result = 0x9123683E; + else if (strcmp(fileSystemName, "ceph") == 0) result = 0x00C36400; + else if (strcmp(fileSystemName, "cgroupfs") == 0) result = 0x0027E0EB; + else if (strcmp(fileSystemName, "cgroup2fs") == 0) result = 0x63677270; + else if (strcmp(fileSystemName, "cifs") == 0) result = 0xFF534D42; + else if (strcmp(fileSystemName, "coda") == 0) result = 0x73757245; + else if (strcmp(fileSystemName, "coherent") == 0) result = 0x012FF7B7; + else if (strcmp(fileSystemName, "configfs") == 0) result = 0x62656570; + else if (strcmp(fileSystemName, "cpuset") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "cramfs") == 0) result = 0x28CD3D45; + else if (strcmp(fileSystemName, "ctfs") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "debugfs") == 0) result = 0x64626720; + else if (strcmp(fileSystemName, "dev") == 0) result = 0x1373; + else if (strcmp(fileSystemName, "devfs") == 0) result = 0x1373; + else if (strcmp(fileSystemName, "devpts") == 0) result = 0x1CD1; + else if (strcmp(fileSystemName, "ecryptfs") == 0) result = 0xF15F; + else if (strcmp(fileSystemName, "efs") == 0) result = 0x00414A53; + else if (strcmp(fileSystemName, "exofs") == 0) result = 0x5DF5; + else if (strcmp(fileSystemName, "ext") == 0) result = 0x137D; + else if (strcmp(fileSystemName, "ext2_old") == 0) result = 0xEF51; + else if (strcmp(fileSystemName, "ext2") == 0) result = 0xEF53; + else if (strcmp(fileSystemName, "ext3") == 0) result = 0xEF53; + else if (strcmp(fileSystemName, "ext4") == 0) result = 0xEF53; + else if (strcmp(fileSystemName, "f2fs") == 0) result = 0xF2F52010; + else if (strcmp(fileSystemName, "fat") == 0) result = 0x4006; + else if (strcmp(fileSystemName, "fd") == 0) result = 0xF00D1E; + else if (strcmp(fileSystemName, "fhgfs") == 0) result = 0x19830326; + else if (strcmp(fileSystemName, "fuse") == 0) result = 0x65735546; + else if (strcmp(fileSystemName, "fuseblk") == 0) result = 0x65735546; + else if (strcmp(fileSystemName, "fusectl") == 0) result = 0x65735543; + else if (strcmp(fileSystemName, "futexfs") == 0) result = 0x0BAD1DEA; + else if (strcmp(fileSystemName, "gfsgfs2") == 0) result = 0x1161970; + else if (strcmp(fileSystemName, "gfs2") == 0) result = 0x01161970; + else if (strcmp(fileSystemName, "gpfs") == 0) result = 0x47504653; + else if (strcmp(fileSystemName, "hfs") == 0) result = 0x4244; + else if (strcmp(fileSystemName, "hfsplus") == 0) result = 0x482B; + else if (strcmp(fileSystemName, "hpfs") == 0) result = 0xF995E849; + else if (strcmp(fileSystemName, "hugetlbfs") == 0) result = 0x958458F6; + else if (strcmp(fileSystemName, "inodefs") == 0) result = 0x11307854; + else if (strcmp(fileSystemName, "inotifyfs") == 0) result = 0x2BAD1DEA; + else if (strcmp(fileSystemName, "isofs") == 0) result = 0x9660; + else if (strcmp(fileSystemName, "jffs") == 0) result = 0x07C0; + else if (strcmp(fileSystemName, "jffs2") == 0) result = 0x72B6; + else if (strcmp(fileSystemName, "jfs") == 0) result = 0x3153464A; + else if (strcmp(fileSystemName, "kafs") == 0) result = 0x6B414653; + else if (strcmp(fileSystemName, "lofs") == 0) result = 0xEF53; + else if (strcmp(fileSystemName, "logfs") == 0) result = 0xC97E8168; + else if (strcmp(fileSystemName, "lustre") == 0) result = 0x0BD00BD0; + else if (strcmp(fileSystemName, "minix_old") == 0) result = 0x137F; + else if (strcmp(fileSystemName, "minix") == 0) result = 0x138F; + else if (strcmp(fileSystemName, "minix2") == 0) result = 0x2468; + else if (strcmp(fileSystemName, "minix2v2") == 0) result = 0x2478; + else if (strcmp(fileSystemName, "minix3") == 0) result = 0x4D5A; + else if (strcmp(fileSystemName, "mntfs") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "mqueue") == 0) result = 0x19800202; + else if (strcmp(fileSystemName, "msdos") == 0) result = 0x4D44; + else if (strcmp(fileSystemName, "nfs") == 0) result = 0x6969; + else if (strcmp(fileSystemName, "nfsd") == 0) result = 0x6E667364; + else if (strcmp(fileSystemName, "nilfs") == 0) result = 0x3434; + else if (strcmp(fileSystemName, "novell") == 0) result = 0x564C; + else if (strcmp(fileSystemName, "ntfs") == 0) result = 0x5346544E; + else if (strcmp(fileSystemName, "objfs") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "ocfs2") == 0) result = 0x7461636F; + else if (strcmp(fileSystemName, "openprom") == 0) result = 0x9FA1; + else if (strcmp(fileSystemName, "omfs") == 0) result = 0xC2993D87; + else if (strcmp(fileSystemName, "overlay") == 0) result = 0x794C7630; + else if (strcmp(fileSystemName, "overlayfs") == 0) result = 0x794C764F; + else if (strcmp(fileSystemName, "panfs") == 0) result = 0xAAD7AAEA; + else if (strcmp(fileSystemName, "pipefs") == 0) result = 0x50495045; + else if (strcmp(fileSystemName, "proc") == 0) result = 0x9FA0; + else if (strcmp(fileSystemName, "pstorefs") == 0) result = 0x6165676C; + else if (strcmp(fileSystemName, "qnx4") == 0) result = 0x002F; + else if (strcmp(fileSystemName, "qnx6") == 0) result = 0x68191122; + else if (strcmp(fileSystemName, "ramfs") == 0) result = 0x858458F6; + else if (strcmp(fileSystemName, "reiserfs") == 0) result = 0x52654973; + else if (strcmp(fileSystemName, "romfs") == 0) result = 0x7275; + else if (strcmp(fileSystemName, "rootfs") == 0) result = 0x53464846; + else if (strcmp(fileSystemName, "rpc_pipefs") == 0) result = 0x67596969; + else if (strcmp(fileSystemName, "samba") == 0) result = 0x517B; + else if (strcmp(fileSystemName, "securityfs") == 0) result = 0x73636673; + else if (strcmp(fileSystemName, "selinux") == 0) result = 0xF97CFF8C; + else if (strcmp(fileSystemName, "sffs") == 0) result = 0x786F4256; + else if (strcmp(fileSystemName, "sharefs") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "smb") == 0) result = 0x517B; + else if (strcmp(fileSystemName, "smb2") == 0) result = 0xFE534D42; + else if (strcmp(fileSystemName, "sockfs") == 0) result = 0x534F434B; + else if (strcmp(fileSystemName, "squashfs") == 0) result = 0x73717368; + else if (strcmp(fileSystemName, "sysfs") == 0) result = 0x62656572; + else if (strcmp(fileSystemName, "sysv2") == 0) result = 0x012FF7B6; + else if (strcmp(fileSystemName, "sysv4") == 0) result = 0x012FF7B5; + else if (strcmp(fileSystemName, "tmpfs") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "tracefs") == 0) result = 0x74726163; + else if (strcmp(fileSystemName, "ubifs") == 0) result = 0x24051905; + else if (strcmp(fileSystemName, "udf") == 0) result = 0x15013346; + else if (strcmp(fileSystemName, "ufs") == 0) result = 0x00011954; + else if (strcmp(fileSystemName, "ufscigam") == 0) result = 0x54190100; + else if (strcmp(fileSystemName, "ufs2") == 0) result = 0x19540119; + else if (strcmp(fileSystemName, "usbdevice") == 0) result = 0x9FA2; + else if (strcmp(fileSystemName, "v9fs") == 0) result = 0x01021997; + else if (strcmp(fileSystemName, "vagrant") == 0) result = 0x786F4256; + else if (strcmp(fileSystemName, "vboxfs") == 0) result = 0x786F4256; + else if (strcmp(fileSystemName, "vmhgfs") == 0) result = 0xBACBACBC; + else if (strcmp(fileSystemName, "vxfs") == 0) result = 0xA501FCF5; + else if (strcmp(fileSystemName, "vzfs") == 0) result = 0x565A4653; + else if (strcmp(fileSystemName, "xenfs") == 0) result = 0xABBA1974; + else if (strcmp(fileSystemName, "xenix") == 0) result = 0x012FF7B4; + else if (strcmp(fileSystemName, "xfs") == 0) result = 0x58465342; + else if (strcmp(fileSystemName, "xia") == 0) result = 0x012FD16D; + else if (strcmp(fileSystemName, "udev") == 0) result = 0x01021994; + else if (strcmp(fileSystemName, "zfs") == 0) result = 0x2FC12FC1; + + assert(result != 0); + return result; +} +#endif + uint32_t SystemNative_GetFileSystemType(intptr_t fd) { #if HAVE_STATFS_VFS || HAVE_STATFS_MOUNT @@ -1489,145 +1624,22 @@ uint32_t SystemNative_GetFileSystemType(intptr_t fd) while ((statfsRes = fstatfs(ToFileDescriptor(fd), &statfsArgs)) == -1 && errno == EINTR) ; if (statfsRes == -1) return 0; +#if defined(__APPLE__) + // On OSX-like systems, f_type is version-specific. Don't use it, just map the name. + return MapFileSystemNameToEnum(statfsArgs.f_fstypename); +#else // On Linux, f_type is signed. This causes some filesystem types to be represented as // negative numbers on 32-bit platforms. We cast to uint32_t to make them positive. uint32_t result = (uint32_t)statfsArgs.f_type; return result; +#endif #elif !HAVE_NON_LEGACY_STATFS int statfsRes; struct statvfs statfsArgs; while ((statfsRes = fstatvfs(ToFileDescriptor(fd), &statfsArgs)) == -1 && errno == EINTR) ; if (statfsRes == -1) return 0; - uint32_t result = 0; - - if (strcmp(statfsArgs.f_basetype, "adfs") == 0) result = 0xADF5; - else if (strcmp(statfsArgs.f_basetype, "affs") == 0) result = 0xADFF; - else if (strcmp(statfsArgs.f_basetype, "afs") == 0) result = 0x5346414F; - else if (strcmp(statfsArgs.f_basetype, "anoninode") == 0) result = 0x09041934; - else if (strcmp(statfsArgs.f_basetype, "apfs") == 0) result = 0x1A; - else if (strcmp(statfsArgs.f_basetype, "aufs") == 0) result = 0x61756673; - else if (strcmp(statfsArgs.f_basetype, "autofs") == 0) result = 0x0187; - else if (strcmp(statfsArgs.f_basetype, "autofs4") == 0) result = 0x6D4A556D; - else if (strcmp(statfsArgs.f_basetype, "befs") == 0) result = 0x42465331; - else if (strcmp(statfsArgs.f_basetype, "bdevfs") == 0) result = 0x62646576; - else if (strcmp(statfsArgs.f_basetype, "bfs") == 0) result = 0x1BADFACE; - else if (strcmp(statfsArgs.f_basetype, "bpf_fs") == 0) result = 0xCAFE4A11; - else if (strcmp(statfsArgs.f_basetype, "binfmt_misc") == 0) result = 0x42494E4D; - else if (strcmp(statfsArgs.f_basetype, "bootfs") == 0) result = 0xA56D3FF9; - else if (strcmp(statfsArgs.f_basetype, "btrfs") == 0) result = 0x9123683E; - else if (strcmp(statfsArgs.f_basetype, "ceph") == 0) result = 0x00C36400; - else if (strcmp(statfsArgs.f_basetype, "cgroupfs") == 0) result = 0x0027E0EB; - else if (strcmp(statfsArgs.f_basetype, "cgroup2fs") == 0) result = 0x63677270; - else if (strcmp(statfsArgs.f_basetype, "cifs") == 0) result = 0xFF534D42; - else if (strcmp(statfsArgs.f_basetype, "coda") == 0) result = 0x73757245; - else if (strcmp(statfsArgs.f_basetype, "coherent") == 0) result = 0x012FF7B7; - else if (strcmp(statfsArgs.f_basetype, "configfs") == 0) result = 0x62656570; - else if (strcmp(statfsArgs.f_basetype, "cpuset") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "cramfs") == 0) result = 0x28CD3D45; - else if (strcmp(statfsArgs.f_basetype, "ctfs") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "debugfs") == 0) result = 0x64626720; - else if (strcmp(statfsArgs.f_basetype, "dev") == 0) result = 0x1373; - else if (strcmp(statfsArgs.f_basetype, "devfs") == 0) result = 0x1373; - else if (strcmp(statfsArgs.f_basetype, "devpts") == 0) result = 0x1CD1; - else if (strcmp(statfsArgs.f_basetype, "ecryptfs") == 0) result = 0xF15F; - else if (strcmp(statfsArgs.f_basetype, "efs") == 0) result = 0x00414A53; - else if (strcmp(statfsArgs.f_basetype, "exofs") == 0) result = 0x5DF5; - else if (strcmp(statfsArgs.f_basetype, "ext") == 0) result = 0x137D; - else if (strcmp(statfsArgs.f_basetype, "ext2_old") == 0) result = 0xEF51; - else if (strcmp(statfsArgs.f_basetype, "ext2") == 0) result = 0xEF53; - else if (strcmp(statfsArgs.f_basetype, "ext3") == 0) result = 0xEF53; - else if (strcmp(statfsArgs.f_basetype, "ext4") == 0) result = 0xEF53; - else if (strcmp(statfsArgs.f_basetype, "f2fs") == 0) result = 0xF2F52010; - else if (strcmp(statfsArgs.f_basetype, "fat") == 0) result = 0x4006; - else if (strcmp(statfsArgs.f_basetype, "fd") == 0) result = 0xF00D1E; - else if (strcmp(statfsArgs.f_basetype, "fhgfs") == 0) result = 0x19830326; - else if (strcmp(statfsArgs.f_basetype, "fuse") == 0) result = 0x65735546; - else if (strcmp(statfsArgs.f_basetype, "fuseblk") == 0) result = 0x65735546; - else if (strcmp(statfsArgs.f_basetype, "fusectl") == 0) result = 0x65735543; - else if (strcmp(statfsArgs.f_basetype, "futexfs") == 0) result = 0x0BAD1DEA; - else if (strcmp(statfsArgs.f_basetype, "gfsgfs2") == 0) result = 0x1161970; - else if (strcmp(statfsArgs.f_basetype, "gfs2") == 0) result = 0x01161970; - else if (strcmp(statfsArgs.f_basetype, "gpfs") == 0) result = 0x47504653; - else if (strcmp(statfsArgs.f_basetype, "hfs") == 0) result = 0x4244; - else if (strcmp(statfsArgs.f_basetype, "hfsplus") == 0) result = 0x482B; - else if (strcmp(statfsArgs.f_basetype, "hpfs") == 0) result = 0xF995E849; - else if (strcmp(statfsArgs.f_basetype, "hugetlbfs") == 0) result = 0x958458F6; - else if (strcmp(statfsArgs.f_basetype, "inodefs") == 0) result = 0x11307854; - else if (strcmp(statfsArgs.f_basetype, "inotifyfs") == 0) result = 0x2BAD1DEA; - else if (strcmp(statfsArgs.f_basetype, "isofs") == 0) result = 0x9660; - else if (strcmp(statfsArgs.f_basetype, "jffs") == 0) result = 0x07C0; - else if (strcmp(statfsArgs.f_basetype, "jffs2") == 0) result = 0x72B6; - else if (strcmp(statfsArgs.f_basetype, "jfs") == 0) result = 0x3153464A; - else if (strcmp(statfsArgs.f_basetype, "kafs") == 0) result = 0x6B414653; - else if (strcmp(statfsArgs.f_basetype, "lofs") == 0) result = 0xEF53; - else if (strcmp(statfsArgs.f_basetype, "logfs") == 0) result = 0xC97E8168; - else if (strcmp(statfsArgs.f_basetype, "lustre") == 0) result = 0x0BD00BD0; - else if (strcmp(statfsArgs.f_basetype, "minix_old") == 0) result = 0x137F; - else if (strcmp(statfsArgs.f_basetype, "minix") == 0) result = 0x138F; - else if (strcmp(statfsArgs.f_basetype, "minix2") == 0) result = 0x2468; - else if (strcmp(statfsArgs.f_basetype, "minix2v2") == 0) result = 0x2478; - else if (strcmp(statfsArgs.f_basetype, "minix3") == 0) result = 0x4D5A; - else if (strcmp(statfsArgs.f_basetype, "mntfs") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "mqueue") == 0) result = 0x19800202; - else if (strcmp(statfsArgs.f_basetype, "msdos") == 0) result = 0x4D44; - else if (strcmp(statfsArgs.f_basetype, "nfs") == 0) result = 0x6969; - else if (strcmp(statfsArgs.f_basetype, "nfsd") == 0) result = 0x6E667364; - else if (strcmp(statfsArgs.f_basetype, "nilfs") == 0) result = 0x3434; - else if (strcmp(statfsArgs.f_basetype, "novell") == 0) result = 0x564C; - else if (strcmp(statfsArgs.f_basetype, "ntfs") == 0) result = 0x5346544E; - else if (strcmp(statfsArgs.f_basetype, "objfs") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "ocfs2") == 0) result = 0x7461636F; - else if (strcmp(statfsArgs.f_basetype, "openprom") == 0) result = 0x9FA1; - else if (strcmp(statfsArgs.f_basetype, "omfs") == 0) result = 0xC2993D87; - else if (strcmp(statfsArgs.f_basetype, "overlay") == 0) result = 0x794C7630; - else if (strcmp(statfsArgs.f_basetype, "overlayfs") == 0) result = 0x794C764F; - else if (strcmp(statfsArgs.f_basetype, "panfs") == 0) result = 0xAAD7AAEA; - else if (strcmp(statfsArgs.f_basetype, "pipefs") == 0) result = 0x50495045; - else if (strcmp(statfsArgs.f_basetype, "proc") == 0) result = 0x9FA0; - else if (strcmp(statfsArgs.f_basetype, "pstorefs") == 0) result = 0x6165676C; - else if (strcmp(statfsArgs.f_basetype, "qnx4") == 0) result = 0x002F; - else if (strcmp(statfsArgs.f_basetype, "qnx6") == 0) result = 0x68191122; - else if (strcmp(statfsArgs.f_basetype, "ramfs") == 0) result = 0x858458F6; - else if (strcmp(statfsArgs.f_basetype, "reiserfs") == 0) result = 0x52654973; - else if (strcmp(statfsArgs.f_basetype, "romfs") == 0) result = 0x7275; - else if (strcmp(statfsArgs.f_basetype, "rootfs") == 0) result = 0x53464846; - else if (strcmp(statfsArgs.f_basetype, "rpc_pipefs") == 0) result = 0x67596969; - else if (strcmp(statfsArgs.f_basetype, "samba") == 0) result = 0x517B; - else if (strcmp(statfsArgs.f_basetype, "securityfs") == 0) result = 0x73636673; - else if (strcmp(statfsArgs.f_basetype, "selinux") == 0) result = 0xF97CFF8C; - else if (strcmp(statfsArgs.f_basetype, "sffs") == 0) result = 0x786F4256; - else if (strcmp(statfsArgs.f_basetype, "sharefs") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "smb") == 0) result = 0x517B; - else if (strcmp(statfsArgs.f_basetype, "smb2") == 0) result = 0xFE534D42; - else if (strcmp(statfsArgs.f_basetype, "sockfs") == 0) result = 0x534F434B; - else if (strcmp(statfsArgs.f_basetype, "squashfs") == 0) result = 0x73717368; - else if (strcmp(statfsArgs.f_basetype, "sysfs") == 0) result = 0x62656572; - else if (strcmp(statfsArgs.f_basetype, "sysv2") == 0) result = 0x012FF7B6; - else if (strcmp(statfsArgs.f_basetype, "sysv4") == 0) result = 0x012FF7B5; - else if (strcmp(statfsArgs.f_basetype, "tmpfs") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "tracefs") == 0) result = 0x74726163; - else if (strcmp(statfsArgs.f_basetype, "ubifs") == 0) result = 0x24051905; - else if (strcmp(statfsArgs.f_basetype, "udf") == 0) result = 0x15013346; - else if (strcmp(statfsArgs.f_basetype, "ufs") == 0) result = 0x00011954; - else if (strcmp(statfsArgs.f_basetype, "ufscigam") == 0) result = 0x54190100; - else if (strcmp(statfsArgs.f_basetype, "ufs2") == 0) result = 0x19540119; - else if (strcmp(statfsArgs.f_basetype, "usbdevice") == 0) result = 0x9FA2; - else if (strcmp(statfsArgs.f_basetype, "v9fs") == 0) result = 0x01021997; - else if (strcmp(statfsArgs.f_basetype, "vagrant") == 0) result = 0x786F4256; - else if (strcmp(statfsArgs.f_basetype, "vboxfs") == 0) result = 0x786F4256; - else if (strcmp(statfsArgs.f_basetype, "vmhgfs") == 0) result = 0xBACBACBC; - else if (strcmp(statfsArgs.f_basetype, "vxfs") == 0) result = 0xA501FCF5; - else if (strcmp(statfsArgs.f_basetype, "vzfs") == 0) result = 0x565A4653; - else if (strcmp(statfsArgs.f_basetype, "xenfs") == 0) result = 0xABBA1974; - else if (strcmp(statfsArgs.f_basetype, "xenix") == 0) result = 0x012FF7B4; - else if (strcmp(statfsArgs.f_basetype, "xfs") == 0) result = 0x58465342; - else if (strcmp(statfsArgs.f_basetype, "xia") == 0) result = 0x012FD16D; - else if (strcmp(statfsArgs.f_basetype, "udev") == 0) result = 0x01021994; - else if (strcmp(statfsArgs.f_basetype, "zfs") == 0) result = 0x2FC12FC1; - - assert(result != 0); - return result; + return MapFileSystemNameToEnum(statfsArgs.f_basetype); #else #error "Platform doesn't support fstatfs or fstatvfs" #endif diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt deleted file mode 100644 index c411aa9ee9cd66..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -project(System.Security.Cryptography.Native.Browser C) - -set (NATIVE_SOURCES - pal_crypto_webworker.c -) - -add_library (System.Security.Cryptography.Native.Browser-Static - STATIC - ${NATIVE_SOURCES} -) - -set_target_properties(System.Security.Cryptography.Native.Browser-Static PROPERTIES OUTPUT_NAME System.Security.Cryptography.Native.Browser CLEAN_DIRECT_OUTPUT 1) - -install (TARGETS System.Security.Cryptography.Native.Browser-Static DESTINATION ${STATIC_LIB_DESTINATION}) diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h deleted file mode 100644 index 775fe634536e26..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_browser.h +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma once - -#include - -#ifndef __EMSCRIPTEN__ -#error Cryptography Native Browser is designed to be compiled with Emscripten. -#endif // __EMSCRIPTEN__ - -#ifndef PALEXPORT -#ifdef TARGET_UNIX -#define PALEXPORT __attribute__ ((__visibility__ ("default"))) -#else -#define PALEXPORT __declspec(dllexport) -#endif -#endif // PALEXPORT diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c deleted file mode 100644 index 6514ddafb3a04f..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.c +++ /dev/null @@ -1,99 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal_browser.h" -#include "pal_crypto_webworker.h" - -// Forward declarations -extern int32_t dotnet_browser_simple_digest_hash( - enum simple_digest ver, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -extern int32_t dotnet_browser_sign( - enum simple_digest hashAlgorithm, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -extern int32_t dotnet_browser_encrypt_decrypt( - int32_t encrypting, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* iv_buffer, - int32_t iv_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -extern int32_t dotnet_browser_derive_bits( - uint8_t* password_buffer, - int32_t password_len, - uint8_t* salt_buffer, - int32_t salt_len, - int32_t iterations, - enum simple_digest hashAlgorithm, - uint8_t* output_buffer, - int32_t output_len); - -extern int32_t dotnet_browser_can_use_subtle_crypto_impl(void); - -int32_t SystemCryptoNativeBrowser_SimpleDigestHash( - enum simple_digest ver, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len) -{ - return dotnet_browser_simple_digest_hash(ver, input_buffer, input_len, output_buffer, output_len); -} - -int32_t SystemCryptoNativeBrowser_Sign( - enum simple_digest hashAlgorithm, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len) -{ - return dotnet_browser_sign(hashAlgorithm, key_buffer, key_len, input_buffer, input_len, output_buffer, output_len); -} - -int32_t SystemCryptoNativeBrowser_EncryptDecrypt( - int32_t encrypting, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* iv_buffer, - int32_t iv_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len) -{ - return dotnet_browser_encrypt_decrypt(encrypting, key_buffer, key_len, iv_buffer, iv_len, input_buffer, input_len, output_buffer, output_len); -} - -int32_t SystemCryptoNativeBrowser_DeriveBits( - uint8_t* password_buffer, - int32_t password_len, - uint8_t* salt_buffer, - int32_t salt_len, - int32_t iterations, - enum simple_digest hashAlgorithm, - uint8_t* output_buffer, - int32_t output_len) -{ - return dotnet_browser_derive_bits(password_buffer, password_len, salt_buffer, salt_len, iterations, hashAlgorithm, output_buffer, output_len); -} - -int32_t SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl(void) -{ - return dotnet_browser_can_use_subtle_crypto_impl(); -} diff --git a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h b/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h deleted file mode 100644 index 6129a6a286c0e6..00000000000000 --- a/src/native/libs/System.Security.Cryptography.Native.Browser/pal_crypto_webworker.h +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma once - -#include - -// These values are also defined in the System.Security.Cryptography library's -// browser-crypto implementation, and utilized in the dotnet-crypto-worker in the wasm runtime. -enum simple_digest -{ - sd_sha_1, - sd_sha_256, - sd_sha_384, - sd_sha_512, -}; - -PALEXPORT int32_t SystemCryptoNativeBrowser_SimpleDigestHash( - enum simple_digest ver, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -PALEXPORT int32_t SystemCryptoNativeBrowser_Sign( - enum simple_digest ver, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -PALEXPORT int32_t SystemCryptoNativeBrowser_EncryptDecrypt( - int32_t encrypting, - uint8_t* key_buffer, - int32_t key_len, - uint8_t* iv_buffer, - int32_t iv_len, - uint8_t* input_buffer, - int32_t input_len, - uint8_t* output_buffer, - int32_t output_len); - -PALEXPORT int32_t SystemCryptoNativeBrowser_DeriveBits( - uint8_t* password_buffer, - int32_t password_len, - uint8_t* salt_buffer, - int32_t salt_len, - int32_t iterations, - enum simple_digest hashAlgorithm, - uint8_t* output_buffer, - int32_t output_len); - -PALEXPORT int32_t SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl(void); diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 291af85bde42a6..55e93530680f41 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -488,15 +488,11 @@ private static bool CheckAllUpToDate(IList argsList) { // compare original assembly vs it's outputs.. all it's outputs! string assemblyPath = args.AOTAssembly.GetMetadata("FullPath"); - if (args.ProxyFiles.Any(pf => IsNewerThanOutput(assemblyPath, pf.TargetFile))) + if (args.ProxyFiles.Any(pf => Utils.IsNewerThan(assemblyPath, pf.TargetFile))) return false; } return true; - - static bool IsNewerThanOutput(string inFile, string outFile) - => !File.Exists(inFile) || !File.Exists(outFile) || - (File.GetLastWriteTimeUtc(inFile) > File.GetLastWriteTimeUtc(outFile)); } private IEnumerable FilterOutUnmanagedAssemblies(IEnumerable assemblies) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 2c624214937d66..8ed6c2c13711e8 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -23,6 +23,10 @@ public static string GetEmbeddedResource(string file) return reader.ReadToEnd(); } + public static bool IsNewerThan(string inFile, string outFile) + => !File.Exists(inFile) || !File.Exists(outFile) || + (File.GetLastWriteTimeUtc(inFile) > File.GetLastWriteTimeUtc(outFile)); + public static (int exitCode, string output) RunShellCommand( TaskLoggingHelper logger, string command, @@ -233,6 +237,9 @@ public static string ComputeHash(string filepath) #if NETCOREAPP public static void DirectoryCopy(string sourceDir, string destDir, Func? predicate=null) { + if (!Directory.Exists(destDir)) + Directory.CreateDirectory(destDir); + string[] files = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories); foreach (string file in files) { diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index de43aee1d97361..c45a413ef53e6f 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -109,11 +109,6 @@ private sealed class WasmEntry : AssetEntry public WasmEntry(string name) : base(name, "dotnetwasm") { } } - private sealed class CryptoWorkerEntry : AssetEntry - { - public CryptoWorkerEntry(string name) : base(name, "js-module-crypto") { } - } - private sealed class ThreadsWorkerEntry : AssetEntry { public ThreadsWorkerEntry(string name) : base(name, "js-module-threads") { } @@ -320,7 +315,6 @@ private bool ExecuteInternal () config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); config.Assets.Add(new WasmEntry ("dotnet.wasm") ); - config.Assets.Add(new CryptoWorkerEntry ("dotnet-crypto-worker.js") ); if (IncludeThreadsWorker) config.Assets.Add(new ThreadsWorkerEntry ("dotnet.worker.js") ); diff --git a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs index 2fd3597425e343..26c043c7df1ba6 100644 --- a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs +++ b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs @@ -20,7 +20,10 @@ namespace Microsoft.Workload.Build.Tasks public class InstallWorkloadFromArtifacts : Task { [Required, NotNull] - public ITaskItem[] WorkloadIds { get; set; } = Array.Empty(); + public ITaskItem[] WorkloadIds { get; set; } = Array.Empty(); + + [Required, NotNull] + public ITaskItem[] InstallTargets { get; set; } = Array.Empty(); [Required, NotNull] public string? VersionBand { get; set; } @@ -32,22 +35,73 @@ public class InstallWorkloadFromArtifacts : Task public string? TemplateNuGetConfigPath { get; set; } [Required, NotNull] - public string? SdkDir { get; set; } + public string SdkWithNoWorkloadInstalledPath { get; set; } = string.Empty; public bool OnlyUpdateManifests{ get; set; } private const string s_nugetInsertionTag = ""; + private string AllManifestsStampPath => Path.Combine(SdkWithNoWorkloadInstalledPath, ".all-manifests.stamp"); public override bool Execute() { try { - foreach (var workloadIdItem in WorkloadIds) + if (!Directory.Exists(SdkWithNoWorkloadInstalledPath)) + throw new LogAsErrorException($"Cannot find {nameof(SdkWithNoWorkloadInstalledPath)}={SdkWithNoWorkloadInstalledPath}"); + + if (!Directory.Exists(LocalNuGetsPath)) + throw new LogAsErrorException($"Cannot find {nameof(LocalNuGetsPath)}={LocalNuGetsPath} . " + + "Set it to the Shipping packages directory in artifacts."); + + if (!InstallAllManifests()) + return false; + + if (OnlyUpdateManifests) + return !Log.HasLoggedErrors; + + InstallWorkloadRequest[] selectedRequests = InstallTargets + .SelectMany(workloadToInstall => + { + if (!HasMetadata(workloadToInstall, nameof(workloadToInstall), "Variants", Log)) + throw new LogAsErrorException($"Missing Variants metadata on item '{workloadToInstall.ItemSpec}'"); + + return workloadToInstall + .GetMetadata("Variants") + .Split(";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(v => (variant: v, target: workloadToInstall)); + }) + .SelectMany(w => + { + IEnumerable workloads = WorkloadIds.Where(wi => wi.GetMetadata("Variant") == w.variant) + .Select(wi => new InstallWorkloadRequest(wi, w.target)); + return workloads.Any() + ? workloads + : throw new LogAsErrorException($"Could not find any workload variant named '{w.variant}'"); + }).ToArray(); + + foreach (InstallWorkloadRequest req in selectedRequests) { - if (!ExecuteInternal(workloadIdItem)) + if (Directory.Exists(req.TargetPath)) + { + Log.LogMessage(MessageImportance.Low, $"Deleting directory {req.TargetPath}"); + Directory.Delete(req.TargetPath, recursive: true); + } + } + + string cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + foreach (InstallWorkloadRequest req in selectedRequests) + { + Log.LogMessage(MessageImportance.High, $"** Installing workload {req.WorkloadId} in {req.TargetPath} **"); + if (!req.Validate(Log)) return false; + + if (!ExecuteInternal(req) && !req.IgnoreErrors) + return false; + + File.WriteAllText(req.StampPath, string.Empty); } - return true; + + return !Log.HasLoggedErrors; } catch (LogAsErrorException laee) { @@ -56,73 +110,105 @@ public override bool Execute() } } - private bool ExecuteInternal(ITaskItem workloadId) + private bool ExecuteInternal(InstallWorkloadRequest req) { - if (!HasMetadata(workloadId, nameof(workloadId), "Version") || - !HasMetadata(workloadId, nameof(workloadId), "ManifestName")) + if (!File.Exists(TemplateNuGetConfigPath)) { + Log.LogError($"Cannot find TemplateNuGetConfigPath={TemplateNuGetConfigPath}"); return false; } - if (!Directory.Exists(SdkDir)) - { - Log.LogError($"Cannot find SdkDir={SdkDir}"); + Log.LogMessage(MessageImportance.Low, $"Duplicating {SdkWithNoWorkloadInstalledPath} into {req.TargetPath}"); + Utils.DirectoryCopy(SdkWithNoWorkloadInstalledPath, req.TargetPath); + + string nugetConfigContents = GetNuGetConfig(); + if (!InstallPacks(req, nugetConfigContents)) return false; - } - if (!File.Exists(TemplateNuGetConfigPath)) + UpdateAppRef(req.TargetPath, req.Version); + + return !Log.HasLoggedErrors; + } + + private bool InstallAllManifests() + { + var allManifestPkgs = Directory.EnumerateFiles(LocalNuGetsPath, "*Manifest*nupkg"); + if (!AnyInputsNewerThanOutput(AllManifestsStampPath, allManifestPkgs)) { - Log.LogError($"Cannot find TemplateNuGetConfigPath={TemplateNuGetConfigPath}"); - return false; + Log.LogMessage(MessageImportance.Low, + $"Skipping installing manifests because the {AllManifestsStampPath} " + + $"is newer than packages {string.Join(',', allManifestPkgs)}."); + return true; } - Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing workload manifest {workloadId.ItemSpec} **{Environment.NewLine}"); - string nugetConfigContents = GetNuGetConfig(); - if (!InstallWorkloadManifest(workloadId, workloadId.GetMetadata("ManifestName"), workloadId.GetMetadata("Version"), nugetConfigContents, stopOnMissing: true)) - return false; + HashSet manifestsInstalled = new(); + foreach (ITaskItem workload in WorkloadIds) + { + InstallWorkloadRequest req = new(workload, new TaskItem()); - if (OnlyUpdateManifests) - return !Log.HasLoggedErrors; + if (manifestsInstalled.Contains(req.ManifestName)) + { + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Manifests for workload {req.WorkloadId} are already installed **{Environment.NewLine}"); + continue; + } - if (!InstallPacks(workloadId, nugetConfigContents)) - return false; + Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing manifests for workload {req.WorkloadId} **"); + if (!InstallWorkloadManifest(workload, + req.ManifestName, + req.Version, + SdkWithNoWorkloadInstalledPath, + nugetConfigContents, + stopOnMissing: true)) + { + return false; + } - UpdateAppRef(workloadId.GetMetadata("Version")); + manifestsInstalled.Add(req.ManifestName); + } - return !Log.HasLoggedErrors; + File.WriteAllText(AllManifestsStampPath, string.Empty); + + return true; } - private bool InstallPacks(ITaskItem workloadId, string nugetConfigContents) + private bool InstallPacks(InstallWorkloadRequest req, string nugetConfigContents) { string nugetConfigPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); File.WriteAllText(nugetConfigPath, nugetConfigContents); - Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** workload install **{Environment.NewLine}"); + // Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** dotnet workload install {req.WorkloadId} **{Environment.NewLine}"); (int exitCode, string output) = Utils.TryRunProcess( Log, - Path.Combine(SdkDir, "dotnet"), - $"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {workloadId.ItemSpec}", + Path.Combine(req.TargetPath, "dotnet"), + $"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {req.WorkloadId}", workingDir: Path.GetTempPath(), silent: false, + logStdErrAsMessage: req.IgnoreErrors, debugMessageImportance: MessageImportance.High); if (exitCode != 0) { - Log.LogError($"workload install failed with exit code {exitCode}: {output}"); - - foreach (string dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "sdk-manifests"), "*", SearchOption.AllDirectories)) - Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "sdk-manifests", dir)}"); + if (req.IgnoreErrors) + { + Log.LogMessage(MessageImportance.High, + $"{Environment.NewLine} ** Ignoring workload installation failure exit code {exitCode}. **{Environment.NewLine}"); + } + else + { + Log.LogError($"workload install failed with exit code {exitCode}: {output}"); + } - foreach (string dir in Directory.EnumerateDirectories(Path.Combine(SdkDir, "packs"), "*", SearchOption.AllDirectories)) - Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(SdkDir, "packs", dir)}"); + foreach (string dir in Directory.EnumerateDirectories(Path.Combine(req.TargetPath, "sdk-manifests"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(req.TargetPath, "sdk-manifests", dir)}"); - return false; + foreach (string dir in Directory.EnumerateDirectories(Path.Combine(req.TargetPath, "packs"), "*", SearchOption.AllDirectories)) + Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(req.TargetPath, "packs", dir)}"); } return !Log.HasLoggedErrors; } - private void UpdateAppRef(string version) + private void UpdateAppRef(string sdkPath, string version) { Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Updating Targeting pack **{Environment.NewLine}"); @@ -131,7 +217,7 @@ private void UpdateAppRef(string version) throw new LogAsErrorException($"Could not find {pkgPath} needed to update the targeting pack to the newly built one." + " Make sure to build the subset `packs`, like `./build.sh -os browser -s mono+libs+packs`."); - string packDir = Path.Combine(SdkDir, "packs", "Microsoft.NETCore.App.Ref"); + string packDir = Path.Combine(sdkPath, "packs", "Microsoft.NETCore.App.Ref"); string[] dirs = Directory.EnumerateDirectories(packDir).ToArray(); if (dirs.Length != 1) throw new LogAsErrorException($"Expected to find exactly one versioned directory under {packDir}, but got " + @@ -150,28 +236,28 @@ private void UpdateAppRef(string version) private string GetNuGetConfig() { string contents = File.ReadAllText(TemplateNuGetConfigPath); - if (contents.IndexOf(s_nugetInsertionTag) < 0) + if (contents.IndexOf(s_nugetInsertionTag, StringComparison.InvariantCultureIgnoreCase) < 0) throw new LogAsErrorException($"Could not find {s_nugetInsertionTag} in {TemplateNuGetConfigPath}"); return contents.Replace(s_nugetInsertionTag, $@""); } - private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string nugetConfigContents, bool stopOnMissing) + private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string sdkDir, string nugetConfigContents, bool stopOnMissing) { - Log.LogMessage(MessageImportance.High, $"Installing workload manifest for {name}/{version}"); + Log.LogMessage(MessageImportance.High, $" ** Installing manifest: {name}/{version}"); // Find any existing directory with the manifest name, ignoring the case // Multiple directories for a manifest, differing only in case causes // workload install to fail due to duplicate manifests! // This is applicable only on case-sensitive filesystems - string outputDir = FindSubDirIgnoringCase(Path.Combine(SdkDir, "sdk-manifests", VersionBand), name); + string outputDir = FindSubDirIgnoringCase(Path.Combine(sdkDir, "sdk-manifests", VersionBand), name); PackageReference pkgRef = new(Name: $"{name}.Manifest-{VersionBand}", Version: version, OutputDir: outputDir, relativeSourceDir: "data"); - if (!PackageInstaller.Install(new[]{ pkgRef }, nugetConfigContents, Log, stopOnMissing)) + if (!PackageInstaller.Install(new[] { pkgRef }, nugetConfigContents, Log, stopOnMissing)) return false; string manifestDir = pkgRef.OutputDir; @@ -209,7 +295,7 @@ private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string v { foreach ((string depName, string depVersion) in manifest.DependsOn) { - if (!InstallWorkloadManifest(workloadId, depName, depVersion, nugetConfigContents, stopOnMissing: false)) + if (!InstallWorkloadManifest(workloadId, depName, depVersion, sdkDir, nugetConfigContents, stopOnMissing: false)) { Log.LogWarning($"Could not install manifest {depName}/{depVersion}. This can be ignored if the workload {workloadId.ItemSpec} doesn't depend on it."); continue; @@ -220,31 +306,35 @@ private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string v return true; } - private bool HasMetadata(ITaskItem item, string itemName, string metadataName) + private static bool HasMetadata(ITaskItem item, string itemName, string metadataName, TaskLoggingHelper log) { if (!string.IsNullOrEmpty(item.GetMetadata(metadataName))) return true; - Log.LogError($"{itemName} item ({item.ItemSpec}) is missing Name metadata"); + log.LogError($"{itemName} item ({item.ItemSpec}) is missing {metadataName} metadata"); return false; } private string FindSubDirIgnoringCase(string parentDir, string dirName) { - IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir, + string[] matchingDirs = Directory.EnumerateDirectories(parentDir, dirName, - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }) + .ToArray(); string? first = matchingDirs.FirstOrDefault(); - if (matchingDirs.Count() > 1) + if (matchingDirs.Length > 1) { - Log.LogWarning($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}" + Log.LogWarning($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs)}" + $"{Environment.NewLine}Using the first one: {first}"); } return first ?? Path.Combine(parentDir, dirName.ToLower(CultureInfo.InvariantCulture)); } + private static bool AnyInputsNewerThanOutput(string output, IEnumerable inputs) + => inputs.Any(i => Utils.IsNewerThan(i, output)); + private sealed record ManifestInformation( object Version, string Description, @@ -272,6 +362,36 @@ private sealed record PackVersionInformation( [property: JsonPropertyName("alias-to")] Dictionary AliasTo ); + + internal sealed record InstallWorkloadRequest( + ITaskItem Workload, + ITaskItem Target) + { + public string ManifestName => Workload.GetMetadata("ManifestName"); + public string Version => Workload.GetMetadata("Version"); + public string TargetPath => Target.GetMetadata("InstallPath"); + public string StampPath => Target.GetMetadata("StampPath"); + public bool IgnoreErrors => Workload.GetMetadata("IgnoreErrors").ToLowerInvariant() == "true"; + public string WorkloadId => Workload.ItemSpec; + + public bool Validate(TaskLoggingHelper log) + { + if (!HasMetadata(Workload, nameof(Workload), "Version", log) || + !HasMetadata(Workload, nameof(Workload), "ManifestName", log) || + !HasMetadata(Target, nameof(Target), "InstallPath", log)) + { + return false; + } + + if (string.IsNullOrEmpty(TargetPath)) + { + log.LogError($"InstallPath is empty for workload {Workload.ItemSpec}"); + return false; + } + + return true; + } + } } internal sealed record PackageReference(string Name, diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs index f034d2fcc40a8f..8123c9cfc1c9ed 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs @@ -21,7 +21,7 @@ public BlazorWasmBuildPublishTests(ITestOutputHelper output, SharedBuildPerTestC _enablePerTestCleanup = true; } - [Theory] + [Theory, TestCategory("no-workload")] [InlineData("Debug")] [InlineData("Release")] public void DefaultTemplate_WithoutWorkload(string config) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs index c119db17c282cf..8ce212003ab122 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; #nullable enable @@ -25,6 +24,7 @@ public class BuildEnvironment public string LogRootPath { get; init; } public string WorkloadPacksDir { get; init; } + public string BuiltNuGetsPath { get; init; } public static readonly string RelativeTestAssetsPath = @"..\testassets\"; public static readonly string TestAssetsPath = Path.Combine(AppContext.BaseDirectory, "testassets"); @@ -51,7 +51,7 @@ public BuildEnvironment() "..", "..", "..", - "dotnet-workload"); + "dotnet-net7"); if (Directory.Exists(probePath)) sdkForWorkloadPath = Path.GetFullPath(probePath); else @@ -99,6 +99,11 @@ public BuildEnvironment() DirectoryBuildTargetsContents = s_directoryBuildTargetsForLocal; } + if (EnvironmentVariables.BuiltNuGetsPath is null || !Directory.Exists(EnvironmentVariables.BuiltNuGetsPath)) + throw new Exception($"Cannot find 'BUILT_NUGETS_PATH={EnvironmentVariables.BuiltNuGetsPath}'"); + + BuiltNuGetsPath = EnvironmentVariables.BuiltNuGetsPath; + // `runtime` repo's build environment sets these, and they // mess up the build for the test project, which is using a different // dotnet @@ -112,6 +117,12 @@ public BuildEnvironment() // helps with debugging EnvVars["WasmNativeStrip"] = "false"; + // Works around an issue in msbuild due to which + // second, and subsequent builds fail without any details + // in the logs + EnvVars["DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER"] = "1"; + DefaultBuildArgs += " /nr:false"; + if (OperatingSystem.IsWindows()) { EnvVars["WasmCachePath"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index a2fd1c3c247cea..d64c9739315100 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -41,6 +41,7 @@ public abstract class BuildTestBase : IClassFixture? envVars = null, string targetFramework = DefaultTargetFramework, string? extraXHarnessMonoArgs = null, + string? extraXHarnessArgs = null, string jsRelativePath = "test-main.js") { buildDir ??= _projectDir; @@ -158,13 +160,15 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, throw new InvalidOperationException("Running tests with V8 on windows isn't supported"); // Use wasm-console.log to get the xharness output for non-browser cases - (string testCommand, string extraXHarnessArgs, bool useWasmConsoleOutput) = host switch + (string testCommand, string xharnessArgs, bool useWasmConsoleOutput) = host switch { RunHost.V8 => ("wasm test", $"--js-file={jsRelativePath} --engine=V8 -v trace", true), RunHost.NodeJS => ("wasm test", $"--js-file={jsRelativePath} --engine=NodeJS -v trace", true), _ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop", false) }; + extraXHarnessArgs += " " + xharnessArgs; + string testLogPath = Path.Combine(_logPath, host.ToString()); string output = RunWithXHarness( testCommand, @@ -213,6 +217,16 @@ protected static string RunWithXHarness(string testCommand, string testLogPath, args.Append($" --expected-exit-code={expectedAppExitCode}"); args.Append($" {extraXHarnessArgs ?? string.Empty}"); + if (File.Exists("/.dockerenv")) + args.Append(" --browser-arg=--no-sandbox"); + + if (!string.IsNullOrEmpty(EnvironmentVariables.BrowserPathForTests)) + { + if (!File.Exists(EnvironmentVariables.BrowserPathForTests)) + throw new Exception($"Cannot find BROWSER_PATH_FOR_TESTS={EnvironmentVariables.BrowserPathForTests}"); + args.Append($" --browser-path=\"{EnvironmentVariables.BrowserPathForTests}\""); + } + args.Append(" -- "); if (extraXHarnessMonoArgs != null) { @@ -325,7 +339,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp { _testOutput.WriteLine ($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}"); - Assert.True(product.Result, $"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); + if (!product.Result) + throw new XunitException($"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); _projectDir = product.ProjectDir; // use this test's id for the run logs @@ -359,7 +374,6 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}{logFileSuffix}.binlog"); _testOutput.WriteLine($"-------- Building ---------"); _testOutput.WriteLine($"Binlog path: {logFilePath}"); - _testOutput.WriteLine($"Binlog path: {logFilePath}"); sb.Append($" /bl:\"{logFilePath}\" /nologo"); sb.Append($" /v:{options.Verbosity ?? "minimal"}"); if (buildArgs.ExtraBuildArgs != null) @@ -410,11 +424,24 @@ public void InitBlazorWasmProjectDir(string id) Directory.CreateDirectory(_projectDir); Directory.CreateDirectory(Path.Combine(_projectDir, ".nuget")); - File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), Path.Combine(_projectDir, "nuget.config")); + File.WriteAllText(Path.Combine(_projectDir, "nuget.config"), + GetNuGetConfigWithLocalPackagesPath( + Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), + s_buildEnv.BuiltNuGetsPath)); + File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); } + private static string GetNuGetConfigWithLocalPackagesPath(string templatePath, string localNuGetsPath) + { + string contents = File.ReadAllText(templatePath); + if (contents.IndexOf(s_nugetInsertionTag, StringComparison.InvariantCultureIgnoreCase) < 0) + throw new Exception($"Could not find {s_nugetInsertionTag} in {templatePath}"); + + return contents.Replace(s_nugetInsertionTag, $@""); + } + public string CreateWasmTemplateProject(string id, string template = "wasmbrowser") { InitPaths(id); @@ -488,6 +515,7 @@ public string CreateBlazorWasmTemplateProject(string id) $"-bl:{logPath}", $"-p:Configuration={config}", "-p:BlazorEnableCompression=false", + "-nr:false", setWasmDevel ? "-p:_WasmDevel=true" : string.Empty }.Concat(extraArgs).ToArray(); @@ -552,8 +580,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string projectName, "dotnet.timezones.blat", "dotnet.wasm", "mono-config.json", - "dotnet.js", - "dotnet-crypto-worker.js" + "dotnet.js" }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); @@ -636,10 +663,10 @@ protected static void AssertFile(string file0, string file1, string? label=null, protected (int exitCode, string buildOutput) AssertBuild(string args, string label="build", bool expectSuccess=true, IDictionary? envVars=null, int? timeoutMs=null) { var result = RunProcess(s_buildEnv.DotNet, _testOutput, args, workingDir: _projectDir, label: label, envVars: envVars, timeoutMs: timeoutMs ?? s_defaultPerTestTimeoutMs); - if (expectSuccess) - Assert.True(0 == result.exitCode, $"Build process exited with non-zero exit code: {result.exitCode}"); - else - Assert.True(0 != result.exitCode, $"Build should have failed, but it didn't. Process exited with exitCode : {result.exitCode}"); + if (expectSuccess && result.exitCode != 0) + throw new XunitException($"Build process exited with non-zero exit code: {result.exitCode}"); + if (!expectSuccess && result.exitCode == 0) + throw new XunitException($"Build should have failed, but it didn't. Process exited with exitCode : {result.exitCode}"); return result; } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs index a2439797566848..87589af53f491b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/EnvironmentVariables.cs @@ -16,5 +16,7 @@ internal static class EnvironmentVariables internal static readonly string? TestLogPath = Environment.GetEnvironmentVariable("TEST_LOG_PATH"); internal static readonly string? SkipProjectCleanup = Environment.GetEnvironmentVariable("SKIP_PROJECT_CLEANUP"); internal static readonly string? XHarnessCliPath = Environment.GetEnvironmentVariable("XHARNESS_CLI_PATH"); + internal static readonly string? BuiltNuGetsPath = Environment.GetEnvironmentVariable("BUILT_NUGETS_PATH"); + internal static readonly string? BrowserPathForTests = Environment.GetEnvironmentVariable("BROWSER_PATH_FOR_TESTS"); } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index d5f0d0f59755a7..278f5872125e1e 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -140,7 +140,7 @@ public static int Main() output); string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; - Assert.Contains(cryptoInitMsg, output); + Assert.DoesNotContain(cryptoInitMsg, output); } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md index 79a0a8945a89d9..a42e30078c8dff 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/README.md @@ -38,7 +38,7 @@ For this, the builds get cached using `BuildArgs` as the key. - when running locally, the default is to test with workloads. For this, sdk with `$(SdkVersionForWorkloadTesting)` is installed in - `artifacts/bin/dotnet-workload`. And the workload packs are installed there + `artifacts/bin/dotnet-net7`. And the workload packs are installed there using packages in `artifacts/packages/$(Configuration)/Shipping`. - If the packages get updated, then the workload will get installed again. diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index a56ba59191d845..8c7ad904c26ebd 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -56,7 +56,7 @@ - + <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload @@ -69,6 +69,9 @@ + + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs index 8a2eaa9f8b0b1c..a1aeee5933cf92 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs @@ -55,6 +55,18 @@ public static int Main() } }", buildArgs, host, id); + [Theory] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) + => TestMain("main_exception", """ + using System; + using System.Threading.Tasks; + + public class TestClass { + public static int Main() => throw new Exception("MessageFromMyException"); + } + """, buildArgs, host, id, expectedExitCode: 71, expectedOutput: "Error: MessageFromMyException"); + private static string s_bug49588_ProgramCS = @" using System; public class TestClass { @@ -165,7 +177,9 @@ protected void TestMain(string projectName, RunHost host, string id, string extraProperties = "", - bool? dotnetWasmFromRuntimePack = null) + bool? dotnetWasmFromRuntimePack = null, + int expectedExitCode = 42, + string expectedOutput = "Hello, World!") { buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties); @@ -179,8 +193,8 @@ protected void TestMain(string projectName, InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("Hello, World!", output), host: host, id: id); + RunAndTestWasmApp(buildArgs, expectedExitCode: expectedExitCode, + test: output => Assert.Contains(expectedOutput, output), host: host, id: id); } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs index 8c5c397b82c3d0..26aa0a7e62ce43 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmSIMDTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using Xunit; using Xunit.Abstractions; @@ -16,25 +17,114 @@ public WasmSIMDTests(ITestOutputHelper output, SharedBuildPerTestClassFixture bu } [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void BuildWithSIMD(BuildArgs buildArgs, RunHost host, string id) - => TestMain("main_simd_aot", - @" - using System; - using System.Runtime.Intrinsics; - - public class TestClass { - public static int Main() - { - var v1 = Vector128.Create(0x12345678); - var v2 = Vector128.Create(0x23456789); - var v3 = v1*v2; - Console.WriteLine(v3); - Console.WriteLine(""Hello, World!""); - - return 42; - } - }", - buildArgs, host, id, extraProperties: "true"); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void BuildWithSIMD_NoAOT_ShouldRelink(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"sim_with_workload_no_aot"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, "true"); + + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + Publish: false, + DotnetWasmFromRuntimePack: false)); + + if (!_buildContext.TryGetBuildFor(buildArgs, out _)) + { + // Check if this is not a cached build + Assert.Contains("Compiling native assets with excc", output); + } + + RunAndTestWasmApp(buildArgs, + extraXHarnessArgs: host == RunHost.NodeJS ? "--engine-arg=--experimental-wasm-simd" : "", + expectedExitCode: 42, + test: output => + { + Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); + Assert.Contains("Hello, World!", output); + }, host: host, id: id); + } + + [Theory] + // https://github.com/dotnet/runtime/issues/75044 - disabled for V8, and NodeJS + //[MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.Chrome })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] + public void PublishWithSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"sim_with_workload_aot"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, "true"); + + BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + DotnetWasmFromRuntimePack: false)); + + RunAndTestWasmApp(buildArgs, + extraXHarnessArgs: host == RunHost.NodeJS ? "--engine-arg=--experimental-wasm-simd" : "", + expectedExitCode: 42, + test: output => + { + Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); + Assert.Contains("Hello, World!", output); + }, host: host, id: id); + } + + [Theory, TestCategory("no-workload")] + [InlineData("Debug", /*aot*/true, /*publish*/true)] + [InlineData("Debug", /*aot*/false, /*publish*/false)] + [InlineData("Debug", /*aot*/false, /*publish*/true)] + [InlineData("Release", /*aot*/true, /*publish*/true)] + [InlineData("Release", /*aot*/false, /*publish*/false)] + [InlineData("Release", /*aot*/false, /*publish*/true)] + public void BuildWithSIMDNeedsWorkload(string config, bool aot, bool publish) + { + string id = Path.GetRandomFileName(); + string projectName = $"simd_no_workload_{config}_aot_{aot}"; + BuildArgs buildArgs = new + ( + ProjectName: projectName, + Config: config, + AOT: aot, + ProjectFileContents: "placeholder", + ExtraBuildArgs: string.Empty + ); + + string extraProperties = """ + browser-wasm + true + """; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties); + + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), + Publish: publish, + ExpectSuccess: false, + UseCache: false)); + Assert.Contains("following workloads must be installed: wasm-tools", output); + } + + private static string s_simdProgramText = @" + using System; + using System.Runtime.Intrinsics; + + public class TestClass { + public static int Main() + { + var v1 = Vector128.Create(0x12345678); + var v2 = Vector128.Create(0x23456789); + var v3 = v1*v2; + Console.WriteLine(v3); + Console.WriteLine(""Hello, World!""); + + return 42; + } + }"; } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets index 5557a97f375cca..419a6d48046ba0 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets @@ -1,75 +1,28 @@ - - Microsoft.NETCore.App - - - - - - - - - - - - + + - - - - - - - - + + - - - - - - + + - - - - <_targetingPackReferenceExclusion Include="$(TargetName)" /> - <_targetingPackReferenceExclusion Include="@(_ResolvedProjectReferencePaths->'%(Filename)')" /> - <_targetingPackReferenceExclusion Include="@(DefaultReferenceExclusion)" /> - + + - - <_targetingPackReferenceWithExclusion Include="@(Reference)"> - %(_targetingPackReferenceExclusion.Identity) - - - + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets index 18ef74cead43c5..d18165944fa43c 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets @@ -2,24 +2,4 @@ - - - - - - - - - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd index d3c47af35c02ff..75b09e58cb2c6f 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.cmd @@ -94,10 +94,10 @@ exit /b %EXIT_CODE% REM Functions :SetEnvVars if [%TEST_USING_WORKLOADS%] == [true] ( - set _DIR_NAME=dotnet-workload + set _DIR_NAME=dotnet-net7 set SDK_HAS_WORKLOAD_INSTALLED=true ) else ( - set _DIR_NAME=sdk-no-workload + set _DIR_NAME=dotnet-none set SDK_HAS_WORKLOAD_INSTALLED=false ) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh index 0c3be485422f90..e27d280a23c2bf 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/RunScriptTemplate.sh @@ -68,10 +68,10 @@ function set_env_vars() { local _DIR_NAME= if [ "x$TEST_USING_WORKLOADS" = "xtrue" ]; then - _DIR_NAME=dotnet-workload + _DIR_NAME=dotnet-net7 export SDK_HAS_WORKLOAD_INSTALLED=true else - _DIR_NAME=sdk-no-workload + _DIR_NAME=dotnet-none export SDK_HAS_WORKLOAD_INSTALLED=false fi diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets index 2274fe83598bf2..b2b875fd8449ce 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets @@ -6,74 +6,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_targetingPackReferenceExclusion Include="$(TargetName)" /> - <_targetingPackReferenceExclusion Include="@(_ResolvedProjectReferencePaths->'%(Filename)')" /> - <_targetingPackReferenceExclusion Include="@(DefaultReferenceExclusion)" /> - - - - <_targetingPackReferenceWithExclusion Include="@(Reference)"> - %(_targetingPackReferenceExclusion.Identity) - - - - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config index d6de05ad43dddc..e7ad45f78b8cab 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/nuget7.config @@ -7,6 +7,7 @@ + diff --git a/src/tests/Common/testenvironment.proj b/src/tests/Common/testenvironment.proj index f60eb9b8394ac7..afa5bbecf7b684 100644 --- a/src/tests/Common/testenvironment.proj +++ b/src/tests/Common/testenvironment.proj @@ -66,6 +66,7 @@ COMPlus_JitRandomGuardedDevirtualization; COMPlus_JitRandomEdgeCounts; COMPlus_JitRandomOnStackReplacement; + COMPlus_JitRandomlyCollect64BitCounts; COMPlus_JitForceControlFlowGuard; COMPlus_JitCFGUseDispatcher; RunningIlasmRoundTrip @@ -209,10 +210,10 @@ - - - - + + + + diff --git a/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs b/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs index ed9b121bef6a23..8a0408768a346d 100644 --- a/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs +++ b/src/tests/Interop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative.cs @@ -156,6 +156,9 @@ public enum ByteEnum : byte [DllImport(nameof(DisabledRuntimeMarshallingNative), EntryPoint = "Invalid")] public static extern void CallWithVarargs(__arglist); + [DllImport(nameof(DisabledRuntimeMarshallingNative), EntryPoint = "Invalid")] + public static extern void CallWithInt128(Int128 i); + [DllImport(nameof(DisabledRuntimeMarshallingNative))] public static extern delegate* unmanaged GetStructWithShortAndBoolCallback(); diff --git a/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs b/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs index 4fa7315d43063d..21b0716948d20b 100644 --- a/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs +++ b/src/tests/Interop/DisabledRuntimeMarshalling/PInvokeAssemblyMarshallingDisabled/PInvokes.cs @@ -148,4 +148,11 @@ public static void CanUseEnumsWithDisabledMarshalling() { Assert.Equal((byte)ByteEnum.Value, DisabledRuntimeMarshallingNative.GetEnumUnderlyingValue(ByteEnum.Value)); } + + [Fact] + [SkipOnMono("Blocking this on CoreCLR should be good enough.")] + public static void Int128_NotSupported() + { + Assert.Throws(() => DisabledRuntimeMarshallingNative.CallWithInt128(default(Int128))); + } } diff --git a/src/tests/Interop/PInvoke/Int128/Int128Native.cpp b/src/tests/Interop/PInvoke/Int128/Int128Native.cpp index 28f70bca06fabd..5b7e298b1e45f7 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Native.cpp +++ b/src/tests/Interop/PInvoke/Int128/Int128Native.cpp @@ -11,14 +11,29 @@ #elif defined(__SIZEOF_INT128__) typedef __int128 Int128; #else - typedef struct { +struct +#if defined(_M_ARM64) || defined(_M_AMD64) || defined(_M_IX86) +alignas(16) +#endif +Int128 { uint64_t lower; uint64_t upper; - } Int128; + }; #endif static Int128 Int128Value = { }; +struct StructWithInt128 +{ + int8_t messUpPadding; + Int128 value; +}; + +struct StructJustInt128 +{ + Int128 value; +}; + extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE GetInt128(uint64_t upper, uint64_t lower) { Int128 result; @@ -35,15 +50,34 @@ extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE GetInt128(uint64_t upper, uint64_ return result; } -extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetInt128Out(uint64_t upper, uint64_t lower, Int128* pValue) + +extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetInt128Out(uint64_t upper, uint64_t lower, char* pValue /* This is a char*, as .NET does not currently guarantee that Int128 values are aligned */) { Int128 value = GetInt128(upper, lower); - *pValue = value; + memcpy(pValue, &value, sizeof(value)); // Perform unaligned write +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetInt128Lower(Int128 value) +{ +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + return (uint64_t)value; +#else + return value.lower; +#endif +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetInt128Lower_S(StructJustInt128 value) +{ +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + return (uint64_t)value.value; +#else + return value.value.lower; +#endif } extern "C" DLL_EXPORT const Int128* STDMETHODCALLTYPE GetInt128Ptr(uint64_t upper, uint64_t lower) { - GetInt128Out(upper, lower, &Int128Value); + GetInt128Out(upper, lower, (char*)&Int128Value); return &Int128Value; } @@ -62,13 +96,221 @@ extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128(Int128 lhs, Int128 rhs) return result; } -extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128s(const Int128* pValues, uint32_t count) +// Test that struct alignment behavior matches with the standard OS compiler +extern "C" DLL_EXPORT void STDMETHODCALLTYPE AddStructWithInt128_ByRef(char *pLhs, char *pRhs) /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */ +{ + StructWithInt128 result = {}; + StructWithInt128 lhs; + memcpy(&lhs, pLhs, sizeof(lhs)); // Perform unaligned read + StructWithInt128 rhs; + memcpy(&rhs, pRhs, sizeof(rhs)); // Perform unaligned read + + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + memcpy(pLhs, &result, sizeof(result)); // Perform unaligned write +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128(StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128_1(int64_t dummy1, StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT StructWithInt128 STDMETHODCALLTYPE AddStructWithInt128_9(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, int64_t dummy9, StructWithInt128 lhs, StructWithInt128 rhs) +{ + StructWithInt128 result = {}; + result.messUpPadding = lhs.messUpPadding; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result.value = lhs.value + rhs.value; +#else + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_1(int64_t dummy1, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_2(int64_t dummy1, int64_t dummy2, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_3(int64_t dummy1, int64_t dummy2, int64_t dummy3, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_4(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_5(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_6(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_7(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_8(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128_9(int64_t dummy1, int64_t dummy2, int64_t dummy3, int64_t dummy4, int64_t dummy5, int64_t dummy6, int64_t dummy7, int64_t dummy8, int64_t dummy9, Int128 lhs, Int128 rhs) +{ + Int128 result; + +#if (INT128_WIDTH == 128) || defined(__SIZEOF_INT128__) + result = lhs + rhs; +#else + result.lower = lhs.lower + rhs.lower; + uint64_t carry = (result.lower < lhs.lower) ? 1 : 0; + result.upper = lhs.upper + rhs.upper + carry; +#endif + + return result; +} + + +extern "C" DLL_EXPORT Int128 STDMETHODCALLTYPE AddInt128s(const char* pValues /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */, uint32_t count) { Int128 result = {}; for (uint32_t i = 0; i < count; i++) { - result = AddInt128(result, pValues[i]); + Int128 input; + memcpy(&input, pValues + (sizeof(Int128) * i), sizeof(Int128)); // Perform unaligned read + result = AddInt128(result, input); } return result; diff --git a/src/tests/Interop/PInvoke/Int128/Int128Test.cs b/src/tests/Interop/PInvoke/Int128/Int128Test.cs index 5a9ddd5bb2135d..c6a8c378d6f25e 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Test.cs +++ b/src/tests/Interop/PInvoke/Int128/Int128Test.cs @@ -5,6 +5,20 @@ using System.Runtime.InteropServices; using Xunit; + +struct StructJustInt128 +{ + public StructJustInt128(Int128 val) { value = val; } + public Int128 value; +} + +struct StructWithInt128 +{ + public StructWithInt128(Int128 val) { value = val; messUpPadding = 0x10; } + public byte messUpPadding; + public Int128 value; +} + unsafe partial class Int128Native { [DllImport(nameof(Int128Native))] @@ -16,6 +30,15 @@ unsafe partial class Int128Native [DllImport(nameof(Int128Native))] public static extern void GetInt128Out(ulong upper, ulong lower, out Int128 value); + [DllImport(nameof(Int128Native))] + public static extern void GetInt128Out(ulong upper, ulong lower, out StructJustInt128 value); + + [DllImport(nameof(Int128Native))] + public static extern ulong GetInt128Lower_S(StructJustInt128 value); + + [DllImport(nameof(Int128Native))] + public static extern ulong GetInt128Lower(Int128 value); + [DllImport(nameof(Int128Native))] public static extern Int128* GetInt128Ptr(ulong upper, ulong lower); @@ -25,6 +48,47 @@ unsafe partial class Int128Native [DllImport(nameof(Int128Native))] public static extern Int128 AddInt128(Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern void AddStructWithInt128_ByRef(ref StructWithInt128 lhs, ref StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128(StructWithInt128 lhs, StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128_1(long dummy1, StructWithInt128 lhs, StructWithInt128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern StructWithInt128 AddStructWithInt128_9(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, long dummy9, StructWithInt128 lhs, StructWithInt128 rhs); + + // Test alignment and proper register usage for Int128 with various amounts of other registers in use. These tests are designed to stress the calling convention of Arm64 and Unix X64. + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_1(long dummy1, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_2(long dummy1, long dummy2, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_3(long dummy1, long dummy2, long dummy3, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_4(long dummy1, long dummy2, long dummy3, long dummy4, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_5(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_6(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_7(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_8(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, Int128 lhs, Int128 rhs); + + [DllImport(nameof(Int128Native))] + public static extern Int128 AddInt128_9(long dummy1, long dummy2, long dummy3, long dummy4, long dummy5, long dummy6, long dummy7, long dummy8, long dummy9, Int128 lhs, Int128 rhs); + [DllImport(nameof(Int128Native))] public static extern Int128 AddInt128s(Int128* pValues, int count); @@ -37,10 +101,14 @@ unsafe partial class Int128Native unsafe partial class Int128Native { - private static void TestInt128() + public static void TestInt128FieldLayout() { - Int128 value1 = Int128Native.GetInt128(1, 2); - Assert.Equal(new Int128(1, 2), value1); + // This test checks that the alignment rules of Int128 structs match the native compiler + StructWithInt128 lhs = new StructWithInt128(new Int128(11, 12)); + StructWithInt128 rhs = new StructWithInt128(new Int128(13, 14)); + + Int128Native.AddStructWithInt128_ByRef(ref lhs, ref rhs); + Assert.Equal(new StructWithInt128(new Int128(24, 26)), lhs); Int128 value2; Int128Native.GetInt128Out(3, 4, &value2); @@ -49,6 +117,28 @@ private static void TestInt128() Int128Native.GetInt128Out(5, 6, out Int128 value3); Assert.Equal(new Int128(5, 6), value3); + StructJustInt128 value4; + Int128Native.GetInt128Out(7, 8, out value4); + Assert.Equal(new StructJustInt128(new Int128(7, 8)), value4); + + // Until we implement the correct abi for Int128, validate that we don't marshal to native + + // Checking return value + Assert.Throws(() => GetInt128(0, 1)); + + // Checking input value as Int128 itself + Assert.Throws(() => GetInt128Lower(default(Int128))); + + // Checking input value as structure wrapping Int128 + Assert.Throws(() => GetInt128Lower_S(default(StructJustInt128))); + } + + private static void TestInt128() + { + Int128 value1 = Int128Native.GetInt128(1, 2); + Assert.Equal(new Int128(1, 2), value1); + + Int128* value4 = Int128Native.GetInt128Ptr(7, 8); Assert.Equal(new Int128(7, 8), *value4); @@ -77,5 +167,45 @@ private static void TestInt128() Int128 value9 = Int128Native.AddInt128s(in values[0], values.Length); Assert.Equal(new Int128(95, 100), value9); + + // Test ABI alignment issues on Arm64 and Unix X64, both enregistered and while spilled to the stack + Int128 value10 = Int128Native.AddInt128_1(1, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value10); + + Int128 value11 = Int128Native.AddInt128_2(1, 2, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value11); + + Int128 value12 = Int128Native.AddInt128_3(1, 2, 3, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value12); + + Int128 value13 = Int128Native.AddInt128_4(1, 2, 3, 4, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value13); + + Int128 value14 = Int128Native.AddInt128_5(1, 2, 3, 4, 5, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value14); + + Int128 value15 = Int128Native.AddInt128_6(1, 2, 3, 4, 5, 6, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value15); + + Int128 value16 = Int128Native.AddInt128_7(1, 2, 3, 4, 5, 6, 7, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value16); + + Int128 value17 = Int128Native.AddInt128_8(1, 2, 3, 4, 5, 6, 7, 8, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value17); + + Int128 value18 = Int128Native.AddInt128_9(1, 2, 3, 4, 5, 6, 7, 8, 9, new Int128(25, 26), new Int128(27, 28)); + Assert.Equal(new Int128(52, 54), value18); + + // Test alignment within a structure + StructWithInt128 value19 = Int128Native.AddStructWithInt128(new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value19); + + // Test abi register alignment within a structure + StructWithInt128 value20 = Int128Native.AddStructWithInt128_1(1, new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value20); + + // Test abi alignment when spilled to the stack + StructWithInt128 value21 = Int128Native.AddStructWithInt128_9(1, 2, 3, 4, 5, 6, 7, 8, 9, new StructWithInt128(new Int128(29, 30)), new StructWithInt128(new Int128(31, 32))); + Assert.Equal(new StructWithInt128(new Int128(60, 62)), value21); } } diff --git a/src/tests/Interop/PInvoke/Int128/Int128Test.csproj b/src/tests/Interop/PInvoke/Int128/Int128Test.csproj index 42fc09c10b7185..e13c49fe94c3f7 100644 --- a/src/tests/Interop/PInvoke/Int128/Int128Test.csproj +++ b/src/tests/Interop/PInvoke/Int128/Int128Test.csproj @@ -6,7 +6,9 @@ exe - + + + diff --git a/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj b/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj new file mode 100644 index 00000000000000..449dc00c211eff --- /dev/null +++ b/src/tests/Interop/PInvoke/Int128/Int128TestFieldLayout.csproj @@ -0,0 +1,15 @@ + + + + true + embedded + exe + + + + + + + + + diff --git a/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.cs b/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.cs new file mode 100644 index 00000000000000..5a31288c5d3266 --- /dev/null +++ b/src/tests/Interop/PInvoke/Int128/ProgramFieldLayout.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +unsafe partial class Int128NativeFieldLayout +{ + public static int Main(string[] args) + { + try + { + Console.WriteLine("Testing Int128"); + Int128Native.TestInt128FieldLayout(); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + return 0; + } + return 100; + } +} diff --git a/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp b/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp index 6d561cf4243439..d07d9d8f5ceb32 100644 --- a/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp +++ b/src/tests/Interop/PInvoke/Int128/UInt128Native.cpp @@ -35,15 +35,15 @@ extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE GetUInt128(uint64_t upper, uint6 return result; } -extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetUInt128Out(uint64_t upper, uint64_t lower, UInt128* pValue) +extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetUInt128Out(uint64_t upper, uint64_t lower, char* pValue /* This is a char*, as .NET does not currently guarantee that Int128 values are aligned */) { UInt128 value = GetUInt128(upper, lower); - *pValue = value; + memcpy(pValue, &value, sizeof(value)); // Perform unaligned write } extern "C" DLL_EXPORT const UInt128* STDMETHODCALLTYPE GetUInt128Ptr(uint64_t upper, uint64_t lower) { - GetUInt128Out(upper, lower, &UInt128Value); + GetUInt128Out(upper, lower, (char*)&UInt128Value); return &UInt128Value; } @@ -62,13 +62,15 @@ extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128(UInt128 lhs, UInt128 return result; } -extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128s(const UInt128* pValues, uint32_t count) +extern "C" DLL_EXPORT UInt128 STDMETHODCALLTYPE AddUInt128s(const char* pValues /* These are char*, as .NET does not currently guarantee that Int128 values are aligned */, uint32_t count) { UInt128 result = {}; for (uint32_t i = 0; i < count; i++) { - result = AddUInt128(result, pValues[i]); + UInt128 input; + memcpy(&input, pValues + (sizeof(UInt128) * i), sizeof(UInt128)); // Perform unaligned read + result = AddUInt128(result, input); } return result; diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs b/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs index 19fd90aff05ef6..622bbc1d04fd2a 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs +++ b/src/tests/JIT/Regression/JitBlue/Runtime_34170/Runtime_34170.cs @@ -21,7 +21,7 @@ public FloatNonAlignedFieldWithSmallOffset(float a) [StructLayout(LayoutKind.Explicit)] internal struct FloatNonAlignedFieldWithLargeOffset { - [FieldOffset(1021)] + [FieldOffset(0x10001)] public float field; public FloatNonAlignedFieldWithLargeOffset(float a) @@ -45,7 +45,7 @@ public DoubleNonAlignedFieldWithSmallOffset(float a) [StructLayout(LayoutKind.Explicit)] internal struct DoubleNonAlignedFieldWithLargeOffset { - [FieldOffset(1021)] + [FieldOffset(0x10001)] public double field; public DoubleNonAlignedFieldWithLargeOffset(float a) diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs new file mode 100644 index 00000000000000..1f4f5895fdc4c6 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +public class Runtime_73951 +{ + [ThreadStatic] + public static IRuntime s_rt; + [ThreadStatic] + public static S1 s_17; + + public static ushort s_result; + + public static int Main() + { + Problem(new Runtime()); + + return s_result == 0 ? 100 : 101; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Problem(IRuntime rt) + { + s_rt = rt; + S0 vr21 = s_17.F1; + new S1(new object()).M105(vr21); + + var vr22 = new C0(vr21.F3); + s_rt.Capture(vr22.F1); + } + + public class C0 + { + public ushort F1; + public C0(ushort f1) + { + F1 = f1; + } + } + + public struct S0 + { + public uint F1; + public int F2; + public byte F3; + } + + public struct S1 + { + public object F0; + public S0 F1; + public S1(object f0) : this() + { + F0 = f0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public S1 M105(S0 arg0) + { + return this; + } + } + + public interface IRuntime + { + void Capture(ushort value); + } + + public class Runtime : IRuntime + { + public void Capture(ushort value) => s_result = value; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj new file mode 100644 index 00000000000000..f492aeac9d056b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73951/Runtime_73951.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.cs new file mode 100644 index 00000000000000..0bb8fa2d83f72b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +unsafe class Runtime_74117 +{ + public unsafe static int Main(string[] args) + { + byte a = 5; + Problem(ref a, 0); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static byte GetByte() => 1; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Problem(ref byte x, int a) + { + JitUse(&a); + Unsafe.Add(ref x, a) = GetByte(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void JitUse(T* arg) where T : unmanaged { } +} \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj new file mode 100644 index 00000000000000..cf94135633b19a --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74117/Runtime_74117.csproj @@ -0,0 +1,10 @@ + + + Exe + True + true + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs new file mode 100644 index 00000000000000..dd21dce550479d --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.cs @@ -0,0 +1,82 @@ +// 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.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.CompilerServices; + +public class Runtime_74126 +{ + public static int Main() + { + if (GetVtor(GetVtor2()) != GetVtor2()) + { + return 101; + } + if (GetVtor(GetVtor3()) != GetVtor3()) + { + return 102; + } + if (GetVtor(GetVtor4()) != GetVtor4()) + { + return 103; + } + if (GetVtor(GetVtor64()) != GetVtor64()) + { + return 104; + } + if (GetVtor(GetVtor128()) != GetVtor128()) + { + return 105; + } + if (GetVtor(GetVtor256()) != GetVtor256()) + { + return 106; + } + + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector2 GetVtor2() + { + return new Vector2(1, 2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector3 GetVtor3() + { + return new Vector3(1, 2, 3); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector4 GetVtor4() + { + return new Vector4(1, 2, 3, 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector64 GetVtor64() + { + return Vector64.Create(1, 2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector128 GetVtor128() + { + return Vector128.Create(1, 2, 3, 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Vector256 GetVtor256() + { + return Vector256.Create(1, 2, 3, 4, 5, 6, 7, 8); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T GetVtor(T vtor) + { + return vtor; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj new file mode 100644 index 00000000000000..f492aeac9d056b --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74126/Runtime_74126.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.cs b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.cs new file mode 100644 index 00000000000000..c06276960e1075 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +public class _74373 +{ + public static int Main(string[] args) + { + Problem(10); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe float Problem(long x) + { + var y = BitConverter.Int32BitsToSingle((int)x); + Use(&x); + JitUse(0); + return y; + } + + public static unsafe void Use(long* arg) { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void JitUse(T arg) { } +} \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj new file mode 100644 index 00000000000000..cf94135633b19a --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_74373/Runtime_74373.csproj @@ -0,0 +1,10 @@ + + + Exe + True + true + + + + + \ No newline at end of file diff --git a/src/tests/JIT/Stress/ABI/ABIs.cs b/src/tests/JIT/Stress/ABI/ABIs.cs index a9c85f864aa88b..3ad1eed8d7c743 100644 --- a/src/tests/JIT/Stress/ABI/ABIs.cs +++ b/src/tests/JIT/Stress/ABI/ABIs.cs @@ -47,14 +47,14 @@ internal class Win86Abi : IAbi new[] { typeof(byte), typeof(short), typeof(int), typeof(long), - typeof(float), typeof(double), + typeof(float), typeof(double), typeof(Int128), typeof(Vector), typeof(Vector128), typeof(Vector256), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U), - typeof(S31U), typeof(S32U), + typeof(S31U), typeof(S32U), typeof(I128_1), typeof(I128_2) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl, CallingConvention.StdCall, }; @@ -107,12 +107,13 @@ internal class SysVAbi : IAbi typeof(byte), typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(Vector), typeof(Vector128), typeof(Vector256), + typeof(Int128), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U), - typeof(S31U), typeof(S32U), + typeof(S31U), typeof(S32U), typeof(I128_1), typeof(I128_2) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl }; @@ -135,14 +136,14 @@ internal class Arm64Abi : IAbi new[] { typeof(byte), typeof(short), typeof(int), typeof(long), - typeof(float), typeof(double), + typeof(float), typeof(double), typeof(Int128), typeof(Vector), typeof(Vector128), typeof(Vector256), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U), typeof(S14U), typeof(S15U), typeof(S16U), - typeof(Hfa1), typeof(Hfa2), + typeof(Hfa1), typeof(Hfa2), typeof(I128_1) }; public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl }; diff --git a/src/tests/JIT/Stress/ABI/Gen.cs b/src/tests/JIT/Stress/ABI/Gen.cs index a7d0d0efbe5435..ddb7c1215c87ef 100644 --- a/src/tests/JIT/Stress/ABI/Gen.cs +++ b/src/tests/JIT/Stress/ABI/Gen.cs @@ -48,6 +48,9 @@ internal static object GenConstant(Type type, FieldInfo[] fields, Random rand) if (type == typeof(double)) return (double)rand.Next(); + if (type == typeof(Int128)) + return new Int128((ulong)(long)GenConstant(typeof(long), null, rand), (ulong)(long)GenConstant(typeof(long), null, rand)); + if (type == typeof(Vector)) return GenConstantVector, int>(rand); @@ -173,6 +176,8 @@ internal static void EmitLoadPrimitive(ILGenerator il, object val) il.Emit(OpCodes.Ldc_R4, (float)val); else if (ty == typeof(double)) il.Emit(OpCodes.Ldc_R8, (double)val); + else if (ty == typeof(Int128)) + EmitLoadBlittable(il, (Int128)val); else if (ty == typeof(Vector)) EmitLoadBlittable(il, (Vector)val); else if (ty == typeof(Vector128)) diff --git a/src/tests/JIT/Stress/ABI/Program.cs b/src/tests/JIT/Stress/ABI/Program.cs index 8eab812a5748c3..1c1c00c648309b 100644 --- a/src/tests/JIT/Stress/ABI/Program.cs +++ b/src/tests/JIT/Stress/ABI/Program.cs @@ -344,6 +344,7 @@ public static void EmitDumpValues(string listName, ILGenerator g, IEnumerable), typeof(Vector128), typeof(Vector256), + typeof(Int128), typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U), typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U), typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U), @@ -351,6 +352,7 @@ public static void EmitDumpValues(string listName, ILGenerator g, IEnumerable new TypeEx(t)).ToArray(); private static readonly IAbi s_abi = SelectAbi(); diff --git a/src/tests/JIT/Stress/ABI/Types.cs b/src/tests/JIT/Stress/ABI/Types.cs index a24c47154de6d2..140ae76762b257 100644 --- a/src/tests/JIT/Stress/ABI/Types.cs +++ b/src/tests/JIT/Stress/ABI/Types.cs @@ -61,6 +61,8 @@ public struct S31U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F1 public struct S32U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31; public S32U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15, byte f16, byte f17, byte f18, byte f19, byte f20, byte f21, byte f22, byte f23, byte f24, byte f25, byte f26, byte f27, byte f28, byte f29, byte f30, byte f31) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, f31); } public struct Hfa1 { public float F0, F1; public Hfa1(float f0, float f1) => (F0, F1) = (f0, f1); } public struct Hfa2 { public double F0, F1, F2, F3; public Hfa2(double f0, double f1, double f2, double f3) => (F0, F1, F2, F3) = (f0, f1, f2, f3); } + public struct I128_1 { public Int128 F0; public I128_1(Int128 f0) => F0 = f0; } + public struct I128_2 { public byte F0; public Int128 F1; public I128_2(byte f0, Int128 f1) => (F0, F1) = (f0, f1); } internal static class TypeExtensions { @@ -78,7 +80,8 @@ public static bool IsOurStructType(this Type t) t == typeof(S14U) || t == typeof(S15U) || t == typeof(S16U) || t == typeof(S17U) || t == typeof(S31U) || t == typeof(S32U) || - t == typeof(Hfa1) || t == typeof(Hfa2); + t == typeof(Hfa1) || t == typeof(Hfa2) || + t == typeof(I128_1) || t == typeof(I128_2); } } } diff --git a/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs b/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs index ae08369177388d..66783d12a7a164 100644 --- a/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs +++ b/src/tests/Loader/classloader/DefaultInterfaceMethods/regressions/github61244.cs @@ -14,11 +14,19 @@ // derived interface contexts, but the order is changed (or different.) // When this occurs the generic info is incorrect for the inflated method. +// TestClass2 tests a regression due to the fix for the previous +// regression that caused Mono to incorrectly instantiate generic +// interfaces that appeared in the MethodImpl table + class Program { static int Main(string[] args) { - return new TestClass().DoTest(); + int result = new TestClass().DoTest(); + if (result != 100) + return result; + result = new TestClass2().DoTest(); + return result; } } @@ -78,4 +86,33 @@ public int DoTest () Console.WriteLine("Passed => 100"); return 100; } -} \ No newline at end of file +} + +public interface IA +{ + public int Foo(); +} + +public interface IB : IA +{ + int IA.Foo() { return 104; } +} + +public interface IC : IB

+{ + int IA.Foo() { return 105; } +} + +public class C : IC +{ + int IA.Foo() { return 100; } +} + +public class TestClass2 +{ + public int DoTest() + { + IA c = new C(); + return c.Foo(); + } +} diff --git a/src/tests/build.proj b/src/tests/build.proj index e2716558b9d8a4..37236bad27175f 100644 --- a/src/tests/build.proj +++ b/src/tests/build.proj @@ -185,7 +185,7 @@ $(BuildDir)\apk $(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).apk False - diagnostics_tracing;marshal-ilgen + diagnostics_tracing 127.0.0.1:9000,nosuspend,listen True $(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)\runtimes\android-$(TargetArchitecture)\ @@ -272,7 +272,7 @@ $(CMDDIR_GrandParent)/$(CategoryWithSlash)/$(XUnitWrapperFileName) $(IntermediateOutputPath)\iOSApps\$(Category) $(XUnitTestBinBase)$(CategoryWithSlash)\$(Category).app - diagnostics_tracing;marshal-ilgen + diagnostics_tracing diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 7caad0c5838b2f..441ecac5c8faf6 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -8,6 +8,9 @@ https://github.com/dotnet/runtime/issues/13703 + + https://github.com/dotnet/runtime/issues/74209 + @@ -916,12 +919,6 @@ https://github.com/dotnet/runtime/issues/62881 - - https://github.com/dotnet/runtime/issues/63856 - - - https://github.com/dotnet/runtime/issues/63856 - @@ -1427,6 +1424,9 @@ + + https://github.com/dotnet/runtime/issues/74223 + https://github.com/dotnet/runtime/issues/71656 @@ -1869,6 +1869,9 @@ needs triage + + https://github.com/dotnet/runtime/issues/69832 + needs triage @@ -2019,10 +2022,10 @@ These tests are not supposed to be run with mono. - needs triage + https://github.com/dotnet/runtime/issues/36113 - needs triage + https://github.com/dotnet/runtime/issues/36113 needs triage @@ -2473,9 +2476,6 @@ needs triage - - https://github.com/dotnet/runtime/issues/69832 - https://github.com/dotnet/runtime/issues/54393 @@ -3703,7 +3703,7 @@ needs triage - + https://github.com/dotnet/runtime/issues/73539 diff --git a/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs b/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs index b8e6b8d57db354..52205934d046d3 100644 --- a/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs +++ b/src/tests/nativeaot/SmokeTests/PInvoke/PInvoke.cs @@ -702,6 +702,8 @@ public struct SequentialStruct public float f2; [MarshalAs(UnmanagedType.LPStr)] public String f3; + [MarshalAs(UnmanagedType.LPUTF8Str)] + public String f4; } [StructLayout(LayoutKind.Sequential)] @@ -713,6 +715,8 @@ public class SequentialClass public float f2; [MarshalAs(UnmanagedType.LPStr)] public String f3; + [MarshalAs(UnmanagedType.LPUTF8Str)] + public String f4; } // A second struct with the same name but nested. Regression test against native types being mangled into @@ -824,15 +828,16 @@ private static void TestStruct() ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; ThrowIfNotEquals(true, StructTest(ss), "Struct marshalling scenario1 failed."); StructTest_ByRef(ref ss); - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "Struct marshalling scenario2 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && ss.f4.Equals("Ipmb"), "Struct marshalling scenario2 failed."); SequentialStruct ss2 = new SequentialStruct(); StructTest_ByOut(out ss2); - ThrowIfNotEquals(true, ss2.f0 == 1 && ss2.f1 == 1.0 && ss2.f2 == 1.0 && ss2.f3.Equals("0123456"), "Struct marshalling scenario3 failed."); + ThrowIfNotEquals(true, ss2.f0 == 1 && ss2.f1 == 1.0 && ss2.f2 == 1.0 && ss2.f3.Equals("0123456") && ss2.f4.Equals("789"), "Struct marshalling scenario3 failed."); NesterOfSequentialStruct.SequentialStruct ss3 = new NesterOfSequentialStruct.SequentialStruct(); ss3.f1 = 10.0f; @@ -861,6 +866,7 @@ private static void TestStruct() ssa[i].f1 = i; ssa[i].f2 = i*i; ssa[i].f3 = i.LowLevelToString(); + ssa[i].f4 = "u8" + i.LowLevelToString(); } ThrowIfNotEquals(true, StructTest_Array(ssa, ssa.Length), "Array of struct marshalling failed"); @@ -923,9 +929,10 @@ private static void TestLayoutClassPtr() ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; ClassTest(ss); - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "LayoutClassPtr marshalling scenario1 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && ss.f4.Equals("Ipmb"), "LayoutClassPtr marshalling scenario1 failed."); } #if OPTIMIZED_MODE_WITHOUT_SCANNER @@ -955,20 +962,22 @@ private static void TestAsAny() sc.f1 = 1; sc.f2 = 10.0f; sc.f3 = "Hello"; + sc.f4 = "Hola"; AsAnyTest(sc); - ThrowIfNotEquals(true, sc.f1 == 2 && sc.f2 == 11.0 && sc.f3.Equals("Ifmmp"), "AsAny marshalling scenario1 failed."); + ThrowIfNotEquals(true, sc.f1 == 2 && sc.f2 == 11.0 && sc.f3.Equals("Ifmmp") && sc.f4.Equals("Ipmb"), "AsAny marshalling scenario1 failed."); SequentialStruct ss = new SequentialStruct(); ss.f0 = 100; ss.f1 = 1; ss.f2 = 10.0f; ss.f3 = "Hello"; + ss.f4 = "Hola"; object o = ss; AsAnyTest(o); ss = (SequentialStruct)o; - ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp"), "AsAny marshalling scenario2 failed."); + ThrowIfNotEquals(true, ss.f1 == 2 && ss.f2 == 11.0 && ss.f3.Equals("Ifmmp") && sc.f4.Equals("Ipmb"), "AsAny marshalling scenario2 failed."); } private static void TestLayoutClass() diff --git a/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp b/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp index 7412274680bcfd..480209d3b2560a 100644 --- a/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp +++ b/src/tests/nativeaot/SmokeTests/PInvoke/PInvokeNative.cpp @@ -471,6 +471,7 @@ struct NativeSequentialStruct int a; float b; char *str; + char *u8str; }; struct NativeSequentialStruct2 @@ -494,6 +495,9 @@ DLL_EXPORT bool __stdcall StructTest(NativeSequentialStruct nss) if (!CompareAnsiString(nss.str, "Hello")) return false; + if (!CompareAnsiString(nss.u8str, "Hola")) + return false; + return true; } @@ -519,6 +523,13 @@ DLL_EXPORT void __stdcall StructTest_ByRef(NativeSequentialStruct *nss) *p = *p + 1; p++; } + + p = nss->u8str; + while (*p != '\0') + { + *p = *p + 1; + p++; + } } DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) @@ -529,7 +540,7 @@ DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) int arrSize = 7; char *p; - p = (char *)MemAlloc(sizeof(char) * arrSize); + p = (char *)MemAlloc(sizeof(char) * arrSize + 1); for (int i = 0; i < arrSize; i++) { @@ -537,6 +548,10 @@ DLL_EXPORT void __stdcall StructTest_ByOut(NativeSequentialStruct *nss) } *(p + arrSize) = '\0'; nss->str = p; + + p = (char *)MemAlloc(sizeof(char) * 4); + strcpy(p, "789"); + nss->u8str = p; } DLL_EXPORT bool __stdcall StructTest_Array(NativeSequentialStruct *nss, int length) @@ -558,6 +573,11 @@ DLL_EXPORT bool __stdcall StructTest_Array(NativeSequentialStruct *nss, int leng if (CompareAnsiString(expected, nss[i].str) == 0) return false; + + sprintf(expected, "u8%d", i); + + if (CompareAnsiString(expected, nss[i].u8str) == 0) + return false; } return true; } diff --git a/src/tests/readytorun/fieldlayout/fieldlayout.csproj b/src/tests/readytorun/fieldlayout/fieldlayout.csproj new file mode 100644 index 00000000000000..a9902bca098f35 --- /dev/null +++ b/src/tests/readytorun/fieldlayout/fieldlayout.csproj @@ -0,0 +1,16 @@ + + + + exe + BuildAndRun + true + true + true + + + + + + + + diff --git a/src/tests/readytorun/fieldlayout/fieldlayouttests.cs b/src/tests/readytorun/fieldlayout/fieldlayouttests.cs new file mode 100644 index 00000000000000..9b421859653648 --- /dev/null +++ b/src/tests/readytorun/fieldlayout/fieldlayouttests.cs @@ -0,0 +1,300 @@ +using System; +using System.Runtime.Intrinsics; + +class Test +{ + // This test uses the same set of types as the type system unittests use, and attempts to validate that the R2R usage of said types works well. + // This is done by touching the various types, and then relying on the verification logic in R2R images to detect failures. + static int Main() + { + ContainsGCPointersFieldsTest.Test(); +// ExplicitTest.Test(); // Explicit layout is known to not quite match the runtime, and if enabled this set of tests will fail. + SequentialTest.Test(); + AutoTest.Test(); + EnumAlignmentTest.Test(); + AutoTestWithVector.Test(); + return 100; + } +} + +class EnumAlignmentTest +{ + static EnumAlignment.LongIntEnumStruct _fld1; + static EnumAlignment.LongIntEnumStructFieldStruct _fld2; + static EnumAlignment.IntShortEnumStruct _fld3; + static EnumAlignment.IntShortEnumStructFieldStruct _fld4; + static EnumAlignment.ShortByteEnumStruct _fld5; + static EnumAlignment.ShortByteEnumStructFieldStruct _fld6; + static EnumAlignment.LongIntEnumStructAuto _fld7; + static EnumAlignment.LongIntEnumStructAutoFieldStruct _fld8; + static EnumAlignment.IntShortEnumStructAuto _fld9; + static EnumAlignment.IntShortEnumStructAutoFieldStruct _fld10; + static EnumAlignment.ShortByteEnumStructAuto _fld11; + static EnumAlignment.ShortByteEnumStructAutoFieldStruct _fld12; + + public static void Test() + { + _fld1._1 = EnumAlignment.LongEnum.Val; + _fld1._2 = EnumAlignment.IntEnum.Val; + _fld1._3 = EnumAlignment.LongEnum.Val; + _fld1._4 = EnumAlignment.IntEnum.Val; + + _fld2._0 = 0; + _fld2._struct = _fld1; + + _fld3._1 = EnumAlignment.IntEnum.Val; + _fld3._2 = EnumAlignment.ShortEnum.Val; + _fld3._3 = EnumAlignment.IntEnum.Val; + _fld3._4 = EnumAlignment.ShortEnum.Val; + + _fld4._0 = 1; + _fld4._struct = _fld3; + + _fld5._1 = EnumAlignment.ShortEnum.Val; + _fld5._2 = EnumAlignment.ByteEnum.Val; + _fld5._3 = EnumAlignment.ShortEnum.Val; + _fld5._4 = EnumAlignment.ByteEnum.Val; + + _fld6._0 = 2; + _fld6._struct = _fld5; + + _fld7._1 = EnumAlignment.LongEnum.Val; + _fld7._2 = EnumAlignment.IntEnum.Val; + _fld7._3 = EnumAlignment.LongEnum.Val; + _fld7._4 = EnumAlignment.IntEnum.Val; + + _fld8._0 = 3; + _fld8._struct = _fld7; + + _fld9._1 = EnumAlignment.IntEnum.Val; + _fld9._2 = EnumAlignment.ShortEnum.Val; + _fld9._3 = EnumAlignment.IntEnum.Val; + _fld9._4 = EnumAlignment.ShortEnum.Val; + + _fld10._0 = 4; + _fld10._struct = _fld9; + + _fld11._1 = EnumAlignment.ShortEnum.Val; + _fld11._2 = EnumAlignment.ByteEnum.Val; + _fld11._3 = EnumAlignment.ShortEnum.Val; + _fld11._4 = EnumAlignment.ByteEnum.Val; + + _fld12._0 = 5; + _fld12._struct = _fld11; + } +} + +class AutoTest +{ + static Auto.StructWithBool _fld1; + static Auto.StructWithIntChar _fld2; + static Auto.StructWithChar _fld3; + static Auto.ClassContainingStructs _fld4 = new Auto.ClassContainingStructs(); + static Auto.BaseClass7BytesRemaining _fld5 = new Auto.BaseClass7BytesRemaining(); + static Auto.BaseClass4BytesRemaining _fld6 = new Auto.BaseClass4BytesRemaining(); + static Auto.BaseClass3BytesRemaining _fld7 = new Auto.BaseClass3BytesRemaining(); + static Auto.OptimizePartial _fld8 = new Auto.OptimizePartial(); + static Auto.Optimize7Bools _fld9 = new Auto.Optimize7Bools(); + static Auto.OptimizeAlignedFields _fld10 = new Auto.OptimizeAlignedFields(); + static Auto.OptimizeLargestField _fld11 = new Auto.OptimizeLargestField(); + static Auto.NoOptimizeMisaligned _fld12 = new Auto.NoOptimizeMisaligned(); + static Auto.NoOptimizeCharAtSize2Alignment _fld13 = new Auto.NoOptimizeCharAtSize2Alignment(); + static Auto.MinPacking _fld14 = new Auto.MinPacking(); + + public static void Test() + { + _fld1.MyStructBool = true; + + _fld2.MyStructInt = 1; + _fld2.MyStructChar = 'A'; + + _fld3.MyStructChar = 'B'; + + _fld4.MyStructWithChar = _fld3; + _fld4.MyStructWithIntChar = _fld2; + _fld4.MyStructWithBool = _fld1; + _fld4.MyString1 = "Str"; + _fld4.MyBool1 = false; + _fld4.MyBool2 = true; + + _fld5.MyBool1 = false; + _fld5.MyLong1 = 2; + _fld5.MyString1 = "Str2"; + _fld5.MyDouble1 = 1.0; + _fld5.MyByteArray1 = new byte[3]; + + _fld6.MyLong1 = 3; + _fld6.MyUint1 = 4; + + _fld7.MyBool1 = true; + _fld7.MyInt1 = 5; + _fld7.MyString1 = "str3"; + + _fld8.OptBool = false; + _fld8.OptChar = 'B'; + _fld8.NoOptLong = 6; + _fld8.NoOptString = "STR4"; + + _fld9.OptBool1 = true; + _fld9.OptBool2 = false; + _fld9.OptBool3 = true; + _fld9.OptBool4 = true; + _fld9.OptBool5 = false; + _fld9.OptBool6 = true; + _fld9.OptBool7 = false; + _fld9.NoOptBool8 = true; + _fld9.NoOptString = "STR5"; + + _fld10.OptBool1 = false; + _fld10.OptBool2 = true; + _fld10.OptBool3 = false; + _fld10.NoOptBool4 = true; + _fld10.OptChar1 = 'C'; + _fld10.OptChar2 = 'D'; + _fld10.NoOptString = "STR6"; + + _fld13.NoOptChar = 'E'; + + _fld14._value = 7; + _fld14._byte = 8; + } +} + +class AutoTestWithVector +{ + static Auto.int8x16x2 _fld1 = new Auto.int8x16x2(); + static Auto.Wrapper_int8x16x2 _fld2 = new Auto.Wrapper_int8x16x2(); + static Auto.Wrapper_int8x16x2_2 _fld3 = new Auto.Wrapper_int8x16x2_2(); + + public static void Test() + { + _fld1._0 = new Vector128(); + _fld1._1 = new Vector128(); + + _fld2.fld = _fld1; + + _fld3.fld1 = true; + _fld3.fld2 = _fld1; + } +} + +class SequentialTest +{ + static Sequential.Class1 _fld1 = new Sequential.Class1(); + static Sequential.Class2 _fld2 = new Sequential.Class2(); + static Sequential.Struct0 _fld3; + static Sequential.Struct1 _fld4; + static Sequential.ClassDoubleBool _fld5 = new Sequential.ClassDoubleBool(); + static Sequential.ClassBoolDoubleBool _fld6 = new Sequential.ClassBoolDoubleBool(); + static Sequential.StructStructByte_StructByteAuto _fld7; + static Sequential.StructStructByte_Struct2BytesAuto _fld8; + static Sequential.StructStructByte_Struct3BytesAuto _fld9; + static Sequential.StructStructByte_Struct4BytesAuto _fld10; + static Sequential.StructStructByte_Struct5BytesAuto _fld11; + static Sequential.StructStructByte_Struct8BytesAuto _fld12; + static Sequential.StructStructByte_Struct9BytesAuto _fld13; + static Sequential.StructStructByte_Int128StructAuto _fld14; + static Sequential.StructStructByte_UInt128StructAuto _fld15; + + public static void Test() + { + _fld1.MyClass1SelfRef = _fld1; + _fld1.MyChar = 'A'; + _fld1.MyInt = 1; + _fld1.MyString = "STR"; + _fld1.MyBool = true; + + _fld2.MyClass1SelfRef = _fld1; + _fld2.MyChar = 'B'; + _fld2.MyInt = 2; + _fld2.MyString = "STR2"; + _fld2.MyBool = false; + _fld2.MyInt2 = 3; + + _fld3.b1 = true; + _fld3.b2 = false; + _fld3.b3 = true; + _fld3.i1 = 4; + _fld3.s1 = "str"; + + _fld4.MyStruct0 = _fld3; + _fld4.MyBool = false; + + _fld5.bool1 = true; + _fld5.double1 = 1.0; + + _fld6.bool1 = false; + _fld6.bool2 = true; + _fld6.double1 = 2.0; + + _fld7.fld2 = default(Auto.StructByte); + _fld8.fld2 = default(Auto.Struct2Bytes); + _fld9.fld2 = default(Auto.Struct3Bytes); + _fld10.fld2 = default(Auto.Struct4Bytes); + _fld11.fld2 = default(Auto.Struct5Bytes); + _fld12.fld2 = default(Auto.Struct8Bytes); + _fld13.fld2 = default(Auto.Struct9Bytes); + _fld14.fld2 = default(Auto.Int128Struct); + _fld15.fld2 = default(Auto.UInt128Struct); + } +} + +class ExplicitTest +{ + static Explicit.Class1 _fld1 = new Explicit.Class1(); + static Explicit.Class2 _fld2 = new Explicit.Class2(); + static Explicit.ExplicitSize _fld3 = new Explicit.ExplicitSize(); + static Explicit.ExplicitEmptyClass _fld4 = new Explicit.ExplicitEmptyClass(); + static Explicit.ExplicitEmptyClassSize0 _fld5 = new Explicit.ExplicitEmptyClassSize0(); + static Explicit.ExplicitEmptyStruct _fld6 = new Explicit.ExplicitEmptyStruct(); + + public static void Test() + { + _fld1.Bar = true; + _fld1.Baz = 'A'; + + _fld2.Baz = 'B'; + _fld2.Bar = false; + _fld2.Lol = 1; + _fld2.Omg = 2; + + _fld3.Omg = 3; + _fld3.Lol = 4; + } +} +class ContainsGCPointersFieldsTest +{ + static ContainsGCPointers.NoPointers _fld1; + static ContainsGCPointers.StillNoPointers _fld2; + static ContainsGCPointers.ClassNoPointers _fld3 = new ContainsGCPointers.ClassNoPointers(); + static ContainsGCPointers.HasPointers _fld4; + static ContainsGCPointers.FieldHasPointers _fld5; + static ContainsGCPointers.ClassHasPointers _fld6 = new ContainsGCPointers.ClassHasPointers(); + static ContainsGCPointers.BaseClassHasPointers _fld7 = new ContainsGCPointers.BaseClassHasPointers(); + static ContainsGCPointers.ClassHasIntArray _fld8 = new ContainsGCPointers.ClassHasIntArray(); + static ContainsGCPointers.ClassHasArrayOfClassType _fld9 = new ContainsGCPointers.ClassHasArrayOfClassType(); + + public static void Test() + { + _fld1.int1 = 1; + _fld1.byte1 = 2; + _fld1.char1 = '0'; + _fld2.bool1 = true; + + _fld2.noPointers1 = _fld1; + + _fld3.char1 = '2'; + + _fld4.string1 = "STR"; + + _fld5.hasPointers1.string1 = "STR2"; + + _fld6.classHasPointers1 = new ContainsGCPointers.ClassHasPointers(); + + _fld7.classHasPointers1 = new ContainsGCPointers.ClassHasPointers(); + + _fld8.intArrayField = new int[1]; + + _fld9.classTypeArray = new ContainsGCPointers.ClassNoPointers[1]; + } +} diff --git a/src/tests/readytorun/tests/main.cs b/src/tests/readytorun/tests/main.cs index 02d94a5939378a..04314860d48ca0 100644 --- a/src/tests/readytorun/tests/main.cs +++ b/src/tests/readytorun/tests/main.cs @@ -414,6 +414,13 @@ static void RVAFieldTest() Assert.AreEqual(value[i], (byte)(9 - i)); } + // public constructor, so we run something when loading from byte array in the test below + public Program() + { + // do something in the constructor to see if it works + TestVirtualMethodCalls(); + } + static void TestLoadR2RImageFromByteArray() { Assembly assembly1 = typeof(Program).Assembly; @@ -422,6 +429,8 @@ static void TestLoadR2RImageFromByteArray() Assembly assembly2 = Assembly.Load(array); Assert.AreEqual(assembly2.FullName, assembly1.FullName); + + assembly2.CreateInstance("Program"); } [MethodImplAttribute(MethodImplOptions.NoInlining)] @@ -513,9 +522,8 @@ static void RunAllTests() Console.WriteLine("RVAFieldTest"); RVAFieldTest(); -// Disable for https://github.com/dotnet/runtime/issues/71507 -// Console.WriteLine("TestLoadR2RImageFromByteArray"); -// TestLoadR2RImageFromByteArray(); + Console.WriteLine("TestLoadR2RImageFromByteArray"); + TestLoadR2RImageFromByteArray(); Console.WriteLine("TestILBodyChange"); TestILBodyChange(); diff --git a/src/workloads/workloads.csproj b/src/workloads/workloads.csproj index e2e96fd572c17e..93df927b5a346a 100644 --- a/src/workloads/workloads.csproj +++ b/src/workloads/workloads.csproj @@ -7,6 +7,7 @@ false $(ArtifactsObjDir)workloads/ + $(WorkloadIntermediateOutputPath)VS/ $(ArtifactsBinDir)workloads/ $(workloadArtifactsPath)/ $(ArtifactsShippingPackagesDir) @@ -28,10 +29,10 @@ - + - + @@ -40,43 +41,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + <_ComponentResources Include="microsoft-net-runtime-mono-tooling-net6" Title=".NET 6.0 Shared Mobile Build Tools" + Description="Shared build tasks for mobile platform development."/> + <_ComponentResources Include="wasm-tools-net6" Title=".NET 6.0 WebAssembly Build Tools" + Description="Build tools for net6.0 WebAssembly ahead-of-time (AoT) compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-android-net6" Title=".NET 6.0 Android Build Tools" + Description="Build tools for net6.0 Android compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-android-aot-net6" Title=".NET 6.0 Android Build Tools (AoT)" + Description="Build tools for net6.0 Android ahead-of-time (AoT) compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-ios-net6" Title=".NET 6.0 iOS Build Tools" + Description="Build tools for net6.0 iOS compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-tvos-net" Title=".NET 6.0 tvOS Build Tools" + Description="Build tools for net6.0 tvOS compilation and native linking."/> + <_ComponentResources Include="microsoft-net-runtime-maccatalyst-net6" Title=".NET 6.0 Mac Catalyst Build Tools" + Description="Build tools for net6.0 Mac Catalyst compilation and native linking."/> + <_ComponentResources Include="runtimes-ios-net6" Title=".NET 6.0 iOS Runtimes" + Description=".NET 6.0 runtime components for iOS execution."/> + <_ComponentResources Include="runtimes-tvos-net6" Title=".NET 6.0 tvOS Build Tools" + Description=".NET 6.0 runtime components for tvOS execution."/> + <_ComponentResources Include="runtimes-maccatalyst-net6" Title=".NET 6.0 Mac Catalyst Build Tools" + Description=".NET 6.0 runtime components for Mac Catalyst execution."/> + <_ComponentResources Include="runtimes-windows-net6" Title=".NET 6.0 Windows Runtimes" + Description=".NET 6.0 runtime components for Windows execution."/> + + @@ -86,6 +96,10 @@ + + + Mono. + Microsoft @@ -95,50 +109,39 @@ - + - - - - - + - + - - + - - - + + + + - - - + + + - + + - @@ -169,15 +172,7 @@ - - - - - - - - - +