Skip to content

Commit

Permalink
Add initial MaxStack calculation (dotnet#93244)
Browse files Browse the repository at this point in the history
* Add initial MaxStack calculation and tests

* Apply suggestions from code review

Co-authored-by: Aaron Robinson <arobins@microsoft.com>

* Make OpCode.StackChange() public and use the method

* Apply the approved API shape

* Add doc, update the ref API ordering

* Add more doc info, add test

* Update doc remarks

---------

Co-authored-by: Aaron Robinson <arobins@microsoft.com>
  • Loading branch information
2 people authored and liveans committed Nov 9, 2023
1 parent 44517cf commit 83385d0
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 273 deletions.
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

0 comments on commit 83385d0

Please sign in to comment.