T03, the language under the language - #41
Merged
Merged
Conversation
Reads the one column T02 skipped. The lesson writes its own IL disassembler in about thirty lines, using the instruction table that already ships on System.Reflection.Emit.OpCodes, and then reads L1 with it: the smallest method, a stack walk of it, the constant the compiler baked in, every method header in the program, every instruction that carries a method token, and the whole of <Main>$ with notes on the ten places where it does not match the source. Picks up three loose ends from T02. The <side>P name in the string heap is a field token in an ldfld, the single StandAloneSig row is the local variable list of the one method with locals, and the pdb's unnamed slot two is the enumerator the foreach needed. Three prediction gates, on the maximum stack depth in a small header, on why a non virtual method is called with callvirt, and on whether disposing a struct enumerator boxes it. The last one answers the dispose gate T02 had to leave open. Adds docs/diagrams/evaluation-stack.dg on why the middle language is a stack machine rather than a register machine. Verified against the committed expected output on osx-arm64 and on server3 on linux-x64. Both matched byte for byte.
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.
Third lesson of the tour, and the one that answers the question most people have never asked out loud. T02 read every column of the assembly except the code. This reads the code.
The lesson writes its own disassembler rather than showing the output of somebody else's. That is the design decision the whole page rests on, so it is worth saying why.
The disassembler is thirty lines and it is on the page
There is no IL disassembler in the box, and adding a tool dependency to a lesson that is supposed to run in a browser tab was not an option. It turns out none is needed.
System.Reflection.Emit.OpCodeshas one public static field per instruction, and every one of them knows its own number, the shape of its operand and what it does to the stack. Six lines of reflection over that class gives you the whole instruction table without typing any of it, and once you have the table, decoding a method body is an opcode, an operand whose length the opcode already told you, and repeat. Resolving the operands is the same token lookup T02 was about.So the reader does not get shown IL. They get shown how to read it, and then read it.
What is on the page
Fifteen blocks. All of them run against L1, the shared program from
lessons/shared/l1that T02 introduced.Square.Areain six instructions, with the byte offsets explained.Circle.Area, whereMath.PIturns out to be eight bytes of operand rather than a field load.callandcallvirtseparate.<Main>$, two hundred bytes, with its five local slots and its one exception region, followed by ten notes on the places where it does not match the source.Three prediction gates. What a small method header says the maximum stack depth is, which instruction calls a non virtual method and why, and whether calling
IDisposable.Disposeon a struct enumerator boxes it. The third one closes the loop on the dispose gate in T02, which asked why the reference was there and could not yet say what it cost.One new diagram,
docs/diagrams/evaluation-stack.dg, on why the middle language is a stack machine rather than a register machine. It is the one part of the lesson the instruction listings cannot show on their own. No numbers in it, same rule as last time.Things the page can now say that T02 could not
T02 left three loose ends and this picks all of them up. The
<side>Pname in the string heap turns out to be a field token in anldfld. The single StandAloneSig row turns out to be the local variable list of the one method that has locals. The gap at slot two in the pdb, where a local had no name, turns out to be the enumerator.How it is checked
Eight captured outputs, committed, including every instruction of every method and their byte offsets. That is the most sensitive thing this repository has committed so far and it is deliberate. If a compiler update changes the lowering of a
foreachor of an interpolated string, this lesson goes red on the next run, which is exactly what a book about internals should do.Two dropped blocks with assertions, on the same pattern as T02. The scale block compares the program's total IL against
System.Private.CoreLiband asserts the ratio is over two thousand, which is under half the real figure today so a shrinking library does not turn a true statement red. The machine block asserts a supported platform and a three part release version. Every claim has a written reason.Verified before opening this
Locally on osx-arm64 and on server3 on linux-x64, both against the committed expected output, byte for byte. IL should be identical everywhere, since the compiler emits the same bytes whatever it is running on, but the opcode count comes from the installed framework rather than from the standard, so it is worth having watched it on a second machine before the CI legs do.
Not in here
No
runtime:citations, same reason as T01 and T02. The page cites ECMA-335 Partition III for the instruction set and II.25.4 for the method body layout, and says plainly why there is nothing from the runtime tree yet.