Lisp与计算机硬件的交互看起来如何?

Geo*_*ard 5 common-lisp

在C语言中,由于诸如“地址”和“易失性”之类的概念已内置于该语言中,因此很容易操作存储器和硬件寄存器。因此,大多数操作系统都是使用C语言族编写的。例如,我可以将任意函数复制到内存中的任意位置,然后将该位置作为函数调用(假设硬件不会阻止我执行数据;当然,这在某些微控制器上是可行的)。

int hello_world()
{
    printf("Hello, world!");
    return 0;
}

int main()
{
    unsigned char buf[1000];
    memcpy(buf, (const void*)hello_world, sizeof buf);
    int (*x)() = (int(*)())buf;
    x();
}
Run Code Online (Sandbox Code Playgroud)

但是,我一直在阅读有关某些专用Lisp机器的Open Genera操作系统的信息。维基百科说:

属完全用Lisp编写;甚至所有低级系统代码都用Lisp编写(设备驱动程序,垃圾回收,进程调度程序,网络堆栈等)。

我对Lisp完全陌生,但这似乎是一件困难的事情:从我所见,Common Lisp对运行的硬件没有很好的抽象。Common Lisp操作系统将如何做一些基本的事情,例如编译以下琐碎的函数,将其机器代码表示形式写入内存,然后调用它?

(defun hello () (format t "Hello, World!"))
Run Code Online (Sandbox Code Playgroud)

当然,Lisp 本身可以很容易地实现,但是用Sam Hughes 的话说:“在某个地方,抽象用完了,机器必须执行一条指令。”

Syl*_*ter 4

Lisp 机器是一个带有 CPU 的计算机硬件,就像今天的现代机器一样,只是 CPU 有一些可以更好地映射到 LISP 的特殊指令。它仍然是一个堆栈机,并将其源代码编译为 CPU 指令,就像现代 Common Lisp 实现在更通用的 CPU 上所做的那样。

Lisp 机器维基百科页面中,您可以看到函数是如何编译的:

(defun example-count (predicate list)
  (let ((count 0))
    (dolist (i list count)
      (when (funcall predicate i)
        (incf count)))))

