Skip to content

Reduce expression collection and LightExpression construction costs - #559

Draft
jogibear9988 wants to merge 202 commits into
dadhi:masterfrom
jogibear9988:perf-expression-compilation-and-runtime
Draft

jogibear9988 wants to merge 202 commits into
dadhi:masterfrom
jogibear9988:perf-expression-compilation-and-runtime

Conversation

@jogibear9988

@jogibear9988 jogibear9988 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Reduce allocation during named LightExpression field construction and recursive collector overhead for terminal binary operands. This draft is stacked on current #558 (dd989683), based on current upstream master (0f37356f). GitHub's nominal base remains master because the dependency branch is in the contributor fork. Review only the performance increment.

Performance changes

  • Resolve named LightExpression fields directly while preserving inherited/private/static lookup and duplicate-name metadata behavior; no global cache.
  • Collect parameter/constant binary left operands through the existing terminal helper, retaining captures and intrinsic callbacks while avoiding a recursive collector frame. Emission is unchanged.
  • Keep constructor signature-pool regression coverage. The production repair is already in master and is not an incremental gain from this PR.

An additional call/constructor-argument shortcut was measured but not retained because its timing benefit did not consistently reproduce. No compatibility changes from #558 are claimed as performance changes here.

Current measurements only

Fresh matched comparison: master 0f37356f, #558 dd989683, #559 production source 00af81b4. The #559 production source is unchanged through this report. Previous-branch reports and values have been removed.

19 workloads, Standard/Light, .NET 8.0.28 / 10.0.9, four phases, two opposite-order process rounds, seven samples each: 14,896 current-stage/reference observations. CPU 5 affinity, High process priority, tiering/ReadyToRun disabled. Geometric means below give equal weight to each workload; above 1 is slower. These are descriptive ratios, not significance tests or application-throughput estimates.

Runtime / API Phase Master #558 / master #559 / master #559 / #558
8.0.28 / standard construct 1.000x 0.984x 0.965x 0.981x
8.0.28 / standard compile 1.000x 1.438x 1.411x 0.981x
8.0.28 / standard compile_first 1.000x 1.027x 1.016x 0.989x
8.0.28 / standard invoke 1.000x 0.995x 0.983x 0.989x
8.0.28 / light construct 1.000x 0.965x 0.932x 0.966x
8.0.28 / light compile 1.000x 1.458x 1.447x 0.992x
8.0.28 / light compile_first 1.000x 1.040x 1.029x 0.990x
8.0.28 / light invoke 1.000x 0.968x 0.971x 1.003x
10.0.9 / standard construct 1.000x 1.015x 1.017x 1.002x
10.0.9 / standard compile 1.000x 1.512x 1.500x 0.992x
10.0.9 / standard compile_first 1.000x 1.037x 1.028x 0.992x
10.0.9 / standard invoke 1.000x 0.957x 0.949x 0.991x
10.0.9 / light construct 1.000x 0.992x 0.932x 0.940x
10.0.9 / light compile 1.000x 1.478x 1.472x 0.996x
10.0.9 / light compile_first 1.000x 1.053x 1.025x 0.973x
10.0.9 / light invoke 1.000x 0.963x 0.922x 0.957x

Shared-host timing variation remains material. Standard construction is an unchanged System-tree control, and the measured delegates have identical IL between #558/#559. Timing movements in unchanged paths are not claimed as improvements. No universal speedup or master-parity claim is made. Compile-only costs remain above master; compile plus first invocation is much closer because exception-aware anonymous hosting can move JIT/access checks into delegate creation.

Allocations: compilation, compilation plus first invocation and warm invocation match #558 in every measured shape/configuration. Light construction reduces allocation in 6/19 shapes and matches 13/19, with no increases: about 32 B per named-field lookup, or 1,024 B in the 32-field-construction shape. One unchanged .NET 8 Standard construction control has an unexplained allocation movement; full data preserves it without attributing it to the patch.

Runtime: 76/76 actual root-plus-returned-nested instruction bundles match exactly between #558/#559. Runtime is also measured independently; no delegate execution speedup is claimed.

