7 pontos por GN⁺ 2025-12-24 | 2 comentários | Compartilhar no WhatsApp
  • MicroQuickJS (MQuickJS) é um motor JavaScript ultraleve projetado para sistemas embarcados, capaz de rodar com cerca de 10 kB de RAM e 100 kB de ROM
  • Adota um coletor de lixo por tracing e armazenamento de strings em UTF-8 para reduzir o uso de memória, mantendo velocidade semelhante à do QuickJS
  • A linguagem suportada é um subconjunto limitado de JavaScript próximo ao ES5, permitindo apenas o modo estrito (strict mode), que proíbe sintaxes com maior chance de erro
  • Com a ferramenta REPL mqjs, é possível executar scripts, salvar bytecode e definir limites de memória; o bytecode gerado pode ser executado diretamente da ROM
  • O motor completo e a biblioteca padrão ficam residentes na ROM, viabilizando inicialização rápida e baixo consumo de memória, aumentando a eficiência da execução de JavaScript em ambientes embarcados

Introdução

  • MicroQuickJS (MQuickJS) é um motor JavaScript voltado para sistemas embarcados, funcionando com 10 kB de RAM e 100 kB de ROM (incluindo código ARM Thumb-2)
    • A velocidade é semelhante à do QuickJS
  • Suporta apenas um subconjunto próximo ao ES5 e opera somente em modo estrito (strict mode), que proíbe sintaxes ineficientes ou propensas a erro
  • Compartilha parte do código com o QuickJS, mas sua estrutura interna foi projetada de forma completamente diferente para economizar memória
    • Usa coletor de lixo por tracing, não utiliza a pilha da CPU e adota armazenamento de strings em UTF-8

REPL

  • O comando do REPL é mqjs e suporta execução de scripts, avaliação, modo interativo, configuração de limite de memória e salvamento de bytecode
    • Exemplo: ./mqjs --memory-limit 10k tests/mandelbrot.js
  • Com a opção -o, é possível salvar o bytecode compilado em um arquivo
    • O bytecode salvo pode ser executado com ./mqjs mandelbrot.bin
  • O bytecode varia conforme o endianness e o tamanho da palavra da CPU (32/64 bits); com a opção -m32, é possível gerar bytecode para 32 bits
  • Com a opção --no-column, é possível remover o número da coluna das informações de depuração

Modo estrito

  • Apenas strict mode é permitido; o uso da palavra-chave with não é possível, e variáveis globais devem obrigatoriamente ser declaradas com var
  • Buracos (holes) em arrays não são permitidos
    • Exemplo: a[10] = 2 gera TypeError
    • Se precisar de um array com buracos, use um objeto comum
  • Apenas eval global é suportado, sem acesso a variáveis locais
  • Value boxing não é suportado (new Number(1) etc.)

Subconjunto de JavaScript

  • Baseado em strict mode, com foco em compatibilidade com ES5
  • O objeto Array não possui buracos, e acessos a índices fora do intervalo geram erro
  • for in percorre apenas as propriedades próprias do objeto, e for of é suportado apenas para arrays
  • O objeto global existe, mas getter/setter não são permitidos, e propriedades criadas diretamente não são expostas como variáveis globais
  • Expressões regulares (Regexp) tratam diferenciação entre maiúsculas e minúsculas apenas em ASCII, e /./ faz correspondência por code point Unicode em vez de UTF-16
  • Funções de string tratam apenas ASCII (toLowerCase, toUpperCase)
  • Date suporta apenas Date.now()
  • Funcionalidades adicionais suportadas:
    • for of, Typed arrays, literais de string \u{hex}
    • Funções de Math: imul, clz32, fround, trunc, log2, log10
    • Operador de exponenciação, flags de regexp (s, y, u), funções de string (replaceAll, trimStart, trimEnd), globalThis

API C

  • Dependência mínima da biblioteca C, sem uso de malloc, free, printf
  • É necessário fornecer diretamente o buffer de memória, e o motor só faz alocação dentro desse buffer
    • Exemplo: ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • Devido ao método de coleta de lixo, não é necessário chamar JS_FreeValue()
  • Como o endereço dos objetos pode mudar a cada alocação, é recomendável usar ponteiros para JSValue
    • JS_PushGCRef() / JS_PopGCRef() permitem gerenciar referências com segurança
  • A biblioteca padrão é compilada como uma estrutura C que pode ser armazenada em ROM, permitindo inicialização rápida e baixo uso de RAM
  • A execução de bytecode pode ocorrer a partir da ROM, realocando com JS_RelocateBytecode() e executando com JS_LoadBytecode() e JS_Run()
  • Inclui biblioteca matemática (libm.c) e emulador de ponto flutuante integrados

