Skip to content

Commit

Permalink
Minimal ILGenerator implementation for new AssemblyBuilder.Save (#92846)
Browse files Browse the repository at this point in the history
* Minimal ILGenerator implementation

* Add more test, disable testing on browser

* Apply feedback, add more test scenarios

* Temporarily count the maxstack depth for each OpCode
  • Loading branch information
buyaa-n authored Oct 3, 2023
1 parent 7ffff3f commit 76b63a3
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,7 @@
<data name="InvalidOperation_NoUnderlyingTypeOnEnum" xml:space="preserve">
<value>Underlying type information on enumeration is not specified.</value>
</data>
<data name="InvalidOperation_ShouldNotHaveMethodBody" xml:space="preserve">
<value>Method body should not exist.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Compile Include="System\Reflection\Emit\EnumBuilderImpl.cs" />
<Compile Include="System\Reflection\Emit\FieldBuilderImpl.cs" />
<Compile Include="System\Reflection\Emit\GenericTypeParameterBuilderImpl.cs" />
<Compile Include="System\Reflection\Emit\ILGeneratorImpl.cs" />
<Compile Include="System\Reflection\Emit\MethodBuilderImpl.cs" />
<Compile Include="System\Reflection\Emit\ModuleBuilderImpl.cs" />
<Compile Include="System\Reflection\Emit\ParameterBuilderImpl.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ internal void Save(Stream stream)
hashAlgorithm: (AssemblyHashAlgorithm)_assemblyName.HashAlgorithm
#pragma warning restore SYSLIB0037
);

_module.WriteCustomAttributes(_customAttributes, assemblyHandle);
// Add module's metadata
_module.AppendMetadata();

var ilBuilder = new BlobBuilder();
MethodBodyStreamEncoder methodBodyEncoder = new MethodBodyStreamEncoder(ilBuilder);
_module.AppendMetadata(methodBodyEncoder);

