Hacker News new | past | comments | ask | show | jobs | submit login

The more I learn about interpreters and compilers, the more everything looks the same.

Snigl [0] is designed as a VM interpreter. But it has a separate compile stage where code may be evaluated using the same interpreter and the results reused at runtime.

And it traces code before running it, evaluating as much as possible statically.

Finally, it supports emitting C code for an interpreter preloaded with the current, traced program. Which is where compile time evaluation really shines, as the results are embedded in the executable.

[0] https://gitlab.com/sifoo/snigl




I have written far more compilers/interpreters in my career than I probably should have, and I reached the same conclusion. For most of them, I probably couldn't even tell you weather or not what I had involved a compiler. At this point, my working distinction is that a compiler produces a serialized representation. If you feed the AST directly into the runtime, then you just have an interperater. I don't see any practical benifit to this distinction, but it seems to match up with what people consider a "compiler".

As an asside, I had a simmilar insight when I built a computer from logic gates [0]. Essentially, the control unit had an EEPROM of microcode, and the logic to execute an actual instruction was to use the upper 4 bits of the instruction as an index into a lookup table, then write the result to the microcode program counter.

Eg:

   switch(OP & 0xf0):

   case 1: jmp &add
   case 2: jmp &sub
   ...
Which looks a lot like an interperator.

It wouldn't suprise me if modern CPUs actually added an additional step, where they first translate (compile?) the input stream into some internal instruction set, and then have microcode that acts as an interperator for that.

[0] Also EEPROM, and various composite circuits.


All modern x86 CPUs do this to some extent. The details changed over the decades, but the basic principle these days is that the CPUs decode the x86 instruction set into a secret, internal only, more orthogonal sequence of instructions. My understanding is that these sequences can be quite long for the more involved CISC operations. And according to the manuals written by Agner Fox, some instructions even have special cases based on their operands to do something equivalent, but much faster. One particular example that I remember is xor ax,ax which is often used to set ax to zero and is a one byte instruction. Modern CPUs don't compute the xor in the ALU, but just rename ax to a register that is set to 0.


So in practice, nobody uses actual x86 assembly anymore, but everybody uses it as a target for compilation. It’s just like The Birth & Death of JavaScript¹, but non-fictional, and on a lower level – a Birth & Death of x86 assembly, if you will.

1. https://www.destroyallsoftware.com/talks/the-birth-and-death...


>nobody uses actual x86 assembly anymore,

well almost.


Or JVM byte code.


"It wouldn't suprise me if modern CPUs actually added an additional step, where they first translate (compile?) the input stream into some internal instruction set, and then have microcode that acts as an interperator for that."

Among Java processors, there was quite a mix of methods ranging from CISC to RISC to in-hardware to interpret-to-regular-CPU's:

https://www.electronicdesign.com/embedded/java-processors

IBM's System/38 also did something with portable microcode that got converted to internal microcode:

https://homes.cs.washington.edu/~levy/capabook/index.html


To me the difference is that an interpreter read the code and execute every operation immediately as it encounter them, while I expect a compiler to process a large portion of the code and potentially perform more transformation on it before executing it. In some way I see an interpreter as potentially invoking a compiler on a single operation one-at-a-time and executing the result of the compilation before moving to the next operation.


The lines are grey, to some extent. Many interpreted languages (such as Tcl) compile to bycode, then interpret that. The compilation happens not at the operation level, but at the block (or maybe file) level.


In Tcl, procedures are bytecode-compiled when they are created, which is generally when the files containing their code are sourced.

Code outside procedures is not compiled, but interpreted line-by line as it is executed.

So if you care about the performance of your Tcl code, put as much of it as you can into procedures.


The Haxe compiler is also an interpreter of the Haxe programming language, and it is properly treated as one of the many back-end targets of the compiler.

Moreover, the interpreter is also used as the macro runtime, that means Haxe macros can run arbitrary "real" Haxe code during compilation, e.g. checking the current git branch name or current time and inject the result as a static read-only string in the code.


DMD also comes with a built in D interpreter, RDMD, allowing you to use D as a scripting language with a compiled binary. This can be embedded or called from your D program allowing your program to have a scripting layer written in the same language as the compiled binary. As far as I know, executing a .d file with RDMD is nearly as fast as running a compiled binary.


I've read about D's compile time evaluation but have no personal experience. I know both Rust and C++ are pushing in the same direction.

From what I can see, it's simply an embedded interpreter for the same language; which means that it probably runs about as fast as interpreters in general (that is, significantly slower than D). I would love to be wrong and learn something new, though.

Starting from an interpreter means it's already there, the only difference is when code is evaluated. And embedding it simply means linking the interpreter library. Snigl and D are sort of approaching Lisp from different angles.


Jai will go even more in this direction allowing even to do system calls, Jon Blow demoed a compile-time HW accelerated game..


Many Lisps already support this; Common Lisp, Racket, Clojure, etc.


My understanding is that you need for pull this off (apart of transpiring) you need that the host have a solid meta-programming facilities.

I'm on rust now and can't see how pull the same idea, apart of emit rust code then compile it with rust.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: