4 ms·
As others hint, you don't need static typing to get optimized native code. For SBCL, you get native code already: * (defun adder (x y) (+ x y)) * (disa
by Jach 4y ago
As others hint, you don't need static typing to get optimized native code. For SBCL, you get native code already:
* (defun adder (x y) (+ x y))
* (disassemble 'adder)
; disassembly for ADDER
; Size: 37 bytes. Origin: #x541CEE1B ; ADDER
; 1B: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer
; 1F: 488945F8 MOV [RBP-8], RAX
; 23: 488BD6 MOV RDX, RSI
; 26: 488BFB MOV RDI, RBX
; 29: FF14250001A052 CALL QWORD PTR [#x52A00100] ; SB-VM::GENERIC-+
; 30: 488B5DE8 MOV RBX, [RBP-24]
; 34: 488B75F0 MOV RSI, [RBP-16]
; 38: 488BE5 MOV RSP, RBP
; 3B: F8 CLC
; 3C: 5D POP RBP
; 3D: C3 RET
; 3E: CC10 INT3 16 ; Invalid argument count trap
Because Common Lisp is dynamic, you can redefine adder with more information for the compiler to optimize with (such as types or ability to freely inline). The compiler is part of the runtime (which really ups the programmability of the language too, you can teach SBCL's compiler new optimization rules to output even better assembly). So if you Know What You're Doing, you can get rid of the generic add by declaring the expected argument and return types (e.g. a fixnum) and redefining the function:
* (declaim (ftype (function (fixnum fixnum) fixnum) adder))
(ADDER)
* (defun adder (x y) (+ x y))
WARNING: redefining COMMON-LISP-USER::ADDER in DEFUN
ADDER
* (disassemble 'adder)
; disassembly for ADDER
; Size: 35 bytes. Origin: #x541CE8DC ; ADDER
; DC: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer
; E0: 488945F8 MOV [RBP-8], RAX
; E4: 488D1C3E LEA RBX, [RSI+RDI]
; E8: 488BD3 MOV RDX, RBX
; EB: 48D1E2 SHL RDX, 1
; EE: 700A JO L0
; F0: 488D141B LEA RDX, [RBX+RBX]
; F4: 488BE5 MOV RSP, RBP
; F7: F8 CLC
; F8: 5D POP RBP
; F9: C3 RET
; FA: L0: CC52 INT3 82 ; OBJECT-NOT-FIXNUM-ERROR
; FC: 0E BYTE #X0E ; RBX(s)
; FD: CC10 INT3 16 ; Invalid argument count trap
Note SBCL still has some safety and debugging bits in there. You can tell it you Really Know What You're Doing, and do so locally so you don't put the rest of your code at risk:
* (defun adder (x y)
(declare (optimize (speed 3) (safety 0) (debug 0)))
(+ x y))
WARNING: redefining COMMON-LISP-USER::ADDER in DEFUN
ADDER
* (disassemble 'adder)
; disassembly for ADDER
; Size: 9 bytes. Origin: #x541CEAB6 ; ADDER
; 6: 4801FA ADD RDX, RDI
; 9: 488BE5 MOV RSP, RBP
; C: F8 CLC
; D: 5D POP RBP
; E: C3 RET
Because of the use of ftype, SBCL will let you know against certain kinds of errors elsewhere, at compile time:
* (defun mistake () (adder 5 "5"))
; in: DEFUN MISTAKE
; (ADDER 5 "5")
;
; caught WARNING:
; Constant "5" conflicts with its asserted type FIXNUM.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
MISTAKE
* (defun mistake2 () (adder 5 (subseq "555" 0 1)))
; in: DEFUN MISTAKE2
; (ADDER 5 (SUBSEQ "555" 0 1))
;
; caught WARNING:
; Derived type of (SB-KERNEL:VECTOR-SUBSEQ* SB-C::SEQ SB-C::START SB-C::END) is
; (VALUES (SIMPLE-ARRAY CHARACTER (1)) &OPTIONAL),
; conflicting with its asserted type
; FIXNUM.
...
(You would normally see these as you go as you send forms or whole files/buffers to the REPL from your editor, but you can also hook up a sort of 'linting' tool to catch them earlier, or at least fail CI jobs. I use vim rather than emacs and can detect such issues at file save-time; with some work on the underlying tooling it would be possible to detect them when leaving insert mode or switching buffers.)
Of course, because Lisp is dynamic, these are just "warnings" because you can always just redefine (and recompile) these things to fix issues without having to restart your program, or recompile the whole system yet again. And when you leave debugging on or even increase its priority during development, you get an experience in iterative creation unmatched by anything else.