Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
67fc96b
test: retain numeric conversion boundary coverage with upstream runner
jogibear9988 Oct 18, 2026
809b372
fix: emit nullable Boolean operators with three-valued logic
jogibear9988 Oct 18, 2026
049db11
fix: reject extension reductions that make no progress
jogibear9988 Oct 18, 2026
9aa7cf8
fix: retain type-test edge cases and reference-constant regressions
jogibear9988 Oct 18, 2026
2f5a9eb
fix: preserve nullable boxing and unboxing value contexts
jogibear9988 Oct 18, 2026
d4076c6
fix: normalize nullable and wide array bounds before allocation
jogibear9988 Oct 18, 2026
98eb4c9
test: retain upstream lifted arithmetic and unsigned division boundaries
jogibear9988 Oct 27, 2026
8e92c5d
fix: retain primitive Not value-context semantics and regressions
jogibear9988 Oct 18, 2026
6752699
fix: dereference by-ref operands when emitting conversions
jogibear9988 Oct 18, 2026
d2abaa5
fix: honor explicit increment methods with typed and lifted regressions
jogibear9988 Oct 18, 2026
25f66a1
fix: emit valid instance calls and index access on struct receivers
jogibear9988 Oct 18, 2026
21a4817
fix: preserve nullable and unsigned numeric comparison semantics
jogibear9988 Oct 18, 2026
c5c15e2
fix: preserve coalesce value contexts with upstream conversions
jogibear9988 Oct 18, 2026
d3bf543
fix: preserve typed and void label values with portable regressions
jogibear9988 Oct 18, 2026
4160f52
fix: preserve reference identity in equality expressions
jogibear9988 Oct 18, 2026
8b64055
fix: emit user-defined conditional logical operators
jogibear9988 Oct 18, 2026
88d9636
fix: invoke coalesce conversion callbacks on non-null operands
jogibear9988 Oct 18, 2026
0bf78cc
fix: honor custom Not methods and retain lifted identity regressions
jogibear9988 Oct 18, 2026
621fc8c
fix: honor selected binary methods and compound assignment conversions
jogibear9988 Oct 18, 2026
643c7a3
fix: preserve arithmetic evaluation and overflow behavior across exce…
jogibear9988 Oct 18, 2026
f5e0e18
fix: keep wide integer switches on ordered comparison dispatch
jogibear9988 Oct 18, 2026
8ad5391
fix: preserve comparison methods and identity through conditional red…
jogibear9988 Oct 18, 2026
bb5987f
fix: lift shifts with nullable counts and nonnullable left operands
jogibear9988 Oct 18, 2026
fefcf92
fix: preserve private delegate lookup and null instance-call semantics
jogibear9988 Oct 18, 2026
602bda3
fix: preserve readonly copies and writable array-element receivers
jogibear9988 Oct 18, 2026
08f950d
fix: preserve formal ref arguments and value-type receiver calls
jogibear9988 Oct 27, 2026
38ce90d
fix: preserve decimal constant sign and scale
jogibear9988 Oct 18, 2026
c7e3037
fix: lift user-defined conditional logical operators
jogibear9988 Oct 18, 2026
4422f5d
fix: emit guarded filters and spill operands across exception regions
jogibear9988 Oct 18, 2026
3bde1fa
fix: load writable boxed capture addresses for ref arguments
jogibear9988 Oct 18, 2026
8a1441f
fix: preserve initializer and constructor evaluation across exception…
jogibear9988 Oct 18, 2026
bd02cbb
test: exclude proven system compiler layout assertions with reasons
jogibear9988 Oct 18, 2026
441ea0b
fix: emit actual array accessors for rank-one non-vector arrays
jogibear9988 Oct 18, 2026
3b19cd9
feat: compile capture-free quotes and direct quoted invocations
jogibear9988 Oct 18, 2026
06b6e07
fix: preserve nested binding effects with upstream initializer helpers
jogibear9988 Oct 18, 2026
69c657d
fix: preserve typed ref copyback and readonly receiver storage
jogibear9988 Oct 18, 2026
311cd99
fix: preserve non-runtime Type constant identities
jogibear9988 Oct 18, 2026
ee40546
fix: reduce custom expression node types through the standard contract
jogibear9988 Oct 18, 2026
0344411
test: cover quoted lambda indexer arguments
jogibear9988 Oct 18, 2026
eb9f2f1
fix: preserve runtime switch test evaluation and void results
jogibear9988 Oct 18, 2026
4bbdf03
test: document deferred stress and verified compiler-layout exclusions
jogibear9988 Oct 18, 2026
4aaa278
fix: reject abstract constructor calls before returning a delegate
jogibear9988 Oct 18, 2026
a515500
fix: preserve quote identity with portable Framework behavior proofs
jogibear9988 Oct 18, 2026
b0fa6f3
fix: preserve writable Unbox and formal ref argument storage
jogibear9988 Oct 18, 2026
7592691
fix: reject lambda signatures that exceed the method parameter limit
jogibear9988 Oct 18, 2026
a73630d
fix: validate label definitions with representation-independent regre…
jogibear9988 Oct 18, 2026
50a9f3f
Preserve value payloads on Light continue gotos
jogibear9988 Oct 18, 2026
cb8c2f2
fix: validate abstract constructors in nested Light lambdas
jogibear9988 Oct 18, 2026
faec1e1
fix: validate exception-region jumps and rethrows with behavior proofs
jogibear9988 Oct 18, 2026
8b67949
feat: invoke runtime expression values with ordered receiver and argu…
jogibear9988 Oct 18, 2026
e63b7d2
fix: bind reused labels within lexical and invocation scopes
jogibear9988 Oct 18, 2026
badea9c
feat: preserve per-occurrence dynamic sites and inlined invocation bo…
jogibear9988 Oct 18, 2026
aa1f143
fix: preserve physical method-tail returns with portable regressions
jogibear9988 Oct 18, 2026
0ecf802
fix: restore the declaring variable scope when leaving nested blocks
jogibear9988 Oct 18, 2026
2787b8b
fix: duplicate only the final assignment field address
jogibear9988 Oct 18, 2026
79170eb
feat: share typed cells between captures quotes and runtime variables
jogibear9988 Oct 18, 2026
898dd18
fix: validate lexical jumps and verify runtime-variable spill behavior
jogibear9988 Oct 18, 2026
0c98eee
Keep unary and conversion operands in value contexts
jogibear9988 Oct 18, 2026
8489480
test: verify strict rejection and compiled behavior before excluding …
jogibear9988 Oct 18, 2026
e996bbd
Fix non-Exception catch policy for generated methods
jogibear9988 Oct 18, 2026
f96451e
Preserve original string constant reference identity
jogibear9988 Oct 18, 2026
4ce171d
Keep Framework boxed receivers verifiable during argument spills
jogibear9988 Oct 18, 2026
13865d7
Reuse collected delegate invocation parameters
jogibear9988 Oct 19, 2026
0ab5da2
Avoid eager scope and exception metadata allocations on label-free paths
jogibear9988 Oct 19, 2026
487538a
Reuse collected reductions for dynamic and quoted extension nodes
jogibear9988 Oct 19, 2026
c72e128
Reuse hoisted capture reflection metadata within each compilation
jogibear9988 Oct 19, 2026
ba97a22
Keep lazy JIT with anonymous catch policy on modern runtimes
jogibear9988 Oct 19, 2026
f06a760
Keep provably readonly captures in existing value closure slots
jogibear9988 Oct 19, 2026
a718790
Fold immutable constant string lengths without capturing string storage
jogibear9988 Oct 19, 2026
c95d03a
Reuse transient scope buffers and skip terminal-node scope tracking
jogibear9988 Oct 19, 2026
7481461
Analyze capture storage mutations once per compilation root
jogibear9988 Oct 19, 2026
75b50db
Reuse capture call signatures and avoid cells for value arguments
jogibear9988 Oct 19, 2026
eef8198
Collect switch test values without enumerator allocations
jogibear9988 Oct 19, 2026
f3bad7c
Allocate hoisted type cache only for actual shared cells
jogibear9988 Oct 19, 2026
934e1ed
Identify vector arrays directly on modern runtimes
jogibear9988 Oct 19, 2026
4ac941a
Keep lexical and exception ancestry in inline indexed metadata
jogibear9988 Oct 19, 2026
e803a77
Collect terminal expression nodes before tracking lexical scopes
jogibear9988 Oct 19, 2026
483a4aa
Keep quote and dynamic emission maps off ordinary compiler contexts
jogibear9988 Oct 19, 2026
fb045d1
Allocate invocation spill metadata only for arguments with jumps
jogibear9988 Oct 19, 2026
1e3a9ce
Preserve readonly capture layout through control flow and initializers
jogibear9988 Oct 19, 2026
edb65d7
Keep primitive arithmetic emission outside lifted-operation handling
jogibear9988 Oct 19, 2026
5a3acb9
Track capture slot writes precisely and scan simple initializers once
jogibear9988 Oct 19, 2026
07ae9bc
perf: reuse cleared capture metadata with indexed-property coverage
jogibear9988 Oct 19, 2026
a73ff53
Skip scoped label helpers while emitting label-free blocks
jogibear9988 Oct 19, 2026
fe83dc7
Share root capture analysis before propagating nested parameters
jogibear9988 Oct 19, 2026
0ca9470
Remove exception cleanup regions from internal collector recursion
jogibear9988 Oct 19, 2026
68bfd7a
Keep common scope compression and unchanged-depth returns inline
jogibear9988 Oct 19, 2026
a9fa7d9
Avoid primitive arithmetic spills for atomic operands after EH
jogibear9988 Oct 19, 2026
dd8cd26
Inline the empty capture-parameter cache path
jogibear9988 Oct 19, 2026
92bad5b
Reuse operand types in scalar arithmetic emission
jogibear9988 Oct 19, 2026
f803f41
Keep private mutable reference captures in their sole child value slot
jogibear9988 Oct 19, 2026
3a0585f
fix: preserve native boxed capture storage and Framework typed-cell s…
jogibear9988 Oct 19, 2026
2f65f60
Batch collection of consecutive single-expression block scopes
jogibear9988 Oct 19, 2026
8da432b
Reuse the initial node type during collection
jogibear9988 Oct 19, 2026
9308db0
Avoid root capture scans when typed storage is already required
jogibear9988 Oct 19, 2026
ec29013
Reuse private reference slots through constructor and array containers
jogibear9988 Oct 19, 2026
24ed88f
Establish the actual capture-analysis root before intrinsic collection
jogibear9988 Oct 19, 2026
ff8421f
Avoid materializing Light array operands during private capture analysis
jogibear9988 Oct 19, 2026
7a6cc9f
Restore typed reference loads for verified Framework closure slots
jogibear9988 Oct 19, 2026
cecbffb
perf: defer quote metadata with its trimming contract
jogibear9988 Oct 19, 2026
41f992c
Keep scope overflow arrays in compact geometric pool buckets
jogibear9988 Oct 19, 2026
a21e1dc
Skip empty jump validators on jump-free expression trees
jogibear9988 Oct 19, 2026
c294f46
Reduce Framework capture constructor metadata allocations
jogibear9988 Oct 19, 2026
ab4790e
Type Framework capture parameters in existing rented signatures
jogibear9988 Oct 19, 2026
8b9f9f1
Avoid the full collector for terminal binary roots
jogibear9988 Oct 19, 2026
67072a0
Use boxed Framework value cells in proven owned roots
jogibear9988 Oct 19, 2026
5215518
Avoid redundant Framework delegate casts in proven owned roots
jogibear9988 Oct 19, 2026
a5497e3
Restore owned Framework capture slot loads without extra casts
jogibear9988 Oct 19, 2026
3eb0915
Avoid arithmetic operand spills when the right side cannot enter EH
jogibear9988 Oct 19, 2026
8e3761b
Materialize try results before using struct receiver addresses
jogibear9988 Oct 19, 2026
aa35729
test: cover upstream SmallMap promotion with colliding keys
jogibear9988 Oct 19, 2026
edf9dc1
Use the supported anonymous DynamicMethod constructor for exception-a…
jogibear9988 Oct 19, 2026
14ecad6
Explain scope pool buckets and outlined frame growth
jogibear9988 Oct 19, 2026
0ba2982
fix: preserve opaque capture and intrinsic boundaries in arithmetic f…
jogibear9988 Oct 19, 2026
901d1e0
docs: consolidate compatibility and performance validation records
jogibear9988 Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Moreover the compiled delegate may be slower than the manually created delegate
_TL;DR;_
> Expression.Compile creates a DynamicMethod and associates it with an anonymous assembly to run it in a sand-boxed environment. This makes it safe for a dynamic method to be emitted and executed by partially trusted code but adds some run-time overhead.

