1 điểm bởi GN⁺ 2024-09-14 | 1 bình luận | Chia sẻ qua WhatsApp
  • Để chạy game engine Trial trên Nintendo Switch, tác giả đã port Common Lisp runtime SBCL trong 2 năm và hiện đã đạt đến mức có thể biên dịch/chạy mã Lisp trên Switch cũng như liên kết với thư viện dùng chung
  • Switch là môi trường ARM64 Cortex-A57 4 nhân, RAM 4GB, nhưng không có shell, dòng lệnh hay compiler, và cũng chặn việc tạo các trang có thể thực thi, nên không phù hợp với cách bootstrap SBCL thông thường
  • Quy trình build kết hợp host Linux ARM64 với Nintendo SDK; dùng fasteval, immobile-codeelfination để tách core thành code và data rồi đóng gói thành package cuối cùng
  • Ví dụ REPL của Trial trên Switch devkit thực hiện được cả tạo OpenGL context, xử lý input và cấp phát shader, nhưng bị crash khi chạy garbage collector, còn audio hiện chưa khả dụng do vấn đề C callback
  • Các việc cốt lõi còn lại là ổn định safepoint cho GC đa luồng, khôi phục C callback, tránh biên dịch runtime của CLOS, tối ưu hiệu năng; một phần mã không thể công khai vì NDA của Nintendo

SBCL hoạt động được đến đâu trên Switch

  • Để chạy game engine Trial trên Nintendo Switch, tác giả đã cùng Charles Zhang port SBCL trong 2 năm qua
  • Bài toán khó cốt lõi là điều chỉnh Common Lisp runtime chạy bên dưới Trial cho phù hợp với nền tảng Switch
  • Hiện có thể làm được:
    • Port runtime và compiler để có thể trực tiếp biên dịch và chạy mã Lisp tùy ý trên Switch
    • Giao tiếp được với các thư viện dùng chung
    • Cũng đã port nhiều thư viện portability ở mức hệ điều hành mà Trial cần trên Switch
  • Ví dụ REPL của Trial chạy trên Switch devkit
    • Vì bản thân Switch không có terminal, Trial tạo OpenGL context, quản lý input và cấp phát shader để hiển thị văn bản lên màn hình
  • Những phần vẫn còn bị vướng:
    • SBCL nhanh chóng crash khi cố chạy garbage collector
    • Vẫn còn những chỗ chưa thể đi vòng do các ràng buộc riêng của Switch
    • Cơ chế C callback đang hỏng nên không thể xuất audio
    • Có thể còn các vấn đề hiệu năng chưa lộ diện

Chi phí và tính bền vững

  • Công việc port đến nay đã tốn khoảng 17.000 USD, được trả cho Charles Zhang theo từng tháng
  • Nguồn thu đến từ doanh số Kandria và tài trợ qua Patreon, GitHub Sponsors, Ko-Fi
    • Tháng tốt khoảng 1.200 USD
    • Tháng xấu khoảng 600 USD
  • Rất khó sống chỉ bằng nguồn thu này ở Zürich, Thụy Sĩ; hiện tác giả sống cùng bố mẹ và cắt giảm chi tiêu cá nhân để duy trì
  • Vì không chắc doanh số Kandria cho Switch có thu hồi được chi phí port hay không, việc quyết định dùng nguồn lực hạn chế vào đâu trở nên khó khăn
  • Doanh thu tài trợ trong thời gian tới sẽ được dùng cho việc port SBCL lên Switch và một dự án game chưa công bố đang thực hiện

Môi trường Switch và điều kiện port

  • Môi trường Switch được biết công khai:
    • Mã người dùng chạy trên chip ARM64 Cortex-A57
    • Sử dụng 4 nhân và RAM 4GB
    • Hoạt động trên một hệ điều hành microkernel độc quyền ban đầu được phát triển cho Nintendo 3DS
  • SBCL đã có sẵn port ARM64 Linux, nên phần sinh mã đã được giải quyết
  • Kandria dễ dàng nằm trong RAM 4GB, vì vậy dung lượng bộ nhớ tự thân không phải vấn đề
  • Khó khăn thực sự nằm ở việc giao tiếp với hệ điều hành độc quyền của Switch
    • Có những ràng buộc không tồn tại trên hệ điều hành PC thông thường
    • Điều này đặc biệt gây vấn đề với các hệ thống như Lisp
  • Switch là console duy nhất hỗ trợ render OpenGL mà Trial dựa vào
    • Xbox chỉ hỗ trợ DirectX, nhưng được biết là có một lớp OpenGL → DirectX do Microsoft phát triển nên có thể vẫn có khả năng
    • Playstation được biết là có API đồ họa hoàn toàn độc quyền, nên vẫn là nền tảng không muốn cân nhắc port sang
  • Để phát triển, tác giả đã nhận được quyền truy cập từ Nintendo of Europe và mua devkit khoảng 400 USD
  • Devkit và SDK chỉ chạy trên Windows, về sau trở thành gánh nặng trong quy trình build

