The VikiD Truth

Knowledge base / Computing / Lisp family

PicoLisp ASM

The PicoLisp Reference | ASM

A page about history (updated October 2026)

From about 2009 to 2020, 64-bit PicoLisp (pil64) was written in its own generic assembly language. The instruction set below was defined in PicoLisp (src64/arch/*.l), and PicoLisp itself translated those generic instructions into real x86-64, ARM64, PowerPC or emulated code. It was a brilliant way to get a fast, portable interpreter with complete control over the machine: registers, stack layout and the tagged-pointer tricks described in The PicoLisp Machine.

pil64 is no longer maintained. Since 2020 PicoLisp is pil21 (current release 26.9). Alexander Burger kept the idea of writing the system in PicoLisp, but swapped his own assembler for LLVM. The sources (src/*.l) are written in a PicoLisp DSL defined in src/lib/llvm.l, which emits LLVM IR. LLVM then handles register allocation, optimisation and code generation for every CPU it supports. The result runs on more platforms (including Linux on ARM, Android/Termux and macOS) with far less machine-specific code.

To see the difference, here is the whole of car in pil21 (src/subr.l). In pil64 the same function was a dozen hand-written VM instructions that evaluated the argument, tested the tag bits and jumped to an error handler:

(de _Car (Exe)
   (chkA (eval (cadr Exe))) )

The data model is unchanged: same cells, same tag bits, same symbols. What changed is that nobody has to write register moves by hand any more.

Why I keep this page

I studied this VM when I first went deep into PicoLisp, and it is still the clearest picture I know of what a Lisp interpreter does at the machine level: registers named for their role (Accumulator, Count, Expression, X/Y/Z index), a stack (S), and a carry/zero/sign flag model that maps directly onto real CPUs. If you want to understand LLVM IR or real assembly, this is a friendly stepping stone. The original reference follows, unchanged.

The pil64 VM reference (archive)

Quicklinks | Readinglist | Computing | Picolisp | The Picolisp Reference | ASM

   CPU Registers:

      +---+---+---+---+---+---+---+---+
      |               A           | B |  \\      [A]ccumulator
      +---+---+---+---+---+---+---+---+   D     [B]yte register
      |               C               |  /      [C]ount register
      +---+---+---+---+---+---+---+---+         [D]ouble register
      |               E               |         [E]xpression register
      +---+---+---+---+---+---+---+---+

      +---+---+---+---+---+---+---+---+
      |               X               |         [X] Index register
      +---+---+---+---+---+---+---+---+         [Y] Index register
      |               Y               |         [Z] Index register
      +---+---+---+---+---+---+---+---+
      |               Z               |
      +---+---+---+---+---+---+---+---+

      +---+---+---+---+---+---+---+---+
      |               L               |         [L]ink register
      +---+---+---+---+---+---+---+---+         [S]tack pointer
      |               S               |
      +---+---+---+---+---+---+---+---+

      +-------------------------------+
      |  [z]ero  [s]ign  [x,b,c]arry  |         [F]lags
      +-------------------------------+
   Source Addressing Modes:
      ld A 1234            # Immediate
      ld A "(a+b-c)"
      ld A R               # Register
      ld A Global          # Direct
      ld A (R)             # Indexed
      ld A (R 8)           # Indexed with offset
      ld A (R OFFS)
      ld A (R Global)
      ld A (Global)        # Indirect
      ld A (Global OFFS)   # Indirect with offset
      ld A ((R))           # Indexed indirect
      ld A ((R 8))         # Indexed with offset indirect
      ld A ((R 8) OFFS)
      ld A ((R Global) OFFS)
      ld A ((R OFFS) Global)
      ...

   Destination Addressing Modes:
      ld R A               # Register
      ld (R) A             # Indexed
      ld (R 8) A           # Indexed with offset
      ld (R OFFS) A
      ld (R Global) A
      ld (Global) A        # Indirect
      ld (Global OFFS) A   # Indirect with offset
      ld ((R)) A           # Indexed indirect
      ld ((R 8)) A         # Indexed with offset indirect
      ld ((R 8) OFFS) A
      ld ((R Global) OFFS) A
      ld ((R OFFS) Global) A
      ...

   Target Addressing Modes:
      jmp 1234             # Absolute
      jmp Label
      jmp (R)              # Indexed
      jmp (R T)            # Indexed SUBR
      jmp (Global)         # Indirect

========================================================================

   Instruction set:
      nop               # No operation

   Move Instructions:
      ld dst src        # Load 'dst' from 'src' [---]
      ld2 src           # Load 'A' from two bytes 'src' (unsigned) [---]
      ld4 src           # Load 'A' from four bytes 'src' (unsigned) [---]
      ldz dst src       # Load if Zero 'dst' from 'src' [---]
      ldnz dst src      # Load if not Zero 'dst' from 'src' [---]
      lea dst src       # Load 'dst' with effective address of 'src' [---]
      st2 dst           # Store two bytes from 'A' into 'dst' [---]
      st4 dst           # Store four bytes from 'A' into 'dst' [---]
      xchg dst dst      # Exchange 'dst's [---]
      movn dst src cnt  # Move 'cnt' bytes from 'src' to 'dst' (non-overlapping or src < dst)
      mset dst cnt      # Set 'cnt' bytes of memory to B
      save src end dst  # Save 'src'..'end' to 'dst' (aligned, non-overlapping or src < dst)
      load dst end src  # Load 'dst'..'end' from 'src' (aligned, non-overlapping)

   Arithmetics:
      add dst src       # Add 'src' to 'dst' [zsc]
      addc dst src      # Add 'src' to 'dst' with Carry [zsc]
      sub dst src       # Subtract 'src' from 'dst' [zsb]
      subb dst src      # Subtract 'src' from 'dst' with Borrow [zsb]

      inc dst           # Increment 'dst' [zs.]
      dec dst           # Decrement 'dst' [zs.]
      not dst           # One's complement negation of 'dst' [z..]
      neg dst           # Two's complement negation of 'dst' [zs.]

      and dst src       # Bitwise AND 'dst' with 'src' [zs.]
      or dst src        # Bitwise OR 'dst' with 'src' [zs.]
      xor dst src       # Bitwise XOR 'dst' with 'src' [zs.]
      off dst src       # Clear 'src' bits in 'dst' [zs.]
      test dst src      # Bit-test 'dst' with 'src' [zs.]

      shl dst src       # Shift 'dst' left into x-bit by 'src' bits [zsx]
      shr dst src       # Shift 'dst' right into x-bit by 'src' bits [zsx]
      rol dst src       # Rotate 'dst' left by 'src' bits [...]
      ror dst src       # Rotate 'dst' right by 'src' bits [...]
      rxl dst src       # Rotate 'dst' with x-bit left by 'src' bits [zsx]
      rxr dst src       # Rotate 'dst' with x-bit right by 'src' bits [zsx]

      mul src           # Multiplication of 'A' and 'src' into 'D' [...]
      div src           # Division of 'D' by 'src' into 'A', 'C' [...]

      zxt               # Zero-extend 'B' to 'A' [...]
      eq                # Set Zero and clear Borrow [z._]
      gt                # Clear Zero and Borrow [_._]
      lt                # Clear Zero and set Borrow [_.b]
      setx              # Set x-bit [--x]
      clrx              # Clear x-bit [--_]

   Comparisons:
      cmp dst src       # Compare 'dst' with 'src' [zsc]
      cmpn dst src cnt  # Compare 'cnt' bytes 'dst' with 'src' [z..]
      slen dst src      # Set 'dst' to the string length of 'src' [...]
      memb src cnt      # Find B in 'cnt' bytes of 'src' memory [z..]
      null src          # Compare 'src' with 0 [zs_]
      nulp src          # Check 'src' for null-pointer [z..]
      nul4              # Compare four bytes in 'A' with 0 [zs_]

   Byte addressing:
      set dst src       # Set 'dst' byte to 'src' [---]
      nul src           # Compare byte 'src' with 0 [z..]

   Types:
      cnt src           # Non-'z' if small number [z..]
      big src           # Non-'z' if bignum [z..]
      num src           # Non-'z' if number [z..]
      sym src           # Non-'z' if symbol [z..]
      atom src          # Non-'z' if atom [z..]

   Floating point:
      ldd               # Load double value pointed to by 'C'
      ldf               # Load float value pointed to by 'C'
      fixnum            # Convert double with scale 'E' to fixnum in 'E'
      float             # Convert fixnum with scale 'A' pointed to by 'X'
      std               # Store double value at address 'Z'
      stf               # Store float value at address 'Z'

   Flow control:
      jmp adr           # Jump to 'adr' [---]
      jz adr            # Jump to 'adr' if Zero [---]
      jnz adr           # Jump to 'adr' if not Zero [---]
      js adr            # Jump to 'adr' if Sign [---]
      jns adr           # Jump to 'adr' if not Sign [---]
      jc adr            # Jump to 'adr' if Carry set [---]
      jnc adr           # Jump to 'adr' if Carry not set [---]
      jb adr            # Jump to 'adr' if Borrow set [---]
      jnb adr           # Jump to 'adr' if Borrow not set [---]
      jx adr            # Jump to 'adr' if x-bit set [---]
      jnx adr           # Jump to 'adr' if x-bit not set [---]

      call adr          # Call 'adr'
      cc adr(src ..)    # C-Call to 'adr' with 'src' arguments
      cc adr reg        # C-Call to 'adr' with top of stacked args in 'reg'

      ret               # Return [---]
      catch             # Prepare non-local jump [---]
      throw             # Resolve non-local jump [---]
      func              # Convert 'E' to function pointer
      begin             # Called from foreign function
      return            # Return to foreign function

   Stack Manipulations:
      push src          # Push 'src' [---]
      pop dst           # Pop 'dst' [---]
      tos dst           # Get return entry [---]
      link              # Setup frame [---]
      tuck src          # Extend frame [---]
      drop              # Drop frame [---]

   Evaluation:
      eval              # Evaluate expression in 'E'
      eval+             # Evaluate expression in partial stack frame
      eval/ret          # Evaluate expression and return
      exec reg          # Execute lists in 'reg', ignore results
      prog reg          # Evaluate expressions in 'reg', return last result

   System:
      initData          # Init runtime data
      initCode          # Init runtime code
      initMain          # Command in X, arguments in Y, last pointer in Z
      initLib           # Library function pointer in A
      stop              # Stop (system-dependent)

========================================================================

   Naming conventions:

   Lisp level functions, which would be all of the form 'doXyzE_E', are written
   as 'doXyz' for brevity.

Last edited · 6 revisions

Tags: #Computing