Use UnsafeAccessor (NET8+) and UnsafeAccessorType (NET10+) for non-public IL generator internals#510
Use UnsafeAccessor (NET8+) and UnsafeAccessorType (NET10+) for non-public IL generator internals#510
Conversation
… enable EmitHacksTest Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/e521f323-57a4-40f5-aea8-5ae1e3bbbff0 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
…ksTest Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/e521f323-57a4-40f5-aea8-5ae1e3bbbff0 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/c6380203-0960-4499-bfad-edb39dd3fc55 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
Done in c0a8a45. Added |
dadhi
left a comment
There was a problem hiding this comment.
- address comments
- general question - does usage of UAT attribute and such helps with aot?
- what other places in fec we can apply it to and what are there potential wins?
There was a problem hiding this comment.
I forgot what are we testing in EmitHacks. Is it something that we already using in Fec or something notvyrt applied. If latter - can ee apply it now with the fixes in place.
There was a problem hiding this comment.
EmitHacksTest demonstrates direct IL stream manipulation — writing bytes straight to m_ILStream (and updating m_length/stack tracking via UpdateStackSize) instead of going through ILGenerator.Emit(). This is a future optimization path that is not yet applied to FEC's main emission loop.
What IS already applied in DynamicMethodHacks:
GetNextLocalVarLocationuses UAT (NET10+) / a startup-generatedDynamicMethod(NET8–9) to accessm_localSignatureandm_localCountto track locals — bypassing the overhead ofILGenerator.DeclareLocal().
What EmitHacksTest proves but FEC doesn't yet use:
- Bypassing
ILGenerator.Emit()entirely and writing opcodes/operands directly intom_ILStream. This would be a significant refactor of every emission site in FEC and is best tracked as a separate performance issue (e.g. issue Hack DynamicMethod #351 is already referenced in the hack comments).
Done in c0a8a45. Added
Does Yes — it's actually one of the primary motivations. Standard Other places in FEC where UAT could be applied and potential wins:
The biggest remaining win would be hooking up the direct IL stream writes (EmitHacksTest approach) to FEC's actual emission loop, but that's a larger refactor best done as a follow-on issue. |
|
@copilot could you assess more on the wins of EmitHackTests approach explaining what is it? |
What the EmitHacksTest approach actually does Normal
The hack approach skips steps 1–2 and replaces step 4 for member tokens: // Normal path (per call: switch + bounds check + virtual dispatch + token lookup)
il.Emit(OpCodes.Call, meth);
// Hack path (after setup: direct byte write + list.Add)
mILStream[mLength++] = (byte)OpCodes.Call.Value;
mTokens.Add(meth.MethodHandle); // direct list write
var token = mTokens.Count - 1 | 0x06000000;
BinaryPrimitives.WriteInt32LittleEndian(mILStream.AsSpan(mLength), token);
mLength += 4;
UpdateStackSize_Net10(il, OpCodes.Call, stackChange); // same as normalPotential wins for FEC FEC has ~482
The comments in FEC already point at exactly this ( The pre-sizing path ( In practice, the biggest measurable win would be on the token-emitting opcodes ( The reason it hasn't been applied yet is that it requires pre-computing the exact byte budget per expression tree node (to pre-size correctly and avoid the |
Accessing private fields of
RuntimeILGeneratorandDynamicScope(both internal .NET types) previously required generatingDynamicMethodwrappers at startup via reflection. NET10'sUnsafeAccessorTypeenables direct, zero-reflection access to these non-public types.Changes
NET10+ —
DynamicMethodHacks.GetNextLocalVarLocation(hot path)Replaces the reflection-based
DynamicMethodgeneration at startup with directUnsafeAccessorTypeaccessors:Six new
[UnsafeAccessorType]accessors added toDynamicMethodHackstargeting:RuntimeILGenerator:m_localSignature,m_localCount,m_length,m_ILStream,UpdateStackSizeDynamicScope:m_tokensEmitHacksTest.cs— bug fixes + NET10+ demonstrationILGeneratorclass (wherem_length,m_ILStream,UpdateStackSizeare not declared). Fixed to useDynamicILGenerator.BaseType→RuntimeILGenerator.UpdateStackSizedelegate creation viaDelegate.CreateDelegatefails for methods on non-public declaring types. Replaced with aDynamicMethod-based wrapper usingskipVisibility: true.DynamicMethod_Emit_Hack_Net10()demonstrating the cleanUnsafeAccessorTypepath.EmitHacksTestin the test runner (was commented out due to the above bugs).Project targets
Added
net10.0to allTargetFrameworksand suppressedNU1510(new .NET 10 SDK warning) globally.Build and CI
net10.0test run tobuild.bat(before net9.0).net10.0test step to.github/workflows/build.yml(before net9.0).