Skip to content

Commit c227042

Browse files
authored
Hash names of all the packaged DSOs (#6522)
[monodroid] Hash the names of all packaged DSOs (#6522) Context: #6522 Context: https://sourceware.org/binutils/docs-2.37/as/index.html Context: https://sourceware.org/binutils/docs-2.37/as/AArch64-Directives.html#AArch64-Directives Context: https://sourceware.org/binutils/docs-2.37/as/ARM-Directives.html#ARM-Directives Context: https://sourceware.org/binutils/docs-2.37/as/ARM-Directives.html#ARM-Directives Context: https://sourceware.org/binutils/docs-2.37/as/Quad.html#Quad Commit 000cf5a introduced hashing of native library names so that we would only try to load specific Mono components which were configured at app build time. Expand this concept so that we support hashing any form of native library name (DSO name) sent to use by Mono, and look for a cached entry for that DSO. The cache includes the actual native library name, whether it should be ignored when requested (e.g. an empty AOT `.so` file; see db161ae & df667b0), and the cached `dlopen()` value: struct DSOCacheEntry { uint64_t hash; bool ignore; const char *name; void *handle; }; DSOCacheEntry dso_cache[] = {…}; The `dso_cache` is computed at App build time, and stored in `libxamarin-app.so`, and contains values sorted on `DSOCacheEntry::hash`. The use of `dso_cache` removes a bit of string processing from both startup and the application run time, reducing app startup times in some scenarios: | Scenario | Before ms | After ms | Δ | | --------------------------------------------- | --------: | --------: | -------: | | Plain Xamarin.Android (JIT, 64-bit) | 311.500 | 311.600 | +0.03% ✗ | | Plain Xamarin.Android (Profiled AOT, 64-bit) | 253.500 | 247.000 | -2.56% ✓ | | .NET MAUI Hello World (JIT, 64-bit) | 1156.300 | 1159.500 | +0.28% ✗ | | .NET MAUI Hello World (Profiled AOT, 64-bit) | 868.900 | 831.700 | -4.28% ✓ | (Times measured on a Pixel 3 XL.) Above table is a subset of values from #6522; see original PR for complete table information. We believe that the occasional increases are within the margin of error. While implementing the above described `dso_cache` idea, I hit known limitations of the native assembler generator code which, until this commit, weren't a problem (mostly related to hard-coded structure and array alignment). Address these limitations by rewriting the assembly generator. It now fully implements the structure and symbol alignment calculation for all the supported architectures. Also added is the ability to generate assembler code from managed structures, using reflection, without the need of manually written code. It also fixes a previously unnoticed issue which made typemap structures not aligned properly, which may have made them slightly slower than necessary.
1 parent 9fc53bb commit c227042

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2626
-1269
lines changed

build-tools/scripts/XABuildConfig.cs.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ namespace Xamarin.Android.Tools
88
public const string SupportedABIs = "@XA_SUPPORTED_ABIS@";
99
public const string NDKRevision = "@NDK_REVISION@";
1010
public const string NDKRelease = "@NDK_RELEASE@";
11+
public const string XamarinAndroidVersion = "@XAMARIN_ANDROID_VERSION@";
12+
public const string XamarinAndroidCommitHash = "@XAMARIN_ANDROID_COMMIT_HASH@";
13+
public const string XamarinAndroidBranch = "@XAMARIN_ANDROID_BRANCH@";
1114
public const int NDKMinimumApiAvailable = @NDK_MINIMUM_API_AVAILABLE@;
1215
public const int AndroidLatestStableApiLevel = @ANDROID_LATEST_STABLE_API_LEVEL@;
1316
public const int AndroidDefaultTargetDotnetApiLevel = @ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL@;

build-tools/xaprepare/xaprepare/Application/BuildInfo.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ partial class BuildInfo : AppObject
2525
public string MonoHash { get; private set; } = String.Empty;
2626
public string FullMonoHash { get; private set; } = String.Empty;
2727

28+
public string XACommitHash { get; private set; } = String.Empty;
29+
public string XABranch { get; private set; } = String.Empty;
30+
2831
public async Task GatherGitInfo (Context context)
2932
{
3033
if (context == null)
@@ -36,6 +39,14 @@ public async Task GatherGitInfo (Context context)
3639
Log.StatusLine ();
3740
DetermineMonoHash (context);
3841
Log.StatusLine ();
42+
DetermineXACommitInfo (context);
43+
}
44+
45+
void DetermineXACommitInfo (Context context)
46+
{
47+
GitRunner git = CreateGitRunner (context);
48+
XACommitHash = git.GetTopCommitHash (shortHash: false);
49+
XABranch = git.GetBranchName ();
3950
}
4051

4152
public bool GatherNDKInfo (Context context)

build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ GeneratedFile Get_XABuildConfig_cs (Context context)
168168
{ "@XA_SUPPORTED_ABIS@", context.Properties.GetRequiredValue (KnownProperties.AndroidSupportedTargetJitAbis).Replace (':', ';') },
169169
{ "@ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL@", context.Properties.GetRequiredValue (KnownProperties.AndroidDefaultTargetDotnetApiLevel) },
170170
{ "@ANDROID_LATEST_STABLE_API_LEVEL@", context.Properties.GetRequiredValue (KnownProperties.AndroidLatestStableApiLevel) },
171+
{ "@XAMARIN_ANDROID_VERSION@", context.Properties.GetRequiredValue (KnownProperties.ProductVersion) },
172+
{ "@XAMARIN_ANDROID_COMMIT_HASH@", context.BuildInfo.XACommitHash },
173+
{ "@XAMARIN_ANDROID_BRANCH@", context.BuildInfo.XABranch },
171174
};
172175

173176
return new GeneratedPlaceholdersFile (

build-tools/xaprepare/xaprepare/ToolRunners/GitRunner.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,33 @@ public async Task<bool> SubmoduleUpdate (string? workingDirectory = null, bool i
135135
return await RunGit (runner, "submodule-update");
136136
}
137137

138+
public string GetBranchName (string? workingDirectory = null)
139+
{
140+
string runnerWorkingDirectory = DetermineRunnerWorkingDirectory (workingDirectory);
141+
var runner = CreateGitRunner (runnerWorkingDirectory);
142+
runner
143+
.AddArgument ("name-rev")
144+
.AddArgument ("--name-only")
145+
.AddArgument ("--exclude=tags/*")
146+
.AddArgument ("HEAD");
147+
148+
string branchName = String.Empty;
149+
using (var outputSink = (OutputSink)SetupOutputSink (runner)) {
150+
outputSink.LineCallback = (string? line) => {
151+
if (!String.IsNullOrEmpty (branchName)) {
152+
return;
153+
}
154+
branchName = line?.Trim () ?? String.Empty;
155+
};
156+
157+
if (!runner.Run ()) {
158+
return String.Empty;
159+
}
160+
161+
return branchName;
162+
}
163+
}
164+
138165
public string GetTopCommitHash (string? workingDirectory = null, bool shortHash = true)
139166
{
140167
string runnerWorkingDirectory = DetermineRunnerWorkingDirectory (workingDirectory);

src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,12 @@ void GenerateCompressedAssemblySources ()
7575
void Generate (IDictionary<string, CompressedAssemblyInfo> dict)
7676
{
7777
foreach (string abi in SupportedAbis) {
78-
NativeAssemblerTargetProvider asmTargetProvider = GeneratePackageManagerJava.GetAssemblyTargetProvider (abi);
7978
string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}");
8079
string asmFilePath = $"{baseAsmFilePath}.s";
81-
var asmgen = new CompressedAssembliesNativeAssemblyGenerator (dict, asmTargetProvider, baseAsmFilePath);
80+
var asmgen = new CompressedAssembliesNativeAssemblyGenerator (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), dict, baseAsmFilePath);
8281

8382
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
84-
asmgen.Write (sw);
83+
asmgen.Write (sw, asmFilePath);
8584
sw.Flush ();
8685
if (Files.CopyIfStreamChanged (sw.BaseStream, asmFilePath)) {
8786
Log.LogDebugMessage ($"File {asmFilePath} was regenerated");

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class GeneratePackageManagerJava : AndroidTask
2828
[Required]
2929
public ITaskItem[] ResolvedUserAssemblies { get; set; }
3030

31+
public ITaskItem[] NativeLibraries { get; set; }
32+
3133
public ITaskItem[] MonoComponents { get; set; }
3234

3335
public ITaskItem[] SatelliteAssemblies { get; set; }
@@ -142,20 +144,20 @@ public override bool RunTask ()
142144
return !Log.HasLoggedErrors;
143145
}
144146

145-
static internal NativeAssemblerTargetProvider GetAssemblyTargetProvider (string abi)
147+
static internal AndroidTargetArch GetAndroidTargetArchForAbi (string abi)
146148
{
147149
switch (abi.Trim ()) {
148150
case "armeabi-v7a":
149-
return new ARMNativeAssemblerTargetProvider (false);
151+
return AndroidTargetArch.Arm;
150152

151153
case "arm64-v8a":
152-
return new ARMNativeAssemblerTargetProvider (true);
154+
return AndroidTargetArch.Arm64;
153155

154156
case "x86":
155-
return new X86NativeAssemblerTargetProvider (false);
157+
return AndroidTargetArch.X86;
156158

157159
case "x86_64":
158-
return new X86NativeAssemblerTargetProvider (true);
160+
return AndroidTargetArch.X86_64;
159161

160162
default:
161163
throw new InvalidOperationException ($"Unknown ABI {abi}");
@@ -351,15 +353,29 @@ void AddEnvironment ()
351353
}
352354
}
353355

356+
var uniqueNativeLibraries = new List<ITaskItem> ();
357+
if (NativeLibraries != null) {
358+
var seenNativeLibraryNames = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
359+
foreach (ITaskItem item in NativeLibraries) {
360+
// We don't care about different ABIs here, just the file name
361+
string name = Path.GetFileName (item.ItemSpec);
362+
if (seenNativeLibraryNames.Contains (name)) {
363+
continue;
364+
}
365+
366+
seenNativeLibraryNames.Add (name);
367+
uniqueNativeLibraries.Add (item);
368+
}
369+
}
370+
354371
bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
355372
var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ApplicationConfigTaskState> (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build);
356373

357374
foreach (string abi in SupportedAbis) {
358-
NativeAssemblerTargetProvider asmTargetProvider = GetAssemblyTargetProvider (abi);
359375
string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}");
360376
string asmFilePath = $"{baseAsmFilePath}.s";
361377

362-
var asmgen = new ApplicationConfigNativeAssemblyGenerator (asmTargetProvider, baseAsmFilePath, environmentVariables, systemProperties) {
378+
var asmgen = new ApplicationConfigNativeAssemblyGenerator (GetAndroidTargetArchForAbi (abi), environmentVariables, systemProperties, Log) {
363379
IsBundledApp = IsBundledApplication,
364380
UsesMonoAOT = usesMonoAOT,
365381
UsesMonoLLVM = EnableLLVM,
@@ -379,11 +395,12 @@ void AddEnvironment ()
379395
// runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names
380396
// and in the same order.
381397
MonoComponents = monoComponents,
398+
NativeLibraries = uniqueNativeLibraries,
382399
HaveAssemblyStore = UseAssemblyStore,
383400
};
384401

385402
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
386-
asmgen.Write (sw);
403+
asmgen.Write (sw, asmFilePath);
387404
sw.Flush ();
388405
Files.CopyIfStreamChanged (sw.BaseStream, asmFilePath);
389406
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Globalization;
45
using System.IO;
56
using System.Text;
67
using System.Text.RegularExpressions;
@@ -33,15 +34,16 @@ public sealed class ApplicationConfig
3334
public uint system_property_count;
3435
public uint number_of_assemblies_in_apk;
3536
public uint bundled_assembly_name_width;
36-
public uint number_of_assembly_blobs;
37+
public uint number_of_assembly_store_files;
38+
public uint number_of_dso_cache_entries;
3739
public uint mono_components_mask;
3840
public string android_package_name;
3941
};
40-
const uint ApplicationConfigFieldCount = 18;
42+
const uint ApplicationConfigFieldCount = 19;
4143

4244
static readonly object ndkInitLock = new object ();
4345
static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' };
44-
static readonly Regex stringLabelRegex = new Regex ("^\\.L\\.env\\.str\\.[0-9]+:", RegexOptions.Compiled);
46+
static readonly Regex stringLabelRegex = new Regex ("^\\.L\\.autostr\\.[0-9]+:", RegexOptions.Compiled);
4547

4648
static readonly HashSet <string> expectedPointerTypes = new HashSet <string> (StringComparer.Ordinal) {
4749
".long",
@@ -102,7 +104,6 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
102104

103105
line = lines [++i];
104106
field = GetField (envFile, line, i);
105-
106107
AssertFieldType (envFile, ".asciz", field [0], i);
107108
strings [label] = AssertIsAssemblerString (envFile, field [1], i);
108109
continue;
@@ -196,17 +197,22 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
196197
ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile, i, field [1]);
197198
break;
198199

199-
case 15: // number_of_assembly_blobs: uint32_t / .word | .long
200+
case 15: // number_of_assembly_store_files: uint32_t / .word | .long
200201
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
201-
ret.number_of_assembly_blobs = ConvertFieldToUInt32 ("number_of_assembly_blobs", envFile, i, field [1]);
202+
ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile, i, field [1]);
202203
break;
203204

204-
case 16: // mono_components_mask: uint32_t / .word | .long
205+
case 16: // number_of_dso_cache_entries: uint32_t / .word | .long
206+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
207+
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile, i, field [1]);
208+
break;
209+
210+
case 17: // mono_components_mask: uint32_t / .word | .long
205211
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
206212
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile, i, field [1]);
207213
break;
208214

