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 driver = new Driver(options);
224 Setup(driver, target);
227 BuildParseOptions(driver, target);
228 if (!driver.ParseCode())
231 Dump(driver.Context.ASTContext, driver.Context.TargetInfo, target);
235 static void BuildParseOptions(Driver driver, Target target)
237 foreach (var header in driver.Options.Headers)
239 var source = driver.Project.AddFile(header);
240 source.Options = driver.BuildParserOptions(source);
242 if (header.Contains ("mini"))
245 source.Options.AddDefines ("HAVE_SGEN_GC");
246 source.Options.AddDefines ("HAVE_MOVING_COLLECTOR");
250 static string GetAndroidNdkPath()
252 if (!String.IsNullOrEmpty (AndroidNdkPath))
253 return AndroidNdkPath;
255 // Find the Android NDK's path from Monodroid's config.
256 var configFile = Path.Combine(MonodroidDir, "env.config");
257 if (!File.Exists(configFile))
258 throw new Exception("Expected a valid Monodroid environment config file at " + configFile);
260 var config = File.ReadAllText(configFile);
261 var match = Regex.Match(config, @"ANDROID_NDK_PATH\s*:=\s(.*)");
262 return match.Groups[1].Value.Trim();
265 static void ParseCommandLineArgs(string[] args)
267 var showHelp = false;
269 var options = new Mono.Options.OptionSet () {
270 { "abi=", "ABI triple to generate", v => Abis.Add(v) },
271 { "o|out=", "output directory", v => OutputDir = v },
272 { "maccore=", "include directory", v => MaccoreDir = v },
273 { "monodroid=", "top monodroid directory", v => MonodroidDir = v },
274 { "android-ndk=", "Path to Android NDK", v => AndroidNdkPath = v },
275 { "targetdir=", "Path to the directory containing the mono build", v =>TargetDir = v },
276 { "mono=", "include directory", v => MonoDir = v },
277 { "h|help", "show this message and exit", v => showHelp = v != null },
281 options.Parse (args);
283 catch (Mono.Options.OptionException e) {
284 Console.WriteLine (e.Message);
290 // Print usage and exit.
291 Console.WriteLine("{0} <options>",
292 AppDomain.CurrentDomain.FriendlyName);
293 options.WriteOptionDescriptions (Console.Out);
298 static void Setup(Driver driver, Target target)
300 var options = driver.Options;
301 options.DryRun = true;
302 options.LibraryName = "Mono";
304 var parserOptions = driver.ParserOptions;
305 parserOptions.Verbose = false;
306 parserOptions.MicrosoftMode = false;
307 parserOptions.AddArguments("-xc");
308 parserOptions.AddArguments("-std=gnu99");
309 parserOptions.AddDefines("CPPSHARP");
311 foreach (var define in target.Defines)
312 parserOptions.AddDefines(define);
314 SetupToolchainPaths(driver, target);
316 SetupMono(driver, target);
319 static void SetupMono(Driver driver, Target target)
322 switch (target.Platform) {
323 case TargetPlatform.Android:
324 if (TargetDir == "") {
325 Console.Error.WriteLine ("The --targetdir= option is required when targeting android.");
326 Environment.Exit (1);
329 Console.Error.WriteLine ("The --mono= option is required when targeting android.");
330 Environment.Exit (1);
332 if (Abis.Count != 1) {
333 Console.Error.WriteLine ("Exactly one --abi= argument is required when targeting android.");
334 Environment.Exit (1);
336 targetBuild = TargetDir;
338 case TargetPlatform.WatchOS:
339 case TargetPlatform.iOS: {
340 if (!string.IsNullOrEmpty (TargetDir)) {
341 targetBuild = TargetDir;
343 string targetPath = Path.Combine (MaccoreDir, "builds");
344 if (!Directory.Exists (MonoDir))
345 MonoDir = Path.GetFullPath (Path.Combine (targetPath, "../../mono"));
346 targetBuild = Path.Combine(targetPath, target.Build);
351 throw new ArgumentOutOfRangeException ();
354 if (!Directory.Exists(targetBuild))
355 throw new Exception(string.Format("Could not find the target build directory: {0}", targetBuild));
357 var includeDirs = new[]
360 Path.Combine(targetBuild, "eglib", "src"),
362 Path.Combine(MonoDir, "mono"),
363 Path.Combine(MonoDir, "mono", "mini"),
364 Path.Combine(MonoDir, "eglib", "src")
367 foreach (var inc in includeDirs)
368 driver.ParserOptions.AddIncludeDirs(inc);
370 var filesToParse = new[]
372 Path.Combine(MonoDir, "mono", "metadata", "metadata-cross-helpers.c"),
373 Path.Combine(MonoDir, "mono", "mini", "mini-cross-helpers.c"),
376 foreach (var file in filesToParse)
377 driver.Options.Headers.Add(file);
380 static void SetupMSVC(Driver driver, string triple)
382 var parserOptions = driver.ParserOptions;
384 parserOptions.Abi = Parser.AST.CppAbi.Microsoft;
385 parserOptions.MicrosoftMode = true;
387 var systemIncludeDirs = new[]
389 @"C:\Program Files (x86)\Windows Kits\8.1\Include\um",
390 @"C:\Program Files (x86)\Windows Kits\8.1\Include\shared"
393 foreach (var inc in systemIncludeDirs)
394 parserOptions.AddSystemIncludeDirs(inc);
396 parserOptions.AddDefines("HOST_WIN32");
399 static void SetupToolchainPaths(Driver driver, Target target)
401 switch (target.Platform) {
402 case TargetPlatform.Android:
403 SetupAndroidNDK(driver, target);
405 case TargetPlatform.iOS:
406 case TargetPlatform.WatchOS:
407 SetupXcode(driver, target);
410 throw new ArgumentOutOfRangeException ();
414 static string GetArchFromTriple(string triple)
416 if (triple.Contains("mips"))
419 if (triple.Contains("arm64") || triple.Contains("aarch64"))
422 if (triple.Contains("arm"))
425 if (triple.Contains("i686"))
428 if (triple.Contains("x86_64"))
431 throw new Exception("Unknown architecture from triple: " + triple);
434 static string GetXcodeToolchainPath()
436 var toolchains = Directory.EnumerateDirectories("/Applications", "Xcode*")
440 var toolchainPath = toolchains.LastOrDefault();
441 if (toolchainPath == null)
442 throw new Exception("Could not find a valid Xcode SDK");
444 return toolchainPath;
447 static string GetXcodeBuiltinIncludesFolder()
449 var toolchainPath = GetXcodeToolchainPath();
451 var toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
452 "Contents/Developer/Toolchains")).ToList();
455 toolchainPath = toolchains.LastOrDefault();
456 if (toolchainPath == null)
457 throw new Exception("Could not find a valid Xcode toolchain");
459 var includePaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
460 "usr/lib/clang")).ToList();
461 var includePath = includePaths.LastOrDefault();
463 if (includePath == null)
464 throw new Exception("Could not find a valid Clang include folder");
466 return Path.Combine(includePath, "include");
469 static string GetXcodeiOSIncludesFolder()
471 var toolchainPath = GetXcodeToolchainPath();
473 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
474 "Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs")).ToList();
475 var sdkPath = sdkPaths.LastOrDefault();
478 throw new Exception("Could not find a valid iPhone SDK");
480 return Path.Combine(sdkPath, "usr/include");
483 static string GetXcodeWatchOSIncludesFolder()
485 var toolchainPath = GetXcodeToolchainPath();
487 var sdkPaths = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
488 "Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs")).ToList();
489 var sdkPath = sdkPaths.LastOrDefault();
492 throw new Exception("Could not find a valid WatchOS SDK");
494 return Path.Combine(sdkPath, "usr/include");
497 static void SetupXcode(Driver driver, Target target)
499 var parserOptions = driver.ParserOptions;
501 var builtinsPath = GetXcodeBuiltinIncludesFolder();
504 switch (target.Platform) {
505 case TargetPlatform.iOS:
506 includePath = GetXcodeiOSIncludesFolder();
508 case TargetPlatform.WatchOS:
509 includePath = GetXcodeWatchOSIncludesFolder();
512 throw new ArgumentOutOfRangeException ();
515 parserOptions.AddSystemIncludeDirs(builtinsPath);
516 parserOptions.AddSystemIncludeDirs(includePath);
518 parserOptions.NoBuiltinIncludes = true;
519 parserOptions.NoStandardIncludes = true;
520 parserOptions.TargetTriple = target.Triple;
523 static string GetAndroidHostToolchainPath()
525 var androidNdkPath = GetAndroidNdkPath ();
526 var toolchains = Directory.EnumerateDirectories(
527 Path.Combine(androidNdkPath, "toolchains"), "llvm*").ToList();
530 var toolchainPath = toolchains.LastOrDefault();
531 if (toolchainPath == null)
532 throw new Exception("Could not find a valid NDK host toolchain");
534 toolchains = Directory.EnumerateDirectories(Path.Combine(toolchainPath,
535 "prebuilt")).ToList();
538 toolchainPath = toolchains.LastOrDefault();
539 if (toolchainPath == null)
540 throw new Exception("Could not find a valid NDK host toolchain");
542 return toolchainPath;
545 static string GetAndroidBuiltinIncludesFolder()
547 var toolchainPath = GetAndroidHostToolchainPath();
549 string clangToolchainPath = Path.Combine(toolchainPath, "lib64", "clang");
550 if (!Directory.Exists (clangToolchainPath))
551 clangToolchainPath = Path.Combine(toolchainPath, "lib", "clang");
553 string includePath = null;
554 if (Directory.Exists (clangToolchainPath)) {
555 var includePaths = Directory.EnumerateDirectories(clangToolchainPath).ToList();
556 includePath = includePaths.LastOrDefault();
558 if (includePath == null)
559 throw new Exception("Could not find a valid Clang include folder");
561 return Path.Combine(includePath, "include");
564 static void SetupAndroidNDK(Driver driver, Target target)
566 var options = driver.Options;
567 var parserOptions = driver.ParserOptions;
569 var builtinsPath = GetAndroidBuiltinIncludesFolder();
570 parserOptions.AddSystemIncludeDirs(builtinsPath);
572 var androidNdkRoot = GetAndroidNdkPath ();
573 const int androidNdkApiLevel = 21;
575 var toolchainPath = Path.Combine(androidNdkRoot, "platforms",
576 "android-" + androidNdkApiLevel, "arch-" + GetArchFromTriple(target.Triple),
578 parserOptions.AddSystemIncludeDirs(toolchainPath);
580 parserOptions.NoBuiltinIncludes = true;
581 parserOptions.NoStandardIncludes = true;
582 parserOptions.TargetTriple = target.Triple;
585 static uint GetTypeAlign(ParserTargetInfo target, ParserIntType type)
589 case ParserIntType.SignedChar:
590 case ParserIntType.UnsignedChar:
591 return target.CharAlign;
592 case ParserIntType.SignedShort:
593 case ParserIntType.UnsignedShort:
594 return target.ShortAlign;
595 case ParserIntType.SignedInt:
596 case ParserIntType.UnsignedInt:
597 return target.IntAlign;
598 case ParserIntType.SignedLong:
599 case ParserIntType.UnsignedLong:
600 return target.LongAlign;
601 case ParserIntType.SignedLongLong:
602 case ParserIntType.UnsignedLongLong:
603 return target.LongLongAlign;
605 throw new Exception("Type has no alignment");
609 static uint GetTypeSize(ParserTargetInfo target, ParserIntType type)
613 case ParserIntType.SignedChar:
614 case ParserIntType.UnsignedChar:
615 return target.CharWidth;
616 case ParserIntType.SignedShort:
617 case ParserIntType.UnsignedShort:
618 return target.ShortWidth;
619 case ParserIntType.SignedInt:
620 case ParserIntType.UnsignedInt:
621 return target.IntWidth;
622 case ParserIntType.SignedLong:
623 case ParserIntType.UnsignedLong:
624 return target.LongWidth;
625 case ParserIntType.SignedLongLong:
626 case ParserIntType.UnsignedLongLong:
627 return target.LongLongWidth;
629 throw new Exception("Type has no size");
633 static string GetTargetPlatformDefine(TargetPlatform target)
636 case TargetPlatform.Android:
637 return "TARGET_ANDROID";
638 case TargetPlatform.iOS:
640 case TargetPlatform.WatchOS:
641 return "TARGET_WATCHOS";
643 throw new ArgumentOutOfRangeException ();
647 static void Dump(ASTContext ctx, ParserTargetInfo targetInfo, Target target)
649 var targetFile = target.Triple;
651 if (!string.IsNullOrEmpty (OutputDir))
652 targetFile = Path.Combine (OutputDir, targetFile);
656 using (var writer = new StreamWriter(targetFile))
657 //using (var writer = Console.Out)
659 writer.WriteLine("#ifndef USED_CROSS_COMPILER_OFFSETS");
660 writer.WriteLine("#ifdef {0}", target.Defines[0]);
661 writer.WriteLine ("#ifdef {0}", GetTargetPlatformDefine (target.Platform));
662 writer.WriteLine("#ifndef HAVE_BOEHM_GC");
663 writer.WriteLine("#define HAS_CROSS_COMPILER_OFFSETS");
664 writer.WriteLine("#if defined (USE_CROSS_COMPILE_OFFSETS) || defined (MONO_CROSS_COMPILE)");
665 writer.WriteLine("#if !defined (DISABLE_METADATA_OFFSETS)");
666 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
668 DumpAligns(writer, targetInfo);
669 DumpSizes(writer, targetInfo);
670 DumpMetadataOffsets(writer, ctx, target);
672 writer.WriteLine("#endif //disable metadata check");
674 DumpJITOffsets(writer, ctx);
676 writer.WriteLine("#endif //cross compiler checks");
677 writer.WriteLine("#endif //gc check");
678 writer.WriteLine("#endif //os check");
679 writer.WriteLine("#endif //arch check");
680 writer.WriteLine("#endif //USED_CROSS_COMPILER_OFFSETS check");
683 Console.WriteLine("Generated offsets file: {0}", targetFile);
686 static void DumpAligns(TextWriter writer, ParserTargetInfo target)
690 new { Name = "gint8", Align = target.CharAlign},
691 new { Name = "gint16", Align = target.ShortAlign},
692 new { Name = "gint32", Align = target.IntAlign},
693 new { Name = "gint64", Align = GetTypeAlign(target, target.Int64Type)},
694 new { Name = "float", Align = target.FloatAlign},
695 new { Name = "double", Align = target.DoubleAlign},
696 new { Name = "gpointer", Align = GetTypeAlign(target, target.IntPtrType)},
699 // Write the alignment info for the basic types.
700 foreach (var align in aligns)
701 writer.WriteLine("DECL_ALIGN2({0},{1})", align.Name, align.Align / 8);
704 static void DumpSizes(TextWriter writer, ParserTargetInfo target)
708 new { Name = "gint8", Size = target.CharWidth},
709 new { Name = "gint16", Size = target.ShortWidth},
710 new { Name = "gint32", Size = target.IntWidth},
711 new { Name = "gint64", Size = GetTypeSize(target, target.Int64Type)},
712 new { Name = "float", Size = target.FloatWidth},
713 new { Name = "double", Size = target.DoubleWidth},
714 new { Name = "gpointer", Size = GetTypeSize(target, target.IntPtrType)},
717 // Write the size info for the basic types.
718 foreach (var size in sizes)
719 writer.WriteLine("DECL_SIZE2({0},{1})", size.Name, size.Size / 8);
722 static Class GetClassFromTypedef(ITypedDecl typedef)
724 var type = typedef.Type.Desugar() as TagType;
728 var @class = type.Declaration as Class;
730 return @class.IsIncomplete ?
731 (@class.CompleteDeclaration as Class) : @class;
734 static void DumpClasses(TextWriter writer, ASTContext ctx, IEnumerable<string> types,
735 bool optional = false)
737 foreach (var @struct in types)
739 var @class = ctx.FindCompleteClass(@struct);
741 @class = ctx.FindCompleteClass("_" + @struct);
745 var typedef = ctx.FindTypedef(@struct).FirstOrDefault(
746 decl => !decl.IsIncomplete);
749 @class = GetClassFromTypedef(typedef);
752 if (@class == null && optional)
756 throw new Exception("Expected to find struct definition for " + @struct);
758 DumpStruct(writer, @class);
762 static void DumpMetadataOffsets(TextWriter writer, ASTContext ctx, Target target)
764 var types = new List<string>
767 "MonoObjectHandlePayload",
771 "MonoInternalThread",
772 "MonoMulticastDelegate",
773 "MonoTransparentProxy",
780 "MonoComInteropProxy",
786 "SgenClientThreadInfo"
789 DumpClasses(writer, ctx, types);
792 static void DumpJITOffsets(TextWriter writer, ASTContext ctx)
794 writer.WriteLine("#ifndef DISABLE_JIT_OFFSETS");
795 writer.WriteLine("#define USED_CROSS_COMPILER_OFFSETS");
800 "MonoMethodRuntimeGenericContext",
802 "MonoGSharedVtMethodRuntimeInfo",
805 "MonoDelegateTrampInfo",
808 DumpClasses(writer, ctx, types);
810 var optionalTypes = new[]
816 "InterpMethodArguments",
819 DumpClasses(writer, ctx, optionalTypes, optional: true);
821 writer.WriteLine("#endif //disable jit check");
824 static void DumpStruct(TextWriter writer, Class @class)
826 var name = @class.Name;
827 if (name.StartsWith ("_", StringComparison.Ordinal))
828 name = name.Substring (1);
830 foreach (var field in @class.Fields)
832 if (field.IsBitField) continue;
834 if (name == "SgenThreadInfo" && field.Name == "regs")
837 var layout = @class.Layout.Fields.First(f => f.FieldPtr == field.OriginalPtr);
839 writer.WriteLine("DECL_OFFSET2({0},{1},{2})", name, field.Name,