HACKER Q&A
📣 HexDecOctBin

Guide for Implementing Common Lisp


I have written compilers and interpreters before, but I find it hard to understand how Common Lisp compilers work – what with their readtables and read macros and normal macros. It's hard to figure out how the compilation pipelines actually works, and how the process of implementing them goes (e.g., what subsets are implemented before the rest, etc.).

This got me thinking: is there any implementation guide on how a Common Lisp is/should be implemented?


  👤 Jach Accepted Answer ✓
This is a very approachable paper from 1990 on one way to do it with a C kernel bootstrapping to Common Lisp: https://www.softwarepreservation.org/projects/LISP/kcl/paper... Kyoto Common Lisp (KCL) is the ancestor of today's Embeddable Common Lisp (ECL).

SICL is probably the best modern version of CL written in CL from a design standpoint, even if it's not taking over SBCL's role anytime soon: https://github.com/robert-strandh/SICL It uses some fancy bootstrapping to have the whole language available early, e.g. their definition of class 'symbol is:

    (defclass symbol (t)
      ((%name :reader symbol-name)
       (%package :reader symbol-package))
      (:metaclass built-in-class))
vs SBCL:

    (define-primitive-object
        (symbol :lowtag other-pointer-lowtag
                :widetag symbol-header-widetag
                :alloc-trans %make-symbol
                :type symbol)
      ...
      (name :ref-trans symbol-name :init :arg)
      (package :ref-trans symbol-package
               :set-trans %set-symbol-package
               :init :null)
      ...)
(That's from one of the papers from the 2019 European Lisp Symposium: https://www.european-lisp-symposium.org/2019/index.html Scroll down to the bottom for proceedings with all the papers in the pdf. Going through the ELS archives and checking out papers and their references can be useful for learning more about this stuff too.)

👤 oumua_don17
Lisp from Nothing and Lisp System Implementation from [1].

There are quite a few open source Lisp implementations in addition to SBCL that you can peruse [2] (follow links to CL's supported by Quicklisp). If you are targeting a specific platform (JVM or embedded), then ABCL/ECL; see Clasp for using LLVM.

To reuse modules for writing your own lisp, see SICL [3]

[1] http://t3x.org

[2] https://www.quicklisp.org/beta/

[3] https://github.com/robert-strandh/SICL


👤 kazinator
Common Lisp compilers do not interact with read macros at all. Read macros are done by the time a compiler gets to the code.

Depending on the design, macros may also be expanded already; the compiler works with macro-expanded code.

The easiest approach, from a boostrapping point of view, is to write an interpreter in some host language. Get macro expansion and read macros working in the interpreter, and then add a compiler.


👤 pfdietz
The readtables and reader macros are entirely independent of Common Lisp compilers. The compilers work on the representation (that is, trees of cons cells) that is produced by the reader, which is after the readtables and reader macros have done their work.

If you are thinking of a Common Lisp compiler as working on a text file, you're thinking about it wrong. In Common Lisp, you can invoke the COMPILE function on a lambda expression that you cons up at runtime and that never is in a textual representation.

As a general rule, don't reimplement a Lisp except as a learning exercise (in which case, you're not going to reimplement Common Lisp; it's too big.)


👤 ceving
Loko is a pretty new Scheme compiler: https://scheme.fail/

It implements everything down to assembler on bare metal.

You can easily analyze what is going on by disassembling the code:

    > (disassemble car)
    Disassembly for #
   
      entry:
       2073E8 83F8F8       (cmp eax #xFFFFFFF8)
       2073EB 0F8506000000 (jnz L0)
     ; (set! rax (car rdi))
       2073F1 488B47FE     (mov rax (mem64+ rdi #x-2))
       2073F5 F8           (clc)
       2073F6 C3           (ret)
      L0:
       2073F7 E9749CFFFF   (jmp (+ rip #x-638C))
    >

👤 hayley-patton
> but I find it hard to understand how Common Lisp compilers work – what with their readtables and read macros and normal macros

Read tables and reader macros are used in the reader, which is separate from the compiler. The compiler macroexpands normal macros though.

> what subsets are implemented before the rest

There are no obvious subsets in Common Lisp - see Baker on the matter <https://www.plover.com/~mjd/misc/hbaker-archive/MetaCircular...>.

SBCL is mostly written without CLOS and then brings in a CLOS implementation late into bootstrapping; that of course prevents you from using CLOS in some places, so you may not want to do that though.

> is there any implementation guide on how a Common Lisp is/should be implemented?

You could do a lot better than the existing Common Lisp implementations, especially in compilers and GC, but the issues there are not specific to Common Lisp.


👤 soegaard
Lisp in Small Pieces - Christian Queinnec https://christian.queinnec.org/WWW/LiSP.html

Anatomy of LISP - John Allen https://archive.org/details/anatomyoflisp0000alle/



👤 Tomte
Lisp in Small Pieces is the standard recommendation.

👤 checker659
Just for completeness sake, here's "arpilisp: A Lisp interpreter for Raspberry Pi implemented in a single ARM assembly file" : https://github.com/marcpaq/arpilisp

*Includes the classic mark-sweep GC from McCarthy 1960


👤 mepian
Starting with the obvious, are you familiar with the HyperSpec? https://www.lispworks.com/documentation/HyperSpec/Front/inde...

👤 bitwize
Mark: How do I compile Lisp source code?

Nolan: That's the neat thing -- you don't.

Lisp compilers operate on Lisp data structures -- generally speaking, mostly lists and atoms. The 'read' function is what turns text into these data structures; reader macros alter how this function operates and control what data structures will be emitted by the reader. This really is the neat part about Lisp -- it's much simpler to write a compiler that consumes an AST directly than one that consumes text, and the AST is expressed directly in terms of Lisp's primitive data structures which you can easily write your own functions over. And it's a relative doddle to write a reader for s-expressions compared to other expression formats. Join these two relatively simple pieces together and you have a complete Lisp compiler system.

As for regular macros, they transform the AST before it's evaluated or compiled. So they come into play after the reader has consumed the source and produced an AST, but before the evaluator or compiler can work.


👤 thesuperbigfrog
Make a Lisp: https://github.com/kanaka/mal

Make a Lisp is more Clojure-flavored than Common Lisp, but it might still be nice to examine because of how many different implementation languages it has been done in by various authors.

It also breaks down the implementation process into 11 distinct steps which are easy to understand.


👤 GianFabien
Perhaps reading the source code for SBCL, https://www.sbcl.org/porting.html provide some of the answers you seek.

👤 silcoon
Compiling a Lisp, the series https://bernsteinbear.com/blog/lisp/

👤 tensility
I would suggest reading Lisp In Small Pieces by Christian Quiennec.

👤 grow2grow
Asking the real questions, thanks.

👤 brudgers
SBCL is open source.

That might be a way to start.

Or not.

Good luck.