209-
case 17: // android_package_name: string / [pointer type]
215+
case 18: // android_package_name: string / [pointer type]
210216
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
211217
pointers.Add (field [1].Trim ());
212218
break;
@@ -216,7 +222,7 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
216222
if (String.Compare (".size", field [0], StringComparison.Ordinal) == 0) {
217223
fieldCount--;
218224
Assert.IsTrue (field [1].StartsWith ("application_config", StringComparison.Ordinal), $"Mismatched .size directive in '{envFile}:{i}'");
219-
break; // We've reached the end of the application_config structure
225+
gatherFields = false; // We've reached the end of the application_config structure
220226
}
221227
}
222228
Assert.AreEqual (ApplicationConfigFieldCount, fieldCount, $"Invalid 'application_config' field count in environment file '{envFile}'");
@@ -280,7 +286,8 @@ static Dictionary<string, string> ReadEnvironmentVariables (string envFile)
280286
field = GetField (envFile, line, i);
281287
if (String.Compare (".size", field [0], StringComparison.Ordinal) == 0) {
282288
Assert.IsTrue (field [1].StartsWith ("app_environment_variables", StringComparison.Ordinal), $"Mismatched .size directive in '{envFile}:{i}'");
283-
break; // We've reached the end of the environment variable array
289+
gatherPointers = false; // We've reached the end of the environment variable array
290+
continue;
284291
}
285292