Cách build SBCL thông thường và xung đột trên Switch

  • SBCL chủ yếu được viết bằng chính Lisp, đồng thời có một C runtime nhỏ
  • C runtime được build bằng compiler C thông thường, nhưng cần biết thông tin về môi trường hệ điều hành đích
  • Vì bản thân runtime không có Lisp compiler, để bootstrap SBCL cần một implementation Lisp khác, lý tưởng là một phiên bản SBCL khác
  • Quy trình build 5 bước trên PC

    • build-config
      • Tập hợp các tùy chọn cấu hình build cho target và xuất ra định dạng mà các bước build sau có thể đọc
    • make-host-1
      • Build cross-compiler bằng compiler Lisp trên host
      • Cũng tạo các header file mô tả layout bộ nhớ của object Lisp bằng struct C
    • make-target-1
      • Tạo C runtime bằng compiler C của target
      • C runtime bao gồm garbage collector và mã keo nối với môi trường hệ điều hành
      • Cũng tạo từ header hệ điều hành các hằng số mà compiler Lisp và runtime của target cần biết
    • make-host-2
      • Dùng cross-compiler Lisp tạo ở make-host-1 để build hệ thống Lisp đích, tức compiler và standard library
      • Tạo cold core mà runtime có thể đi vào
    • make-target-2
      • Load cold core vào runtime đích và hoàn tất bootstrap
      • Sau khi hệ thống Lisp được load vào bộ nhớ, nó được dump thành warm core
      • Từ đó về sau có thể tự do load mã mới và dump image mới
  • Điểm gây vấn đề trên Switch

    • Build SBCL cần một bước chạy mã Lisp trên máy đích
    • Mã Lisp của người dùng không thể xử lý chỉ bằng batch compilation đơn giản như C, và giả định rằng khi chạy nó đang ở trong môi trường đích
    • Việc phân phối ứng dụng cũng tương tự make-target-2: biên dịch dần mã Lisp rồi dump core kèm runtime
    • Khi khởi động, SBCL runtime map blob core vào bộ nhớ, đánh dấu các trang code là có thể thực thi, rồi nhảy vào hàm entry tùy chỉnh
    • Trên Switch, toàn bộ cách này đều có vấn đề

Chiến lược build cho Switch

  • Switch không phải môi trường PC và không có shell, dòng lệnh hay bộ compiler
  • Vì hệ điều hành không cho phép tạo trang có thể thực thi, ngay cả khi có thể chạy các bước build trên Switch thì incremental compilation Lisp thông thường cũng không khả dụng
  • Phần lớn mã là độc lập nền tảng nên có thể biên dịch cho ARM64
  • Chỉ những phần chạm đến môi trường hệ điều hành xung quanh mới cần biết target là Switch, còn implementation có thể được tạo trên môi trường ARM64 khác như Linux
  • Các bước build cho Switch

    • build-config
      • Chạy trên hệ thống host và dùng flag đặc biệt biểu thị build cho Switch
      • Bật contrib fasteval
      • Vì trên Switch không thể biên dịch runtime, fasteval thay thế những điểm cần gọi compiler
    • make-host-1
      • Không thay đổi lớn, tạo header chuẩn bị cho nền tảng Switch
    • make-target-1
      • Cross-compile C runtime cho Switch bằng compiler C do Nintendo SDK cung cấp
      • Vì Switch OS không tương thích POSIX, tạo target runtime tùy chỉnh trong SBCL và stub hoặc bọc các khác biệt hệ điều hành như dynamic linking, page mapping
    • Lần thứ hai chạy build-config, make-host-1, make-target-1
      • Tạo một hệ thống ARM64 Linux thông thường có cùng tập tính năng với Switch
      • Với một số process Lisp, dùng flag đặc biệt báo rằng target cuối cùng là Switch
    • make-host-2, make-target-2
      • Thu được một build SBCL cho Linux ARM64 hơi đặc biệt, rồi sau đó biên dịch mã người dùng
    • Biên dịch mã người dùng
      • Sửa *features* để khiến hệ thống tin rằng nó đang chạy trên Switch chứ không phải Linux
      • Bao gồm :nx và loại trừ :linux, :unix, :posix
      • Vô hiệu hóa ASDF rồi biên dịch chương trình như Trial theo cách gần với thông thường và dump core mới

