Fabrice Bellard, MicroQuickJS 공개

1 month ago 9

  • 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)
  • Date는 Date.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 소유

Read Entire Article