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
Today, we have an internal XElement constructor that isn't invoked directly:
runtime/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs
Lines 134 to 137 in 1d70b31
However, we need to tell the IL Linker that it needs to preserve this constructor:
runtime/src/libraries/System.Private.Xml.Linq/src/ILLinkTrim.xml
Lines 3 to 5 in 1d70b31
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:
runtime/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs
Lines 2394 to 2418 in 1d70b31
The only place this constructor is truly necessary is when XmlSerializer generates C# code.
runtime/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs
Lines 1939 to 1965 in 1d70b31
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 caseXElement(just like we did for the IL generation case), and have it generatenew XElement("default")instead of callingActivator.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.xmlfile toILLinkTrim_LibraryBuild.xmlso it gets used only while building theSystem.Private.Xml.Linqassembly. 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: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