For more higher level code you are probably interested in this topic which explains the internals:
Then for the low-level things this could be helpful:
Do you want to do specific things or do you just want to research and look into different representations, to gain more insights?
Regarding outputting LLVM I think there is also some way to do that, but somebody else will have to point that out, I tend to look at asm or types at comptime via @compileLog, I haven’t really explored the in-between stages.
If you don’t care about llvm and you want the disassembly, try the compiler explorer (zig.godbolt.org)
On the top right enter -target avr-freestanding or -target thumb-freestanding or any other architecture in: zig targets | jq .arch
On the left enter you zig functions with export, on enter your entire zig program.
i actually have a very specific mission in mind for zig, which i’ll hopefully reveal as concepts solidify in my mind…
as suggested in my original question, my focus is on resource-constrained MCUs with (say) only 32K of memory; and i’m always looking for kindred spirits…
thanks again… perhaps the best “maiden-voyage” feedback i’ve had in a long time…
This was a bit of an A-B problem, but here’s some info about the question in the title in case someone stumbles across this thread.
There are 3 interesting IRs in the pipeline:
ZIR (Zig Intermediate Representation)
AIR (Analyzed Intermediate Representation)
LLVM IR (only if you’re using the LLVM backend, which you probably are currently)
ZIR generally isn’t terribly interesting – it’s generated on a per-file basis before any semantic analysis or anything. Using a debug build of the compiler, you can see it with zig ast-check -t file.zig.
AIR is generated for your whole compilation after semantic analysis – it’s what we send to code generation backends. Using a debug build of the compiler, you can tack --verbose-air onto a build-{exe,lib,obj} command to spit it all out (be warned: there’ll be a lot!). This is probably the best thing to look at if you want to understand, for instance, how Zig is lowering a certain construct, but it’s probably still tricky to understand if you’re not at least a bit familiar with the compiler internals.
Lastly, and arguably most usefully, there’s LLVM IR, which is what our LLVM backend generates to send to LLVM’s optimizer and code generator. You can see this with --verbose-llvm-ir, or with -femit-llvm-ir=file.ll to dump it to a file. The latter can be accessed in a build script using the Step.Compile.getEmittedLlvmIr method.
i’m glad i was a little ambiguous, as this trio of IRs is quite interesting…
i’ll look into the AIR some more, which then raises another question:
what’s the best way to see the form of my program AFTER comptime ???
said another way, in C-land one could always run cpp and inspect it’s output – to ensure what you wanted to happen in the “meta-program” actually worked!!!
seeing a “partially evaluated” / “specialized” / “curryed” form of your original program is incredibly useful – functions with fewer arguments, specialized clones of generics, results of constant expressions, etc…
in summary, i can clearly understand what the backend compiler does but simply looking at the generated asm code; but how can know what happened upstream at comptime ???
For the full explanation take a look at the linked answer.
There is no step that generates something that looks like Zig source code again, because it would be an unnecessary step / going backwards, it may even be difficult to generate such a thing without creating something that is a bit of a lie (In the sense that abstractions lie), it might be possible to generate the “monomorphized” version, but still that would probably result in an explosion of code size and ultimately the AIR is always more faithful towards what is going on, because it’s the representation that is actually used.
Personally I think @compileLog together with assertions is already sufficient to gain the necessary insight into what is happening at comptime. So basically I would say use programming to ensure that the things you wanted to happen, do happen. Via checking types and using @compileError if they are wrong.