Skip to content

Compile-time branch elimination for null/default equality and constant switch expressions#505

Open
Copilot wants to merge 4 commits intomasterfrom
copilot/some-form-of-compile-time-branch-elimination
Open

Compile-time branch elimination for null/default equality and constant switch expressions#505
Copilot wants to merge 4 commits intomasterfrom
copilot/some-form-of-compile-time-branch-elimination

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 12, 2026

After inlining invocations (common in AutoMapper), conditional tests like Constant(null) == Default(typeof(X)) appear — statically evaluable to true but previously not reduced, causing both branches to be emitted and their closure info collected unnecessarily. Similarly, switch expressions with a constant switch value were emitting all case branches instead of only the matching one.

Changes

Conditional branch elimination

  • TryReduceConditional — extended to eliminate branches when both sides of Equal/NotEqual represent null: a ConstantExpression with Value == null or a DefaultExpression of a reference type, interface, or Nullable<T>.
  • IsNullDefault helper — new Tools.IsNullDefault(Type) using Nullable.GetUnderlyingType() (avoids [RequiresUnreferencedCode] unlike IsNullable()).
  • Emit phase — adds a TryReduceConditional fallback after TryInterpretBool fails; the latter only handles primitive-typed comparisons, missing reference-type null checks.
  • Collect phase — adds TryReduceConditional before collecting closure info, so dead branches never contribute unnecessary closure slots.

Switch branch elimination (#489)

  • Interpreter.TryFindSwitchBranch — new method that detects when a switch value is a compile-time constant (or an interpretable arithmetic expression via TryInterpretInt/TryInterpretPrimitiveValue) and returns only the matching case body. Works for integer/char/enum and string switch values with no custom equality method.
  • Emit phaseTryEmitSwitch calls TryFindSwitchBranch first; on success, emits only the matched branch body.
  • Collect phase — skips collecting closure info for all dead case branches when the switch value is deterministic.
  • RefactorConvertValueObjectToLong moved from private EmittingVisitor to internal Interpreter for shared use. Added PValueToLong helper in Interpreter.

Examples

// Conditional: null == Default(string) → always true
var expr = Lambda<Func<string>>(
    Condition(
        Equal(Constant(null, typeof(string)), Default(typeof(string))),
        Constant("live"),
        Constant("dead")));
// CompileFast emits: ldstr "live" / ret

// Switch: constant value selects single branch
var expr = Lambda<Func<string>>(
    Switch(Constant(2),
        Constant("Z"),
        SwitchCase(Constant("A"), Constant(1)),
        SwitchCase(Constant("B"), Constant(2)),
        SwitchCase(Constant("C"), Constant(5))));
// CompileFast emits: ldstr "B" / ret

// Switch: interpreted arithmetic selects single branch
var expr = Lambda<Func<string>>(
    Switch(Add(Constant(1), Constant(4)), /* same cases */));
// CompileFast emits: ldstr "C" / ret  (1+4=5 → case 5)

Conditional covers: null == Default(X), Default(X) == null, Default(X) == Default(X), and null == Default(Nullable<T>) — all with both Equal and NotEqual.

Switch covers: constant integer/enum/char/string switch values and integer arithmetic expressions on constants; falls through to default when no case matches.

Copilot AI linked an issue Apr 12, 2026 that may be closed by this pull request
…itional expressions

- Extend TryReduceConditional to eliminate branches when test is null/default equality
- Add IsNullDefault helper to check if a type's default value is null
- Add TryReduceConditional call in the emit phase as fallback for non-primitive comparisons
- Add TryReduceConditional call in the collect phase to skip dead branch closure collection
- Add tests covering null==Default, Default==null, Default==Default, and nullable cases

Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/babb0376-0bd1-4f18-aaef-62de01a6b8f9

Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
Copilot AI changed the title [WIP] Review compile time branch elimination progress Compile-time branch elimination for null/default equality in conditional expressions Apr 12, 2026
Copilot AI requested a review from dadhi April 12, 2026 07:14
Copy link
Copy Markdown
Owner

@dadhi dadhi left a comment

Choose a reason for hiding this comment

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

Cool. There is tied up ussue #489. Let see how can address it too reusing and compressing the logic and preserving consistency with as fast and memory efficient and possible. General question - we need to weight time/compute on the elimination check vs actual emit and work of eliminating jit - benchmark?

- Add Interpreter.TryFindSwitchBranch: detects constant/interpretable switch values
  and returns the matching case body, eliminating dead case branches at compile time
- Supports integer/enum/char (constant + arithmetic via TryInterpretInt) and string
- Wire into both collect phase (skips dead branch closure collection) and emit phase
- Refactor: move ConvertValueObjectToLong from private EmittingVisitor to internal Interpreter
- Add PValueToLong helper in Interpreter for PValue → long conversion
- Add 5 tests: int, string, enum, no-match→default, interpreted arithmetic switch values

Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/a41fbeeb-293f-421d-93ad-a1b8351344ee

Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 12, 2026

Cool. There is tied up ussue #489. Let see how can address it too reusing and compressing the logic and preserving consistency with as fast and memory efficient and possible. General question - we nee...

Addressed issue #489 in commit d4423e8 — added Interpreter.TryFindSwitchBranch which detects when a switch value is a compile-time constant (or interpretable arithmetic expression like 1 + 4) and selects only the matching case body, eliminating all dead branches in both the collect phase (skips dead branch closure collection) and emit phase. Works for integer/enum/char and string switch values. Also refactored ConvertValueObjectToLong from private EmittingVisitor into internal Interpreter so both the existing switch table code and the new TryFindSwitchBranch share it cleanly.

On the benchmark question: the elimination check cost is O(n_cases) comparisons, each O(1) — always dominated by the savings of O(n_non-matching_cases × case_body_complexity) in both closure collection and IL emission. The JIT can't help here since FEC's optimization happens at IL-generation time, before any JIT sees the code. For large switches on constant values (common post-inlining), the speedup can be significant. A dedicated benchmark would be a good follow-up if you want numbers.

Copilot AI changed the title Compile-time branch elimination for null/default equality in conditional expressions Compile-time branch elimination for null/default equality and constant switch expressions Apr 12, 2026
Copilot AI requested a review from dadhi April 12, 2026 08:02
@dadhi dadhi marked this pull request as ready for review April 12, 2026 08:07
Copy link
Copy Markdown
Owner

@dadhi dadhi left a comment

Choose a reason for hiding this comment

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

Let add a feeling benchamrk were we compare speed of the execution of switch compiled by System.Compile vs compiled by Fec - first for expr. which is not target for Fec branch elimination to introduce a baseline and second for the expr.with some branch eliminated. Use existing benchmark dotnet project in repo. Makes sense?

- New Issue489_Switch_BranchElimination.cs with Compile and Invoke nested classes:
  - Baseline: Switch(Parameter(...), ...) — runtime value, no FEC branch elimination
  - Eliminated: Switch(Constant(2), ...) — constant value, FEC emits only matching branch
- Both registered in Program.cs (commented-out as per existing convention)

Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/03a3da65-f598-4c66-90ce-0545d43edc04

Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
// Models the AutoMapper pattern: after inlining a null argument into a null-check lambda
public void Condition_with_null_constant_equal_to_default_of_class_type_is_eliminated(TestContext t)
{
// Condition(Equal(Constant(null), Default(typeof(string))), Constant("trueBranch"), Constant("falseBranch"))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why do we need this comment if it repeats the actual expression below? I would rather see the reasult of PrintCSharp output for the more compact/evident C# reprrsentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Some form of compile time branch elimination

2 participants