We believe we need to add reflection emit to .NET Standard.
We already have features in .NET Standard that imply support for dynamic code generation, for example Assembly.Load(byte[]), RegexOptions.Compiled, and LambdaExpression.Compile().
Today, developers have no way to test whether runtime code generation is actually supported -- the runtime might throw PlatformNotSupported. Furthermore, even if it is supported, it might be slower as the runtime is interpreting the code (for example, by walking the expressions trees or interpreting the IL). While that ensures that scenarios that depend on code generation just work, it makes it hard to support scenarios that use code generation as a performance optimization. In those cases, not using code generation is usually faster.
Hence, we need an API that allows developers to check for the support of code generation as well as an API that allows developers to check using runtime code generation as a performance optimization is sensible.
```C#
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
public static bool IsDynamicCodeSupported { get; }
public static bool IsDynamicCodeCompiled { get; }
}
}
The semantics are:
* If the runtime supports loading and running dynamic code, `IsDynamicCodeSupported` returns `true`. This doesn't tell developers whether the code is interpreted or compiled.
* If the runtime supports loading and running dynamic code by using a just-in-time compiler, `IsDynamicCodeCompiled` returns `true`.
* `RuntimeFeature.IsSupported(string)` also returns the appropriate value when called with the strings `"IsDynamicCodeSupported"` and `"IsDynamicCodeCompiled"`.
## Affected features
* Loading assemblies from bytes (`Assembly.Load(byte[])`)
* Linq expression trees
* Reflection emit
* `DynamicMethod` (lightweight code generation, LCG)
* DispatchProxy
* Regex
## Scenarios
### Failing early with `RuntimeFeature.IsDynamicCodeSupported`
In some scenarios, performance isn't the primary goal but use to get stuff to work, for example, mocking. A mocking framework might provide an API like `Proxy.Create<TInterface>()` that returns you a type that implements `TInterface`. In this case, performance isn't the higher order and the only thing that matters is that the runtime supports generating code on the fly. Some environments, for example, iOS, disallow actual JITs. For that reason, the Xamarin team is considering providing an IL interpreter to make the scenarios work.
The author of the mocking framework could just use reflection emit and rely on the implementation of it to throw `PlatformNotSupported` but a much better developer experience would be to check early & fail early with a descriptive error message. For example:
```C#
public static class Proxy
{
public static Proxy<TInterface> Create<TInterface>()
{
if (!RuntimeFeature.IsDynamicCodeSupported)
throw new PlatformNotSupportedException("We cannot create proxies on your runtime because it doesn't allow dynamic code generation.");
return UseReflectionEmitToCreateImplementation();
}
}
RuntimeFeature.IsDynamicCodeCompiledIn most cases, dynamically generating code is meant as a performance optimization. This
```C#
var options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
// We only want to use compiled regexes if the underlying runtime
// supports compiling IL with a JIT. Otherwise it's likely much slower
// than walking the state machine.
if (RuntimeFeature.IsDynamicCodeCompiled)
{
options |= RegexOptions.Compiled;
}
```
Similar areas include using expression trees to accelerate setting properties and calling constructors in IoC systems.
We have duplicate issues of this:
https://github.com/dotnet/corefx/issues/13535
https://github.com/dotnet/corefx/issues/25530
The preferred design for API availability checks in earlier discussions was to just add static IsSupported property to the relevant API, like AssemblyBuilder.IsSupported, DynamicMethod.IsSupported or Registry.IsSupported. This pattern scalles well to 3rd party APIs.
RuntimeFeature.HasJit makes sense to me.
The IsSupported pattern would make this proposal look like this (with an IsSupported method for all entrypoint classes for reflection emit). What do folks think?
namespace System.Runtime.CompilerServices
{
public class RuntimeFeature
{
public static bool HasJit { get; }
}
}
namespace System.Reflection.Emit
{
public class AssemblyBuilder
{
public static bool IsSupported { get; }
}
public class DynamicMethod
{
public static bool IsSupported { get; }
}
public class CustomAttributeBuilder
{
public static bool IsSupported { get; }
}
}
LGTM.
I am not sure whether we need CustomAttributeBuilder.IsSupported. Can CustomAttributeBuilder be ever meaningfully used without AssemblyBuilder?
We could probably do without CustomAttributeBuilder.IsSupported. On closer inspection, the only thing it does by itself is build a binary representation of the custom attribute. While it's not in CoreRT right now, it could be as long as we drop the internal methods used by other parts of reflection emit.
After a bit of digging, we could potentially need more IsSupported APIs to cover downstream usage refemit. For example, Regex and System.Composition have APIs that depend on it. Would we want some sort of support check on those as well?
CC @davidfowl, who could hopefully use this in ASP.NET.
Yes, I think having a property like bool Regex.IsCompiledSupported would make sense.
We should walk through the flow chart on what this would look like for our DI container. Today we have a couple of different modes (https://github.com/aspnet/DependencyInjection/blob/943ffa1187399ad4c2af0d23641af9804eacd4e6/src/DI/ServiceProviderMode.cs#L6):
Here's what I'm thinking the flow control would be:
// Set the default mode to runtime if there's no JIT
https://github.com/aspnet/DependencyInjection/blob/06e2de235ce5b27b425e823d9dcbd045811ba48e/src/DI/ServiceProviderOptions.cs#L21
```C#
Mode = RuntimeFeature.HasJit ? ServiceProviderMode.Dynamic : ServiceProviderMode.Runtime;
https://github.com/aspnet/DependencyInjection/blob/06e2de235ce5b27b425e823d9dcbd045811ba48e/src/DI/ServiceLookup/ServiceProviderEngine.cs#L23
// This name should be CompiledBuilder
ExpressionBuilder = DynamicMethod.IsSupported ? new ILEmitResolver(...) : new CallSiteExpressionBuilder(...);
```
/cc @pakrym for DI
/cc @JamesNK (he originally asked for this) for JSON.NET
@davidfowl, now that I think about it a bit more, is DynamicMethod.IsSupported useful or should we only have RuntimeFeatures.HasJit? In other words if refemit is interpreted, would you ever want to use it?
@davidfowl, now that I think about it a bit more, is DynamicMethod.IsSupported useful or should we only have RuntimeFeatures.HasJit? In other words if refemit is interpreted, would you ever want to use it?
No we'd never want to use it if it was interpreted in this scenario unless it was faster than reflection.
I want a way to figure out the fastest way to do reflection.
Compiled expressions are faster than traditional MethodInfo.Invoke reflection
But MethodInfo.Invoke reflection is faster that interpreted expressions.
A runtime way to figure out which to choose out of the two - basically are expressions going to be compiled or interpreted - is what I want.
Ok, let's just go with RuntimeFeatures.HasJit until/unless we see a useful scenario for using RefEmit with no JIT.
I incorrectly thought RuntimeFeatures was public. Would System.Runtime.InteropServices.RuntimeInformation be the right public class @stephentoub?
@morganbr System.Runtime.CompilerServices.RuntimeFeature is public: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimefeature?view=netcore-2.0
Ah, right you are. I just didn't find it in the contract sources due to magic in how System.Runtime is built. In that case, I'll go ahead and mark this ready for review.
A few thoughts:
RuntimeFeature is mostly for static discovery of runtime featuresDynamicMethod.IsSupported and AssemblyBuilder.IsSupported.This needs a broader discussion. I'll invite @morganbr for the next one.
@terrajobst , the point of HasJit is that some features, like compiled LINQ expressions may work on AoT, but they're slow. That might eventually be true of DynamicMethod/Assemblybuilder as well. In those cases, IsSupported would be true, but a library like ASP.NET or Json.NET would want to find out whether they make code faster than reflection (because there's a JIT) or slower (because they're interpreted).
I'm open to putting HasJit on a different class.
This is a dynamic feature discovery. It seems this should be DynamicMethod.IsSupported and AssemblyBuilder.IsSupported.
I wanted to use a flag like this to test whether System.Linq.Expressions would get compiled or interpreted after calling Compile(). This flag isn't just specific to DynamicMethod so I don't think it should be placed on DynamicMethod..
We still have to figure out how either of these two can be used in the context of .NET Standard
The proposed API is for inclusion in the .NET Core platform. If somebody wants to build a package with API that does best effort approximation of this API for existing .NET Standard 2.0 platforms, that's fine but it should be different assembly and package name that does not unify with the .NET Core platform one.
I'd rather have HasJit in .NET Standard, particularly since it's relevant in UAP. AssemblyBuilder/DynamicMethod are different and not part of this proposal.
If we agree on a reasonable API, I am sure it will be included in .NET Standard 3.0. .NET Standard 2.0 is done. There is no way to add new APIs to it.
Agreed. We'll just need to bring it to UAP as well as .NET Core (and perhaps some Xamarin flavors too).
So is the api ready for review?
This is related: https://github.com/dotnet/standard/issues/832
I believe this feature needs a more comprehensive design. I'll follow-up.
I've updated the proposal based on our meeting notes. I hope @morganbr doesn't mind it shows up as his :-)
Heh, the update is fine (though I don't think there's actually a penalty for using compiled regex, it just uses the state machine).
The existing design of the class is using string constants, is there any reason we are deviating from that and switching to bool properties instead?
The string constants are meant for static discovery of runtime features. I believe these capabilities are inherently dynamic, so making them string constants feels wrong (note that the presence of the string constants presupposes the runtime must return true from IsSupported(string)).
Are we going to backport the implementation of RuntimeFeature.IsSupported to 2.1/2.2?
If we don't, any app that's targeting netcoreapp lower than 3.0 and using RuntimeFeature.IsSupported("IsDynamicCodeCompiled") would be told that compilation is not supported.
Hi @terrajobst,
Can I use RuntimeFeature to detect whether DispatchProxy is supported on the current platform?
Thanks and happy holidays
Elad
Anyone else who knows the answer is also welcome to reply :)
@darkl We are not monitoring old closed issues, and your question is somewhat unrelated to what this issue about. If you would like to get answer to your question, open a new question issue in https://github.com/dotnet/runtime
Most helpful comment
I wanted to use a flag like this to test whether
System.Linq.Expressionswould get compiled or interpreted after callingCompile(). This flag isn't just specific toDynamicMethodso I don't think it should be placed onDynamicMethod..