Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
ccc0592
test: retain numeric conversion boundary coverage with upstream runner
jogibear9988 Oct 18, 2026
c8a88c9
fix: emit nullable Boolean operators with three-valued logic
jogibear9988 Oct 18, 2026
c7218af
fix: retain type-test edge cases and reference-constant regressions
jogibear9988 Oct 18, 2026
4cff5ec
fix: preserve nullable boxing and unboxing value contexts
jogibear9988 Oct 18, 2026
ecdffc3
fix: normalize nullable and wide array bounds before allocation
jogibear9988 Oct 18, 2026
27732cf
test: retain upstream lifted arithmetic and unsigned division boundaries
jogibear9988 Oct 27, 2026
d6c48f4
fix: retain primitive Not value-context semantics and regressions
jogibear9988 Oct 18, 2026
32efc62
fix: honor explicit increment methods with typed and lifted regressions
jogibear9988 Oct 18, 2026
f41049c
fix: emit valid instance calls and index access on struct receivers
jogibear9988 Oct 18, 2026
ed9edcb
fix: preserve nullable and unsigned numeric comparison semantics
jogibear9988 Oct 18, 2026
4da22e3
fix: preserve coalesce value contexts with upstream conversions
jogibear9988 Oct 18, 2026
60e21a6
fix: preserve typed and void label values with portable regressions
jogibear9988 Oct 18, 2026
75fe846
fix: preserve reference identity in equality expressions
jogibear9988 Oct 18, 2026
556069f
fix: emit user-defined conditional logical operators
jogibear9988 Oct 18, 2026
4920559
fix: invoke coalesce conversion callbacks on non-null operands
jogibear9988 Oct 18, 2026
ecb1edb
test: retain lifted complement and boxed constant identity coverage
jogibear9988 Nov 4, 2026
e5ffcf8
fix: honor selected binary methods and compound assignment conversions
jogibear9988 Oct 18, 2026
92e3e74
fix: preserve arithmetic evaluation and overflow behavior across exce…
jogibear9988 Oct 18, 2026
70e6133
fix: preserve comparison methods and identity through conditional red…
jogibear9988 Oct 18, 2026
ba6b74e
fix: lift shifts with nullable counts and nonnullable left operands
jogibear9988 Oct 18, 2026
41d2caf
fix: preserve null instance-call semantics with delegate regressions
jogibear9988 Oct 18, 2026
981a097
fix: preserve readonly copies and writable array-element receivers
jogibear9988 Oct 18, 2026
8fe39f0
fix: preserve formal ref arguments and value-type receiver calls
jogibear9988 Oct 27, 2026
8d0001d
fix: preserve decimal constant sign and scale
jogibear9988 Oct 18, 2026
55b4470
fix: lift user-defined conditional logical operators
jogibear9988 Oct 18, 2026
29c7bd2
fix: emit guarded filters and spill operands across exception regions
jogibear9988 Oct 18, 2026
30b3577
fix: load writable boxed capture addresses for ref arguments
jogibear9988 Oct 18, 2026
e752775
fix: preserve initializer and constructor evaluation across exception…
jogibear9988 Oct 18, 2026
6aceec5
test: exclude proven system compiler layout assertions with reasons
jogibear9988 Oct 18, 2026
676e2c0
fix: emit actual array accessors for rank-one non-vector arrays
jogibear9988 Oct 18, 2026
1e050fd
feat: compile capture-free quotes and direct quoted invocations
jogibear9988 Oct 18, 2026
d54080d
fix: preserve nested binding effects with upstream initializer helpers
jogibear9988 Oct 18, 2026
035b68b
fix: preserve typed ref copyback and readonly receiver storage
jogibear9988 Oct 18, 2026
c19f9b7
test: cover quoted lambda indexer arguments
jogibear9988 Oct 18, 2026
f5c1408
fix: preserve runtime switch test evaluation and void results
jogibear9988 Oct 18, 2026
0a40ac4
test: document deferred stress and verified compiler-layout exclusions
jogibear9988 Oct 18, 2026
70b1836
fix: reject abstract constructor calls before returning a delegate
jogibear9988 Oct 18, 2026
8fa29fa
fix: preserve quote identity with portable Framework behavior proofs
jogibear9988 Oct 18, 2026
f0667bc
fix: preserve writable Unbox and formal ref argument storage
jogibear9988 Oct 18, 2026
720fc04
fix: reject lambda signatures that exceed the method parameter limit
jogibear9988 Oct 18, 2026
0b17165
fix: validate label definitions with representation-independent regre…
jogibear9988 Oct 18, 2026
2224c72
Preserve value payloads on Light continue gotos
jogibear9988 Oct 18, 2026
cf075bc
fix: validate abstract constructors in nested Light lambdas
jogibear9988 Oct 18, 2026
73ba41e
fix: validate exception-region jumps and rethrows with behavior proofs
jogibear9988 Oct 18, 2026
6b869a2
feat: invoke runtime expression values with ordered receiver and argu…
jogibear9988 Oct 18, 2026
6556ded
fix: bind reused labels within lexical and invocation scopes
jogibear9988 Oct 18, 2026
035288d
feat: preserve per-occurrence dynamic sites and inlined invocation bo…
jogibear9988 Oct 18, 2026
6c7ccab
fix: preserve physical method-tail returns with portable regressions
jogibear9988 Oct 18, 2026
23d57cd
fix: restore the declaring variable scope when leaving nested blocks
jogibear9988 Oct 18, 2026
fa1bf90
fix: duplicate only the final assignment field address
jogibear9988 Oct 18, 2026
865472d
feat: share typed cells between captures quotes and runtime variables
jogibear9988 Oct 18, 2026
5512a46
fix: validate lexical jumps and verify runtime-variable spill behavior
jogibear9988 Oct 18, 2026
ac232c3
Keep unary and conversion operands in value contexts
jogibear9988 Oct 18, 2026
1f1567b
test: verify strict rejection and compiled behavior before excluding …
jogibear9988 Oct 18, 2026
c54a40c
Fix non-Exception catch policy for generated methods
jogibear9988 Oct 18, 2026
01bb4f1
Preserve original string constant reference identity
jogibear9988 Oct 18, 2026
6fb2d3f
Keep Framework boxed receivers verifiable during argument spills
jogibear9988 Oct 18, 2026
2b3ad1f
Reuse collected delegate invocation parameters
jogibear9988 Oct 19, 2026
8b0348d
Avoid eager scope and exception metadata allocations on label-free paths
jogibear9988 Oct 19, 2026
0fdf00b
test: verify upstream reduction reuse with dynamic and quoted express…
jogibear9988 Nov 4, 2026
5228e38
Reuse hoisted capture reflection metadata within each compilation
jogibear9988 Oct 19, 2026
d05f99b
Keep lazy JIT with anonymous catch policy on modern runtimes
jogibear9988 Oct 19, 2026
d8e2d8e
Keep provably readonly captures in existing value closure slots
jogibear9988 Oct 19, 2026
d8936e7
Fold immutable constant string lengths without capturing string storage
jogibear9988 Oct 19, 2026
dc4ba75
Reuse transient scope buffers and skip terminal-node scope tracking
jogibear9988 Oct 19, 2026
dac8966
Analyze capture storage mutations once per compilation root
jogibear9988 Oct 19, 2026
ec80441
Reuse capture call signatures and avoid cells for value arguments
jogibear9988 Oct 19, 2026
0315a4b
Collect switch test values without enumerator allocations
jogibear9988 Oct 19, 2026
dfe8636
Allocate hoisted type cache only for actual shared cells
jogibear9988 Oct 19, 2026
e5f1dc0
Identify vector arrays directly on modern runtimes
jogibear9988 Oct 19, 2026
251bbca
Keep lexical and exception ancestry in inline indexed metadata
jogibear9988 Oct 19, 2026
65d1b73
Collect terminal expression nodes before tracking lexical scopes
jogibear9988 Oct 19, 2026
59ccf28
Keep quote and dynamic emission maps off ordinary compiler contexts
jogibear9988 Oct 19, 2026
4090c28
Allocate invocation spill metadata only for arguments with jumps
jogibear9988 Oct 19, 2026
7a38f57
Preserve readonly capture layout through control flow and initializers
jogibear9988 Oct 19, 2026
b7db222
Keep primitive arithmetic emission outside lifted-operation handling
jogibear9988 Oct 19, 2026
4996df5
Track capture slot writes precisely and scan simple initializers once
jogibear9988 Oct 19, 2026
362f3a4
perf: reuse cleared capture metadata with indexed-property coverage
jogibear9988 Oct 19, 2026
be42598
Skip scoped label helpers while emitting label-free blocks
jogibear9988 Oct 19, 2026
0f3be94
Share root capture analysis before propagating nested parameters
jogibear9988 Oct 19, 2026
270e9b2
Remove exception cleanup regions from internal collector recursion
jogibear9988 Oct 19, 2026
7eabfb0
Keep common scope compression and unchanged-depth returns inline
jogibear9988 Oct 19, 2026
19852e1
Avoid primitive arithmetic spills for atomic operands after EH
jogibear9988 Oct 19, 2026
124a098
Inline the empty capture-parameter cache path
jogibear9988 Oct 19, 2026
2e399b9
Reuse operand types in scalar arithmetic emission
jogibear9988 Oct 19, 2026
004fe22
Keep private mutable reference captures in their sole child value slot
jogibear9988 Oct 19, 2026
12eb6ee
fix: preserve native boxed capture storage and Framework typed-cell s…
jogibear9988 Oct 19, 2026
c79ecaf
Batch collection of consecutive single-expression block scopes
jogibear9988 Oct 19, 2026
d074e67
Reuse the initial node type during collection
jogibear9988 Oct 19, 2026
69d425f
Avoid root capture scans when typed storage is already required
jogibear9988 Oct 19, 2026
86c2867
Reuse private reference slots through constructor and array containers
jogibear9988 Oct 19, 2026
a6ad8e4
Establish the actual capture-analysis root before intrinsic collection
jogibear9988 Oct 19, 2026
3b76325
Avoid materializing Light array operands during private capture analysis
jogibear9988 Oct 19, 2026
ecbbe73
Restore typed reference loads for verified Framework closure slots
jogibear9988 Oct 19, 2026
eb90a29
perf: defer quote metadata with its trimming contract
jogibear9988 Oct 19, 2026
a1f2eff
Keep scope overflow arrays in compact geometric pool buckets
jogibear9988 Oct 19, 2026
7856283
Skip empty jump validators on jump-free expression trees
jogibear9988 Oct 19, 2026
23d06a1
Reduce Framework capture constructor metadata allocations
jogibear9988 Oct 19, 2026
46a6325
Type Framework capture parameters in existing rented signatures
jogibear9988 Oct 19, 2026
1c50197
Avoid the full collector for terminal binary roots
jogibear9988 Oct 19, 2026
6c83457
Use boxed Framework value cells in proven owned roots
jogibear9988 Oct 19, 2026
f3ceb61
Avoid redundant Framework delegate casts in proven owned roots
jogibear9988 Oct 19, 2026
97ccc63
Restore owned Framework capture slot loads without extra casts
jogibear9988 Oct 19, 2026
54af6b7
Avoid arithmetic operand spills when the right side cannot enter EH
jogibear9988 Oct 19, 2026
5a25021
Materialize try results before using struct receiver addresses
jogibear9988 Oct 19, 2026
f56859e
test: cover upstream SmallMap promotion with colliding keys
jogibear9988 Oct 19, 2026
a4cf92c
Use the supported anonymous DynamicMethod constructor for exception-a…
jogibear9988 Oct 19, 2026
d906a9e
Explain scope pool buckets and outlined frame growth
jogibear9988 Oct 19, 2026
2bd69d6
fix: preserve opaque capture and intrinsic boundaries in arithmetic f…
jogibear9988 Oct 19, 2026
896b3c5
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