7 คะแนน โดย GN⁺ 2025-12-24 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • MicroQuickJS(MQuickJS) คือ JavaScript engine ขนาดเล็กมากที่ออกแบบมาสำหรับระบบฝังตัว โดยสามารถทำงานได้ด้วย RAM ราว 10kB และ ROM เพียง 100kB
  • ใช้ tracing garbage collector และ การจัดเก็บสตริงแบบ UTF-8 เพื่อลดการใช้หน่วยความจำ ขณะยังคง ความเร็วใกล้เคียง QuickJS
  • ภาษาที่รองรับคือ ชุดย่อยที่จำกัดของ JavaScript ใกล้เคียง ES5 และอนุญาตเฉพาะ strict mode ที่ห้ามไวยากรณ์ซึ่งมีโอกาสผิดพลาดสูง
  • ผ่าน REPL tool mqjs สามารถรันสคริปต์ บันทึก bytecode และตั้งค่าขีดจำกัดหน่วยความจำได้ และ bytecode ที่สร้างขึ้นสามารถรันจาก ROM ได้โดยตรง
  • เอนจินทั้งหมดและ standard library อยู่ใน ROM ทำให้ได้ การเริ่มต้นทำงานที่รวดเร็วและการใช้หน่วยความจำต่ำ ช่วยเพิ่ม ประสิทธิภาพการรัน JavaScript ในสภาพแวดล้อมแบบฝังตัว

แนะนำ

  • MicroQuickJS(MQuickJS) คือ JavaScript engine สำหรับระบบฝังตัว ทำงานได้บน RAM 10kB และ ROM 100kB (รวมโค้ด ARM Thumb-2)
    • ความเร็วใกล้เคียงกับ QuickJS
  • รองรับเพียง ชุดย่อยที่ใกล้เคียง ES5 และทำงานได้เฉพาะใน strict mode ที่ ห้ามไวยากรณ์ที่ไม่มีประสิทธิภาพหรือมีความเสี่ยงต่อข้อผิดพลาดสูง
  • แม้จะแชร์โค้ดบางส่วนกับ QuickJS แต่โครงสร้างภายในถูก ออกแบบใหม่ทั้งหมดเพื่อประหยัดหน่วยความจำ
    • ใช้ tracing garbage collector, ไม่ใช้ CPU stack, และ จัดเก็บสตริงแบบ UTF-8

REPL

  • คำสั่ง REPL คือ mqjs รองรับการรันสคริปต์ การประเมินผล โหมดโต้ตอบ การตั้งค่าขีดจำกัดหน่วยความจำ และการบันทึก bytecode
    • ตัวอย่าง: ./mqjs --memory-limit 10k tests/mandelbrot.js
  • ใช้ตัวเลือก -o เพื่อบันทึก bytecode ที่คอมไพล์แล้ว ลงไฟล์ได้
    • bytecode ที่บันทึกไว้สามารถรันได้ด้วย ./mqjs mandelbrot.bin
  • bytecode จะแตกต่างกันตาม endianness และขนาด word ของ CPU (32/64 บิต) และสามารถใช้ตัวเลือก -m32 เพื่อสร้าง bytecode สำหรับ 32 บิตได้
  • ใช้ตัวเลือก --no-column เพื่อลบ หมายเลขคอลัมน์ในข้อมูลดีบัก ได้

strict mode

  • อนุญาตเฉพาะ strict mode เท่านั้น, ไม่สามารถใช้คีย์เวิร์ด with ได้ และตัวแปร global ต้องประกาศด้วย var เสมอ
  • ไม่อนุญาต array hole
    • ตัวอย่าง: a[10] = 2 จะทำให้เกิด TypeError
    • หากต้องการอาร์เรย์ที่มีช่องว่าง ให้ใช้ object ทั่วไปแทน
  • รองรับเฉพาะ global eval และไม่สามารถเข้าถึงตัวแปรภายในได้
  • ไม่รองรับ value boxing (new Number(1) เป็นต้น)