System: a separate unchanged reference, with no before/after columns or PR-stage delta. It is also the untimed behavior oracle. FEC is not universally faster than System; complete values are retained.

Validation and evidence

Release solution build passed; native Standard/Light .NET 8/10 runners passed (1,733 / 1,734 legacy cases plus registered fixtures); Framework passed 1,051 TestX + 1,617 legacy cases using English UI culture for existing message assertions. Imported runtime suite: 35,088 passed, 59 skipped, zero failures/crashes. Deep-tree cases remain deferred.

Current three-stage tables, all 304 result groups, standalone System values, exact IL comparison, raw samples, source/binary hashes, replay harness and full validation logs. Only current master, current #558 and final #559 are reported. #558 is unchanged by this work. Remains draft.

@jogibear9988
jogibear9988 force-pushed the perf-expression-compilation-and-runtime branch from 87187b3 to 73eef09 Compare October 24, 2026 15:35
@jogibear9988 jogibear9988 changed the title Improve expression construction and compilation performance (depends on #558) Reduce LightExpression named-field construction costs Oct 24, 2026
@jogibear9988

Copy link
Copy Markdown
Contributor Author

Rebased this performance layer onto current #558 (aaa79fe7) and pushed with an explicit lease against the previous #559 head. New head: 73eef09d; measured/validated production source: e300f4e2.

The vector-array classification patch was already present in #558, so it is dropped as redundant. The remaining production increment is the named LightExpression field lookup optimization (d4a74798). The PR description now focuses on that change and fresh measurements.

The report compares current master without #558 → current #558 → rebased #559, across 41 workloads, Standard/Light and .NET 8/10. System is shown separately as an unchanged reference, with no before/after delta; its earlier run-to-run variation did not mean System was modified.

Fixed-count compilation allocation checks: #558 lower/same/higher = 14/27/0 in each configuration; #559 matches #558 in 41/41. #558's observed compilation-time medians are 4.6–16.9% above master; #559's observed medians are 3.0–4.4% above #558. Standard source is identical across #558/#559, and Light's emitter is identical, so these timing movements do not establish a compilation benefit or a causal Standard regression from the field-lookup patch.

Longer Light construction checks show about 32 B less per affected named-field lookup and roughly 5.6–30.1% lower times in the selected affected workloads. All 82 root delegate IL bodies match between #558/#559; no runtime speedup is claimed.

Validation: 35,064 imported passes; zero executed failures/crashes; 69 unchanged exclusions. All production/native .NET 8/10/Framework checks and the benchmark-project build pass. Every workload, raw samples, confirmations, separate System references, hashes and validation logs. This remains draft, and #558 was not changed.

@jogibear9988

Copy link
Copy Markdown
Contributor Author

One subsequent upstream development: master advanced to f49acef0 (through c89a3b2c) after these snapshots were frozen. Its nullable truth/Quote/invocation edits conflict with the parent compatibility layer in the two compiler files. This branch remains correctly rebased onto the requested #558 head aaa79fe7; #558 was not modified. The parent should integrate the new upstream semantics before refreshing this child layer. I documented the distinction in f9fc34bf and the PR description; the three-stage results stay pinned to their exact measured revisions. Latest branch head is f9fc34bf; draft retained.

Use unsigned-source checked conversion opcodes, zero-extend unsigned values widened to Int64, and sign-extend signed integers converted unchecked to UInt64. Apply ConvertChecked semantics and underlying enum conversions before constructing nullable targets. Keep the conversion switch and shared System/Light emitter layout.

Add boundary matrices for primitive numeric types and signed/unsigned enums, covering nullable inputs/outputs, null, overflow, NaN, and infinity. Update the existing signed-source IL assertion and propagate ITestX failures to the native runner exit code.

Validation: all 1,712 existing native tests plus the new regression methods pass on .NET 10; focused System/Light regressions pass on .NET 8. The 3,407-case imported conversion/coalesce slice improves from 353 failures to 39 failures, with no compiler fallback.
Implement planned nullable bool Not, AndAlso and OrElse support (dadhi#496) in the existing emitter. Share three-valued AND/OR result selection with the non-short-circuit operators while evaluating each operand once and respecting ignored results. Keep the nonnullable paths unchanged and reuse existing locals, nullable accessors and IL emission helpers.

Preserve nullable result types in the LightExpression And/AndAlso/OrElse factories using the existing left-typed node. Add shared System/Light truth-table, side-effect, exception, member-access, ignored-result and long-branch regressions, including direct Light factory construction. Require strict compilation in issue dadhi#480 and run its LightExpression variant.

Validation: all ten focused imported nullable Boolean cases now pass (previously ten failures). The adjacent logical slice retains its 79 passes and 40 existing custom-operator failures. The native .NET 10 runner passes all 1,712 existing tests and new ITestX regressions; 24 focused regression methods pass across System/Light on .NET 8.
Guard the existing collection and emission reduction loops against null or self reductions. Return the normal unsupported result instead of looping forever; leave system fallback and its validation exceptions unchanged. No extra graph traversal, reduction cache or rewrite phase is introduced.

Add native regressions for irreducible and null-reducing nodes, valid reduction chains with parameters, a reduction that stops progressing during emission, strict/throw/fallback contracts, and CompileFastToIL.

Validation: native .NET 10 runner passes all existing tests and the new regression groups. All 29 focused System/Light regression methods pass on .NET 8. The imported irreducible-extension cases now terminate immediately; their strict-system exception assertions remain failures because FEC uses its existing unsupported-expression contract.
Use the existing boxing helper before isinst, preserve void operand effects, and emit false for void/pointer targets with nonvoid operands. Keep TypeEqual unsupported.

Add shared Standard/LightExpression regressions for nullable, enum, struct, reference, constant and local operands, plus ignored results and side effects. All 28 focused imported type-test cases pass; all 1712 native .NET 10 tests and 37 focused regression methods pass. Broader imported classes still encounter the separate boxed-constant conversion defect.
Register all four regression groups for Standard and LightExpression where supported, including the strict original issue 480 reproduction.

Validation: .NET Framework passes 138 TestX methods and 1595 legacy tests with en-US UI culture supplied externally. Both libraries build all configured targets. All 37 new regression methods pass on .NET 8 and .NET 10. The full .NET 8 native runner retains the issue 346 crash reproduced on the parent branch; German UI culture retains two existing issue 261 error-text assertions.
Emit a boxed value for all reference-typed value constants, including ValueType, Enum and interfaces, using the existing constant emitter. Previously only object-typed constants were boxed, leaving invalid stack values that could crash casts and type tests.

Add shared Standard/LightExpression ITestX regressions for returned constants, type tests, interface calls and repeated closure constants. Eight new methods pass on .NET 8 and .NET 10; the full .NET 10 native runner passes. The imported cast/as/type-test slice now completes without crashes: 7243 pass and 12 fail across 7255 cases. Remaining failures expose separate ValueType unboxing behavior.
Recognize reference-to-value unboxing through assignment compatibility with the target's underlying value type. Include ValueType and nullable interface/Enum conversions while preserving LightExpression's user-defined conversion lookup.

Add shared checked/unchecked parameter and constant matrices comparing values and InvalidCastException/NullReferenceException behavior with the system compiler. Twelve reference regression methods pass on .NET 8 and .NET 10. The full .NET 10 native runner passes, including existing conversion-operator tests. Imported cast/type-test/unary-conversion slice: 7274 pass and two remaining nullable-boxing failures across 7276 cases, with no crashes.
Do not pass instance-call flags when loading nullable arithmetic operands: the emitter stores their values and explicitly takes the temporary address for HasValue/value access. Inlined lambda parameters become locals and previously loaded managed addresses into value slots, producing fatal CLR errors.

Add shared ITestX matrices for inlined arguments, block variables and captured parameters across seven primitive types and five operators. Six new methods pass on .NET 10. All 90 imported nullable lambda arithmetic cases now finish without crashes: 76 pass and 14 retain lifted decimal/unsigned division failures. The full .NET 10 native runner passes.
Box nullable values directly when converting to ValueType, Enum or interfaces, preserving null and validating incompatible interface casts. Keep nonnullable conversion paths and user-defined conversion lookup intact.

Add shared checked/unchecked constant, parameter and local matrices plus a direct-factory user-defined conversion regression. All 21 imported unary conversion cases pass. The full .NET 10 native runner, including the new regressions and existing exact IL checks, passes.
Extract nullable bounds with Value and use the existing checked numeric conversion helper to emit Int32 bounds before newarr or array construction. Preserve evaluation order and one evaluation per bound, including ignored array results.

All 4195 imported ArrayBoundsTests cases pass without crashes. Six new Standard/LightExpression regression methods pass, covering signed/unsigned bounds, nullable null, 64-bit overflow, multidimensional arrays and ignored results.
Look up operators using the underlying nullable operand types after their values have been extracted. This enables the existing lifted arithmetic path for decimal and custom structs, including operators with a different result type.

Six new Standard/LightExpression regression methods pass, covering decimal overflow, division by zero, custom structs and DateTime subtraction to TimeSpan. The 90-case imported nullable lambda arithmetic slice now has 86 passes and only four unsigned division failures, with no crashes.
Select div.un for unsigned division in the existing arithmetic opcode dispatch. Cover ordinary, lifted and compound-assignment forms with values crossing the signed range and divide-by-zero exceptions.

Six new Standard/LightExpression methods pass. All 138 imported nullable lambda arithmetic and adjacent binary division cases now pass, with no crashes.
Reuse nullable Not emission for primitive nullable complements, emit explicit nonlifted complement operator methods, and preserve operand effects when primitive results are ignored. Clear member-access flags when extracting nullable local values.

Seven new regression methods pass, including Standard-only explicit operator methods and shared Standard/LightExpression nullable matrices, member access and ignored results. All 87 imported complement/bitwise-not cases now pass without crashes. LightExpression's unsupported unary-method factory API remains unchanged.
Mark conversion operands as values and dereference nullable by-ref parameters for arithmetic/value contexts, while retaining nullable address behavior for member access. This prevents boxing a managed address when a ref parameter is read inside an array initializer.

Four new Standard/LightExpression methods pass for primitive, struct and nullable ref parameters. Combined by-ref conversion and increment/decrement matrices pass all twelve methods on .NET 10; the increment helper correction is a separate following commit.
Use correctly typed one constants, resolve struct/decimal operators, preserve nullable null and wrap small integer overflow in the existing increment helper. Restore a saved nullable operand before incrementing it. Apply the helper to local, parameter, field and ref assignment paths.

Eight new Standard/LightExpression regression methods pass, covering pre/post forms, nullable values, custom structs, numeric boundaries and ref parameters. All 1768 imported increment/decrement cases now finish without crashes: 1752 pass and 16 explicit-method cases remain unsupported. Full native suites pass on .NET 8 (1711 legacy tests) and .NET 10 (1712), including the previous baseline .NET 8 issue 346 crash and all registered regressions.
Carry unary operator metadata through the existing assignment/arithmetic emitters. Call supplied methods for primitive and reference values, lift methods accepting underlying values, and call nullable-parameter methods even for null operands.

Four Standard-only regression methods compare pre/post results, stored values and call counts for local, parameter, field and ref targets. All 1768 imported increment/decrement cases now pass without crashes. LightExpression's unsupported explicit unary-method factory remains unchanged.
Share receiver-aware dispatch for calls and property/indexer accessors: call struct-declared methods directly and constrain inherited/interface calls. Materialize addresses for value receivers in index get/set and plain member assignment paths, retain ref nullable addresses, and discard ignored getter results. Nullable null comparisons request address operands for HasValue explicitly.

Twelve Standard/LightExpression regression methods pass for Object methods, interface methods/properties/indexers, nullable ref receivers and null comparisons, mutable setters and getter side effects. The imported call/index/assignment slice now completes without crashes: 204 pass and 26 fail across 230 cases. No AST classes or public API surface were added.
Treat nullable null constants as nullable operands for relational and lifted comparisons, use unsigned ordering for unsigned strict comparisons, and construct actual nullable Boolean results for lift-to-null comparisons. Emit operands as values and keep result/address metadata consistent.

Six shared Standard/LightExpression regression methods pass across parameters, constants, nulls, unsigned boundaries, NaN/infinity, lifted member access and operand side effects. All 350 imported relational/equality cases pass.
Extract and widen non-null nullable left operands, convert fallback values consistently, preserve nullable null and boxing, and resolve implicit decimal conversions. Reuse existing numeric conversion/nullable helpers through a local coalesce conversion helper. Correct LightExpression coalesce inference to consider the unwrapped left type.

Six Standard/LightExpression regression methods pass across accepted numeric type pairs, nullable targets, reference results and operand side effects. Imported BinaryCoalesceTests: 179 pass and five explicit conversion-callback cases remain failing across 184 cases.
Evaluate goto values for side effects when the target is void, discard the value, and branch to the actual label, including leave from try/finally. Keep the adjacent-return optimization limited to non-void targets so void labels can continue within the block.

Add eight standard/Light regression methods covering Return, Goto and Break values, cleanup, inlining and labels followed by more work. Focused regressions and the full native .NET 10 runner pass.
The .NET Framework system compiler rejects valid implicit nullable-to-decimal coalesces with Unhandled convert. Compare against an explicit HasValue conditional and Convert instead, preserving the full matrix on every framework.

Validated all six standard/Light coalesce regression methods on .NET 10 and the full .NET Framework runner: 1,595 legacy tests and all registered TestX groups pass.
Honor explicit unary methods and lift nullable operands through the existing EmitIncOrDec helper, while preserving ignored effects and reading ref operands without assignment. Remove the duplicate primitive-only path.

All 208 imported unary increment/decrement cases pass. Five new regression methods pass against the integrated .NET 10 compiler and the isolated .NET 8 compiler.
Limit adjacent-return elimination to terminal labels. Preserve typed label values in existing label locals and branch to the target instead of returning from the containing lambda; retain the existing stack-based loop break convention. Reserve typed return labels before backward jumps.

Eight new standard/Light regressions pass, as do all 1,712 native .NET 10 tests. Imported Return/Goto/Break/MakeGoto slice improves from 223 pass / 72 fail to 265 pass / 30 fail (295 cases).
Emit ceq for reference comparisons instead of rediscovering overloaded equality or calling object.Equals. Carry the identity flag through existing Light logical binary nodes, system conversions, and optimized Not(Equal).

Eight new standard/Light regression methods pass. Imported reference comparison slice: 422 pass, 8 remaining boxed-constant identity failures out of 430 cases.
CLR 4 allocates 144 bytes for each closed generic constructor resolved with
GetMethodFromHandle. Its ordinary empty constructor lookup allocates nothing,
and value constructor lookup needs 96 bytes with the existing rented Type array.
Use those APIs for Framework only, and avoid initializing unused open handles.
Keep modern constructor handles and all closure representations unchanged.

Allocation-only causal probe: per-type constructor lookups 288 -> 96 bytes.
Separate-process warmed mutable-capture compilation: 5272 -> 5080 bytes (-192).
Activation stays 184 bytes, nested invocation 0, factory/invoke 184. Readonly
capture8/reference bytes stay unchanged. Forward/reverse fresh-process timing
medians observe mutable compilation ratio 0.927; unchanged-path timing variation
is retained and is not attributed to this metadata-only change.

Validation: normal Standard and Light production builds pass every configured
target with zero warnings/errors. CapturedValueRefRegressionTests passes 29
Standard and 32 Light cases on net472, net8.0 and net10.0. Parsing/compilation,
activation, nested runtime and factory/runtime measurements captured separately
for Framework baseline/current/candidate and modern current/candidate controls.
Scratch reproduction and raw data: work/pr558-repair/frame-capture-round2/.
For Framework nested lambdas with user parameters, declare the hidden argument
as ArrayClosureWithNonPassedParams. Existing Curry<C,...> already supports this
type, so capture accesses no longer need repeated closure casts. Record the
private method's exact signature in a status bit; public manual emitter contexts
keep their original cast behavior. Keep zero-user-argument signatures unchanged:
a separate cached array caused a measured cold allocation regression and was
rejected. No closure fields or runtime allocation layout changes.

Add regression coverage for private reference captures under exception handling,
zero-argument parent signature reuse, Action<T> currying and shared mutation,
debug flags, ordinary parent signature rentals and concurrent compilation.

Framework warmed and first-use allocation: Func<T> and EH Func<T> compile -120 B;
Action<T> -64 B; constant-folded dead capture unchanged. Runtime activation,
nested invocation and factory/invoke allocation unchanged. Modern control bytes
unchanged. Parsing/runtime timings are captured independently; mixed compiler
ratios and VM variability do not establish a CPU guarantee. No extra first-use
signature array is allocated by this conservative version.

Validation: normal Standard and Light production builds pass all configured
targets without warnings/errors. CapturedValueRefRegressionTests: Standard30,
Light33 on net472/net8/net10. The broader typed prototype also passed full
Framework1003 TestX and1595 legacy tests before zero-argument paths were narrowed
back to the existing implementation. Scratch source/raw measurements and the
rejected no-argument cold tradeoff remain under work/pr558-repair/frame-capture-*.
Collect eligible root binary operands directly through the existing terminal
collector. This avoids JIT-compiling the large scoped visitor for a simple root,
without adding a prepass, changing closure storage, or bypassing jump checks on
complex trees. Preserve left-to-right failures, constant identity and public
cleanup. Trust ordinary Light nodes as requested; explicit intrinsic roots or
operands retain their existing collection callbacks and scopes.

Measured Standard add warm compile ratios: 0.961 (.NET 8), 0.927 (.NET 10).
Nine fresh-process add compile+first medians: -21% / -34%, identical bytes.
Framework add cold compile+first: 156.40 -> 118.90 ms; 27080 -> 27040 B.
Trusted Light paired add warm compile ratios: 0.961 / 0.920, 872 B unchanged.
All measured warmed allocation medians are unchanged, including invocation.
Cold and fallback timing controls varied substantially with host load; retain
all raw results rather than claim universal CPU parity or speedups. The rejected
Light generic-type-provenance prototype added 160/168 B cold and is not included.

Add public collector error-order and bound-constant identity regressions, plus
Light intrinsic callback/scope regressions. Full native Standard/Light suites:
1712 passing on .NET 10, 1711 on .NET 8. Separate Framework harness checks agree
with System results. Evidence: workspace work/pr558-round2/trivial-binary*, to be
published with the integrated round-2 report after final validation.
Enable the existing native boxed value-cell representation for nonnullable
Framework captures only in the normal fully collecting root entry. Carry
that root eligibility through nested contexts and require completed capture
analysis. Any exception region anywhere in the root, quote, runtime variables,
or unknown node retains the StrongBox representation throughout the root.
Manual and collection-skipping APIs never establish owned-root eligibility,
including their privately compiled children. Hosting/security policy and
the existing object[] closure layout are unchanged.

Add controls for sibling order, outer exception regions, flags/debug mode,
shared identity, and manual default/UserProvided collection. Reuse existing
struct, byref, enum, nullable, quote, runtime-variable and unknown-node tests.

Framework measurements against 7a871c4d, separate one-compiler processes:
- Mutable capture compile: 5080 -> 4760 B (-320 B). Pre-PR e4c232d is
  4376 B, so the remaining compile allocation residual is explicitly +384 B.
- Activation/invoke stay 184 B, nested invocation stays 0 B.
- Fresh first compile+invoke: 40272 -> 37888 B (-2384 B).
- Capture8/reference/protected capture compile and runtime bytes unchanged.
- All 32 modern runtime/scenario/phase allocation controls unchanged.
- Descriptive two-order warm medians: mutable compile 43.92 -> 41.92 us,
  activation 31.80 -> 30.23 ns, nested invocation 6.064 -> 5.993 ns.
  Three fresh cold medians: compile+first 246.84 -> 221.64 ms.
  Unchanged controls show substantial host timing variation; these timing
  observations do not establish universal CPU parity or causal speedups.

Validation: normal Standard/Light production builds all configured targets,
zero warnings/errors; focused Standard/Light fixture 32/35 on Framework,
31/34 on net8 and net10, all passing; full Framework en-US runner passes
1007 TestX and 1595 legacy tests. Initial German-culture run had only existing
English-message assertion failures. Independent source review found no
actionable correctness defect. Raw evidence: work/pr558-repair/frame-owned-boxed.
Restore FEC's original nested-delegate slot load only when the normal root
has completed capture analysis and proves owned hosting throughout without
Try, Quote, runtime variables or unknown nodes. Manual/UserProvided and
anonymous exception-hosted methods retain the exact verifier cast. Hosting,
closure layout and delegate/Curry construction are unchanged.

Against 25955bb5, four-shape one-compiler Framework probes show -32 B compile
for mutable, capture8 and reference captures, warm and cold; protected
capture_try and all runtime allocations are unchanged. Mutable compile is
4728 B versus pre-PR 4376 B, leaving +352 B explicitly unresolved.
Two opposite process-order warm rounds and three fresh cold reps measure
mutable compile 40.81 -> 36.21 us, activation 26.02 -> 24.88 ns,
nested invocation 5.467 -> 5.483 ns, compile+first 185.55 -> 183.44 ms.
Host timing variation in unchanged controls prevents universal CPU claims.

Normal Standard/Light builds all targets: zero warnings/errors. Focused
fixtures pass on Framework/net8/net10 (32/31/31 Standard, 35/34/34 Light).
Full Framework en-US runner: 1007 TestX +1595 legacy passing. Independent
source audit confirms the conservative owned-host guard. Evidence:
work/pr558-repair/frame-owned-delegate/measurements.json and raw logs.
Omit the hidden closure cast only under the same completed ordinary owned-root
proof as the nested delegate cast. Typed signatures keep their existing fast
path; all anonymous/manual/UserProvided/EH/Quote/RV/unknown cases keep exact
typing. No signature arrays, metadata caches, hosting or layout changes.

Against 3320b13e, Framework warm/cold compilation saves 208 B for mutable,
120 B for readonly reference and 264 B for capture8; protected capture_try
and runtime allocations are unchanged. Mutable compile is now 4520 B versus
pre-PR 4376 (+144 B); reference is 4256 versus 4264 (-8 B).
Two opposite process-order warm rounds and three fresh cold repetitions:
mutable compile 42.83 -> 38.82 us, nested invocation 5.730 -> 4.963 ns,
compile+first 239.07 -> 204.11 ms. Activation 25.67 -> 28.55 ns despite its
unchanged emitted code illustrates host noise; no universal CPU claim.

Normal Standard/Light builds all targets pass with zero warnings/errors.
Focused fixtures pass on Framework/net8/net10 (32/31/31 Standard,
35/34/34 Light), including protected siblings, manual contexts and shared
mutation. Combined final full suite is scheduled by the parent task.
Evidence: work/pr558-repair/frame-owned-closure/measurements.json and raw logs.
Reuse the existing atomic-operand predicate for non-lifted operands. Keep
complex right operands, nullable lifting, custom Light intrinsics and
operator evaluation order on their existing paths. No new metadata query,
cache, analysis pass or closure layout is introduced.

For 64 decimal additions under real finally blocks, isolated compilation
allocations fall from 11496 to 5664 B on modern .NET and 21136 to 7088 B on
Framework. Warm compile improves 16-30%, compile plus first execution
12-44%, and invocation 7-19% across Standard/Light on .NET 8/10/Framework.
Native fixture checks pass in all six configurations. Cold controls and
material timing outliers remain in the evidence; universal parity is not
established. Full combined validation follows this logical commit.
Emit non-byref try body and catch results with the existing value-parent
mask before storing the typed result temporary. Receiver, lvalue and
duplication flags must not cause a managed address to be stored as a
struct value. Preserve enclosing EH/inlining context and genuine byref
Try results; mark a loaded ordinary result as a value.

Fixes the introduced Light new-struct receiver regression and existing
variable/field/ref-parameter receiver forms, preserving mutation-copy
semantics. Register seven native regression methods for Standard/Light
on modern .NET and Framework. All six focused configurations pass40cases;
modern byref-return aliases remain correct. No hosting, metadata cache,
closure layout or empty-finally elimination is changed.

Performance controls retain unchanged runtime/activation allocations.
A genuine Framework Light compilation-history allocation residual of
192B in two capture controls remains documented; isolated controls match.
This correctness repair does not claim universal performance parity.
Full combined native/imported validation follows this logical commit.
Retain collision and stable-reference tests against the master implementation.
…ware methods

Address dadhi review discussion_r4318926508: passing the runtime anonymous module into the module overload violates an explicit CoreCLR assertion even though tested release runtimes accept it. Remove that module-reuse workaround and its lazy holder. Use the public restricted anonymous constructor on every target; ordinary owner-hosted methods are unchanged.

Keep raw/wrapped non-Exception catch matching and private-member access semantics. The supported constructor eagerly JITs at CreateDelegate, so compile-only timings must be reported separately from compile plus first execution. NonExceptionThrowPolicy, ExceptionRegionValidation and TryResultValue fixtures pass Standard/Light on net8.0, net10.0 and net472.
Resolve declared fields directly while preserving inherited/private/hidden fields and duplicate-metadata behavior. Save full construction, compilation and invocation comparisons, including per-lookup allocation reduction and the unreproduced property-test failure.
Keep semantic regression oracles from the rejected conditional-branch experiment; do not retain its inconsistent production optimization. Record 144 benchmark measurements, identical benchmark IL for kept changes, all target/native checks, and unchanged imported outcomes: 34945 pass, 152 existing failures, 36 skips, no new failures or observed crashes.
Keep alternating unused lambda signatures and earlier delegates under default, no-inlining and debug flags. The production pool return is already in master.
Call the existing terminal collector directly for parameter and constant left operands. Preserve nested-lambda capture analysis and intrinsic callbacks, while avoiding a recursive collector frame for these leaves. Emission is unchanged.

Paired screening against fab2d6bd: 41 workloads, construction/compilation/invocation, Standard and Light, .NET 8 and 10, 21 samples each. Compilation allocations match for every case. Balanced256 compilation improves 2.6-6.9%; blocks32 improves 7.1-10.9%; overall workload median improves 0.6-1.0%, with mixed individual controls. Raw evidence will be retained with the current-master three-stage report. Native .NET 10: all 1718 tests pass.
Freeze master f49acef, locally merged dadhi#558 at 593c72e2, and optimized dadhi#559 source ef577910. Record 44 workloads, Standard/Light, .NET 8/10, construction/compilation/warm invocation plus a separate compilation-and-first-invocation pass. Retain 34496 main/reference samples, per-commit allocation/timing evidence, actual root and nested IL strings, source/binary hashes, rejected candidates and validation logs.

Compile-only medians improve 0.7-2.2% versus dadhi#558 but remain 8.2-9.9% above master. Compilation plus first invocation remains 0.1-1.5% above master on median workload ratios. The apparent nested-exception compile-only outlier shifts JIT work into CreateDelegate for the correctness-required anonymous host; the end-to-end pass accounts for it. Keep slower System comparison cases and unchanged-code allocation controls explicit.

Correct the historical IL claim: its serializer compared StringBuilder metadata. The new actual-instruction check matches all 176 root/nested bundles between dadhi#558 and dadhi#559. Production and native suites, Framework 1051 TestX plus 1601 legacy tests, and all 35064 executed imported tests pass. Existing exclusions unchanged; production source unchanged by this evidence commit.
@jogibear9988
jogibear9988 force-pushed the perf-expression-compilation-and-runtime branch from f9fc34b to 93af41d Compare October 25, 2026 11:03
@jogibear9988 jogibear9988 changed the title Reduce LightExpression named-field construction costs Reduce expression collection and LightExpression construction costs Oct 25, 2026
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.

1 participant