5P by GN⁺ 11시간전 | ★ favorite | 댓글 2개
  • MicroQuickJS(MQuickJS) 는 임베디드 시스템용으로 설계된 초경량 JavaScript 엔진으로, 약 10kB RAM과 100kB ROM만으로 실행 가능
  • QuickJS와 유사한 속도를 유지하면서도 메모리 사용량을 줄이기 위해 트레이싱 가비지 컬렉터UTF-8 문자열 저장 방식을 채택
  • 지원 언어는 ES5에 가까운 제한된 JavaScript 부분집합이며, 오류 가능성이 높은 구문을 금지하는 엄격 모드(strict mode) 만 허용
  • REPL 도구 mqjs 를 통해 스크립트 실행, 바이트코드 저장 및 메모리 제한 설정이 가능하며, 생성된 바이트코드는 ROM에서 직접 실행 가능
  • 전체 엔진과 표준 라이브러리가 ROM에 상주해 빠른 초기화와 낮은 메모리 소비를 실현, 임베디드 환경에서의 JavaScript 실행 효율성을 높임

소개

  • MicroQuickJS(MQuickJS) 는 임베디드 시스템을 대상으로 하는 JavaScript 엔진으로, 10kB RAM과 100kB ROM(ARM Thumb-2 코드 포함)에서 동작
    • 속도는 QuickJS와 유사
  • ES5에 가까운 부분집합만 지원하며, 비효율적이거나 오류 가능성이 높은 구문을 금지하는 엄격 모드(strict mode) 로만 작동
  • QuickJS와 코드 일부를 공유하지만, 내부 구조는 메모리 절약을 위해 완전히 다르게 설계
    • 트레이싱 가비지 컬렉터, CPU 스택 미사용, UTF-8 문자열 저장 방식 사용

REPL

  • REPL 명령은 mqjs이며, 스크립트 실행, 평가, 인터랙티브 모드, 메모리 제한 설정, 바이트코드 저장 등을 지원
    • 예: ./mqjs --memory-limit 10k tests/mandelbrot.js
  • -o 옵션으로 컴파일된 바이트코드를 파일로 저장 가능
    • 저장된 바이트코드는 ./mqjs mandelbrot.bin으로 실행 가능
  • 바이트코드는 CPU의 엔디언 및 워드 길이(32/64비트) 에 따라 다르며, -m32 옵션으로 32비트용 바이트코드 생성 가능
  • --no-column 옵션으로 디버그 정보의 열 번호 제거 가능

엄격 모드

  • strict mode만 허용하며, with 키워드 사용 불가, 전역 변수는 반드시 var로 선언해야 함
  • 배열의 구멍(hole) 허용 안 함
    • 예: a[10] = 2는 TypeError 발생
    • 구멍이 있는 배열이 필요하면 일반 객체 사용
  • 전역 eval만 지원, 지역 변수 접근 불가
  • 값 박싱(value boxing) 비지원 (new Number(1) 등)

JavaScript 부분집합

  • strict mode 기반, ES5 호환성 중심
  • Array 객체는 구멍이 없으며, 범위를 벗어난 인덱스 접근은 오류
  • for in은 객체의 자체 속성만 순회, for of는 배열만 지원
  • 글로벌 객체는 존재하지만 getter/setter 불가, 직접 생성한 속성은 전역 변수로 노출되지 않음
  • 정규식(Regexp) 은 ASCII만 대소문자 구분 처리, /./은 UTF-16 대신 유니코드 코드포인트 단위로 매칭
  • 문자열 함수는 ASCII만 처리 (toLowerCase, toUpperCase)
  • DateDate.now()만 지원
  • 추가 지원 기능:
    • for of, Typed arrays, \u{hex} 문자열 리터럴
    • Math 함수: imul, clz32, fround, trunc, log2, log10
    • 지수 연산자, 정규식 플래그(s, y, u) , 문자열 함수(replaceAll, trimStart, trimEnd), globalThis

C API

  • C 라이브러리 의존성 최소화, malloc, free, printf 미사용
  • 메모리 버퍼를 직접 제공해야 하며, 엔진은 해당 버퍼 내에서만 메모리 할당
    • 예: ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • 가비지 컬렉션 방식으로 인해 JS_FreeValue() 호출 불필요
  • 객체 주소는 할당 시마다 이동 가능하므로, JSValue 포인터 사용 권장
    • JS_PushGCRef() / JS_PopGCRef()로 안전한 참조 관리
  • 표준 라이브러리는 ROM에 저장 가능한 C 구조체로 컴파일되어, 빠른 초기화와 낮은 RAM 사용량 달성
  • 바이트코드 실행은 ROM에서 가능하며, JS_RelocateBytecode()로 재배치 후 JS_LoadBytecode()JS_Run()으로 실행
  • 수학 라이브러리(libm.c)부동소수점 에뮬레이터 내장

