Skip to content

Commit fc944ee

Browse files
authored
LLVM IR code generator refactoring and updates (#8140)
Context: https://llvm.org/docs/OpaquePointers.html Context: 903ba37 903ba37 added a new native code generator using the LLVM IR language, to let LLVM code generator handle all the CPU and platform specific details. This commit improves the original generator by simplifying the code generator model so that tasks generating native code need to know **less** about LLVM IR structure. The most important changes: * Migrate to opaque pointers (LLVM 15+) which will be the only ones supported from LLVM 16 onwards. This will allow us to update our toolchain to LLVM 16 or newer at some point. It also simplifies both the code generator **and** the generated code. * Change code generation model. Now the "client" classes don't need to concern themselves with **how** the LLVM IR code looks and how it is formatted. Instead, they build a model of the code they want to output and let the generator do the rest. * Handle many more tasks automatically: * LLVM IR string management and registration * Buffer management for structures which have pointers to buffers * References to local variables and strings * Local temporary counting (unnamed labels and function parameters). LLVM requires that all unnamed local temporaries (that is labels and function parameters) are named using a shared counter, with all of thus generated names being numbers in sequence. In the previous version of the generator it was problematic, in this version it's easy and seamless.
1 parent a016b31 commit fc944ee

File tree

59 files changed

+6150
-3229
lines changed

Some content is hidden

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

59 files changed

+6150
-3229
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,22 @@ void GenerateCompressedAssemblySources ()
7474

7575
void Generate (IDictionary<string, CompressedAssemblyInfo> dict)
7676
{
77-
var llvmAsmgen = new CompressedAssembliesNativeAssemblyGenerator (dict);
78-
llvmAsmgen.Init ();
77+
var composer = new CompressedAssembliesNativeAssemblyGenerator (dict);
78+
LLVMIR.LlvmIrModule compressedAssemblies = composer.Construct ();
7979

8080
foreach (string abi in SupportedAbis) {
8181
string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}");
8282
string llvmIrFilePath = $"{baseAsmFilePath}.ll";
8383

8484
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
85-
llvmAsmgen.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath);
86-
sw.Flush ();
85+
try {
86+
composer.Generate (compressedAssemblies, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath);
87+
} catch {
88+
throw;
89+
} finally {
90+
sw.Flush ();
91+
}
92+
8793
if (Files.CopyIfStreamChanged (sw.BaseStream, llvmIrFilePath)) {
8894
Log.LogDebugMessage ($"File {llvmIrFilePath} was regenerated");
8995
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,24 @@ void Generate ()
7777
Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count);
7878
}
7979

80-
void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeReplacementsCount)
80+
void Generate (JniRemappingAssemblyGenerator jniRemappingComposer, int typeReplacementsCount)
8181
{
82-
jniRemappingGenerator.Init ();
82+
LLVMIR.LlvmIrModule module = jniRemappingComposer.Construct ();
8383

8484
foreach (string abi in SupportedAbis) {
8585
string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}");
8686
string llFilePath = $"{baseAsmFilePath}.ll";
8787

8888
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
89-
jniRemappingGenerator.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath);
89+
jniRemappingComposer.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath);
9090
sw.Flush ();
9191
Files.CopyIfStreamChanged (sw.BaseStream, llFilePath);
9292
}
9393
}
9494

9595
BuildEngine4.RegisterTaskObjectAssemblyLocal (
9696
ProjectSpecificTaskObjectKey (JniRemappingNativeCodeInfoKey),
97-
new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingGenerator.ReplacementMethodIndexEntryCount),
97+
new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingComposer.ReplacementMethodIndexEntryCount),
9898
RegisteredTaskObjectLifetime.Build
9999
);
100100
}

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ void AddEnvironment ()
390390
// and up to 4 other for arch-specific assemblies. Only **one** arch-specific store is ever loaded on the app
391391
// runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names
392392
// and in the same order.
393-
MonoComponents = monoComponents,
393+
MonoComponents = (MonoComponent)monoComponents,
394394
NativeLibraries = uniqueNativeLibraries,
395395
HaveAssemblyStore = UseAssemblyStore,
396396
AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token,
@@ -400,7 +400,7 @@ void AddEnvironment ()
400400
JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount,
401401
MarshalMethodsEnabled = EnableMarshalMethods,
402402
};
403-
appConfigAsmGen.Init ();
403+
LLVMIR.LlvmIrModule appConfigModule = appConfigAsmGen.Construct ();
404404

405405
var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<MarshalMethodsState> (ProjectSpecificTaskObjectKey (GenerateJavaStubs.MarshalMethodsRegisterTaskKey), RegisteredTaskObjectLifetime.Build);
406406
MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen;
@@ -415,26 +415,36 @@ void AddEnvironment ()
415415
} else {
416416
marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (assemblyCount, uniqueAssemblyNames);
417417
}
418-
marshalMethodsAsmGen.Init ();
418+
LLVMIR.LlvmIrModule marshalMethodsModule = marshalMethodsAsmGen.Construct ();
419419