Đóng gói core bằng immobile-code và elfination

  • Vì trên Switch không thể dùng chiến lược mapping core thông thường, cách gắn core mới vào runtime cho Switch không hoạt động
  • Giải pháp là dùng immobile-codeelfination, các tính năng tương đối ít được biết đến của SBCL
  • SBCL thông thường đặt mã được biên dịch lúc runtime vào một trang nào đó và đánh dấu trang đó là có thể thực thi
    • Về sau nếu không cần mã đó nữa thì có thể trở thành đối tượng của garbage collection
    • Có thể thu hồi không gian và nén phần mã còn lại
  • immobile-code dùng chiến lược khác
    • Đặt mã vào các trang code được dành riêng và giữ nguyên ở đó
    • Mã không thể bị garbage collection
    • Đổi lại có thể tận dụng hỗ trợ file thực thi truyền thống của hệ điều hành
    • File thực thi có các section được đánh dấu sẵn mà hệ điều hành biết là code, và khi chương trình bắt đầu, hệ điều hành xử lý việc mapping
  • Bước elfination chuyển core thành các section code và data riêng cần cho file thực thi thông thường
    • elfinator phân tích core
    • Viết lại assembly để trở thành position-independent code cần cho address space layout randomization
    • Tách thành file assembly thuần code và file payload thuần data
  • Bước cuối:
    • Tạo file assembly bằng elfinator
    • Link binary cuối cùng
    • Dùng công cụ authoring của Nintendo SDK để đóng gói metadata, thư viện dùng chung, asset và binary ứng dụng thành một package cuối cùng

Hạ tầng build và phạm vi có thể công khai

  • Build tối thiểu cần một máy ARM64 Linux để thực hiện phần lớn quá trình build
  • Để chạy compiler và công cụ authoring của Nintendo SDK, cần máy AMD64 Windows hoặc máy AMD64 Linux dùng Wine
  • Cấu hình thực tế gần như gồm tổng cộng ba máy
    • AMD64 “driver”
    • Host build ARM64
    • Windows VM giao tiếp với devkit
  • Tác giả đã viết một hệ thống build đặc biệt có logic caching và đồng bộ giữa các máy để tự động hóa
    • Vì cũng phải chạy được trong môi trường MSYS2/Windows nên có vấn đề chuyển đổi path
  • Đã làm cho elfinator và immobile-code hoạt động trên ARM64, đồng thời port cả các thư viện hỗ trợ như pathname-utils, libmixed, cl-gamepad
  • Do NDA của Nintendo, nhiều chi tiết không thể công khai
  • Những phần có thể công khai đã được upstream, và các thư viện Lisp không dùng private fork
  • Để tránh liên kết trực tiếp với Nintendo SDK, tác giả tạo một thư viện C riêng, còn các thư viện Lisp truy cập chức năng hệ điều hành qua interface tùy chỉnh này
    • Đây là cấu trúc để phần Lisp được công khai và chỉ giữ riêng thư viện C nhỏ

Con trỏ tuyệt đối và relocation khi load

  • Elfination vốn không phải tính năng được thiết kế ngay từ đầu để tạo mã Lisp thực thi độc lập vị trí
  • Mã Lisp thường chứa nhiều con trỏ tuyệt đối
  • Cần làm việc trên SBCL compiler và runtime để hỗ trợ relocation con trỏ tuyệt đối khi load
  • Object code thường chứa các hằng số code, và cần đảm bảo chúng không còn có con trỏ tuyệt đối
    • GC không thể sửa section thực thi
    • OS loader cũng không thể sửa section thực thi để relocation con trỏ tuyệt đối
  • Cách giải quyết:
    • Chuyển các con trỏ tuyệt đối như hằng số code sang vùng có thể đọc/ghi bên ngoài vùng text
    • Viết lại tham chiếu hằng số trong code để load từ vùng đọc/ghi này
    • Loader và moving GC có thể sửa con trỏ trong vùng đó

