[LLVMdev] llvm-mc direction
clattner at apple.com
Mon Jul 6 18:51:46 PDT 2009
Bruno asked me to write up a short email about the direction we're
going with llvm-mc and how it interacts with the object writer stuff.
My hope was that .o writing could be basically done without worrying
about llvm-mc, but I think we've reached the point where it is useful
to talk about how the two will interact in the future. It turns out
that I also failed in the goal of writing a *short* email, oh well.
llvm-mc has many goals, but it basically revolves around generating
full native assemblers and disassemblers from suitably enhanced
versions of the .td files we already use for the code generator. For
this email, I'll just focus on the native assembler part.
If you look at writing out a .o file, there are a couple of
interesting parts: 1) encoding machine instructions, 2) wrapping an
ELF/PECOFF/MACHO container around them, 3) supporting inline assembly,
and 4) wiring it into the LLVM backend.
LLVM already handles #1 fairly well. It can certainly be improved,
but the JIT works today.
Aaron and Bruno are working on #2, integrating .o file writing into
the backend but ignoring inline assembly. The integration into llvm
(#4) is pretty ugly, because the .o file writers duplicate a ton of
logic in the asmprinters. I intentionally asked Bruno to ignore
My goal with llvm-mc is to solve all of these problems, pulling in
Bruno and Aaron's work on #2 when the time is right. The ultimate
design I'd like to get to looks like this:
1. Implement a full assembler, with a .s parser, a fragment processor,
and .o file writer. The main data-structure the .o file writer will
work on are "fragments" which (at the end of the assembler) are
basically chunks of bytes with an associated section and a list of
relocations (which is exactly what llvm::BinaryObject is!).
2. The full assembler should work as a drop in replacement for
"as"/"gas". This allows easy testing, and may also be interesting to
the BSD folks. I am slightly worried and paranoid about getting
different output when using "llvm-gcc -S + as" and using "llvm-gcc -
c", so testing is very important to me.
3. The assembler needs an API between the .s parser and the "assembler
backend". This is called MCStreamer so far - it is somewhat similar
to the "Actions" interface in clang. This separates the "parsing"
from the "fragment construction", relaxation, and other stuff that an
4. Since we have a nice MCStreamer API, we plan to implement it in two
ways: one way will use the assembler backend to write out a .o file.
The other way will just *prints out a .s file* that is semantically
identical to the input. This is currently starting to limp along, and
is a good way to bring up the asm parser without worrying about the
"assembler backend" and .o file writing.
5. The LLVM "asmprinter" will change from writing out text directly
with raw_ostream calls to making virtual method calls on the
MCStreamer API. For example, instead of 'O << "\t.align 2\n";', the
asmprinter will end up calling Streamer->EmitValueToAlignment(2).
When run in "-S" mode, the implementation of the Streamer will be
the .s file writer, which just writes out the .align directive.
6. When the "assembler backend" comes up, this means that we can drop
it into the current "asmprinter" and the virtual methods (like
EmitValueToAlignment) will just do the right thing for building a .o
file. This also means that the compiler can pull in the asmparser to
handle inline assembly with no problem.
Taking this sort of approach has a number of advantages. A big
problem that I see today is that the .s writer and .o writers use
completely different code paths that make almost exactly the same
decisions. For example, if separate, they both need to have some
equivalent of the X86ATTAsmPrinter::printModuleLevelGV function, which
decides what low-level linkage, visibility, and many other aspects of
a global to emit.
Duplicating all this logic is bad and is likely to lead to -c and -S
doing different things. If the only difference between -c and -S is a
different implementation of MCStreamer, and if we can independently
test that the assembler "does the right thing", we should be in good
shape for correctness.
There is clearly some potential for overlap between what the MC stuff
is doing and the .o file writing work is doing. Before I talk about
that, I want to talk about the implementation plan for MC. The plan
is to do X86-32 first then x86-64, then move on to ARM probably, with
these steps (many of which are parallel):
1. Make a new llvm-mc test tool that is a driver for various
2. Build up an asm parser for x86 that can print out a semantically-
identical .s file (one that can be assembled to the same bytes as the
input). This "complicated cat" is just a testing mechanism, but it
also allows implementation of the asm parser to proceed in parallel
with other pieces, and will be used by "-S" eventually (in progress).
3. Implement support for tblgen-generating the parsing logic that maps
from "opcode name + argument" in the asmparser to an instruction enum
value + MCOperands.
4. Refactor the asm printer to go through MCStreamer instead of
writing to raw_ostream directly (in progress).
5. Refactor the asm printer to not depend at all on libx86, codegen,
vmcore etc. The goal is for x86/asmprinter to only depend (at a link
level) on libsupport. There is a lot involved in this, to say the
least, but this is important for "llvm-mc as a disassembler API".
6. Implement the "assembler middle end", which handles relaxation
operations (e.g. determining whether something is a short or long
branch on x86, figuring out how big a uleb has to be, etc), which
generates the final fragments (which are basically going to be
7. Implement support for writing a .o file.
As you can see, llvm-mc is a pretty ambitious and non-trivial
project. Because of this, I don't expect to get to #7 for a couple
months at least, and I understand that the .o file work needs to be
almost complete by then (for GSoC etc). My goal was for the .o
writing work to focus on stuff that doesn't intersect with MC stuff
until late in the game: the encoding to ELF, writing DWARF as bytes
instead of ".byte's" etc. My hope was that the .o file writer stuff
could just be refactored directly into providing #7, completing the
Coming back to the short-term issue of "we need templates to make byte
emission fast in the code generator", I hope that I've convinced you
that this will "just not matter". Please focus on making .o file
writing be done in a simple, clear and concise way without worry about
micro-level performance optimizations. By the time we're done with
this whole project, many things will have changed. Doing optimization
of .o file writing at this stage is *way way way* too early.
One nice thing about this design is that (like clang) it will be very
easy to decompose the layers and performance test just (e.g.) the .o
writer without testing the whole compiler's performance.
More information about the llvm-dev