Skip to content

[CoreCLR] Generate less relocations in the native typemap code #10225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 1, 2025
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
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;

namespace Xamarin.Android.Tasks.LLVMIR;

abstract class LlvmIrArraySectionBase
{
public Type DataType { get; }
public List<object> Data { get; } = [];
public string? Header { get; }

protected LlvmIrArraySectionBase (Type type, string? header = null)
{
DataType = type;
Header = header;
}

protected void Add (object data) => Data.Add (data);
}

class LlvmIrArraySection<T> : LlvmIrArraySectionBase
{
public LlvmIrArraySection (string? header = null)
: base (typeof(T), header)
{}

public void Add (T data) => base.Add (data!);
}
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,14 @@ ulong GetAggregateValueElementCount (GeneratorWriteContext context, Type type, o
return 1; // String blobs are a collection of bytes
}

if (type.IsSubclassOf (typeof(LlvmIrSectionedArrayBase))) {
var sectionedArray = value as LlvmIrSectionedArrayBase;
if (sectionedArray == null) {
throw new InvalidOperationException ($"Internal error: null sectioned arrays aren't supported");
}
return sectionedArray.Count;
}

if (!type.IsArray ()) {
throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count");
}
Expand Down Expand Up @@ -474,10 +482,27 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv
string irType;
ulong size;
bool isPointer;
bool isStandardArray = type.IsArray ();
bool isSectionedArray = !isStandardArray && type.IsSubclassOf (typeof(LlvmIrSectionedArrayBase));

