Skip to content

Commit cb09ba0

Browse files
jtschusterCopilot
andcommitted
Implement NativeHashtable keyed Lookup and LookupInstanceMethodEntryPoint
Port the runtime VM's NativeHashtable.Enumerator.GetNext() to the managed reflection mirror. The bucket scan walks entries sorted by low hashcode, yields candidates whose low byte matches the lookup, and exits early when the scan passes the target. Add ReadyToRunReader.LookupInstanceMethodEntryPoint(section, versionResilientHash, predicate): performs the hashtable probe, decodes each candidate's signature, asks the caller's predicate to confirm the full key match, and on success returns the fully-decoded InstanceMethodPayload (runtime function index + fixup cell list). Document that Lookup/Enumerator only scan a single bucket and that the low hashcode byte is a probe filter requiring caller-side key verification. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2a7e779 commit cb09ba0

3 files changed

Lines changed: 98 additions & 6 deletions

File tree

src/ILCompiler.Reflection.ReadyToRun.Structural.Assertions/MethodInventory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ private static void AddInstanceMethodEntries(
205205
IReadOnlyList<ImportSectionSignatures> importSections,
206206
List<MethodInventoryEntry> output)
207207
{
208-
InstanceMethodEntryPointsTable table = reader.GetInstanceMethodEntryPointsTable(section);
208+
InstanceMethodEntryPointsTable table = reader.GetInstanceMethodEntryPointsHashTable(section);
209209

210210
foreach (InstanceMethodEntry entry in table.Entries)
211211
{

src/ILCompiler.Reflection.ReadyToRun.Structural/InstanceMethodEntryPointsTable.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,43 @@ internal InstanceMethodEntryPointsTable(List<InstanceMethodEntry> entries)
2828

2929
public partial class ReadyToRunReader
3030
{
31-
public InstanceMethodEntryPointsTable GetInstanceMethodEntryPointsTable(ReadyToRunSection section)
31+
/// <summary>
32+
/// Look up an instance-method entry by version-resilient hashcode and signature predicate.
33+
/// Mirrors the runtime VM's pattern of probing the NativeHashtable bucket for entries with
34+
/// a matching low-byte hash, decoding each candidate's signature, and asking the caller's
35+
/// predicate to confirm the full match (signature equality is decided by the caller because
36+
/// only it knows the original key being resolved).
37+
/// </summary>
38+
/// <returns>The decoded payload for the first matching entry, or <c>null</c> if no entry matches.</returns>
39+
public InstanceMethodPayload LookupInstanceMethodEntryPoint(ReadyToRunSection section, int versionResilientHash, Func<MethodSignature, bool> predicate)
3240
{
41+
int sectionOffset = GetOffsetForRVA(section.RelativeVirtualAddress);
42+
NativeParser parser = new NativeParser(_nativeReader, (uint)sectionOffset);
43+
NativeHashtable hashtable = new NativeHashtable(_nativeReader, parser, (uint)(sectionOffset + section.Size));
44+
NativeHashtable.Enumerator enumerator = hashtable.Lookup(versionResilientHash);
45+
46+
NativeParser entryParser = enumerator.GetNext();
47+
while (!entryParser.IsNull())
48+
{
49+
int payloadOffset = (int)entryParser.Offset;
50+
R2RSignatureDecodeResult signature = RawSignatureDecoder.DecodeMethodSignatureWithEndOffset(_nativeReader, payloadOffset, TargetPointerSize);
51+
MethodSignature methodSig = MethodSignature.FromSignature(signature.Signature);
52+
if (predicate(methodSig))
53+
{
54+
(RuntimeFunctionIndex runtimeFunctionIndex, FixupCellListHandle? fixupCellListHandle) = DecodeRuntimeFunctionIdAndFixupCellList(signature.EndOffset);
55+
return new InstanceMethodPayload(signature.Signature, runtimeFunctionIndex, fixupCellListHandle);
56+
}
57+
entryParser = enumerator.GetNext();
58+
}
59+
60+
return null;
61+
}
62+
63+
public InstanceMethodEntryPointsTable GetInstanceMethodEntryPointsHashTable(ReadyToRunSection section)
64+
{
65+
if (section.Type != Internal.Runtime.ReadyToRunSectionType.InstanceMethodEntryPoints)
66+
throw new InvalidOperationException();
67+
3368
int sectionOffset = GetOffsetForRVA(section.RelativeVirtualAddress);
3469
NativeParser parser = new NativeParser(_nativeReader, (uint)sectionOffset);
3570
NativeHashtable hashtable = new NativeHashtable(_nativeReader, parser, (uint)(sectionOffset + section.Size));

src/ILCompiler.Reflection.ReadyToRun/NativeHashtable.cs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,20 @@ public override string ToString()
148148
return sb.ToString();
149149
}
150150

151-
//
152-
// The enumerator does not conform to the regular C# enumerator pattern to avoid paying
153-
// its performance penalty (allocation, multiple calls per iteration)
154-
//
151+
/// <summary>
152+
/// Enumerates candidate matches for a hashcode lookup within a <em>single</em> hash bucket.
153+
/// <see cref="NativeHashtable.Lookup(int)"/> selects the bucket from the upper bits of the
154+
/// hashcode, and this enumerator walks that one bucket looking for entries whose low
155+
/// hashcode byte matches the lookup target. Bucket entries are sorted by low hashcode,
156+
/// so the scan terminates as soon as it passes the target.
157+
///
158+
/// Multiple matches per bucket are normal because the low byte is only an 8-bit probe
159+
/// filter; the caller must verify the full key against each entry returned by
160+
/// <see cref="GetNext"/>.
161+
///
162+
/// The enumerator does not conform to the regular C# enumerator pattern to avoid paying
163+
/// its performance penalty (allocation, multiple calls per iteration).
164+
/// </summary>
155165
public struct Enumerator
156166
{
157167
private NativeParser _parser;
@@ -164,6 +174,42 @@ internal Enumerator(NativeParser parser, uint endOffset, byte lowHashcode)
164174
_endOffset = endOffset;
165175
_lowHashcode = lowHashcode;
166176
}
177+
178+
/// <summary>
179+
/// Advance to the next bucket entry whose low hashcode byte matches the looked-up
180+
/// hashcode. Returns a parser positioned at that entry's payload, or a null parser
181+
/// when no more matches are available. Multiple matches are possible (low-byte hash
182+
/// collisions within the bucket); the caller must verify the full key against each
183+
/// returned entry.
184+
/// </summary>
185+
public NativeParser GetNext()
186+
{
187+
while (_parser.Offset < _endOffset)
188+
{
189+
uint entryStart = _parser.Offset;
190+
byte lowHashcode = _parser.GetByte();
191+
192+
if (lowHashcode == _lowHashcode)
193+
{
194+
// Rewind so GetParserFromRelativeOffset re-reads the byte and the
195+
// signed delta, returning a parser at the entry's payload.
196+
_parser.Offset = entryStart;
197+
return _parser.GetParserFromRelativeOffset();
198+
}
199+
200+
// The entries are sorted by low hashcode within the bucket, so once we
201+
// see a higher value we can terminate the scan early.
202+
if (lowHashcode > _lowHashcode)
203+
{
204+
_endOffset = _parser.Offset;
205+
break;
206+
}
207+
208+
// Skip past the signed relative-offset integer for this non-matching entry.
209+
_parser.GetSigned();
210+
}
211+
return default;
212+
}
167213
}
168214

169215
public struct AllEntriesEnumerator
@@ -225,6 +271,17 @@ private NativeParser GetParserForBucket(uint bucket, out uint endOffset)
225271
return new NativeParser(_imageReader, _baseOffset + start);
226272
}
227273

274+
/// <summary>
275+
/// Begin a hashcode lookup. Selects the single hash bucket addressed by the upper bits
276+
/// of <paramref name="hashcode"/> (<c>(hashcode &gt;&gt; 8) &amp; bucketMask</c>) and
277+
/// returns an <see cref="Enumerator"/> that walks <em>only that bucket</em>, yielding
278+
/// entries whose stored low hashcode byte equals <c>(byte)hashcode</c>.
279+
///
280+
/// Because the low byte is just an 8-bit probe filter, multiple matches may be returned
281+
/// from the same bucket; the caller is responsible for verifying the full key against
282+
/// each candidate. Entries whose full hashcode falls in a different bucket are never
283+
/// considered.
284+
/// </summary>
228285
public Enumerator Lookup(int hashcode)
229286
{
230287
uint endOffset;

0 commit comments

Comments
 (0)