Repository navigation
Managed ilasm: per-point PDB documents, the input file and .line files as documents, LocalSignature row id - #135297
Open
pcshrosbree wants to merge 7 commits into
Open
pcshrosbree wants to merge 7 commits into
pcshrosbree wants to merge 7 commits into
Conversation
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
This was referenced Oct 6, 2026
3 tasks
pcshrosbree
force-pushed
the
ilasm-goodwill/pdb-documents
branch
from
October 7, 2026 16:11
986e0dc to
85536a1
Compare
pcshrosbree
force-pushed
the
ilasm-goodwill/pdb-documents
branch
from
October 8, 2026 03:18
85536a1 to
ea812f3
Compare
jkoritzinsky
reviewed
Oct 8, 2026
pcshrosbree
force-pushed
the
ilasm-goodwill/pdb-documents
branch
from
October 9, 2026 02:15
ea812f3 to
11bf178
Compare
jkotas
reviewed
Oct 9, 2026
| @@ -33,6 +33,7 @@ internal static class NativeCommandLine | |||
| ["NOL"] = "--nologo", | |||
| ["OPT"] = "--optimize", | |||
| ["OUT"] = "--output", | |||
| ["PAT"] = "--pathmap", | |||
Member
There was a problem hiding this comment.
The legacy command-line options recognized here are supported solely for backward compatibility with native ilasm. No new options should be added here.
Contributor
Author
There was a problem hiding this comment.
Removed in 39e68ce: the entry, its rows in the command-line test data and the test of that form; the README no longer names it. A row in UnrecognizedNativeOption_Throws now checks that -PATHMAP= is rejected like any other unknown native option.
pcshrosbree
force-pushed
the
ilasm-goodwill/pdb-documents
branch
3 times, most recently
from
October 9, 2026 21:10
bf6b1b0 to
8e01ead
Compare
…an several documents
The managed ilasm kept one document per method: the file named by the first
.line directive of the method. A method whose .line directives named two
files had all of its sequence points in the first one, and an empty file
name ('' or "") created a document named ''.
Each sequence point now records the document that is current when its
directive is applied, as an index into the compilation's PDB document
table. When the points of a method span several documents, its
MethodDebugInformation row has a nil Document, the sequence points blob
starts with the InitialDocument (the document of the first point), and a
document-record precedes each non-hidden point whose document differs from
the current one. Hidden points take the current document. This is the
encoding native ilasm writes (portable_pdb.cpp, DefineSequencePoints).
A method whose points are all in one document still names that document
and has no InitialDocument.
An empty file name leaves the current document unchanged, as in native
ilasm (SetSourceFileName ignores an empty name). ildasm writes '' for "the
same file as the previous directive", including on the first .line of a
method whose file did not change.
A directive at the same IL offset as the previous one still replaces that
point; the replacement now also takes the last directive's document.
A method without an IL body (abstract, pinvokeimpl, runtime-implemented)
gets a MethodDebugInformation row with a nil Document and no sequence
points blob, even when it contains a .line directive. Before, such a method
got a point at IL offset 0 of a body it does not have; native ilasm records
sequence points as it emits instructions, so it records none.
MethodDefinitionEntity.HasBody is the one test for "has a body" that both
the IL writer and the PDB writer use.
Tests: PdbDocumentTests reads the PDB back with System.Reflection.Metadata
and checks the nil Document, the InitialDocument, the document of each
point (including A, B, A switching and hidden points, with the blob's raw
records showing that no document-record is written for a hidden point),
'' within and at the start of a method, that a method without points keeps
a nil Document and no blob, that abstract and pinvokeimpl methods with a
.line directive get no points, nor does a body with directives but no
instructions, and that in an error-tolerant compile a method that still
has its instructions keeps its points.
The managed ilasm created a PDB document only for a file that a method's first sequence point used, in MethodDef order, keyed by name and language, and with no language unless .language was given. The input .il file was never a document. Native ilasm defines documents as it meets them: the input file, by full path, before parsing it (main.cpp), and each file named by .line or #line when the directive is applied, at top level, in a class or in a method, whether or not a sequence point ever uses it (SetSourceFileName, DefineDocument). A document is identified by its name alone and keeps the language current at its first definition. The language defaults to IL assembly, af046cd3-d0e1-11d2-977c-00a0c9b4d50c (CorSym_LanguageType_ILAssembly). The managed ilasm now does the same: - BeginDocument defines each input file as a document and makes it the current document, so a .line without a file name before any named file is in the input file, and each input file starts in its own document. - A .line or #line with a non-empty file name defines that file and makes it current wherever the directive appears. - The document table keeps documents in encounter order, keyed by name; the PDB lists them in that order. - Documents defined before any .language directive have the IL assembly language; .language applies to documents defined after it. - The CLI passes the full path of each input file, which is the input document's name. Library callers get SourceText.Path as given. Error messages from the CLI now name the input file by its full path too. Existing expectations that change because the input file is now a document, each now asserting the complete ordered list of document names (input file first) where it asserted a count or a name: SourceDirectiveTests (PdbGeneration_WithLineAndLanguageDirectives_CreatesValidPdb, PdbGeneration_LanguageWithVendorAndDocumentType_CreatesValidPdb, LanguageDirective_SyntaxVariant_EmitsDocumentLanguage, ClassScopedLanguageAndLineDirectives_ApplyToMethodSequencePoint, MultiDocumentCompile_WithLineDirectivesAcrossDocuments_EmitsPdbDocumentsForEachSource, MalformedLanguageDirective_DoesNotPartiallyUpdateGuidState), CompilerOptionsTests.PdbOption_ProducesPortablePdbWithoutLineDirectives (one document, the input file, instead of none) and ModuleTests.GlobalMethodAndModuleAttribute_EmitModuleMetadataAndPdb. MultiDocumentCompile_DoesNotReusePreviousDocumentPath now expects the second file's .line without a file name to be a point in the second input file instead of no point at all, and checks the three documents; it still checks that the first file's .line file is not reused. Tests: PdbDocumentTests checks the input file as the first document, the encounter order (not MethodDef order), files named at top level, class level or by a replaced directive, the default language, .language for later documents and for documents of the next input file, a file named again keeping its first language, names that differ only in case being different documents (native compares with strcmp), one document per input file, and identical PDB bytes for a deterministic multi-document program. PdbCaseGenerator generates 200 seeded programs, in one input file or two, whose methods (some of them abstract) move between a few documents with named, '', unnamed and hidden .line directives in the full form native ilasm accepts, class-level directives and .language, and computes the expected documents and points; PdbGeneratedCaseTests checks each point's document, the method's Document, the InitialDocument and the document table against it.
The sequence points blob starts with LocalSignature, which the Portable PDB spec (docs/design/specs/PortablePdb-Metadata.md, MethodDebugInformation Table) defines as the row id of the method's local signature in the StandAloneSig table. The managed ilasm wrote 0 for every method; native ilasm writes RidFromToken(m_LocalsSig). When EntityRegistry writes a method body it now records the local signature handle that the body references in the method's debug information, and EncodeSequencePoints writes its row id, or 0 when the body has no locals. With /FOLD, a method whose body is shared with an earlier one gets the same handle, because the shared body's bytes include the local signature token. Tests: PdbDocumentTests checks the row id for two methods with different local signatures (rows 1 and 2), 0 for a method without locals, the row id of methods whose bodies /FOLD shares, and that a method with locals but no .line directive has no blob at all. A seeded theory checks that the blob's LocalSignature equals the body's for every generated method with sequence points, and that abstract methods have no body.
CommandLinePdbDocumentTests runs the ilasm command line in process, as CommandLinePdbFileTests does, and reads the image and PDB it writes. It covers what the command line decides and the library tests cannot see: - Without any .line directive, the PDB has one document: the input file, named by its full path. - An input given by a path relative to the current directory is named by the full path it resolves to. The test reads the current directory and never sets it. - Two input files are two documents, by full path, in command-line order, before the document of a .line directive in the second. - The TestDocuments2 case of the runtime's PortablePdb tests: '' on a method's first .line keeps the previous method's document, so both methods' points are in the named file and the second method does not span documents. PortablePdbTestReader gets an Open(imagePath, pdbPath) factory beside Compile, so its helpers serve the file-based tests too. MethodTests uses it for the PDB side of the entry point: - The Portable PDB header's entry point is the MethodDef of the method that carries .entrypoint, not the first MethodDef, and is nil when no method carries it.
…d narrow the test reader's blob check BuildPdbMetadata now quotes docs/design/specs/PortablePdb-Metadata.md where it writes a method's MethodDebugInformation row: the Document column names a document only when every sequence point of the method is in it, and otherwise it is nil and the sequence points blob carries the InitialDocument and the document-records. The comment on the branch for methods without an IL body is removed; the method's remarks already state that rule. In the tests, PortablePdbTestReader.ReadBlobRecords, which returned every record of a blob, becomes AssertNoDocumentRecordInSequencePointsBlob, the one check its only caller needed. HiddenPoint_HasNoDocumentRecordAndBelongsToTheCurrentDocument used to compare the whole record list with point@0, hidden@1, point@2; it now asserts that the blob has no document-record, and its assertions on the points' hidden flags and documents are unchanged. A new test checks that the assertion fails for a method whose blob does have a document-record.
…e CodeView entry The PDB names each input file by its full path, and without --deterministic the image's CodeView entry records the full path of the PDB, so the output depends on where the tree is. --pathmap (-PATHMAP=...) maps path prefixes in both, so that with --deterministic, which already records only the PDB's file name, the same tree assembled in two places gives byte-identical image and PDB files. The rules are the C# compiler's -pathmap. The value is a list of <path>=<sourcePath> entries separated by ',', where ',,' and '==' stand for the character itself; an entry without exactly one '=', or with an empty side, is an error that names it, and an empty entry is skipped. Each path and replacement that does not end with '/' or '\' gets the separator it already uses (the platform's when it uses neither or both), so an entry matches whole leading directories only, and the first entry that matches a path wins, with the result's separators normalised to the replacement's when it uses only one kind. The option can be given more than once and the entries apply in order. Options.PathMap holds the map. PdbDocumentTable maps every document name before it looks it up, so two names that map to one name are one document; this covers the input files and the .line and #line file names. GetPdbFilePath maps Options.PdbFilePath for the CodeView entry. Files are still read and written at their real paths, and diagnostics still name them by those paths. An invalid --pathmap fails the run before any file is read or written. Tests: PathMapTests for the parsing and mapping rules; PdbDocumentTests and CompilerOptionsTests for the documents and the CodeView path; and CommandLinePathMapTests, which run the command line in process: the same source in two directories, each mapped to /_/, gives identical files, both option forms are accepted and accumulate, a malformed value fails and writes nothing, and the PDB is written at its real path. CommandLineTests' native value-option data gains PAT. No existing expectation changed.
The three-letter options that NativeCommandLine recognises exist only for compatibility with the command lines of native ilasm, which has no path-map option, so --pathmap gets no -PATHMAP form. The table entry is removed, with the command-line tests' data rows for it and the test of the native form; the remaining command-line tests pass the option as --pathmap <value>. A row in UnrecognizedNativeOption_Throws checks that -PATHMAP= is rejected like any other unknown native option.
pcshrosbree
force-pushed
the
ilasm-goodwill/pdb-documents
branch
from
October 11, 2026 00:42
8e01ead to
aec8b70
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of making the managed ilasm (
src/tools/ilasm) a drop-in for native ilasm, as discussed in #135212. There @jkotas pointed to the managed rewrite as the way forward, @jkoritzinsky confirmed that PDB differences from native ilasm are unintended, and @am11 suggested combining efforts.#135289 (the separate
.pdbfile) has merged; this PR's seven commits are now the whole diff; the last three address review feedback.Without this PR, with the separate-PDB fix applied, the runtime's PortablePdb test cases (
IlasmPortablePdbTesterinsrc/tests/ilasm/PortablePdb) pass 10/10 with native ilasm and 4/10 with the managed one, measured by running those cases against both ilasms on Linux x64. This PR takes the document-related failures and one spec defect..line), so a method whose.linedirectives named two files had every point in the first. After: each point records the document current when its directive is applied. A method spanning documents hasDocument = nil, anInitialDocumentin the blob header, and adocument-recordbefore each non-hidden point whose document changes, which is what native writes (DefineSequencePoints).''means "same file". Before:.line N : C ''created a document named''. ildasm writes that form when the file did not change, including on a method's first.line. After: an empty name keeps the current document, as native'sSetSourceFileNamedoes..language; and the input.ilfile was never a document. After, as in native: the input file, by full path, is document WIP: repo consolidation scouting kick-off - make clr build locally on Windows #1; every.line/#linefile is defined when its directive is applied, wherever it appears and whether or not a point uses it; documents are in encounter order, keyed by name (case-sensitive); each keeps the language current at its first definition, which defaults to IL assembly (af046cd3-d0e1-11d2-977c-00a0c9b4d50c).LocalSignature = 0. After: it is the StandAloneSig row id of the body's local signature, as the Portable PDB spec requires and native writes, or 0 without locals. This includes bodies shared by/FOLD..lineinside an abstract orpinvokeimplmethod, or in a body with directives but no instructions, gave it a sequence point at IL offset 0 of a body it does not have. After: such a method has no sequence points; the directive still names its file, as in native.--pathmap(from review).--pathmap <path>=<sourcePath>[,...]replaces path prefixes in the paths the debug information records, every PDB document name and the PDB path in the CodeView entry, with the C# compiler's-pathmaprules: each side is completed with a trailing separator so a key matches whole directories only, the first matching entry wins, and the result's separators follow the replacement's. With/DET, where the CodeView entry already names only the PDB's file name, mapping the directory of the inputs gives byte-identical image and PDB files wherever the tree is. Files are still read and written at their real paths.Behaviour changes to be aware of:
/DEBUGor/PDB, every PDB now has the input file as a document, even without.linedirectives.Deliberate difference from native: a method with no sequence points, or without a body, keeps a nil
Document, as the spec says; native names the current document.Other differences from native ilasm found in the same survey, not taken by any of these PRs, are tracked in #135314.
Not in this PR (follow-ups):
.ilsource's own lines.#included files as documents. In particular, a file named by a.lineinside an include currently stays current after the include ends, whereas native re-establishes the including file.#lineauto-increment..lineend-column defaults..lineafter a method's last instruction gives a point at the end offset, where native gives none; a class-level.linefollowed by a method without.linegives that method's first instruction no point, where native gives it the class-level coordinates.Testing:
PdbDocumentTestsreads the PDB back with System.Reflection.Metadata, and for the encoding also reads the blob's raw records. It covers: the nil Document, the InitialDocument and each point's document (A→B→A switching; hidden points, with no document-record); two directives at one offset;''within a method and on its first.line; files named at top level, class level or by a replaced directive; encounter order (not MethodDef order) and case-sensitive names; the default language,.languagefor later documents and for the next input file, and the first language sticking; one document per input file; abstract andpinvokeimplmethods getting no points; no points for a body with directives but no instructions, while an error-tolerant compile keeps the points of a method that still has its instructions; identical PDB bytes under/DET; the LocalSignature row id: rows 1 and 2, 0 without locals,/FOLD, and no blob for a method with locals but no.line. Each new behaviour has a test that fails when the behaviour is removed; I checked this by breaking each one in turn. Existing tests that checked documents now assert the complete ordered list of document names, input file first.'', unnamed and hidden.linedirectives (in the full form native accepts), class-level directives,.languageand locals. The generator computes the expected documents and points independently. Theories check each point's document, the method's Document, the InitialDocument, the document table and the LocalSignature.CommandLinePdbDocumentTestsruns the ilasm command line in process, asCommandLinePdbFileTestsdoes, and reads the PDB it writes. It checks what only the command line decides: without any.line, the input file is the only document, by full path; an input given by a path relative to the current directory (written under it, so the argument is never rooted, without setting the current directory) is named by its full path; two input files are two documents in command-line order, before the document of a.linefile; and theTestDocuments2case end to end (''on a method's first.linekeeps the previous method's document). Each of these fails when the behaviour it checks is broken.PortablePdbTestReadercan now also open an image and PDB from files.MethodTestsnow also checks that the PDB header's entry point is the.entrypointmethod's MethodDef, not the first MethodDef, and nil without one; the runtime'sTestPdbDebugDirectory2case was the only test of this.--pathmap.PathMapTestschecks the parsing and mapping rules as strings;PdbDocumentTestsandCompilerOptionsTestscheck the document names and the CodeView path on the library;CommandLinePathMapTestsruns the command line in process: the same source in two directories, each mapped to/_/, assembled with-DET -DEBUG, gives byte-identical image and PDB files; the option can be given more than once and the entries accumulate in order; a malformed value fails before anything is written; the PDB is written at its real path. Each rule has a test that fails when the rule is removed.src/tests/ilasm. I ran that directory'sIlasmPortablePdbTestercases against both ilasms on Linux x64, outside the tree: native passes 10/10, and the managed ilasm passes 4/10 at Write the managed ilasm PDB to a separate file, as native ilasm does #135289's head and 6/10 with this PR; the four still red areTestPortablePdbLocalScope(TestLocalScopes1..4.il), which the local-scope PR takes. The two cases I added while measuring,TestDocuments2and an input given by a relative path, passed with both ilasms and are now inCommandLinePdbDocumentTests.Note
This change was prepared with AI assistance (Anthropic Claude and OpenAI Codex) under my direction. AI agents wrote the code and ran the build and tests on my machine. I reviewed the change before posting.