(disassemble (compile #'example-count))

  0  ENTRY: 2 REQUIRED, 0 OPTIONAL      ;Creating PREDICATE and LIST
  2  PUSH 0                             ;Creating COUNT
  3  PUSH FP|3                          ;LIST
  4  PUSH NIL                           ;Creating I
  5  BRANCH 15
  6  SET-TO-CDR-PUSH-CAR FP|5
  7  SET-SP-TO-ADDRESS-SAVE-TOS SP|-1
 10  START-CALL FP|2                    ;PREDICATE
 11  PUSH FP|6                          ;I
 12  FINISH-CALL-1-VALUE
 13  BRANCH-FALSE 15
 14  INCREMENT FP|4                     ;COUNT
 15  ENDP FP|5
 16  BRANCH-FALSE 6
 17  SET-SP-TO-ADDRESS SP|-2
 20  RETURN-SINGLE-STACK
Run Code Online (Sandbox Code Playgroud)

然后将其存储在某个内存位置,当运行此函数时,它只是跳转或调用此函数。与任何汇编代码一样,CPU 在运行完此代码后会收到指示继续运行其他代码,并且可能是 Lisp 主循环本身(REPL)。

使用 SBCL 编译的相同代码:

; Size: 203 bytes
; 02CB9181:       48C745E800000000 MOV QWORD PTR [RBP-24], 0  ; no-arg-parsing entry point
;      189:       488B4DF0         MOV RCX, [RBP-16]
;      18D:       48894DE0         MOV [RBP-32], RCX
;      191:       660F1F840000000000 NOP
;      19A:       660F1F440000     NOP
;      1A0: L0:   488B4DE0         MOV RCX, [RBP-32]
;      1A4:       8D41F9           LEA EAX, [RCX-7]
;      1A7:       A80F             TEST AL, 15
;      1A9:       0F8598000000     JNE L2
;      1AF:       4881F917001020   CMP RCX, 537919511
;      1B6:       750A             JNE L1
;      1B8:       488B55E8         MOV RDX, [RBP-24]
;      1BC:       488BE5           MOV RSP, RBP
;      1BF:       F8               CLC
;      1C0:       5D               POP RBP
;      1C1:       C3               RET
;      1C2: L1:   488B45E0         MOV RAX, [RBP-32]
;      1C6:       488B40F9         MOV RAX, [RAX-7]
;      1CA:       488945D8         MOV [RBP-40], RAX
;      1CE:       488B45E0         MOV RAX, [RBP-32]
;      1D2:       488B4801         MOV RCX, [RAX+1]
;      1D6:       48894DE0         MOV [RBP-32], RCX
;      1DA:       488B55F8         MOV RDX, [RBP-8]
;      1DE:       4883EC18         SUB RSP, 24
;      1E2:       48896C2408       MOV [RSP+8], RBP
;      1E7:       488D6C2408       LEA RBP, [RSP+8]
;      1EC:       B902000000       MOV ECX, 2
;      1F1:       FF1425B80F1020   CALL QWORD PTR [#x20100FB8]  ; %COERCE-CALLABLE-TO-FUN
;      1F8:       488BC2           MOV RAX, RDX
;      1FB:       488D5C24F0       LEA RBX, [RSP-16]
;      200:       4883EC18         SUB RSP, 24
;      204:       488B55D8         MOV RDX, [RBP-40]
;      208:       B902000000       MOV ECX, 2
;      20D:       48892B           MOV [RBX], RBP
;      210:       488BEB           MOV RBP, RBX
;      213:       FF50FD           CALL QWORD PTR [RAX-3]
;      216:       480F42E3         CMOVB RSP, RBX
;      21A:       4881FA17001020   CMP RDX, 537919511
;      221:       0F8479FFFFFF     JEQ L0
;      227:       488B55E8         MOV RDX, [RBP-24]
;      22B:       BF02000000       MOV EDI, 2
;      230:       41BBF0010020     MOV R11D, 536871408        ; GENERIC-+
;      236:       41FFD3           CALL R11
;      239:       488955E8         MOV [RBP-24], RDX
;      23D:       E95EFFFFFF       JMP L0
;      242:       CC0A             BREAK 10                   ; error trap
;      244:       02               BYTE #X02
;      245:       19               BYTE #X19                  ; INVALID-ARG-COUNT-ERROR
;      246:       9A               BYTE #X9A                  ; RCX
;      247: L2:   CC0A             BREAK 10                   ; error trap
;      249:       02               BYTE #X02
;      24A:       02               BYTE #X02                  ; OBJECT-NOT-LIST-ERROR
;      24B:       9B               BYTE #X9B                  ; RCX
NIL
Run Code Online (Sandbox Code Playgroud)

指令并不那么少。当运行这个函数时,它是获得控制的机器代码,并将控制权交还给系统,因为返回地址可能是 REPL 或下一条指令,就像编译的 C 语言一样。

一般来说,Lisp 的一个特殊之处是需要处理词法闭包。在 C 中,当调用完成时,变量不再存在,但在 Lisp 中,它可能会返回或存储稍后使用这些变量的函数,并且该函数不再在范围内。这意味着变量的处理效率几乎与编译代码中的解释代码一样低效,尤其是对于没有进行太多优化的旧编译器。

C 编译器是否也能进行翻译,否则 C 语言编程比汇编语言编程的原因是什么?Intel x86 处理器不支持过程调用中的参数。它由 C 编译器模拟。调用者在堆栈上设置值,然后进行清理,然后将其撤消。诸如 for 和 while 之类的循环结构不存在。仅分支/jmp。是的,在 C 语言中你可以对底层硬件有更多的感觉,但它确实与机器代码不同。它只会泄漏更多。

作为操作系统的 Lisp 实现可以具有诸如作为 Lisp 操作码的低级汇编指令等功能。然后编译会将所有内容转换为低级 lisp,然后这些字节与机器字节的比例为 1:1。

具有 ac 库和 ac 编译器的操作系统可以完成同样的事情。它运行机器代码的翻译,然后可以运行代码本身。这也是 Lisp 系统的工作方式,因此您唯一需要的是硬件 API,可以是低级别的内存映射 I/O 等。