1 pontos por GN⁺ 1 일 전 | 1 comentários | Compartilhar no WhatsApp
  • Lone Lisp é um Lisp que roda diretamente sobre chamadas de sistema do Linux, sem libc; Matheus Moreira o iniciou para verificar se seria possível criar uma linguagem completa e programas de espaço de usuário usando apenas C freestanding
  • Ao usar diretamente a estável ABI de espaço de usuário do Linux, ele removeu estados globais e APIs legadas da biblioteca C, como errno, locale e buffering implícito de arquivos, e implementou por conta própria o runtime, o alocador de memória e as ferramentas de teste
  • Oferece suporte a FEXPR e otimização adequada de chamadas de cauda, além de geradores, continuações delimitadas e tratamento de erros retomável; vetores, tabelas e continuações também são tratados pelo mesmo mecanismo de chamada de função
  • A inicialização é rápida, mas em benchmarks provisórios é um interpretador em nível de listas 10 a 100 vezes mais lento que Python; ele pretende manter o interpretador em C como referência de bootstrap e, no longo prazo, implementar um compilador JIT no próprio Lone
  • Moreira usa Claude para revisão de código e gerenciamento de projeto, mas escreve e revisa pessoalmente o código do Lone; seu objetivo é criar um gerador de site estático, um shell e utilitários para reconstruir o espaço de usuário tradicional do Linux

Dos games a C++, Ruby e Lisp

  • O que despertou seu interesse por programação foi Mega Man Battle Network; para interagir em fóruns relacionados, aprendeu inglês e também sua primeira linguagem, C++
  • Começou um curso de informática por volta dos 13 ou 14 anos em uma escola técnica no Brasil
    • Usou Dev-C++ e os tutoriais do cplusplus.com
    • Em 6 meses, aprendeu todo o currículo da escola e foi contratado como monitor para ajudar outros alunos
    • Também tentou criar jogos, mas não teve sucesso por falta do conhecimento de física necessário
  • Depois de C++, passou por Java, Ruby e Python até chegar a Lisp e Scheme, sendo atraído pela elegância linguística de Scheme
    • Aprendeu programação orientada a objetos lendo quase todo o tutorial de Java da Sun
    • Gostava de Java Swing e do design Nimbus, e chegou a criar um app para desenhar círculos e arcos para um professor de matemática
    • Em Ruby, escreveu gems, incluindo Acclaim, um parser de argumentos baseado em comandos no estilo Git
  • A motivação para explorar várias linguagens começou em projetos escolares, mas depois passou a ser movida pela curiosidade e pelo desejo de encontrar a linguagem certa para si

A expressividade de Ruby e a transparência de C

  • Ruby combinava bem com seu modo de pensar: muitas vezes dava para adivinhar o nome de um método e acertar, e a linguagem suportava formas no singular, no plural e sinônimos
    • Com composições próximas de pipelines de shell, como items.each.with_index.map, é possível fazer muito com pouco código
    • Ele ainda mantém Ruby sempre instalado e recentemente também iniciou um projeto Rails
  • Em C, as funções correspondem diretamente a símbolos ELF e código assembly, tornando o resultado da compilação fácil de entender
    • Ele considera que o C++ moderno se tornou, na prática, uma linguagem diferente da antiga, e já não diz mais que conhece C++
    • Tentou migrar para Rust, mas não se fixou; também se interessa por Zig e por seu criador, que justificam reescritas em C com velocidade e ABI
  • Na adolescência, lendo o código-fonte da VM do CRuby, encontrava na implementação respostas para perguntas do Stack Overflow como “por que Ruby se comporta assim?”
  • Depois explorou os códigos de CRuby, CPython, JVM, Guile e V8, avaliando especialmente o código-fonte do kernel Linux como referência de programação C profissional

Como lê código de outras pessoas

  • Ele não se limita a usar bibliotecas: lê código-fonte continuamente para entender implementações internas, a comunicação entre drivers e hardware e camadas ocultas
  • Mesmo ao criar aplicações reais, frequentemente acabava desviando para investigar a tecnologia de base, e desenvolveu uma tendência a refazer melhor problemas já resolvidos
  • Por concluir que essa tendência não combinava com uma carreira comum em desenvolvimento de software, decidiu não escolher programação como profissão e mantê-la como hobby
  • Ele não entendia código de drivers desde o começo; continuou lendo apesar da frustração até conseguir compreender parte do funcionamento

