- 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ết và cá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 và độ 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() và JS_Run()
- Tích hợp thư viện toán học (libm.c) và 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
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 Bellard và Charlie Gordon
2 bình luận
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
Ý 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ế
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
Ở 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
Người ta hầu như không thử tách parser và lexer ra rồi thay các token như
{}bằngthen/endThảo luận liên quan: chuỗi HN, thảo luận trên Reddit
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
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ó lẽ thêm tùy chọn
emcc -O3hoặc--closure 1sẽ giảm thêm đượcQuickJS 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
Liên kết JSLinux
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
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
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
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
MicroQuickJS chỉ triển khai một phần ES5 và cũng không cung cấp environment binding
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
malloc(). Điều đó mở ra khả năng mớiThờ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ả
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
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
Nếu là người thật thì hẳn cũng phải ngủ chứ
ts_server, TextSynth
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ố
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