Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal ILGenerator implementation for new AssemblyBuilder.Save #92846

Merged
merged 5 commits into from
Oct 3, 2023
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
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));
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
_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)
Copy link
Member

Choose a reason for hiding this comment

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

The CoreLib implementation also has a check for DllImport here. Is it intentionally omitted?

Copy link
Member Author

@buyaa-n buyaa-n Oct 2, 2023

Choose a reason for hiding this comment

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

We are setting the _attributes with MethodAttributes.PinvokeImpl when DllImportAttribute is set, no need to keep another boolean flag for this

case "System.Runtime.InteropServices.DllImportAttribute":
{
_dllImportData = DllImportData.CreateDllImportData(CustomAttributeInfo.DecodeCustomAttribute(con, binaryAttribute), out var preserveSig);
_attributes |= MethodAttributes.PinvokeImpl;

{
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