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 = @"";
33 public enum TargetPlatform
44 Defines = new List<string>();
45 Arguments = new List<string>();
48 public Target(Target target)
50 Platform = target.Platform;
51 Triple = target.Triple;
53 Defines = target.Defines;
54 Arguments = target.Arguments;
57 public TargetPlatform Platform;
60 public List<string> Defines;
61 public List<string> Arguments;
64 public static List<Target> Targets = new List<Target>();
66 public static IEnumerable<Target> AndroidTargets
68 get { return Targets.Where ((t) => t.Platform == TargetPlatform.Android); }
71 public static IEnumerable<Target> DarwinTargets
75 return Targets.Where ((t) => t.Platform == TargetPlatform.iOS ||
76 t.Platform == TargetPlatform.WatchOS);
80 public static IEnumerable<Target> iOSTargets
84 return Targets.Where ((t) => t.Platform == TargetPlatform.iOS);
88 public static void SetupAndroidTargets()
90 Targets.Add (new Target {
91 Platform = TargetPlatform.Android,
92 Triple = "i686-none-linux-android",
93 Defines = { "TARGET_X86" }
96 Targets.Add (new Target {
97 Platform = TargetPlatform.Android,
98 Triple = "x86_64-none-linux-android",
99 Defines = { "TARGET_AMD64" }
102 Targets.Add (new Target {
103 Platform = TargetPlatform.Android,
104 Triple = "armv5-none-linux-androideabi",
105 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
108 Targets.Add (new Target {
109 Platform = TargetPlatform.Android,
110 Triple = "armv7-none-linux-androideabi",
111 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5", "HAVE_ARMV6",
116 Targets.Add (new Target {
117 Platform = TargetPlatform.Android,
118 Triple = "aarch64-v8a-linux-android",
119 Defines = { "TARGET_ARM64" }
122 /*Targets.Add(new Target {
123 Platform = TargetPlatform.Android,
124 Triple = "mipsel-none-linux-android",
125 Defines = { "TARGET_MIPS", "__mips__" }
128 foreach (var target in AndroidTargets)
129 target.Defines.AddRange (new string[] { "HOST_ANDROID",
130 "TARGET_ANDROID", "MONO_CROSS_COMPILE", "USE_MONO_CTX"
134 public static void SetupiOSTargets()
136 Targets.Add(new Target {
137 Platform = TargetPlatform.iOS,
138 Triple = "arm-apple-darwin10",
140 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
143 Targets.Add(new Target {
144 Platform = TargetPlatform.iOS,
145 Triple = "aarch64-apple-darwin10",
147 Defines = { "TARGET_ARM64" }
150 foreach (var target in iOSTargets) {
151 target.Defines.AddRange (new string[] { "HOST_DARWIN",
152 "TARGET_IOS", "TARGET_MACH", "MONO_CROSS_COMPILE", "USE_MONO_CTX",
157 Targets.Add(new Target {
158 Platform = TargetPlatform.WatchOS,
159 Triple = "armv7k-apple-darwin",
160 Build = "targetwatch",
161 Defines = { "TARGET_ARM", "ARM_FPU_VFP", "HAVE_ARMV5" }
164 foreach (var target in DarwinTargets) {
165 target.Defines.AddRange (new string[] { "HOST_DARWIN",
166 "TARGET_IOS", "TARGET_MACH", "MONO_CROSS_COMPILE", "USE_MONO_CTX",
172 static bool GetParentSubDirectoryPath(string parent, out string subdir)
174 var directory = Directory.GetParent(Directory.GetCurrentDirectory());
176 while (directory != null) {
177 var path = Path.Combine(directory.FullName, parent);
179 if (Directory.Exists (path)) {
184 directory = directory.Parent;
191 public static void Main(string[] args)
193 ParseCommandLineArgs(args);
196 if (!Directory.Exists (MonodroidDir) &&
197 GetParentSubDirectoryPath ("monodroid", out monodroidDir)) {
198 MonodroidDir = Path.Combine (monodroidDir);
201 if (Directory.Exists (MonodroidDir))
202 SetupAndroidTargets();
205 if (!Directory.Exists (MaccoreDir) &&
206 GetParentSubDirectoryPath ("maccore", out maccoreDir)) {
207 MaccoreDir = Path.Combine (maccoreDir);
210 if (Directory.Exists(MaccoreDir) || GenIOS)
213 foreach (var target in Targets)
215 if (Abis.Any() && !Abis.Any (target.Triple.Contains))
219 Console.WriteLine("Processing triple: {0}", target.Triple);
221 var options = new DriverOptions();
223 var driver = new Driver(options);
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");
248 source.Options.AddDefines("MONO_GENERATING_OFFSETS");
252 static string GetAndroidNdkPath()
254 if (!String.IsNullOrEmpty (AndroidNdkPath))
255 return AndroidNdkPath;
257 // Find the Android NDK's path from Monodroid's config.
258 var configFile = Path.Combine(MonodroidDir, "env.config");
259 if (!File.Exists(configFile))
260 throw new Exception("Expected a valid Monodroid environment config file at " + configFile);
262 var config = File.ReadAllText(configFile);
263 var match = Regex.Match(config, @"ANDROID_NDK_PATH\s*:=\s(.*)");
264 return match.Groups[1].Value.Trim();
267 static void ParseCommandLineArgs(string[] args)
269 var showHelp = false;
271 var options = new Mono.Options.OptionSet () {
272 { "abi=", "ABI triple to generate", v => Abis.Add(v) },
273 { "o|out=", "output directory", v => OutputDir = v },
274 { "maccore=", "include directory", v => MaccoreDir = v },
275 { "monodroid=", "top monodroid directory", v => MonodroidDir = v },
276 { "android-ndk=", "Path to Android NDK", v => AndroidNdkPath = v },
277 { "targetdir=", "Path to the directory containing the mono build", v =>TargetDir = v },
278 { "mono=", "include directory", v => MonoDir = v },
279 { "gen-ios", "generate iOS offsets", v => GenIOS = v != null },
280 { "h|help", "show this message and exit", v => showHelp = v != null },
284 options.Parse (args);
286 catch (Mono.Options.OptionException e) {
287 Console.WriteLine (e.Message);
293 // Print usage and exit.
294 Console.WriteLine("{0} <options>",
295 AppDomain.CurrentDomain.FriendlyName);
296 options.WriteOptionDescriptions (Console.Out);
301 static void Setup(Driver driver, Target target)
303 var options = driver.Options;
304 options.DryRun = true;
305 options.LibraryName = "Mono";
307 var parserOptions = driver.ParserOptions;
308 parserOptions.Verbose = false;
309 parserOptions.MicrosoftMode = false;
310 parserOptions.AddArguments("-xc");
311 parserOptions.AddArguments("-std=gnu99");
312 parserOptions.AddDefines("CPPSHARP");
313 parserOptions.AddDefines("MONO_GENERATING_OFFSETS");
315 foreach (var define in target.Defines)
316 parserOptions.AddDefines(define);
318 SetupToolchainPaths(driver, target);
320 SetupMono(driver, target);
323 static void SetupMono(Driver driver, Target target)
326 switch (target.Platform) {
327 case TargetPlatform.Android:
328 if (TargetDir == "") {
329 Console.Error.WriteLine ("The --targetdir= option is required when targeting android.");
330 Environment.Exit (1);
333 Console.Error.WriteLine ("The --mono= option is required when targeting android.");
334 Environment.Exit (1);
336 if (Abis.Count != 1) {
337 Console.Error.WriteLine ("Exactly one --abi= argument is required when targeting android.");
338 Environment.Exit (1);
340 targetBuild = TargetDir;
342 case TargetPlatform.WatchOS:
343 case TargetPlatform.iOS: {
344 if (!string.IsNullOrEmpty (TargetDir)) {
345 targetBuild = TargetDir;
347 string targetPath = Path.Combine (MaccoreDir, "builds");
348 if (!Directory.Exists (MonoDir))
349 MonoDir = Path.GetFullPath (Path.Combine (targetPath, "../../mono"));
350 targetBuild = Path.Combine(targetPath, target.Build);
355 throw new ArgumentOutOfRangeException ();
358 if (!Directory.Exists(targetBuild))
359 throw new Exception(string.Format("Could not find the target build directory: {0}", targetBuild));
361 var includeDirs = new[]
364 Path.Combine(targetBuild, "mono", "eglib"),
366 Path.Combine(MonoDir, "mono"),
367 Path.Combine(MonoDir, "mono", "mini"),
368 Path.Combine(MonoDir, "mono", "eglib")
371 foreach (var inc in includeDirs)
372 driver.ParserOptions.AddIncludeDirs(inc);
374 var filesToParse = new[]
376 Path.Combine(MonoDir, "mono", "metadata", "metadata-cross-helpers.c"),
377 Path.Combine(MonoDir, "mono", "mini", "mini-cross-helpers.c"),
380 foreach (var file in filesToParse)
381 driver.Options.Headers.Add(file);
384 static void SetupMSVC(Driver driver, string triple)
386 var parserOptions = driver.ParserOptions;
388 parserOptions.Abi = Parser.AST.CppAbi.Microsoft;
389 parserOptions.MicrosoftMode = true;
391 var systemIncludeDirs = new[]
393 @"C:\Program Files (x86)\Windows Kits\8.1\Include\um",
394 @"C:\Program Files (x86)\Windows Kits\8.1\Include\shared"
397 foreach (var inc in systemIncludeDirs)
398 parserOptions.AddSystemIncludeDirs(inc);
400 parserOptions.AddDefines("HOST_WIN32");
403 static void SetupToolchainPaths(Driver driver, Target target)
405 switch (target.Platform) {
406 case TargetPlatform.Android:
407 SetupAndroidNDK(driver, target);
409 case TargetPlatform.iOS:
410 case TargetPlatform.WatchOS:
411 SetupXcode(driver, target);
414 throw new ArgumentOutOfRangeException ();
418 static string GetArchFromTriple(string triple)
420 if (triple.Contains("mips"))
423 if (triple.Contains("arm64") || triple.Contains("aarch64"))
426 if (triple.Contains("arm"))
429 if (triple.Contains("i686"))
432 if (triple.Contains("x86_64"))
435 throw new Exception("Unknown architecture from triple: " + triple);
438 static string GetXcodeToolchainPath()
440 var toolchains = Directory.EnumerateDirectories("/Applications", "Xcode*")
444 var toolchainPath = toolchains.LastOrDefault();
445 if (toolchainPath == null)
446 throw new Exception("Could not find a valid Xcode SDK");
448 return toolchainPath;
451 static string GetXcodeBuiltinIncludesFolder()
453 var toolchainPath = GetXcodeToolchainPath();
455 var toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
456 "Contents/Developer/Toolchains")).ToList();
459 toolchainPath = toolchains.LastOrDefault();
460 if (toolchainPath == null)
461 throw new Exception("Could not find a valid Xcode toolchain");
463 var includePaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
464 "usr/lib/clang")).ToList();
465 var includePath = includePaths.LastOrDefault();
467 if (includePath == null)
468 throw new Exception("Could not find a valid Clang include folder");
470 return Path.Combine(includePath, "include");
473 static string GetXcodeiOSIncludesFolder()
475 var toolchainPath = GetXcodeToolchainPath();
477 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
478 "Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs")).ToList();
479 var sdkPath = sdkPaths.LastOrDefault();
482 throw new Exception("Could not find a valid iPhone SDK");
484 return Path.Combine(sdkPath, "usr/include");
487 static string GetXcodeWatchOSIncludesFolder()
489 var toolchainPath = GetXcodeToolchainPath();
491 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
492 "Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs")).ToList();
493 var sdkPath = sdkPaths.LastOrDefault();
496 throw new Exception("Could not find a valid WatchOS SDK");
498 return Path.Combine(sdkPath, "usr/include");
501 static void SetupXcode(Driver driver, Target target)
503 var parserOptions = driver.ParserOptions;
505 var builtinsPath = GetXcodeBuiltinIncludesFolder();
508 switch (target.Platform) {
509 case TargetPlatform.iOS:
510 includePath = GetXcodeiOSIncludesFolder();
512 case TargetPlatform.WatchOS:
513 includePath = GetXcodeWatchOSIncludesFolder();
516 throw new ArgumentOutOfRangeException ();
519 parserOptions.AddSystemIncludeDirs(builtinsPath);
520 parserOptions.AddSystemIncludeDirs(includePath);
522 parserOptions.NoBuiltinIncludes = true;
523 parserOptions.NoStandardIncludes = true;
524 parserOptions.TargetTriple = target.Triple;
527 static string GetAndroidHostToolchainPath()
529 var androidNdkPath = GetAndroidNdkPath ();
530 var toolchains = Directory.EnumerateDirectories(
531 Path.Combine(androidNdkPath, "toolchains"), "llvm*").ToList();
534 var toolchainPath = toolchains.LastOrDefault();
535 if (toolchainPath == null)
536 throw new Exception("Could not find a valid NDK host toolchain");
538 toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
539 "prebuilt")).ToList();
542 toolchainPath = toolchains.LastOrDefault();
543 if (toolchainPath == null)
544 throw new Exception("Could not find a valid NDK host toolchain");
546 return toolchainPath;
549 static string GetAndroidBuiltinIncludesFolder()
551 var toolchainPath = GetAndroidHostToolchainPath();
553 string clangToolchainPath = Path.Combine(toolchainPath, "lib64", "clang");
554 if (!Directory.Exists (clangToolchainPath))
555 clangToolchainPath = Path.Combine(toolchainPath, "lib", "clang");
557 string includePath = null;
558 if (Directory.Exists (clangToolchainPath)) {
559 var includePaths = Directory.EnumerateDirectories(clangToolchainPath).ToList();
560 includePath = includePaths.LastOrDefault();
562 if (includePath == null)
563 throw new Exception("Could not find a valid Clang include folder");
565 return Path.Combine(includePath, "include");
568 static void SetupAndroidNDK(Driver driver, Target target)
570 var options = driver.Options;
571 var parserOptions = driver.ParserOptions;
573 var builtinsPath = GetAndroidBuiltinIncludesFolder();
574 parserOptions.AddSystemIncludeDirs(builtinsPath);
576 var androidNdkRoot = GetAndroidNdkPath ();
577 const int androidNdkApiLevel = 21;
579 var toolchainPath = Path.Combine(androidNdkRoot, "platforms",
580 "android-" + androidNdkApiLevel, "arch-" + GetArchFromTriple(target.Triple),
582 parserOptions.AddSystemIncludeDirs(toolchainPath);
584 parserOptions.NoBuiltinIncludes = true;
585 parserOptions.NoStandardIncludes = true;
586 parserOptions.TargetTriple = target.Triple;
589 static uint GetTypeAlign(ParserTargetInfo target, ParserIntType type)
593 case ParserIntType.SignedChar:
594 case ParserIntType.UnsignedChar:
595 return target.CharAlign;
596 case ParserIntType.SignedShort:
597 case ParserIntType.UnsignedShort:
598 return target.ShortAlign;
599 case ParserIntType.SignedInt:
600 case ParserIntType.UnsignedInt:
601 return target.IntAlign;
602 case ParserIntType.SignedLong:
603 case ParserIntType.UnsignedLong:
604 return target.LongAlign;
605 case ParserIntType.SignedLongLong:
606 case ParserIntType.UnsignedLongLong:
607 return target.LongLongAlign;
609 throw new Exception("Type has no alignment");
613 static uint GetTypeSize(ParserTargetInfo target, ParserIntType type)
617 case ParserIntType.SignedChar:
618 case ParserIntType.UnsignedChar:
619 return target.CharWidth;
620 case ParserIntType.SignedShort:
621 case ParserIntType.UnsignedShort:
622 return target.ShortWidth;
623 case ParserIntType.SignedInt:
624 case ParserIntType.UnsignedInt:
625 return target.IntWidth;
626 case ParserIntType.SignedLong:
627 case ParserIntType.UnsignedLong:
628 return target.LongWidth;
629 case ParserIntType.SignedLongLong:
630 case ParserIntType.UnsignedLongLong:
631 return target.LongLongWidth;
633 throw new Exception("Type has no size");
637 static string GetTargetPlatformDefine(TargetPlatform target)
640 case TargetPlatform.Android:
641 return "TARGET_ANDROID";
642 case TargetPlatform.iOS:
644 case TargetPlatform.WatchOS:
645 return "TARGET_WATCHOS";
647 throw new ArgumentOutOfRangeException ();
651 static void Dump(ASTContext ctx, ParserTargetInfo targetInfo, Target target)
653 var targetFile = target.Triple;
655 if (!string.IsNullOrEmpty (OutputDir))
656 targetFile = Path.Combine (OutputDir, targetFile);
660 using (var writer = new StreamWriter(targetFile))
661 //using (var writer = Console.Out)
663 writer.WriteLine("#ifndef USED_CROSS_COMPILER_OFFSETS");
664 writer.WriteLine("#ifdef {0}", target.Defines[0]);
665 writer.WriteLine ("#ifdef {0}", GetTargetPlatformDefine (target.Platform));
666 writer.WriteLine("#ifndef HAVE_BOEHM_GC");
667 writer.WriteLine("#define HAS_CROSS_COMPILER_OFFSETS");
668 writer.WriteLine("#if defined (USE_CROSS_COMPILE_OFFSETS) || defined (MONO_CROSS_COMPILE)");
669 writer.WriteLine("#if !defined (DISABLE_METADATA_OFFSETS)");
670 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
672 DumpAligns(writer, targetInfo);
673 DumpSizes(writer, targetInfo);
674 DumpMetadataOffsets(writer, ctx, target);
676 writer.WriteLine("#endif //disable metadata check");
678 DumpJITOffsets(writer, ctx);
680 writer.WriteLine("#endif //cross compiler checks");
681 writer.WriteLine("#endif //gc check");
682 writer.WriteLine("#endif //os check");
683 writer.WriteLine("#endif //arch check");
684 writer.WriteLine("#endif //USED_CROSS_COMPILER_OFFSETS check");
687 Console.WriteLine("Generated offsets file: {0}", targetFile);
690 static void DumpAligns(TextWriter writer, ParserTargetInfo target)
694 new { Name = "gint8", Align = target.CharAlign},
695 new { Name = "gint16", Align = target.ShortAlign},
696 new { Name = "gint32", Align = target.IntAlign},
697 new { Name = "gint64", Align = GetTypeAlign(target, target.Int64Type)},
698 new { Name = "float", Align = target.FloatAlign},
699 new { Name = "double", Align = target.DoubleAlign},
700 new { Name = "gpointer", Align = GetTypeAlign(target, target.IntPtrType)},
703 // Write the alignment info for the basic types.
704 foreach (var align in aligns)
705 writer.WriteLine("DECL_ALIGN2({0},{1})", align.Name, align.Align / 8);
708 static void DumpSizes(TextWriter writer, ParserTargetInfo target)
712 new { Name = "gint8", Size = target.CharWidth},
713 new { Name = "gint16", Size = target.ShortWidth},
714 new { Name = "gint32", Size = target.IntWidth},
715 new { Name = "gint64", Size = GetTypeSize(target, target.Int64Type)},
716 new { Name = "float", Size = target.FloatWidth},
717 new { Name = "double", Size = target.DoubleWidth},
718 new { Name = "gpointer", Size = GetTypeSize(target, target.IntPtrType)},
721 // Write the size info for the basic types.
722 foreach (var size in sizes)
723 writer.WriteLine("DECL_SIZE2({0},{1})", size.Name, size.Size / 8);
726 static Class GetClassFromTypedef(ITypedDecl typedef)
728 var type = typedef.Type.Desugar() as TagType;
732 var @class = type.Declaration as Class;
734 return @class.IsIncomplete ?
735 (@class.CompleteDeclaration as Class) : @class;
738 static void DumpClasses(TextWriter writer, ASTContext ctx, IEnumerable<string> types,
739 bool optional = false)
741 foreach (var @struct in types)
743 var @class = ctx.FindCompleteClass(@struct);
745 @class = ctx.FindCompleteClass("_" + @struct);
749 var typedef = ctx.FindTypedef(@struct).FirstOrDefault(
750 decl => !decl.IsIncomplete);
753 @class = GetClassFromTypedef(typedef);
756 if (@class == null && optional)
760 throw new Exception("Expected to find struct definition for " + @struct);
762 DumpStruct(writer, @class);
766 static void DumpMetadataOffsets(TextWriter writer, ASTContext ctx, Target target)
768 var types = new List<string>
771 "MonoObjectHandlePayload",
775 "MonoInternalThread",
776 "MonoMulticastDelegate",
777 "MonoTransparentProxy",
784 "MonoComInteropProxy",
790 "SgenClientThreadInfo",
791 "MonoProfilerCallContext"
794 DumpClasses(writer, ctx, types);
797 static void DumpJITOffsets(TextWriter writer, ASTContext ctx)
799 writer.WriteLine("#ifndef DISABLE_JIT_OFFSETS");
800 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
805 "MonoMethodRuntimeGenericContext",
807 "MonoGSharedVtMethodRuntimeInfo",
810 "MonoDelegateTrampInfo",
813 DumpClasses(writer, ctx, types);
815 var optionalTypes = new[]
821 "InterpMethodArguments",
824 DumpClasses(writer, ctx, optionalTypes, optional: true);
826 writer.WriteLine("#endif //disable jit check");
829 static void DumpStruct(TextWriter writer, Class @class)
831 var name = @class.Name;
832 if (name.StartsWith ("_", StringComparison.Ordinal))
833 name = name.Substring (1);
835 foreach (var field in @class.Fields)
837 if (field.IsBitField) continue;
839 if (name == "SgenThreadInfo" && field.Name == "regs")
842 var layout = @class.Layout.Fields.First(f => f.FieldPtr == field.OriginalPtr);
844 writer.WriteLine("DECL_OFFSET2({0},{1},{2})", name, field.Name,