IL emit hack: direct token writes via UnsafeAccessorType (NET10+), caller-hint overloads, and benchmark harness#515
Conversation
…t helpers, NET10 benchmark class Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/dc0b3120-928b-481e-ac2d-183f3609f945 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
… duplication Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/dc0b3120-928b-481e-ac2d-183f3609f945 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
dadhi
left a comment
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
I already saw this combination for specialized emit, let compress it to the same inliniable check method
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Again, can be a one liner check with method
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Can we specialize it more for specific store and load and remove the checks.
There was a problem hiding this comment.
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>
Done in 0324a82:
|
Wires the NET10+
SUPPORTS_IL_EMIT_HACKpath into FEC's emission loop so all token-emittingDemitcalls (MethodInfo,ConstructorInfo,FieldInfo) bypassILGenerator.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, callsEnsureCapacity, looks upDynamicScope.GetTokenFor()(hashtable) — the hack writes bytes directly:Changes
ILGeneratorTools(FastExpressionCompiler.cs)#define SUPPORTS_IL_EMIT_HACKunder#if NET10_0_OR_GREATERUseILEmitHackflag — startsfalse; enabled at end ofDynamicMethodHacksstatic init once_mScopeFieldis confirmed resolvedDemit(il, opcode, MethodInfo/ConstructorInfo/FieldInfo)gain#if SUPPORTS_IL_EMIT_HACKfast paths — all ~92 token-emitting call sites in FEC benefit automaticallyDemitCallStaticVoid,DemitCallStatic,DemitCallInstanceVoid,DemitCallInstance,DemitNewobj— skipIsStatic/ReturnType/GetParameterswhen the call site already knows themDemitLdfld,DemitStfld,DemitLdsfld,DemitLdflda— hardcode the opcode and stack change, bypassing the runtime opcode switch inHackEmitField; all 38 field-access call sites updated to use theseCanUseHackEmit(declaringType, il)— single inlinable guard deduplicating theDynamicILGeneratorType+ non-generic check across all helpersHackEmitMethod,HackEmitCtor,HackEmitFieldguards compressed to single expressionsDynamicMethodHacks_mScopeFieldstatic +GetScopeTokens(il)— resolvesDynamicScope.m_tokensref (oneFieldInfo.GetValueform_scope, then UAT form_tokens)HackEmitMethodToken,HackEmitFieldToken(promoted tointernal),HackEmitMethod,HackEmitCtor,HackEmitFieldcore helpersFallbacks (correctness)
GenericMethodInfo/GenericFieldInfo— cannot construct without reflectionRuntimeILGenerator(e.g.MethodBuilder.GetILGenerator())m_scopeonly exists onDynamicILGeneratorBenchmark & tests
net10.0added toFastExpressionCompiler.Benchmarks.csprojEmitHacks.Net10Emitbenchmark comparing baselineil.Emit,Demit_NoHack,Demit_WithHack, raw UAT hackEmitHacksTest:DynamicMethod_Demit_WithHack/DynamicMethod_Demit_NoHackround-trip tests