Engenharia reversa de um teclado RGB

  • O aplicativo Windows para controlar o teclado RGB de um notebook barebone Clevo era tão lento que levava mais de 1 minuto para abrir a janela, então ele criou um substituto
  • Com Wireshark, capturou os pacotes USB que o app enviava ao teclado e analisou sua estrutura para escrever um driver em C no espaço de usuário do Linux
  • Depois de colocar no GitHub, esqueceu o projeto, mas usuários reais apareceram; um deles até criou uma GUI
  • Em seguida, fez engenharia reversa de outros recursos do notebook, como gerenciamento de energia via ACPI, embora não tenha descoberto todas as funções

liblinux contornando a libc

  • Chamadas de sistema do Linux têm código simples o bastante para se parecerem com chamadas de função comuns, permitindo mirar diretamente no Linux sem passar pela libc
  • Linux é conhecido pela estabilidade da ABI de espaço de usuário, mas ele considera que os componentes de espaço de usuário não têm a mesma estabilidade
    • Encontrou no manual a observação de que algumas chamadas de sistema não eram suportadas pela glibc
    • Também foi influenciado por um caso da LWN segundo o qual getrandom levou cerca de 2 anos para entrar na glibc
  • A partir da ideia de criar uma liblinux.a contendo apenas chamadas de sistema e vinculá-la com -llinux, desenvolveu a liblinux
    • Era uma coleção de wrappers de chamadas de sistema do Linux para C freestanding
    • O runtime C (CRT) se refere a bibliotecas C padrão como glibc, musl e uclibc

Problemas que desaparecem em C freestanding

  • Linux retorna erros como valores de retorno comuns, mas a biblioteca C os armazena na variável global errno
  • Ao remover a libc, é possível evitar vários estados globais e APIs antigas
    • errno, rand/srand, estado de locale
    • strtok, que não é reentrante
    • buffering implícito de arquivos, que pode causar problemas inesperados
    • gets, atexit, signal, malloc
    • math.h, que se torna complexo ao se combinar com NaN e errno
  • Por outro lado, as regras de promoção de inteiros e a ausência de verificação de overflow são problemas do próprio C e permanecem
    • Para overflow, ele usa recursos embutidos do compilador
    • Também se mantém atento à possibilidade de criar novos defeitos por conta própria
  • Sem biblioteca padrão, foi necessário escrever tudo, de cópia de bytes a conversão de inteiro para string, mas a tarefa não foi tão impossível quanto parecia
    • O repositório da glibc era difícil de interpretar, mas o código-fonte da musl libc era limpo e fácil de explorar, ajudando bastante
    • No Lone, também implementou uma suíte de testes automatizados que usa apenas memória estática, sem biblioteca C nem alocação dinâmica de memória

A descoberta do nolibc e o início do Lone Lisp

  • Ele perguntou a Greg Kroah-Hartman por que o Linux não tinha uma biblioteca C oficial, e recebeu como resposta que no passado existiu o projeto klibc
  • Depois descobriu que desenvolvedores do kernel Linux haviam criado nolibc.h para ferramentas internas
    • Considerou que era mais completo e usável que sua liblinux, então arquivou o projeto
    • Hoje, nolibc.h cresceu dentro da árvore do kernel e virou um diretório próximo de uma libc temporária, mas não há garantia clara de estabilidade externa
  • Como projeto seguinte, iniciou Lone Lisp
    • Ele pensou que, se fosse possível criar um Lisp freestanding que roda diretamente no Linux, qualquer programa poderia ser escrito do mesmo modo
    • É um experimento para remover o complexo espaço de usuário moderno do Linux e recomeçar sobre a estável interface binária de chamadas de sistema
  • Também enviou patches ao Cosmopolitan, incluindo uma implementação de parsing de ponto flutuante
    • Ele valoriza o formato APE, interpretado simultaneamente como formatos executáveis de várias plataformas, e o recurso de arquivos embutidos dentro do executável

Tratamento de erros e continuações delimitadas

  • No início do Lone, todos os caminhos de erro encerravam o interpretador com exit(255), mas ele implementou um sistema de condições retomáveis ao estilo Common Lisp
  • A base do tratamento de erros são continuações delimitadas
    • Ele entende continuações como a cópia de frames da pilha e sua reprodução em outro lugar
    • Também vê exceções como uma forma de continuação que não captura valores
  • Tipos de erro são mapeados de símbolos como 'not-found para lambdas
    • (lambda (error) ...), com um argumento, funciona como um tratador de exceções comum
    • (lambda (error continuation) ...), com dois argumentos, pode chamar a continuação e retomar o código que errou com um valor
  • Ele optou deliberadamente por não adotar uma API de restart em que o código que sinaliza o erro enumera opções de reinício, como use-value em Common Lisp
    • signal, throw e transfer são estruturados como mecanismos básicos internos do avaliador, de modo que se comportem como se simplesmente tivessem retornado um valor

FEXPR e elementos da linguagem chamáveis

  • Ele também evita o modelo em que um avaliador Lisp tradicional trata diretamente formas especiais como if
  • Lone suporta FEXPR, equivalente a plugins do avaliador, separando formas especiais para fora do avaliador
    • Com esse design, a otimização de chamadas de cauda para funções Lisp puras foi possível primeiro, mas primitives tiveram bugs por algum tempo
    • O problema foi resolvido adicionando um mecanismo separado para que primitives pudessem retornar em posição de cauda
  • Vetores e tabelas também são chamáveis
    • ([1 2 3] 0) retorna 1
    • ([1 2 3] 0 4) altera o vetor para [4 2 3]
  • Continuações e geradores seguem a mesma convenção de chamada
    • control é parecido com begin, mas com suporte a continuações
    • transfer sai do bloco control mais próximo e retorna uma continuação chamável que representa o ponto de suspensão
    • Ao chamar essa continuação com um valor, a computação suspensa pode ser retomada com outro valor de retorno

Chamadas de sistema e estrutura de execução

  • Antes do Lone iniciar, o processo pai chama execve, e o Lone faz cerca de 12 chamadas a mmap para seu gerenciamento interno de memória
  • Ele lê um script da entrada padrão, executa e encerra; durante a execução, podem ocorrer chamadas adicionais a mmap, munmap e mremap
  • O sistema de módulos usa openat e close
  • Das mais de 300 chamadas de sistema do Linux, o próprio Lone usa apenas algumas, mas oferece uma primitive de chamada de sistema no nível Lisp para que programas possam chamar as demais
  • Para chamadas como ioctl, que exigem structs C do kernel, será preciso adicionar recursos de criação e manipulação de structs
    • Consultar o tamanho do terminal é um exemplo que exige esse suporte

O espaço de usuário que pretende criar com Lone

  • No nível da linguagem, ele implementou tratamento de erros, otimização adequada de chamadas de cauda, geradores e continuações delimitadas, e no longo prazo quer também incorporar ideias de Erlang/BEAM
  • A estrutura está sendo preparada para que vários interpretadores possam executar código Lisp em paralelo
  • O primeiro programa que ele pretende criar com Lone é um substituto para seu atual gerador de site estático baseado em um fork do PugJS
  • Depois, planeja escrever seu próprio shell e utilitários para recriar o espaço de usuário tradicional do Linux à sua maneira
  • Ele também criou uma implementação que exporta env como uma tabela Lone Lisp; na época não havia recurso de iteração, mas hoje há suporte a geradores
  • O interpretador também tem um recurso para ler e executar código Lisp de um segmento ELF especial
    • Ao copiar o binário do interpretador e inserir código Lisp, é possível criar um programa independente

Revisão e gerenciamento de projeto com Claude

  • Embora não haja limite para o volume total de trabalho, ele considera que o conjunto de recursos da linguagem em si está se aproximando da conclusão
  • Usa Claude como uma espécie de gerente, e a maior ajuda vem em revisão de código e gerenciamento de projeto
    • Pede que ele examine toda a base de código de forma rigorosa em busca de problemas, áreas não desenvolvidas e direções futuras
    • Abordagens rejeitadas e recursos a tratar depois são gerenciados em documentos e na memória do projeto
    • O coletor de lixo e o alocador de memória melhoraram bastante porque Claude sugeriu algoritmos melhores
  • Ele revisa pessoalmente cada linha que entra no Lone e não confia em IA para escrever código do Lone em seu lugar, pois também vê a escrita do código como educativa
  • Por outro lado, em um gerador de site estático em JavaScript, já deixou Claude escrever um recurso inteiro
  • Entre os recursos planejados estão suporte às APIs dos compiladores GCC/Clang e funções de proteção de pilha

Sintaxe trazida de Ruby e Scheme

  • Ele tentou combinar a expressividade de Ruby com a concisão de Scheme; como o parser de Lisp é simples, pôde escrevê-lo por conta própria e manipular listas aninhadas diretamente
  • O momento em que realmente entendeu Lisp foi ao implementar FEXPR, em que a função recebe a própria lista, não valores avaliados
  • Arrays usam a sintaxe [1 2 3], e dicionários usam {k v}
    • Isso reduz barreiras de entrada desnecessárias por ser familiar não só para usuários de Clojure e newLisp, mas também de Ruby, Python e JavaScript
  • Seu estilo de codificação em C adapta o estilo do kernel Linux, mas com maior preferência por alinhamento e ornamentos
  • Em Ruby, prefere reduzir pontuação e escrever mais próximo de frases em inglês, como method 'arg' em vez de method('arg')
  • Em Lisp, também reduz o aninhamento sintático
    • O let do Lone tem a forma (let (a b c d ...) body...)
    • Ele considera que nomes e valores aparecendo em sequência é mais natural para a aplicação de funções do que o formato tradicional (let ((a b) (c d) ...) body...)