Vấn đề garbage collector và safepoint

  • GC tiêu chuẩn của SBCL là gencgc, tức garbage collector theo thế hệ
  • gencgc tách các thế hệ object để quét với tần suất khác nhau, đồng thời nén không gian bằng cách copy object sang vị trí thế hệ khác
  • Bản thân cấu trúc này không phải vấn đề cốt lõi trên Switch, nhưng multithreading tạo ra vấn đề
  • Khi có nhiều thread, một thread có thể đang truy cập object nên không thể tùy ý di chuyển object
  • Cách giải quyết dễ nhất là dừng tất cả thread trước khi bắt đầu GC
  • Khác biệt giữa Unix và Switch

    • Trên hệ thống Unix, có thể dùng cơ chế signal để gửi tín hiệu cho thread khác và buộc chúng park
    • Switch không có cơ chế signal và cũng không thể interrupt thread
    • Cần khiến từng thread tự nhận ra rằng nó phải park; chiến lược thông thường là safepoint
    • Safepoint khiến compiler chèn thêm mã để thread kiểm tra xem có cần park không
    • Vì việc thêm check có chi phí, cần thực hiện càng ít càng tốt
    • Nếu check quá thưa, GC không thể bắt đầu cho đến khi tất cả thread park, khiến các thread khác bị dừng
    • Nếu check cần nhiều lệnh, nó cản trở cache line CPU và tối ưu pipeline
  • Giới hạn của safepoint SBCL hiện có

    • Hệ thống safepoint hiện tại của SBCL được viết cho Windows
    • Windows cũng không có signal handler giữa các process, nhưng khác Switch ở chỗ có signal handling cho thread hiện tại
    • Cách hiện có:
      • Mỗi thread giữ một trang nơi safepoint ghi một word
      • Khi GC bắt đầu, trang đó được đánh dấu read-only
      • Khi thread khác đến safepoint và cố ghi vào trang, segmentation fault xảy ra và thread đó park
      • Cách này hiệu quả vì chỉ cần một lệnh ghi đơn
    • Trên Switch, kỹ thuật này cũng không thể dùng, nên phải chèn check phức tạp hơn
    • Vì safepoint không cần thiết trên các nền tảng ngoài Windows, nó chưa được kiểm thử ở nơi khác và không ổn định, độc lập với các sửa đổi cho Switch
    • Phần này trong codebase có thể cần được dọn dẹp đáng kể, nhưng hiện vẫn hy vọng không phải viết lại hoàn toàn

Tránh biên dịch runtime trong CLOS

  • CLOS thường trì hoãn việc biên dịch discriminating function cần cho method dispatch cho đến lần gọi đầu tiên của generic function
  • Vì CLOS rất động và method có thể được thêm/xóa gần như bất kỳ lúc nào, rất khó biết thời điểm hệ thống đã hoàn chỉnh
  • Trên Switch không thể gọi compiler, nên không thể giữ nguyên cách biên dịch trì hoãn này
  • Chiến lược hiện tại là dựa vào fast evaluator
    • Stub hàm compile
    • Thay vì biên dịch, tạo lambda chạy mã bằng evaluator
    • Cách này cũng hoạt động với mã người dùng phụ thuộc vào compile
    • Tốc độ thực thi chậm hơn nhiều so với biên dịch thật

Công việc còn lại và rủi ro hiệu năng

  • Cách dùng fasteval chủ yếu là fallback
  • Tác giả muốn khám phá cách cố định nhiều trạng thái CLOS nhất có thể và tiền biên dịch nhiều mã nhất có thể ngay trước khi dump image cuối cùng
  • Cũng sẽ tiếp tục điều tra block compilation mode mà Charles đã khôi phục vài năm trước
  • Do bộ xử lý của Switch tương đối yếu, có thể cần tối ưu thêm
    • Phía Trial engine
    • Phía mã Kandria
  • Cho đến nay, máy tính 10 năm tuổi cũng xử lý đủ yêu cầu của game, nên trước đây không cần tối ưu quá nhiều
  • Các việc có ưu tiên cao:
    • Làm cho garbage collector hoạt động hoàn toàn
      • Hiện có thể boot và đi vào main loop của Trial
      • Thất bại khi đến multi-generation compaction
    • Làm cho callback từ C hoạt động trở lại
      • Phần tương ứng trong codebase SBCL có nhiều routine assembly viết tay
      • Có thể cần điều chỉnh cho phù hợp với immobile-code và elfination
      • Trial chỉ cần callback để phát âm thanh thông qua libmixed
    • Làm thêm các chức năng portability cần thiết trước khi selftest suite của Trial chạy qua hoàn toàn trên Switch

