Numbers and tagged pointers in early Lisp implementations
snellman.net
snellman.net
My understanding of the history here is that a lot of Sun people, notably Bill Joy, came from Berkeley, where Franz Lisp was also developed; and in the mid-1980s (the first SPARC came out in 1987) it still seemed possible that Lisp would be of great commercial importance. AFAIK, these instructions were dropped for the 64-bit SPARC.
But the point of posting this is to underscore that the use of "fixnums", tagged immediate integers, is well known. Not only, as Juho has helpfully uncovered, does it date back to the 1960s, but it had explicit support in what was once a major microprocessor architecture.
I'll digress a little further and add that I really wish Java had used the Lisp approach for arbitrary-precision integers. Then Joshua Bloch would never have had to write this blog post: [0], among many other benefits.
[0] https://research.googleblog.com/2006/06/extra-extra-read-all...
Thanks for sharing the experience.
Ada was also another example of having Ada Machines, that is how Rational was created.
ld %rd, [%r1 - 1]
if %r1 didn't have a "01" tag, this would trap.Finally, SPARC reserved 8 registers for "global" use, that is registers what wouldn't be otherwise used by the compiler. Use was as simple as
register int *sp asm("%g3");
which was immensely valuable for VM implementations (and others).Unfortunately, the tagged add instructions didn't get much use; perhaps SPARC wasn't a big enough base to justify designing around it.
https://compilers.iecc.com/comparch/article/91-04-082
> Yes, the tagged arithmetic instructions were put in the SPARC architecture for Lucid Common Lisp.
http://ftp.lanet.lv/ftp/sun-info/sunflash/1990/Aug/20.01.lis...
http://3e8.org/pub/scheme/doc/lisp-pointers/v2i2/p5-endelman...
Lispworks's and Clozure CL's editor also started from Hemlock.
[1] https://books.google.co.uk/books/about/The_design_and_evalua... [2] https://en.wikipedia.org/wiki/Rice_Institute_Computer
This seems practical for a few reasons:
Full 64-bit pointers and numbers can fit into tagged words. (No boxing and so no heap allocation for u64 and pointer arithmetic.)
Naked 64-bit values can still be stored in registers and used directly. The JIT knows the type of each register and does not need to load the tag bits (they are checked on load and added on store.)
The x86-64 target CPU is efficient for unaligned access.
We will see how this works out in practice. Link: https://github.com/raptorjit/raptorjit/pull/93
First, this is a tracing JIT and so it basically inlines everything. There is no argument passing convention at all within a block of JIT code because subroutines bodies are completely inline. So - passing tagged values as arguments is actually a fairly infrequent operation.
Then in cases where tagged values really do need to be passed around they will always be passed by pointer reference. The JIT will write them to memory - the Lua stack or the heap - and pass a pointer reference in a register.
The really happy circumstance is that the whole C runtime system is already written to reference tagged values using pointers, and there is already a port that supports 64-bit Lua values using 32-bit machine words.
Good luck with the project.
There are really two kinds of supporting routines here.
Runtime system routines operate on tagged values and do have to swallow the stack convention. Thankfully the code is already written in this style and the JIT seldom invokes any of these routines in optimized programs (it generates inline versions.)
Second is C routines called via FFI. These use native C data types and the standard platform calling convention. They never see the tagged values (get an untagged uint64_t in a register instead of a TValue struct.) These are easy, cheap, and frequently used.
I'm not sure that PDP-6 Lisp used tags though, MacLisp used BiBop to determine the type of an object so the entries in the type table corresponding to the special pointers would have contained the fixnum type.
But it's a fair point that the encoding in PDP-6 LISP doesn't match what we'd think of as tagging today. They left the 0-pointer as a non-integer (probably that meant NIL), so the type test would have been done using two comparisons rather than a mask+compare.