WritePEImage(stream, ilBuilder);
_previouslySaved = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Reflection.Emit
{
internal sealed class ILGeneratorImpl : ILGenerator
{
private const int DefaultSize = 16;
private readonly MethodBuilder _methodBuilder;
private readonly BlobBuilder _builder;
private readonly InstructionEncoder _il;
private bool _hasDynamicStackAllocation;
private int _maxStackSize;

internal ILGeneratorImpl(MethodBuilder methodBuilder, int size)
{
_methodBuilder = methodBuilder;
// For compat, runtime implementation doesn't throw for negative or zero value.
_builder = new BlobBuilder(Math.Max(size, DefaultSize));
_il = new InstructionEncoder(_builder, new ControlFlowBuilder());
}

internal int GetMaxStackSize() => _maxStackSize;

internal InstructionEncoder Instructions => _il;
internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation;

public override int ILOffset => _il.Offset;

public override void BeginCatchBlock(Type? exceptionType) => throw new NotImplementedException();
public override void BeginExceptFilterBlock() => throw new NotImplementedException();
public override Label BeginExceptionBlock() => throw new NotImplementedException();
public override void BeginFaultBlock() => throw new NotImplementedException();
public override void BeginFinallyBlock() => throw new NotImplementedException();
public override void BeginScope() => throw new NotImplementedException();
public override LocalBuilder DeclareLocal(Type localType, bool pinned) => throw new NotImplementedException();
public override Label DefineLabel() => throw new NotImplementedException();

public override void Emit(OpCode opcode)
{
if (opcode == OpCodes.Localloc)
{
_hasDynamicStackAllocation = true;
}
_il.OpCode((ILOpCode)opcode.Value);

// TODO: for now only count the Opcodes emitted, in order to calculate it correctly we might need to make internal Opcode APIs public
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/Opcode.cs#L48
_maxStackSize++;
}

public override void Emit(OpCode opcode, byte arg)
{
_il.OpCode((ILOpCode)opcode.Value);
_builder.WriteByte(arg);
}

public override void Emit(OpCode opcode, double arg)
{
_il.OpCode((ILOpCode)opcode.Value);
_builder.WriteDouble(arg);
}

public override void Emit(OpCode opcode, float arg)
{
_il.OpCode((ILOpCode)opcode.Value);
_builder.WriteSingle(arg);
}

public override void Emit(OpCode opcode, short arg)
{
_il.OpCode((ILOpCode)opcode.Value);
_builder.WriteInt16(arg);
}

public override void Emit(OpCode opcode, int arg)
{
// Special-case several opcodes that have shorter variants for common values.
if (opcode.Equals(OpCodes.Ldc_I4))
{
if (arg >= -1 && arg <= 8)
{
_il.OpCode(arg switch
{
-1 => ILOpCode.Ldc_i4_m1,
0 => ILOpCode.Ldc_i4_0,
1 => ILOpCode.Ldc_i4_1,
2 => ILOpCode.Ldc_i4_2,
3 => ILOpCode.Ldc_i4_3,
4 => ILOpCode.Ldc_i4_4,
5 => ILOpCode.Ldc_i4_5,
6 => ILOpCode.Ldc_i4_6,
7 => ILOpCode.Ldc_i4_7,
_ => ILOpCode.Ldc_i4_8,
});
return;
}

if (arg >= -128 && arg <= 127)
{
_il.OpCode(ILOpCode.Ldc_i4_s);
_builder.WriteSByte((sbyte)arg) ;
return;
}
}
else if (opcode.Equals(OpCodes.Ldarg))
{
if ((uint)arg <= 3)
{
_il.OpCode(arg switch
{
0 => ILOpCode.Ldarg_0,
1 => ILOpCode.Ldarg_1,
2 => ILOpCode.Ldarg_2,
_ => ILOpCode.Ldarg_3,
});
return;
}

if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Ldarg_s);
_builder.WriteByte((byte)arg);
return;
}

if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
{
_il.OpCode(ILOpCode.Ldarg);
_builder.WriteInt16((short)arg);
return;
}
}
else if (opcode.Equals(OpCodes.Ldarga))
{
if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Ldarga_s);
_builder.WriteByte((byte)arg);
return;
}

if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
{
_il.OpCode(ILOpCode.Ldarga);
_builder.WriteInt16((short)arg);
return;
}
}
else if (opcode.Equals(OpCodes.Starg))
{
if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Starg_s);
_builder.WriteByte((byte)arg);
return;
}

if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
{
_il.OpCode(ILOpCode.Starg);
_builder.WriteInt16((short)arg);
return;
}
}

// For everything else, put the opcode followed by the arg onto the stream of instructions.
_il.OpCode((ILOpCode)opcode.Value);
_builder.WriteInt32(arg);
}

public override void Emit(OpCode opcode, long arg)
{
_il.OpCode((ILOpCode)opcode.Value);
_il.CodeBuilder.WriteInt64(arg);
}

public override void Emit(OpCode opcode, string str)
{
// Puts the opcode onto the IL stream followed by the metadata token
// represented by str.
ModuleBuilder modBuilder = (ModuleBuilder)_methodBuilder.Module;
int tempVal = modBuilder.GetStringMetadataToken(str);
_il.OpCode((ILOpCode)opcode.Value);
_il.Token(tempVal);
}

