Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Diagnostics;
Expand Down Expand Up @@ -207,10 +208,10 @@ protected static string AdbStartActivity (string activity)
return RunAdbCommand ($"shell am start -S -n \"{activity}\"");
}

protected static void RunProjectAndAssert (XamarinAndroidApplicationProject proj, ProjectBuilder builder, string logName = "run.log", bool doNotCleanupOnUpdate = false, string [] parameters = null)
protected static void RunProjectAndAssert (XamarinAndroidApplicationProject proj, ProjectBuilder builder, string logName = "run.log", Dictionary<string, string> environmentVariables = null, bool doNotCleanupOnUpdate = false, string [] parameters = null)
{
builder.BuildLogFile = logName;
Assert.True (builder.RunTarget (proj, "Run", doNotCleanupOnUpdate: doNotCleanupOnUpdate, parameters: parameters), "Project should have run.");
Assert.True (builder.RunTarget (proj, "Run", doNotCleanupOnUpdate: doNotCleanupOnUpdate, parameters: parameters, environmentVariables: environmentVariables), "Project should have run.");
}

protected static void StartActivityAndAssert (XamarinAndroidApplicationProject proj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ bool IsValueAssignableFrom (Type valueType, LlvmIrVariable variable)

ulong GetAggregateValueElementCount (GeneratorWriteContext context, Type type, object? value, LlvmIrGlobalVariable? globalVariable = null)
{
if (type == typeof(LlvmIrStringBlob)) {
return 1; // String blobs are a collection of bytes
}

if (!type.IsArray ()) {
throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count");
}
Expand Down Expand Up @@ -479,6 +483,11 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv
return;
}

if (type == typeof(LlvmIrStringBlob)) {
WriteStringBlobType (context, (LlvmIrStringBlob?)value, out typeInfo);
return;
}

irType = GetIRType (context, type, out size, out isPointer);
typeInfo = new LlvmTypeInfo (
isPointer: isPointer,
Expand All @@ -490,6 +499,21 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv
context.Output.Write (irType);
}

void WriteStringBlobType (GeneratorWriteContext context, LlvmIrStringBlob? blob, out LlvmTypeInfo typeInfo)
{
long size = blob?.Size ?? 0;
// Blobs are always arrays of bytes
context.Output.Write ($"[{size} x i8]");

typeInfo = new LlvmTypeInfo (
isPointer: false,
isAggregate: true,
isStructure: false,
size: (ulong)size,
maxFieldAlignment: 1
);
}

void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo)
{
WriteArrayType (context, elementType, elementCount, variable: null, out typeInfo);
Expand Down Expand Up @@ -769,6 +793,11 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value,
return;
}

if (type == typeof(LlvmIrStringBlob)) {
WriteStringBlobArray (context, (LlvmIrStringBlob)value);
return;
}

