Skip to content

ARM64 port draft milestones

R. Matthew Emerson edited this page Jan 5, 2024 · 8 revisions

An ARM64 port will take at the very least 3 to 4 months. This claim is based on past estimates from Gary Byers, who is a genius programmer and more familiar with the implementation of CCL than anyone. Less wizardly programmers will necessarily require more time.

Document the architecture.

Much will be taken from PPC-64, but will there be changes? It should at least be written down.

  • Tagging system
  • Register conventions
  • Stack layout
  • Calling conventions
  • Something about tail-call optimization?
  • Something about unwind-protect?
  • Something about atomic code & interruptibility?

Editing code & building infrastructure

This will involve a fair amount of work, including

  • Implementing the assembler and disassembler
  • Writing LAP (assembly) code to implement bignums and other functionality as found in ccl:level-0;ARCH; (where ARCH is ARM, X86, etc.)
  • Writing ARM64 vinsns (assembly language templates) for use by the compiler backend
  • Implement lisp-called subprimitive routines and other runtime support in the lisp kernel (about 10,000 lines of assembly)
  • Understand the ARM64 C ABI and implement support for calling external C code from CCL
  • Update and enhance our new libclang-based ffigen to create the .cdb files comprising the interface database (as used by the #_ reader macro).


There will not be much visible progress at this stage, except for a lot of commits. This step is likely to take an easy 6 to 8 weeks on its own.

Cross assemble simple LAP functions

When the 32-bit ARM port was coming up, this was an early milestone. A 64-bit ARM port will need a similar one.

(let* ((*target-backend* (find-backend  :linuxarm)))
 (%define-arm-lap-function
  'fact
  '((let ((n arg_z))
      (check-nargs 1)
      l0
      (cmp n ($ 0))
      (moveq arg_z '1)
      (bxeq lr)
      (build-lisp-frame imm0)
      (vpush1 arg_z)
      (sub arg_z arg_z '1)
      (bl l0)
      (vpop1 arg_y)
      (restore-lisp-frame imm0)
      (mov pc
           ($ (arm-subprimitive-address '.spbuiltin-times)))))))
#<XFUNCTION #xCFBEC6E>
? (uvref * 1)
#<XCODE-VECTOR #xCFBEC2E>
? (dotimes (i (uvsize *)) (format t "~&~d: #x~8,'0x" i (uvref * i)))
0: #xE3502004
1: #x17F001F0
2: #xE3504000
3: #x03A04004
4: #x012FFF1E
5: #xE3A00013
6: #xE92D4C01
7: #xE52A4004
8: #xE2444004
9: #xEBFFFFF7
10: #xE49A5004
11: #xE8BD4C01
12: #xE3A0FCA5
13: #x00000000
14: #x0000003C
NIL
?

Cross compile simple lambdas

Produce reasonable-looking results from examples like

(arm64-xcompile-lambda '(lambda () (+ 2 2)) :show-vinsns t)
(arm64-xcompile-lambda '(lambda (x) (prin1 (+ x 2))) :show-vinsns t)
;; demonstrate inline consing
(arm64-xcompile-lambda '(lambda (x y)
                         (let ((q (float (+ x y)))
                               (c (cons x y)))
                           (when (zerop q)
                             (format t "yow!"))
                           (vector x y q c))) :show-vinsns t)

Compiler backend “complete”

All .lisp files can be compiled to ARM64 fasls. This implies that there is an ARM64 backend (and file compiler) that is complete enough to compile the lisp sources. While it’s obviously desirable for the compiled code to be correct as possible, there’s no practical way to test this at this stage.

Lisp kernel and bootstrapping image

There is an ARM64 lisp kernel and bootstrapping image that can find and call the initial function in that image (namely, the value of %toplevel-function in ccl:level-0;nfasload.lisp). This demonstrates that there is a bootstrapping lisp image, and enough of a lisp kernel to map the image into memory and start executing lisp code.

Fasload up through l1-streams

The file l1-streams is the first thing in the loading sequence that makes significant use of CLOS, and a very high percentage of all the basic functionality in the lisp has to work to get to that point.

Self-bootstrap

The ARM64 lisp can re-compile itself.

Tests might be useful...