perf(dotnet): index declaring-type ranges once per metadata table (#918)

perf(dotnet): index ordered declaring-type ranges once per table
This commit is contained in:
morluto authored and GitHub committed 2026-10-07 17:48:39 +08:00
1 parent 900d7c8606
commit 7a9f587c3a
3 files changed
+137 -3

No files matched your search

@@ -0,0 +1,96 @@
import { fc, it } from "@fast-check/vitest";
import { describe, expect } from "vitest";
import {
createDeclaringTypeLookup,
declaringType,
type TypeRange,
} from "./ManagedMemberInspectorCore.js";
const range = (
name: string,
fieldStart: number,
fieldEnd: number,
methodStart = fieldStart,
methodEnd = fieldEnd,
): TypeRange => ({
token: name,
fullName: name,
fieldStart,
fieldEnd,
methodStart,
methodEnd,
});
describe("managed declaring type ownership", () => {
it("resolves field and method boundaries independently, including empty types and gaps", () => {
const ranges = [
range("first", 1, 3, 1, 1),
range("empty", 3, 3, 1, 1),
range("last", 5, 7, 1, 4),
];
const field = createDeclaringTypeLookup(ranges, "field");
const method = createDeclaringTypeLookup(ranges, "method");
expect([0, 1, 2, 3, 4, 5, 6, 7].map(field)).toEqual([
null,
{ token: "first", fullName: "first" },
{ token: "first", fullName: "first" },
null,
null,
{ token: "last", fullName: "last" },
{ token: "last", fullName: "last" },
null,
]);
expect([0, 1, 2, 3, 4].map(method)).toEqual([
null,
{ token: "last", fullName: "last" },
{ token: "last", fullName: "last" },
{ token: "last", fullName: "last" },
null,
]);
});
it("retains the first observed owner for overlapping, reversed, or out-of-order ranges", () => {
const ranges = [
range("first", 5, 9),
range("overlap", 1, 7),
range("reversed", 10, 2),
];
const lookup = createDeclaringTypeLookup(ranges, "method");
expect(lookup(6)).toEqual({ token: "first", fullName: "first" });
expect(lookup(2)).toEqual({ token: "overlap", fullName: "overlap" });
expect(lookup(10)).toBeNull();
expect(createDeclaringTypeLookup([], "method")(1)).toBeNull();
});
it.prop([
fc.array(
fc.record({
fieldStart: fc.integer({ min: 0, max: 30 }),
fieldEnd: fc.integer({ min: 0, max: 30 }),
methodStart: fc.integer({ min: 0, max: 30 }),
methodEnd: fc.integer({ min: 0, max: 30 }),
}),
{ maxLength: 20 },
),
])(
"agrees with observed first-match ownership for arbitrary metadata ranges",
(raw) => {
const ranges = raw.map((value, index) => ({
...value,
token: String(index),
fullName: `Type${String(index)}`,
}));
for (const table of ["field", "method"] as const) {
const lookup = createDeclaringTypeLookup(ranges, table);
for (const row of [
NaN,
-Infinity,
Infinity,
...Array.from({ length: 32 }, (_, i) => i),
])
expect(lookup(row)).toEqual(declaringType(ranges, table, row));
}
},
);
});
+36
View File
@@ -150,6 +150,42 @@ export const declaringType = (
return null;
};
/** Resolve member ownership without rescanning ordered type ranges per row. */
export const createDeclaringTypeLookup = (
ranges: readonly TypeRange[],
table: "field" | "method",
): ((row: number) => ReturnType<typeof declaringType>) => {
const entries = ranges.map((range) => ({
range,
start: table === "field" ? range.fieldStart : range.methodStart,
end: table === "field" ? range.fieldEnd : range.methodEnd,
}));
const ordered = entries.every((entry, index) => {
const previous = entries[index - 1];
return (
entry.start <= entry.end &&
(previous === undefined || previous.end <= entry.start)
);
});
// Malformed metadata can overlap or reverse ranges. Retain the original
// first-match interpretation rather than guessing a different owner.
if (!ordered) return (row) => declaringType(ranges, table, row);
return (row) => {
let first = 0;
let end = entries.length;
while (first < end) {
const middle = first + Math.floor((end - first) / 2);
const entry = entries[middle];
if (entry === undefined) return null;
if (row >= entry.start && row < entry.end)
return { token: entry.range.token, fullName: entry.range.fullName };
if (row < entry.start) end = middle;
else first = middle + 1;
}
return null;
};
};
export const parseTypes = (
bytes: Buffer,
layout: ManagedMetadataLayout,
+5 -3
View File
@@ -1,7 +1,7 @@
import type { ManagedPeLayout } from "./ManagedPeReader.js";
import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js";
import {
declaringType,
createDeclaringTypeLookup,
signature,
type FieldCore,
type ManagedCallEdge,
@@ -33,6 +33,7 @@ export const parseFields = (
const fields: ManagedField[] = [];
const core = new Map<string, FieldCore>();
const table = layout.table(4);
const declaringType = createDeclaringTypeLookup(ranges, "field");
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
const cursor = metadataRowCursor(bytes, layout, 4, row);
const flags = cursor.readUInt16();
@@ -48,7 +49,7 @@ export const parseFields = (
cursor.readIndex(layout.blobIndexSize),
layout.blob.size,
);
const declared = declaringType(ranges, "field", row);
const declared = declaringType(row);
const token = metadataToken(4, row);
fields.push({
token,
@@ -123,6 +124,7 @@ export const parseMethods = ({
const methods: ManagedMethod[] = [];
const core = new Map<string, MethodCore>();
const table = layout.table(6);
const declaringType = createDeclaringTypeLookup(ranges, "method");
for (let row = 1; row <= (table?.rowCount ?? 0); row += 1) {
const cursor = metadataRowCursor(bytes, layout, 6, row);
const rva = cursor.readUInt32();
@@ -141,7 +143,7 @@ export const parseMethods = ({
layout.blob.size,
);
cursor.readIndex(layout.tableIndexSize(8));
const declared = declaringType(ranges, "method", row);
const declared = declaringType(row);
const token = metadataToken(6, row);
methods.push({
token,