if (isStandardArray || isSectionedArray) {
Type elementType;
ulong elementCount;

if (isStandardArray) {
elementType = type.GetArrayElementType ();
elementCount = GetAggregateValueElementCount (context, type, value, globalVariable);
} else if (isSectionedArray) {
var sectionedArray = value as LlvmIrSectionedArrayBase;
if (sectionedArray == null) {
throw new InvalidOperationException ($"Internal error: sectioned array variables must have a value");
}

if (type.IsArray ()) {
Type elementType = type.GetArrayElementType ();
ulong elementCount = GetAggregateValueElementCount (context, type, value, globalVariable);
elementType = sectionedArray.ContainedType;
elementCount = GetAggregateValueElementCount (context, type, value, globalVariable);
} else {
throw new InvalidOperationException ("Internal error: should never get here (forgot to add a handler for another array type?)");
}

WriteArrayType (context, elementType, elementCount, globalVariable, out typeInfo);
return;
Expand Down Expand Up @@ -807,6 +832,11 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value,
throw new NotSupportedException ($"Internal error: array of type {type} is unsupported");
}

if (type.IsSubclassOf (typeof(LlvmIrSectionedArrayBase))) {
WriteSectionedArrayValue (context, (LlvmIrSectionedArrayBase)value);
return;
}

if (type == typeof (LlvmIrVariableReference) || type.IsSubclassOf (typeof (LlvmIrVariableReference))) {
WriteVariableReference (context, (LlvmIrVariableReference)value);
return;
Expand All @@ -815,6 +845,47 @@ public void WriteValue (GeneratorWriteContext context, Type type, object? value,
throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported");
}

void WriteSectionedArrayValue (GeneratorWriteContext context, LlvmIrSectionedArrayBase? array)
{
if (array == null) {
context.Output.Write (" zeroinitializer");
return;
}

Type elementType = array.ContainedType;
WriteArrayValueStart (context);
ulong globalCounter = 0;
int lastSectionIndex = array.Sections.Count - 1;

for (int i = 0; i < array.Sections.Count; i++) {
LlvmIrArraySectionBase section = array.Sections[i];

if (i > 0) {
context.Output.WriteLine ();
}

if (!String.IsNullOrEmpty (section.Header)) {
context.Output.Write (context.CurrentIndent);
WriteCommentLine (context, $" Module map index: {globalCounter}");
context.Output.Write (context.CurrentIndent);
WriteCommentLine (context, section.Header);
}

WriteArrayEntries (
context,
variable: null,
section.Data,
section.DataType,
stride: 1,
writeIndices: true,
terminateWithComma: i < lastSectionIndex
);
globalCounter += (ulong)section.Data.Count;
}

WriteArrayValueEnd (context);
}

void WriteStringBlobArray (GeneratorWriteContext context, LlvmIrStringBlob blob)
{
// The stride determines how many elements are written on a single line before a newline is added.
Expand Down Expand Up @@ -962,7 +1033,7 @@ uint GetArrayStride (LlvmIrVariable variable)
return 1;
}

void WriteArrayEntries (GeneratorWriteContext context, LlvmIrVariable variable, ICollection? entries, Type elementType, uint stride, bool writeIndices, bool terminateWithComma = false)
void WriteArrayEntries (GeneratorWriteContext context, LlvmIrVariable? variable, ICollection? entries, Type elementType, uint stride, bool writeIndices, bool terminateWithComma = false)
{
bool first = true;
bool ignoreComments = stride > 1;
Expand All @@ -987,7 +1058,7 @@ void WriteArrayEntries (GeneratorWriteContext context, LlvmIrVariable variable,

if (!ignoreComments) {
prevItemComment = null;
if (variable.GetArrayItemCommentCallback != null) {
if (variable != null && variable.GetArrayItemCommentCallback != null) {
prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;

namespace Xamarin.Android.Tasks.LLVMIR;

/// <summary>
/// Represents a uniform array which is composed of several sections. Each section must contain
/// entries of the same type (or derived from the same base type), has its own header but is otherwise
/// treated as a separate entity to other sections. The resulting output array will look as a flattened
/// array of arrays (sections).
/// </summary>
abstract class LlvmIrSectionedArrayBase
{
readonly Type containedType;
readonly List<LlvmIrArraySectionBase> sections = new ();

public List<LlvmIrArraySectionBase> Sections => sections;
public Type ContainedType => containedType;
public ulong Count => GetItemCount ();

protected LlvmIrSectionedArrayBase (Type containedType)
{
this.containedType = containedType;
}

protected void Add (LlvmIrArraySectionBase section)
{
if (!containedType.IsAssignableFrom (section.DataType)) {
throw new ArgumentException ("must be of type {containedType} or derived from it", nameof (section));
}

sections.Add (section);
}

ulong GetItemCount ()
{
ulong ret = 0;
foreach (LlvmIrArraySectionBase section in sections) {
ret += (ulong)section.Data.Count;
}
return ret;
}
}

class LlvmIrSectionedArray<T> : LlvmIrSectionedArrayBase
{
public LlvmIrSectionedArray ()
: base (typeof (T))
{}

public void Add (LlvmIrArraySection<T> section) => base.Add (section);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,32 @@

namespace Xamarin.Android.Tasks.LLVMIR
{
enum StructureInstanceType
{
/// <summary>
/// Instance describes actual data to be output. Guarantees that the
/// <see cref="StructureInstance.Obj"/> property isn't `null`.
/// </summary>
Data,

/// <summary>
/// Instance marks the beginning of section in an array of
/// structures. Ignored if we're not outputting an array of structures.
/// <see cref="StructureInstance.Obj"/> property will **always** be `null`
/// in such instances.
/// </summary>
ArraySection,
}

abstract class StructureInstance
{
StructureInfo info;

public object? Obj { get; }
public Type Type => info.Type;
public StructureInfo Info => info;
public StructureInstanceType InstanceType { get; }
public string? Comment { get; }

/// <summary>
/// Do **not** set this property, it is used internally by <see cref="LlvmIrModule.AddStructureArrayGlobalVariable"/>,
Expand All @@ -28,18 +47,27 @@ abstract class StructureInstance
/// </summary>
public bool IsZeroInitialized { get; set; }

protected StructureInstance (StructureInfo info, object instance)
protected StructureInstance (StructureInfo info, object instance, string? comment = null)
{
if (instance == null) {
throw new ArgumentNullException (nameof (instance));
}

InstanceType = StructureInstanceType.Data;
if (!info.Type.IsAssignableFrom (instance.GetType ())) {
throw new ArgumentException ($"must be an instance of, or derived from, the {info.Type} type, or `null` (was {instance})", nameof (instance));
}

this.info = info;
Obj = instance;
Comment = comment;
}

protected StructureInstance (StructureInfo info, string? comment = null)
{
InstanceType = StructureInstanceType.ArraySection;
this.info = info;
Comment = comment;
}
}

Expand All @@ -49,16 +77,20 @@ protected StructureInstance (StructureInfo info, object instance)
/// only get in the way), but on the other hand we need to be able to get the structure type (whose instance is in
/// <see cref="Obj"/> and <see cref="Instance"/>) only by looking at the **type**. This is needed in situations when we have
/// an array of some structures that is empty - we wouldn't be able to gleam the structure type from any instance and we still
/// need to output a stronly typed LLVM IR declaration of the structure array. With this class, most of the code will use the
/// need to output a strongly typed LLVM IR declaration of the structure array. With this class, most of the code will use the
/// abstract <see cref="StructureInstance"/> type, and knowing we have only one non-abstract implementation of the class allows
/// us to use StructureInstance&lt;T&gt; in a cast, to get <c>T</c> via reflection.
/// <summary>
sealed class StructureInstance<T> : StructureInstance
{
public T? Instance => (T)Obj;

public StructureInstance (StructureInfo info, T instance)
: base (info, instance)
public StructureInstance (StructureInfo info, T instance, string? comment = null)
: base (info, instance, comment)
{}

public StructureInstance (StructureInfo info, string? comment = null)
: base (info, comment)
{}
}
}
Loading
Loading