FEC keeps owned DynamicMethods for ordinary lambdas. Methods containing exception regions use anonymous hosting to match System's non-Exception throw and catch policy. The APIs that skip collection also use anonymous hosting, because their exception regions are unknown before emission. Anonymous hosting adds eager JIT work when creating the delegate. `CompileFastToIL` inherits the host policy of the caller's method.

See also [a deep dive to Delegate internals](https://mattwarren.org/2017/01/25/How-do-.NET-delegates-work/#different-types-of-delegates).


Expand Down Expand Up @@ -263,17 +265,34 @@ var x = f(new B());
The idea is to provide the fast compilation for the supported expression types
and fallback to the system `Expression.Compile()` for the not supported types:

Invoking a runtime `System.Linq.Expressions.Expression<TDelegate>` value compiles that value
with its `Compile()` method before invoking the resulting delegate, as the system compiler does.
This runtime operation is distinct from compilation fallback: with
`CompileFast(ifFastFailedReturnNull: true)`, FEC still compiles the surrounding lambda directly.
Errors from compiling or executing the runtime expression propagate to its caller.
Ref-property arguments retain FEC's normal setter copyback after successful invocation.
The system compiler can lose this copyback when another argument contains an exception
block; FEC preserves the property mutation instead of reproducing that behavior.

Dynamic expressions use ordinary DLR call sites: each emitted occurrence has its own site,
reuses binding rules, and reads the current mutable `Target` for every invocation.
Runtime binder rule compilation is intrinsic DLR behavior; the outer lambda still compiles directly with FEC.

Quoted lambdas and `RuntimeVariables` share live typed cells with compiled outer and sibling lambdas.
Mutations remain visible after the creating method returns; independent activations keep separate storage.
Capture-free quoted trees retain their original identity. The closure arrays and direct IL emitter remain in use.

### What's not supported yet

**FEC does not support yet:**

- `Quote`
- `Dynamic`
- `RuntimeVariables`
- Captures of by-ref parameters, pointer types, or byref-like values that cannot be stored in a heap cell
- Reused physical method-tail labels reached with pending operand values or from protected exception regions (strict null / `NotSupported_MethodReturn_Context`)
- `DebugInfo`
- `MemberInit` with the `MemberMemberBinding` and the `ListMemberBinding` binding types
- `NewArrayInit` multi-dimensional array initializer is not supported yet

Nested `MemberInit` member and list bindings are supported. Very deep expression trees still have a known recursive traversal limit; see the [runtime compatibility results](test/FastExpressionCompiler.DotNetRuntimeTests/results/fixes/README.md).

To find what nodes are not supported in your expression you may use the technic described below in the [Diagnostics](#diagnostics) section.

The compilation is done by traversing the expression nodes and emitting the IL.
Expand Down
22 changes: 11 additions & 11 deletions src/FastExpressionCompiler.LightExpression/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1340,21 +1340,22 @@ public static ElementInit ElementInit(MethodInfo addMethod, params Expression[]
public static ElementInit ElementInit(MethodInfo addMethod, IEnumerable<Expression> arguments) =>
ElementInit(addMethod, arguments.AsReadOnlyList());

[RequiresUnreferencedCode(Trimming.Message)]
// The outer tree still consists of LightExpression nodes. A parameter or constant
// may hold a System Expression<TDelegate> as runtime data; Invoke compiles that
// value and invokes its delegate. This does not mix System nodes into the tree.
private static Type GetInvocationReturnType(Expression expression) =>
expression is LambdaExpression directLambda ? directLambda.ReturnType :
expression is UnaryExpression quote && quote.NodeType == ExpressionType.Quote && quote.Operand is LambdaExpression lambda
? lambda.ReturnType
: (expression.Type.FindExpressionCompileMethod()?.ReturnType ?? expression.Type).FindDelegateInvokeMethod().ReturnType;

public static InvocationExpression Invoke(LambdaExpression expression) =>
new NotNullExpressionInvocationExpression(expression);

public static InvocationExpression Invoke(LambdaExpression expression, Expression a0) =>
new OneArgumentInvocationExpression(expression, a0);

[MethodImpl((MethodImplOptions)256)]
[RequiresUnreferencedCode(Trimming.Message)]
private static Type GetInvocationReturnType(Expression expression) =>
expression is LambdaExpression lambda
? lambda.ReturnType
: expression is UnaryExpression unExpr && unExpr.NodeType == ExpressionType.Quote && unExpr.Operand is LambdaExpression quotedLambda
? quotedLambda.ReturnType
: expression.Type.FindDelegateInvokeMethod().ReturnType;

[RequiresUnreferencedCode(Trimming.Message)]
public static InvocationExpression Invoke(Expression expression, Expression a0) =>
expression is LambdaExpression
Expand Down Expand Up @@ -6063,11 +6064,10 @@ internal TypedReturnManyParametersExpression(Expression body, IReadOnlyList<Para
: base(body, parameters) => ReturnType = returnType;
}

// todo: @feature is not supported yet
public sealed class DynamicExpression : Expression
{
public override ExpressionType NodeType => ExpressionType.Dynamic;
public override Type Type { get;}
public override Type Type { get; }
public Type DelegateType { get; }
public CallSiteBinder Binder { get; }
public IReadOnlyList<Expression> Arguments { get; }
Expand Down
Loading
Loading