C as an intermediate language (2012)
yosefk.com
yosefk.com
Fundamentally, CIL is a nice subset of C wrapped up into a nicely usable API. The idea is to shave off as many of C's inconsistent sharp edges as possible. You won't have to worry about quirks in the syntax or odd behavior because you have a nice high-level, curated API for generating C code.
It's designed to have a clean semantics, and semantics are very important. (Or so I maintain.)
Compiler Design in C (1990)
http://www.amazon.com/Compiler-Design-C-Prentice-Hall-softwa...
EDIT: Adding some extra remarks I think might also be interesting to share.
Another approach, that I really like, is to output bytecodes that are mapped directly to macros in typical macro assemblers like NASM/MASM/TASM. Those macro assemblers provide very powerful macro systems.
Then map those macros to the corresponding assembly code.
Sure it gives a bit more work, but I find it more fun.
The intermediate code is similar to the article, a mix of macros and basic C expressions as high level assembler.
I cannot remember all the details, the last time I used the book was around 1996.
You can still get the source code, http://www.holub.com/software/compiler.design.in.c.html
touch *.c *.hWhen compiling dynamic languages of course, that often doesn't work as the optimiser doesn't have anything to go on if you do everything through void* pointers, or a similar dynamic construct.
You might find some anti-C bias from my post history, but I am also very pragmatic and would certainly use C if required to do so.
I used the compilation to bytecode disguised as Assembler macros as part of a few toy compilers back in late 90's.
If it was today I would probably use LLVM or C(++) as being discussed in the article.
mov reg1, reg2 ==> reg1 = reg2
mov reg1, [reg2] ==> reg1 = *reg2
add reg1, reg2 ==> reg1 += reg2
And so on.> Dynamic binding: easy enough.
That technique won't be fast enough for dynamic languages. You really need polymorphic inline caches to make dynamic binding fast, which requires careful cooperation between the code generator, the front end, and the IR. A C compiler won't give you that level of control.
> Garbage collection
It doesn't work that well. The problem is that C compilers don't provide a way for the runtime to find all roots on the stack (tell apart integers from pointers). You pretty much either have to be conservative on the stack or spill all roots to the stack across function calls. Neither of them is very good: the former costs accuracy and prevents you from using a bump allocator in the nursery, and the latter costs performance.
There are other issues to consider as well, for example tail call optimization and undefined behavior.
Languages where a really great implementation might involve C code generation are probably indeed quite static, however. An example is Synopsys's VCS which AFAIK compiles Verilog to C++.
My own hands-on experience with this type of thing is with an in-house HDL and an in-house C dialect with (sizable, static) extensions for accelerator programming.
One day we came up with an idea: what if we could query the data store with SQL? The first iteration actually attempted to embed sqlite3 into the data access layer, which was functional but extremely slow because of all the type marshaling going on. A coworker and I came up with the second iteration which worked like this: a custom SQL-like language would be parsed by a Perl program using Parse::RecDescent, a recursive parser generator. The parse tree would then be translated into C++ that used the data access layers and processing layers directly. The compiled program was distributed to the cluster in the same way as the daily processes.
As far as I know this monstrosity still gets daily use, four years later.
As a linkage format C is both too high-level (it provides a lot of facilities that are irrelevant to this use case - so while C may be ubiquitous now, I suspect it's much easier to implement an interpreter from scratch for something like LLVM bytecode) and too low-level (it exposes the host machine's memory model, making it inherently unportable). Use the right tool for the job - programming languages for writing programs in, intermediate representations for code for representing intermediate code.
We took the code from IBM minicomputers and compiled it into C on many, many platforms (back when there were a few more unices, as well as OS/2 and VAX/VMS kicking around).
I only did a little maintenance on the compiler front end checker, though. I mostly worked on some supplemental tokenizers/runtimes for data file browser language and a DB/form DDL.
Anyway, GCC made a nice target to hit a large number of systems. (alas, Borland C on DOS at the time tended to choke on larger generated subroutines, being 16 bit w/out "huge" pointer support and all)
There has been a little bit of research done on writing optimising compilers that use source-code-like intermediate representations: the Janus project from the 1990s springs to mind.
The C code generator is very simple: http://sourceforge.net/p/cito/code/ci/master/tree/GenC.cs
harder than it looks.