🇧🇷 Olhando para o futuro de Haskell e JavaScript para Plutus

(Originalmente publicado por Luite Stegeman em 04/06/2020, traduzido por Joselmo Cabral)

Olhando para o futuro de Haskell e JavaScript para Plutus

Contratos inteligentes usam o compilador cruzado GHCJS para traduzir código fora da cadeia

Esta é a segunda das postagens técnicas do desenvolvedor Deep Dive da nossa equipe Haskell. Esta série ocasional oferece uma visão sincera dos elementos centrais da plataforma e dos protocolos Cardano e fornece insights sobre as escolhas de engenharia que foram feitas. Aqui, descrevemos alguns dos trabalhos que estão sendo realizados para melhorar as bibliotecas e as ferramentas de desenvolvedor do Plutus, a plataforma de contratos inteligentes de Cardano.

Introdução

Na IOHK, estamos desenvolvendo a plataforma de contrato inteligente Plutus para o blockchain Cardano. Um contrato Plutus é um programa Haskell parcialmente compilado no código Plutus Core on-chain e parcialmente no off-chain. O código on-chain é executado pelos nós da rede Cardano usando o intérprete do Plutus Core, a linguagem de contrato inteligente incorporada no ledger Cardano. É assim que a rede verifica as transações. O código fora da cadeia é para tarefas como a configuração do contrato e a interação do usuário. Ele é executado dentro da carteira de cada usuário do contrato, em um tempo de execução do node.js.

Compilar a parte fora da cadeia de um contrato Plutus envolve, portanto, traduzir o código fora da cadeia para JavaScript. Para isso, usamos o GHCJS, o compilador cruzado Glasgow Haskell to JavaScript.

No ano passado, não fizemos muitas alterações no gerador de código GHCJS. Em vez disso, fizemos uma reestruturação para tornar as coisas de compilação com o GHCJS mais confiáveis ​​e previsíveis, além de adicionar suporte ao Windows e usar os recursos mais recentes do Cabal. Este post fornece uma visão geral do que aconteceu e uma breve descrição do que está reservado para este ano.

Suporte do Cabal

Ao instalar um pacote com o GHCJS, você provavelmente usa o sinalizador da linha de comando –ghcjs ou inclui o compilador: ghcjs no seu arquivo de configuração. Isso ativa o flavor do compilador ghcjs no Cabal. O flavor ghcjs é baseado no flavor ghc e adiciona recursos como suporte para a execução de scripts de configuração criados pelo GHCJS e para adicionar fontes JavaScript.

A Cabal introduziu muitos recursos nos últimos anos, incluindo suporte para mochila, compilações locais no estilo Nix, várias bibliotecas (nomeadas) por pacote e planos de compilação por componente. Infelizmente, os novos recursos resultaram em muitas alterações na base de código e a manutenção do flavor ghcjs ficou para trás por algum tempo. Atualizamos o suporte ao GHCJS novamente na versão 3.0. Se você deseja usar os recursos de construção de novo estilo, certifique-se de usar o cabal-install versão 3 ou posterior.

As diferenças entre os flavours do compilador ghcjs e ghc são pequenas e o suporte à compilação cruzada no Cabal vem melhorando. Portanto, esperamos que, eventualmente, possamos descartar completamente o flavor do compilador ghcjs. As extensões seriam adicionadas como comportamento específico da plataforma no flavor ghc.

Plug-ins do compilador

O GHC permite que o compilador seja estendido com plug-ins, o que pode alterar aspectos do pipeline de compilação. Por exemplo, os plug-ins podem introduzir novos recursos de otimização ou estender o typechecker.

Diferentemente do Template Haskell, que é separado do compilador por meio da abstração Quasi typeclass, os plug-ins podem usar diretamente toda a API do GHC. Isso torna a abordagem de “intérprete externo” que o GHCJS introduziu para executar o Template Haskell em um compilador cruzado inadequado para plug-ins de compilador. Em vez disso, os plug-ins precisam ser criados para a plataforma de compilação (que executa o GHC).

Em 2016, o GHCJS introduziu suporte experimental para plug-ins de compilador. Isso dependia de procurar o plug-in no banco de dados do pacote GHCJS e tentar encontrar uma correspondência próxima para o pacote e o módulo do plug-in no banco de dados do pacote GHC. Agora adicionamos um novo sinalizador para apontar o GHCJS para pacotes executáveis ​​no sistema de compilação. Isso torna os plug-ins utilizáveis ​​novamente com compilações de novo estilo e outras configurações de banco de dados de pacotes ‘exóticos’.

Em princípio, nosso novo sinalizador pode fazer com que os plug-ins funcionem em qualquer compilador cruzado do GHC, mas o requisito de também criar o plug-in com o compilador cruzado é bastante feio. Estamos trabalhando para remover esse requisito, seguido pela mesclagem do suporte de plug-in para compiladores cruzados no GHC upstream (consulte o ticket 14335 e o 17957).

Emscripten toolchain

Há muito, muito tempo, o GHCJS trabalhou no Windows. Uma ou duas almas corajosas podem realmente ter usado isso! Seus pacotes de inicialização (os pacotes criados pelo ghcjs-boot) incluem o pacote Win32 na plataforma de compilação do Windows. O motivo disso foi a configuração do Cabal com o GHCJS. O sinalizador de sistema operacional da Cabal (win32) seria definido se a plataforma de compilação fosse o Windows. Na época, era mais fácil corrigir o pacote para compilar sem erros com o GHCJS e incluí-lo nos pacotes de inicialização. No entanto, o pacote Win32 realmente não funcionou e mantê-lo atualizado era um fardo de manutenção. Em algum momento, ficou para trás e o GHCJS não funcionou mais no Windows.

