Skip to content

Add initial MaxStack calculation #93244

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

Merged
merged 12 commits into from
Oct 25, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ internal void InternalEmit(OpCode opcode)
m_ILStream[m_length++] = (byte)opcodeValue;
}

UpdateStackSize(opcode, opcode.StackChange());
UpdateStackSize(opcode, opcode.EvaluationStackDelta);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ internal OpCode(OpCodeValues value, int flags)
internal bool EndsUncondJmpBlk() =>
(m_flags & EndsUncondJmpBlkFlag) != 0;

internal int StackChange() =>
/// <summary>
/// The value of how the IL instruction changes the evaluation stack.
/// </summary>
/// <remarks>
/// The difference between how many elements are popped from the stack and how many are pushed onto the stack as a result of the IL instruction.
/// For some IL instructions like <see cref="OpCodes.Call"/> stack change is not fixed and depends on the called reference signature.
/// For such <see cref="OpCodes"/> the <see cref="OpCode.EvaluationStackDelta"/> returns 0. In this case you should not rely on
/// <see cref="OpCode.EvaluationStackDelta"/> for calculating stack size and/or max stack, instead need to evaluate the reference signature.
/// For example, in case the instruction is calling a method reference, need to evaluate the method signature,
/// the push count depends on the returning value, the pop count depends on how many parameters passed.
/// </remarks>
public int EvaluationStackDelta =>
m_flags >> StackChangeShift;

public OperandType OperandType => (OperandType)(m_flags & OperandTypeMask);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// 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
Expand All @@ -17,6 +15,7 @@ internal sealed class ILGeneratorImpl : ILGenerator
private readonly InstructionEncoder _il;
private bool _hasDynamicStackAllocation;
private int _maxStackSize;
private int _currentStack;

internal ILGeneratorImpl(MethodBuilder methodBuilder, int size)
{
Expand All @@ -42,40 +41,46 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size)
public override LocalBuilder DeclareLocal(Type localType, bool pinned) => throw new NotImplementedException();
public override Label DefineLabel() => throw new NotImplementedException();

public override void Emit(OpCode opcode)
private void UpdateStackSize(OpCode opCode)
{
_currentStack += opCode.EvaluationStackDelta;
_maxStackSize = Math.Max(_maxStackSize, _currentStack);
}

public void EmitOpcode(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++;
_il.OpCode((ILOpCode)opcode.Value);
UpdateStackSize(opcode);
}

public override void Emit(OpCode opcode) => EmitOpcode(opcode);

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

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

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

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

Expand All @@ -86,98 +91,91 @@ public override void Emit(OpCode opcode, int arg)
{
if (arg >= -1 && arg <= 8)
{
_il.OpCode(arg switch
EmitOpcode(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,
-1 => OpCodes.Ldc_I4_M1,
0 => OpCodes.Ldc_I4_0,
1 => OpCodes.Ldc_I4_1,
2 => OpCodes.Ldc_I4_2,
3 => OpCodes.Ldc_I4_3,
4 => OpCodes.Ldc_I4_4,
5 => OpCodes.Ldc_I4_5,
6 => OpCodes.Ldc_I4_6,
7 => OpCodes.Ldc_I4_7,
_ => OpCodes.Ldc_I4_8
});
return;
}

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

if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Ldarg_s);
_builder.WriteByte((byte)arg);
Emit(OpCodes.Ldarg_S, (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);
Emit(OpCodes.Ldarg, (short)arg);
return;
}
}
else if (opcode.Equals(OpCodes.Ldarga))
{
if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Ldarga_s);
_builder.WriteByte((byte)arg);
Emit(OpCodes.Ldarga_S, (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);
Emit(OpCodes.Ldarga, (short)arg);
return;
}
}
else if (opcode.Equals(OpCodes.Starg))
{
if ((uint)arg <= byte.MaxValue)
{
_il.OpCode(ILOpCode.Starg_s);
_builder.WriteByte((byte)arg);
Emit(OpCodes.Starg_S, (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);
Emit(OpCodes.Starg, (short)arg);
return;
}
}

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

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

Expand All @@ -187,7 +185,7 @@ public override void Emit(OpCode opcode, string str)
// represented by str.
ModuleBuilder modBuilder = (ModuleBuilder)_methodBuilder.Module;
int tempVal = modBuilder.GetStringMetadataToken(str);
_il.OpCode((ILOpCode)opcode.Value);
EmitOpcode(opcode);
_il.Token(tempVal);
}

Expand Down
Loading