2 using System.Collections.Generic;
5 using System.Text.RegularExpressions;
7 using CppSharp.AST.Extensions;
13 * This tool dumps the offsets of structures used in the Mono VM needed
14 * by the AOT compiler for cross-compiling code to target platforms
15 * different than the host the compiler is being invoked on.
17 * It takes two arguments: the path to your clone of the Mono repo and
18 * the path to the root of Android NDK.
20 static class MonoAotOffsetsDumper
22 static string MonoDir = @"";
24 static List<string> Abis = new List<string> ();
25 static string OutputDir;
27 static string MonodroidDir = @"";
28 static string AndroidNdkPath = @"";
29 static string MaccoreDir = @"";
30 static string TargetDir = @"";
32 public enum TargetPlatform
43 Defines = new List<string>();
44 Arguments = new List<string>();
47 public Target(Target target)
49 Platform = target.Platform;
50 Triple = target.Triple;
52 Defines = target.Defines;
53 Arguments = target.Arguments;
56 public TargetPlatform Platform;
59 public List<string> Defines;
60 public List<string> Arguments;
63 public static List<Target> Targets = new List<Target>();
65 public static IEnumerable<Target> AndroidTargets
67 get { return Targets.Where ((t) => t.Platform == TargetPlatform.Android); }
70 public static IEnumerable<Target> DarwinTargets
74 return Targets.Where ((t) => t.Platform == TargetPlatform.iOS ||
75 t.Platform == TargetPlatform.WatchOS);
79 public static IEnumerable<Target> iOSTargets
83 return Targets.Where ((t) => t.Platform == TargetPlatform.iOS);
87 public static void SetupAndroidTargets()
89 Targets.Add (new Target {
90 Platform = TargetPlatform.Android,
91 Triple = "i686-none-linux-android",
92 Defines = { "TARGET_X86" }
95 Targets.Add (new Target {
96 Platform = TargetPlatform.Android,
97 Triple = "x86_64-none-linux-android",
98 Defines = { "TARGET_AMD64" }
101 Targets.Add (new Target {
102 Platform = TargetPlatform.Android,
103 Triple = "armv5-none-linux-androideabi",
104 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
107 Targets.Add (new Target {
108 Platform = TargetPlatform.Android,
109 Triple = "armv7-none-linux-androideabi",
110 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5", "HAVE_ARMV6",
115 Targets.Add (new Target {
116 Platform = TargetPlatform.Android,
117 Triple = "aarch64-v8a-linux-android",
118 Defines = { "TARGET_ARM64" }
121 /*Targets.Add(new Target {
122 Platform = TargetPlatform.Android,
123 Triple = "mipsel-none-linux-android",
124 Defines = { "TARGET_MIPS", "__mips__" }
127 foreach (var target in AndroidTargets)
128 target.Defines.AddRange (new string[] { "PLATFORM_ANDROID",
129 "TARGET_ANDROID", "MONO_CROSS_COMPILE", "USE_MONO_CTX"
133 public static void SetupiOSTargets()
135 Targets.Add(new Target {
136 Platform = TargetPlatform.iOS,
137 Triple = "arm-apple-darwin10",
139 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
142 Targets.Add(new Target {
143 Platform = TargetPlatform.iOS,
144 Triple = "aarch64-apple-darwin10",
146 Defines = { "TARGET_ARM64" }
149 foreach (var target in iOSTargets) {
150 target.Defines.AddRange (new string[] { "PLATFORM_DARWIN",
151 "TARGET_IOS", "TARGET_MACH", "MONO_CROSS_COMPILE", "USE_MONO_CTX",
156 Targets.Add(new Target {
157 Platform = TargetPlatform.WatchOS,
158 Triple = "armv7k-apple-darwin",
159 Build = "targetwatch",
160 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
163 foreach (var target in DarwinTargets) {
164 target.Defines.AddRange (new string[] { "PLATFORM_DARWIN",
165 "TARGET_IOS", "TARGET_MACH", "MONO_CROSS_COMPILE", "USE_MONO_CTX",
171 static bool GetParentSubDirectoryPath(string parent, out string subdir)
173 var directory = Directory.GetParent(Directory.GetCurrentDirectory());
175 while (directory != null) {
176 var path = Path.Combine(directory.FullName, parent);
178 if (Directory.Exists (path)) {
183 directory = directory.Parent;
190 public static void Main(string[] args)
192 ParseCommandLineArgs(args);
195 if (!Directory.Exists (MonodroidDir) &&
196 GetParentSubDirectoryPath ("monodroid", out monodroidDir)) {
197 MonodroidDir = Path.Combine (monodroidDir);
200 if (Directory.Exists (MonodroidDir))
201 SetupAndroidTargets();
204 if (!Directory.Exists (MaccoreDir) &&
205 GetParentSubDirectoryPath ("maccore", out maccoreDir)) {
206 MaccoreDir = Path.Combine (maccoreDir);
209 if (Directory.Exists(MaccoreDir))
212 foreach (var target in Targets)
214 if (Abis.Any() && !Abis.Any (target.Triple.Contains))
218 Console.WriteLine("Processing triple: {0}", target.Triple);
220 var options = new DriverOptions();
222 var log = new TextDiagnosticPrinter();
223 var driver = new Driver(options, log);
225 Setup(driver, target);
228 BuildParseOptions(driver, target);
229 if (!driver.ParseCode())
232 Dump(driver.Context.ASTContext, driver.Context.TargetInfo, target);
236 static void BuildParseOptions(Driver driver, Target target)
238 foreach (var header in driver.Options.Headers)
240 var source = driver.Project.AddFile(header);
241 source.Options = driver.BuildParserOptions(source);
243 if (header.Contains ("mini"))
246 source.Options.AddDefines ("HAVE_SGEN_GC");
247 source.Options.AddDefines ("HAVE_MOVING_COLLECTOR");
251 static string GetAndroidNdkPath()
253 if (!String.IsNullOrEmpty (AndroidNdkPath))
254 return AndroidNdkPath;
256 // Find the Android NDK's path from Monodroid's config.
257 var configFile = Path.Combine(MonodroidDir, "env.config");
258 if (!File.Exists(configFile))
259 throw new Exception("Expected a valid Monodroid environment config file at " + configFile);
261 var config = File.ReadAllText(configFile);
262 var match = Regex.Match(config, @"ANDROID_NDK_PATH\s*:=\s(.*)");
263 return match.Groups[1].Value.Trim();
266 static void ParseCommandLineArgs(string[] args)
268 var showHelp = false;
270 var options = new Mono.Options.OptionSet () {
271 { "abi=", "ABI triple to generate", v => Abis.Add(v) },
272 { "o|out=", "output directory", v => OutputDir = v },
273 { "maccore=", "include directory", v => MaccoreDir = v },
274 { "monodroid=", "top monodroid directory", v => MonodroidDir = v },
275 { "android-ndk=", "Path to Android NDK", v => AndroidNdkPath = v },
276 { "targetdir=", "Path to the directory containing the mono build", v =>TargetDir = v },
277 { "mono=", "include directory", v => MonoDir = v },
278 { "h|help", "show this message and exit", v => showHelp = v != null },
282 options.Parse (args);
284 catch (Mono.Options.OptionException e) {
285 Console.WriteLine (e.Message);
291 // Print usage and exit.
292 Console.WriteLine("{0} <options>",
293 AppDomain.CurrentDomain.FriendlyName);
294 options.WriteOptionDescriptions (Console.Out);
299 static void Setup(Driver driver, Target target)
301 var options = driver.Options;
302 options.DryRun = true;
303 options.LibraryName = "Mono";
305 var parserOptions = driver.ParserOptions;
306 parserOptions.Verbose = false;
307 parserOptions.MicrosoftMode = false;
308 parserOptions.AddArguments("-xc");
309 parserOptions.AddArguments("-std=gnu99");
310 parserOptions.AddDefines("CPPSHARP");
312 foreach (var define in target.Defines)
313 parserOptions.AddDefines(define);
315 SetupToolchainPaths(driver, target);
317 SetupMono(driver, target);
320 static void SetupMono(Driver driver, Target target)
323 switch (target.Platform) {
324 case TargetPlatform.Android:
325 if (TargetDir == "") {
326 Console.Error.WriteLine ("The --targetdir= option is required when targeting android.");
327 Environment.Exit (1);
330 Console.Error.WriteLine ("The --mono= option is required when targeting android.");
331 Environment.Exit (1);
333 if (Abis.Count != 1) {
334 Console.Error.WriteLine ("Exactly one --abi= argument is required when targeting android.");
335 Environment.Exit (1);
337 targetBuild = TargetDir;
339 case TargetPlatform.WatchOS:
340 case TargetPlatform.iOS: {
341 string targetPath = Path.Combine (MaccoreDir, "builds");
342 if (!Directory.Exists (MonoDir))
343 MonoDir = Path.GetFullPath (Path.Combine (targetPath, "../../mono"));
344 targetBuild = Path.Combine(targetPath, target.Build);
348 throw new ArgumentOutOfRangeException ();
351 if (!Directory.Exists(targetBuild))
352 throw new Exception(string.Format("Could not find the target build directory: {0}", targetBuild));
354 var includeDirs = new[]
357 Path.Combine(targetBuild, "eglib", "src"),
359 Path.Combine(MonoDir, "mono"),
360 Path.Combine(MonoDir, "mono", "mini"),
361 Path.Combine(MonoDir, "eglib", "src")
364 foreach (var inc in includeDirs)
365 driver.ParserOptions.AddIncludeDirs(inc);
367 var filesToParse = new[]
369 Path.Combine(MonoDir, "mono", "metadata", "metadata-cross-helpers.c"),
370 Path.Combine(MonoDir, "mono", "mini", "mini-cross-helpers.c"),
373 foreach (var file in filesToParse)
374 driver.Options.Headers.Add(file);
377 static void SetupMSVC(Driver driver, string triple)
379 var parserOptions = driver.ParserOptions;
381 parserOptions.Abi = Parser.AST.CppAbi.Microsoft;
382 parserOptions.MicrosoftMode = true;
384 var systemIncludeDirs = new[]
386 @"C:\Program Files (x86)\Windows Kits\8.1\Include\um",
387 @"C:\Program Files (x86)\Windows Kits\8.1\Include\shared"
390 foreach (var inc in systemIncludeDirs)
391 parserOptions.AddSystemIncludeDirs(inc);
393 parserOptions.AddDefines("HOST_WIN32");
396 static void SetupToolchainPaths(Driver driver, Target target)
398 switch (target.Platform) {
399 case TargetPlatform.Android:
400 SetupAndroidNDK(driver, target);
402 case TargetPlatform.iOS:
403 case TargetPlatform.WatchOS:
404 SetupXcode(driver, target);
407 throw new ArgumentOutOfRangeException ();
411 static string GetArchFromTriple(string triple)
413 if (triple.Contains("mips"))
416 if (triple.Contains("arm64") || triple.Contains("aarch64"))
419 if (triple.Contains("arm"))
422 if (triple.Contains("i686"))
425 if (triple.Contains("x86_64"))
428 throw new Exception("Unknown architecture from triple: " + triple);
431 static string GetXcodeToolchainPath()
433 var toolchains = Directory.EnumerateDirectories("/Applications", "Xcode*")
437 var toolchainPath = toolchains.LastOrDefault();
438 if (toolchainPath == null)
439 throw new Exception("Could not find a valid Xcode SDK");
441 return toolchainPath;
444 static string GetXcodeBuiltinIncludesFolder()
446 var toolchainPath = GetXcodeToolchainPath();
448 var toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
449 "Contents/Developer/Toolchains")).ToList();
452 toolchainPath = toolchains.LastOrDefault();
453 if (toolchainPath == null)
454 throw new Exception("Could not find a valid Xcode toolchain");
456 var includePaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
457 "usr/lib/clang")).ToList();
458 var includePath = includePaths.LastOrDefault();
460 if (includePath == null)
461 throw new Exception("Could not find a valid Clang include folder");
463 return Path.Combine(includePath, "include");
466 static string GetXcodeiOSIncludesFolder()
468 var toolchainPath = GetXcodeToolchainPath();
470 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
471 "Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs")).ToList();
472 var sdkPath = sdkPaths.LastOrDefault();
475 throw new Exception("Could not find a valid iPhone SDK");
477 return Path.Combine(sdkPath, "usr/include");
480 static string GetXcodeWatchOSIncludesFolder()
482 var toolchainPath = GetXcodeToolchainPath();
484 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
485 "Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs")).ToList();
486 var sdkPath = sdkPaths.LastOrDefault();
489 throw new Exception("Could not find a valid WatchOS SDK");
491 return Path.Combine(sdkPath, "usr/include");
494 static void SetupXcode(Driver driver, Target target)
496 var parserOptions = driver.ParserOptions;
498 var builtinsPath = GetXcodeBuiltinIncludesFolder();
501 switch (target.Platform) {
502 case TargetPlatform.iOS:
503 includePath = GetXcodeiOSIncludesFolder();
505 case TargetPlatform.WatchOS:
506 includePath = GetXcodeWatchOSIncludesFolder();
509 throw new ArgumentOutOfRangeException ();
512 parserOptions.AddSystemIncludeDirs(builtinsPath);
513 parserOptions.AddSystemIncludeDirs(includePath);
515 parserOptions.NoBuiltinIncludes = true;
516 parserOptions.NoStandardIncludes = true;
517 parserOptions.TargetTriple = target.Triple;
520 static string GetAndroidHostToolchainPath()
522 var androidNdkPath = GetAndroidNdkPath ();
523 var toolchains = Directory.EnumerateDirectories(
524 Path.Combine(androidNdkPath, "toolchains"), "llvm*").ToList();
527 var toolchainPath = toolchains.LastOrDefault();
528 if (toolchainPath == null)
529 throw new Exception("Could not find a valid NDK host toolchain");
531 toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
532 "prebuilt")).ToList();
535 toolchainPath = toolchains.LastOrDefault();
536 if (toolchainPath == null)
537 throw new Exception("Could not find a valid NDK host toolchain");
539 return toolchainPath;
542 static string GetAndroidBuiltinIncludesFolder()
544 var toolchainPath = GetAndroidHostToolchainPath();
546 string clangToolchainPath = Path.Combine(toolchainPath, "lib64", "clang");
547 if (!Directory.Exists (clangToolchainPath))
548 clangToolchainPath = Path.Combine(toolchainPath, "lib", "clang");
550 string includePath = null;
551 if (Directory.Exists (clangToolchainPath)) {
552 var includePaths = Directory.EnumerateDirectories(clangToolchainPath).ToList();
553 includePath = includePaths.LastOrDefault();
555 if (includePath == null)
556 throw new Exception("Could not find a valid Clang include folder");
558 return Path.Combine(includePath, "include");
561 static void SetupAndroidNDK(Driver driver, Target target)
563 var options = driver.Options;
564 var parserOptions = driver.ParserOptions;
566 var builtinsPath = GetAndroidBuiltinIncludesFolder();
567 parserOptions.AddSystemIncludeDirs(builtinsPath);
569 var androidNdkRoot = GetAndroidNdkPath ();
570 const int androidNdkApiLevel = 21;
572 var toolchainPath = Path.Combine(androidNdkRoot, "platforms",
573 "android-" + androidNdkApiLevel, "arch-" + GetArchFromTriple(target.Triple),
575 parserOptions.AddSystemIncludeDirs(toolchainPath);
577 parserOptions.NoBuiltinIncludes = true;
578 parserOptions.NoStandardIncludes = true;
579 parserOptions.TargetTriple = target.Triple;
582 static uint GetTypeAlign(ParserTargetInfo target, ParserIntType type)
586 case ParserIntType.SignedChar:
587 case ParserIntType.UnsignedChar:
588 return target.CharAlign;
589 case ParserIntType.SignedShort:
590 case ParserIntType.UnsignedShort:
591 return target.ShortAlign;
592 case ParserIntType.SignedInt:
593 case ParserIntType.UnsignedInt:
594 return target.IntAlign;
595 case ParserIntType.SignedLong:
596 case ParserIntType.UnsignedLong:
597 return target.LongAlign;
598 case ParserIntType.SignedLongLong:
599 case ParserIntType.UnsignedLongLong:
600 return target.LongLongAlign;
602 throw new Exception("Type has no alignment");
606 static uint GetTypeSize(ParserTargetInfo target, ParserIntType type)
610 case ParserIntType.SignedChar:
611 case ParserIntType.UnsignedChar:
612 return target.CharWidth;
613 case ParserIntType.SignedShort:
614 case ParserIntType.UnsignedShort:
615 return target.ShortWidth;
616 case ParserIntType.SignedInt:
617 case ParserIntType.UnsignedInt:
618 return target.IntWidth;
619 case ParserIntType.SignedLong:
620 case ParserIntType.UnsignedLong:
621 return target.LongWidth;
622 case ParserIntType.SignedLongLong:
623 case ParserIntType.UnsignedLongLong:
624 return target.LongLongWidth;
626 throw new Exception("Type has no size");
630 static string GetTargetPlatformDefine(TargetPlatform target)
633 case TargetPlatform.Android:
634 return "TARGET_ANDROID";
635 case TargetPlatform.iOS:
637 case TargetPlatform.WatchOS:
638 return "TARGET_WATCHOS";
640 throw new ArgumentOutOfRangeException ();
644 static void Dump(ASTContext ctx, ParserTargetInfo targetInfo, Target target)
646 var targetFile = target.Triple;
648 if (!string.IsNullOrEmpty (OutputDir))
649 targetFile = Path.Combine (OutputDir, targetFile);
653 using (var writer = new StreamWriter(targetFile))
654 //using (var writer = Console.Out)
656 writer.WriteLine("#ifndef USED_CROSS_COMPILER_OFFSETS");
657 writer.WriteLine("#ifdef {0}", target.Defines[0]);
658 writer.WriteLine ("#ifdef {0}", GetTargetPlatformDefine (target.Platform));
659 writer.WriteLine("#ifndef HAVE_BOEHM_GC");
660 writer.WriteLine("#define HAS_CROSS_COMPILER_OFFSETS");
661 writer.WriteLine("#if defined (USE_CROSS_COMPILE_OFFSETS) || defined (MONO_CROSS_COMPILE)");
662 writer.WriteLine("#if !defined (DISABLE_METADATA_OFFSETS)");
663 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
665 DumpAligns(writer, targetInfo);
666 DumpSizes(writer, targetInfo);
667 DumpMetadataOffsets(writer, ctx, target);
669 writer.WriteLine("#endif //disable metadata check");
671 DumpJITOffsets(writer, ctx);
673 writer.WriteLine("#endif //cross compiler checks");
674 writer.WriteLine("#endif //gc check");
675 writer.WriteLine("#endif //os check");
676 writer.WriteLine("#endif //arch check");
677 writer.WriteLine("#endif //USED_CROSS_COMPILER_OFFSETS check");
680 Console.WriteLine("Generated offsets file: {0}", targetFile);
683 static void DumpAligns(TextWriter writer, ParserTargetInfo target)
687 new { Name = "gint8", Align = target.CharAlign},
688 new { Name = "gint16", Align = target.ShortAlign},
689 new { Name = "gint32", Align = target.IntAlign},
690 new { Name = "gint64", Align = GetTypeAlign(target, target.Int64Type)},
691 new { Name = "float", Align = target.FloatAlign},
692 new { Name = "double", Align = target.DoubleAlign},
693 new { Name = "gpointer", Align = GetTypeAlign(target, target.IntPtrType)},
696 // Write the alignment info for the basic types.
697 foreach (var align in aligns)
698 writer.WriteLine("DECL_ALIGN2({0},{1})", align.Name, align.Align / 8);
701 static void DumpSizes(TextWriter writer, ParserTargetInfo target)
705 new { Name = "gint8", Size = target.CharWidth},
706 new { Name = "gint16", Size = target.ShortWidth},
707 new { Name = "gint32", Size = target.IntWidth},
708 new { Name = "gint64", Size = GetTypeSize(target, target.Int64Type)},
709 new { Name = "float", Size = target.FloatWidth},
710 new { Name = "double", Size = target.DoubleWidth},
711 new { Name = "gpointer", Size = GetTypeSize(target, target.IntPtrType)},
714 // Write the size info for the basic types.
715 foreach (var size in sizes)
716 writer.WriteLine("DECL_SIZE2({0},{1})", size.Name, size.Size / 8);
719 static Class GetClassFromTypedef(ITypedDecl typedef)
721 var type = typedef.Type.Desugar() as TagType;
725 var @class = type.Declaration as Class;
727 return @class.IsIncomplete ?
728 (@class.CompleteDeclaration as Class) : @class;
731 static void DumpClasses(TextWriter writer, ASTContext ctx, IEnumerable<string> types,
732 bool optional = false)
734 foreach (var @struct in types)
736 var @class = ctx.FindCompleteClass(@struct);
738 @class = ctx.FindCompleteClass("_" + @struct);
742 var typedef = ctx.FindTypedef(@struct).FirstOrDefault(
743 decl => !decl.IsIncomplete);
746 @class = GetClassFromTypedef(typedef);
749 if (@class == null && optional)
753 throw new Exception("Expected to find struct definition for " + @struct);
755 DumpStruct(writer, @class);
759 static void DumpMetadataOffsets(TextWriter writer, ASTContext ctx, Target target)
761 var types = new List<string>
764 "MonoObjectHandlePayload",
768 "MonoInternalThread",
769 "MonoMulticastDelegate",
770 "MonoTransparentProxy",
777 "MonoComInteropProxy",
783 "SgenClientThreadInfo"
786 DumpClasses(writer, ctx, types);
789 static void DumpJITOffsets(TextWriter writer, ASTContext ctx)
791 writer.WriteLine("#ifndef DISABLE_JIT_OFFSETS");
792 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
797 "MonoMethodRuntimeGenericContext",
799 "MonoGSharedVtMethodRuntimeInfo",
802 "MonoDelegateTrampInfo",
805 DumpClasses(writer, ctx, types);
807 var optionalTypes = new[]
815 DumpClasses(writer, ctx, optionalTypes, optional: true);
817 writer.WriteLine("#endif //disable jit check");
820 static void DumpStruct(TextWriter writer, Class @class)
822 var name = @class.Name;
823 if (name.StartsWith ("_", StringComparison.Ordinal))
824 name = name.Substring (1);
826 foreach (var field in @class.Fields)
828 if (field.IsBitField) continue;
830 if (name == "SgenThreadInfo" && field.Name == "regs")
833 var layout = @class.Layout.Fields.First(f => f.FieldPtr == field.OriginalPtr);
835 writer.WriteLine("DECL_OFFSET2({0},{1},{2})", name, field.Name,