Skip to content

Commit

Permalink
Add support for creating type initializers
Browse files Browse the repository at this point in the history
  • Loading branch information
Wazner committed Jul 17, 2015
1 parent 0355200 commit 9ad9ee9
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 18 deletions.
107 changes: 105 additions & 2 deletions Sigil/Emit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ public MethodBuilder CreateMethod(OptimizationOptions optimizationOptions = Opti
/// </summary>
public ConstructorBuilder CreateConstructor(out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (ConstrBuilder == null)
if (ConstrBuilder == null || IsBuildingConstructor)
{
throw new InvalidOperationException("Emit was not created to build a constructor, thus CreateConstructor cannot be called");
}
Expand Down Expand Up @@ -467,6 +467,63 @@ public ConstructorBuilder CreateConstructor(OptimizationOptions optimizationOpti
return CreateConstructor(out ignored, optimizationOptions);
}

/// <summary>
/// Writes the CIL stream out to the ConstructorBuilder used to create this Emit.
///
/// Validation that cannot be run until a method is finished is run, and various instructions
/// are re-written to choose "optimal" forms (Br may become Br_S, for example).
///
/// Once this method is called the Emit may no longer be modified.
///
/// Returns a ConstructorBuilder, which can be used to define overrides or for further inspection.
///
/// `instructions` will be set to a representation of the instructions making up the returned constructor.
/// Note that this string is typically *not* enough to regenerate the constructor, it is available for
/// debugging purposes only. Consumers may find it useful to log the instruction stream in case
/// the returned constructor fails validation (indicative of a bug in Sigil) or
/// behaves unexpectedly (indicative of a logic bug in the consumer code).
/// </summary>
public ConstructorBuilder CreateTypeInitializer(out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (ConstrBuilder == null || !IsBuildingConstructor)
{
throw new InvalidOperationException("Emit was not created to build a type initializer, thus CreateTypeInitializer cannot be called");
}

if (ConstructorBuilt)
{
instructions = null;
return ConstrBuilder;
}

ConstructorBuilt = true;

Seal(optimizationOptions);

var il = ConstrBuilder.GetILGenerator();
instructions = IL.UnBuffer(il);

AutoNamer.Release(this);

return ConstrBuilder;
}

/// <summary>
/// Writes the CIL stream out to the ConstructorBuilder used to create this Emit.
///
/// Validation that cannot be run until a method is finished is run, and various instructions
/// are re-written to choose "optimal" forms (Br may become Br_S, for example).
///
/// Once this method is called the Emit may no longer be modified.
///
/// Returns a ConstructorBuilder, which can be used to define overrides or for further inspection.
/// </summary>
public ConstructorBuilder CreateTypeInitializer(OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
string ignored;
return CreateConstructor(out ignored, optimizationOptions);
}

private static void ValidateNewParameters<CheckDelegateType>()
{
var delType = typeof(CheckDelegateType);
Expand Down Expand Up @@ -693,7 +750,7 @@ public static Emit<DelegateType> BuildInstanceMethod(TypeBuilder type, string na
}

/// <summary>
/// Creates a new Emit, suitable for building a constructo on the given TypeBuilder.
/// Creates a new Emit, suitable for building a constructor on the given TypeBuilder.
///
/// The DelegateType and TypeBuilder must agree on parameter types and parameter counts.
///
Expand Down Expand Up @@ -747,6 +804,52 @@ public static Emit<DelegateType> BuildConstructor(TypeBuilder type, MethodAttrib
return ret;
}

/// <summary>
/// Creates a new Emit, suitable for building a type initializer on the given TypeBuilder.
///
/// The DelegateType and TypeBuilder must agree on parameter types and parameter counts.
///
/// If you intend to use unveriable code, you must set allowUnverifiableCode to true.
///
/// If doVerify is false (default is true) Sigil will *not* throw an exception on invalid IL. This is faster, but the benefits
/// of Sigil are reduced to "a nicer ILGenerator interface".
///
/// If strictBranchValidation is true (default is false) Sigil will enforce "Backward branch constraints" which are *technically* required
/// for valid CIL, but in practice often ignored. The most common case to set this option is if you are generating types to write to disk.
/// </summary>
public static Emit<DelegateType> BuildTypeInitializer(TypeBuilder type, bool allowUnverifiableCode = false, bool doVerify = true, bool strictBranchVerification = false)
{
if (type == null)
{
throw new ArgumentNullException("type");
}

ValidateNewParameters<DelegateType>();

var delType = typeof(DelegateType);

var invoke = delType.GetMethod("Invoke");
var returnType = invoke.ReturnType;
var parameters = invoke.GetParameters();

if (returnType != typeof(void))
{
throw new ArgumentException("DelegateType used must return void");
}

if (parameters.Length > 0)
{
throw new ArgumentException("A type initializer can have no arguments.");
}

var constructorBuilder = type.DefineTypeInitializer();

var ret = new Emit<DelegateType>(CallingConventions.Standard, typeof(void), Type.EmptyTypes, allowUnverifiableCode, doVerify, strictBranchVerification);
ret.ConstrBuilder = constructorBuilder;

return ret;
}

private void RemoveInstruction(int index)
{
IL.Remove(index);
Expand Down
12 changes: 12 additions & 0 deletions Sigil/Impl/NonGenericEmitType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Sigil.Impl
{
internal enum NonGenericEmitType
{
DynamicMethod,
Method,
Constructor,
TypeInitializer
}
}
111 changes: 95 additions & 16 deletions Sigil/NonGeneric/Emit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ public partial class Emit
private MethodBuilder CreatedMethod;
private ConstructorBuilder CreatedConstructor;

private bool IsDynamicMethod;
private bool IsMethod;
private bool IsConstructor;
private NonGenericEmitType EmitType;

private TypeBuilder TypeBuilder;
private MethodAttributes Attributes;
Expand Down Expand Up @@ -60,14 +58,12 @@ public partial class Emit
/// </summary>
public bool AllowsUnverifiableCIL { get { return InnerEmit.AllowsUnverifiableCIL; } }

private Emit(Emit<NonGenericPlaceholderDelegate> innerEmit, bool isDynamicMethod, bool isMethod, bool isConstructor)
private Emit(Emit<NonGenericPlaceholderDelegate> innerEmit, NonGenericEmitType type)
{
InnerEmit = innerEmit;
IsDynamicMethod = isDynamicMethod;
IsMethod = isMethod;
IsConstructor = isConstructor;
EmitType = type;

innerEmit.IsBuildingConstructor = isConstructor;
innerEmit.IsBuildingConstructor = type == NonGenericEmitType.Constructor;

Shorthand = new EmitShorthand(this);
}
Expand Down Expand Up @@ -115,7 +111,7 @@ public static Emit NewDynamicMethod(Type returnType, Type[] parameterTypes, stri

var innerEmit = Emit<NonGenericPlaceholderDelegate>.MakeNonGenericEmit(CallingConventions.Standard, returnType, parameterTypes, Emit<NonGenericPlaceholderDelegate>.AllowsUnverifiableCode(module), doVerify, strictBranchVerification);

var ret = new Emit(innerEmit, isDynamicMethod: true, isMethod: false, isConstructor: false);
var ret = new Emit(innerEmit, NonGenericEmitType.DynamicMethod);
ret.Module = module;
ret.Name = name ?? AutoNamer.Next("_DynamicMethod");
ret.ReturnType = returnType;
Expand Down Expand Up @@ -183,7 +179,7 @@ private void ValidateDelegateType(Type delegateType)
/// </summary>
public Delegate CreateDelegate(Type delegateType, out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (!IsDynamicMethod)
if (EmitType != NonGenericEmitType.DynamicMethod)
{
throw new InvalidOperationException("Emit was not created to build a DynamicMethod, thus CreateDelegate cannot be called");
}
Expand Down Expand Up @@ -301,8 +297,8 @@ public static Emit BuildMethod(Type returnType, Type[] parameterTypes, TypeBuild
}

var innerEmit = Emit<NonGenericPlaceholderDelegate>.MakeNonGenericEmit(callingConvention, returnType, parameterTypes, allowUnverifiableCode, doVerify, strictBranchVerification);
var ret = new Emit(innerEmit, isDynamicMethod: false, isMethod: true, isConstructor: false);

var ret = new Emit(innerEmit, NonGenericEmitType.Method);
ret.Name = name;
ret.ReturnType = returnType;
ret.ParameterTypes = passedParameterTypes;
Expand Down Expand Up @@ -351,7 +347,7 @@ public static Emit BuildStaticMethod(Type returnType, Type[] parameterTypes, Typ
/// </summary>
public MethodBuilder CreateMethod(out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (!IsMethod)
if (EmitType != NonGenericEmitType.Method)
{
throw new InvalidOperationException("Emit was not created to build a method, thus CreateMethod cannot be called");
}
Expand Down Expand Up @@ -388,7 +384,7 @@ public MethodBuilder CreateMethod(OptimizationOptions optimizationOptions = Opti
}

/// <summary>
/// Creates a new Emit, suitable for building a constructo on the given TypeBuilder.
/// Creates a new Emit, suitable for building a constructor on the given TypeBuilder.
///
/// The DelegateType and TypeBuilder must agree on parameter types and parameter counts.
///
Expand Down Expand Up @@ -427,7 +423,7 @@ public static Emit BuildConstructor(Type[] parameterTypes, TypeBuilder type, Met
var innerEmit = Emit<NonGenericPlaceholderDelegate>.MakeNonGenericEmit(callingConvention, typeof(void), parameterTypes, allowUnverifiableCode, doVerify, strictBranchVerification);
innerEmit.ConstructorDefinedInType = type;

var ret = new Emit(innerEmit, isDynamicMethod: false, isMethod: false, isConstructor: true);
var ret = new Emit(innerEmit, NonGenericEmitType.Constructor);
ret.ReturnType = type;
ret.ParameterTypes = passedParameters;
ret.Attributes = attributes;
Expand All @@ -437,6 +433,37 @@ public static Emit BuildConstructor(Type[] parameterTypes, TypeBuilder type, Met
return ret;
}

/// <summary>
/// Creates a new Emit, suitable for building a type initializer on the given TypeBuilder.
///
/// If you intend to use unveriable code, you must set allowUnverifiableCode to true.
///
/// If doVerify is false (default is true) Sigil will *not* throw an exception on invalid IL. This is faster, but the benefits
/// of Sigil are reduced to "a nicer ILGenerator interface".
///
/// If strictBranchValidation is true (default is false) Sigil will enforce "Backward branch constraints" which are *technically* required
/// for valid CIL, but in practice often ignored. The most common case to set this option is if you are generating types to write to disk.
/// </summary>
public static Emit BuildTypeInitializer(TypeBuilder type, bool allowUnverifiableCode = false, bool doVerify = true, bool strictBranchVerification = false)
{
if (type == null)
{
throw new ArgumentNullException("type");
}

var innerEmit = Emit<NonGenericPlaceholderDelegate>.MakeNonGenericEmit(CallingConventions.Standard, typeof(void), Type.EmptyTypes, allowUnverifiableCode, doVerify, strictBranchVerification);
innerEmit.ConstructorDefinedInType = type;

var ret = new Emit(innerEmit, NonGenericEmitType.TypeInitializer);
ret.ReturnType = type;
ret.ParameterTypes = Type.EmptyTypes;
ret.Attributes = 0;
ret.CallingConvention = CallingConventions.Standard;
ret.TypeBuilder = type;

return ret;
}

/// <summary>
/// Writes the CIL stream out to the ConstructorBuilder used to create this Emit.
///
Expand All @@ -455,7 +482,7 @@ public static Emit BuildConstructor(Type[] parameterTypes, TypeBuilder type, Met
/// </summary>
public ConstructorBuilder CreateConstructor(out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (!IsConstructor)
if (EmitType != NonGenericEmitType.Constructor)
{
throw new InvalidOperationException("Emit was not created to build a constructor, thus CreateConstructor cannot be called");
}
Expand Down Expand Up @@ -491,6 +518,58 @@ public ConstructorBuilder CreateConstructor(OptimizationOptions optimizationOpti
return CreateConstructor(out ignored, optimizationOptions);
}

/// <summary>
/// Writes the CIL stream out to the ConstructorBuilder used to create this Emit.
///
/// Validation that cannot be run until a method is finished is run, and various instructions
/// are re-written to choose "optimal" forms (Br may become Br_S, for example).
///
/// Once this method is called the Emit may no longer be modified.
///
/// Returns a ConstructorBuilder, which can be used to define overrides or for further inspection.
///
/// `instructions` will be set to a representation of the instructions making up the returned constructor.
/// Note that this string is typically *not* enough to regenerate the constructor, it is available for
/// debugging purposes only. Consumers may find it useful to log the instruction stream in case
/// the returned constructor fails validation (indicative of a bug in Sigil) or
/// behaves unexpectedly (indicative of a logic bug in the consumer code).
/// </summary>
public ConstructorBuilder CreateTypeInitializer(out string instructions, OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
if (EmitType != NonGenericEmitType.TypeInitializer) {
throw new InvalidOperationException("Emit was not created to build a type initializer, thus CreateTypeInitializer cannot be called");
}

if (CreatedConstructor != null) {
instructions = null;
return CreatedConstructor;
}

var constructorBuilder = TypeBuilder.DefineTypeInitializer();

InnerEmit.ConstrBuilder = constructorBuilder;

CreatedConstructor = InnerEmit.CreateConstructor(out instructions, optimizationOptions);

return CreatedConstructor;
}

/// <summary>
/// Writes the CIL stream out to the ConstructorBuilder used to create this Emit.
///
/// Validation that cannot be run until a method is finished is run, and various instructions
/// are re-written to choose "optimal" forms (Br may become Br_S, for example).
///
/// Once this method is called the Emit may no longer be modified.
///
/// Returns a ConstructorBuilder, which can be used to define overrides or for further inspection.
/// </summary>
public ConstructorBuilder CreateTypeInitializer(OptimizationOptions optimizationOptions = OptimizationOptions.All)
{
string ignored;
return CreateTypeInitializer(out ignored, optimizationOptions);
}

/// <summary>
/// Returns a string representation of the CIL opcodes written to this Emit to date.
///
Expand Down
1 change: 1 addition & 0 deletions Sigil/Sigil.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
<Compile Include="Emit.TryCatchFinally.cs">
<DependentUpon>Emit.cs</DependentUpon>
</Compile>
<Compile Include="Impl\NonGenericEmitType.cs" />
<Compile Include="Impl\RollingVerifier.NoVerification.cs">
<DependentUpon>RollingVerifier.cs</DependentUpon>
</Compile>
Expand Down
4 changes: 4 additions & 0 deletions SigilTests/SigilTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@
</Compile>
<Compile Include="NoVerification.cs" />
<Compile Include="Tail.cs" />
<Compile Include="TypeInitializer.cs" />
<Compile Include="TypeInitializer.NonGeneric.cs">
<DependentUpon>TypeInitializer.cs</DependentUpon>
</Compile>
<Compile Include="WriteLine.NonGeneric.cs">
<DependentUpon>WriteLine.cs</DependentUpon>
</Compile>
Expand Down
37 changes: 37 additions & 0 deletions SigilTests/TypeInitializer.NonGeneric.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sigil.NonGeneric;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace SigilTests
{
public partial class TypeInitializer
{
[TestMethod]
public void SimpleNonGeneric()
{
var asm = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Foo"), AssemblyBuilderAccess.Run);
var mod = asm.DefineDynamicModule("Bar");
var t = mod.DefineType("T");

var foo = t.DefineField("Foo", typeof(int), FieldAttributes.Public | FieldAttributes.Static);

var c = Emit.BuildTypeInitializer(t);
c.LoadConstant(123);
c.StoreField(foo);
c.Return();

c.CreateTypeInitializer();

var type = t.CreateType();

var fooGet = type.GetField("Foo");

Assert.AreEqual(123, (int)fooGet.GetValue(null));
}
}
}
Loading

0 comments on commit 9ad9ee9

Please sign in to comment.