Repository navigation
Reuse extension reductions in both expression compilers (follow-up to #579) - #584
Closed
jogibear9988 wants to merge 3 commits into
Closed
jogibear9988 wants to merge 3 commits into
jogibear9988 wants to merge 3 commits into
Conversation
Use CanReduce and ReduceAndCheck for unknown System expression node kinds in the existing collector and emitter. Keep validation in a private cold helper; preserve label operand scope. Test reduction chains, captured constants and invalid result types.
Keep reduced trees in a lazy per-compilation reference-identity cache so collection and emission observe the same locals, constants, and nested lambdas. Preserve ReduceAndCheck validation and leave LightExpression unchanged. Add regressions for fresh reductions, shared and distinct node identities, repeated compilation, and emission without collection. Verified all regression cases on net8.0, net10.0 and net472; full Release build passes. Existing master nullable MultiplyChecked parity failures remain unchanged.
Extend the lazy per-compilation reduction cache from dadhi#579 to ExpressionType.Extension for Standard and LightExpression. Collection and emission use the same reduced locals, constants, labels and nested lambdas. Preserve System validation for custom node kinds and existing null/self reduction rejection; intrinsic callbacks remain unchanged. Retain the parent per-node reference-identity policy: shared nodes reduce once per lambda compilation, distinct nodes stay separate, and later compilations get fresh reductions. Update the existing progress regression to assert reuse of a valid first reduction instead of requiring a second emission-time reduction. New regressions reproduce strict compilation failures on the parent and pass on net8.0, net10.0 and net472. Full solution, legacy suites and Framework runner pass; modern full runners retain only the existing nullable MultiplyChecked parity failures. Measured working controls emit identical IL and retain invocation allocations. Single-extension compilation adds a 120-byte lazy cache; ordinary controls retain compilation allocations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Superseded by #579 and closed to avoid a duplicate merge. The full Extension-reduction fix and its regressions are now included in #579, rebased onto upstream master
65ef6641as commit183ea000. No changes from this follow-up remain outside #579. The original proposal and measurements below are retained as historical evidence; no separate merge is needed.Follow-up to #579; please merge the parent first. The branch starts at its exact head
fcc70222f4b2b387ed7fa6bbd6c4092287f646e9and adds one fix commit (08b190e9). GitHub's formal base is master because the #579 branch lives in the fork. Review the incremental follow-up diff; #579 itself is unchanged.Problem and fix
Ordinary Extension nodes are reduced separately during collection and emission. A reducer that creates fresh locals, captured constants, labels or nested lambdas can therefore emit a different tree from the one whose identities were collected. The new regression fails strict compilation on #579 for both Standard and LightExpression, with Reduce called twice. With this follow-up, the same test returns the expected result and Reduce runs once.
Extend #579's existing lazy reduction cache to the shared Extension path. Both visitors use the same helper and the same reduced tree. The cache remains local to each lambda compilation and keyed by reference identity, using FEC's existing SmallMap. No new compiler traversal, expression rewrite phase or closure layout is introduced. Light intrinsic callbacks retain their existing dispatch before this path.
The policy matches #579: one reduced representation per source node per lambda compilation, including repeated references to that node. Distinct node instances remain separate even if they override equality; compiling the same source again obtains new reductions. No global cache is used.
System's unknown custom node kinds retain CanReduce/ReduceAndCheck validation. Extension nodes keep the existing null/self rejection contract shared with LightExpression. The existing reduction-progress test is updated to assert reuse of a valid first result, rather than requiring an unnecessary second reduction during emission. Invalid first reductions remain rejected, including emission without collection.
Validation
Performance and allocation cost
Compare only the exact #579 parent above with this follow-up. Windows ARM64, SDK 10.0.301, .NET 8.0.28/10.0.9, Release; tiering disabled, fixed CPU affinity. Expression construction/Light conversion is outside timed compilation. Strict FEC; 300 compilation warmups and 100,000 invocation warmups, two opposite-load-order processes, nine alternating samples per side, approximately 24 ms pair-calibration target, GC before samples and a no-GC region where available. Times below combine process medians geometrically.
Tradeoff: each tested single-extension compilation adds 120 B for the lazy cache, in both APIs. A stable reducer pays this cost too: the compiler cannot assume a second Reduce call would return the same tree. Larger reduction sets can require additional map storage. The cache is compilation-local; compiled delegates do not retain it.
Ordinary add, reference-constant and constructor controls retain the same compilation allocations. All measured controls retain identical decoded emitted IL and the same invocation allocations. Constructor invocation intentionally allocates its Marker object (24 B); other invocation controls allocate zero. The previously broken fresh-reduction cases are correctness tests, not a valid before/after performance baseline.
Timing remains noisy, with slower observations included below. No speedup or zero-compilation-overhead claim is made. Runtime timing differences below are observations of identical emitted control IL. Framework is correctness-tested; its timing was not measured.
Identical-binary parent/parent compilation control ranges across the two process medians: