Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
b6620db
test: retain numeric conversion boundary coverage with upstream runner
jogibear9988 Oct 18, 2026
6be54a2
fix: emit nullable Boolean operators with three-valued logic
jogibear9988 Oct 18, 2026
2019d4f
fix: retain type-test edge cases and reference-constant regressions
jogibear9988 Oct 18, 2026
50bd6f2
fix: preserve nullable boxing and unboxing value contexts
jogibear9988 Oct 18, 2026
591439e
fix: normalize nullable and wide array bounds before allocation
jogibear9988 Oct 18, 2026
4ab4488
test: retain upstream lifted arithmetic and unsigned division boundaries
jogibear9988 Oct 27, 2026
12ca518
fix: retain primitive Not value-context semantics and regressions
jogibear9988 Oct 18, 2026
e21a58a
fix: dereference by-ref operands when emitting conversions
jogibear9988 Oct 18, 2026
a63e8f0
fix: honor explicit increment methods with typed and lifted regressions
jogibear9988 Oct 18, 2026
bf8f4ec
fix: emit valid instance calls and index access on struct receivers
jogibear9988 Oct 18, 2026
c18bbdc
fix: preserve nullable and unsigned numeric comparison semantics
jogibear9988 Oct 18, 2026
89db397
fix: preserve coalesce value contexts with upstream conversions
jogibear9988 Oct 18, 2026
f892fcb
fix: preserve typed and void label values with portable regressions
jogibear9988 Oct 18, 2026
c7773ec
fix: preserve reference identity in equality expressions
jogibear9988 Oct 18, 2026
ef3e8d3
fix: emit user-defined conditional logical operators
jogibear9988 Oct 18, 2026
6f9b5b2
fix: invoke coalesce conversion callbacks on non-null operands
jogibear9988 Oct 18, 2026
9cb18cf
fix: honor custom Not methods and retain lifted identity regressions
jogibear9988 Oct 18, 2026
573a34f
fix: honor selected binary methods and compound assignment conversions
jogibear9988 Oct 18, 2026
25c8bf9
fix: preserve arithmetic evaluation and overflow behavior across exce…
jogibear9988 Oct 18, 2026
f093cff
fix: keep wide integer switches on ordered comparison dispatch
jogibear9988 Oct 18, 2026
3611e0b
fix: preserve comparison methods and identity through conditional red…
jogibear9988 Oct 18, 2026
2e38357
fix: lift shifts with nullable counts and nonnullable left operands
jogibear9988 Oct 18, 2026
d1a66fa
fix: preserve null instance-call semantics with delegate regressions
jogibear9988 Oct 18, 2026
d934803
fix: preserve readonly copies and writable array-element receivers
jogibear9988 Oct 18, 2026
61b9182
fix: preserve formal ref arguments and value-type receiver calls
jogibear9988 Oct 27, 2026
86e0c6a
fix: preserve decimal constant sign and scale
jogibear9988 Oct 18, 2026
7620ab9
fix: lift user-defined conditional logical operators
jogibear9988 Oct 18, 2026
708f62f
fix: emit guarded filters and spill operands across exception regions
jogibear9988 Oct 18, 2026
9280267
fix: load writable boxed capture addresses for ref arguments
jogibear9988 Oct 18, 2026
3a5a74d
fix: preserve initializer and constructor evaluation across exception…
jogibear9988 Oct 18, 2026
7d4cc4d
test: exclude proven system compiler layout assertions with reasons
jogibear9988 Oct 18, 2026
84a68dd
fix: emit actual array accessors for rank-one non-vector arrays
jogibear9988 Oct 18, 2026
f36288f
feat: compile capture-free quotes and direct quoted invocations
jogibear9988 Oct 18, 2026
ffcc9dd
fix: preserve nested binding effects with upstream initializer helpers
jogibear9988 Oct 18, 2026
250dd91
fix: preserve typed ref copyback and readonly receiver storage
jogibear9988 Oct 18, 2026
96a01a4
fix: preserve non-runtime Type constant identities
jogibear9988 Oct 18, 2026
ea6cab2
fix: reduce custom expression node types through the standard contract
jogibear9988 Oct 18, 2026
ebb4dc4
test: cover quoted lambda indexer arguments
jogibear9988 Oct 18, 2026
b22136e
fix: preserve runtime switch test evaluation and void results
jogibear9988 Oct 18, 2026
25cb790
test: document deferred stress and verified compiler-layout exclusions
jogibear9988 Oct 18, 2026
1f5e849
fix: reject abstract constructor calls before returning a delegate
jogibear9988 Oct 18, 2026
01ca3de
fix: preserve quote identity with portable Framework behavior proofs
jogibear9988 Oct 18, 2026
cd51a60
fix: preserve writable Unbox and formal ref argument storage
jogibear9988 Oct 18, 2026
00b695d
fix: reject lambda signatures that exceed the method parameter limit
jogibear9988 Oct 18, 2026
6f64e7f
fix: validate label definitions with representation-independent regre…
jogibear9988 Oct 18, 2026
d51a324
Preserve value payloads on Light continue gotos
jogibear9988 Oct 18, 2026
efcfe4d
fix: validate abstract constructors in nested Light lambdas
jogibear9988 Oct 18, 2026
186f59c
fix: validate exception-region jumps and rethrows with behavior proofs
jogibear9988 Oct 18, 2026
d5c2f98
feat: invoke runtime expression values with ordered receiver and argu…
jogibear9988 Oct 18, 2026
af1192e
fix: bind reused labels within lexical and invocation scopes
jogibear9988 Oct 18, 2026
ad8fb46
feat: preserve per-occurrence dynamic sites and inlined invocation bo…
jogibear9988 Oct 18, 2026
f1f575e
fix: preserve physical method-tail returns with portable regressions
jogibear9988 Oct 18, 2026
7f2bb7a
fix: restore the declaring variable scope when leaving nested blocks
jogibear9988 Oct 18, 2026
570b40d
fix: duplicate only the final assignment field address
jogibear9988 Oct 18, 2026
e12be91
feat: share typed cells between captures quotes and runtime variables
jogibear9988 Oct 18, 2026
bc06551
fix: validate lexical jumps and verify runtime-variable spill behavior
jogibear9988 Oct 18, 2026
1c2cdb0
Keep unary and conversion operands in value contexts
jogibear9988 Oct 18, 2026
d9d34a5
test: verify strict rejection and compiled behavior before excluding …
jogibear9988 Oct 18, 2026
ff9e9f6
Fix non-Exception catch policy for generated methods
jogibear9988 Oct 18, 2026
2a0c59e
Preserve original string constant reference identity
jogibear9988 Oct 18, 2026
1430258
Keep Framework boxed receivers verifiable during argument spills
jogibear9988 Oct 18, 2026
9ef55cc
Reuse collected delegate invocation parameters
jogibear9988 Oct 19, 2026
ad6fcb7
Avoid eager scope and exception metadata allocations on label-free paths
jogibear9988 Oct 19, 2026
730319d
Reuse collected reductions for dynamic and quoted extension nodes
jogibear9988 Oct 19, 2026
5039ef3
Reuse hoisted capture reflection metadata within each compilation
jogibear9988 Oct 19, 2026
81af20a
Keep lazy JIT with anonymous catch policy on modern runtimes
jogibear9988 Oct 19, 2026
8c34bf3
Keep provably readonly captures in existing value closure slots
jogibear9988 Oct 19, 2026
9ce9e36
Fold immutable constant string lengths without capturing string storage
jogibear9988 Oct 19, 2026
4a8141b
Reuse transient scope buffers and skip terminal-node scope tracking
jogibear9988 Oct 19, 2026
9c86c2f
Analyze capture storage mutations once per compilation root
jogibear9988 Oct 19, 2026
22828a4
Reuse capture call signatures and avoid cells for value arguments
jogibear9988 Oct 19, 2026
bc76346
Collect switch test values without enumerator allocations
jogibear9988 Oct 19, 2026
9dbe720
Allocate hoisted type cache only for actual shared cells
jogibear9988 Oct 19, 2026
96fd398
Identify vector arrays directly on modern runtimes
jogibear9988 Oct 19, 2026
0b0ca5a
Keep lexical and exception ancestry in inline indexed metadata
jogibear9988 Oct 19, 2026
0ef4a38
Collect terminal expression nodes before tracking lexical scopes
jogibear9988 Oct 19, 2026
98e2977
Keep quote and dynamic emission maps off ordinary compiler contexts
jogibear9988 Oct 19, 2026
ca59986
Allocate invocation spill metadata only for arguments with jumps
jogibear9988 Oct 19, 2026
f6823d6
Preserve readonly capture layout through control flow and initializers
jogibear9988 Oct 19, 2026
5b50a66
Keep primitive arithmetic emission outside lifted-operation handling
jogibear9988 Oct 19, 2026
b77ebf0
Track capture slot writes precisely and scan simple initializers once
jogibear9988 Oct 19, 2026
e0e03b9
perf: reuse cleared capture metadata with indexed-property coverage
jogibear9988 Oct 19, 2026
87a0020
Skip scoped label helpers while emitting label-free blocks
jogibear9988 Oct 19, 2026
f8876c1
Share root capture analysis before propagating nested parameters
jogibear9988 Oct 19, 2026
f1c434e
Remove exception cleanup regions from internal collector recursion
jogibear9988 Oct 19, 2026
230d02e
Keep common scope compression and unchanged-depth returns inline
jogibear9988 Oct 19, 2026
c33cfd4
Avoid primitive arithmetic spills for atomic operands after EH
jogibear9988 Oct 19, 2026
6d5b8b3
Inline the empty capture-parameter cache path
jogibear9988 Oct 19, 2026
c603465
Reuse operand types in scalar arithmetic emission
jogibear9988 Oct 19, 2026
8208def
Keep private mutable reference captures in their sole child value slot
jogibear9988 Oct 19, 2026
3a6fc8f
fix: preserve native boxed capture storage and Framework typed-cell s…
jogibear9988 Oct 19, 2026
831d411
Batch collection of consecutive single-expression block scopes
jogibear9988 Oct 19, 2026
e0d0d1c
Reuse the initial node type during collection
jogibear9988 Oct 19, 2026
5100868
Avoid root capture scans when typed storage is already required
jogibear9988 Oct 19, 2026
c5e3519
Reuse private reference slots through constructor and array containers
jogibear9988 Oct 19, 2026
14b194f
Establish the actual capture-analysis root before intrinsic collection
jogibear9988 Oct 19, 2026
e343514
Avoid materializing Light array operands during private capture analysis
jogibear9988 Oct 19, 2026
f79c9ae
Restore typed reference loads for verified Framework closure slots
jogibear9988 Oct 19, 2026
9291501
perf: defer quote metadata with its trimming contract
jogibear9988 Oct 19, 2026
a7402b9
Keep scope overflow arrays in compact geometric pool buckets
jogibear9988 Oct 19, 2026
9389780
Skip empty jump validators on jump-free expression trees
jogibear9988 Oct 19, 2026
b342450
Reduce Framework capture constructor metadata allocations
jogibear9988 Oct 19, 2026
37395bb
Type Framework capture parameters in existing rented signatures
jogibear9988 Oct 19, 2026
5c287e1
Avoid the full collector for terminal binary roots
jogibear9988 Oct 19, 2026
0484d06
Use boxed Framework value cells in proven owned roots
jogibear9988 Oct 19, 2026
6c3ae98
Avoid redundant Framework delegate casts in proven owned roots
jogibear9988 Oct 19, 2026
eb85a59
Restore owned Framework capture slot loads without extra casts
jogibear9988 Oct 19, 2026
9fd211e
Avoid arithmetic operand spills when the right side cannot enter EH
jogibear9988 Oct 19, 2026
b086886
Materialize try results before using struct receiver addresses
jogibear9988 Oct 19, 2026
01e66c1
test: cover upstream SmallMap promotion with colliding keys
jogibear9988 Oct 19, 2026
3f79cc5
Use the supported anonymous DynamicMethod constructor for exception-a…
jogibear9988 Oct 19, 2026
7e8d78e
Explain scope pool buckets and outlined frame growth
jogibear9988 Oct 19, 2026
b91ee19
fix: preserve opaque capture and intrinsic boundaries in arithmetic f…
jogibear9988 Oct 19, 2026
bf908bd
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