diff --git a/docs/workflow/testing/coreclr/disasm-checks.md b/docs/workflow/testing/coreclr/disasm-checks.md new file mode 100644 index 0000000000000000000000000000000000000000..e85bb3f49aaa7ae67b617a1232dde4b693e3f5c4 --- /dev/null +++ b/docs/workflow/testing/coreclr/disasm-checks.md @@ -0,0 +1,85 @@ +# Disassembly output verification checks +There are tests that the runtime executes that will be able to verify X64/ARM64 assembly output from the JIT. +The tools used to accomplish this are LLVM FileCheck, SuperFileCheck, and the JIT's ability to output disassembly using `DOTNET_JitDisasm`. LLVM FileCheck is built in https://www.github.com/dotnet/llvm-project and provides several packages for the various platforms. See more about LLVM FileCheck and its syntax here: https://llvm.org/docs/CommandGuide/FileCheck.html. SuperFileCheck is a custom tool located in https://www.github.com/dotnet/runtime. It wraps LLVM FileCheck and provides a simplified workflow for writing these tests in a C# file by leveraging Roslyn's syntax tree APIs. +# What is FileCheck? +From https://www.llvm.org/docs/CommandGuide/FileCheck.html: + +> **FileCheck** reads two files (one from standard input, and one specified on the command line) and uses one +to verify the other. This behavior is particularly useful for the testsuite, which wants to verify that the +output of some tool (e.g. **llc**) contains the expected information (for example, a movsd from esp or +whatever is interesting). This is similar to using **grep**, but it is optimized for matching multiple +different inputs in one file in a specific order. +# Converting an existing test to use disassembly checking +We will use the existing test `JIT\Regression\JitBlue\Runtime_33972` as an example. The test's intent is to verify that on ARM64, the method `AdvSimd.CompareEqual` behaves correctly when a zero vector is passed as the second argument. Below are snippets of its use: +```csharp + static Vector64 AdvSimd_CompareEqual_Vector64_Byte_Zero(Vector64 left) + { + return AdvSimd.CompareEqual(left, Vector64.Zero); + } +... +... + if (!ValidateResult_Vector64(AdvSimd_CompareEqual_Vector64_Byte_Zero(Vector64.Zero), Byte.MaxValue)) + result = -1; +``` +Currently, the test only verifies that the behavior is correct. It does not verify that the optimal ARM64 instruction was actually used. So now we will add this verification. +First we need to modify the project file `Runtime_33972.csproj`: +```xml + + + Exe + + + None + True + True + + + + + +``` +Looking at the `ItemGroup`: +```xml + + + +``` +We want to add `true` as a child of the `Compile` tag: +```xml + + + true + + +``` +Doing this lets the test builder and runner know that this test has assembly that needs to be verified. Finally, we need to write the assembly check and put the `[MethodImpl(MethodImplOptions.NoInlining)]` attribute on the method `AdvSimd_CompareEqual_Vector64_Byte_Zero`: +```csharp + [MethodImpl(MethodImplOptions.NoInlining)] + static Vector64 AdvSimd_CompareEqual_Vector64_Byte_Zero(Vector64 left) + { + // ARM64-FULL-LINE: cmeq v0.8b, v0.8b, #0 + return AdvSimd.CompareEqual(left, Vector64.Zero); + } +``` +And that is it. A few notes about the above example: +- `ARM64-FULL-LINE` checks to see if there is an exact line that matches the disassembly output of the method `AdvSimd_CompareEqual_Vector64_Byte_Zero` - leading and trailing spaces are ignored. +- Method bodies that have FileCheck syntax, e.g. `ARM64-FULL-LINE:`/`X64:`/etc, must have the attribute `[MethodImpl(MethodImplOptions.NoInlining)]`. If it does not, then an error is reported. +- FileCheck syntax outside of a method body will also report an error. +# Additional functionality +LLVM has a different setup where each test file is passed to `lit`, and `RUN:` lines inside the test specify +configuration details such as architectures to run, FileCheck prefixes to use, etc. In our case, the build +files handle a lot of this with build conditionals and `.cmd`/`.sh` file generation. Additionally, LLVM tests +rely on the order of the compiler output corresponding to the order of the input functions in the test file. +When running under the JIT, the compilation order is dependent on execution, not the source order. + +Functionality that has been added or moved to MSBuild: +- Conditionals controlling test execution +- Automatic specificiation of `CHECK` and `` as check prefixes + +Functionality that has been added or moved to SuperFileCheck: +- Each function is run under a separate invocation of FileCheck. SuperFileCheck adds additional `CHECK` lines + that search for the beginning and end of the output for each function. This ensures that output from + different functions don't contaminate each other. The separate invocations remove any dependency on the + order of the functions. +- `-FULL-LINE:` - same as using FileCheck's `:`, but checks that the line matches exactly; leading and trailing whitespace is ignored. +- `-FULL-LINE-NEXT:` - same as using FileCheck's `-NEXT:`, but checks that the line matches exactly; leading and trailing whitespace is ignored. diff --git a/eng/Subsets.props b/eng/Subsets.props index 1f4987bfc30c8f3c474555e39dfedbc3cd319c76..9e666f71fe0a6ac3c70651dd270dbbd6c0297299 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -313,6 +313,8 @@ + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 63d29c4c4cb408541c4a5ab8a7950186ca30d07f..278c46ba4cc4f34b600469f1b2da5e73b59be52f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -48,6 +48,38 @@ https://github.com/dotnet/llvm-project 8688ea2538ef1287c6619d35a26b5750d355cc18 + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + 754d13817d834b716d339183e21aac7d2489c496 + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + + + https://github.com/dotnet/llvm-project + c1304304028d603e34f6803b0740c34441f80d2e + https://github.com/dotnet/command-line-api 5618b2d243ccdeb5c7e50a298b33b13036b4351b diff --git a/eng/Versions.props b/eng/Versions.props index bc5dcb50ca08f7dd29b62b48c08dbdb12edf52e8..f509132c93fec55acc3aa1faeb855a4d88aecfd7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -204,5 +204,14 @@ 1.1.87-gba258badda 1.0.0-v3.14.0.5722 6.0.0-preview.5.21275.7 + + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 + 1.0.0-alpha.1.22431.4 diff --git a/src/coreclr/scripts/superpmi_collect_setup.py b/src/coreclr/scripts/superpmi_collect_setup.py index b16220f4a2471f008b55d98e088baf23cff8023b..a848f9ea190ed2f2cb23d7d4ebcbb56ab0583fab 100644 --- a/src/coreclr/scripts/superpmi_collect_setup.py +++ b/src/coreclr/scripts/superpmi_collect_setup.py @@ -168,6 +168,9 @@ native_binaries_to_ignore = [ "vcruntime140_1.dll", "R2RDump.exe", "R2RTest.exe", + "FileCheck.exe", + "SuperFileCheck.exe", + "llvm-mca.exe", "superpmi.exe", "superpmi-shim-collector.dll", "superpmi-shim-counter.dll", diff --git a/src/coreclr/tools/SuperFileCheck/Program.cs b/src/coreclr/tools/SuperFileCheck/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..79b5d1548b101a4f80a11e44cc37a87b1b1a1c34 --- /dev/null +++ b/src/coreclr/tools/SuperFileCheck/Program.cs @@ -0,0 +1,760 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace SuperFileCheck +{ + internal readonly record struct MethodDeclarationInfo(MethodDeclarationSyntax Syntax, string FullyQualifiedName); + + internal readonly record struct FileCheckResult(int ExitCode, string StandardOutput, string StandardError); + + internal class SuperFileCheckException : Exception + { + public SuperFileCheckException(string message): base(message) { } + } + + internal class Program + { + const string CommandLineArgumentCSharp = "--csharp"; + const string CommandLineArgumentCSharpListMethodNames = "--csharp-list-method-names"; + const string CommandLineCheckPrefixes = "--check-prefixes"; + const string CommandLineCheckPrefixesEqual = "--check-prefixes="; + const string CommandLineInputFile = "--input-file"; + const string SyntaxDirectiveFullLine = "-FULL-LINE:"; + const string SyntaxDirectiveFullLineNext = "-FULL-LINE-NEXT:"; + + static string FileCheckPath; + + static Program() + { + // Determine the location of LLVM FileCheck. + // We first look through the "runtimes" directory relative to + // the location of SuperFileCheck to find FileCheck. + // If it cannot find it, then we assume FileCheck + // is in the same directory as SuperFileCheck. + var superFileCheckPath = typeof(Program).Assembly.Location; + if (String.IsNullOrEmpty(superFileCheckPath)) + { + throw new SuperFileCheckException("Invalid SuperFileCheck path."); + } + var superFileCheckDir = Path.GetDirectoryName(superFileCheckPath); + if (superFileCheckDir != null) + { + var fileCheckPath = + Directory.EnumerateFiles(Path.Combine(superFileCheckDir, "runtimes/"), "FileCheck*", SearchOption.AllDirectories) + .FirstOrDefault(); + if (fileCheckPath != null) + { + FileCheckPath = fileCheckPath; + } + else + { + FileCheckPath = Path.Combine(superFileCheckDir, "FileCheck"); + } + } + else + { + FileCheckPath = "FileCheck"; + } + } + + /// + /// Checks if the given string contains LLVM "" directives, such as ":", "-LABEL:", etc.. + /// + static bool ContainsCheckPrefixes(string str, string[] checkPrefixes) + { + // LABEL, NOT, SAME, etc. are from LLVM FileCheck https://llvm.org/docs/CommandGuide/FileCheck.html + + // FULL-LINE and FULL-LINE-NEXT are not part of LLVM FileCheck - they are new syntax directives for SuperFileCheck to be able to + // match a single full-line, similar to that of LLVM FileCheck's --match-full-lines option. + + var pattern = $"({String.Join('|', checkPrefixes)})+?({{LITERAL}})?(:|-LABEL:|-NEXT:|-NOT:|-SAME:|-EMPTY:|-COUNT:|-DAG:|{SyntaxDirectiveFullLine}|{SyntaxDirectiveFullLineNext})"; + var regex = new System.Text.RegularExpressions.Regex(pattern); + return regex.Count(str) > 0; + } + + /// + /// Runs LLVM's FileCheck executable. + /// Will always redirect standard error and output. + /// + static async Task RunLLVMFileCheckAsync(string[] args) + { + var startInfo = new ProcessStartInfo(); + startInfo.FileName = FileCheckPath; + startInfo.Arguments = String.Join(' ', args); + startInfo.CreateNoWindow = true; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + + try + { + using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) + using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) + using (var proc = Process.Start(startInfo)) + { + if (proc == null) + { + return new FileCheckResult(1, String.Empty, String.Empty); + } + + var stdOut = new StringBuilder(); + var stdErr = new StringBuilder(); + + proc.OutputDataReceived += (_, e) => + { + if (e.Data == null) + { + outputWaitHandle.Set(); + } + else + { + stdOut.AppendLine(e.Data); + } + }; + + proc.ErrorDataReceived += (_, e) => + { + if (e.Data == null) + { + errorWaitHandle.Set(); + } + else + { + stdErr.AppendLine(e.Data); + } + }; + + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + + await proc.WaitForExitAsync(); + outputWaitHandle.WaitOne(); + errorWaitHandle.WaitOne(); + + var exitCode = proc.ExitCode; + return new FileCheckResult(exitCode, stdOut.ToString(), stdErr.ToString()); + } + } + catch (Exception ex) + { + return new FileCheckResult(1, String.Empty, ex.Message); + } + } + + /// + /// Get the method name from the method declaration. + /// + static string GetMethodName(MethodDeclarationSyntax methodDecl) + { + var methodName = methodDecl.Identifier.ValueText; + + var typeArity = methodDecl.TypeParameterList?.ChildNodes().Count(); + if (typeArity > 0) + { + methodName = $"{methodName}[*]"; + } + + return $"{methodName}(*)"; + } + + /// + /// Get the enclosing type declaration syntax of the given node. + /// Errors if it cannot find one. + /// + static TypeDeclarationSyntax GetEnclosingTypeDeclaration(SyntaxNode node) + { + var typeDecl = node.Ancestors().OfType().FirstOrDefault(); + if (typeDecl == null) + { + throw new SuperFileCheckException($"Unable to find enclosing type declaration on: {node.Span}"); + } + return typeDecl; + } + + /// + /// Get all the acestoral enclosing type declaration syntaxes of the given node. + /// + static TypeDeclarationSyntax[] GetEnclosingTypeDeclarations(SyntaxNode node) + { + return node.Ancestors().OfType().ToArray(); + } + + /// + /// Try to get an enclosing type name from the given syntax node. + /// + static string GetTypeName(TypeDeclarationSyntax typeDecl) + { + var typeName = typeDecl.Identifier.ValueText; + + var typeArity = typeDecl.TypeParameterList?.ChildNodes().Count(); + if (typeArity > 0) + { + typeName = $"{typeName}`{typeArity}[*]"; + } + + return typeName; + } + + /// + /// Get the method's fully qualified enclosing namespace and type name. + /// + static string GetFullyQualifiedEnclosingTypeName(MethodDeclarationSyntax methodDecl) + { + var qualifiedTypeName = String.Empty; + + var typeDecl = GetEnclosingTypeDeclaration(methodDecl); + qualifiedTypeName = GetTypeName(typeDecl); + + var typeDecls = GetEnclosingTypeDeclarations(typeDecl); + for (var i = 0; i < typeDecls.Length; i++) + { + typeDecl = typeDecls[i]; + qualifiedTypeName = $"{GetTypeName(typeDecl)}+{qualifiedTypeName}"; + } + + var namespaceDecl = typeDecl.Ancestors().OfType().FirstOrDefault(); + if (namespaceDecl != null) + { + var identifiers = + namespaceDecl.DescendantTokens().Where(x => x.IsKind(SyntaxKind.IdentifierToken)).Select(x => x.ValueText); + return $"{String.Join(".", identifiers)}.{qualifiedTypeName}"; + } + + return qualifiedTypeName; + } + + /// + /// Get all the descendant single line comment trivia items. + /// + static IEnumerable GetDescendantSingleLineCommentTrivia(SyntaxNode node) + { + return + node + .DescendantTrivia() + .Where(x => x.IsKind(SyntaxKind.SingleLineCommentTrivia)); + } + + /// + /// Gather all syntactical method declarations whose body contains + /// FileCheck syntax. + /// + static MethodDeclarationInfo[] FindMethodsByFile(string filePath, string[] checkPrefixes) + { + var syntaxTree = CSharpSyntaxTree.ParseText(SourceText.From(File.ReadAllText(filePath))); + var root = syntaxTree.GetRoot(); + + var trivia = + GetDescendantSingleLineCommentTrivia(root) + .Where(x => + { + if (x.Token.Parent == null) + { + return true; + } + + // A comment before the method declaration is considered a child of the method + // declaration. In this example: + // + // // trivia1 + // public void M() + // { + // // trivia2 + // } + // + // Both // trivia1 and // trivia2 are descendants of MethodDeclarationSyntax. + // + // We are only allowing checks to occur in 'trivia2'. The 'Contains' check is + // used to find 'trivia1'. + return !x.Token.Parent.Ancestors().Any(p => p.IsKind(SyntaxKind.MethodDeclaration) && p.Span.Contains(x.Span)); + }) + .Where(x => ContainsCheckPrefixes(x.ToString(), checkPrefixes)) + .ToArray(); + + if (trivia.Length > 0) + { + throw new SuperFileCheckException("FileCheck syntax not allowed outside of a method."); + } + + return + root + .DescendantNodes() + .OfType() + .Where(x => ContainsCheckPrefixes(x.ToString(), checkPrefixes)) + .Select(x => new MethodDeclarationInfo(x, $"{GetFullyQualifiedEnclosingTypeName(x)}:{GetMethodName(x)}")) + .ToArray(); + } + + /// + /// Helper to expand FileCheck syntax. + /// + static string? TryTransformDirective(string lineStr, string[] checkPrefixes, string syntaxDirective, string transformSuffix) + { + var index = lineStr.IndexOf(syntaxDirective); + if (index == -1) + { + return null; + } + + var prefix = lineStr.Substring(0, index); + + // Do not transform if the prefix is not part of --check-prefixes. + if (!checkPrefixes.Any(x => prefix.EndsWith(x))) + { + return null; + } + + return lineStr.Substring(0, index) + $"{transformSuffix}: {{{{^ *}}}}" + lineStr.Substring(index + syntaxDirective.Length) + "{{$}}"; + } + + /// + /// Will try to transform a line containing custom SuperFileCheck syntax, e.g. "CHECK-FULL-LINE:" + /// to the appropriate FileCheck syntax. + /// + static string TransformLine(TextLine line, string[] checkPrefixes) + { + var text = line.Text; + if (text == null) + { + throw new InvalidOperationException("SourceText is null."); + } + + var lineStr = text.ToString(line.Span); + + var result = TryTransformDirective(lineStr, checkPrefixes, SyntaxDirectiveFullLine, String.Empty); + if (result != null) + { + return result; + } + + result = TryTransformDirective(lineStr, checkPrefixes, SyntaxDirectiveFullLineNext, "-NEXT"); + + return result ?? lineStr; + } + + /// + /// Will try to transform a method containing custom SuperFileCheck syntax, e.g. "CHECK-FULL-LINE:" + /// to the appropriate FileCheck syntax. + /// + static string TransformMethod(MethodDeclarationSyntax methodDecl, string[] checkPrefixes) + { + return String.Join(Environment.NewLine, methodDecl.GetText().Lines.Select(x => TransformLine(x, checkPrefixes))); + } + + /// + /// Gets the starting line number of the method declaration. + /// + static int GetMethodStartingLineNumber(MethodDeclarationSyntax methodDecl) + { + var leadingTrivia = methodDecl.GetLeadingTrivia(); + if (leadingTrivia.Count == 0) + { + return methodDecl.GetLocation().GetLineSpan().StartLinePosition.Line; + } + else + { + return leadingTrivia[0].GetLocation().GetLineSpan().StartLinePosition.Line; + } + } + + /// + /// Returns only the method declaration text along with any SuperFileCheck transformations. + /// + static string PreProcessMethod(MethodDeclarationInfo methodDeclInfo, string[] checkPrefixes) + { + var methodDecl = methodDeclInfo.Syntax; + var methodName = methodDeclInfo.FullyQualifiedName.Replace("*", "{{.*}}"); // Change wild-card to FileCheck wild-card syntax. + + // Create anchors from the first prefix. + var startAnchorText = $"// {checkPrefixes[0]}-LABEL: {methodName}"; + var endAnchorText = $"// {checkPrefixes[0]}: {methodName}"; + + // Create temp source file based on the source text of the method. + // Newlines are added to pad the text so FileCheck's error messages will correspond + // to the correct line and column of the original source file. + // This is not perfect but will work for most cases. + var lineNumber = GetMethodStartingLineNumber(methodDecl); + var tmpSrc = new StringBuilder(); + for (var i = 1; i < lineNumber; i++) + { + tmpSrc.AppendLine(String.Empty); + } + tmpSrc.AppendLine(startAnchorText); + tmpSrc.AppendLine(TransformMethod(methodDecl, checkPrefixes)); + tmpSrc.AppendLine(endAnchorText); + + return tmpSrc.ToString(); + } + + /// + /// Runs SuperFileCheck logic. + /// + + static async Task RunSuperFileCheckAsync(MethodDeclarationInfo methodDeclInfo, string[] args, string[] checkPrefixes, string tmpFilePath) + { + File.WriteAllText(tmpFilePath, PreProcessMethod(methodDeclInfo, checkPrefixes)); + + try + { + args[0] = tmpFilePath; + return await RunLLVMFileCheckAsync(args); + } + finally + { + try { File.Delete(tmpFilePath); } catch { } + } + } + + /// + /// Checks if the argument is --csharp. + /// + static bool IsArgumentCSharp(string arg) + { + return arg.Equals(CommandLineArgumentCSharp); + } + + /// + /// Checks if the argument is --csharp-list-method-names. + /// + static bool IsArgumentCSharpListMethodNames(string arg) + { + return arg.Equals(CommandLineArgumentCSharpListMethodNames); + } + + /// + /// Checks if the argument contains -h. + /// + static bool ArgumentsContainHelp(string[] args) + { + return args.Any(x => x.Contains("-h")); + } + + /// + /// From the given arguments, find the first --check-prefixes argument and parse its value + /// in the form of an array. + /// + static string[] ParseCheckPrefixes(string[] args) + { + var checkPrefixesArg = args.FirstOrDefault(x => x.StartsWith(CommandLineCheckPrefixesEqual)); + if (checkPrefixesArg == null) + { + return new string[] { }; + } + + return + checkPrefixesArg + .Replace(CommandLineCheckPrefixesEqual, "") + .Split(",") + .Where(x => !String.IsNullOrWhiteSpace(x)) + .ToArray(); + } + + /// + /// Will always return one or more prefixes. + /// + static string[] DetermineCheckPrefixes(string[] args) + { + var checkPrefixes = ParseCheckPrefixes(args); + if (checkPrefixes.Length == 0) + { + // FileCheck's default. + return new string[] { "CHECK" }; + } + + return checkPrefixes; + } + + /// + /// Prints error expecting a CSharp file. + /// + static void PrintErrorExpectedCSharpFile() + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("Expected C# file."); + Console.ResetColor(); + } + + /// + /// Prints error indicating a duplicate method name was found. + /// + static void PrintErrorDuplicateMethodName(string methodName) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"Duplicate method name found: {methodName}"); + Console.ResetColor(); + } + + /// + /// Prints error indicating the method was not marked with attribute 'MethodImpl(MethodImplOptions.NoInlining)'. + /// + static void PrintErrorMethodNoInlining(string methodName) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"'{methodName}' is not marked with attribute 'MethodImpl(MethodImplOptions.NoInlining)'."); + Console.ResetColor(); + } + + /// + /// Prints error indicating that no methods were found to have any FileCheck syntax + /// of the given --check-prefixes. + /// + static void PrintErrorNoMethodsFound(string[] checkPrefixes) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("No methods were found. Check if any method bodies are using one or more of the following FileCheck prefixes:"); + foreach (var prefix in checkPrefixes) + { + Console.Error.WriteLine($" {prefix}"); + } + Console.ResetColor(); + } + + /// + /// Prints error indicating that a --input-file was not found. + /// + static void PrintErrorNoInputFileFound() + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"{CommandLineInputFile} is required."); + Console.ResetColor(); + } + + /// + /// Prints command line help. + /// + static void PrintHelp() + { + Console.Write(Environment.NewLine); + Console.WriteLine("USAGE: SuperFileCheck [options] "); + Console.WriteLine("USAGE: SuperFileCheck [options]"); + Console.Write(Environment.NewLine); + Console.WriteLine("SUPER OPTIONS:"); + Console.Write(Environment.NewLine); + Console.WriteLine($" --csharp - A {CommandLineInputFile} is required."); + Console.WriteLine($" must be a C# source file."); + Console.WriteLine($" Methods must not have duplicate names."); + Console.WriteLine($" Methods must be marked as not inlining."); + Console.WriteLine($" One or more methods are required."); + Console.WriteLine($" Prefixes are determined by {CommandLineCheckPrefixes}."); + Console.WriteLine($" --csharp-list-method-names - Print a space-delimited list of method names to be"); + Console.WriteLine($" supplied to environment variable DOTNET_JitDisasm."); + Console.WriteLine($" must be a C# source file."); + Console.WriteLine($" Methods must not have duplicate names."); + Console.WriteLine($" Methods must be marked as not inlining."); + Console.WriteLine($" Prints nothing if no methods are found."); + Console.WriteLine($" Prefixes are determined by {CommandLineCheckPrefixes}."); + } + + /// + /// Try to find the first duplicate method name of the given method declarations. + /// + static string? TryFindDuplicateMethodName(MethodDeclarationInfo[] methodDeclInfos) + { + var set = new HashSet(); + + var duplicateMethodDeclInfo = + methodDeclInfos.FirstOrDefault(x => !set.Add(x.FullyQualifiedName)); + + return duplicateMethodDeclInfo.FullyQualifiedName; + } + + /// + /// Is the method marked with MethodImpl(MethodImplOptions.NoInlining)? + /// + static bool MethodHasNoInlining(MethodDeclarationSyntax methodDecl) + { + return methodDecl.AttributeLists.ToString().Contains("MethodImplOptions.NoInlining"); + } + + /// + /// Will print an error if any duplicate method names are found. + /// + static bool CheckDuplicateMethodNames(MethodDeclarationInfo[] methodDeclInfos) + { + var duplicateMethodName = TryFindDuplicateMethodName(methodDeclInfos); + if (duplicateMethodName != null) + { + PrintErrorDuplicateMethodName(duplicateMethodName); + return false; + } + + return true; + } + + /// + /// Will print an error if the methods containing FileCheck syntax + /// is not marked with the attribute 'MethodImpl(MethodImplOptions.NoInlining)'. + /// + static bool CheckMethodsHaveNoInlining(MethodDeclarationInfo[] methodDeclInfos) + { + return + methodDeclInfos + .All(methodDeclInfo => + { + if (!MethodHasNoInlining(methodDeclInfo.Syntax)) + { + PrintErrorMethodNoInlining(methodDeclInfo.FullyQualifiedName); + return false; + } + + return true; + }); + } + + /// + /// The goal of SuperFileCheck is to make writing LLVM FileCheck tests against the + /// NET Core Runtime easier in C#. + /// + static async Task Main(string[] args) + { + if (args.Length >= 1) + { + if (IsArgumentCSharpListMethodNames(args[0])) + { + if (args.Length == 1) + { + PrintErrorExpectedCSharpFile(); + return 1; + } + + var checkPrefixes = DetermineCheckPrefixes(args); + try + { + var methodDeclInfos = FindMethodsByFile(args[1], checkPrefixes); + + if (methodDeclInfos.Length == 0) + { + return 0; + } + + if (!CheckDuplicateMethodNames(methodDeclInfos)) + { + return 1; + } + + Console.Write(String.Join(' ', methodDeclInfos.Select(x => x.FullyQualifiedName))); + return 0; + } + catch(Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + return 1; + } + } + + if (IsArgumentCSharp(args[0])) + { + if (args.Length == 1) + { + PrintErrorExpectedCSharpFile(); + return 1; + } + + var checkFilePath = args[1]; + var checkFileNameNoExt = Path.GetFileNameWithoutExtension(checkFilePath); + + var hasInputFile = args.Any(x => x.Equals(CommandLineInputFile)); + if (!hasInputFile) + { + PrintErrorNoInputFileFound(); + return 1; + } + + var checkPrefixes = DetermineCheckPrefixes(args); + try + { + var methodDeclInfos = FindMethodsByFile(checkFilePath, checkPrefixes); + + if (!CheckDuplicateMethodNames(methodDeclInfos)) + { + return 1; + } + + if (!CheckMethodsHaveNoInlining(methodDeclInfos)) + { + return 1; + } + + if (methodDeclInfos.Length > 0) + { + var didSucceed = true; + + var tasks = new Task[methodDeclInfos.Length]; + + // Remove the first 'csharp' argument so we can pass the rest of the args + // to LLVM FileCheck. + var argsToCopy = args.AsSpan(1).ToArray(); + + for (int i = 0; i < methodDeclInfos.Length; i++) + { + var index = i; + var tmpFileName = $"__tmp{index}_{checkFileNameNoExt}.cs"; + var tmpDirName = Path.GetDirectoryName(checkFilePath); + string tmpFilePath; + if (String.IsNullOrWhiteSpace(tmpDirName)) + { + tmpFilePath = tmpFileName; + } + else + { + tmpFilePath = Path.Combine(tmpDirName, tmpFileName); + } + tasks[i] = Task.Run(() => RunSuperFileCheckAsync(methodDeclInfos[index], argsToCopy.ToArray(), checkPrefixes, tmpFilePath)); + } + + await Task.WhenAll(tasks); + + foreach (var x in tasks) + { + if (x.Result.ExitCode != 0) + { + didSucceed = false; + } + Console.Write(x.Result.StandardOutput); + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.Write(x.Result.StandardError); + Console.ResetColor(); + } + + return didSucceed ? 0 : 1; + } + else + { + PrintErrorNoMethodsFound(checkPrefixes); + return 1; + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + return 1; + } + } + } + + var result = await RunLLVMFileCheckAsync(args); + Console.Write(result.StandardOutput); + + if (ArgumentsContainHelp(args)) + { + PrintHelp(); + } + + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.Write(result.StandardError); + Console.ResetColor(); + return result.ExitCode; + } + } +} diff --git a/src/coreclr/tools/SuperFileCheck/SuperFileCheck.csproj b/src/coreclr/tools/SuperFileCheck/SuperFileCheck.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f4825414ceec611d3060435812c366e147986d2b --- /dev/null +++ b/src/coreclr/tools/SuperFileCheck/SuperFileCheck.csproj @@ -0,0 +1,51 @@ + + + SuperFileCheck + Exe + $(NetCoreAppToolCurrent) + AnyCPU + false + true + true + false + enable + enable + $(RuntimeBinDir)\SuperFileCheck + + + + + <_jitToolsRidPlatformIndex>$(OutputRid.LastIndexOf('-')) + $(OutputRid.Substring(0, $(_jitToolsRidPlatformIndex))) + $(OutputRid.Substring($(_jitToolsRidPlatformIndex)).TrimStart('-')) + + + linux + + + osx.10.12 + osx.11.0 + + + x64 + arm64 + $(JITToolsRidWithoutPlatform)-$(JITToolsRidPlatform) + + $(runtimelinuxarm64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimelinuxx64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimelinuxmuslarm64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimelinuxmuslx64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimewinarm64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimewinx64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimeosx110arm64MicrosoftNETCoreRuntimeJITToolsVersion) + $(runtimeosx1012x64MicrosoftNETCoreRuntimeJITToolsVersion) + + + + + + $(JITToolsVersion) + + + + diff --git a/src/tests/Common/CLRTest.Execute.Bash.targets b/src/tests/Common/CLRTest.Execute.Bash.targets index 7ed0dcb9db2a1a5a2b12351c4efe26e302a7ef77..b892c0d95fc44c9d5760ed35ba9560451028b2c1 100644 --- a/src/tests/Common/CLRTest.Execute.Bash.targets +++ b/src/tests/Common/CLRTest.Execute.Bash.targets @@ -430,8 +430,13 @@ echo /usr/bin/env bash $(InputAssemblyName) %24(printf "'%s' " "${CLRTestExecuti CLRTestExitCode=$? CLRTestExpectedExitCode=0 ]]> - + + + @(CLRTestBashEnvironmentVariable -> '%(Identity)', '%0a') diff --git a/src/tests/Common/CLRTest.Execute.Batch.targets b/src/tests/Common/CLRTest.Execute.Batch.targets index 04c9e017d7e513cf3194fec3dc1de8d36216d1fc..a64b2aee4a1206cbebf002e02cf30ca458b27a2c 100644 --- a/src/tests/Common/CLRTest.Execute.Batch.targets +++ b/src/tests/Common/CLRTest.Execute.Batch.targets @@ -301,6 +301,7 @@ $(BatchIlrtTestLaunchCmds) if defined RunCrossGen2 ( call :TakeLock ) + ECHO %LAUNCHER% %ExePath% %CLRTestExecutionArguments% %LAUNCHER% %ExePath% %CLRTestExecutionArguments% set CLRTestExitCode=!ERRORLEVEL! @@ -338,6 +339,12 @@ cmd /c $(InputAssemblyName) set CLRTestExitCode=!ERRORLEVEL! set CLRTestExpectedExitCode=0 ]]> + + + @@ -445,7 +452,7 @@ $(CLRTestBatchPreCommands) REM Launch $(BatchCLRTestLaunchCmds) REM PostCommands -$(CLRTestBatchPostCommands) +$(BatchCLRTestPostCommands) $(BatchCLRTestExitCodeCheck) ]]> diff --git a/src/tests/Common/CLRTest.Jit.targets b/src/tests/Common/CLRTest.Jit.targets index f5558e69e484c1eb236337a68d2699414cb77f72..ad751b734bb7cb48087748054f6807c4ece8e00d 100644 --- a/src/tests/Common/CLRTest.Jit.targets +++ b/src/tests/Common/CLRTest.Jit.targets @@ -20,6 +20,11 @@ WARNING: When setting properties based on their current state (for example: --> + + $(BashScriptSnippetGen);GetIlasmRoundTripBashScript;GetDisasmCheckBashScript + $(BatchScriptSnippetGen);GetIlasmRoundTripBatchScript;GetDisasmCheckBatchScript + + + + + + + + + + + false + true + + /dev/null + $(scriptPath)__jit_disasm.out + + /dev/null + $(scriptPath)__jit_disasm_list.out + + ' dotnet $CORE_ROOT/SuperFileCheck/SuperFileCheck.dll --csharp-list-method-names "%(Identity)" --allow-unused-prefixes --check-prefixes=CHECK,$(TargetArchitecture.ToUpperInvariant()),$(TargetArchitecture.ToUpperInvariant())-$(TargetOS.ToUpperInvariant()) > "$(BashDisasmListOutputFile)" + ERRORLEVEL=$? + export COMPlus_JitDisasm=`cat $(BashDisasmListOutputFile)` + export COMPlus_JitDiffableDasm=1 + export COMPlus_JitStdOutFile=$(BashDisasmOutputFile) + if [[ $ERRORLEVEL -ne 0 ]] + then + echo EXECUTION OF FILECHECK - FAILED $ERRORLEVEL + exit 1 + fi', '%0a') +]]> + + + + ' dotnet $CORE_ROOT/SuperFileCheck/SuperFileCheck.dll --csharp "%(Identity)" --allow-unused-prefixes --check-prefixes=CHECK,$(TargetArchitecture.ToUpperInvariant()),$(TargetArchitecture.ToUpperInvariant())-$(TargetOS.ToUpperInvariant()) --dump-input-context 25 --input-file "$(BashDisasmOutputFile)" + ERRORLEVEL=$? + if [[ $ERRORLEVEL -ne 0 ]] + then + echo EXECUTION OF FILECHECK - FAILED $ERRORLEVEL + exit 1 + fi', '%0a') +fi +]]> + + + + + + + false + true + + NUL + $(scriptPath)__jit_disasm.out + + NUL + $(scriptPath)__jit_disasm_list.out + + + ' dotnet %CORE_ROOT%\SuperFileCheck\SuperFileCheck.dll --csharp-list-method-names "%(Identity)" --check-prefixes=CHECK,$(TargetArchitecture.ToUpperInvariant()),$(TargetArchitecture.ToUpperInvariant())-$(TargetOS.ToUpperInvariant()) > "$(BatchDisasmListOutputFile)" + IF NOT "!ERRORLEVEL!" == "0" ( + ECHO EXECUTION OF FILECHECK LISTING METHOD NAMES - FAILED !ERRORLEVEL! + Exit /b 1 + )', '%0d%0a') + for /F "delims=" %%g in ($(BatchDisasmListOutputFile)) do set COMPlus_JitDisasm=%%g + set COMPlus_JitDiffableDasm=1 + set COMPlus_JitStdOutFile=$(BatchDisasmOutputFile) +]]> + + + + ' dotnet %CORE_ROOT%\SuperFileCheck\SuperFileCheck.dll --csharp "%(Identity)" --allow-unused-prefixes --check-prefixes=CHECK,$(TargetArchitecture.ToUpperInvariant()),$(TargetArchitecture.ToUpperInvariant())-$(TargetOS.ToUpperInvariant()) --dump-input-context 25 --input-file "$(BatchDisasmOutputFile)" + IF NOT "!ERRORLEVEL!" == "0" ( + ECHO EXECUTION OF FILECHECK - FAILED !ERRORLEVEL! + Exit /b 1 + )', '%0d%0a') +) +]]> + + + + + + True + + diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets index 4636f9f00f70955d786407b17fd946667aaf3345..369abf945a373ca3b24f22cbfd0c713cc86c2596 100644 --- a/src/tests/Directory.Build.targets +++ b/src/tests/Directory.Build.targets @@ -383,6 +383,11 @@ True + + + True + + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.cs b/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.cs index d57bd99e680e4ed0714e83a9f159b2b86bf9d105..44fb1ff1822f1d6439dc57539cb661ea67b42849 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.cs +++ b/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.cs @@ -8,143 +8,167 @@ class Program { - // CompareEqual + // CompareEqual [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Byte_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.8b, v0.8b, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_SByte_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.8b, v0.8b, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_UInt16_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.4h, v0.4h, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int16_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.4h, v0.4h, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_UInt32_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int32_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Single_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmeq v0.2s, v0.2s, #0.0 return AdvSimd.CompareEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int32_CreateZero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(left, Vector64.Create(0)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int32_CreateZeroZero(Vector64 left) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(left, Vector64.Create(0, 0)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Single_CreateZero(Vector64 left) { + // ARM64-FULL-LINE: fcmeq v0.2s, v0.2s, #0.0 return AdvSimd.CompareEqual(left, Vector64.Create(0f)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Single_CreateZeroZero(Vector64 left) { + // ARM64-FULL-LINE: fcmeq v0.2s, v0.2s, #0.0 return AdvSimd.CompareEqual(left, Vector64.Create(0f, 0f)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Byte_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.16b, v0.16b, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_SByte_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.16b, v0.16b, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_UInt16_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.8h, v0.8h, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int16_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.8h, v0.8h, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_UInt32_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int32_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Single_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmeq v0.4s, v0.4s, #0.0 return AdvSimd.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int32_CreateZero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(left, Vector128.Create(0)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int32_CreateZeroZeroZeroZero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(left, Vector128.Create(0, 0, 0, 0)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Single_CreateZero(Vector128 left) { + // ARM64-FULL-LINE: fcmeq v0.4s, v0.4s, #0.0 return AdvSimd.CompareEqual(left, Vector128.Create(0f)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Single_CreateZeroZeroZeroZero(Vector128 left) { + // ARM64-FULL-LINE: fcmeq v0.4s, v0.4s, #0.0 return AdvSimd.CompareEqual(left, Vector128.Create(0f, 0f, 0f, 0f)); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Single_CreateZeroZeroZeroZero_AsVariable(Vector128 left) { + // 'asVar' should be propagated. + // ARM64-FULL-LINE: fcmeq v0.4s, v0.4s, #0.0 var asVar = Vector128.Create(0f, 0f, 0f, 0f); return AdvSimd.CompareEqual(left, asVar); } @@ -154,12 +178,19 @@ static Vector128 AdvSimd_CompareEqual_Vector128_Single_CreateZeroZeroZero { Vector128 result = default; var asVar = Vector128.Create(0f, 0f, 0f, 0f); + // 'asVar' should be propagated. + // 'AdvSimd.CompareEqual' should be hoisted out of the loops. + // ARM64-FULL-LINE: fcmeq {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, #0.0 + // ARM64: blt + // ARM64-NOT: fcmeq for (var i = 0; i < 4; i++) { result = AdvSimd.CompareEqual(left, asVar); result = AdvSimd.CompareEqual(left, asVar); result = AdvSimd.CompareEqual(left, asVar); result = AdvSimd.CompareEqual(left, asVar); + // ARM64: blt + // ARM64-NOT: fcmeq for (var j = 0; j < 4; j++) { result = AdvSimd.CompareEqual(left, asVar); @@ -178,9 +209,18 @@ static unsafe Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Long_AsVariab Vector128 asVar = Vector128.Create((long)0); Vector128 asVar2 = Vector128.Create((nint)0); Vector128 asVar3 = asVar2.AsInt64(); + // 'asVar' should be propagated. + // 'asVar2' should be propagated. + // 'asVar3' should be propagated. + // 'AdvSimd.CompareEqual' should be hoisted out of the loops. + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.2d, {{v[0-9]+}}.2d, #0 + // ARM64: blt + // ARM64-NOT: cmeq for (var i = 0; i < 4; i++) { result = AdvSimd.Arm64.CompareEqual(left, asVar); + // ARM64: blt + // ARM64-NOT: cmeq for (var j = 0; j < 4; j++) { result = AdvSimd.Arm64.CompareEqual(left, asVar3); @@ -192,42 +232,49 @@ static unsafe Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Long_AsVariab [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Double_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmeq v0.2d, v0.2d, #0.0 return AdvSimd.Arm64.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_UInt64_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Int64_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmeq v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Single_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmeq s0, s0, #0.0 return AdvSimd.Arm64.CompareEqualScalar(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Double_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmeq d0, d0, #0.0 return AdvSimd.Arm64.CompareEqualScalar(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_UInt64_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq d0, d0, #0 return AdvSimd.Arm64.CompareEqualScalar(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Int64_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmeq d0, d0, #0 return AdvSimd.Arm64.CompareEqualScalar(left, Vector64.Zero); } @@ -236,126 +283,147 @@ static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Int64_Zero(Vecto [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Byte_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.8b, v0.8b, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_SByte_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.8b, v0.8b, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_UInt16_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.4h, v0.4h, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int16_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.4h, v0.4h, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_UInt32_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Int32_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq v0.2s, v0.2s, #0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareEqual_Vector64_Single_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: fcmeq v0.2s, v0.2s, #0.0 return AdvSimd.CompareEqual(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Byte_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.16b, v0.16b, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_SByte_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.16b, v0.16b, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_UInt16_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.8h, v0.8h, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int16_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.8h, v0.8h, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_UInt32_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Int32_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.4s, v0.4s, #0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareEqual_Vector128_Single_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: fcmeq v0.4s, v0.4s, #0.0 return AdvSimd.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Double_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: fcmeq v0.2d, v0.2d, #0.0 return AdvSimd.Arm64.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_UInt64_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareEqual_Vector128_Int64_Zero_Swapped(Vector128 right) { + // ARM64-FULL-LINE: cmeq v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareEqual(Vector128.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Single_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: fcmeq s0, s0, #0.0 return AdvSimd.Arm64.CompareEqualScalar(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Double_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: fcmeq d0, d0, #0.0 return AdvSimd.Arm64.CompareEqualScalar(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_UInt64_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq d0, d0, #0 return AdvSimd.Arm64.CompareEqualScalar(Vector64.Zero, right); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Int64_Zero_Swapped(Vector64 right) { + // ARM64-FULL-LINE: cmeq d0, d0, #0 return AdvSimd.Arm64.CompareEqualScalar(Vector64.Zero, right); } @@ -364,48 +432,58 @@ static Vector64 AdvSimd_Arm64_CompareEqualScalar_Vector64_Int64_Zero_Swapp [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareGreaterThan_Vector64_Byte_Zero(Vector64 left) { + // ARM64-FULL-LINE: movi {{v[0-9]+}}.2s, #0 + // ARM64-FULL-LINE-NEXT: cmhi {{v[0-9]+}}.8b, {{v[0-9]+}}.8b, {{v[0-9]+}}.8b return AdvSimd.CompareGreaterThan(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareGreaterThan_Vector64_Single_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmgt v0.2s, v0.2s, #0.0 return AdvSimd.CompareGreaterThan(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareGreaterThan_Vector128_Byte_Zero(Vector128 left) { + // ARM64-FULL-LINE: movi {{v[0-9]+}}.4s, #0 + // ARM64-FULL-LINE-NEXT: cmhi {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b return AdvSimd.CompareGreaterThan(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareGreaterThan_Vector128_Single_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmgt v0.4s, v0.4s, #0.0 return AdvSimd.CompareGreaterThan(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareGreaterThan_Vector128_Double_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmgt v0.2d, v0.2d, #0.0 return AdvSimd.Arm64.CompareGreaterThan(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareGreaterThan_Vector128_Int64_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmgt v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareGreaterThan(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareGreaterThanScalar_Vector64_Double_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmgt d0, d0, #0.0 return AdvSimd.Arm64.CompareGreaterThanScalar(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareGreaterThanScalar_Vector64_Int64_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmgt d0, d0, #0 return AdvSimd.Arm64.CompareGreaterThanScalar(left, Vector64.Zero); } @@ -414,48 +492,58 @@ static Vector64 AdvSimd_Arm64_CompareGreaterThanScalar_Vector64_Int64_Zero [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareGreaterThanOrEqual_Vector64_Byte_Zero(Vector64 left) { + // ARM64-FULL-LINE: movi {{v[0-9]+}}.2s, #0 + // ARM64-FULL-LINE-NEXT: cmhs {{v[0-9]+}}.8b, {{v[0-9]+}}.8b, {{v[0-9]+}}.8b return AdvSimd.CompareGreaterThanOrEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_CompareGreaterThanOrEqual_Vector64_Single_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmge v0.2s, v0.2s, #0.0 return AdvSimd.CompareGreaterThanOrEqual(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareGreaterThanOrEqual_Vector128_Byte_Zero(Vector128 left) { + // ARM64-FULL-LINE: movi {{v[0-9]+}}.4s, #0 + // ARM64-FULL-LINE-NEXT: cmhs {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b return AdvSimd.CompareGreaterThanOrEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_CompareGreaterThanOrEqual_Vector128_Single_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmge v0.4s, v0.4s, #0.0 return AdvSimd.CompareGreaterThanOrEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareGreaterThanOrEqual_Vector128_Double_Zero(Vector128 left) { + // ARM64-FULL-LINE: fcmge v0.2d, v0.2d, #0.0 return AdvSimd.Arm64.CompareGreaterThanOrEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector128 AdvSimd_Arm64_CompareGreaterThanOrEqual_Vector128_Int64_Zero(Vector128 left) { + // ARM64-FULL-LINE: cmge v0.2d, v0.2d, #0 return AdvSimd.Arm64.CompareGreaterThanOrEqual(left, Vector128.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareGreaterThanOrEqualScalar_Vector64_Double_Zero(Vector64 left) { + // ARM64-FULL-LINE: fcmge d0, d0, #0.0 return AdvSimd.Arm64.CompareGreaterThanOrEqualScalar(left, Vector64.Zero); } [MethodImpl(MethodImplOptions.NoInlining)] static Vector64 AdvSimd_Arm64_CompareGreaterThanOrEqualScalar_Vector64_Int64_Zero(Vector64 left) { + // ARM64-FULL-LINE: cmge d0, d0, #0 return AdvSimd.Arm64.CompareGreaterThanOrEqualScalar(left, Vector64.Zero); } diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.csproj index bf6f589eb325bb8e811900352a5760e071828316..dd154d5d0fec6b2d3373ae97041992660cc458c4 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.csproj +++ b/src/tests/JIT/Regression/JitBlue/Runtime_33972/Runtime_33972.csproj @@ -6,8 +6,20 @@ None True True + + - + + true + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.cs b/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.cs index 9d61a2f13f1958277bf9575c9c1d72c787dd63b7..01ff8aafc7fb8df7f5f9309224dc31ec9116de61 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.cs +++ b/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.cs @@ -9,12 +9,35 @@ class Program [MethodImpl(MethodImplOptions.NoInlining)] static uint PerformMod_1(uint i) { + // X64-FULL-LINE: mov [[REG0:[a-z]+]], [[REG1:[a-z0-9]+]] + // X64-FULL-LINE-NEXT: and [[REG0]], 7 + + // ARM64-FULL-LINE: and w0, w0, #7 + return i % 8; } [MethodImpl(MethodImplOptions.NoInlining)] static int PerformMod_2(int i) { + // X64-FULL-LINE: mov [[REG0:[a-z]+]], [[REG1:[a-z]+]] + // X64-FULL-LINE-NEXT: sar [[REG0]], 31 + // X64-FULL-LINE-NEXT: and [[REG0]], 15 + // X64-FULL-LINE-NEXT: add [[REG0]], [[REG1]] + // X64-FULL-LINE-NEXT: and [[REG0]], -16 + // X64-WINDOWS-FULL-LINE-NEXT: mov [[REG2:[a-z]+]], [[REG1]] + // X64-WINDOWS-FULL-LINE-NEXT: sub [[REG2]], [[REG0]] + // X64-WINDOWS-FULL-LINE-NEXT: mov [[REG0]], [[REG2]] + // X64-LINUX-FULL-LINE-NEXT: sub [[REG1]], [[REG0]] + // X64-LINUX-FULL-LINE-NEXT: mov [[REG0]], [[REG1]] + // X64-OSX-FULL-LINE-NEXT: sub [[REG1]], [[REG0]] + // X64-OSX-FULL-LINE-NEXT: mov [[REG0]], [[REG1]] + + // ARM64-FULL-LINE: and w1, w0, #15 + // ARM64-FULL-LINE-NEXT: negs w0, w0 + // ARM64-FULL-LINE-NEXT: and w0, w0, #15 + // ARM64-FULL-LINE-NEXT: csneg w0, w1, w0, mi + return i % 16; } @@ -27,6 +50,12 @@ static int PerformMod_3(int i, int j) [MethodImpl(MethodImplOptions.NoInlining)] static int MSUB(int a, int b, int c) { + // X64-FULL-LINE: imul [[REG0:[a-z]+]], [[REG1:[a-z0-9]+]] + // X64-FULL-LINE-NEXT: mov [[REG2:[a-z]+]], [[REG3:[a-z]+]] + // X64-FULL-LINE-NEXT: sub [[REG2]], [[REG0]] + + // ARM64-FULL-LINE: msub w0, w1, w2, w0 + return a - b * c; } diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.csproj index 17923c1464d4fd8ddd36db5a481f0f07170070d7..a2c782813fb29bd0464d914dc5857f8a44380cb9 100644 --- a/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.csproj +++ b/src/tests/JIT/Regression/JitBlue/Runtime_34937/Runtime_34937.csproj @@ -6,9 +6,21 @@ True True + + - + + true + diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.cs new file mode 100644 index 0000000000000000000000000000000000000000..d8321f9e249822f48b8920c3b37bc8fdc24cdeb1 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.cs @@ -0,0 +1,32 @@ +// 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; + +public class Program +{ + public static int Main() + { + Console.WriteLine(CallFoo(new C())); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CallFoo(T val) where T : IFace + { + // This is testing that a constrained.callvirt through a T variable doesn't use a helper lookup. + // CHECK-NOT: CORINFO_HELP + return val.Foo(); + } +} + +public interface IFace +{ + int Foo(); +} + +public class C : IFace +{ + public int Foo() => 0; +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.csproj new file mode 100644 index 0000000000000000000000000000000000000000..79b88249c477bdea7545aa88c47c3f3d04f32937 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73681/Runtime_73681.csproj @@ -0,0 +1,11 @@ + + + Exe + True + + + + true + + +