ชุดย่อยของ JavaScript

  • อิงตาม strict mode และเน้น ความเข้ากันได้กับ ES5
  • Array object ไม่มี hole และการเข้าถึง index ที่อยู่นอกช่วงจะเป็นข้อผิดพลาด
  • for in จะวนเฉพาะ พร็อพเพอร์ตีของ object เอง และ for of รองรับเฉพาะอาร์เรย์
  • มี global object แต่ไม่รองรับ getter/setter และพร็อพเพอร์ตีที่สร้างขึ้นโดยตรงจะไม่ถูกเปิดเผยเป็นตัวแปร global
  • Regexp รองรับการแยกตัวพิมพ์เล็ก-ใหญ่เฉพาะ ASCII และ /./ จะจับคู่ในระดับ Unicode code point แทน UTF-16
  • ฟังก์ชันสตริง จัดการเฉพาะ ASCII (toLowerCase, toUpperCase)
  • Date รองรับเพียง Date.now()
  • ความสามารถเพิ่มเติมที่รองรับ:
    • for of, Typed arrays, string literal แบบ \u{hex}
    • ฟังก์ชัน Math: imul, clz32, fround, trunc, log2, log10
    • operator ยกกำลัง, Regexp flags (s, y, u), ฟังก์ชันสตริง (replaceAll, trimStart, trimEnd), globalThis

C API

  • ลดการพึ่งพา C library ให้เหลือน้อยที่สุด, ไม่ใช้ malloc, free, printf
  • ต้อง จัดเตรียม memory buffer เองโดยตรง และเอนจินจะจัดสรรหน่วยความจำภายในบัฟเฟอร์นั้นเท่านั้น
    • ตัวอย่าง: ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • ด้วย วิธีการทำ garbage collection จึงไม่จำเป็นต้องเรียก JS_FreeValue()
  • ที่อยู่ของ object สามารถเปลี่ยนได้ทุกครั้งที่มีการจัดสรร จึง แนะนำให้ใช้ pointer ไปยัง JSValue
    • จัดการการอ้างอิงอย่างปลอดภัยด้วย JS_PushGCRef() / JS_PopGCRef()
  • standard library ถูกคอมไพล์เป็นโครงสร้าง C ที่สามารถเก็บไว้ใน ROM ได้ ทำให้ได้ การเริ่มต้นที่รวดเร็วและใช้ RAM ต่ำ
  • การรัน bytecode ทำได้จาก ROM โดยตรง หลังจากย้ายตำแหน่งด้วย JS_RelocateBytecode() แล้วจึงใช้ JS_LoadBytecode() และ JS_Run() เพื่อรัน
  • มี math library (libm.c) และ floating-point emulator ในตัว

โครงสร้างภายในและการเปรียบเทียบกับ QuickJS

  • garbage collection: ใช้ GC แบบ tracing และ compacting แทน reference counting
    • ป้องกัน memory fragmentation และลดขนาด object
  • การแทนค่า: ออกแบบให้สอดคล้องกับขนาด word ของ CPU (32/64 บิต)
    • สามารถเก็บจำนวนเต็ม 31 บิต, Unicode code point, จำนวนทศนิยม และ pointer ไปยัง memory block ได้
  • สตริงถูกเก็บแบบ UTF-8 มีประสิทธิภาพมากกว่าวิธีใช้อาร์เรย์ 8/16 บิตของ QuickJS
  • ฟังก์ชัน C สามารถเก็บเป็นค่าเดี่ยวได้ และไม่สามารถเพิ่มพร็อพเพอร์ตีได้
  • standard library อยู่ใน ROM และลด object ใน RAM ให้เหลือน้อยที่สุด ทำให้ เริ่มต้นเอนจินได้รวดเร็ว
  • bytecode เป็นแบบ stack-based และทำให้เป็นแบบอ่านอย่างเดียวผ่าน indirect reference table
    • บีบอัดหมายเลขบรรทัดและคอลัมน์ด้วย Golomb code
  • คอมไพเลอร์ คล้าย QuickJS แต่ใช้ parser แบบไม่เรียกซ้ำ เพื่อจำกัดการใช้ C stack
    • สร้าง bytecode แบบ single pass โดยไม่มี parse tree

การทดสอบและเบนช์มาร์ก

  • การทดสอบพื้นฐาน: make test
  • QuickJS micro benchmark: make microbench
  • Octane benchmark (เวอร์ชันดัดแปลงสำหรับ strict mode) สามารถดาวน์โหลดแยกได้
    • รันด้วย: make octane

ไลเซนส์

  • เผยแพร่ภายใต้ MIT License
  • ลิขสิทธิ์ซอร์สโค้ดเป็นของ Fabrice Bellard และ Charlie Gordon

2 ความคิดเห็น

 
xguru 2025-12-24

ถ้าอยากรู้จัก Fabrice Bellard ลองดูสิ่งที่ผมเคยเขียนไว้ในคอมเมนต์ก่อนหน้านี้ได้เลยครับ คนนี้ทั้งสม่ำเสมอและน่าทึ่งแบบสัตว์ประหลาดจริงๆ..
https://news.hada.io/comment?id=51

 
GN⁺ 2025-12-24
ความคิดเห็นจาก Hacker News
  • ถ้าสิ่งนี้มีมาตั้งแต่ปี 2010 ภาษาสคริปต์ของ Redis ก็น่าจะเป็น JavaScript ไม่ใช่ Lua
    Lua ถูกเลือกไม่ใช่เพราะเหตุผลด้านตัวภาษา แต่เพราะข้อจำกัดด้านการติดตั้งใช้งาน (เล็ก เร็ว และอิง ANSI-C)
    ไอเดียบางอย่างของ Lua ก็ดี แต่ส่วนตัวรู้สึกว่าการออกห่างจาก ไวยากรณ์สาย Algol นั้นไม่จำเป็น
    ความสับสนที่เกิดจากการต้องเรียนรู้แนวคิดเชิงนามธรรมใหม่แบบ SmallTalk หรือ FORTH นั้นคุ้มค่า แต่การเปลี่ยนแปลงของ Lua ดูเหมือนไม่มีเหตุผลมากพอในระดับนั้น

    • แม้ผมจะไม่ได้ชอบไวยากรณ์ของ Lua แต่ก็คิดว่าเหตุผลที่นักพัฒนาเลือกมันนั้นสมเหตุสมผลดี
      Lua เป็นภาษาขนาดเล็กเพียงภาษาเดียวที่รองรับ tail call optimization(TCO) ทำให้สามารถเขียนโปรแกรมด้วยรีเคอร์ชันล้วน ๆ โดยไม่ต้องมีลูปได้
      JavaScript ไม่มีการเพิ่มประสิทธิภาพแบบนี้ จึงทำแบบเดียวกันไม่ได้
      Lua ยังเหมาะกับการ เขียนคอมไพเลอร์ เป็นพิเศษ เพราะมีโครงสร้างแบบรีเคอร์ซีฟเยอะ
      สำหรับสคริปต์ของ Redis นั้น JS อาจจะเหมาะกว่า แต่ก็น่าเสียดายถ้าจะดูแคลน Lua
    • ถ้าคิดว่า Lua ออกมาครั้งแรกในปี 1993 ไวยากรณ์ของมันก็ถือว่า ดั้งเดิมพอสมควร สำหรับยุคนั้น
      ในบราซิล Pascal กับ Ada ถูกใช้อย่างแพร่หลายกว่า C จึงได้รับอิทธิพลจากสองภาษานั้น
      Ruby กับ Perl ก็ออกมาในช่วงใกล้เคียงกัน แต่ทดลองเปลี่ยนไวยากรณ์แบบสุดโต่งกว่ามาก
    • ตอนแรกผมจะเล่าว่าตัวเองเรียน Lua ได้ง่ายแค่ไหนตอนอายุ 13 แต่ก็หยุดเมื่อรู้ว่าคนเขียนคอมเมนต์คือ antirez ตัวจริง เลยตกใจมาก
    • แม้จะไม่แก้ปัญหาเรื่องไวยากรณ์ แต่แนวคิด “language skins” ก็น่าสนใจ
      ทั้งที่แยก parser กับ lexer ออกจากกันอยู่แล้ว แต่แทบไม่มีใครลองสลับโทเค็นอย่าง then/end แทน {}
      การพูดคุยที่เกี่ยวข้อง: เธรด HN, กระทู้ Reddit
    • สงสัยว่าเคยพิจารณา Tcl เป็นภาษาสคริปต์ของ Redis บ้างไหม เพราะมันคือภาษา embedded ดั้งเดิม
  • เอนจินนี้จำกัด JS ในแบบที่ผมเคยอยากได้ตอนทำงานกับ JSC
    บนเว็บเราทำข้อจำกัดแบบนี้ไม่ได้เพราะเรื่องความเข้ากันได้ แต่ในสภาพแวดล้อมแบบ embedded ข้อจำกัดแบบนี้กลับเป็น การออกแบบที่น่ายินดี ได้

    • เรามี JS engine ที่ไม่มีข้อจำกัดแบบนี้อยู่แล้ว
    • อยากรู้ว่างาน มัลติเธรด ที่ทำใน JSC ไปถึงไหนแล้ว หลังออกจาก Apple มันหยุดไปเลยไหม หรือโค้ดยังอยู่
  • ผมทำ playground สำหรับลองรัน MicroQuickJS ได้ทันทีในเบราว์เซอร์
    MicroQuickJS เวอร์ชัน WebAssembly
    และแน่นอนว่ามี QuickJS เวอร์ชันต้นฉบับ ด้วย
    QuickJS มีขนาด 2.28MB ส่วน MicroQuickJS แค่ 303KB จึงเบากว่ามาก

    • ดูเหมือนว่าข้อมูลชื่อและเมทาดาทาถูกฝังมากับบิลด์เลยทำให้ขนาดใหญ่ขึ้น
      ถ้าใส่ตัวเลือก emcc -O3 หรือเพิ่ม --closure 1 ก็น่าจะลดลงได้อีก
      QuickJS ถูกปรับแต่งไว้แล้ว ส่วน MicroQuickJS ยังมีพื้นที่ให้ปรับปรุง
  • อย่างที่ Jeff Atwood เคยพูดไว้ว่า “แอปทุกตัวที่สามารถเขียนด้วย JavaScript ได้ สุดท้ายก็จะถูกเขียนด้วย JavaScript
    ตอนนี้ดูเหมือนว่าคำนั้นจะใช้ได้กับระบบ embedded ด้วย
    วิกิของ Jeff Atwood

    • เห็นด้วย มีบรรยายที่เกี่ยวข้องคือ The Birth and Death of JavaScript
    • Fabrice Bellard เคยสร้าง VM ที่ใช้ JS เป็นฐานและรัน Linux ในเบราว์เซอร์ได้ ด้วย
      ลิงก์ JSLinux
    • อันนี้แทบจะเหมือน Rule 35 ของอินเทอร์เน็ตเลย
  • เสียดายที่อัปโหลดมาโดยไม่มีประวัติ commit
    ผมอยากเห็นว่านักพัฒนาระดับนี้ทำโปรเจกต์เสร็จเร็วแค่ไหน
    แต่ยังไงมันก็อิงจาก QuickJS อยู่แล้ว การเทียบตรง ๆ คงไม่มีความหมายมากนัก

    • พอเห็นคำว่า “public repository of…” ก็เป็นไปได้ว่าประวัติการทำงานจริงอยู่ในรีโปส่วนตัว
    • หรือไม่ก็อาจจะ ทำเสร็จในช็อตเดียว ไปเลย
  • สงสัยว่านี่อาจเป็นวิธีที่เบาที่สุดในการแก้ YouTube JS challenge ของ yt-dlp หรือเปล่า
    ดู เอกสาร EJS ของ yt-dlp
    ตอนนี้รองรับ QuickJS อยู่แล้ว

    • โอกาสน้อย เพราะรองรับแค่ บางส่วนของ ES5
      ปริศนา JS ของ YouTube ซับซ้อนเกินไป ถึงขั้นที่ JS emulator ที่เขียนด้วย Python ยังยอมแพ้
    • พอมีแค่ ES5 แบบบางส่วน ก็มองว่าใช้งานจริงยาก
  • ผมไม่ค่อยรู้เรื่องระบบ embedded แต่สงสัยว่าเอนจินแบบนี้จะทำให้ เขียนโปรแกรม ESP32 หรือ Arduino ด้วย JavaScript ได้ไหม
    คล้าย ๆ MicroPython

    • มีโปรเจกต์คล้ายกันอยู่แล้ว
      • Espruino
      • Elk
      • DeviceScript (โปรเจกต์ที่ยุติไปแล้วของ Microsoft Research)
    • เอนจิน XS ของ Moddable/Kinoma รองรับ ES6 ขึ้นไป
      ส่วน MicroQuickJS รองรับเพียงบางส่วนของ ES5 และไม่มี environment binding มาให้
    • เมื่อก่อนมีบอร์ดเขียนโปรแกรมด้วย JS ชื่อ Tessel
      มันแปลงโค้ด JS เป็น Lua VM bytecode เพื่อรัน ซึ่งเป็นแนวทางที่ฉลาดมาก
      ไม่นานมานี้ผมยังลองเขียน Node 0.8 CLI เก่านั้นใหม่ด้วย Rust แต่สุดท้ายอุปกรณ์ก็กลับเข้าไปอยู่ในลิ้นชักเหมือนเดิม
    • ประเด็นสำคัญคือมันเป็นสถาปัตยกรรมที่ ไม่มี malloc() ซึ่งนี่แหละที่เปิดความเป็นไปได้ใหม่ ๆ
  • จังหวะเวลาสำคัญมาก ตอนโพสต์เมื่อคืนแทบไม่มีใครตอบเลย

    • น่าจะเป็นแค่ เรื่องดวง มากกว่า
    • คนอื่นก็เคยลองแล้วแต่ไม่ค่อยมีเสียงตอบรับ
      มีวิธีคือโพสต์ใหม่ช่วงเช้าของอเมริกา หรือรีโพสต์เป็นระยะ ๆ
  • Fabrice Bellard คือหนึ่งในโปรแกรมเมอร์ที่ทั้งสร้างผลงานได้มากและรอบด้านที่สุดคนหนึ่งที่ยังมีชีวิตอยู่
    ผลงานเด่น: FFmpeg, QEMU, JSLinux, TCC, QuickJS
    เป็นบุคคลระดับตำนาน

    • แม้เขาจะได้รับการยกย่องสูง แต่คนที่สนใจ วิธีการพัฒนา ของเขากลับมีไม่มาก
      แนวทางการสร้างโปรแกรมที่สมบูรณ์ด้วย dependency และเครื่องมือให้น้อยที่สุดนั้นน่าประทับใจมาก
    • ตอนนี้ผมเริ่มคิดว่าเขาอาจไม่ใช่คนคนเดียว แต่เป็น โค้ดเนมของกลุ่มแฮ็กเกอร์ฝีมือสูง
      เพราะถ้าเป็นคนจริงก็คงต้องนอนบ้าง
    • เขายังพัฒนา เอนจิน inference สำหรับ LLM เอง และดูแลมาตั้งแต่ยุค GPT-2
      ts_server, TextSynth
    • สิ่งที่น่าสนใจคือโปรแกรมส่วนใหญ่ของเขา ไม่ได้เน้นส่วนติดต่อผู้ใช้แบบ GUI
      ดูเหมือนเขาจะชอบโครงสร้างที่ผู้ใช้ตั้งค่าพารามิเตอร์ แล้วโปรแกรมทำงานจบในตัว
    • เขายังเคยชนะ International Obfuscated C Code Contest(IOCCC) ถึง 3 ครั้ง
      รายชื่อผู้ชนะ IOCCC
  • จุดที่น่าประทับใจคือมัน คอมไพล์และรัน JS ได้แม้มี RAM แค่ 10kB
    จังหวะนี้เหมาะมากในยุคที่ RAM กำลังแพงขึ้น
    สงสัยว่าจะเอาไปใส่ใน Chromium หรือ Electron ได้ไหม

    • น่าจะยากเพราะข้อจำกัดด้านความเข้ากันได้กับเว็บ แต่ถึงอย่างนั้น ผลด้านการประหยัดหน่วยความจำ ใน Chromium ก็คงไม่ได้มากนัก