;; In this log we look at the internals of SBCL to understand why EQ does FIXNUMs and FLOATs ;; but not BIGNUMs (nor strings, etc.) ;; A detailed tutorial on SBCL internals is at https://simonsafar.com/2020/sbcl/ [sergey@thepond ~]$ rlwrap sbcl ...skipped... * (eq 3.0 3.0) ; in GCL, this is nil. Try it. Alas, I don't know how to quickly expose GCL internals T * (SB-KERNEL:GET-LISP-OBJ-ADDRESS 3.0) 4629700416936869913 ; Ugh, decimal. Luckily, we can get hex: * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 3.0)) #.(SB-SYS:INT-SAP #X4040000000000019) ; this looks interesting. Like a pointer but maybe not a pointer * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 100)) #.(SB-SYS:INT-SAP #X000000C8) ; totally not a pointer. But: 0xc8 is 200 * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 100000000000000)) #.(SB-SYS:INT-SAP #XB5E620F48000) ; looks like a pointer * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 200)) #.(SB-SYS:INT-SAP #X00000190) ; 0x190 is 400. Seems like FIXNUMs are represented by number x2 * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 500)) #.(SB-SYS:INT-SAP #X000003E8) ; 0x3e8 is 1000. Totally a pattern. * (eq 500 500) T * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 5.0) ) #.(SB-SYS:INT-SAP #X40A0000000000019) ; note the change in the 3rd hex digit from the left * (SB-SYS:INT-SAP (SB-KERNEL:GET-LISP-OBJ-ADDRESS 6.0)) #.(SB-SYS:INT-SAP #X40C0000000000019) ; same pattern for this internal representation of FLOATs ;;; SBCL readily exposes the assembly of its compiled functions. * (defun fact (n) (if (= n 0) 1 (* n (fact (- n 1))))) WARNING: redefining COMMON-LISP-USER::FACT in DEFUN ;; uh ok, we just overwrote a native function by this name. This is fine, we are interested in the disassembly of ours, not theirs, just yet. FACT * (disassemble (function fact)) ; disassembly for FACT ; Size: 99 bytes. Origin: #xB800ADF1D0 ; FACT ; 1D0: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer ; 1D4: 488945F8 MOV [RBP-8], RAX ; 1D8: 488B55F0 MOV RDX, [RBP-16] ; 1DC: 31FF XOR EDI, EDI ; <<-- make a 0 ; 1DE: 41FF942431FDFFFF CALL [R12-719] ; [#5200000FFCC8] = #B800001260 ; GENERIC-= ; <<-- call a generic "=" compare function ; 1E6: 7442 JE L1 ; <<-- if arg is 0, jump to L1 (base case) ; 1E8: 488B55F0 MOV RDX, [RBP-16] ; 1EC: BF02000000 MOV EDI, 2 ; 1F1: 41FF942409FDFFFF CALL [R12-759] ; [#5200000FFCA0] = #B8000010A0 ; GENERIC-- ; <<-- that's subtracting 1 from the argument "n" ; 1F9: 4883EC10 SUB RSP, 16 ; 1FD: B902000000 MOV ECX, 2 ; 202: 48892C24 MOV [RSP], RBP ; 206: 488BEC MOV RBP, RSP ; 209: 498B442429 MOV RAX, [R12+41] ; LISP-LINKAGE-TABLE ; 20E: FF9060060000 CALL [RAX+1632] ; FACT <<<--- recursive call to self ; 214: 480F42E3 CMOVB RSP, RBX ; 218: 488BFA MOV RDI, RDX ; 21B: 488B55F0 MOV RDX, [RBP-16] ; 21F: 41FF942411FDFFFF CALL [R12-751] ; [#5200000FFCA8] = #B800001110 ; GENERIC-* ; <<-- multiple by the argument "n" ; 227: L0: C9 LEAVE ; 228: F8 CLC ; 229: C3 RET ; 22A: L1: BA02000000 MOV EDX, 2 ; <<--- this is returning a FIXNUM "1", see above ; 22F: EBF6 JMP L0 ; 231: CC0F INT3 15 ; Invalid argument count trap NIL * (defun fact1 (n acc) (if (= n 0) acc (fact1 (- n 1) (* n acc)))) FACT1 * (disassemble (function fact1)) ; disassembly for FACT1 ; Size: 102 bytes. Origin: #xB800ADF294 ; FACT1 ; 94: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer ; 98: 488945F8 MOV [RBP-8], RAX ; 9C: 488B55F0 MOV RDX, [RBP-16] ; A0: 31FF XOR EDI, EDI ; A2: 41FF942431FDFFFF CALL [R12-719] ; [#5200000FFCC8] = #B800001260 ; GENERIC-= ; AA: 7507 JNE L0 ; AC: 488B55E8 MOV RDX, [RBP-24] ; B0: C9 LEAVE ; B1: F8 CLC ; B2: C3 RET ; B3: L0: 488B55F0 MOV RDX, [RBP-16] ; B7: BF02000000 MOV EDI, 2 ; BC: 41FF942409FDFFFF CALL [R12-759] ; [#5200000FFCA0] = #B8000010A0 ; GENERIC-- ; C4: 488BF2 MOV RSI, RDX ; C7: 488975E0 MOV [RBP-32], RSI ; CB: 488B55F0 MOV RDX, [RBP-16] ; CF: 488B7DE8 MOV RDI, [RBP-24] ; D3: 41FF942411FDFFFF CALL [R12-751] ; [#5200000FFCA8] = #B800001110 ; GENERIC-* ; DB: 488BFA MOV RDI, RDX ; DE: 488B75E0 MOV RSI, [RBP-32] ; E2: 488BD6 MOV RDX, RSI ; E5: B904000000 MOV ECX, 4 ; EA: FF7508 PUSH QWORD PTR [RBP+8] ; ED: 498B442429 MOV RAX, [R12+41] ; LISP-LINKAGE-TABLE ; F2: FFA0D8040000 JMP [RAX+1240] ; FACT1 <<<--- JMP, not CALL! It's a loop now. The tail call has been optimized down to a loop. ; F8: CC0F INT3 15 ; Invalid argument count trap NIL * ;;;;;;;;;;;;;; And now for something completely different: circular lists! ;;;;;;;;;;;;;;; [sergey@thepond ~]$ gcl ...skipped... >(setq ll '(A B C D E)) (A B C D E) ;; Let's make a circular list. We'll loop it back at its last CONS cell. >(cdr (cdr (cdr (cdr (cdr ll))))) ; ahem, a CDR too far. Off-by-ones are still a thing :) NIL >(cdr (cdr (cdr (cdr ll)))) ; OK, this is what I meant (E) >(RPLACD (cdr (cdr (cdr (cdr ll)))) ll) (A B C D E A B C D E A B C D E A B C D E .... ) ;; <<-- repeating endlessly! The PRINT part of the READ-EVAL-PRINT loop (REPL) can't handle circular lists by default. Time to Ctrl+C Correctable error:Console interrupt. Fast links are on: do (si::use-fast-links nil) for debugging Signalled by "AN ANONYMOUS FUNCTION". If continued: A B C D EType :r to resume execution, or :q to quit to top level. Console interrupt. Broken at NIL. Type :H for Help. 1 (continue) Type :r to resume execution, or :q to quit to top level. 2 Return to top level. >>2 Top level. ;; BTW, this is the READ part of the REPL, from string to CONS, then handed to EVAL: >(READ-FROM-STRING "(RPLACD (cdr (cdr (cdr (cdr ll)))) ll)") (RPLACD (CDR (CDR (CDR (CDR LL)))) LL) 38 ;; Luckily, an environment variable will make PRINT to look for loops and avoid them: >(setf *print-circle* t) T >ll #1=(A B C D E . #1#) ;; we are safe now! ;; And now we can fix the list to be normal again: >(RPLACD (cdr (cdr (cdr (cdr ll)))) nil) (E) >ll ;; indeed, fixed. (A B C D E) >(setf *print-circle* nil) ;; back to living/PRINTing dangerously NIL >ll (A B C D E) ;; ...and we are fine :) >(RPLACA (cdr ll) 'V) ;; let's replace the second cons' CAR (V C D E) >ll (A V C D E) ;; there we have it: ;;;;;;;;;;;; Iterating over a list to produce a new list is a basic LISP operation ;;;;;;;;; >(MAPCAR (lambda (x) (* x x)) '(1 2 3 4 5 6 7)) (1 4 9 16 25 36 49) >(setq sq ''(1 2 3 4 5 6 7)) ;; Oops. What did I do wrong here? '(1 2 3 4 5 6 7) >(mapcar (lambda (x) (* x x)) sq) Correctable error:TYPE-ERROR: DATUM QUOTE EXPECTED-TYPE NUMBER Fast links are on: do (si::use-fast-links nil) for debugging Signalled by *. If continued: Check type again. TYPE-ERROR: DATUM QUOTE EXPECTED-TYPE NUMBER Broken at *. Type :H for Help. 1 (continue) Check type again. 2 Supply a new value of SYSTEM::N1. 3 Return to top level. >>3 Top level. >(setq sq '(1 2 3 4 5 6 7)) ;; OK, back to normal (1 2 3 4 5 6 7) >(mapcar (lambda (x) (* x x)) sq) (1 4 9 16 25 36 49) ;; the original list is unchanged, because MAPCAR makes new cons cells to collect the results >sq (1 2 3 4 5 6 7) ;;;;; -------- end log -------- If you wonder how I got this log back after the endless PRINT loop flooded my terminal, here is how: First, Ctrl+A in the terminal, to copy all of the text (including endless A B C D E A ...) into MacOS clipboard. Then: sergey@snowball lisp % pbpaste > circ-lisp-log.txt // That's a lot of lines: sergey@snowball lisp % wc circ-lisp-log.txt 20604 695926 1431057 circ-lisp-log.txt // Suppress the endless repetitions: sergey@snowball lisp % grep -v 'A B C D E A ' circ-lisp-log.txt > circ-lisp-log-filtered.txt // That's a lot less: sergey@snowball lisp % wc circ-lisp-log-filtered.txt 800 2786 24971 circ-lisp-log-filtered.txt // Now I can edit it in Emacs or Vim. The result is what you just read.