Os pacotes de inicialização que precisam incluir o Win32 no Windows eram indicativos de uma separação ruim entre a plataforma de compilação (que executa o compilador) e a plataforma host (que executa o executável produzido pelo compilador). Isso foi causado pela falta de uma cadeia de ferramentas C completa para o GHCJS. Muitos pacotes não possuem apenas código Haskell, mas também arquivos produzidos por uma cadeia de ferramentas C, por exemplo, por meio de um script de configuração do Autotools ou hsc2hs.

A abordagem do GHCJS era incluir alguns arquivos pré-gerados e usar a cadeia de ferramentas C da plataforma de construção (a mesma que o GHC usaria) para todo o resto, esperando que não quebrasse. Se ele quebrasse, corrigiríamos o pacote.

Nos últimos anos, o navegador como alvo de compilação vem ganhando cada vez mais força. A Emscripten fornece uma cadeia de ferramentas C há muitos anos e recentemente mudou de seu próprio back-end do compilador para o compilador Clang com o back-end padrão do LLVM.

O Clang é suportado pelo GHC como uma cadeia de ferramentas C por um tempo. Ele pode gerar código asm.js e WebAssembly que podem ser executados diretamente no navegador. Infelizmente, os usuários do compilador ainda não podem interagir diretamente com o código C compilado por meio do C FFI (importação estrangeira chamada) no GHCJS. Mas ter uma cadeia de ferramentas C permite que pacotes que dependem de scripts de configuração ou hsc2hs sejam compilados com muito mais confiabilidade. Isso corrige alguns problemas de compilação de longa data e nos permite dar suporte ao Windows novamente. Achamos que isso já vale a dependência adicional.

Uma variante do GHCJS 8.6 usando a cadeia de ferramentas Emscripten está disponível na ramificação ghc-8.6-emscripten, que pode ser instalada no Windows. Desta vez, o conjunto de pacotes de inicialização é o mesmo em todas as plataformas de compilação. O Emscripten está planejado para ser o conjunto de ferramentas padrão do GHCJS 8.8 em diante.

Organização do código

Em algum momento nos primeiros dias, o GHCJS foi implementado como um patch para o GHC. Gerou JavaScript a partir do código intermediário STG que uma instalação regular do GHC produziu. Isso facilitou a instalação de pacotes com o GHCJS. Basta instalar normalmente e obter JavaScript gratuitamente. Mesmo o Template Haskell funcionou!

A desvantagem dessa abordagem foi que a plataforma de construção afetou muito o código gerado. Se você construir em uma máquina Linux de 64 bits, todas as constantes da plataforma virão da plataforma Linux. E o código seria construído com a suposição de que Int é de 64 bits. Isso não é muito eficiente para implementar em JavaScript. E Cabal não estaria ciente de que o JavaScript estava sendo gerado.

Mais tarde, passamos a usar o ghc como uma biblioteca, introduzindo o Hooks para alterar o pipeline de compilação, quando necessário. Isso tornou possível tornar o tamanho da palavra da plataforma GHCJS independente da plataforma de construção, introduzir a extensão JavaScriptFFI e executar o Template Haskell em um processo separado com o node.js.

Infelizmente, acabou sendo difícil acompanhar as mudanças na biblioteca ghc upstream. Além disso, a modificação do ghc existente por meio de Hooks incentivou os engenheiros a contornar problemas em vez de corrigi-los diretamente no upstream.

No início de 2018, decidimos criar uma biblioteca ghc personalizada para o GHCJS, instalada como ghc-api-ghcjs, permitindo que resolvamos problemas sérios antes que eles fossem mesclados no upstream. Recentemente, descartamos a biblioteca separada e construímos o código-fonte GHC e GHCJS em uma biblioteca: ghcjs.

Embora ainda não possamos construir o GHCJS com o sistema de criação do GHC, estamos usando a árvore de origem do GHC upstream muito mais diretamente novamente. Estamos voltando ao passado? Talvez, mas desta vez tenhamos nossa própria plataforma com uma cadeia de ferramentas e ferramentas de construção, evitando as armadilhas que tornaram essa abordagem tão problemática na primeira vez.

Outlook

Em 2019, vimos melhorias para fazer o Cabal funcionar novamente, trazer de volta o suporte ao Windows e melhorar a cadeia de ferramentas C. Esperamos que as alterações subjacentes na base de código do GHCJS tornem mais fácil mesclar mais upstream e tornar as bibliotecas compatíveis com menos alterações. Continuando esse caminho de transformar o GHCJS em um compilador cruzado adequado, com uma cadeia de ferramentas adequada, além de reduzir suas ferramentas personalizadas, pretendemos alinhar ainda mais e, eventualmente, mesclar o GHCJS ao GHC. Embora sempre existam bibliotecas específicas da plataforma que simplesmente não podem ser compiladas de maneira cruzada, nosso objetivo final é fornecer ao GHC um destino JavaScript, juntamente com seus destinos x86_64, Arm, AArch64 e outros destinos de geração de código.

1 Like