From 97170013d24345b7efac04fd3c91739262a665ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:28:05 +0000 Subject: [PATCH 1/5] Initial plan From 99a07a2f9b1239fd70fb135f8ffdecb1bd81b78b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:53:29 +0000 Subject: [PATCH 2/5] Optimize branches to use short form where possible Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/12b84d82-7d59-4139-8f30-ddc49bf21766 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FastExpressionCompiler.cs | 83 +++++++++++++++++-- .../Issue159_NumericConversions.cs | 2 +- ...InvalidProgramException_when_using_loop.cs | 6 +- ...ssible_to_implement_ref_local_variables.cs | 2 +- 4 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index f3d986fb..86f096df 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -2568,7 +2568,7 @@ private static bool TryEmitCoalesceOperator(BinaryExpression expr, ILGenerator i il.DmarkLabel(labelFalse); else { - il.Demit(OpCodes.Br, labelDone); + il.Demit(OpCodes.Br_S, labelDone); // safe: jumps over Castclass (5 bytes) = 5 bytes il.DmarkLabel(labelFalse); // todo: @bug? should we insert the boxing for the Nullable value type before the Castclass il.Demit(OpCodes.Castclass, exprType); il.DmarkLabel(labelDone); @@ -2947,9 +2947,9 @@ private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr, Expressio { var falseLabel = il.DefineLabel(); var continueLabel = il.DefineLabel(); - il.Demit(OpCodes.Brfalse, falseLabel); + il.Demit(OpCodes.Brfalse_S, falseLabel); // safe: jumps over Ldc_I4_0 (1 byte) + Br_S (2 bytes) = 3 bytes il.Demit(OpCodes.Ldc_I4_0); - il.Demit(OpCodes.Br, continueLabel); + il.Demit(OpCodes.Br_S, continueLabel); // safe: jumps over Ldc_I4_1 (1 byte) = 1 byte il.DmarkLabel(falseLabel); il.Demit(OpCodes.Ldc_I4_1); il.DmarkLabel(continueLabel); @@ -5767,9 +5767,9 @@ var methodName var resultLabel = il.DefineLabel(); var isNullLabel = il.DefineLabel(); EmitLoadLocalVariable(il, leftHasValueVar); - il.Demit(OpCodes.Brfalse, isNullLabel); + il.Demit(OpCodes.Brfalse_S, isNullLabel); // safe: jumps over EmitLoadLocalVariable (1-4 bytes) + Brtrue_S (2 bytes) = 3-6 bytes EmitLoadLocalVariable(il, rightHasValueVar); - il.Demit(OpCodes.Brtrue, resultLabel); + il.Demit(OpCodes.Brtrue_S, resultLabel); // safe: jumps over Pop (1 byte) + Ldnull (1 byte) = 2 bytes il.DmarkLabel(isNullLabel); il.Demit(OpCodes.Pop); il.Demit(OpCodes.Ldnull); @@ -5974,7 +5974,7 @@ private static bool TryEmitLogicalOperator(BinaryExpression expr, ExpressionType return false; var labelDone = il.DefineLabel(); - il.Demit(OpCodes.Br, labelDone); + il.Demit(OpCodes.Br_S, labelDone); // safe: jumps over Ldc_I4_0 or Ldc_I4_1 (1 byte) = 1 byte il.DmarkLabel(labelSkipRight); // label the second branch il.Demit(nodeType == ExpressionType.AndAlso ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1); @@ -8372,6 +8372,30 @@ public static class ILGeneratorTools /// Configuration option to disable the ILGenerator Emit debug output public static bool DisableDemit; + // Tracks the IL offset of each marked label per ILGenerator, used by smart branch emitters to select short form when possible + internal static readonly ConditionalWeakTable> _ilLabelPositions = + new ConditionalWeakTable>(); + + // Maps a long-form branch opcode to its short-form equivalent, or returns the same opcode if no short form exists + [MethodImpl((MethodImplOptions)256)] + private static OpCode GetShortFormBranchOpCode(OpCode longFormOpCode) + { + if (longFormOpCode == OpCodes.Br) return OpCodes.Br_S; + if (longFormOpCode == OpCodes.Brtrue) return OpCodes.Brtrue_S; + if (longFormOpCode == OpCodes.Brfalse) return OpCodes.Brfalse_S; + if (longFormOpCode == OpCodes.Beq) return OpCodes.Beq_S; + if (longFormOpCode == OpCodes.Bne_Un) return OpCodes.Bne_Un_S; + if (longFormOpCode == OpCodes.Blt) return OpCodes.Blt_S; + if (longFormOpCode == OpCodes.Blt_Un) return OpCodes.Blt_Un_S; + if (longFormOpCode == OpCodes.Bgt) return OpCodes.Bgt_S; + if (longFormOpCode == OpCodes.Bgt_Un) return OpCodes.Bgt_Un_S; + if (longFormOpCode == OpCodes.Ble) return OpCodes.Ble_S; + if (longFormOpCode == OpCodes.Ble_Un) return OpCodes.Ble_Un_S; + if (longFormOpCode == OpCodes.Bge) return OpCodes.Bge_S; + if (longFormOpCode == OpCodes.Bge_Un) return OpCodes.Bge_Un_S; + return longFormOpCode; // no short form available + } + #if DEMIT [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) @@ -8431,6 +8455,22 @@ public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo val public static void Demit(this ILGenerator il, OpCode opcode, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { + // Check if label is already marked (backward branch) and if short form is possible + var shortFormOpCode = GetShortFormBranchOpCode(opcode); + if (shortFormOpCode != opcode && + _ilLabelPositions.TryGetValue(il, out var positions) && + positions.TryGetValue(value.GetHashCode(), out var labelOffset)) + { + // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 + var delta = labelOffset - (il.ILOffset + 2); + if (delta >= -128 && delta <= 127) + { + il.Emit(shortFormOpCode, value); + if (DisableDemit) return; + Debug.WriteLine($"{shortFormOpCode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); + return; + } + } il.Emit(opcode, value); if (DisableDemit) return; Debug.WriteLine($"{opcode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); @@ -8451,6 +8491,8 @@ public static void DmarkLabel(this ILGenerator il, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.MarkLabel(value); + // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) + _ilLabelPositions.GetOrCreateValue(il)[value.GetHashCode()] = il.ILOffset; if (DisableDemit) return; Debug.WriteLine($"MarkLabel: {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); } @@ -8537,13 +8579,35 @@ public static void Demit(this ILGenerator il, string value, OpCode opcode, [Call public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value) => il.Emit(opcode, value); [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Label value) => il.Emit(opcode, value); + public static void Demit(this ILGenerator il, OpCode opcode, Label value) + { + // Check if label is already marked (backward branch) and if short form is possible + var shortFormOpCode = GetShortFormBranchOpCode(opcode); + if (shortFormOpCode != opcode && + _ilLabelPositions.TryGetValue(il, out var positions) && + positions.TryGetValue(value.GetHashCode(), out var labelOffset)) + { + // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 + var delta = labelOffset - (il.ILOffset + 2); + if (delta >= -128 && delta <= 127) + { + il.Emit(shortFormOpCode, value); + return; + } + } + il.Emit(opcode, value); + } [MethodImpl((MethodImplOptions)256)] public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels) => il.Emit(OpCodes.Switch, gotoLabels); [MethodImpl((MethodImplOptions)256)] - public static void DmarkLabel(this ILGenerator il, Label value) => il.MarkLabel(value); + public static void DmarkLabel(this ILGenerator il, Label value) + { + il.MarkLabel(value); + // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) + _ilLabelPositions.GetOrCreateValue(il)[value.GetHashCode()] = il.ILOffset; + } [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, byte value) => il.Emit(opcode, value); @@ -8593,6 +8657,9 @@ public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Ty if (pooledIL != null) { reuseILGenerator(dynMethod, pooledIL, returnType, paramTypes); + // Clear any stale label positions from the previous compilation to prevent incorrect branch optimization + if (ILGeneratorTools._ilLabelPositions.TryGetValue(pooledIL, out var positions)) + positions.Clear(); return pooledIL; } else diff --git a/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs b/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs index 0b2c68fa..f82fee26 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs @@ -365,7 +365,7 @@ public void ComparisonsWithConversionsShouldWork4() OpCodes.Cgt_Un, OpCodes.Ldc_I4_0, OpCodes.Ceq, - OpCodes.Br, + OpCodes.Br_S, // short form: jumps over Ldc_I4_0 (1 byte) = 1 byte OpCodes.Ldc_I4_0, OpCodes.Ret); diff --git a/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs b/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs index c8bcfb28..1e3e2e6a 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs @@ -96,9 +96,9 @@ public void Original_test(TestContext t) OpCodes.Ldc_I4_1, // at IL_0024 OpCodes.Add, // at IL_0025 OpCodes.Stloc_1, // at IL_0026 - OpCodes.Br, // IL_0000 at IL_0027 - OpCodes.Br, // IL_0000 at IL_0032 - OpCodes.Ret // at IL_0037 + OpCodes.Br_S, // IL_0000 at IL_0027 (short backward branch: fits in sbyte) + OpCodes.Br_S, // IL_0000 at IL_002A (short backward branch: fits in sbyte) + OpCodes.Ret // at IL_002C ); calResult = ff(); diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs index 325c7245..72f439fb 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs @@ -616,7 +616,7 @@ public void Real_world_test_ref_array_element() OpCodes.Stloc_1, OpCodes.Br, //60 OpCodes.Br, //65 - OpCodes.Br, //10 + OpCodes.Br_S, //10 (short backward branch: loop back-jump fits in sbyte) OpCodes.Ldloc_0, OpCodes.Ret ); From b17f7518be98dfd65ed508a33c064cc9c95ca19b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:58:26 +0000 Subject: [PATCH 3/5] Fix label tracking to use Label struct directly instead of GetHashCode Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/12b84d82-7d59-4139-8f30-ddc49bf21766 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- src/FastExpressionCompiler/FastExpressionCompiler.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 86f096df..bcb79878 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -8373,8 +8373,8 @@ public static class ILGeneratorTools public static bool DisableDemit; // Tracks the IL offset of each marked label per ILGenerator, used by smart branch emitters to select short form when possible - internal static readonly ConditionalWeakTable> _ilLabelPositions = - new ConditionalWeakTable>(); + internal static readonly ConditionalWeakTable> _ilLabelPositions = + new ConditionalWeakTable>(); // Maps a long-form branch opcode to its short-form equivalent, or returns the same opcode if no short form exists [MethodImpl((MethodImplOptions)256)] @@ -8459,7 +8459,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, Label value, var shortFormOpCode = GetShortFormBranchOpCode(opcode); if (shortFormOpCode != opcode && _ilLabelPositions.TryGetValue(il, out var positions) && - positions.TryGetValue(value.GetHashCode(), out var labelOffset)) + positions.TryGetValue(value, out var labelOffset)) { // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 var delta = labelOffset - (il.ILOffset + 2); @@ -8492,7 +8492,7 @@ public static void DmarkLabel(this ILGenerator il, Label value, { il.MarkLabel(value); // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) - _ilLabelPositions.GetOrCreateValue(il)[value.GetHashCode()] = il.ILOffset; + _ilLabelPositions.GetOrCreateValue(il)[value] = il.ILOffset; if (DisableDemit) return; Debug.WriteLine($"MarkLabel: {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); } @@ -8585,7 +8585,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, Label value) var shortFormOpCode = GetShortFormBranchOpCode(opcode); if (shortFormOpCode != opcode && _ilLabelPositions.TryGetValue(il, out var positions) && - positions.TryGetValue(value.GetHashCode(), out var labelOffset)) + positions.TryGetValue(value, out var labelOffset)) { // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 var delta = labelOffset - (il.ILOffset + 2); @@ -8606,7 +8606,7 @@ public static void DmarkLabel(this ILGenerator il, Label value) { il.MarkLabel(value); // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) - _ilLabelPositions.GetOrCreateValue(il)[value.GetHashCode()] = il.ILOffset; + _ilLabelPositions.GetOrCreateValue(il)[value] = il.ILOffset; } [MethodImpl((MethodImplOptions)256)] From 4692876b05cd566e79ebd1771832144f8c40557d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:01:31 +0000 Subject: [PATCH 4/5] Refactor: extract shared helper TryGetShortFormOpCodeForBackwardBranch Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/12b84d82-7d59-4139-8f30-ddc49bf21766 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FastExpressionCompiler.cs | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index bcb79878..3d5d7368 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -8396,6 +8396,26 @@ private static OpCode GetShortFormBranchOpCode(OpCode longFormOpCode) return longFormOpCode; // no short form available } + // Returns the short-form opcode if the label is already marked (backward branch) and the branch delta fits in a signed byte. + // The delta is computed from the end of the short-form instruction (ILOffset + 2) to the target label position. + [MethodImpl((MethodImplOptions)256)] + private static bool TryGetShortFormOpCodeForBackwardBranch(ILGenerator il, OpCode opcode, Label label, out OpCode shortFormOpCode) + { + shortFormOpCode = GetShortFormBranchOpCode(opcode); + if (shortFormOpCode != opcode && + _ilLabelPositions.TryGetValue(il, out var positions) && + positions.TryGetValue(label, out var labelOffset)) + { + // Short form branch: 2 bytes total (1-byte opcode + 1-byte signed offset). + // The offset is relative to the start of the next instruction (ILOffset + 2). + var delta = labelOffset - (il.ILOffset + 2); + if (delta >= -128 && delta <= 127) + return true; + } + shortFormOpCode = opcode; + return false; + } + #if DEMIT [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) @@ -8455,21 +8475,12 @@ public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo val public static void Demit(this ILGenerator il, OpCode opcode, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { - // Check if label is already marked (backward branch) and if short form is possible - var shortFormOpCode = GetShortFormBranchOpCode(opcode); - if (shortFormOpCode != opcode && - _ilLabelPositions.TryGetValue(il, out var positions) && - positions.TryGetValue(value, out var labelOffset)) + if (TryGetShortFormOpCodeForBackwardBranch(il, opcode, value, out var emitOpCode)) { - // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 - var delta = labelOffset - (il.ILOffset + 2); - if (delta >= -128 && delta <= 127) - { - il.Emit(shortFormOpCode, value); - if (DisableDemit) return; - Debug.WriteLine($"{shortFormOpCode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); - return; - } + il.Emit(emitOpCode, value); + if (DisableDemit) return; + Debug.WriteLine($"{emitOpCode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); + return; } il.Emit(opcode, value); if (DisableDemit) return; @@ -8579,24 +8590,8 @@ public static void Demit(this ILGenerator il, string value, OpCode opcode, [Call public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value) => il.Emit(opcode, value); [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Label value) - { - // Check if label is already marked (backward branch) and if short form is possible - var shortFormOpCode = GetShortFormBranchOpCode(opcode); - if (shortFormOpCode != opcode && - _ilLabelPositions.TryGetValue(il, out var positions) && - positions.TryGetValue(value, out var labelOffset)) - { - // Short form instruction: 2 bytes (opcode + 1-byte offset), so next instruction is at ILOffset + 2 - var delta = labelOffset - (il.ILOffset + 2); - if (delta >= -128 && delta <= 127) - { - il.Emit(shortFormOpCode, value); - return; - } - } - il.Emit(opcode, value); - } + public static void Demit(this ILGenerator il, OpCode opcode, Label value) => + il.Emit(TryGetShortFormOpCodeForBackwardBranch(il, opcode, value, out var shortForm) ? shortForm : opcode, value); [MethodImpl((MethodImplOptions)256)] public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels) => il.Emit(OpCodes.Switch, gotoLabels); From f4fc8ab36e49a501f21b5dc5a1a23acfc580de57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:14:27 +0000 Subject: [PATCH 5/5] Drop ConditionalWeakTable general backward-branch optimizer, keep only ad-hoc short-form branches Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/20368e58-840b-428b-b785-c852cbfd5438 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FastExpressionCompiler.cs | 66 +------------------ ...InvalidProgramException_when_using_loop.cs | 6 +- ...ssible_to_implement_ref_local_variables.cs | 2 +- 3 files changed, 6 insertions(+), 68 deletions(-) diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 3d5d7368..332ecc32 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -8372,50 +8372,6 @@ public static class ILGeneratorTools /// Configuration option to disable the ILGenerator Emit debug output public static bool DisableDemit; - // Tracks the IL offset of each marked label per ILGenerator, used by smart branch emitters to select short form when possible - internal static readonly ConditionalWeakTable> _ilLabelPositions = - new ConditionalWeakTable>(); - - // Maps a long-form branch opcode to its short-form equivalent, or returns the same opcode if no short form exists - [MethodImpl((MethodImplOptions)256)] - private static OpCode GetShortFormBranchOpCode(OpCode longFormOpCode) - { - if (longFormOpCode == OpCodes.Br) return OpCodes.Br_S; - if (longFormOpCode == OpCodes.Brtrue) return OpCodes.Brtrue_S; - if (longFormOpCode == OpCodes.Brfalse) return OpCodes.Brfalse_S; - if (longFormOpCode == OpCodes.Beq) return OpCodes.Beq_S; - if (longFormOpCode == OpCodes.Bne_Un) return OpCodes.Bne_Un_S; - if (longFormOpCode == OpCodes.Blt) return OpCodes.Blt_S; - if (longFormOpCode == OpCodes.Blt_Un) return OpCodes.Blt_Un_S; - if (longFormOpCode == OpCodes.Bgt) return OpCodes.Bgt_S; - if (longFormOpCode == OpCodes.Bgt_Un) return OpCodes.Bgt_Un_S; - if (longFormOpCode == OpCodes.Ble) return OpCodes.Ble_S; - if (longFormOpCode == OpCodes.Ble_Un) return OpCodes.Ble_Un_S; - if (longFormOpCode == OpCodes.Bge) return OpCodes.Bge_S; - if (longFormOpCode == OpCodes.Bge_Un) return OpCodes.Bge_Un_S; - return longFormOpCode; // no short form available - } - - // Returns the short-form opcode if the label is already marked (backward branch) and the branch delta fits in a signed byte. - // The delta is computed from the end of the short-form instruction (ILOffset + 2) to the target label position. - [MethodImpl((MethodImplOptions)256)] - private static bool TryGetShortFormOpCodeForBackwardBranch(ILGenerator il, OpCode opcode, Label label, out OpCode shortFormOpCode) - { - shortFormOpCode = GetShortFormBranchOpCode(opcode); - if (shortFormOpCode != opcode && - _ilLabelPositions.TryGetValue(il, out var positions) && - positions.TryGetValue(label, out var labelOffset)) - { - // Short form branch: 2 bytes total (1-byte opcode + 1-byte signed offset). - // The offset is relative to the start of the next instruction (ILOffset + 2). - var delta = labelOffset - (il.ILOffset + 2); - if (delta >= -128 && delta <= 127) - return true; - } - shortFormOpCode = opcode; - return false; - } - #if DEMIT [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) @@ -8475,13 +8431,6 @@ public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo val public static void Demit(this ILGenerator il, OpCode opcode, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { - if (TryGetShortFormOpCodeForBackwardBranch(il, opcode, value, out var emitOpCode)) - { - il.Emit(emitOpCode, value); - if (DisableDemit) return; - Debug.WriteLine($"{emitOpCode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); - return; - } il.Emit(opcode, value); if (DisableDemit) return; Debug.WriteLine($"{opcode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); @@ -8502,8 +8451,6 @@ public static void DmarkLabel(this ILGenerator il, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.MarkLabel(value); - // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) - _ilLabelPositions.GetOrCreateValue(il)[value] = il.ILOffset; if (DisableDemit) return; Debug.WriteLine($"MarkLabel: {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); } @@ -8590,19 +8537,13 @@ public static void Demit(this ILGenerator il, string value, OpCode opcode, [Call public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value) => il.Emit(opcode, value); [MethodImpl((MethodImplOptions)256)] - public static void Demit(this ILGenerator il, OpCode opcode, Label value) => - il.Emit(TryGetShortFormOpCodeForBackwardBranch(il, opcode, value, out var shortForm) ? shortForm : opcode, value); + public static void Demit(this ILGenerator il, OpCode opcode, Label value) => il.Emit(opcode, value); [MethodImpl((MethodImplOptions)256)] public static void DemitSwitch(this ILGenerator il, Label[] gotoLabels) => il.Emit(OpCodes.Switch, gotoLabels); [MethodImpl((MethodImplOptions)256)] - public static void DmarkLabel(this ILGenerator il, Label value) - { - il.MarkLabel(value); - // Track the label position for smart branch selection (used by Demit with Label to select short form for backward branches) - _ilLabelPositions.GetOrCreateValue(il)[value] = il.ILOffset; - } + public static void DmarkLabel(this ILGenerator il, Label value) => il.MarkLabel(value); [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, byte value) => il.Emit(opcode, value); @@ -8652,9 +8593,6 @@ public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Ty if (pooledIL != null) { reuseILGenerator(dynMethod, pooledIL, returnType, paramTypes); - // Clear any stale label positions from the previous compilation to prevent incorrect branch optimization - if (ILGeneratorTools._ilLabelPositions.TryGetValue(pooledIL, out var positions)) - positions.Clear(); return pooledIL; } else diff --git a/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs b/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs index 1e3e2e6a..c8bcfb28 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue498_InvalidProgramException_when_using_loop.cs @@ -96,9 +96,9 @@ public void Original_test(TestContext t) OpCodes.Ldc_I4_1, // at IL_0024 OpCodes.Add, // at IL_0025 OpCodes.Stloc_1, // at IL_0026 - OpCodes.Br_S, // IL_0000 at IL_0027 (short backward branch: fits in sbyte) - OpCodes.Br_S, // IL_0000 at IL_002A (short backward branch: fits in sbyte) - OpCodes.Ret // at IL_002C + OpCodes.Br, // IL_0000 at IL_0027 + OpCodes.Br, // IL_0000 at IL_0032 + OpCodes.Ret // at IL_0037 ); calResult = ff(); diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs index 72f439fb..325c7245 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs @@ -616,7 +616,7 @@ public void Real_world_test_ref_array_element() OpCodes.Stloc_1, OpCodes.Br, //60 OpCodes.Br, //65 - OpCodes.Br_S, //10 (short backward branch: loop back-jump fits in sbyte) + OpCodes.Br, //10 OpCodes.Ldloc_0, OpCodes.Ret );