7 điểm bởi GN⁺ 2025-12-24 | 2 bình luận | Chia sẻ qua WhatsApp
  • MicroQuickJS(MQuickJS) là một JavaScript engine siêu nhẹ được thiết kế cho hệ thống nhúng, có thể chạy chỉ với khoảng 10kB RAM và 100kB ROM
  • Vẫn duy trì tốc độ tương tự QuickJS nhưng giảm mức dùng bộ nhớ bằng cách áp dụng trình gom rác truy vếtcách lưu chuỗi UTF-8
  • Ngôn ngữ được hỗ trợ là một tập con JavaScript giới hạn gần với ES5, và chỉ cho phép strict mode nhằm cấm các cú pháp dễ gây lỗi
  • Thông qua công cụ REPL mqjs, có thể chạy script, lưu bytecode và thiết lập giới hạn bộ nhớ; bytecode được tạo ra có thể chạy trực tiếp từ ROM
  • Toàn bộ engine và thư viện chuẩn nằm thường trú trong ROM để đạt được khởi tạo nhanh và mức tiêu thụ bộ nhớ thấp, qua đó nâng cao hiệu quả thực thi JavaScript trong môi trường nhúng

Giới thiệu

  • MicroQuickJS(MQuickJS) là một JavaScript engine nhắm tới các hệ thống nhúng, hoạt động với 10kB RAM và 100kB ROM (bao gồm mã ARM Thumb-2)
    • Tốc độ tương đương QuickJS
  • Chỉ hỗ trợ một tập con gần với ES5 và chỉ chạy ở strict mode, vốn cấm các cú pháp kém hiệu quả hoặc dễ gây lỗi
  • Có chia sẻ một phần mã với QuickJS, nhưng cấu trúc bên trong được thiết kế hoàn toàn khác để tiết kiệm bộ nhớ
    • Sử dụng trình gom rác truy vết, không dùng CPU stack, và lưu chuỗi theo UTF-8

REPL

  • Lệnh REPL là mqjs, hỗ trợ chạy script, đánh giá biểu thức, chế độ tương tác, thiết lập giới hạn bộ nhớ và lưu bytecode
    • Ví dụ: ./mqjs --memory-limit 10k tests/mandelbrot.js
  • Có thể dùng tùy chọn -o để lưu bytecode đã biên dịch ra tệp
    • Bytecode đã lưu có thể chạy bằng ./mqjs mandelbrot.bin
  • Bytecode khác nhau tùy theo endiannessđộ dài từ CPU (32/64-bit); có thể dùng tùy chọn -m32 để tạo bytecode cho 32-bit
  • Có thể dùng tùy chọn --no-column để loại bỏ số cột trong thông tin debug

Strict mode

  • Chỉ cho phép strict mode; không thể dùng từ khóa with, và biến toàn cục bắt buộc phải được khai báo bằng var
  • Không cho phép lỗ trống (hole) trong mảng
    • Ví dụ: a[10] = 2 sẽ phát sinh TypeError
    • Nếu cần mảng có lỗ trống thì hãy dùng object thông thường
  • Chỉ hỗ trợ eval toàn cục, không thể truy cập biến cục bộ
  • Không hỗ trợ value boxing (new Number(1) v.v.)

Tập con JavaScript

  • Dựa trên strict mode, tập trung vào tương thích ES5
  • Đối tượng Array không có lỗ trống, và truy cập chỉ số ngoài phạm vi sẽ gây lỗi
  • for in chỉ duyệt qua thuộc tính riêng của object, còn for of chỉ hỗ trợ mảng
  • Global object có tồn tại nhưng không hỗ trợ getter/setter, và các thuộc tính được tạo trực tiếp sẽ không được lộ ra thành biến toàn cục
  • Biểu thức chính quy (Regexp) chỉ phân biệt hoa thường với ASCII, và /./ sẽ khớp theo đơn vị mã Unicode thay vì UTF-16
  • Các hàm xử lý chuỗi chỉ xử lý ASCII (toLowerCase, toUpperCase)
  • Date chỉ hỗ trợ Date.now()
  • Các tính năng bổ sung được hỗ trợ:
    • for of, Typed arrays, literal chuỗi \u{hex}
    • Các hàm Math: imul, clz32, fround, trunc, log2, log10
    • toán tử lũy thừa, cờ biểu thức chính quy (s, y, u), hàm chuỗi (replaceAll, trimStart, trimEnd), globalThis