286293
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
@@ -311,13 +318,20 @@ static Dictionary<string, string> ReadEnvironmentVariables (string envFile)
311318
static bool IsCommentLine (string line)
312319
{
313320
string l = line?.Trim ();
314-
return !String.IsNullOrEmpty (l) && l.StartsWith ("/*", StringComparison.Ordinal);
321+
if (String.IsNullOrEmpty (l)) {
322+
return false;
323+
}
324+
325+
return l.StartsWith ("/*", StringComparison.Ordinal) ||
326+
l.StartsWith ("//", StringComparison.Ordinal) ||
327+
l.StartsWith ("#", StringComparison.Ordinal) ||
328+
l.StartsWith ("@", StringComparison.Ordinal);
315329
}
316330

317331
static string[] GetField (string file, string line, int lineNumber)
318332
{
319333
string[] ret = line?.Trim ()?.Split ('\t');
320-
Assert.AreEqual (2, ret.Length, $"Invalid assembler field format in file '{file}:{lineNumber}': '{line}'");
334+
Assert.IsTrue (ret.Length >= 2, $"Invalid assembler field format in file '{file}:{lineNumber}': '{line}'");
321335

322336
return ret;
323337
}
@@ -497,10 +511,11 @@ static void DumpLines (string streamName, List <string> lines)
497511

