Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
200bff1
test: run existing nullable Boolean issue with strict compilation
jogibear9988 Oct 18, 2026
4a356bb
test: retain upstream lifted arithmetic and unsigned division boundaries
jogibear9988 Oct 27, 2026
8249d7b
fix: honor explicit increment methods with typed and lifted regressions
jogibear9988 Oct 18, 2026
503535f
fix: emit valid instance calls and index access on struct receivers
jogibear9988 Oct 18, 2026
81eb2ad
fix: preserve nullable and unsigned numeric comparison semantics
jogibear9988 Oct 18, 2026
5f69e68
fix: preserve typed and void label values with portable regressions
jogibear9988 Oct 18, 2026
5745d33
fix: preserve reference identity in equality expressions
jogibear9988 Oct 18, 2026
6f05516
test: retain lifted complement and boxed constant identity coverage
jogibear9988 Nov 4, 2026
49b0e89
fix: honor selected binary methods and compound assignment conversions
jogibear9988 Oct 18, 2026
68d56f4
fix: preserve arithmetic evaluation and overflow behavior across exce…
jogibear9988 Oct 18, 2026
798295a
fix: lift shifts with nullable counts and nonnullable left operands
jogibear9988 Oct 18, 2026
174e738
fix: preserve formal ref arguments and value-type receiver calls
jogibear9988 Oct 27, 2026
d6355d2
fix: emit guarded filters and spill operands across exception regions
jogibear9988 Oct 18, 2026
27cb2cb
fix: load writable boxed capture addresses for ref arguments
jogibear9988 Oct 18, 2026
98322fc
fix: preserve initializer and constructor evaluation across exception…
jogibear9988 Oct 18, 2026
0f80f47
test: exclude proven system compiler layout assertions with reasons
jogibear9988 Oct 18, 2026
64ef0d2
feat: compile capture-free quotes and direct quoted invocations
jogibear9988 Oct 18, 2026
6ef07d2
fix: preserve nested binding effects with upstream initializer helpers
jogibear9988 Oct 18, 2026
c9fd2e5
fix: preserve typed ref copyback and readonly receiver storage
jogibear9988 Oct 18, 2026
3ac19c6
test: cover quoted lambda indexer arguments
jogibear9988 Oct 18, 2026
89eedd9
test: document deferred stress and verified compiler-layout exclusions
jogibear9988 Oct 18, 2026
055ffad
fix: preserve quote identity with portable Framework behavior proofs
jogibear9988 Oct 18, 2026
15b5a6c
fix: preserve writable Unbox and formal ref argument storage
jogibear9988 Oct 18, 2026
4d46a20
fix: validate label definitions with representation-independent regre…
jogibear9988 Oct 18, 2026
6fe7225
fix: validate exception-region jumps and rethrows with behavior proofs
jogibear9988 Oct 18, 2026
972c0ef
feat: invoke runtime expression values with ordered receiver and argu…
jogibear9988 Oct 18, 2026
8f8609f
fix: bind reused labels within lexical and invocation scopes
jogibear9988 Oct 18, 2026
d2f8574
feat: preserve per-occurrence dynamic sites and inlined invocation bo…
jogibear9988 Oct 18, 2026
c7456dd
fix: preserve physical method-tail returns with portable regressions
jogibear9988 Oct 18, 2026
8de81c4
feat: share typed cells between captures quotes and runtime variables
jogibear9988 Oct 18, 2026
5a91f06
fix: validate lexical jumps and verify runtime-variable spill behavior
jogibear9988 Oct 18, 2026
3fbec83
Keep unary and conversion operands in value contexts
jogibear9988 Oct 18, 2026
f09b227
test: verify strict rejection and compiled behavior before excluding …
jogibear9988 Oct 18, 2026
6f6c3fb
Fix non-Exception catch policy for generated methods
jogibear9988 Oct 18, 2026
20dcc3f
Preserve original string constant reference identity
jogibear9988 Oct 18, 2026
698ab7d
Keep Framework boxed receivers verifiable during argument spills
jogibear9988 Oct 18, 2026
ef31946
Reuse collected delegate invocation parameters
jogibear9988 Oct 19, 2026
9d070d8
Avoid eager scope and exception metadata allocations on label-free paths
jogibear9988 Oct 19, 2026
337ab9e
test: verify upstream reduction reuse with dynamic and quoted express…
jogibear9988 Nov 4, 2026
93ee480
Reuse hoisted capture reflection metadata within each compilation
jogibear9988 Oct 19, 2026
f0c981d
Keep lazy JIT with anonymous catch policy on modern runtimes
jogibear9988 Oct 19, 2026
949c394
Keep provably readonly captures in existing value closure slots
jogibear9988 Oct 19, 2026
80cf9c6
Fold immutable constant string lengths without capturing string storage
jogibear9988 Oct 19, 2026
d1bcb12
Reuse transient scope buffers and skip terminal-node scope tracking
jogibear9988 Oct 19, 2026
e51e2fd
Analyze capture storage mutations once per compilation root
jogibear9988 Oct 19, 2026
1e654cd
Reuse capture call signatures and avoid cells for value arguments
jogibear9988 Oct 19, 2026
c2eb150
Collect switch test values without enumerator allocations
jogibear9988 Oct 19, 2026
5c8d0d0
Allocate hoisted type cache only for actual shared cells
jogibear9988 Oct 19, 2026
1f88402
Keep lexical and exception ancestry in inline indexed metadata
jogibear9988 Oct 19, 2026
9937a12
Collect terminal expression nodes before tracking lexical scopes
jogibear9988 Oct 19, 2026
e0a4d63
Keep quote and dynamic emission maps off ordinary compiler contexts
jogibear9988 Oct 19, 2026
e371895
Allocate invocation spill metadata only for arguments with jumps
jogibear9988 Oct 19, 2026
8826fa9
Preserve readonly capture layout through control flow and initializers
jogibear9988 Oct 19, 2026
80d54be
Keep primitive arithmetic emission outside lifted-operation handling
jogibear9988 Oct 19, 2026
0bfb181
Track capture slot writes precisely and scan simple initializers once
jogibear9988 Oct 19, 2026
efb65a5
perf: reuse cleared capture metadata with indexed-property coverage
jogibear9988 Oct 19, 2026
f3d7f41
Skip scoped label helpers while emitting label-free blocks
jogibear9988 Oct 19, 2026
8b774bd
Share root capture analysis before propagating nested parameters
jogibear9988 Oct 19, 2026
3a9d03d
Remove exception cleanup regions from internal collector recursion
jogibear9988 Oct 19, 2026
df14fe7
Keep common scope compression and unchanged-depth returns inline
jogibear9988 Oct 19, 2026
ba798a8
Avoid primitive arithmetic spills for atomic operands after EH
jogibear9988 Oct 19, 2026
e44d2b9
Inline the empty capture-parameter cache path
jogibear9988 Oct 19, 2026
9a81766
Reuse operand types in scalar arithmetic emission
jogibear9988 Oct 19, 2026
d752361
Keep private mutable reference captures in their sole child value slot
jogibear9988 Oct 19, 2026
d8ee0aa
fix: preserve native boxed capture storage and Framework typed-cell s…
jogibear9988 Oct 19, 2026
7b6378d
Batch collection of consecutive single-expression block scopes
jogibear9988 Oct 19, 2026
3f0d535
Reuse the initial node type during collection
jogibear9988 Oct 19, 2026
169e207
Avoid root capture scans when typed storage is already required
jogibear9988 Oct 19, 2026
4b8d401
Reuse private reference slots through constructor and array containers
jogibear9988 Oct 19, 2026
adab27d
Establish the actual capture-analysis root before intrinsic collection
jogibear9988 Oct 19, 2026
abe99a5
Avoid materializing Light array operands during private capture analysis
jogibear9988 Oct 19, 2026
ce8814c
Restore typed reference loads for verified Framework closure slots
jogibear9988 Oct 19, 2026
e0af6df
perf: defer quote metadata with its trimming contract
jogibear9988 Oct 19, 2026
a0a09b8
Keep scope overflow arrays in compact geometric pool buckets
jogibear9988 Oct 19, 2026
de7dd74
Skip empty jump validators on jump-free expression trees
jogibear9988 Oct 19, 2026
2ae94e8
Reduce Framework capture constructor metadata allocations
jogibear9988 Oct 19, 2026
261da6b
Type Framework capture parameters in existing rented signatures
jogibear9988 Oct 19, 2026
0fd4c03
Avoid the full collector for terminal binary roots
jogibear9988 Oct 19, 2026
9207192
Use boxed Framework value cells in proven owned roots
jogibear9988 Oct 19, 2026
48b8655
Avoid redundant Framework delegate casts in proven owned roots
jogibear9988 Oct 19, 2026
3eab2b6
Restore owned Framework capture slot loads without extra casts
jogibear9988 Oct 19, 2026
82b4c35
Avoid arithmetic operand spills when the right side cannot enter EH
jogibear9988 Oct 19, 2026
ef9f65d
Use the supported anonymous DynamicMethod constructor for exception-a…
jogibear9988 Oct 19, 2026
444f245
Explain scope pool buckets and outlined frame growth
jogibear9988 Oct 19, 2026
0b1b96e
fix: preserve opaque capture and intrinsic boundaries in arithmetic f…
jogibear9988 Oct 19, 2026
78bddd4
docs: consolidate compatibility and performance validation records
jogibear9988 Oct 25, 2026
0173d13
cleanup: remove redundant comparison changes and regression matrices
jogibear9988 Nov 8, 2026
3ab699c
fix: preserve exception-region evaluation after upstream integration
jogibear9988 Nov 10, 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 @@ -1346,21 +1346,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 @@ -6081,11 +6082,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