420420
foreach (string abi in SupportedAbis) {
421421
string targetAbi = abi.ToLowerInvariant ();
422422
string environmentBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{targetAbi}");
423423
string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
424424
string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll";
425425
string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
426-
427426
AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi);
427+
428428
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
429-
appConfigAsmGen.Write (targetArch, sw, environmentLlFilePath);
430-
sw.Flush ();
431-
Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath);
429+
try {
430+
appConfigAsmGen.Generate (appConfigModule, targetArch, sw, environmentLlFilePath);
431+
} catch {
432+
throw;
433+
} finally {
434+
sw.Flush ();
435+
Files.CopyIfStreamChanged (sw.BaseStream, environmentLlFilePath);
436+
}
432437
}
433438

434439
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
435-
marshalMethodsAsmGen.Write (targetArch, sw, marshalMethodsLlFilePath);
436-
sw.Flush ();
437-
Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath);
440+
try {
441+
marshalMethodsAsmGen.Generate (marshalMethodsModule, targetArch, sw, marshalMethodsLlFilePath);
442+
} catch {
443+
throw;
444+
} finally {
445+
sw.Flush ();
446+
Files.CopyIfStreamChanged (sw.BaseStream, marshalMethodsLlFilePath);
447+
}
438448
}
439449
}
440450

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

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,8 @@ string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolP
293293
libs.Add (Path.Combine (androidLibPath, "libc.so"));
294294
libs.Add (Path.Combine (androidLibPath, "libm.so"));
295295
} else if (!UseAndroidNdk && EnableLLVM) {
296-
// We need to link against libc and libm, but since NDK is not in use, the linker won't be able to find the actual Android libraries.
297-
// Therefore, we will use their stubs to satisfy the linker. At runtime they will, of course, use the actual Android libraries.
298-
string relPath = Path.Combine ("..", "..");
299-
if (!OS.IsWindows) {
300-
// the `binutils` directory is one level down (${OS}/binutils) than the Windows one
301-
relPath = Path.Combine (relPath, "..");
302-
}
303-
string libstubsPath = Path.GetFullPath (Path.Combine (AndroidBinUtilsDirectory, relPath, "libstubs", ArchToRid (arch)));
296+
string libstubsPath = MonoAndroidHelper.GetLibstubsArchDirectoryPath (AndroidBinUtilsDirectory, arch);
297+
304298
libs.Add (Path.Combine (libstubsPath, "libc.so"));
305299
libs.Add (Path.Combine (libstubsPath, "libm.so"));
306300
}
@@ -332,26 +326,6 @@ string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolP
332326
}
333327

334328
return ldFlags.ToString ();
335-
336-
string ArchToRid (AndroidTargetArch arch)
337-
{
338-
switch (arch) {
339-
case AndroidTargetArch.Arm64:
340-
return "android-arm64";
341-
342-
case AndroidTargetArch.Arm:
343-
return "android-arm";
344-
345-
case AndroidTargetArch.X86:
346-
return "android-x86";
347-
348-
case AndroidTargetArch.X86_64:
349-
return "android-x64";
350-
351-
default:
352-
throw new InvalidOperationException ($"Internal error: unsupported ABI '{arch}'");
353-
}
354-
}
355329
}
356330

357331
static string GetNdkToolchainLibraryDir (NdkTools ndk, string binDir, string archDir = null)

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ sealed class InputFiles
2929
{
3030
public List<string> ObjectFiles;
3131
public string OutputSharedLibrary;
32+
public List<string> ExtraLibraries;
3233
}
3334

3435
[Required]
@@ -112,22 +113,23 @@ void RunLinker (Config config)
112113

113114
IEnumerable<Config> GetLinkerConfigs ()
114115
{
116+
string runtimeNativeLibsDir = MonoAndroidHelper.GetNativeLibsRootDirectoryPath (AndroidBinUtilsDirectory);
117+
string runtimeNativeLibStubsDir = MonoAndroidHelper.GetLibstubsRootDirectoryPath (AndroidBinUtilsDirectory);
115118
var abis = new Dictionary <string, InputFiles> (StringComparer.Ordinal);
116119
ITaskItem[] dsos = ApplicationSharedLibraries;
117120
foreach (ITaskItem item in dsos) {
118121
string abi = item.GetMetadata ("abi");
119-
abis [abi] = GatherFilesForABI(item.ItemSpec, abi, ObjectFiles);
122+
abis [abi] = GatherFilesForABI (item.ItemSpec, abi, ObjectFiles, runtimeNativeLibsDir, runtimeNativeLibStubsDir);
120123
}
121124

122125
const string commonLinkerArgs =
123-
"--unresolved-symbols=ignore-in-shared-libs " +
126+
"--shared " +
127+
"--allow-shlib-undefined " +
124128
"--export-dynamic " +
125129
"-soname libxamarin-app.so " +
126130
"-z relro " +
127131
"-z noexecstack " +
128132
"--enable-new-dtags " +
129-
"--eh-frame-hdr " +
130-
"-shared " +
131133
"--build-id " +
132134
"--warn-shared-textrel " +
133135
"--fatal-warnings";
@@ -177,6 +179,12 @@ IEnumerable<Config> GetLinkerConfigs ()
177179
targetLinkerArgs.Add ("-o");
178180
targetLinkerArgs.Add (QuoteFileName (inputs.OutputSharedLibrary));
179181

182+
if (inputs.ExtraLibraries != null) {
183+
foreach (string lib in inputs.ExtraLibraries) {
184+
targetLinkerArgs.Add (lib);
185+
}
186+
}
187+
180188
string targetArgs = String.Join (" ", targetLinkerArgs);
181189
yield return new Config {
182190
LinkerPath = ld,
@@ -186,11 +194,24 @@ IEnumerable<Config> GetLinkerConfigs ()
186194
}
187195
}
188196

