|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Linq; |
| 4 | +using Microsoft.Build.Framework; |
| 5 | +using Xamarin.Localization.MSBuild; |
| 6 | +using Xamarin.Utils; |
| 7 | + |
| 8 | +#nullable enable |
| 9 | + |
| 10 | +namespace Xamarin.MacDev.Tasks { |
| 11 | + /// <summary> |
| 12 | + /// Computes the minimum instruction set required for a given OS version and platform. |
| 13 | + /// This is used to configure the R2R/NAOT compiler (crossgen2) with the appropriate --instruction-set argument. |
| 14 | + /// </summary> |
| 15 | + public class ComputeInstructionSet : XamarinTask { |
| 16 | + #region Inputs |
| 17 | + |
| 18 | + [Required] |
| 19 | + public string SupportedOSPlatformVersion { get; set; } = ""; |
| 20 | + |
| 21 | + #endregion |
| 22 | + |
| 23 | + #region Outputs |
| 24 | + |
| 25 | + [Output] |
| 26 | + public string InstructionSet { get; set; } = ""; |
| 27 | + |
| 28 | + #endregion |
| 29 | + |
| 30 | + // Device-to-CPU mapping |
| 31 | + // This maps each device model to its CPU model |
| 32 | + static readonly Dictionary<string, string> DeviceToCpu = new Dictionary<string, string> { |
| 33 | + // iOS devices |
| 34 | + { "iPhone6s", "A9" }, |
| 35 | + { "iPhone6sPlus", "A9" }, |
| 36 | + { "iPhoneSE", "A9" }, |
| 37 | + { "iPhone7", "A10" }, |
| 38 | + { "iPhone7Plus", "A10" }, |
| 39 | + { "iPhone8", "A11" }, |
| 40 | + { "iPhone8Plus", "A11" }, |
| 41 | + { "iPhoneX", "A11" }, |
| 42 | + { "iPhoneXR", "A12" }, |
| 43 | + { "iPhoneXS", "A12" }, |
| 44 | + { "iPhoneXSMax", "A12" }, |
| 45 | + { "iPhone11", "A13" }, |
| 46 | + { "iPhone11Pro", "A13" }, |
| 47 | + { "iPhone11ProMax", "A13" }, |
| 48 | + { "iPhoneSE2", "A13" }, |
| 49 | + { "iPhone12mini", "A14" }, |
| 50 | + { "iPhone12", "A14" }, |
| 51 | + { "iPhone12Pro", "A14" }, |
| 52 | + { "iPhone12ProMax", "A14" }, |
| 53 | + { "iPhone13mini", "A15" }, |
| 54 | + { "iPhone13", "A15" }, |
| 55 | + { "iPhone13Pro", "A15" }, |
| 56 | + { "iPhone13ProMax", "A15" }, |
| 57 | + { "iPhoneSE3", "A15" }, |
| 58 | + { "iPhone14", "A15" }, |
| 59 | + { "iPhone14Plus", "A15" }, |
| 60 | + { "iPhone14Pro", "A16" }, |
| 61 | + { "iPhone14ProMax", "A16" }, |
| 62 | + { "iPhone15", "A16" }, |
| 63 | + { "iPhone15Plus", "A16" }, |
| 64 | + { "iPhone15Pro", "A17Pro" }, |
| 65 | + { "iPhone15ProMax", "A17Pro" }, |
| 66 | + { "iPhone16", "A18" }, |
| 67 | + { "iPhone16Plus", "A18" }, |
| 68 | + { "iPhone16Pro", "A18Pro" }, |
| 69 | + { "iPhone16ProMax", "A18Pro" }, |
| 70 | + |
| 71 | + // iPad models |
| 72 | + { "iPadAir2", "A8X" }, |
| 73 | + { "iPadMini4", "A8" }, |
| 74 | + { "iPadPro9_7", "A9X" }, |
| 75 | + { "iPadPro12_9", "A9X" }, |
| 76 | + { "iPad5", "A9" }, |
| 77 | + { "iPadPro10_5", "A10X" }, |
| 78 | + { "iPadPro12_9_2", "A10X" }, |
| 79 | + { "iPad6", "A10" }, |
| 80 | + { "iPadAir3", "A12" }, |
| 81 | + { "iPadMini5", "A12" }, |
| 82 | + { "iPad7", "A10" }, |
| 83 | + { "iPadPro11", "A12X" }, |
| 84 | + { "iPadPro12_9_3", "A12X" }, |
| 85 | + { "iPad8", "A12" }, |
| 86 | + { "iPadAir4", "A14" }, |
| 87 | + { "iPad9", "A13" }, |
| 88 | + { "iPadMini6", "A15" }, |
| 89 | + { "iPadAir5", "M1" }, |
| 90 | + { "iPadPro11_3", "M1" }, |
| 91 | + { "iPadPro12_9_5", "M1" }, |
| 92 | + { "iPad10", "A14" }, |
| 93 | + { "iPadPro11_4", "M2" }, |
| 94 | + { "iPadPro12_9_6", "M2" }, |
| 95 | + { "iPadAir6", "M2" }, |
| 96 | + { "iPadPro11_M4", "M4" }, |
| 97 | + { "iPadPro13_M4", "M4" }, |
| 98 | + |
| 99 | + // Apple TV models |
| 100 | + { "AppleTV4", "A8" }, |
| 101 | + { "AppleTV4K", "A10X" }, |
| 102 | + { "AppleTV4K2", "A12" }, |
| 103 | + { "AppleTV4K3", "A15" }, |
| 104 | + }; |
| 105 | + |
| 106 | + // Device-to-max-OS mapping |
| 107 | + // This maps each device to its maximum supported OS version |
| 108 | + static readonly Dictionary<string, string> DeviceToMaxOS = new Dictionary<string, string> { |
| 109 | + // iOS devices |
| 110 | + { "iPhone6s", "15.8" }, |
| 111 | + { "iPhone6sPlus", "15.8" }, |
| 112 | + { "iPhoneSE", "15.8" }, |
| 113 | + { "iPhone7", "15.8" }, |
| 114 | + { "iPhone7Plus", "15.8" }, |
| 115 | + { "iPhone8", "16.7" }, |
| 116 | + { "iPhone8Plus", "16.7" }, |
| 117 | + { "iPhoneX", "16.7" }, |
| 118 | + { "iPhoneXR", "18.2" }, |
| 119 | + { "iPhoneXS", "18.2" }, |
| 120 | + { "iPhoneXSMax", "18.2" }, |
| 121 | + { "iPhone11", "18.2" }, |
| 122 | + { "iPhone11Pro", "18.2" }, |
| 123 | + { "iPhone11ProMax", "18.2" }, |
| 124 | + { "iPhoneSE2", "18.2" }, |
| 125 | + { "iPhone12mini", "18.2" }, |
| 126 | + { "iPhone12", "18.2" }, |
| 127 | + { "iPhone12Pro", "18.2" }, |
| 128 | + { "iPhone12ProMax", "18.2" }, |
| 129 | + { "iPhone13mini", "18.2" }, |
| 130 | + { "iPhone13", "18.2" }, |
| 131 | + { "iPhone13Pro", "18.2" }, |
| 132 | + { "iPhone13ProMax", "18.2" }, |
| 133 | + { "iPhoneSE3", "18.2" }, |
| 134 | + { "iPhone14", "18.2" }, |
| 135 | + { "iPhone14Plus", "18.2" }, |
| 136 | + { "iPhone14Pro", "18.2" }, |
| 137 | + { "iPhone14ProMax", "18.2" }, |
| 138 | + { "iPhone15", "18.2" }, |
| 139 | + { "iPhone15Plus", "18.2" }, |
| 140 | + { "iPhone15Pro", "18.2" }, |
| 141 | + { "iPhone15ProMax", "18.2" }, |
| 142 | + { "iPhone16", "18.2" }, |
| 143 | + { "iPhone16Plus", "18.2" }, |
| 144 | + { "iPhone16Pro", "18.2" }, |
| 145 | + { "iPhone16ProMax", "18.2" }, |
| 146 | + |
| 147 | + // iPad models |
| 148 | + { "iPadAir2", "15.8" }, |
| 149 | + { "iPadMini4", "15.8" }, |
| 150 | + { "iPadPro9_7", "16.7" }, |
| 151 | + { "iPadPro12_9", "16.7" }, |
| 152 | + { "iPad5", "16.7" }, |
| 153 | + { "iPadPro10_5", "16.7" }, |
| 154 | + { "iPadPro12_9_2", "16.7" }, |
| 155 | + { "iPad6", "16.7" }, |
| 156 | + { "iPadAir3", "17.7" }, |
| 157 | + { "iPadMini5", "17.7" }, |
| 158 | + { "iPad7", "17.7" }, |
| 159 | + { "iPadPro11", "18.2" }, |
| 160 | + { "iPadPro12_9_3", "18.2" }, |
| 161 | + { "iPad8", "18.2" }, |
| 162 | + { "iPadAir4", "18.2" }, |
| 163 | + { "iPad9", "18.2" }, |
| 164 | + { "iPadMini6", "18.2" }, |
| 165 | + { "iPadAir5", "18.2" }, |
| 166 | + { "iPadPro11_3", "18.2" }, |
| 167 | + { "iPadPro12_9_5", "18.2" }, |
| 168 | + { "iPad10", "18.2" }, |
| 169 | + { "iPadPro11_4", "18.2" }, |
| 170 | + { "iPadPro12_9_6", "18.2" }, |
| 171 | + { "iPadAir6", "18.2" }, |
| 172 | + { "iPadPro11_M4", "18.2" }, |
| 173 | + { "iPadPro13_M4", "18.2" }, |
| 174 | + |
| 175 | + // Apple TV models - tvOS versions |
| 176 | + { "AppleTV4", "15.6" }, |
| 177 | + { "AppleTV4K", "18.2" }, |
| 178 | + { "AppleTV4K2", "18.2" }, |
| 179 | + { "AppleTV4K3", "18.2" }, |
| 180 | + }; |
| 181 | + |
| 182 | + // CPU-to-instruction-set mapping |
| 183 | + // This maps each CPU model to the instruction set it supports |
| 184 | + static readonly Dictionary<string, string> CpuToInstructionSet = new Dictionary<string, string> { |
| 185 | + // ARM chips |
| 186 | + { "A8", "armv8-a" }, // ARMv8.0-A (iPhone 6, iPad Air 2, Apple TV 4) |
| 187 | + { "A8X", "armv8-a" }, // ARMv8.0-A (iPad Air 2) |
| 188 | + { "A9", "armv8-a" }, // ARMv8.0-A (iPhone 6s, iPad 5, iPad Pro 9.7) |
| 189 | + { "A9X", "armv8-a" }, // ARMv8.0-A (iPad Pro 12.9 1st gen, iPad Pro 9.7) |
| 190 | + { "A10", "armv8-a" }, // ARMv8.0-A (iPhone 7) |
| 191 | + { "A10X", "armv8.1-a" }, // ARMv8.1-A (iPad Pro 10.5, iPad Pro 12.9 2nd gen, Apple TV 4K) |
| 192 | + { "A11", "armv8.2-a" }, // ARMv8.2-A (iPhone 8, iPhone X) |
| 193 | + { "A12", "armv8.3-a" }, // ARMv8.3-A (iPhone XS, iPad Air 3, Apple TV 4K 2nd gen) |
| 194 | + { "A12X", "armv8.3-a" }, // ARMv8.3-A (iPad Pro 11, iPad Pro 12.9 3rd gen) |
| 195 | + { "A13", "armv8.4-a" }, // ARMv8.4-A (iPhone 11, iPhone SE 2nd gen) |
| 196 | + { "A14", "armv8.4-a" }, // ARMv8.4-A with additional features (iPhone 12, iPad Air 4) |
| 197 | + { "A15", "armv8.5-a" }, // ARMv8.5-A (iPhone 13, Apple TV 4K 3rd gen) |
| 198 | + { "A16", "armv8.6-a" }, // ARMv8.6-A (iPhone 14 Pro) |
| 199 | + { "A17Pro", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 15 Pro) |
| 200 | + { "A18", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 16) |
| 201 | + { "A18Pro", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 16 Pro) |
| 202 | + |
| 203 | + // Apple Silicon (M-series) for macOS/Mac Catalyst |
| 204 | + { "M1", "apple-m1" }, // Apple M1 (Mac, iPad Air 5, iPad Pro) |
| 205 | + { "M2", "apple-m1" }, // Apple M2 (similar to M1 in instruction support for crossgen2) |
| 206 | + { "M3", "apple-m1" }, // Apple M3 (similar to M1 in instruction support for crossgen2) |
| 207 | + { "M4", "apple-m1" }, // Apple M4 (similar to M1 in instruction support for crossgen2) |
| 208 | + |
| 209 | + // Intel chips for macOS |
| 210 | + { "Intel", "x86-64-v2" }, // Default Intel instruction set |
| 211 | + }; |
| 212 | + |
| 213 | + // Crossgen2 supported instruction sets (from crossgen2 --help) |
| 214 | + static readonly HashSet<string> SupportedInstructionSets = new HashSet<string> { |
| 215 | + "x86-64-v2", |
| 216 | + "x86-64-v3", |
| 217 | + "x86-64-v4", |
| 218 | + "armv8-a", |
| 219 | + "armv8.1-a", |
| 220 | + "armv8.2-a", |
| 221 | + "armv8.3-a", |
| 222 | + "armv8.4-a", |
| 223 | + "armv8.5-a", |
| 224 | + "armv8.6-a", |
| 225 | + "apple-m1" |
| 226 | + }; |
| 227 | + |
| 228 | + public override bool Execute () |
| 229 | + { |
| 230 | + try { |
| 231 | + if (string.IsNullOrEmpty (SupportedOSPlatformVersion)) { |
| 232 | + Log.LogMessage (MessageImportance.Low, "SupportedOSPlatformVersion is not set, skipping instruction set computation"); |
| 233 | + return true; |
| 234 | + } |
| 235 | + |
| 236 | + var instructionSet = ComputeMinimumInstructionSet (Platform, SupportedOSPlatformVersion); |
| 237 | + if (!string.IsNullOrEmpty (instructionSet)) { |
| 238 | + InstructionSet = instructionSet; |
| 239 | + Log.LogMessage (MessageImportance.Low, $"Computed instruction set '{InstructionSet}' for {PlatformName} {SupportedOSPlatformVersion}"); |
| 240 | + } else { |
| 241 | + Log.LogMessage (MessageImportance.Low, $"No instruction set computed for {PlatformName} {SupportedOSPlatformVersion}"); |
| 242 | + } |
| 243 | + |
| 244 | + return !Log.HasLoggedErrors; |
| 245 | + } catch (Exception ex) { |
| 246 | + Log.LogError ($"Error computing instruction set: {ex.Message}"); |
| 247 | + return false; |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + string? ComputeMinimumInstructionSet (ApplePlatform platform, string osVersion) |
| 252 | + { |
| 253 | + // Parse the OS version |
| 254 | + if (!Version.TryParse (osVersion, out var targetVersion)) { |
| 255 | + Log.LogMessage (MessageImportance.Low, $"Could not parse OS version: {osVersion}"); |
| 256 | + return null; |
| 257 | + } |
| 258 | + |
| 259 | + // For macOS and Mac Catalyst, we need different logic |
| 260 | + if (platform == ApplePlatform.MacOSX || platform == ApplePlatform.MacCatalyst) { |
| 261 | + return ComputeMacInstructionSet (platform, targetVersion); |
| 262 | + } |
| 263 | + |
| 264 | + // For iOS and tvOS, find the oldest device that can run this OS version |
| 265 | + var devicesToCheck = platform == ApplePlatform.TVOS ? |
| 266 | + DeviceToMaxOS.Where (kv => kv.Key.StartsWith ("AppleTV")) : |
| 267 | + DeviceToMaxOS.Where (kv => !kv.Key.StartsWith ("AppleTV")); |
| 268 | + |
| 269 | + string? oldestCpu = null; |
| 270 | + string? oldestInstructionSet = null; |
| 271 | + |
| 272 | + foreach (var device in devicesToCheck) { |
| 273 | + var deviceName = device.Key; |
| 274 | + var maxOSString = device.Value; |
| 275 | + |
| 276 | + if (!Version.TryParse (maxOSString, out var maxOS)) |
| 277 | + continue; |
| 278 | + |
| 279 | + // Check if this device can run the target OS version |
| 280 | + // A device can run the target OS if its max OS >= target OS |
| 281 | + if (maxOS >= targetVersion) { |
| 282 | + // This device can run the target OS |
| 283 | + if (DeviceToCpu.TryGetValue (deviceName, out var cpu)) { |
| 284 | + if (CpuToInstructionSet.TryGetValue (cpu, out var instructionSet)) { |
| 285 | + // Keep track of the oldest instruction set we've seen |
| 286 | + // We want the minimum instruction set that all compatible devices support |
| 287 | + if (oldestCpu == null || IsOlderInstructionSet (instructionSet, oldestInstructionSet)) { |
| 288 | + oldestCpu = cpu; |
| 289 | + oldestInstructionSet = instructionSet; |
| 290 | + } |
| 291 | + } |
| 292 | + } |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + // Validate that the instruction set is supported by crossgen2 |
| 297 | + if (oldestInstructionSet != null && !SupportedInstructionSets.Contains (oldestInstructionSet)) { |
| 298 | + Log.LogMessage (MessageImportance.Low, $"Instruction set '{oldestInstructionSet}' is not supported by crossgen2, skipping"); |
| 299 | + return null; |
| 300 | + } |
| 301 | + |
| 302 | + return oldestInstructionSet; |
| 303 | + } |
| 304 | + |
| 305 | + string? ComputeMacInstructionSet (ApplePlatform platform, Version targetVersion) |
| 306 | + { |
| 307 | + // For macOS, we need to consider both Intel and Apple Silicon |
| 308 | + // For now, we'll use a conservative approach: |
| 309 | + // - macOS 11.0+ (Big Sur): First version to support Apple Silicon |
| 310 | + // - macOS < 11.0: Intel only |
| 311 | + |
| 312 | + if (targetVersion.Major >= 11) { |
| 313 | + // Supports Apple Silicon, but we need to support Intel too |
| 314 | + // For cross-platform support, we'll return the Intel instruction set |
| 315 | + // The actual instruction set will depend on the RuntimeIdentifier |
| 316 | + return "x86-64-v2"; // Conservative Intel support |
| 317 | + } else { |
| 318 | + // Intel only |
| 319 | + return "x86-64-v2"; |
| 320 | + } |
| 321 | + } |
| 322 | + |
| 323 | + bool IsOlderInstructionSet (string? instructionSet1, string? instructionSet2) |
| 324 | + { |
| 325 | + if (instructionSet2 == null) |
| 326 | + return true; |
| 327 | + if (instructionSet1 == null) |
| 328 | + return false; |
| 329 | + |
| 330 | + // Order instruction sets from oldest to newest |
| 331 | + var order = new[] { |
| 332 | + "armv8-a", |
| 333 | + "armv8.1-a", |
| 334 | + "armv8.2-a", |
| 335 | + "armv8.3-a", |
| 336 | + "armv8.4-a", |
| 337 | + "armv8.5-a", |
| 338 | + "armv8.6-a", |
| 339 | + "apple-m1", |
| 340 | + "x86-64-v2", |
| 341 | + "x86-64-v3", |
| 342 | + "x86-64-v4" |
| 343 | + }; |
| 344 | + |
| 345 | + var index1 = Array.IndexOf (order, instructionSet1); |
| 346 | + var index2 = Array.IndexOf (order, instructionSet2); |
| 347 | + |
| 348 | + if (index1 == -1 || index2 == -1) |
| 349 | + return false; |
| 350 | + |
| 351 | + return index1 < index2; |
| 352 | + } |
| 353 | + } |
| 354 | +} |
0 commit comments