Skip to content

Move XElement ILLinkTrim.xml file to only be used during the library build #35725

Description

@eerhardt

Today, we have an internal XElement constructor that isn't invoked directly:

internal XElement()
: this("default")
{
}

However, we need to tell the IL Linker that it needs to preserve this constructor:

<type fullname="System.Xml.Linq.XElement">
<!-- needed by serialization -->
<method name=".ctor" />

because it is used by the XmlSerializer when generating code. See #20445, #21454, and dotnet/corefx#19244 for some information about this.

However, XmlSerializer is already special casing creating an XElement when it generates IL:

// Special case XElement
// codegen the same as 'internal XElement : this("default") { }'
if (type.FullName == "System.Xml.Linq.XElement")
{
Type xName = type.Assembly.GetType("System.Xml.Linq.XName");
if (xName != null)
{
MethodInfo XName_op_Implicit = xName.GetMethod(
"op_Implicit",
CodeGenerator.StaticBindingFlags,
new Type[] { typeof(string) }
);
ConstructorInfo XElement_ctor = type.GetConstructor(
CodeGenerator.InstanceBindingFlags,
new Type[] { xName }
);
if (XName_op_Implicit != null && XElement_ctor != null)
{
ilg.Ldstr("default");
ilg.Call(XName_op_Implicit);
ilg.New(XElement_ctor);
return;
}
}
}

The only place this constructor is truly necessary is when XmlSerializer generates C# code.

internal string GetStringForCreateInstance(string escapedTypeName, bool useReflection, bool ctorInaccessible, bool cast, string arg)
{
if (!useReflection && !ctorInaccessible)
return "new " + escapedTypeName + "(" + arg + ")";
return GetStringForCreateInstance(GetStringForTypeof(escapedTypeName, useReflection), cast && !useReflection ? escapedTypeName : null, ctorInaccessible, arg);
}
internal string GetStringForCreateInstance(string type, string cast, bool nonPublic, string arg)
{
StringBuilder createInstance = new StringBuilder();
if (cast != null && cast.Length > 0)
{
createInstance.Append('(');
createInstance.Append(cast);
createInstance.Append(')');
}
createInstance.Append(typeof(Activator).FullName);
createInstance.Append(".CreateInstance(");
createInstance.Append(type);
createInstance.Append(", ");
string bindingFlags = typeof(BindingFlags).FullName;
createInstance.Append(bindingFlags);
createInstance.Append(".Instance | ");
createInstance.Append(bindingFlags);
createInstance.Append(".Public | ");
createInstance.Append(bindingFlags);
createInstance.Append(".CreateInstance");

Here the XmlSerializer relies on the fact that there is an internal, parameterless constructor for XElement.

Investigating an early Blazor on .NET 5 app, leaving XElement rooted is causing roughly 500KB out of 4MB of unnecessary IL code to be left in the application, since this one constructor will bring in a large amount of XML code.

We should change the C# generation to special case XElement (just like we did for the IL generation case), and have it generate new XElement("default") instead of calling Activator.CreateInstance. This will allow us to remove this "dead" constructor from XElement, and it will allow us to delete the ILLinkTrim.xml file. This way the linker will never have to worry about preserving the code (in the shared framework, or a linked app).

We should make the default constructor public, so the linker knows if it can trim it successfully or not. If no one is calling the constructor, it can be trimmed.

We should rename the ILLinkTrim.xml file to ILLinkTrim_LibraryBuild.xml so it gets used only while building the System.Private.Xml.Linq assembly. While linking/trimming a complete application, the constructor will be preserved if it is needed by the generated code since the generated code uses this pattern:

o.@Element = (global::System.Xml.Linq.XElement)ReadSerializable(( System.Xml.Serialization.IXmlSerializable)System.Activator.CreateInstance(typeof(global::System.Xml.Linq.XElement), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.CreateInstance | System.Reflection.BindingFlags.NonPublic, null, new object[0], null), true

The linker will be able to recognize Activator.CreateInstance(typeof(global::System.Xml.Linq.XElement) and preserve the constructors on XElement.

cc @krwq @mconnew @StephenMolloy

Metadata

Metadata

Assignees

Labels

area-System.Xmllinkable-frameworkIssues associated with delivering a linker friendly frameworkuntriagedNew issue has not been triaged by the area owner

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions