From 64423297176442805a53086febd1da23cf5e75e5 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 18:00:19 +0800 Subject: [PATCH] fix(gpu): parse legacy PTX body parameters PTX 1.0 through 1.3 declares kernel parameters inside the entry body. Keep later-version parameter-list parsing unchanged. Fixes #6594 --- docs/gpu-code-objects.md | 5 +- src/gpu/PtxKernels.ts | 55 +++++++++- tests/acceptance/gpuCodeObjectsCli.test.ts | 116 +++++++++++++++++---- 3 files changed, 152 insertions(+), 24 deletions(-) diff --git a/docs/gpu-code-objects.md b/docs/gpu-code-objects.md index 0c377c216c..7cd3a8b5c5 100644 --- a/docs/gpu-code-objects.md +++ b/docs/gpu-code-objects.md @@ -81,8 +81,9 @@ Kernel representations have different meanings: - PTX reports parameter declarations and virtual registers. Shared bytes cover supported static declarations inside that entry; they do not establish module shared memory, dynamic allocation or physical device resources. - A `.target compute_xx` spelling is retained as written; NVIDIA PTX defines it - as a synonym for `.target sm_xx`. + PTX 1.0–1.3 declares entry parameters in the body; PTX 1.4 and later uses the + entry parameter list. A `.target compute_xx` spelling is retained as written; + NVIDIA PTX defines it as a synonym for `.target sm_xx`. Scalar register declarations count each bare or percent-prefixed name and each parameterized range. Unsupported declaration forms or overflowing totals retain an unknown count and partial decoding coverage. Unsupported parameter diff --git a/src/gpu/PtxKernels.ts b/src/gpu/PtxKernels.ts index b1edccf59c..438351ccb3 100644 --- a/src/gpu/PtxKernels.ts +++ b/src/gpu/PtxKernels.ts @@ -71,6 +71,16 @@ const integer = (token: string | undefined): number | null => { const value = Number(token); return Number.isSafeInteger(value) ? value : null; }; +type PtxVersion = { major: number; minor: number }; +const ptxVersion = (token: string | undefined): PtxVersion | null => { + const match = /^(\d+)\.(\d+)$/u.exec(token ?? ""); + if (match === null) return null; + const major = Number(match[1]), + minor = Number(match[2]); + return Number.isSafeInteger(major) && Number.isSafeInteger(minor) + ? { major, minor } + : null; +}; const sizeOf = (type: string | undefined): number | null => { const match = /^\.(?:b|u|s|f)(8|16|32|64|128)$/u.exec(type ?? ""); return match === null ? null : Number(match[1]) / 8; @@ -196,6 +206,7 @@ export function* readPtxKernels( }; let kernelCount = 0; let outerDepth = 0; + let version: PtxVersion | null = null; for (let step = iterator.next(); !step.done; step = iterator.next()) { const token = step.value; if (token.value === "{") { @@ -207,7 +218,13 @@ export function* readPtxKernels( outerDepth--; continue; } - if (outerDepth !== 0 || token.value !== ".entry") continue; + if (outerDepth !== 0) continue; + if (token.value === ".version") { + const value = iterator.next(); + version = value.done ? null : ptxVersion(value.value.value); + continue; + } + if (token.value !== ".entry") continue; const name = next(); if (!identifier.test(name.value)) rejectGpu("Malformed PTX entry name or parameters"); @@ -217,6 +234,12 @@ export function* readPtxKernels( const flush = (): void => { if (part.length === 0) return; if (part[0] !== ".param") rejectGpu("Unknown PTX parameter declaration"); + if (parameters.length >= 65536) + throw new BinaryResourceFailure( + "structure", + 65536, + "PTX parameter declarations exceed the facts budget", + ); const value = declaration(part); let offset: number | null = null; if ( @@ -246,7 +269,7 @@ export function* readPtxKernels( for (t = next(); t.value !== ")"; t = next()) { if (t.value === ",") flush(); else part.push(t.value); - if (part.length > 128 || parameters.length > 65536) + if (part.length > 128) throw new BinaryResourceFailure( "structure", 65536, @@ -280,6 +303,29 @@ export function* readPtxKernels( depth--; continue; } + if ( + t.value === ".param" && + version !== null && + version.major === 1 && + version.minor <= 3 + ) { + const bodyParameter = [t.value]; + for (t = next(); t.value !== ";"; t = next()) { + if (t.value === "}") + rejectGpu("Malformed PTX parameter declaration"); + if (bodyParameter.length >= 128) + throw new BinaryResourceFailure( + "structure", + 65536, + "PTX parameter declarations exceed the facts budget", + ); + bodyParameter.push(t.value); + } + part = bodyParameter; + flush(); + end = t.end; + continue; + } if (t.value !== ".reg" && t.value !== ".shared") continue; if (t.value === ".reg") { const { type, count } = registerDeclaration(next); @@ -401,12 +447,13 @@ export const readPtxTarget = (text: string): string | null => { break; if (token.value === ".version") { const value = stream.next(); - version = !value.done && /^[0-9]+\.[0-9]+$/u.test(value.value.value); + version = !value.done && ptxVersion(value.value.value) !== null; } if (token.value === ".target") { const value = stream.next(); target = - !value.done && /^(?:sm|compute)_[0-9]+[a-z]?$/u.test(value.value.value) + !value.done && + /^(?:sm|compute)_[0-9]+[a-z]?$/u.test(value.value.value) ? value.value.value : null; } diff --git a/tests/acceptance/gpuCodeObjectsCli.test.ts b/tests/acceptance/gpuCodeObjectsCli.test.ts index 4483c6a445..bb5bc8ce02 100644 --- a/tests/acceptance/gpuCodeObjectsCli.test.ts +++ b/tests/acceptance/gpuCodeObjectsCli.test.ts @@ -111,24 +111,104 @@ cliTest( }, ); -cliTest("accepts the PTX compute_xx .target synonym verbatim", async ({ cli }) => { - const root = await createTestTempDirectory("rea-ptx-target-cli-"); - const text = `.version 8.8\n.target compute_80\n.address_size 64\n.entry kernel() {\nret;\n}\n`; - const path = join(root, "target.ptx"); - await writeFile(path, text); - const response = await cli.run({ - arguments: ["inspect-gpu-code-objects", path, "--json"], - }); - expect(response.exitCode, response.stderr).toBe(0); - const evidence = parseEvidence( - toolContract("inspect_gpu_code_objects").outputSchema.parse(response.json), - ); - const report = gpuCodeObjectsSchema.parse(evidence.normalized_result); - expect(report.objects[0]!.target).toBe("compute_80"); - expect(report.ptx_artifacts[0]!.sha256).toBe( - createHash("sha256").update(text).digest("hex"), - ); -}); +cliTest( + "accepts the PTX compute_xx .target synonym verbatim", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-ptx-target-cli-"); + const text = `.version 8.8\n.target compute_80\n.address_size 64\n.entry kernel() {\nret;\n}\n`; + const path = join(root, "target.ptx"); + await writeFile(path, text); + const response = await cli.run({ + arguments: [ + "inspect-gpu-code-objects", + path, + "--analysis", + "kernels", + "--json", + ], + }); + expect(response.exitCode, response.stderr).toBe(0); + const evidence = parseEvidence( + toolContract("inspect_gpu_code_objects").outputSchema.parse( + response.json, + ), + ); + const report = gpuCodeObjectsSchema.parse(evidence.normalized_result); + expect(report.objects[0]!.target).toBe("compute_80"); + expect(report.ptx_artifacts[0]!.sha256).toBe( + createHash("sha256").update(text).digest("hex"), + ); + }, +); + +cliTest( + "extracts entry parameters declared in PTX 1.3 bodies", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-ptx-legacy-cli-"); + const text = `.version 1.3\n.target sm_80\n.address_size 64\n.entry kernel {\n.param .u32 input;\nret;\n}\n`; + const path = join(root, "legacy.ptx"); + await writeFile(path, text); + const response = await cli.run({ + arguments: [ + "inspect-gpu-code-objects", + path, + "--analysis", + "kernels", + "--detail", + "complete", + "--json", + ], + }); + expect(response.exitCode, response.stderr).toBe(0); + const evidence = parseEvidence( + toolContract("inspect_gpu_code_objects").outputSchema.parse( + response.json, + ), + ); + const report = gpuCodeObjectsSchema.parse(evidence.normalized_result); + expect(report.kernels).toEqual([ + expect.objectContaining({ + name: "kernel", + parameter_count: 1, + parameter_bytes: 4, + parameters: [ + expect.objectContaining({ + ordinal: 0, + name: "input", + offset: 0, + size: 4, + type: ".u32", + }), + ], + }), + ]); + const malformedPath = join(root, "truncated.ptx"); + await writeFile( + malformedPath, + `.version 1.3\n.target sm_80\n.entry kernel {\n.param .u32 input\n`, + ); + const malformed = await cli.run({ + arguments: [ + "inspect-gpu-code-objects", + malformedPath, + "--analysis", + "kernels", + "--json", + ], + }); + expect(malformed.exitCode, malformed.stderr).toBe(0); + const malformedEvidence = parseEvidence( + toolContract("inspect_gpu_code_objects").outputSchema.parse( + malformed.json, + ), + ); + const malformedReport = gpuCodeObjectsSchema.parse( + malformedEvidence.normalized_result, + ); + expect(malformedReport.objects[0]!.inspection).toBe("malformed"); + expect(malformedReport.coverage.payloads).toBe("partial"); + }, +); cliTest( "attributes malformed selector JSON to its actual CLI option",