Estrutura interna e comparação com o QuickJS

  • Coleta de lixo: usa GC por tracing e compactação em vez de contagem de referências
    • Evita fragmentação de memória e reduz o tamanho dos objetos
  • Representação de valores: projetada de acordo com o tamanho da palavra da CPU (32/64 bits)
    • Pode armazenar inteiros de 31 bits, code points Unicode, ponto flutuante e ponteiros para blocos de memória
  • Strings são armazenadas em UTF-8, de forma mais eficiente que o esquema de arrays de 8/16 bits do QuickJS
  • Funções C podem ser armazenadas como um único valor, sem possibilidade de adicionar propriedades
  • A biblioteca padrão fica residente na ROM, permitindo inicialização rápida do motor com o mínimo de objetos em RAM
  • O bytecode é baseado em pilha e tratado como somente leitura por meio de uma tabela de referências indiretas
    • Usa código Golomb para comprimir números de linha e coluna
  • O compilador é semelhante ao do QuickJS, mas usa um parser não recursivo para limitar o uso da pilha em C
    • Geração de bytecode em passagem única, sem árvore de parsing

Testes e benchmarks

  • Teste básico: make test
  • Microbenchmark do QuickJS: make microbench
  • O benchmark Octane (versão modificada para modo estrito) pode ser baixado separadamente
    • Execução: make octane

Licença

  • Distribuído sob a licença MIT
  • Os direitos autorais do código-fonte pertencem a Fabrice Bellard e Charlie Gordon

2 comentários

 
xguru 2025-12-24