Desempenho e plano de bootstrap

  • O benchmark em que o Lone se destaca é a velocidade de inicialização: ele consegue iniciar e encerrar um programa mais rápido do que Ruby leva para ler Rubygems
  • Em medições provisórias, é cerca de 10 a 100 vezes mais lento que Python
    • Lone é um interpretador em nível de listas parecido com a forma do capítulo 5.4 de SICP
    • Python e Ruby são interpretadores de bytecode, portanto pertencem a outra categoria de desempenho
  • Ele evita compilar para bytecode porque sente que transformar listas em bytecode faz o Lisp perder sua essência
  • No longo prazo, pretende manter o interpretador em C como referência final de bootstrap e escrever um compilador JIT dentro do Lone
    • A abordagem seria obter páginas executáveis com mmap e então gerar instruções
  • Em uma comparação provisória de Fibonacci recursivo com Bash, Lone foi muito mais rápido, mas ele reconhece distorções de medição, como a possibilidade de Bash criar um processo a cada iteração
  • Em scripts, o tempo de inicialização é importante, então o bootstrap rápido do Lone pode ser útil
    • Se tabelas hash forem pré-computadas na etapa de build e transformadas em dados de inicialização em C, o custo de inicialização em tempo de execução pode cair ainda mais

Memória e ambiente mínimo

  • mmap solicita páginas de memória ao kernel, e o tamanho comum de página é 4.096 bytes
  • É possível especificar permissões de leitura, escrita e execução, e JIT usa páginas executáveis
  • Mesmo que sejam necessários apenas 16 bytes, pedir 4 KiB gera muito desperdício; por isso, alocadores comuns obtêm uma região grande, como 128 MB, e a dividem em pedaços pequenos
  • O ambiente de nível superior do Lone tem apenas os dois símbolos import e export
    • Além de listas, literais e mecanismo de chamada de função, até if e quote precisam ser importados
    • É um design para que o usuário controle diretamente todos os nomes do ambiente
  • Atualmente há cerca de 50 funções, com foco em if, let, begin, control/transfer, intercept/signal, flatten e outras

Biblioteca padrão e documentação

  • A documentação da linguagem ainda está incompleta, mas o trabalho em biblioteca padrão está se aproximando mais que novos recursos da linguagem
  • Ele planeja tomar como referência a biblioteca padrão de Ruby e a convenção de nomes de Scheme
  • A próxima tarefa é um protocolo de iteração
    • Considera a forma (for (each collection) (lambda (item) ...))
    • As funções each existentes serão redesenhadas para todas retornarem geradores
  • Depois, pretende implementar manipulação de strings e um string builder para concatenação eficiente
  • Quando iteração e recursos de string estiverem prontos, ele acredita que também poderá escrever um framework web no estilo Hiccup
  • Ele se preocupa com a possibilidade de perder foco quando as funções de baixo nível terminarem e o trabalho passar para tarefas repetitivas de biblioteca padrão, e pretende usar a ajuda de gerenciamento de Claude

O objetivo de possuir diretamente a stack inteira

  • Mesmo recursos que pareciam misteriosos, como continuações, viram problemas comuns de implementação quando entendidos como cópia de frames da pilha; o objetivo do Lone é estimular outras pessoas a criarem e explorarem seus próprios sistemas
  • Ele quer deixar de ser apenas consumidor de bibliotecas e se tornar criador de bibliotecas, usando diretamente até chamadas de sistema que parecem tratadas como proibidas para desenvolvedores comuns
  • Ter um sistema próprio implica consertá-lo pessoalmente quando quebrar, mas ele espera que mais pessoas escolham essa responsabilidade e suas recompensas
  • Ele acredita que não é preciso temer o código antigo acumulado na computação e que camadas desnecessárias podem ser removidas
    • Talvez quem refaça o espaço de usuário do Linux não seja ele, mas desenvolvedores Rust ou Zig
    • Mesmo que Lone não tenha sucesso, ele ficará satisfeito se inspirar tentativas desse tipo

