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

ILEmit backend for DependencyInjeciton #630

Merged
merged 10 commits into from
Mar 19, 2018
Merged

Conversation

pakrym
Copy link
Contributor

@pakrym pakrym commented Mar 14, 2018

No description provided.

@rynowak
Copy link
Member

rynowak commented Mar 14, 2018

I see Emit and I upvote ❤️ 🔥 🍺

src/DI/DI.csproj Outdated
@@ -17,6 +17,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.TypeNameHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsTypeNameHelperSourcesPackageVersion)" />
<PackageReference Include="System.Reflection.Emit" PrivateAssets="All" Version="$(SystemReflectionEmitPackageVersion)" />
Copy link
Member

Choose a reason for hiding this comment

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

Private assets?

@@ -16,5 +16,6 @@ public ConstantCallSite(Type serviceType, object defaultValue)

public Type ServiceType => DefaultValue.GetType();
public Type ImplementationType => DefaultValue.GetType();
public CallSiteKind Kind { get; } = CallSiteKind.Constant;
Copy link
Member

@davidfowl davidfowl Mar 14, 2018

Choose a reason for hiding this comment

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

public CallSiteKind Kind => CallSiteKind.Constant; Why do you want a backing field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So JIT can do it's JITty stuff

@pakrym
Copy link
Contributor Author

pakrym commented Mar 14, 2018

Before/After BuildServiceProvider with Scoped->Scoped->Scoped tree

25% faster, ~3x less allocations in count and size

image

image

src/DI/DI.csproj Outdated
@@ -17,6 +17,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.TypeNameHelper.Sources" PrivateAssets="All" Version="$(MicrosoftExtensionsTypeNameHelperSourcesPackageVersion)" />
<PackageReference Include="System.Reflection.Emit" PrivateAssets="All" Version="$(SystemReflectionEmitPackageVersion)" />
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend making these package references conditional on TargetFramework. System.Ref.Emit is not supported in all netstandard2.0-compatible platforms.

Copy link
Member

Choose a reason for hiding this comment

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

Yah, what we actually really need is this https://github.com/dotnet/corefx/issues/25944

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this is temporary.

if (ReferenceEquals(scope, rootScope)
&& _scopedServices.TryGetValue(serviceType, out scopedService))
&& _scopedServices.TryGetValue(serviceType, out var scopedService))
Copy link
Member

Choose a reason for hiding this comment

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

PranavPoints++

{
VisitCallSite(parameterCallSite, argument);
}
argument.Generator.Emit(OpCodes.Newobj, constructorCallSite.ConstructorInfo);
Copy link
Member

Choose a reason for hiding this comment

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

Do we support value types?

Copy link
Member

Choose a reason for hiding this comment

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

Has anyone ever put a struct in the container? Now I'm curious...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm sure someone did and we have to support it.

Copy link
Member

Choose a reason for hiding this comment

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

Make sure we add tests for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It didn't work in expression either, I tried adding tests and they fail.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah but the IL for newing up a struct is different soooo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nono, I'm sure they would fail in my code. They are failing in old Expression generating code too.

@jawn
Copy link

jawn commented Mar 15, 2018

Typo alert in PR title:
DependencyInjeciton >
DependencyInjection

@@ -12,5 +12,7 @@ internal interface IServiceCallSite
{
Type ServiceType { get; }
Type ImplementationType { get; }
CallSiteKind Kind { get; }

Copy link
Member

Choose a reason for hiding this comment

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

nit: space

@@ -0,0 +1,15 @@
using System.Collections.Generic;
Copy link
Member

Choose a reason for hiding this comment

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

Add copyright everywhere

CallSiteFactory = new CallSiteFactory(serviceDescriptors);
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());

RealizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>(new ReferenceEqualsEqualityComparer<Type>());
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment about ReferenceEqualsEqualityComparer