Para uma introdução ao Fabrice Bellard, consulte o que escrevi em um comentário anteriormente. Esse cara é realmente um monstro surpreendente e consistentemente brilhante..
https://news.hada.io/comment?id=51

 
GN⁺ 2025-12-24
Comentários do Hacker News
  • Se algo assim existisse em 2010, acho que a linguagem de script do Redis teria sido JavaScript, não Lua
    Lua foi escolhida não por razões da linguagem em si, mas por limitações de implementação (pequena, rápida e baseada em ANSI-C)
    Algumas ideias de Lua são boas, mas pessoalmente sempre achei desnecessário ela se afastar da sintaxe da família Algol
    A confusão que surge ao aprender novos conceitos de abstração, como em SmallTalk ou FORTH, vale a pena, mas acho que as mudanças do Lua não têm justificativa suficiente para isso

    • Não sou fã da sintaxe do Lua, mas acho totalmente compreensível por que os desenvolvedores o escolheram
      Lua é a única linguagem leve que oferece tail call optimization (TCO), o que permite escrever programas só com recursão, sem loops
      JavaScript não tem essa otimização, então isso não é possível da mesma forma
      Lua também é especialmente adequado para escrever compiladores, porque há muitas estruturas recursivas
      Talvez JS combine mais com scripts do Redis, mas é uma pena menosprezar o Lua
    • Considerando que Lua surgiu em 1993, sua sintaxe era bem tradicional para a época
      No Brasil, Pascal e Ada eram mais usados do que C, então a influência veio daí
      Ruby e Perl também apareceram em época parecida, mas tentaram mudanças sintáticas bem mais radicais
    • Eu ia comentar que aprendi Lua com facilidade aos 13 anos, mas parei quando percebi que quem escreveu o comentário era o próprio antirez
    • Isso não resolve o problema da sintaxe, mas a ideia de “language skins” é interessante
      Quase ninguém tenta separar parser e lexer e, ainda assim, trocar tokens como {} por then/end
      Discussões relacionadas: thread no HN, discussão no Reddit
    • Fico curioso se Tcl chegou a ser considerado como linguagem de script do Redis, já que foi a linguagem embarcada original
  • Esse engine restringe JS exatamente da forma que eu queria quando trabalhava no JSC
    Na web, restrições assim são impossíveis por causa da compatibilidade, mas em ambientes embarcados elas podem até ser um design prazeroso

    • Já temos um engine de JS sem essas restrições
    • Queria saber no que deu o trabalho de multithreading no JSC. Foi interrompido depois que você saiu da Apple, ou o código ainda existe?
  • Criei um playground para rodar o MicroQuickJS direto no navegador
    Versão WebAssembly do MicroQuickJS
    E, por referência, também existe a versão original do QuickJS
    QuickJS tem 2,28MB, enquanto MicroQuickJS tem 303KB, então é bem mais leve

    • Parece que o build ficou maior por incluir informações como nomes
      Com a opção emcc -O3 ou adicionando --closure 1, talvez desse para reduzir ainda mais
      QuickJS já está otimizado, e só o MicroQuickJS ainda parece ter margem para melhorar
  • Como naquela famosa frase do Jeff Atwood, “todo aplicativo que pode ser escrito em JavaScript eventualmente será escrito em JavaScript
    Agora parece que isso também vale para sistemas embarcados
    Wiki de Jeff Atwood

  • É uma pena que tenha sido publicado sem histórico de commits
    Eu queria ver com que rapidez alguém desse nível consegue concluir um projeto
    Como é baseado em QuickJS, essa comparação provavelmente não significaria muita coisa de qualquer forma

    • Pela expressão “public repository of…”, é possível que o histórico real do trabalho esteja em um repositório privado
    • Ou talvez ele simplesmente tenha feito tudo de uma vez
  • Fico me perguntando se essa pode ser a forma mais leve de resolver o desafio de JS do YouTube no yt-dlp
    Veja a documentação EJS do yt-dlp
    QuickJS já é suportado

    • É pouco provável. Ele só suporta uma implementação parcial no nível de ES5
      Os quebra-cabeças de JS do YouTube são complexos demais; até um emulador de JS feito em Python acabou sendo abandonado por causa disso
    • Como só implementa ES5, realisticamente parece difícil
  • Não conheço muito de sistemas embarcados, mas fico curioso se um engine desses poderia permitir programar ESP32 ou Arduino em JavaScript
    Algo como o MicroPython

    • Já existem projetos parecidos
    • O engine XS do Moddable/Kinoma suporta ES6 ou superior
      MicroQuickJS implementa apenas parte do ES5 e também não fornece bindings de ambiente
    • Antigamente existia uma placa programável em JS chamada Tessel
      Ela executava código JS convertendo-o para bytecode de uma VM Lua, o que era uma abordagem bem inteligente
      Recentemente reescrevi aquele velho CLI do Node 0.8 em Rust, mas no fim o hardware voltou para a gaveta
    • O ponto principal é a arquitetura sem malloc(). Isso é o que abre possibilidades
  • Timing é realmente importante. Quando foi postado ontem à noite, não teve reação nenhuma

    • Provavelmente foi só questão de sorte
    • Outra pessoa também tentou e não teve reação
      Existe a estratégia de repostar no horário da manhã nos EUA ou republicar periodicamente
  • Fabrice Bellard é um dos programadores mais produtivos e versáteis que existem hoje
    Obras principais: FFmpeg, QEMU, JSLinux, TCC, QuickJS
    Uma figura lendária

    • Por mais reconhecido que ele seja, pouca gente parece se interessar pelo modo como ele desenvolve
      É impressionante a abordagem de criar programas completos com o mínimo possível de dependências e ferramentas
    • Às vezes penso que ele talvez não seja uma pessoa só, mas o codinome de um grupo de hackers experientes
      Porque, se for uma pessoa de verdade, em algum momento ela precisa dormir
    • Ele também desenvolveu por conta própria um engine de inferência para LLMs, mantido desde a época do GPT-2
      ts_server, TextSynth
    • O interessante é que a maior parte dos programas que ele cria não lida com interfaces gráficas centradas no usuário
      Parece preferir estruturas em que o usuário define parâmetros e o programa roda até o fim por conta própria
    • Ele também venceu o International Obfuscated C Code Contest (IOCCC) três vezes
      Lista de vencedores do IOCCC
  • Impressiona que seja possível compilar e executar JS com apenas 10kB de RAM
    Isso chega em boa hora, justamente num momento em que RAM está ficando cara
    Fico curioso se daria para colocar isso no Chromium ou no Electron

    • Seria difícil por causa da compatibilidade com a web, mas de qualquer forma o ganho de memória no Chromium provavelmente não seria tão grande assim