// 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.Linq; using Internal.TypeSystem; using Internal.JitInterface; namespace ILCompiler { public class InstructionSetSupport { private readonly TargetArchitecture _targetArchitecture; private readonly InstructionSetFlags _optimisticInstructionSets; private readonly InstructionSetFlags _supportedInstructionSets; private readonly InstructionSetFlags _unsupportedInstructionSets; private readonly InstructionSetFlags _nonSpecifiableInstructionSets; public InstructionSetSupport(InstructionSetFlags supportedInstructionSets, InstructionSetFlags unsupportedInstructionSets, TargetArchitecture architecture) : this(supportedInstructionSets, unsupportedInstructionSets, supportedInstructionSets, default(InstructionSetFlags), architecture) { } public InstructionSetSupport(InstructionSetFlags supportedInstructionSets, InstructionSetFlags unsupportedInstructionSets, InstructionSetFlags optimisticInstructionSets, InstructionSetFlags nonSpecifiableInstructionSets, TargetArchitecture architecture) { _supportedInstructionSets = supportedInstructionSets; _unsupportedInstructionSets = unsupportedInstructionSets; _optimisticInstructionSets = optimisticInstructionSets; _targetArchitecture = architecture; _nonSpecifiableInstructionSets = nonSpecifiableInstructionSets; } public bool IsInstructionSetSupported(InstructionSet instructionSet) { return _supportedInstructionSets.HasInstructionSet(instructionSet); } public bool IsInstructionSetOptimisticallySupported(InstructionSet instructionSet) { return _optimisticInstructionSets.HasInstructionSet(instructionSet); } public bool IsInstructionSetExplicitlyUnsupported(InstructionSet instructionSet) { return _unsupportedInstructionSets.HasInstructionSet(instructionSet); } public InstructionSetFlags OptimisticFlags => _optimisticInstructionSets; public InstructionSetFlags SupportedFlags => _supportedInstructionSets; public InstructionSetFlags ExplicitlyUnsupportedFlags => _unsupportedInstructionSets; public InstructionSetFlags NonSpecifiableFlags => _nonSpecifiableInstructionSets; public TargetArchitecture Architecture => _targetArchitecture; public static string GetHardwareIntrinsicId(TargetArchitecture architecture, TypeDesc potentialTypeDesc) { if (!potentialTypeDesc.IsIntrinsic || !(potentialTypeDesc is MetadataType potentialType)) return ""; // 64-bit ISA variants are not included in the mapping dictionary, so we use the containing type instead if (potentialType.Name.SequenceEqual("X64"u8) || potentialType.Name.SequenceEqual("Arm64"u8)) { if (architecture is TargetArchitecture.X64 or TargetArchitecture.ARM64) potentialType = potentialType.ContainingType; else return ""; } // We assume that managed names in InstructionSetDesc.txt use an underscore separator for nested classes string suffix = ""; while (potentialType.ContainingType is MetadataType containingType) { suffix = $"_{potentialType.GetName()}{suffix}"; potentialType = containingType; } if (architecture is TargetArchitecture.X64 or TargetArchitecture.X86) { if (!potentialType.Namespace.SequenceEqual("System.Runtime.Intrinsics.X86"u8)) return ""; } else if (architecture is TargetArchitecture.ARM64 or TargetArchitecture.ARM) { if (!potentialType.Namespace.SequenceEqual("System.Runtime.Intrinsics.Arm"u8)) return ""; } else if (architecture is TargetArchitecture.LoongArch64) { return ""; } else if (architecture is TargetArchitecture.RiscV64) { return ""; } else { throw new InternalCompilerErrorException($"Unknown architecture '{architecture}'"); } return potentialType.GetName() + suffix; } public SimdVectorLength GetVectorTSimdVector() { if ((_targetArchitecture == TargetArchitecture.X64) || (_targetArchitecture == TargetArchitecture.X86)) { Debug.Assert(InstructionSet.X64_VectorT128 == InstructionSet.X86_VectorT128); Debug.Assert(InstructionSet.X64_VectorT256 == InstructionSet.X86_VectorT256); Debug.Assert(InstructionSet.X64_VectorT512 == InstructionSet.X86_VectorT512); if (IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT512)) { Debug.Assert(!IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT128)); Debug.Assert(!IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT256)); return SimdVectorLength.Vector512Bit; } else if (IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT256)) { Debug.Assert(!IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT128)); return SimdVectorLength.Vector256Bit; } else if (IsInstructionSetOptimisticallySupported(InstructionSet.X64_VectorT128)) { return SimdVectorLength.Vector128Bit; } else { return SimdVectorLength.None; } } else if (_targetArchitecture == TargetArchitecture.ARM64) { if (IsInstructionSetOptimisticallySupported(InstructionSet.ARM64_VectorT128)) { return SimdVectorLength.Vector128Bit; } else { return SimdVectorLength.None; } } else if (_targetArchitecture == TargetArchitecture.ARM) { return SimdVectorLength.None; } else if (_targetArchitecture == TargetArchitecture.LoongArch64) { return SimdVectorLength.None; } else if (_targetArchitecture == TargetArchitecture.RiscV64) { return SimdVectorLength.None; } else if (_targetArchitecture == TargetArchitecture.Wasm32) { return SimdVectorLength.Vector128Bit; } else { Debug.Assert(false); // Unknown architecture return SimdVectorLength.None; } } } public class InstructionSetSupportBuilder { private static Dictionary<TargetArchitecture, Dictionary<string, InstructionSet>> s_instructionSetSupport = ComputeInstructionSetSupport(); private static Dictionary<TargetArchitecture, InstructionSetFlags> s_nonSpecifiableInstructionSets = ComputeNonSpecifiableInstructionSetSupport(); private static Dictionary<TargetArchitecture, Dictionary<string, InstructionSet>> ComputeInstructionSetSupport() { var supportMatrix = new Dictionary<TargetArchitecture, Dictionary<string, InstructionSet>>(); foreach (TargetArchitecture arch in Enum.GetValues<TargetArchitecture>()) { supportMatrix[arch] = ComputeInstructSetSupportForArch(arch); } return supportMatrix; } private static Dictionary<TargetArchitecture, InstructionSetFlags> ComputeNonSpecifiableInstructionSetSupport() { var matrix = new Dictionary<TargetArchitecture, InstructionSetFlags>(); foreach (TargetArchitecture arch in Enum.GetValues<TargetArchitecture>()) { matrix[arch] = ComputeNonSpecifiableInstructionSetSupportForArch(arch); } return matrix; } private static Dictionary<string, InstructionSet> ComputeInstructSetSupportForArch(TargetArchitecture architecture) { var support = new Dictionary<string, InstructionSet>(); foreach (var instructionSet in InstructionSetFlags.ArchitectureToValidInstructionSets(architecture)) { // Only instruction sets with associated R2R enum values are specifiable if (instructionSet.Specifiable) { _ = support.TryAdd(instructionSet.Name, instructionSet.InstructionSet); Debug.Assert(support[instructionSet.Name] == instructionSet.InstructionSet); } } return support; } private static InstructionSetFlags ComputeNonSpecifiableInstructionSetSupportForArch(TargetArchitecture architecture) { var support = new InstructionSetFlags(); foreach (var instructionSet in InstructionSetFlags.ArchitectureToValidInstructionSets(architecture)) { // Only instruction sets with associated R2R enum values are specifiable if (!instructionSet.Specifiable) support.AddInstructionSet(instructionSet.InstructionSet); } return support; } public static InstructionSetFlags GetNonSpecifiableInstructionSetsForArch(TargetArchitecture architecture) { return s_nonSpecifiableInstructionSets[architecture]; } private readonly SortedSet<string> _supportedInstructionSets; private readonly SortedSet<string> _unsupportedInstructionSets; private readonly TargetArchitecture _architecture; public TargetArchitecture Architecture => _architecture; public InstructionSetSupportBuilder(TargetArchitecture architecture) { _supportedInstructionSets = new SortedSet<string>(); _unsupportedInstructionSets = new SortedSet<string>(); _architecture = architecture; } public InstructionSetSupportBuilder(InstructionSetSupportBuilder other) { _supportedInstructionSets = new SortedSet<string>(other._supportedInstructionSets); _unsupportedInstructionSets = new SortedSet<string>(other._unsupportedInstructionSets); _architecture = other._architecture; } public override string ToString() => (_supportedInstructionSets.Count > 0 ? "+" : "") + string.Join(",+", _supportedInstructionSets) + (_supportedInstructionSets.Count > 0 && _unsupportedInstructionSets.Count > 0 ? "," : "") + (_unsupportedInstructionSets.Count > 0 ? "-" : "") + string.Join(",-", _unsupportedInstructionSets); /// <summary> /// Add a supported instruction set to the specified list. /// </summary> /// <returns>returns "false" if instruction set isn't valid on this architecture</returns> public bool AddSupportedInstructionSet(string instructionSet) { // First, check if it's a "known cpu family" group of instruction sets e.g. "haswell" var sets = InstructionSetFlags.CpuNameToInstructionSets(instructionSet, _architecture); if (sets != null) { foreach (string set in sets) { if (!s_instructionSetSupport[_architecture].ContainsKey(set)) { // Groups can contain other groups if (AddSupportedInstructionSet(set)) { continue; } return false; } _supportedInstructionSets.Add(set); _unsupportedInstructionSets.Remove(set); } return true; } if (!s_instructionSetSupport[_architecture].ContainsKey(instructionSet)) return false; _supportedInstructionSets.Add(instructionSet); _unsupportedInstructionSets.Remove(instructionSet); return true; } /// <summary> /// Removes a supported instruction set to the specified list. /// </summary> /// <returns>returns "false" if instruction set isn't valid on this architecture</returns> public bool RemoveInstructionSetSupport(string instructionSet) { if (!s_instructionSetSupport[_architecture].ContainsKey(instructionSet)) return false; _supportedInstructionSets.Remove(instructionSet); _unsupportedInstructionSets.Add(instructionSet); return true; } /// <summary> /// Seal modifications to instruction set support /// </summary> /// <returns>returns "false" if instruction set isn't valid on this architecture</returns> public bool ComputeInstructionSetFlags(int maxVectorTBitWidth, bool skipAddingVectorT, out InstructionSetFlags supportedInstructionSets, out InstructionSetFlags unsupportedInstructionSets, Action<string, string> invalidInstructionSetImplication) { supportedInstructionSets = new InstructionSetFlags(); unsupportedInstructionSets = new InstructionSetFlags(); Dictionary<string, InstructionSet> instructionSetConversion = s_instructionSetSupport[_architecture]; foreach (string unsupported in _unsupportedInstructionSets) { unsupportedInstructionSets.AddInstructionSet(instructionSetConversion[unsupported]); } unsupportedInstructionSets.ExpandInstructionSetByReverseImplication(_architecture); unsupportedInstructionSets.Set64BitInstructionSetVariants(_architecture); if ((_architecture == TargetArchitecture.X86) || (_architecture == TargetArchitecture.ARM)) unsupportedInstructionSets.Set64BitInstructionSetVariantsUnconditionally(_architecture); if (_supportedInstructionSets.Any(iSet => iSet.Contains("avx512"))) { // These ISAs should automatically extend to 512-bit if // AVX-512 is enabled. if (_supportedInstructionSets.Contains("gfni")) _supportedInstructionSets.Add("gfni_v512"); if (_supportedInstructionSets.Contains("vpclmul")) _supportedInstructionSets.Add("vpclmul_v512"); } if (_supportedInstructionSets.Any(iSet => iSet.Contains("avx"))) { // These ISAs should automatically extend to 256-bit if // AVX is enabled. if (_supportedInstructionSets.Contains("gfni")) _supportedInstructionSets.Add("gfni_v256"); } foreach (string supported in _supportedInstructionSets) { supportedInstructionSets.AddInstructionSet(instructionSetConversion[supported]); supportedInstructionSets.ExpandInstructionSetByImplication(_architecture); foreach (string unsupported in _unsupportedInstructionSets) { InstructionSetFlags checkForExplicitUnsupport = new InstructionSetFlags(); checkForExplicitUnsupport.AddInstructionSet(instructionSetConversion[unsupported]); checkForExplicitUnsupport.ExpandInstructionSetByReverseImplication(_architecture); checkForExplicitUnsupport.Set64BitInstructionSetVariants(_architecture); InstructionSetFlags supportedTemp = supportedInstructionSets; supportedTemp.Remove(checkForExplicitUnsupport); // If removing the explicitly unsupported instruction sets, changes the set of // supported instruction sets, then the parameter is invalid if (!supportedTemp.Equals(supportedInstructionSets)) { invalidInstructionSetImplication(supported, unsupported); return false; } } } if (skipAddingVectorT) { // For partial AOT scenarios, we need to skip adding Vector<T> // in the supported set so it doesn't cause the entire image // to be thrown away due to the host machine supporting a larger // size. return true; } switch (_architecture) { case TargetArchitecture.X64: case TargetArchitecture.X86: { Debug.Assert(InstructionSet.X86_X86Base == InstructionSet.X64_X86Base); Debug.Assert(InstructionSet.X86_AVX2 == InstructionSet.X64_AVX2); Debug.Assert(InstructionSet.X86_AVX512 == InstructionSet.X64_AVX512); Debug.Assert(InstructionSet.X86_VectorT128 == InstructionSet.X64_VectorT128); Debug.Assert(InstructionSet.X86_VectorT256 == InstructionSet.X64_VectorT256); Debug.Assert(InstructionSet.X86_VectorT512 == InstructionSet.X64_VectorT512); // We only want one size supported for Vector<T> and we want the other sizes explicitly // unsupported to ensure we throw away the given methods if runtime picks a larger size Debug.Assert(supportedInstructionSets.HasInstructionSet(InstructionSet.X86_X86Base)); Debug.Assert((maxVectorTBitWidth == 0) || (maxVectorTBitWidth >= 128)); supportedInstructionSets.AddInstructionSet(InstructionSet.X86_VectorT128); if (supportedInstructionSets.HasInstructionSet(InstructionSet.X86_AVX512) && (maxVectorTBitWidth >= 512)) { supportedInstructionSets.RemoveInstructionSet(InstructionSet.X86_VectorT128); supportedInstructionSets.AddInstructionSet(InstructionSet.X86_VectorT512); } else if (supportedInstructionSets.HasInstructionSet(InstructionSet.X86_AVX2) && (maxVectorTBitWidth is 0 or >= 256)) { supportedInstructionSets.RemoveInstructionSet(InstructionSet.X86_VectorT128); supportedInstructionSets.AddInstructionSet(InstructionSet.X86_VectorT256); } break; } case TargetArchitecture.ARM64: { Debug.Assert(supportedInstructionSets.HasInstructionSet(InstructionSet.ARM64_AdvSimd)); Debug.Assert((maxVectorTBitWidth == 0) || (maxVectorTBitWidth >= 128)); supportedInstructionSets.AddInstructionSet(InstructionSet.ARM64_VectorT128); break; } } return true; } } } |