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
);