@@ -18,9 +18,10 @@ internal class ServiceProviderEngineScope : IServiceScope, IServiceProvider
public ServiceProviderEngineScope(ServiceProviderEngine engine)
{
Engine = engine;
ResolvedServices = new Dictionary<object, object>();
Copy link
Member

Choose a reason for hiding this comment

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

Undo


protected override ILEmitCallSiteAnalysisResult VisitIEnumerable(IEnumerableCallSite enumerableCallSite, object argument)
{
var result = new ILEmitCallSiteAnalysisResult(6);
Copy link
Member

Choose a reason for hiding this comment

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

What is 6?


protected override ILEmitCallSiteAnalysisResult VisitSingleton(SingletonCallSite singletonCallSite, object argument) => VisitCallSite(singletonCallSite.ServiceCallSite, argument);

protected override ILEmitCallSiteAnalysisResult VisitScoped(ScopedCallSite scopedCallSite, object argument) => new ILEmitCallSiteAnalysisResult(64, true).Add(VisitCallSite(scopedCallSite.ServiceCallSite, argument));
Copy link
Member

@davidfowl davidfowl Mar 15, 2018

Choose a reason for hiding this comment

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

64, true?


private static bool BeginCaptureDisposable(Type implType, ILEmitResolverBuilderContext argument)
{
var shouldCapture = !(implType != null && !typeof(IDisposable).GetTypeInfo().IsAssignableFrom(implType.GetTypeInfo()));
Copy link
Member

Choose a reason for hiding this comment

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

😒

@davidfowl
Copy link
Member

@jawn thanks 😄

@davidfowl davidfowl closed this Mar 15, 2018
@davidfowl davidfowl reopened this Mar 15, 2018
@davidfowl davidfowl changed the title [WIP] ILEmit backend for DependencyInjeciton ILEmit backend for DependencyInjeciton Mar 15, 2018
@davidfowl davidfowl changed the title ILEmit backend for DependencyInjeciton ILEmit backend for DependancyInjeciton Mar 15, 2018
@davidfowl davidfowl changed the title ILEmit backend for DependancyInjeciton ILEmit backend for DependancyInjeciton lenght Mar 15, 2018
@davidfowl davidfowl changed the title ILEmit backend for DependancyInjeciton lenght ILEmit backend for DependencyInjeciton Mar 15, 2018
@@ -17,5 +17,7 @@ public FactoryCallSite(Type serviceType, Func<IServiceProvider, object> factory)

public Type ServiceType { get; }
public Type ImplementationType => null;

Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove space


namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal struct ILEmitCallSiteAnalysisResult
Copy link
Member

Choose a reason for hiding this comment

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

Make this read only since Add returns a new struct?


public bool HasScope;

public ILEmitCallSiteAnalysisResult Add(ILEmitCallSiteAnalysisResult other)
Copy link
Member

Choose a reason for hiding this comment

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

Pass via in?


namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
internal sealed class ILEmitCallSiteAnalyzer : CallSiteVisitor<object, ILEmitCallSiteAnalysisResult>
Copy link
Member

Choose a reason for hiding this comment

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

Write a general comment about why this exists.

{
protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) =>
collection.BuildServiceProvider(new ServiceProviderOptions { Mode = ServiceProviderMode.Compiled });
collection.BuildServiceProvider(new ServiceProviderOptions() { Mode = ServiceProviderMode.ILEmit});
Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove the ()

@@ -5,9 +5,9 @@

namespace Microsoft.Extensions.DependencyInjection.Tests
{
public class ServiceProviderCompiledContainerTests : ServiceProviderContainerTests
public class ServiceProviderILEmitContainerTests: ServiceProviderContainerTests
Copy link
Member

Choose a reason for hiding this comment

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

nit: fix formatting


private Func<ServiceProviderEngineScope, object> BuildType(IServiceCallSite callSite)
{
var dynamicMethod = new DynamicMethod("ResolveService", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(object), new [] {typeof(ILEmitResolverBuilderRuntimeContext), typeof(ServiceProviderEngineScope) }, GetType(), true);
Copy link
Member

Choose a reason for hiding this comment

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

This line is too long. Comment on why you had to pass true as the last argument and use the named parameter syntax.

return null;
}


Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove extra space

@pakrym pakrym force-pushed the pakrym/il-emit-backend branch from 10f7b69 to c0ccdd2 Compare March 16, 2018 22:15

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
// This class walkes the service scope tree and tries to calculate approximate
Copy link
Member

Choose a reason for hiding this comment

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

walks.....

{
VisitCallSite(parameterCallSite, argument);
}
argument.Generator.Emit(OpCodes.Newobj, constructorCallSite.ConstructorInfo);
Copy link
Member

@davidfowl davidfowl Mar 16, 2018

Choose a reason for hiding this comment

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

Add the comment // new T(...)

@pakrym pakrym force-pushed the pakrym/il-emit-backend branch 2 times, most recently from fdb06da to c1d0447 Compare March 16, 2018 22:38

private Func<ServiceProviderEngineScope, object> BuildType(IServiceCallSite callSite)
{
// We need to skit visibility checks because services/constructors might be private
Copy link
Member

Choose a reason for hiding this comment

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

nit: skit


context.Generator.BeginExceptionBlock();

// scope
Copy link
Member

Choose a reason for hiding this comment

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

Fix the codegen comment here


private static void EndCaptureDisposable(ILEmitResolverBuilderContext argument)
{
argument.Generator.Emit(OpCodes.Callvirt, ExpressionResolverBuilder.CaptureDisposableMethodInfo);
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment here

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

LGTM just the nits

@davidfowl
Copy link
Member

@pakrym why is the built red?

@Zoxive
Copy link

Zoxive commented Mar 17, 2018

I see the new test does 350 dependencies deep. Is this the new "limit"? (I see there are test files still included for 999) I'll grab this branch and try it out tomorrow with my breaking project from dotnet/aspnetcore#2737

@Zoxive
Copy link

Zoxive commented Mar 17, 2018

@pakrym Works great. Also as @davidfowl mentioned about the build failing i had to add readonly to the struct properties to get it to compile locally as well.

C:\projects\dependencyinjection\src\DI\ServiceLookup\ILEmit\ILEmitCallSiteAnalysisResult.cs(19,20): error CS8340: Instance fields of readonly structs must be readonly. [C:\projects\dependencyinjection\src\DI\DI.csproj]

@pakrym pakrym force-pushed the pakrym/il-emit-backend branch from 8198538 to 7a34593 Compare March 19, 2018 16:02
@pakrym pakrym changed the base branch from dev to release/2.1 March 19, 2018 16:08
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants