Wow, this is a delightfully weird architecture. You start with vaguely conventional 32-bit-per-word memory with some of the bits used as tag bits; sure. Then you add a bit of extra memory per word hidden in the CPU, which flags whether there’s a type error; an addition of two fixnums is done in hardware at the same time the type checking is done, and if one of those fixnums was actually something else, the processor notices afterwards and fires an interrupt to resolve the error. Clever, cause it makes the common path (no error) happen with barely any type checking involved.
Then you get to the calling convention, which involves 256 stack frames hidden in non-addressable memory, kinda like a register window… Except it’s not a stack, the stack frames (well, activation records) are dynamically allocated and kept in a freelist, in hardware. That is definitely odd! And then there’s special instructions for tail calls and ways to shuffle continuations around and other stuff I don’t fully understand, but it comes down to: function calls can be REAL fast.
THEN you get to the point where functions are all statically linked, and if a function gets redefined it just marks it as such in a tag bit that tells the CPU to stop what it’s doing and basically re-link anything referring to that function… And my mind is THOROUGHLY blown.
I wish I know more about Lisp Machine architecture. I wonder how closely this stuff could map to a modern RISC architecture (like ARM), and if there would be some way of exploiting some instruction to do hardware type checking quickly?
If you can track down the paper Architecture of the Symbolics 3600 then you’ll probably learn a lot.
I don’t think it maps well to something like ARM, but it could probably be made to work. This presentation should help give you an idea.
Recent arm versions have hardware support for memory tagging - this might be useful when accessing memory. You could assume that value stored is of common type (tag) and get cpu to verify it for you while you try to execute happy path.
Additionally many modern cpu support much smaller address space than 64 bits so storing those type tags in pointers wouldn’t mean that fixnums need to be 24-26 bits wide but more like 48 bits wide.
Sadly my knowledge about all of this is very limited :(
The AMD64 architecture is specifically designed to discourage using the high bits in a pointer as tag bits because it causes hell every time they try to expand the address space.
If your values are always aligned to 8 bytes or such (which AMD64 helps facilitate, cause the stack has to start aligned), you have a few low bits which are unused and can be used for tags freely.
That said, it’s no harder to mask off high bits than low bits.
Very true, but masking the low bits is more future-proof.