Phạm vi công khai bị giới hạn bởi NDA

  • Tác giả muốn công khai toàn bộ công việc port, nhưng không thể vì NDA của Nintendo
  • Những phần có thể công khai đã được upstream hoặc công bố
  • Một số mã liên kết trực tiếp với Nintendo SDK không thể chia sẻ với người chưa ký NDA
  • Nếu có người muốn phát hành game Common Lisp trên Nintendo Switch, họ có thể nhận quyền truy cập vào công việc port sau khi ký NDA
  • Trong các bản cập nhật hằng tháng dành cho Patron, tác giả dự định chia sẻ tiến độ chi tiết hơn

1 bình luận

 
GN⁺ 2024-09-14
Ý kiến trên Hacker News
  • Trong vài tuần qua tôi đã dùng thử Trial(https://github.com/Shirakumo/trial) để thử phát triển game bằng Common Lisp, và thấy rất thú vị
    Việc có thể thay đổi gần như mọi thứ khi game đang chạy là một ưu điểm cực lớn, nên tôi hy vọng bản port này thành công

    • Các ngôn ngữ họ Lisp có vẻ rất phù hợp để làm game. Khả năng đánh giá mã theo kiểu tương tác mà không cần biên dịch lại tạo ra khác biệt lớn trong phát triển tính năng, phát triển tăng dần và sửa lỗi
      Việc có thể giữ trạng thái ứng dụng giữa các lần thay đổi mã cũng có vẻ rất hữu ích, và Common Lisp cũng có vẻ nhanh hơn nhiều so với hình dung mơ hồ trước đây của tôi
      Cá nhân tôi thấy nhược điểm lớn nhất không chỉ trong phát triển game mà nói chung là cách sử dụng cấu trúc dữ liệu, đặc biệt là cách xử lý map, khá thô vụng; nhưng sự đánh đổi đó có vẻ chấp nhận được
  • Thứ này thật sự rất hay. SBCL là một bản triển khai ngôn ngữ tuyệt vời, và tôi đã muốn thử phát triển Common Lisp trên một máy console game “thật”
    Việc Shinmera làm việc này cũng là một bất ngờ theo nghĩa tốt. Trước đây tôi từng thấy cô ấy vài lần trên #lispgames và Lisp Discord, nhưng không biết cô ấy cũng quan tâm đến kiểu phát triển cấp thấp như thế này
    Tôi từng nhìn thoáng vào bên trong SBCL rồi sợ quá rút lui, nên chuyện này càng có vẻ ấn tượng hơn. Tôi cũng tò mò liệu tổ hợp SBCL với threading và SDL2 hiện có chạy được trên Raspberry Pi không

    • Phần việc phía SBCL không phải do tôi làm; toàn bộ là công việc của Charles, người tôi thuê. Phần tôi phụ trách là tính di động để Trial chạy được trên nhiều môi trường, cấu trúc build tổng thể và các stub runtime ban đầu
      Và như đã nói ở trên, là *her :)
    • Không phải “her” mới đúng sao?
  • Cảm ơn tác giả vì đã viết một bài thú vị và chi tiết. Những chi tiết ở mức này về port chính thức lên console thường chỉ lộ ra sau khi vòng đời console kết thúc vài năm
    Đọc những công việc chuyên sâu như thế này đôi khi khiến tôi nhớ đến công việc của mình, nơi cả ngày dùng phần mềm lặp đi lặp lại, và thấy hơi ghen tị

    • Theo thời tôi từng làm, ngay cả các công cụ chính thức như vậy thường cũng là những thứ được chắp vá khá tạm bợ; nếu bỏ qua hỗ trợ debug thì toolchain homebrew hiện đại cho các nền tảng cũ nhiều khi còn tốt hơn
      Devkit thường có các hook tốt hơn, nhưng đồng thời cũng có lợi thế là tránh được luồng làm việc xoay quanh GDB. Đã trải qua cả hai phía, tôi không chắc đó là một thế giới đáng ghen tị đến vậy
    • Tôi vừa chuẩn bị bắt đầu thêm một ngày với Ruby on Rails, nên đang nghĩ xem sau này có sở thích hay dự án mã nguồn mở nào đáng để mình dồn sự quan tâm vào không
      Có lẽ không phải là dự án đang cần sự quan tâm của tôi, mà là dự án mà sau này sự quan tâm của tôi sẽ cần đến :D
  • Thấy đoạn “muốn công khai hết nhưng không thể vì NDA”, tôi tò mò vì sao họ dùng SDK chính thức thay vì homebrew SDK không bị ràng buộc(https://switchbrew.org/wiki/Setting_up_Development_Environment)
    Hoàn toàn suy đoán thôi, có thể là vì Nintendo không cho phép phát hành chính thức game được build bằng SDK bên thứ ba

    • Game làm bằng homebrew không thể phát hành; bắt buộc phải dùng SDK chính thức. Hơn nữa rất ít người có Switch đã jailbreak, nên nếu không dùng giả lập thì việc chạy game cũng trở nên rất khó
  • Liên quan có https://opengoal.dev
    Nhìn vào bối cảnh, khi làm series Jak & Daxter trên PS2, Naughty Dog đã dùng GOAL, một ngôn ngữ tự tạo khá giống Lisp. Nhờ họ để lại đủ thông tin debug nên có thể dịch ngược, và dự án OpenGOAL đã làm được việc đó
    Hiện giờ có thể chạy các game này trên mọi nền tảng mà trình biên dịch GOAL đã được port sang. Theo tôi biết thì hiện tại khoảng x86, và nếu được port lên Switch thì sẽ rất tuyệt

  • Tôi vừa mua Kandria. Tôi không phải người chơi game nhiều nên chắc sẽ không chơi nhiều, nhưng Shinmera rõ ràng đang mở rộng ranh giới của thế giới Lisp, nên đáng để ủng hộ

  • Công việc của cô ấy thật sự tuyệt vời. Thỉnh thoảng tôi dùng Common Lisp, nên rất vui khi thấy những việc như thế này

  • Ước gì các công ty như Nintendo hay Sony trực tiếp hỗ trợ những nỗ lực như vậy. Rốt cuộc đây là một cách khác để tạo game cho console, tức là tạo IP; tôi không rõ ở cấp nền tảng thì có nhược điểm gì nếu họ khởi động thứ gì đó như Github Accelerator

    • Ai cũng biết rằng nhà phát triển game sẽ tự bỏ chi phí để vượt qua bất kỳ quy trình nào mà chủ nền tảng yêu cầu. Khi quyết định phát hành trên nền tảng nào, nhà phát triển không có nhiều sức mặc cả để khó tính về các chi tiết kỹ thuật
      Nintendo không cần tạo thêm động lực mới để khuyến khích phát hành trên Switch. Họ đã có động lực lớn nhất rồi: hơn 140 triệu máy đã bán ra và tỷ lệ mua game cao
      Dù vậy, quy trình cũng không còn nhiều như trước. Các hệ thống ngày nay phần lớn đã hội tụ về kiến trúc CPU và GPU phổ dụng, và nếu có khác biệt thì nhiều khi chỉ là những chi tiết trang trí nhỏ
    • Trước đây từng có sự hỗ trợ như vậy, nhưng phần lớn những gì mọi người làm là port MAME và các trình giả lập khác, hoặc sao chép game thời 8-bit・16-bit
      Vì thế mà những điều tốt đẹp trở nên khó được tận hưởng hơn
  • Tôi lên HN để đọc những bài như thế này. Xin vỗ tay cho tác giả bài gốc và đồng nghiệp của cô ấy. Biết là không thể, nhưng nếu Nintendo đối xử với hệ thống của mình cởi mở hơn một chút thì đó thật sự sẽ là một phước lành lớn

  • Vì không thấy chỗ nào giải thích nên bổ sung: SBCL là “Steel Bank Common Lisp”
    “Steel Bank Common Lisp(SBCL) là một trình biên dịch Common Lisp hiệu năng cao. Đây là phần mềm mã nguồn mở/tự do với giấy phép rộng rãi; ngoài trình biên dịch và hệ thống runtime cho ANSI Common Lisp, nó còn cung cấp một môi trường tương tác gồm debugger, trình profiler thống kê, công cụ code coverage và nhiều phần mở rộng”
    https://www.sbcl.org/