498512
static bool ConvertFieldToBool (string fieldName, string envFile, int fileLine, string value)
499513
{
500-
Assert.AreEqual (1, value.Length, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (too long)");
514+
// Allow both decimal and hexadecimal values
515+
Assert.IsTrue (value.Length > 0 && value.Length <= 3, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (length not between 1 and 3)");
501516

502517
uint fv;
503-
Assert.IsTrue (UInt32.TryParse (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (not a valid integer)");
518+
Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (not a valid integer)");
504519
Assert.IsTrue (fv == 0 || fv == 1, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid boolean value (not a valid boolean value 0 or 1)");
505520

506521
return fv == 1;
@@ -511,7 +526,7 @@ static uint ConvertFieldToUInt32 (string fieldName, string envFile, int fileLine
511526
Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint32_t value (not long enough)");
512527

513528
uint fv;
514-
Assert.IsTrue (UInt32.TryParse (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint32_t value (not a valid integer)");
529+
Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint32_t value (not a valid integer)");
515530

516531
return fv;
517532
}
@@ -521,9 +536,27 @@ static byte ConvertFieldToByte (string fieldName, string envFile, int fileLine,
521536
Assert.IsTrue (value.Length > 0, $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint8_t value (not long enough)");
522537

523538
byte fv;
524-
Assert.IsTrue (Byte.TryParse (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint8_t value (not a valid integer)");
539+
Assert.IsTrue (TryParseInteger (value, out fv), $"Field '{fieldName}' in {envFile}:{fileLine} is not a valid uint8_t value (not a valid integer)");
525540

526541
return fv;
527542
}
543+
544+
static bool TryParseInteger (string value, out uint fv)
545+
{
546+
if (value.StartsWith ("0x", StringComparison.Ordinal)) {
547+
return UInt32.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv);
548+
}
549+
550+
return UInt32.TryParse (value, out fv);
551+
}
552+
553+
static bool TryParseInteger (string value, out byte fv)
554+
{
555+
if (value.StartsWith ("0x", StringComparison.Ordinal)) {
556+
return Byte.TryParse (value.Substring (2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out fv);
557+
}
558+
559+
return Byte.TryParse (value, out fv);
560+
}
528561
}
529562
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<OutputPath>..\..\..\..\bin\Test$(Configuration)</OutputPath>
77
<SignAssembly>true</SignAssembly>
88
<AssemblyOriginatorKeyFile>..\..\..\..\product.snk</AssemblyOriginatorKeyFile>
9+
<DebugSymbols>True</DebugSymbols>
10+
<DebugType>portable</DebugType>
911
</PropertyGroup>
1012

1113
<Import Project="..\..\..\..\Configuration.props" />

0 commit comments

Comments
 (0)