if (type.IsArray) {
if (type == typeof(byte[])) {
WriteInlineArray (context, (byte[])value, encodeAsASCII: true);
Expand All @@ -786,6 +815,69 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value,
throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported");
}

void WriteStringBlobArray (GeneratorWriteContext context, LlvmIrStringBlob blob)
{
const uint stride = 16;
Type elementType = typeof(byte);

LlvmIrVariableNumberFormat oldNumberFormat = context.NumberFormat;
context.NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal;
WriteArrayValueStart (context);
foreach (LlvmIrStringBlob.StringInfo si in blob.GetSegments ()) {
if (si.Offset > 0) {
context.Output.Write (',');
context.Output.WriteLine ();
context.Output.WriteLine ();
}

context.Output.Write (context.CurrentIndent);
WriteCommentLine (context, $" '{si.Value}' @ {si.Offset}");
WriteBytes (si.Bytes);
}
context.Output.WriteLine ();
WriteArrayValueEnd (context);
context.NumberFormat = oldNumberFormat;

void WriteBytes (byte[] bytes)
{
ulong counter = 0;
bool first = true;
foreach (byte b in bytes) {
if (!first) {
WriteCommaWithStride (counter);
} else {
context.Output.Write (context.CurrentIndent);
first = false;
}

counter++;
WriteByteTypeAndValue (b);
}

WriteCommaWithStride (counter);
WriteByteTypeAndValue (0); // Terminating NUL is counted for each string, but not included in its bytes
}

void WriteCommaWithStride (ulong counter)
{
context.Output.Write (',');
if (stride == 1 || counter % stride == 0) {
context.Output.WriteLine ();
context.Output.Write (context.CurrentIndent);
} else {
context.Output.Write (' ');
}
}

void WriteByteTypeAndValue (byte v)
{
WriteType (context, elementType, v, out _);

context.Output.Write (' ');
WriteValue (context, elementType, v);
}
}

void WriteStructureValue (GeneratorWriteContext context, StructureInstance? instance)
{
if (instance == null || instance.IsZeroInitialized) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;

namespace Xamarin.Android.Tasks.LLVMIR;

/// <summary><para>
/// This class is an optimization which allows us to store strings
/// as a single "blob" of data where each string follows another, all
/// of them separated with a NUL character. This allows us to use a single
/// pointer at run time instead of several (one per string). The result is
/// less relocations in the final .so, which is good for performance
/// </para><para>
/// Each string is converted to UTF8 before storing as a byte array. To optimize
/// for size, duplicate strings are not stored, instead the earlier offset+length
/// are returned when calling the <see cref="Add(string)" /> method.
/// </para>
/// </summary>
class LlvmIrStringBlob
{
// Length is one more than byte size, to account for the terminating nul
public record struct StringInfo (int Offset, int Length, byte[] Bytes, string Value);

Dictionary<string, StringInfo> cache = new (StringComparer.Ordinal);
List<StringInfo> segments = new ();
long size = 0;

public long Size => size;

public (int offset, int length) Add (string s)
{
if (cache.TryGetValue (s, out StringInfo info)) {
return (info.Offset, info.Length);
}

byte[] bytes = MonoAndroidHelper.Utf8StringToBytes (s);
int offset;
if (segments.Count > 0) {
StringInfo lastSegment = segments[segments.Count - 1];
offset = lastSegment.Offset + lastSegment.Length + 1; // Include trailing NUL here
} else {
offset = 0;
}

info = new StringInfo (
Offset: offset,
Length: bytes.Length,
Bytes: bytes,
Value: s
);
segments.Add (info);
cache.Add (s, info);
size += info.Length + 1; // Account for the trailing NUL

return (info.Offset, info.Length);
}

public int GetIndexOf (string s)
{
if (String.IsNullOrEmpty (s)) {
return -1;
}

if (!cache.TryGetValue (s, out StringInfo info)) {
return -1;
}

return info.Offset;
}

public IEnumerable<StringInfo> GetSegments ()
{
foreach (StringInfo si in segments) {
yield return si;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ static TypeMapDebugEntry GetDebugEntry (TypeDefinition td, TypeDefinitionCache c
return new TypeMapDebugEntry {
JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, cache),
ManagedName = GetManagedTypeName (td),
ManagedTypeTokenId = td.MetadataToken.ToUInt32 (),
TypeDefinition = td,
SkipInJavaToManaged = ShouldSkipInJavaToManaged (td),
AssemblyName = td.Module.Assembly.Name.Name,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ internal sealed class TypeMapDebugEntry
{
public string JavaName;
public string ManagedName;
public uint ManagedTypeTokenId;
public bool SkipInJavaToManaged;
public TypeMapDebugEntry DuplicateForJavaToManaged;
public string AssemblyName;

// This field is only used by the Cecil adapter for temp storage while reading.
// It is not used to create the typemap.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
Expand Down Expand Up @@ -101,6 +102,7 @@ void WriteTypeMapDebugEntry (XmlWriter xml, TypeMapDebugEntry entry)
xml.WriteAttributeStringIfNotDefault ("managed-name", entry.ManagedName);
xml.WriteAttributeStringIfNotDefault ("skip-in-java-to-managed", entry.SkipInJavaToManaged);
xml.WriteAttributeStringIfNotDefault ("is-invoker", entry.IsInvoker);
xml.WriteAttributeString ("managed-type-token-id", entry.ManagedTypeTokenId.ToString (CultureInfo.InvariantCulture));
xml.WriteEndElement ();
}

Expand Down Expand Up @@ -198,19 +200,20 @@ public static TypeMapObjectsXmlFile Import (string filename)

static void ImportDebugData (XElement root, TypeMapObjectsXmlFile file)
{
var isMonoAndroid = root.GetAttributeOrDefault ("assembly-name", string.Empty) == "Mono.Android";
var assemblyName = root.GetAttributeOrDefault ("assembly-name", string.Empty);
var isMonoAndroid = assemblyName == "Mono.Android";
var javaToManaged = root.Element ("java-to-managed");

if (javaToManaged is not null) {
foreach (var entry in javaToManaged.Elements ("entry"))
file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid));
file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid));
}

var managedToJava = root.Element ("managed-to-java");

if (managedToJava is not null) {
foreach (var entry in managedToJava.Elements ("entry"))
file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid));
file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, assemblyName, isMonoAndroid));
}
}

Expand Down Expand Up @@ -251,19 +254,22 @@ public static void WriteEmptyFile (string destination, TaskLoggingHelper log)
File.Create (destination).Dispose ();
}

static TypeMapDebugEntry FromDebugEntryXml (XElement entry, bool isMonoAndroid)
static TypeMapDebugEntry FromDebugEntryXml (XElement entry, string assemblyName, bool isMonoAndroid)
{
var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty);
var managedName = entry.GetAttributeOrDefault ("managed-name", string.Empty);
var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false);
var isInvoker = entry.GetAttributeOrDefault ("is-invoker", false);
var managedTokenId = entry.GetAttributeOrDefault ("managed-type-token-id", (uint)0);

return new TypeMapDebugEntry {
JavaName = javaName,
ManagedName = managedName,
ManagedTypeTokenId = managedTokenId,
SkipInJavaToManaged = skipInJavaToManaged,
IsInvoker = isInvoker,
IsMonoAndroid = isMonoAndroid,
AssemblyName = assemblyName,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ public override string GetComment (object data, string fieldName)
var entry = EnsureType<TypeMapEntry> (data);

if (String.Compare ("from", fieldName, StringComparison.Ordinal) == 0) {
return $"from: {entry.from}";
return $" from: {entry.from}";
}

if (String.Compare ("to", fieldName, StringComparison.Ordinal) == 0) {
return $"to: {entry.to}";
return $" to: {entry.to}";
}

return String.Empty;
Expand Down
Loading
Loading