C API

  • Giảm tối đa phụ thuộc vào thư viện C, không dùng malloc, free, printf
  • Phải tự cung cấp trực tiếp bộ đệm bộ nhớ, và engine chỉ cấp phát bộ nhớ trong bộ đệm đó
    • Ví dụ: ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • Do cơ chế gom rác, không cần gọi JS_FreeValue()
  • Địa chỉ object có thể thay đổi mỗi lần cấp phát, vì vậy nên dùng con trỏ JSValue
    • Quản lý tham chiếu an toàn bằng JS_PushGCRef() / JS_PopGCRef()
  • Thư viện chuẩn được biên dịch thành cấu trúc C có thể lưu trong ROM, giúp đạt được khởi tạo nhanh và dùng ít RAM
  • Có thể thực thi bytecode từ ROM; sau khi tái định vị bằng JS_RelocateBytecode(), chạy bằng JS_LoadBytecode()JS_Run()
  • Tích hợp thư viện toán học (libm.c)trình giả lập số thực dấu phẩy động

Cấu trúc bên trong và so sánh với QuickJS

  • Gom rác: dùng GC truy vết và nén thay cho đếm tham chiếu
    • Ngăn phân mảnh bộ nhớ, giảm kích thước object
  • Biểu diễn giá trị: được thiết kế theo kích thước từ của CPU (32/64-bit)
    • Có thể lưu số nguyên 31-bit, điểm mã Unicode, số thực dấu phẩy động và con trỏ tới khối bộ nhớ
  • Chuỗi được lưu dưới dạng UTF-8, hiệu quả hơn kiểu mảng 8/16-bit của QuickJS
  • Hàm C có thể được lưu như một giá trị đơn, không thể thêm thuộc tính
  • Thư viện chuẩn thường trú trong ROM, giảm thiểu object trong RAM nên có thể khởi tạo engine nhanh
  • Bytecode dựa trên stack và được xử lý chỉ đọc thông qua bảng tham chiếu gián tiếp
    • Dùng mã Golomb để nén số dòng và cột
  • Trình biên dịch tương tự QuickJS nhưng dùng parser không đệ quy để giới hạn mức dùng C stack
    • Tạo bytecode theo một lượt duy nhất mà không cần cây phân tích cú pháp

Kiểm thử và benchmark

  • Kiểm thử cơ bản: make test
  • Micro benchmark của QuickJS: make microbench
  • Benchmark Octane (phiên bản đã chỉnh sửa cho strict mode) có thể tải riêng
    • Chạy: make octane

Giấy phép

  • Phát hành theo giấy phép MIT
  • Bản quyền mã nguồn thuộc về Fabrice BellardCharlie Gordon

2 bình luận

 
xguru 2025-12-24

Phần giới thiệu về Fabrice Bellard thì trước đây tôi đã từng viết trong một bình luận, hãy tham khảo ở đó. Đúng là một con quái vật đáng kinh ngạc và bền bỉ..
https://news.hada.io/comment?id=51

 
GN⁺ 2025-12-24
Ý kiến trên Hacker News
  • Nếu thứ này đã tồn tại vào năm 2010 thì có lẽ ngôn ngữ scripting của Redis đã là JavaScript chứ không phải Lua
    Lua được chọn không phải vì lý do ngôn ngữ mà vì các ràng buộc khi triển khai (nhỏ, nhanh, dựa trên ANSI-C)
    Một vài ý tưởng của Lua khá hay, nhưng cá nhân tôi thấy việc rời xa cú pháp họ Algol là không cần thiết
    Sự bối rối phát sinh khi phải học các khái niệm trừu tượng mới như SmallTalk hay FORTH thì còn đáng giá, nhưng tôi không nghĩ những thay đổi của Lua có lý do mạnh đến thế

    • Tôi không hẳn thích cú pháp của Lua, nhưng tôi nghĩ lý do các nhà phát triển chọn nó là hoàn toàn dễ hiểu
      Lua là ngôn ngữ hạng nhẹ duy nhất hỗ trợ tail call optimization (TCO), nhờ đó có thể viết chương trình chỉ bằng đệ quy mà không cần vòng lặp
      JavaScript không có tối ưu hóa này nên không thể làm tương tự
      Lua cũng đặc biệt phù hợp để viết compiler vì có rất nhiều cấu trúc đệ quy
      Redis scripting có thể hợp với JS hơn, nhưng hạ thấp Lua thì hơi đáng tiếc
    • Nếu xét việc Lua ra đời lần đầu vào năm 1993 thì vào thời điểm đó nó có cú pháp khá truyền thống
      Ở Brazil, Pascal và Ada phổ biến hơn C nên nó chịu ảnh hưởng từ đó
      Ruby và Perl cũng ra đời vào thời kỳ tương tự nhưng thử những thay đổi cú pháp cấp tiến hơn nhiều
    • Ban đầu tôi định nói là mình đã học Lua rất dễ khi mới 13 tuổi, rồi chợt dừng lại khi nhận ra người viết bình luận là chính antirez
    • Có lẽ chuyện này không giải quyết được vấn đề cú pháp, nhưng khái niệm “language skins” khá thú vị
      Người ta hầu như không thử tách parser và lexer ra rồi thay các token như {} bằng then/end
      Thảo luận liên quan: chuỗi HN, thảo luận trên Reddit
    • Tôi cũng tò mò không biết Tcl đã từng được cân nhắc làm ngôn ngữ scripting cho Redis chưa, vì đó là ngôn ngữ nhúng nguyên bản
  • Engine này giới hạn JS đúng theo kiểu tôi từng muốn khi làm việc với JSC
    Trên web thì vì tương thích nên không thể có các ràng buộc như vậy, nhưng trong môi trường nhúng thì ngược lại, các ràng buộc này có thể là một thiết kế đem lại niềm vui

    • Chúng ta đã có các engine JS không có những ràng buộc đó rồi
    • Tôi tò mò không biết công việc đa luồng trước đây ở JSC giờ ra sao. Sau khi rời Apple thì nó bị dừng lại, hay mã nguồn vẫn còn đó?
  • Tôi đã làm một playground để chạy MicroQuickJS trực tiếp trong trình duyệt
    Phiên bản WebAssembly của MicroQuickJS
    Nhân tiện cũng có phiên bản QuickJS gốc
    QuickJS là 2.28MB còn MicroQuickJS chỉ 303KB nên nhẹ hơn nhiều

    • Có vẻ bản build bị đội kích thước do kèm theo thông tin tên gọi v.v.
      Có lẽ thêm tùy chọn emcc -O3 hoặc --closure 1 sẽ giảm thêm được
      QuickJS thì đã được tối ưu sẵn rồi, còn MicroQuickJS vẫn còn chỗ để cải thiện
  • Như câu nói nổi tiếng của Jeff Atwood, “Mọi ứng dụng có thể được viết bằng JavaScript rồi cuối cùng sẽ được viết bằng JavaScript
    Giờ có vẻ câu đó cũng đang đúng với cả hệ thống nhúng
    Wiki của Jeff Atwood

    • Đồng ý. Bài nói chuyện liên quan: The Birth and Death of JavaScript
    • Fabrice Bellard cũng từng tạo một VM dựa trên JS có thể chạy Linux ngay trong trình duyệt
      Liên kết JSLinux
    • Cái này gần như kiểu Rule 35 của Internet vậy
  • Hơi tiếc vì nó được đưa lên mà không có lịch sử commit
    Tôi muốn xem một người ở trình độ này hoàn thành dự án nhanh đến mức nào
    Dù sao nó cũng dựa trên QuickJS nên việc so sánh có lẽ không mang nhiều ý nghĩa

    • Cụm từ “public repository of…” khiến tôi nghĩ lịch sử làm việc thật có thể nằm trong một repo riêng tư
    • Cũng có thể ông ấy просто làm xong trong một phát
  • Tôi tự hỏi liệu đây có thể là cách nhẹ nhất để giải được thử thách YouTube JS của yt-dlp không
    Xem tài liệu yt-dlp EJS
    QuickJS đã được hỗ trợ rồi

    • Khả năng thấp. Nó chỉ hỗ trợ triển khai một phần ở mức ES5
      Các câu đố JS của YouTube quá phức tạp, đến mức cả trình giả lập JS viết bằng Python cũng đã bỏ cuộc
    • Mới chỉ triển khai ES5 nên thực tế là khá khó
  • Tôi không rành hệ thống nhúng, nhưng tự hỏi liệu engine kiểu này có thể cho phép lập trình ESP32 hay Arduino bằng JavaScript không
    Giống như MicroPython ấy

    • Đã có vài dự án tương tự rồi
    • Engine XS của Moddable/Kinoma hỗ trợ từ ES6 trở lên
      MicroQuickJS chỉ triển khai một phần ES5 và cũng không cung cấp environment binding
    • Trước đây từng có một bo mạch lập trình JS tên là Tessel
      Nó chuyển mã JS thành bytecode cho Lua VM để chạy, một cách tiếp cận khá thông minh
      Gần đây tôi còn thử viết lại CLI Node 0.8 ngày xưa đó bằng Rust, nhưng cuối cùng thiết bị vẫn quay lại nằm trong ngăn kéo
    • Điểm mấu chốt là nó có cấu trúc không dùng malloc(). Điều đó mở ra khả năng mới
  • Thời điểm thực sự rất quan trọng. Khi đăng tối qua thì chẳng có phản hồi nào cả

    • Chắc chỉ là vấn đề may mắn thôi
    • Người khác cũng đã thử mà không có phản hồi
      Có chiến lược là đăng lại vào buổi sáng ở Mỹ hoặc định kỳ đăng lại
  • Fabrice Bellard là một trong những lập trình viên năng suất và đa năng nhất còn đang hoạt động
    Tác phẩm tiêu biểu: FFmpeg, QEMU, JSLinux, TCC, QuickJS
    Một nhân vật huyền thoại

    • Ông ấy được đánh giá rất cao, nhưng lại có ít người quan tâm đến cách ông phát triển phần mềm
      Cách tiếp cận tạo ra chương trình hoàn chỉnh với tối thiểu phụ thuộc và công cụ thật sự rất ấn tượng
    • Giờ tôi bắt đầu nghĩ có khi đó không phải một người mà là mật danh của cả một nhóm hacker lão luyện
      Nếu là người thật thì hẳn cũng phải ngủ chứ
    • Ông ấy còn tự phát triển cả engine suy luận LLM và duy trì nó từ thời GPT-2 đến nay
      ts_server, TextSynth
    • Điều thú vị là phần lớn chương trình ông ấy làm ra dường như không xoay quanh giao diện người dùng kiểu GUI
      Có vẻ ông thích kiểu chương trình chạy trọn vẹn sau khi người dùng đặt tham số
    • Ông ấy cũng từng vô địch Cuộc thi mã C làm rối quốc tế (IOCCC) tới 3 lần
      Danh sách tác giả đoạt giải IOCCC
  • Việc “có thể biên dịch và chạy JS chỉ với 10kB RAM” thật ấn tượng
    Rất đúng thời điểm trong lúc RAM ngày càng đắt đỏ như hiện nay
    Tôi tự hỏi liệu có thể nhét thứ này vào Chromium hay Electron không

    • Chắc khó vì yêu cầu tương thích web, nhưng dù sao hiệu quả tiết kiệm bộ nhớ trong Chromium có lẽ cũng không lớn lắm