Software livre e Android

  • Ele valoriza o fortalecimento dos direitos do usuário buscado pelo software livre e se preocupa com a redução dos computadores de uso geral a eletrodomésticos gerenciados por empresas ou governos
  • Apesar de ter adotado uma abordagem pesada em alguns aspectos, systemd é visto por ele como um caso bem-sucedido de reimaginar e reconstruir o espaço de usuário tradicional do Linux
  • Ele acredita que é preciso escolher as batalhas, em vez de construir pessoalmente todas as camadas
    • Se Lone tivesse sido feito como um Lisp bare-metal, talvez nem chegasse à etapa de boot
    • Cita como exemplo o Sector Lisp de jart, um Lisp de setor de boot com menos de 512 bytes
  • Lone começou dentro do Termux, e uma parte considerável foi escrita no celular
  • Embora Android bloqueie algumas chamadas de sistema, incluindo statx, Lone roda no Android e a suíte de testes passa 100%

Médico e programador não profissional

  • Moreira é médico, formado em medicina em 2019
    • No ensino médio, interessou-se por biologia ao ler sobre programação de redes neurais
    • Sua rotina permite reservar tempo para programar: em alguns dias atende por 10 a 14 horas, em outros folga
  • Em hospitais que usam prontuário eletrônico, reduziu muito o trabalho com templates e habilidade de digitação
    • Consegue registrar informações mantendo contato visual com o paciente
    • O projeto Rails que iniciou recentemente é um sistema de prontuário eletrônico para resolver suas próprias necessidades
  • Ele aponta a falta de interesse pelo próprio computador como uma das razões pelas quais desenvolvedores profissionais não resolvem diretamente problemas pessoais
  • Para investigar implementações por dentro, é preciso ter curiosidade, não apenas a atitude de querer obter um resultado; ele ainda não sabe como despertar essa curiosidade nas pessoas

1 comentários

 
GN⁺ 1 일 전
Comentários no Lobste.rs
  • Gosto tanto dessa série de entrevistas que leio todas sem falta, e é muito interessante conhecer a relação pessoal que cada um construiu com a computação

    • É especialmente fascinante reunir e comparar os gatilhos de entrada que fizeram as pessoas passarem a gostar de computadores como algo além de uma simples ferramenta
      Shonumi, autor do emulador GBE+ GBA, também foi influenciado por Mega Man Battle Network como eu, e espero poder ler uma entrevista com ele algum dia
  • Exceções reiniciáveis não exigem necessariamente continuations de primeira classe (first-class continuations)
    Common Lisp oferece exceções reiniciáveis mesmo sem continuations, e o ponto principal é não desenrolar a pilha antes de chamar o handler
    Assim, a pilha e o estado em que a exceção ocorreu são preservados, e o handler pode retornar normalmente ou saltar para algum reinício em algum ponto da pilha, como no Common Lisp

    • Se passei a impressão de que isso era indispensável, foi por excesso de empolgação com a minha descoberta
      Ainda assim, no Lone Lisp isso de fato é necessário, porque a continuation retornada pode ser chamada várias vezes e pode escapar para fora do manipulador de sinal
      (import (lone print set quote lambda intercept signal))
      
      (set continuation  
        (intercept  
          (('return (lambda (value continuation) continuation)))  
          (signal 'return 'value)))
      
      (print (continuation 1)); 1  
      (print (continuation 2)); 2  
      (print (continuation 3)); 3  
      
      Como geradores são a base do processamento iterativo, o desempenho é mais importante, então usei uma pilha separada em vez de continuations delimitadas para eliminar cópias
      Como tratamento de erros é um caminho relativamente raro, decidi preservar toda a funcionalidade
  • Para muita gente, o trabalho ou projeto mais importante pode ser justamente aquilo que faz no tempo livre, sem remuneração e por diversão
    Isso me faz imaginar que até o meu médico poderia estar levando uma vida dupla secreta, criando sua própria linguagem Lisp

    • Pelo visto, eu não era o único a pensar assim
      O Dr. Cameron Kaiser também tocou projetos de peso, compartilha uma filosofia sobre propriedade das máquinas e usa no dia a dia uma workstation POWER9 totalmente aberta
  • Foi a primeira vez que ouvi falar de Lone Lisp, e parece um projeto muito legal
    Eu também comecei a aprender C++ por volta dos 13 ou 14 anos, usando o velho Dev-C++ nas aulas de informática, e isso me trouxe uma nostalgia enorme

    • É surpreendente saber que o Dev-C++ ainda continua existindo e teve lançamento até em 2021
  • Eu jamais esperaria que ele fosse médico, e isso me lembrou Con Kolivas
    Pelo visto, há bem mais médicos-programadores do que eu imaginava
    https://en.wikipedia.org/wiki/Con_Kolivas