public override void Emit(OpCode opcode, ConstructorInfo con) => throw new NotImplementedException();
public override void Emit(OpCode opcode, Label label) => throw new NotImplementedException();
public override void Emit(OpCode opcode, Label[] labels) => throw new NotImplementedException();
public override void Emit(OpCode opcode, LocalBuilder local) => throw new NotImplementedException();
public override void Emit(OpCode opcode, SignatureHelper signature) => throw new NotImplementedException();
public override void Emit(OpCode opcode, FieldInfo field) => throw new NotImplementedException();
public override void Emit(OpCode opcode, MethodInfo meth) => throw new NotImplementedException();
public override void Emit(OpCode opcode, Type cls) => throw new NotImplementedException();
public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) => throw new NotImplementedException();
public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => throw new NotImplementedException();
public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) => throw new NotImplementedException();
public override void EndExceptionBlock() => throw new NotImplementedException();
public override void EndScope() => throw new NotImplementedException();
public override void MarkLabel(Label loc) => throw new NotImplementedException();
public override void UsingNamespace(string usingNamespace) => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace System.Reflection.Emit
{
Expand All @@ -20,6 +21,8 @@ internal sealed class MethodBuilderImpl : MethodBuilder
private MethodAttributes _attributes;
private MethodImplAttributes _methodImplFlags;
private GenericTypeParameterBuilderImpl[]? _typeParameters;
private ILGeneratorImpl? _ilGenerator;
private bool _initLocals;

internal DllImportData? _dllImportData;
internal List<CustomAttributeWrapper>? _customAttributes;
Expand All @@ -46,8 +49,11 @@ internal MethodBuilderImpl(string name, MethodAttributes attributes, CallingConv
}

_methodImplFlags = MethodImplAttributes.IL;
_initLocals = true;
}

internal ILGeneratorImpl? ILGeneratorImpl => _ilGenerator;

internal BlobBuilder GetMethodSignatureBlob() => MetadataSignatureHelper.MethodSignatureEncoder(_module,
_parameterTypes, ReturnType, GetSignatureConvention(_callingConventions), GetGenericArguments().Length, !IsStatic);

Expand All @@ -68,7 +74,14 @@ internal static SignatureCallingConvention GetSignatureConvention(CallingConvent

return convention;
}
protected override bool InitLocalsCore { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
protected override bool InitLocalsCore
{
get { ThrowIfGeneric(); return _initLocals; }
set { ThrowIfGeneric(); _initLocals = value; }
}

private void ThrowIfGeneric() { if (IsGenericMethod && !IsGenericMethodDefinition) throw new InvalidOperationException(); }

protected override GenericTypeParameterBuilder[] DefineGenericParametersCore(params string[] names)
{
if (_typeParameters != null)
Expand Down Expand Up @@ -98,7 +111,28 @@ protected override ParameterBuilder DefineParameterCore(int position, ParameterA
return parameter;
}

protected override ILGenerator GetILGeneratorCore(int size) => throw new NotImplementedException();
protected override ILGenerator GetILGeneratorCore(int size)
{
if (IsGenericMethod && !IsGenericMethodDefinition)
{
throw new InvalidOperationException();
}

if ((_methodImplFlags & MethodImplAttributes.CodeTypeMask) != MethodImplAttributes.IL ||
(_methodImplFlags & MethodImplAttributes.Unmanaged) != 0 ||
(_attributes & MethodAttributes.PinvokeImpl) != 0)
{
throw new InvalidOperationException(SR.InvalidOperation_ShouldNotHaveMethodBody);
}

if ((_attributes & MethodAttributes.Abstract) != 0)
{
throw new InvalidOperationException(SR.InvalidOperation_ShouldNotHaveMethodBody);
}

return _ilGenerator ??= new ILGeneratorImpl(this, size);
}

protected override void SetCustomAttributeCore(ConstructorInfo con, ReadOnlySpan<byte> binaryAttribute)
{
// Handle pseudo custom attributes
Expand Down Expand Up @@ -199,8 +233,8 @@ public override object Invoke(object? obj, BindingFlags invokeAttr, Binder? bind
public override bool IsDefined(Type attributeType, bool inherit) => throw new NotSupportedException(SR.NotSupported_DynamicModule);

[RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")]
[RequiresUnreferencedCodeAttribute("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")]
public override MethodInfo MakeGenericMethod(params System.Type[] typeArguments)
[RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")]
public override MethodInfo MakeGenericMethod(params Type[] typeArguments)
=> throw new NotImplementedException();
}
}
Loading

0 comments on commit 76b63a3

Please sign in to comment.