189-
InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles)
197+
InputFiles GatherFilesForABI (string runtimeSharedLibrary, string abi, ITaskItem[] objectFiles, string runtimeNativeLibsDir, string runtimeNativeLibStubsDir)
190198
{
199+
List<string> extraLibraries = null;
200+
string RID = MonoAndroidHelper.AbiToRid (abi);
201+
AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
202+
string libStubsPath = Path.Combine (runtimeNativeLibStubsDir, RID);
203+
string runtimeLibsDir = Path.Combine (runtimeNativeLibsDir, RID);
204+
205+
extraLibraries = new List<string> {
206+
$"-L \"{runtimeLibsDir}\"",
207+
$"-L \"{libStubsPath}\"",
208+
"-lc",
209+
};
210+
191211
return new InputFiles {
192212
OutputSharedLibrary = runtimeSharedLibrary,
193213
ObjectFiles = GetItemsForABI (abi, objectFiles),
214+
ExtraLibraries = extraLibraries,
194215
};
195216
}
196217

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ static Dictionary<string, string> ReadEnvironmentVariables (EnvironmentFile envF
402402
static string[] GetField (string llvmAssemblerFile, string nativeAssemblerFile, string line, ulong lineNumber)
403403
{
404404
string[] ret = line?.Trim ()?.Split ('\t');
405-
Assert.IsTrue (ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'");
405+
Assert.IsTrue (ret != null && ret.Length >= 2, $"Invalid assembler field format in file '{nativeAssemblerFile}:{lineNumber}': '{line}'. File generated from '{llvmAssemblerFile}'");
406406

407407
return ret;
408408
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public SymbolMetadata (SymbolMetadataKind kind, string value = null)
192192

193193
static readonly char[] splitOnWhitespace = new char[] { ' ', '\t' };
194194
static readonly char[] splitOnComma = new char[] { ',' };
195-
static readonly Regex assemblerLabelRegex = new Regex ("^[_.a-zA-Z0-9]+:", RegexOptions.Compiled);
195+
static readonly Regex assemblerLabelRegex = new Regex ("^[_.$a-zA-Z0-9]+:", RegexOptions.Compiled);
196196

197197
Dictionary<string, AssemblerSymbol> symbols = new Dictionary<string, AssemblerSymbol> (StringComparer.Ordinal);
198198
Dictionary<string, List<SymbolMetadata>> symbolMetadata = new Dictionary<string, List<SymbolMetadata>> (StringComparer.Ordinal);
@@ -238,8 +238,10 @@ void Load (string sourceFilePath)
238238
AssemblerSection currentSection = null;
239239
AssemblerSymbol currentSymbol = null;
240240

241-
string symbolName;
241+
string symbolName = null;
242242
ulong lineNumber = 0;
243+
bool addedNewSymbol = false;
244+
243245
foreach (string l in File.ReadLines (sourceFilePath, Encoding.UTF8)) {
244246
lineNumber++;
245247

@@ -253,6 +255,15 @@ void Load (string sourceFilePath)
253255
continue;
254256
}
255257

258+
if (addedNewSymbol) {
259+
addedNewSymbol = false;
260+
// Some forms of LLVM IR can generate two labels for a single symbol, depending on symbol visibility, attributes and llc parameters.
261+
// The exported symbol name 'symbol:' may be followed by another one '.Lsymbol$local:', we need to detect this and ignore the new symbol.
262+
if (assemblerLabelRegex.IsMatch (line) && String.Compare (line.Trim (), $".L{symbolName}$local:", StringComparison.Ordinal) == 0) {
263+
continue;
264+
}
265+
}
266+
256267
if (StartsNewSection (parts, ref currentSection)) {
257268
currentSymbol = null; // Symbols cannot cross sections
258269
continue;
@@ -265,6 +276,7 @@ void Load (string sourceFilePath)
265276
if (assemblerLabelRegex.IsMatch (line)) {
266277
symbolName = GetSymbolName (line);
267278
currentSymbol = AddNewSymbol (symbolName);
279+
addedNewSymbol = true;
268280
continue;
269281
}
270282

0 commit comments

Comments
 (0)