내부 구조 및 QuickJS 비교

  • 가비지 컬렉션: 참조 카운팅 대신 트레이싱·압축형 GC 사용
    • 메모리 단편화 방지, 객체 크기 축소
  • 값 표현: CPU 워드 크기(32/64비트)에 맞춰 설계
    • 31비트 정수, 유니코드 코드포인트, 부동소수점, 메모리 블록 포인터 저장 가능
  • 문자열은 UTF-8로 저장, QuickJS의 8/16비트 배열 방식보다 효율적
  • C 함수는 단일 값으로 저장 가능, 속성 추가 불가
  • 표준 라이브러리는 ROM에 상주하며, RAM 객체 최소화로 빠른 엔진 초기화 가능
  • 바이트코드는 스택 기반이며, 간접 참조 테이블을 통해 읽기 전용 처리
    • Golomb 코드로 행·열 번호 압축
  • 컴파일러는 QuickJS와 유사하지만 비재귀적 파서를 사용해 C 스택 사용량 제한
    • 파스 트리 없이 단일 패스 바이트코드 생성

테스트 및 벤치마크

  • 기본 테스트: make test
  • QuickJS 마이크로 벤치마크: make microbench
  • Octane 벤치마크(엄격 모드용 수정 버전)는 별도 다운로드 가능
    • 실행: make octane

라이선스

  • MIT 라이선스로 배포
  • 소스코드 저작권은 Fabrice BellardCharlie Gordon 소유

Fabrice Bellard 에 대한 소개는 예전에 제가 댓글로 적은 것을 참고하세요. 이분 참 꾸준하면서 놀라운 괴물..
https://news.hada.io/comment?id=51

Hacker News 의견들
  • 2010년에 이런 게 있었다면 Redis 스크립팅 언어는 Lua가 아니라 JavaScript였을 것 같음
    Lua는 언어적 이유가 아니라 구현상의 제약(작고, 빠르고, ANSI-C 기반) 때문에 선택된 것임
    Lua의 몇몇 아이디어는 좋지만, 개인적으로는 Algol 계열 문법에서 벗어난 점이 불필요하게 느껴졌음
    SmallTalk이나 FORTH처럼 새로운 추상 개념을 배우는 대가로 생기는 혼란은 가치가 있지만, Lua의 변화는 그만큼의 이유가 없다고 생각함

    • Lua의 문법이 마음에 드는 건 아니지만, 개발자들이 선택한 이유는 충분히 납득할 만하다고 생각함
      Lua는 tail call optimization(TCO) 을 지원하는 유일한 경량 언어로, 이 덕분에 루프 없이 재귀로만 프로그램을 짤 수 있음
      JavaScript는 이런 최적화가 없어 같은 방식으로는 불가능함
      Lua는 컴파일러 작성에도 특히 적합함. 재귀적 구조가 많기 때문임
      Redis 스크립팅에는 JS가 더 어울릴 수도 있지만, Lua를 폄하하는 건 아쉬움
    • Lua가 1993년에 처음 나왔다는 점을 고려하면, 당시로서는 꽤 전통적인 문법이었음
      브라질에서는 C보다 Pascal과 Ada가 더 널리 쓰였기 때문에 그 영향을 받은 것임
      Ruby나 Perl도 비슷한 시기에 나왔지만 훨씬 더 급진적인 문법 변화를 시도했음
    • 처음엔 13살 때 Lua를 쉽게 배웠다는 얘기를 하려다 멈췄는데, 댓글 작성자가 antirez 본인이라는 걸 깨닫고 놀랐음
    • 문법 문제를 해결하진 못하겠지만, “language skins” 개념이 흥미로움
      파서와 렉서를 분리해두면서도, {} 대신 then/end 같은 토큰을 바꿔 끼우는 시도는 거의 없었음
      관련 논의: HN 스레드, Reddit 토론
    • 혹시 Redis 스크립팅 언어로 Tcl은 고려되지 않았는지 궁금함. 원조 임베디드 언어이기 때문임
  • 이 엔진은 예전에 JSC 작업할 때 내가 원하던 방식으로 JS를 제한
    웹에서는 호환성 때문에 이런 제약이 불가능하지만, 임베디드 환경에서는 오히려 이런 제약이 기쁨을 주는 설계가 될 수 있음

    • 이미 이런 제약이 없는 JS 엔진을 가지고 있음
    • JSC에서 하던 멀티스레딩 작업은 어떻게 되었는지 궁금함. 애플을 떠난 뒤 중단된 건지, 코드가 여전히 남아 있는지 알고 싶음
  • 브라우저에서 MicroQuickJS를 바로 실행해볼 수 있는 플레이그라운드를 만들었음
    MicroQuickJS WebAssembly 버전
    참고로 원본 QuickJS 버전도 있음
    QuickJS는 2.28MB, MicroQuickJS는 303KB로 훨씬 가벼움

    • 빌드에 이름 정보 등이 포함돼서 크기가 커진 듯함
      emcc -O3 옵션이나 --closure 1을 추가하면 더 줄일 수 있을 것 같음
      QuickJS는 이미 최적화돼 있고, MicroQuickJS만 개선 여지가 있음
  • Jeff Atwood의 유명한 말처럼, “JavaScript로 쓸 수 있는 모든 앱은 결국 JavaScript로 쓰이게 됨
    이제 그 말이 임베디드 시스템에도 적용되는 듯함
    Jeff Atwood 위키

  • 커밋 히스토리 없이 업로드된 게 아쉬움
    이런 수준의 개발자가 프로젝트를 얼마나 빨리 완성하는지 보고 싶었음
    어차피 QuickJS 기반이라 비교 자체가 큰 의미는 없을 듯함

    • “public repository of…”라는 표현을 보면, 실제 작업 히스토리는 비공개 저장소에 있을 가능성이 있음
    • 아마 그냥 원샷으로 완성했을 수도 있음
  • 이게 yt-dlp의 YouTube JS 챌린지를 해결하는 가장 가벼운 방법이 될 수 있을지 궁금함
    yt-dlp EJS 문서 참고
    QuickJS는 이미 지원 중임

    • 가능성은 낮음. ES5 수준의 부분 구현만 지원하기 때문임
      YouTube의 JS 퍼즐은 너무 복잡해서, Python으로 만든 JS 에뮬레이터도 포기했을 정도임
    • ES5만 구현된 상태라 현실적으로 어렵다고 봄
  • 임베디드 시스템은 잘 모르지만, 이런 엔진이 ESP32나 Arduino를 JavaScript로 프로그래밍할 수 있게 해줄 수 있을지 궁금함
    MicroPython처럼 말임

    • 이미 비슷한 프로젝트들이 있음
    • Moddable/Kinoma의 XS 엔진은 ES6 이상을 지원함
      MicroQuickJS는 ES5 일부만 구현돼 있고, 환경 바인딩도 제공하지 않음
    • 예전에 Tessel이라는 JS 프로그래밍 보드가 있었음
      JS 코드를 Lua VM 바이트코드로 변환해 실행했는데, 꽤 영리한 접근이었음
      최근 그 옛날 Node 0.8 CLI를 Rust로 다시 써봤지만, 결국 장비는 서랍으로 돌아감
    • 핵심은 malloc()이 없는 구조라는 점임. 그게 가능성을 열어줌
  • 타이밍이 정말 중요함. 어젯밤에 올렸을 땐 아무 반응이 없었음

    • 아마 운의 문제일 뿐임
    • 다른 사람도 시도했지만 반응이 없었음
      미국 아침 시간대에 다시 올리거나, 주기적으로 재게시하는 전략이 있음
  • Fabrice Bellard는 현존하는 가장 생산적이고 다재다능한 프로그래머 중 한 명임
    대표작: FFmpeg, QEMU, JSLinux, TCC, QuickJS
    전설적인 인물임

    • 그가 높이 평가받는 만큼, 그의 개발 방식에 관심을 가지는 사람은 적음
      최소한의 의존성과 도구로 완전한 프로그램을 만드는 접근이 인상적임
    • 이제는 그가 한 명이 아니라 숙련된 해커 집단의 코드네임일지도 모른다는 생각이 듦
      진짜 사람이라면 잠은 자야 하니까
    • 그는 LLM 추론 엔진도 직접 개발해 GPT-2 시절부터 유지 중임
      ts_server, TextSynth
    • 흥미로운 점은, 그가 만든 프로그램 대부분이 GUI 중심의 사용자 인터페이스를 다루지 않는다는 것임
      사용자가 매개변수를 설정하면 프로그램이 완결적으로 실행되는 구조를 선호하는 듯함
    • 국제 난독화 C 코드 콘테스트(IOCCC) 에서 3회 우승한 경력도 있음
      IOCCC 수상자 목록
  • “10kB RAM에서도 JS를 컴파일하고 실행할 수 있다”는 점이 인상적임
    요즘처럼 RAM이 비싸지는 시기에 딱 맞는 타이밍임
    이걸 Chromium이나 Electron에 넣을 수 있을지 궁금함

    • 웹 호환성 때문에 어렵겠지만, 어차피 Chromium의 메모리 절감 효과는 크지 않을 듯함