Skip to content

IL emit hack: direct token writes via UnsafeAccessorType (NET10+), caller-hint overloads, and benchmark harness#515

Draft
Copilot wants to merge 4 commits intocopilot/use-unsafeaccess-net8-and-net10from
copilot/sub-pr-510
Draft

IL emit hack: direct token writes via UnsafeAccessorType (NET10+), caller-hint overloads, and benchmark harness#515
Copilot wants to merge 4 commits intocopilot/use-unsafeaccess-net8-and-net10from
copilot/sub-pr-510

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 13, 2026

Wires the NET10+ SUPPORTS_IL_EMIT_HACK path into FEC's emission loop so all token-emitting Demit calls (MethodInfo, ConstructorInfo, FieldInfo) bypass ILGenerator.Emit() overhead in a single place, with no call-site changes.

What the hack does

Instead of ILGenerator.Emit(OpCodes.Call, meth) — which switches on operand type, calls EnsureCapacity, looks up DynamicScope.GetTokenFor() (hashtable) — the hack writes bytes directly:

// Hack path (non-generic types, DynamicILGenerator only)
mILStream[mLength++] = (byte)OpCodes.Call.Value;
ref var tokens = ref GetScopeTokens(il);   // one FieldInfo.GetValue for m_scope
tokens.Add(meth.MethodHandle);             // direct List.Add
BinaryPrimitives.WriteInt32LittleEndian(mILStream.AsSpan(mLength), (tokens.Count - 1) | 0x06000000);
mLength += 4;
UpdateStackSize(il, opcode, stackChange);

Changes

ILGeneratorTools (FastExpressionCompiler.cs)

  • #define SUPPORTS_IL_EMIT_HACK under #if NET10_0_OR_GREATER
  • UseILEmitHack flag — starts false; enabled at end of DynamicMethodHacks static init once _mScopeField is confirmed resolved
  • Demit(il, opcode, MethodInfo/ConstructorInfo/FieldInfo) gain #if SUPPORTS_IL_EMIT_HACK fast paths — all ~92 token-emitting call sites in FEC benefit automatically
  • Caller-hint specialized helpers: DemitCallStaticVoid, DemitCallStatic, DemitCallInstanceVoid, DemitCallInstance, DemitNewobj — skip IsStatic/ReturnType/GetParameters when the call site already knows them
  • Specialized field helpers: DemitLdfld, DemitStfld, DemitLdsfld, DemitLdflda — hardcode the opcode and stack change, bypassing the runtime opcode switch in HackEmitField; all 38 field-access call sites updated to use these
  • CanUseHackEmit(declaringType, il) — single inlinable guard deduplicating the DynamicILGeneratorType + non-generic check across all helpers
  • HackEmitMethod, HackEmitCtor, HackEmitField guards compressed to single expressions

DynamicMethodHacks

  • _mScopeField static + GetScopeTokens(il) — resolves DynamicScope.m_tokens ref (one FieldInfo.GetValue for m_scope, then UAT for m_tokens)
  • HackEmitMethodToken, HackEmitFieldToken (promoted to internal), HackEmitMethod, HackEmitCtor, HackEmitField core helpers

Fallbacks (correctness)

Case Reason
Generic / array declaring type Requires internal GenericMethodInfo/GenericFieldInfo — cannot construct without reflection
RuntimeILGenerator (e.g. MethodBuilder.GetILGenerator()) m_scope only exists on DynamicILGenerator

Benchmark & tests

  • net10.0 added to FastExpressionCompiler.Benchmarks.csproj
  • EmitHacks.Net10Emit benchmark comparing baseline il.Emit, Demit_NoHack, Demit_WithHack, raw UAT hack
  • EmitHacksTest: DynamicMethod_Demit_WithHack / DynamicMethod_Demit_NoHack round-trip tests

Copilot AI changed the title [WIP] Use UnsafeAccessor for non-public IL generator internals IL emit hack: direct token writes via UnsafeAccessorType (NET10+), caller-hint overloads, and benchmark harness Apr 13, 2026
Copilot AI requested a review from dadhi April 13, 2026 22:37
Copy link
Copy Markdown
Owner

@dadhi dadhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analyze callsites and repeated condition checks through collchain, the goal is to benifit from our use pattern and simplify / avoid garbage code along the call chain if we akready checked the invariants in the boundaries.
Also compact comments a bit, more focus and meaning and less buatifying fences and non default unicode symbols please

// Fall back for methods on generic/array types: DynamicScope requires GenericMethodInfo
// (an internal runtime type) for these; we cannot construct it without reflection overhead.
var declaringType = meth.DeclaringType;
if (declaringType != null && (declaringType.IsGenericType || declaringType.IsArray))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already saw this combination for specialized emit, let compress it to the same inliniable check method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0324a82. The two-condition guard is now a single expression: if (il.GetType() != DynamicILGeneratorType || (dt != null && (dt.IsGenericType || dt.IsArray))) { il.Emit(opcode, meth); return; }


// Fall back for ctors on generic/array types
var declaringType = ctor.DeclaringType;
if (declaringType != null && (declaringType.IsGenericType || declaringType.IsArray))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same compressiin needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0324a82 — same single-expression guard applied to HackEmitCtor.

// calls FieldInfo.GetFieldFromHandle(handle) without a type context, which throws for generic types.
// Also DynamicILGenerator.Emit always stores GenericFieldInfo (not RuntimeFieldHandle) for such fields.
var declaringType = field.DeclaringType;
if (declaringType != null && (declaringType.IsGenericType || declaringType.IsArray))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, can be a one liner check with method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0324a82 — same single-expression guard applied to HackEmitField.

// Ldsfld/Ldsflda: push value/addr → net +1
// Stsfld: pop value → net -1
int stackChange;
if (opcode.Equals(OpCodes.Ldfld) || opcode.Equals(OpCodes.Ldflda))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we specialize it more for specific store and load and remove the checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0324a82. Added DemitLdfld, DemitStfld, DemitLdsfld, DemitLdflda helpers that hardcode the opcode and stack change, bypassing the runtime opcode switch in HackEmitField. All 38 call sites updated to use the specialized versions. HackEmitFieldToken promoted to internal so ILGeneratorTools calls it directly.

…, update call sites, compact comments

Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/9613f5f4-79a9-4a47-ad46-14c291909fb4

Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

Analyze callsites and repeated condition checks through collchain, the goal is to benifit from our use pattern and simplify / avoid garbage code along the call chain if we akready checked the invarian...

Done in 0324a82:

  • All unicode box-drawing fences (─────), bullets (), and arrows () removed from comments and replaced with plain text
  • HackEmitMethod/HackEmitCtor/HackEmitField guards compressed to single expressions
  • Added DemitLdfld/DemitStfld/DemitLdsfld/DemitLdflda so call sites that know the field opcode skip both the HackEmitField dispatch and the HackEmitFieldToken opcode switch — 38 call sites updated

Copilot AI requested a review from dadhi April 14, 2026 06:18
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.

2 participants