Skip to content

Commit 3ab1b45

Browse files
Copilotrolfbjarne
andcommitted
Add ComputeInstructionSet MSBuild task and integration
Co-authored-by: rolfbjarne <249268+rolfbjarne@users.noreply.github.com>
1 parent 420af79 commit 3ab1b45

File tree

2 files changed

+378
-0
lines changed

2 files changed

+378
-0
lines changed

dotnet/targets/Xamarin.Shared.Sdk.targets

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99

1010
<UsingTask TaskName="Xamarin.MacDev.Tasks.CompileNativeCode" AssemblyFile="$(_XamarinTaskAssembly)" />
11+
<UsingTask TaskName="Xamarin.MacDev.Tasks.ComputeInstructionSet" AssemblyFile="$(_XamarinTaskAssembly)" />
1112
<UsingTask TaskName="Xamarin.MacDev.Tasks.FindAotCompiler" AssemblyFile="$(_XamarinTaskAssembly)" />
1213
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetFullPaths" AssemblyFile="$(_XamarinTaskAssembly)" />
1314
<UsingTask TaskName="Xamarin.MacDev.Tasks.InstallNameTool" AssemblyFile="$(_XamarinTaskAssembly)" />
@@ -517,6 +518,29 @@
517518
</PropertyGroup>
518519
</Target>
519520

521+
<!--
522+
Compute the instruction set for R2R/NAOT compilation based on the SupportedOSPlatformVersion.
523+
This target runs early in the build to set PublishReadyToRunCrossgen2ExtraArgs if needed.
524+
-->
525+
<Target Name="_ComputeInstructionSetForCrossgen2"
526+
Condition="'$(PublishReadyToRun)' == 'true'"
527+
BeforeTargets="PrepareForILLink">
528+
<ComputeInstructionSet
529+
SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)"
530+
SessionId="$(SessionId)"
531+
TargetFrameworkMoniker="$(TargetFramework)"
532+
SdkDevPath="$(_SdkDevPath)"
533+
>
534+
<Output TaskParameter="InstructionSet" PropertyName="_ComputedInstructionSet" />
535+
</ComputeInstructionSet>
536+
537+
<PropertyGroup Condition="'$(_ComputedInstructionSet)' != ''">
538+
<!-- Append the instruction set to the existing PublishReadyToRunCrossgen2ExtraArgs -->
539+
<PublishReadyToRunCrossgen2ExtraArgs Condition="'$(PublishReadyToRunCrossgen2ExtraArgs)' == ''">--instruction-set:$(_ComputedInstructionSet)</PublishReadyToRunCrossgen2ExtraArgs>
540+
<PublishReadyToRunCrossgen2ExtraArgs Condition="'$(PublishReadyToRunCrossgen2ExtraArgs)' != ''">$(PublishReadyToRunCrossgen2ExtraArgs) --instruction-set:$(_ComputedInstructionSet)</PublishReadyToRunCrossgen2ExtraArgs>
541+
</PropertyGroup>
542+
</Target>
543+
520544
<Target Name="_ComputeLinkerArguments" DependsOnTargets="$(_ComputeLinkerArgumentsDependsOn)" />
521545

522546
<Target Name="_ComputeLinkerInputs">
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
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

Comments
 (0)