These approaches aren't just for toy languages. Java/C#/Python standard implementations compile to byte code that runs on virtual machines. Glasgow Haskell Compiler compiles to C--, and a bunch of languages compile to JavaScript.
Do the generation to some kind of byte code as you are suggesting, but using an instruction format that be used as macro calls in macro assemblers.
Then just write the macros for each bytecode, doesn't matter matter if the register usage is bad, goal is just to have a plain native executable that runs.
What exactly do you mean by this? Direct object code emission?
> the emulator is easier than the compiler, by a lot
Yeah, definite +1 there. For most simple machines it really is not much more than a loop with a switch on the opcode where the cases update the CPU state.
Writing an emulator is almost as much fun as writing a compiler. And when you are done, you will have another level of appreciation of how computers work.
http://schierlm.github.io/OberonEmulator/
I sent a bunch of patches to rework the in-browser emulator a couple weeks ago. If you don't know C, I recommend reading through the JS emulator's source. (View source should suffice—it's all unminified vanilla JS; there's no opinionated JS framework involved.)
With it running in the browser, the emulator frontend treats the web platform as its widget toolkit. The code to interface with that is in webdriver.js[2] and takes about 1000 lines of code. The CPU and memory operations themselves are implemented in risc.js[3] and only take about 1/3 that.
To follow along with instruction fetching/decoding/execution, you'll need to understand the ISA. There's a good 3-page overview linked from projectoberon.com under the title "RISC Architecture"[4]. A more in-depth description of the design is also available[5].
I have some tentative work for a machine-code level debugger online[6]. It's unfinished, however, so it comes with no documentation and the toolbar icons are missing. (There are tooltips, however.) So you can play with it if you feel like watching the registers and flags change while stepping through machine instructions.
1. https://github.com/pdewacht/oberon-risc-emu/
2. https://github.com/schierlm/OberonEmulator/blob/master/JS/we...
3. https://github.com/schierlm/OberonEmulator/blob/master/JS/ri...
4. https://www.inf.ethz.ch/personal/wirth/FPGA-relatedWork/RISC...
5. https://www.inf.ethz.ch/personal/wirth/FPGA-relatedWork/RISC...
6. https://www.colbyrussell.com/staging/aubergine/emu.html?imag...
Having had the pleasure of writing a couple emulators and a couple compilers at this point: the emulator is easier than the compiler, by a lot. Relative to parsing and emitting code, emulating a straightforward architecture is easy.
This is a great post!