Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Get CoreRT Hello World selfcontained binary under 1 MB #7962

Merged
merged 4 commits into from
Feb 3, 2020
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
2 changes: 2 additions & 0 deletions src/BuildIntegration/Microsoft.NETCore.Native.targets
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ See the LICENSE file in the project root for more information.
<IlcArg Condition="$(Optimize) == 'true'" Include="-O" />
<IlcArg Condition="$(DebugSymbols) == 'true' or ($(DebugType) != 'none' and $(DebugType) != '')" Include="-g" />
<IlcArg Condition="$(IlcGenerateMapFile) == 'true'" Include="--map:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).map.xml" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--dgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).codegen.dgml.xml" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--scandgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).scan.dgml.xml" />
<IlcArg Include="@(RdXmlFile->'--rdxml:%(Identity)')" />
<IlcArg Condition="$(OutputType) == 'Library' and $(NativeLib) != ''" Include="--nativelib" />
<IlcArg Condition="$(ExportsFile) != ''" Include="--exportsfile:$(ExportsFile)" />
Expand Down
86 changes: 34 additions & 52 deletions src/Common/src/System/CommonRuntimeTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,40 @@ namespace System
//
internal static class CommonRuntimeTypes
{
internal static Type Object { get { return s_object; } }
internal static Type ValueType { get { return s_valuetype; } }
internal static Type Type { get { return s_type; } }
internal static Type Attribute { get { return s_attribute; } }
internal static Type String { get { return s_string; } }
internal static Type Array { get { return s_array; } }
internal static Type Enum { get { return s_enum; } }
internal static Type Boolean { get { return s_boolean; } }
internal static Type Char { get { return s_char; } }
internal static Type Byte { get { return s_byte; } }
internal static Type SByte { get { return s_sByte; } }
internal static Type UInt16 { get { return s_uInt16; } }
internal static Type Int16 { get { return s_int16; } }
internal static Type UInt32 { get { return s_uInt32; } }
internal static Type Int32 { get { return s_int32; } }
internal static Type UInt64 { get { return s_uInt64; } }
internal static Type Int64 { get { return s_int64; } }
internal static Type UIntPtr { get { return s_uIntPtr; } }
internal static Type IntPtr { get { return s_intPtr; } }
internal static Type Single { get { return s_single; } }
internal static Type Double { get { return s_double; } }
internal static Type Decimal { get { return s_decimal; } }
internal static Type DateTime { get { return s_datetime; } }
internal static Type Nullable { get { return s_nullable; } }
internal static Type Void { get { return s_void; } }
internal static Type MulticastDelegate { get { return s_multicastDelegate; } }
internal static Type Object { get; } = typeof(object);
internal static Type ValueType { get; } = typeof(ValueType);
internal static Type Type { get; } = typeof(Type);
internal static Type Attribute { get; } = typeof(Attribute);
internal static Type String { get; } = typeof(string);
internal static Type Array { get; } = typeof(Array);
internal static Type Enum { get; } = typeof(Enum);
internal static Type Boolean { get; } = typeof(bool);
internal static Type Char { get; } = typeof(char);
internal static Type Byte { get; } = typeof(byte);
internal static Type SByte { get; } = typeof(sbyte);
internal static Type UInt16 { get; } = typeof(ushort);
internal static Type Int16 { get; } = typeof(short);
internal static Type UInt32 { get; } = typeof(uint);
internal static Type Int32 { get; } = typeof(int);
internal static Type UInt64 { get; } = typeof(ulong);
internal static Type Int64 { get; } = typeof(long);
internal static Type UIntPtr { get; } = typeof(UIntPtr);
internal static Type IntPtr { get; } = typeof(IntPtr);
internal static Type Single { get; } = typeof(float);
internal static Type Double { get; } = typeof(double);
internal static Type Decimal { get { return NotSoCommonTypes.s_decimal; } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to deleted these and convert the few places that use it to use typeof, e.g. if (type == typeof(Decimal)).

Caching these in statics is actually a deoptimization.

The JIT has optimization for operator== on System.Type that kicks in when one of the sides is typeof. It should compile this into a simple vtable compare.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to port the fix for this deoptimization to CoreCLR as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it, the comment at the top of this class only applies for .NET Native?

Copy link
Member Author

@MichalStrehovsky MichalStrehovsky Feb 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually have a hard time finding the places where RyuJIT would be able to optimize the type comparison - most of these are used in deep reflection code where we have a System.Type from who knows where that we want to compare to a well known one, or we want to grab a specific System.Type to return.

I think typeof is still more expensive on CoreRT than on CoreCLR. It's at minimum a hashtable lookup. CoreCLR can do it faster for some reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommonTypes.Decimal is used in a single place in

If you change this if (type == typeof(Decimal)), the RyuJIT should optimize it to a simple cmp instruction (look for CORINFO_INTRINSIC_TypeEQ).

I am not suggestion to get rid of this class completely. I agree that it is not straightforward to get rid of this completely. I am just suggestion to get rid of the ones you are moving to NotSoCommonTypes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, the optimization is not as good as I though. It is not able to get rid of the materializing of the Type object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, the optimization is not as good as I though. It is not able to get rid of the materializing of the Type object.

We could probably come up with a scheme for RyuJIT to be able optimize this.

typeof(X) expands to a set of calls "load native TypeHandle → get RuntimeTypeHandle from native TypeHandle -> Type.GetTypeFromHandle. RyuJIT optimizes this entire sequence into a single CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE helper call that takes the native TypeHandle as an argument.

RyuJIT can then optimize this to a simple compare if the other side of the comparison is a another typeof or someObject.GetType(). But that's not the case here.

If we could add a helper to compare native TypeHandle with an arbitrary System.Type, we could have RyuJIT call that in that case. That would help avoid materializing the System.Type from the native TypeHandle.

I could try to prototype that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dotnet/runtime#31686 is the JIT fix to optimize the pattern and make a lot of CommonRuntimeTypes obsolete.

internal static Type DateTime { get { return NotSoCommonTypes.s_datetime; } }
internal static Type Nullable { get; } = typeof(Nullable<>);
internal static Type Void { get; } = typeof(void);
internal static Type MulticastDelegate { get; } = typeof(MulticastDelegate);

private static Type s_object = typeof(object);
private static Type s_valuetype = typeof(ValueType);
private static Type s_type = typeof(Type);
private static Type s_attribute = typeof(Attribute);
private static Type s_string = typeof(string);
private static Type s_array = typeof(Array);
private static Type s_enum = typeof(Enum);
private static Type s_boolean = typeof(bool);
private static Type s_char = typeof(char);
private static Type s_byte = typeof(byte);
private static Type s_sByte = typeof(sbyte);
private static Type s_uInt16 = typeof(ushort);
private static Type s_int16 = typeof(short);
private static Type s_uInt32 = typeof(uint);
private static Type s_int32 = typeof(int);
private static Type s_uInt64 = typeof(ulong);
private static Type s_int64 = typeof(long);
private static Type s_uIntPtr = typeof(UIntPtr);
private static Type s_intPtr = typeof(IntPtr);
private static Type s_single = typeof(float);
private static Type s_double = typeof(double);
private static Type s_decimal = typeof(decimal);
private static Type s_datetime = typeof(DateTime);
private static Type s_nullable = typeof(Nullable<>);
private static Type s_void = typeof(void);
private static Type s_multicastDelegate = typeof(MulticastDelegate);
// Following types are not so common and their ToString and formatting is particularly heavy.
// Make it less likely that they'll get included in small projects by placing them into
// a separate class constructor.
private static class NotSoCommonTypes
{
internal static Type s_decimal = typeof(decimal);
internal static Type s_datetime = typeof(DateTime);
}
}
}
87 changes: 87 additions & 0 deletions src/ILCompiler.DependencyAnalysisFramework/WhyDgml/Why.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved

MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
using System;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Reflection;

// This is a simple command line tool to inspect DGML files generated by
// ILCompiler.DependencyAnalysisFramework's DgmlWriter class.
//
// It works best if FirstMarkLogStrategy is used. It might hit cycles if full
// marking strategy is used, so better not try that. That's untested.
//
// Given the name of the DGML file and a name of the node in the DGML graph,
// it prints the path from the node to the roots.

class Program
{
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage:");
Console.Write(Assembly.GetExecutingAssembly().ManifestModule.Name);
Console.WriteLine(" dgmlfile.xml node_name");
return;
}

string file = args[0];

var directedGraph = XElement.Load(file);

var idToNode = new Dictionary<int, Node>();
var nameToNode = new Dictionary<string, Node>(StringComparer.Ordinal);
var nodes = directedGraph.Elements().Single(e => e.Name.LocalName == "Nodes");
foreach (var node in nodes.Elements())
{
Debug.Assert(node.Name.LocalName == "Node");
int id = int.Parse(node.Attribute("Id").Value);
string name = node.Attribute("Label").Value;
var n = new Node(name);
idToNode[id] = n;
nameToNode[name] = n;
}

var links = directedGraph.Elements().Single(e => e.Name.LocalName == "Links");
foreach (var link in links.Elements())
{
int source = int.Parse(link.Attribute("Source").Value);
int target = int.Parse(link.Attribute("Target").Value);
string reason = link.Attribute("Reason").Value;
idToNode[target].Edges.Add((idToNode[source], reason));
}

string goal = args[1];
if (!nameToNode.ContainsKey(goal))
Console.WriteLine($"No such node: '{goal}'.");
else
Dump(nameToNode[goal]);
}

static void Dump(Node current, int indent = 0, string reason = "")
{
Console.WriteLine($"{new string(' ', indent)}({reason}) {current.Name}");

foreach (var edge in current.Edges)
{
Dump(edge.Node, indent + 2, edge.Label);
}
}
}

class Node
{
public readonly string Name;
public readonly List<(Node Node, string Label)> Edges;

public Node(string name)
{
Name = name;
Edges = new List<(Node, string)>();
}
}
6 changes: 6 additions & 0 deletions src/ILCompiler.DependencyAnalysisFramework/WhyDgml/Why.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>