Skip to content

Publish semantic MethodDef identity from Method Query Source - #9970

Merged
richlander merged 3 commits into
mainfrom
feature/semantic-identity-shared-reader
Oct 12, 2026
Merged

richlander merged 3 commits into
mainfrom
feature/semantic-identity-shared-reader

Conversation

@richlander

@richlander richlander commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

Refs #9830. This is prerequisite 3 of 6 for the unsafe member census on Assembly Analysis Operation (#5254). The census keys each finding by the declared member's MethodIdentity (Unsafe member findings). The exact-member consumer in #9832 needs the same identity.

This PR adds a SemanticIdentity layer to Method Query Source: per-method depth that publishes a MethodDef's detached structural identity.

One owner, two sources. MethodSemanticIdentityReader takes over identity decoding from LibraryBodyPrimaryMetadataResolver. Library Body Analysis now builds its MethodIdentity through it, so the two paths can't diverge. This follows the pattern SameModuleMethodResolver set in #9938.

Supersedes #9874, which publishes semantic identity only for exact-MethodDef plans, through a second decoder in MethodRowGate. The census needs library and type scope. This PR serves every breadth and moves identity decoding out of Library Body Analysis instead of adding a copy beside it.

Normative owner: docs/design/method-query-source.md#semantic-identity.

What the layer publishes

MethodSemanticIdentity has every structural field of MethodIdentity except CallerUnsafeMode:

  • assembly name and MVID;
  • declaring TypeRef;
  • name, token, and static and extension flags;
  • parameter and return types;
  • generic arity and parameter names;
  • signature header and vararg required count;
  • the invalid-generic-declaration and virtual-dispatch facts.

ToMethodIdentity(mode) composes it with a caller-unsafe mode from the separately declared MemorySafetyContracts support (#9831). That dependency stays visible in the declaration and the receipts.

The result is typed:

Outcome Meaning
Decoded The full identity.
SignatureBoundExceeded The signature passes the depth guard's limit. The identity is still published, degraded exactly as MethodIdentity is today.
Malformed The metadata can't be read. No identity is published, and the producer decides whether to fail the unit.

The layer:

  • acquires no body;
  • doesn't imply ModuleLookup;
  • doesn't arm the IdentityText budget;
  • decodes each MethodDef once per execution group, shared across lanes.

MethodSemanticIdentityCoverage receipts:

  • the one read of the assembly name and MVID;
  • signature bytes;
  • the type decoder's structural work;
  • GenericParam and CustomAttribute rows;
  • name bytes;
  • the methods decoded or read;
  • a count of each outcome.

The work is counted with a new observe-only mode of TypeRefDecoder, which counts work without changing how it decodes.

Evidence

Parity: MethodQuerySource_SemanticIdentityMatchesLibraryBodyAnalysis covers every MethodDef in:

  • the callee-resolution fixture;
  • the Analysis test assembly;
  • System.Collections.Immutable;
  • System.Linq.

The source issues Library Body Analysis's MethodIdentity for every method it publishes, and Malformed for every other. A shape check confirms the subjects include extension, generic, nested-type, bodiless, and virtual-dispatch methods.

Synthetic cases: each SameModuleTarget_* case now also asserts identity parity. New cases:

  • a vararg definition keeps its required count;
  • a signature past the depth guard is SignatureBoundExceeded;
  • a truncated class token is a typed Malformed outcome, which Library Body Analysis omits.

Receipt and boundary gates:

  • absent when undeclared;
  • requires declaration;
  • exact method work equals a fresh oracle decode, and a hand count matches signature bytes, name bytes, and rows;
  • a bodiless method gets an identity with no body attempted;
  • decoded once across lanes.

Suites (Release):

Suite Passed Skipped
ILInspector.Analysis.Tests 2,304 8
ILInspector.Research.Tests 414 0

Performance

NativeAOT linux-x64 on perf-2 under perf-guard (CPU 96.90% idle), 2026-10-12T21:20:12Z–21:30:12Z, 25 interleaved samples per side after 3 warm-ups. Base is 38cba3206 (main) and head is a1df36faa. Binary size: 147,622,912 → 147,614,784 bytes (−8,128).

No production producer declares SemanticIdentity yet. Both sections build a MethodIdentity for every method through Library Body Analysis, which now goes through the shared reader.

Section Assembly Base median / p95 Head median / p95 Δ median Rows Output
Unsafe Members System.Runtime.Numerics 145.2 / 149.1 ms 146.1 / 149.2 ms +0.9 ms (+0.6%) 239 identical
Unsafe Members System.Text.Json 299.5 / 306.3 ms 298.2 / 307.1 ms −1.3 ms (−0.4%) 314 identical
Unsafe Members System.Private.CoreLib 2436.6 / 2458.8 ms 2419.8 / 2483.0 ms −16.8 ms (−0.7%) 14,128 identical
Top Leverage System.Runtime.Numerics 190.3 / 194.4 ms 190.7 / 193.5 ms +0.4 ms (+0.2%) 864 identical
Top Leverage System.Text.Json 628.6 / 668.3 ms 631.9 / 668.1 ms +3.3 ms (+0.5%) 4,395 identical
Top Leverage System.Private.CoreLib 7053.4 / 7216.1 ms 6990.6 / 7258.3 ms −62.8 ms (−0.9%) 42,706 identical

Every difference is within noise. The layer's own cost will be measured when the census adopts it.

Review

  • Round 1: CLEAN at a1df36faa. Four non-blocking observations carry forward to the census slice:
    • the kernel path doesn't wire declared supports;
    • multipass re-decode;
    • a stronger row-count gate;
    • parity mostly checks composition.

Non-claims

🤖 Generated with Claude Code

richlander and others added 3 commits October 12, 2026 13:13
Add the SemanticIdentity layer: per-method depth that publishes a
MethodDef's detached structural identity, decoded once per physical
MethodDef, shared across lanes, and receipted. MethodSemanticIdentityReader
becomes the single owner of identity decoding; Library Body Analysis builds
its MethodIdentity through it. An observing TypeRefDecoder counts decode work
without changing results.

Refs #9830

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give the shared semantic-identity reader its own unreadable-metadata filter
instead of borrowing LibraryMethodAnalysisRunner's, so the new owner has no
dependency on Library Body Analysis.

Refs #9830

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@richlander

Copy link
Copy Markdown
Owner Author

Round 1 reconciliation (head a1df36faa)

Reviewer: Claude Opus. CLEAN. The reviewer confirmed by reading the code:

  • Library Body Analysis still builds exactly the same MethodIdentity. Every caller passes CreateScope of the same method, the helpers moved verbatim, and the extension check and exception set are unchanged.
  • The observing TypeRefDecoder decodes identically to Instance, and the budgeted mode is unchanged.
  • Receipts count every read once, both physically and per request.
  • Malformed is reachable only through unreadable metadata.
  • The layer is rejected when undeclared, and no planner path drops it.

Four non-blocking observations. None needs a push here; they carry forward to the census slice, the first consumer:

  • A. The kernel path doesn't wire declared supports. MethodDefinitionPredicates builds its unit without the semantic-identity support, as it already does for memory-safety and call-target support. A single all-definition predicate or query producer that declares any of these layers would fail visibly instead of answering. A library-scope census Rows producer has exactly that shape, so the census slice wires the supports into the kernel, or keeps such producers off it, with a gate.
  • B. Multipass re-decode. A later pass builds a new unit, so a producer and a dependency that both declare SemanticIdentity in different passes decode twice. The census slice either keeps the decode across passes or narrows the claim to "per pass".
  • C. Weak row-count gate. The exact-work gate checks GenericParam and CustomAttribute rows only as zero. The census slice adds a generic extension method on a generic or static type, with independently derived row counts.
  • D. Parity mostly checks composition. Library Body Analysis now shares the reader, so parity mostly checks composition. Equivalence with the prior MethodIdentity rests on the verbatim move, which the reviewer checked, plus the existing suites.

Adding review-clean.

🤖 Generated with Claude Code

@richlander richlander added the review-clean PR has met the adversarial review standard per AGENTS.md. label Oct 12, 2026
@richlander
richlander merged commit 15d49e8 into main Oct 12, 2026
15 checks passed
@richlander
richlander deleted the feature/semantic-identity-shared-reader branch October 12, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-clean PR has met the adversarial review standard per AGENTS.md.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant