Design: Proposta: Adicionar ligações de tipo interlinguística

Criado em 1 mai. 2019  ·  61Comentários  ·  Fonte: WebAssembly/design

Atualmente, o WebAssembly é muito bom na execução de código escrito em linguagens arbitrárias a partir de um determinado interpretador (geralmente JS), mas carece de vários recursos importantes quando se trata de combinar várias linguagens arbitrárias .

Um desses recursos é um sistema de tipo independente de idioma. Eu gostaria de propor que um ou vários desses sistemas sejam adicionados ao WebAssembly.

Como um aparte, em discussões anteriores sobre recursos, alguns colaboradores expressaram que a interoperabilidade de linguagem não deve ser um objetivo de design do WebAssembly. Embora eu concorde que não deva ser necessariamente uma meta de alta prioridade , acho que é uma meta pela qual se busca a longo prazo. Portanto, antes de entrar nas metas de design, vou expor os motivos pelos quais acho que a interoperabilidade de linguagem vale o esforço.

Por que se preocupar com a interoperabilidade da linguagem?

Os benefícios de reduzir as barreiras de idioma para idioma incluem:

  • Mais bibliotecas para usuários wasm : nem é

  • Adoção mais fácil de pequenos idiomas: no mercado atual, muitas vezes é difícil para os idiomas sem suporte corporativo obterem tração. Novas linguagens (e até mesmo linguagens como D com anos de refinamento) têm que competir com linguagens com grandes ecossistemas e sofrem com sua própria falta de bibliotecas. A interoperabilidade da linguagem permitiria que eles usassem ecossistemas existentes como Python ou Java.

  • Melhores conjuntos de ferramentas independentes de linguagem : no momento, a maioria das linguagens tem seu próprio esquema de carregamento de biblioteca e gerenciador de pacotes (ou, no caso C / C ++, vários não oficiais). Escrever um construtor de projeto independente de linguagem é difícil, porque essas linguagens geralmente têm dependências sutis e incompatibilidades ABI, que requerem uma solução monolítica para todo o projeto para serem resolvidas. Um sistema robusto de tipo inter-linguagem tornaria mais fácil para os projetos serem divididos em módulos menores, que podem ser tratados por uma solução do tipo npm.

No geral, acho que o primeiro ponto é o mais importante, por uma ampla margem. Um sistema de tipos melhor significa melhor acesso a outras linguagens, o que significa mais oportunidades de reutilizar o código em vez de escrevê-lo do zero. Eu não posso exagerar o quão importante isso é.

Requisitos

Com isso em mente, quero delinear os requisitos que um sistema de tipos de interlinguagem precisa passar.

Estou escrevendo sob a suposição de que o sistema de tipos seria estritamente usado para anotar funções passadas entre módulos e não verificaria como as linguagens usam sua própria memória linear ou gerenciada de forma alguma.

Para ser realmente útil em um ambiente wasm, tal sistema de tipos precisaria de:

1 - Segurança

  • Tipo seguro: o receptor deve ter acesso apenas aos dados especificados pelo chamador, no estilo de recursos do objeto.

    • A memória deve ser "esquecida" no final de uma chamada. Um chamado não deve ser capaz de obter acesso aos dados de um chamador, retornar e, em seguida, acessar esses dados novamente de qualquer forma.

2 - Sobrecarga

  • Os desenvolvedores devem se sentir à vontade para fazer chamadas entre módulos regularmente, por exemplo, em um loop de renderização.

    • Cópia zero: o sistema de tipos deve ser expressivo o suficiente para permitir que os intérpretes implementem estratégias de cópia zero se desejarem, e expressivo o suficiente para que esses implementadores saibam quando a cópia zero é ideal.

3 - Gráficos estruturais

  • O sistema de tipos deve incluir estruturas, ponteiros opcionais, matrizes de comprimento variável, fatias, etc.

    • Idealmente, o chamador deve ser capaz de enviar um gráfico de objeto espalhado na memória, respeitando os requisitos 1 e 2.

4 - Tipos de referência

  • Os módulos devem ser capazes de trocar tipos de referência aninhados profundamente nos gráficos de estrutura.

5 - Ponte entre layouts de memória

  • Este é um ponto muito importante. Diferentes categorias de idiomas têm diferentes requisitos. Linguagens que dependem de memória linear devem passar por fatias de memória, enquanto linguagens que dependem de GC devem passar referências de GC.

    • Um sistema de tipo ideal deve expressar tipos semânticos e permitir que as linguagens decidam como interpretá-los na memória. Embora a passagem de dados entre linguagens com layouts de memória incompatíveis sempre acarrete alguma sobrecarga, a passagem de dados entre linguagens semelhantes deve ser idealmente barata (por exemplo, os incorporadores devem evitar etapas de serialização-desserialização se um memcpy puder fazer o mesmo trabalho).

    • Ligações adicionais também podem permitir o armazenamento em cache e outras estratégias de otimização.

    • O trabalho de conversão ao passar dados entre dois módulos deve ser transparente para o desenvolvedor, desde que os tipos semânticos sejam compatíveis.

6 - Tratamento de erros em tempo de compilação

  • Qualquer erro relacionado a argumentos de chamada de função inválidos deve ser detectável e expressável em tempo de compilação, ao contrário de, por exemplo, JS, onde TypeErrors são lançados em tempo de execução ao tentar avaliar o argumento.
  • Idealmente, os próprios compiladores de linguagem devem detectar erros de tipo ao importar módulos wasm e gerar erros idiomáticos expressivos para o usuário. A forma que essa verificação de erro deve assumir precisa ser detalhada no repositório de convenções de ferramentas .
  • Isso significa que um IDL com conversores existentes para outros idiomas seria uma vantagem.

7 - Fornece um ponto de Schelling para interação interlinguística

  • É mais fácil falar do que fazer, mas acho que o wasm deve enviar um sinal para todos os escritores do compilador, que a maneira padrão de interoperar entre as linguagens é o X. Por razões óbvias, não é desejável ter vários padrões concorrentes para interoperabilidade de linguagem.

Implementação proposta

O que proponho é que as ligações ao Cap'n'Proto IDL por @kentonv sejam adicionadas ao Webassembly.

Eles funcionariam de maneira semelhante às ligações WebIDL: os módulos wasm exportariam funções e usariam instruções especiais para vinculá-los a assinaturas digitadas; outros módulos importariam essas assinaturas e as vinculariam às suas próprias funções.

A pseudo-sintaxe a seguir tem o objetivo de dar uma ideia de como seriam essas ligações; é aproximado e fortemente inspirado pela proposta WebIDL, e se concentra mais nos desafios técnicos do que em fornecer listas exaustivas de instruções.

As instruções de ligação Capnproto seriam todas armazenadas em uma nova seção de ligações Cap'n'proto .

Tipos cap'n'proto

O padrão precisaria de uma representação interna da linguagem de esquema do capnproto . Como exemplo, o seguinte tipo de Capnproto:

`` `Capitão Proto
struct Person {
nome @ 0 : Texto;
data de nascimento @ 3 : Data;

email @ 1 : Texto;
telefones @ 2 : Lista (PhoneNumber);

struct PhoneNumber {
número @ 0 : Texto;
tipo @ 1 : Tipo;

enum Type {
  mobile @0;
  home @1;
  work @2;
}

}
}

struct Date {
ano @ 0 : Int16;
mês @ 1 : UInt8;
dia @ 2 : UInt8;
}

might be represented as

```wasm
(<strong i="32">@capnproto</strong> type $Date (struct
    (field "year" Int16)
    (field "month" UInt8)
    (field "day" UInt8)
))
(<strong i="33">@capnproto</strong> type $Person_PhoneNumber_Type (enum 0 1 2))
(<strong i="34">@capnproto</strong> type $Person_PhoneNumber (struct
    (field "number" Text)
    (field "type" $Person_PhoneNumber_Type)
))
(<strong i="35">@capnproto</strong> type $Person (struct
    (field "name" Text)
    (field "email" Text)
    (field "phones" (generic List $Person_PhoneNumber))
    (field "birthdate" $Data)
))

Serializando da memória linear

As mensagens Capnproto passam dois tipos de dados: segmentos (bytes brutos) e recursos.

Eles mapeiam aproximadamente para a memória linear e tabelas do WebAssembly. Como tal, a maneira mais simples possível para webassembly criar mensagens capnproto seria passar um deslocamento e comprimento para a memória linear para os segmentos e um deslocamento e comprimento para uma tabela de recursos.

(Uma abordagem melhor poderia ser concebida para recursos, para evitar verificações de tipo de tempo de execução.)

Observe que os cálculos reais de serialização ocorreriam no código glue, se ocorrerem (consulte Gerando o código glue ).

Operadores de ligação

| Operador | Imediatos | Crianças | Descrição
| : --- | : --- | : --- | : --- |
| segmento | off-idx
len ‑ idx | | Obtém os valores off-idx 'th e len-idx ' th wasm da tupla de origem, que devem ser i32 s, como o deslocamento e o comprimento de uma parte da memória linear em em que um segmento é armazenado. |
| capturável | off-idx
len ‑ idx | | Obtém os valores off-idx 'th e len-idx ' th wasm da tupla de origem, que devem ser i32 s, como o deslocamento e o comprimento de uma parte da tabela na qual a tabela de capacidade é armazenada. |
| mensagem | capnproto-tipo
tabela de capacidade | segmentos | Cria uma mensagem capnproto com o formato capnproto-type , usando a tabela de capacidade e segmentos fornecidos. |

Serializando da memória gerenciada

É difícil definir um comportamento específico antes que a proposta do GC chegue. Mas a implementação geral é que as ligações capnproto usariam um único operador de conversão para obter os tipos capnproto dos tipos GC.

As regras de conversão para tipos de baixo nível seriam bastante diretas: i8 converte para Int8, UInt8 e bool, i16 converte para Int16, etc. Tipos de alto nível seriam convertidos em seus equivalentes capnproto: referências de estrutura e matriz convertidas em ponteiros, referências opacas converter em capacidades.

Uma proposta mais completa precisaria definir uma estratégia para enum e sindicatos.

Operadores de ligação

| Operador | Imediatos | Crianças | Descrição
| : --- | : --- | : --- | : --- |
| como | capnproto-tipo
idx | | Pega o idx 'th wasm da tupla de origem, que deve ser uma referência, e produz um valor capnproto de capnproto-type . |

Desserializando para memória linear

A desserialização para a memória linear é mais semelhante à serialização a partir dela, com uma ressalva adicional: o código wasm muitas vezes não sabe com antecedência quanta memória o tipo capnproto vai ocupar e precisa fornecer ao host algum tipo de método de gerenciamento de memória dinâmico .

Na proposta de vinculações WebIDL, a solução proposta é passar callbacks de alocador para a função de host. Para ligações capnproto, esse método seria insuficiente, porque as alocações dinâmicas precisam acontecer tanto no lado do chamador quanto no lado do receptor.

Outra solução seria permitir que os mapas de ligação de entrada se vinculassem a duas

Desserializando para memória gerenciada

A desserialização para a memória gerenciada usaria o mesmo tipo de operador de conversão da direção oposta.

Gerando o código cola

Ao vincular dois módulos wasm juntos (seja estaticamente ou dinamicamente), o incorporador deve listar todos os tipos de capnproto comuns a ambos os módulos, ligações entre tipos de função e tipos de capnproto, e gerar código de adesão entre cada par diferente de tipos de função.

O código de adesão dependeria dos tipos de dados vinculados. O código de cola entre ligações de memória linear se resumiria a chamadas memcpy. O código de colagem entre ligações de memória gerenciada se resumiria a referências de passagem. Por outro lado, o código de adesão entre a memória linear e gerenciada envolveria operações de conversão aninhadas mais complicadas.

Por exemplo, um módulo Java pode exportar uma função, tomando os argumentos como tipos de GC e vincular essa função a uma assinatura digitada; o interpretador deve permitir que um módulo Python e um C ++ importem essa assinatura de tipo; a vinculação C ++ passaria dados da memória linear, enquanto a vinculação Python passaria dados da memória GC. As conversões necessárias seriam transparentes para os compiladores Java, Python e C ++.

Soluções alternativas

Nesta seção, examinarei maneiras alternativas de trocar dados e como elas se classificam nas métricas definidas na seção Requisitos .

Trocar mensagens JSON

É a solução da força bruta. Não vou gastar muito tempo com isso, porque suas falhas são bastante óbvias. Não cumpre os requisitos 2, 4 e 6.

Envie bytes brutos codificados em um formato de serialização

É uma solução parcial. Defina uma maneira para que os módulos wasm passem fatias de memória linear e tabelas para outros módulos, e os escritores de módulo podem usar um formato de serialização (capnproto, protobuff ou algum outro) para codificar um gráfico estruturado em uma sequência de bytes, passar os bytes, e use o mesmo formato para decodificá-lo.

Ele passa 1 e 3, e pode passar 2 e 4 com alguns ajustes (por exemplo, passar as referências como índices para uma tabela). Pode passar de 6 se o usuário certificar-se de exportar o tipo de serialização para uma definição de tipo no idioma do chamador.

No entanto, ele falha nos requisitos 5 e 7. É impraticável ao vincular entre duas implementações de GC; por exemplo, um módulo Python chamando uma biblioteca Java por meio de Protobuf precisaria serializar um dicionário como memória linear, passar essa fatia de memória e, em seguida, desserializá-la como um objeto Java, em vez de fazer algumas pesquisas hashtable que podem ser otimizadas em uma implementação JIT.

E incentiva cada escritor de biblioteca a usar seu próprio formato de serialização (JSON, Protobuf, FlatBuffer, Cap'n Proto, SBE), que não é ideal para interoperabilidade; embora isso possa ser aliviado definindo um formato de serialização canônico nas convenções de ferramentas .

No entanto, adicionar a possibilidade de passar fatias arbitrárias de memória linear seria um bom primeiro passo.

Enviar objetos GC

Seria possível contar com módulos enviando objetos GC uns aos outros.

A solução tem algumas vantagens: a proposta do GC já está em andamento; ele passa 1, 3, 4 e 7. Os dados coletados por GC são caros para alocar, mas baratos para repassar.

No entanto, essa solução não é ideal para linguagens C-like. Por exemplo, um módulo D passando dados para um módulo Rust precisaria serializar seus dados em um gráfico GC, passar o gráfico para a função Rust, que o desserializaria em sua memória linear. Este processo aloca nós de GC que são imediatamente descartados, para uma grande quantidade de overhead desnecessário.

Deixando isso de lado, a proposta atual do GC não tem suporte embutido para enums e sindicatos; e o tratamento de erros seria em tempo de link ou tempo de execução em vez de tempo de compilação, a menos que o compilador possa ler e entender os tipos de GC wasm.

Use outras codificações

Qualquer biblioteca de serialização que define um sistema de tipos pode funcionar para o wasm.

Capnproto parece mais apropriado, por causa de sua ênfase na cópia zero, e seus recursos de objeto embutidos que mapeiam ordenadamente para tipos de referência.

Trabalho restante

Os conceitos a seguir precisariam ser elaborados para transformar essa proposta básica em um documento que pudesse ser submetido ao Grupo da Comunidade.

  • Operadores de ligação
  • Equivalências de tipo GC
  • Capacidades de objeto
  • Matrizes Bool
  • Matrizes
  • Constantes
  • Genéricos
  • Evolução de tipo
  • Adicione um terceiro tipo de ligação "getters e setters".
  • Possíveis estratégias de cache
  • Suporte para várias tabelas e memórias lineares

Nesse ínterim, qualquer feedback sobre o que já escrevi será bem-vindo. O escopo aqui é muito vasto, portanto, gostaria de ajudar a restringir as perguntas que esta proposta precisa responder.

Comentários muito úteis

podemos adicionar algumas ligações por tipo de IR para cobrir a grande maioria dos idiomas.

Esta é a suposição subjacente crucial que acredito simplesmente não ser verdadeira. Minha experiência é que existem (pelo menos!) Tantas opções de representação quanto implementações de linguagem. E eles podem ser arbitrariamente complicados.

Pegue o V8, que sozinho tem algumas dezenas (!) De representações para strings, incluindo codificações diferentes, cordas heterogêneas, etc.

O caso Haskell é muito mais complicado do que você descreve, porque as listas em Haskell são preguiçosas, o que significa que para cada caractere em uma string você pode precisar invocar um thunk.

Outras linguagens usam representações engraçadas para o comprimento de uma string ou não a armazenam explicitamente, mas exigem que seja computada.

Esses dois exemplos já mostram que um layout de dados declarativo não funciona, você frequentemente precisa ser capaz de invocar o código de tempo de execução, que por sua vez pode ter suas próprias convenções de chamada.

E isso são apenas strings, que são um tipo de dados bastante simples conceitualmente. Não quero nem pensar no número infinito de maneiras pelas quais as linguagens representam tipos de produtos (tuplas / estruturas / objetos).

E então há o lado receptor, onde você teria que ser capaz de criar todas essas estruturas de dados!

Então eu acho que é totalmente irreal chegarmos mesmo remotamente perto de apoiar a "vasta maioria das línguas". Em vez disso, começaríamos a privilegiar alguns, enquanto já cultivamos um grande zoológico de coisas arbitrárias. Isso parece fatal em vários níveis.

Todos 61 comentários

Isso é realmente interessante! Eu apenas li rapidamente e apenas tenho algumas idéias iniciais, mas minha primeira e principal pergunta seria perguntar por que o mecanismo FFI existente que a maioria das linguagens já fornece / usa não é suficiente para WebAssembly. Praticamente todas as linguagens com as quais estou familiarizado têm alguma forma de C FFI e, portanto, já são capazes de interoperar hoje. Muitas dessas linguagens são capazes de fazer verificação de tipo estático com base nessas ligações também. Além disso, já existe uma grande quantidade de ferramentas em torno dessas interfaces (por exemplo, a caixa bindgen para Rust, erl_nif para Erlang / BEAM, etc.). C FFI já atende aos requisitos mais importantes e tem o principal benefício de já ser amplamente comprovado e usado na prática.

5 - Ponte entre layouts de memória

Um sistema de tipo ideal deve expressar tipos semânticos e permitir que as linguagens decidam como interpretá-los na memória. Embora a passagem de dados entre linguagens com layouts de memória incompatíveis sempre acarrete alguma sobrecarga, a passagem de dados entre linguagens semelhantes deve ser idealmente barata (por exemplo, os incorporadores devem evitar etapas de serialização-desserialização se um memcpy puder fazer o mesmo trabalho).

O trabalho de conversão ao passar dados entre dois módulos deve ser transparente para o desenvolvedor, desde que os tipos semânticos sejam compatíveis.

A tradução transparente de um layout para outro ao passar dados pela barreira FFI realmente parece um trabalho para back-ends de compiladores ou tempos de execução de linguagem para mim, e provavelmente nem um pouco desejável em linguagens sensíveis ao desempenho como C / C ++ / Rust / etc. Em particular, para coisas que você planeja passar de um lado para outro na FFI, parece-me sempre preferível usar uma ABI comum, em vez de fazer qualquer tipo de tradução, pois a tradução provavelmente incorreria em um custo muito alto. O benefício de escolher um layout diferente da ABI comum da plataforma provavelmente não valerá a pena, mas vou admitir prontamente que posso estar entendendo mal o que você entende por layouts alternativos.

Como um aparte, colocar a carga de ferramentas FFI sólidas em compiladores / tempos de execução tem um benefício adicional, em que quaisquer melhorias feitas são aplicáveis ​​em outras plataformas e vice-versa, já que as melhorias para FFI para plataformas não Wasm beneficiam o Wasm. Acho que o argumento tem que ser realmente convincente para essencialmente começar da estaca zero e construir um novo mecanismo de FFI.

Peço desculpas se eu entendi mal o propósito da proposta, ou perdi algo crítico, como mencionei acima, preciso ler novamente com mais atenção, mas senti que precisava levantar minhas perguntas iniciais enquanto tinha algum tempo.

O Apache Arrow existe para isso também, mas é mais focado em aplicativos de alto desempenho.

Acho que concordo com a motivação geral aqui e basicamente se alinha com as discussões que tivemos sobre como as Web IDL Bindings poderiam ser generalizadas no futuro. De fato, os rascunhos anteriores do explicador continham uma entrada de FAQ mencionando este caso de uso interlinguístico.

Minha principal preocupação (e motivo para omitir essa entrada do FAQ) é o escopo: o problema geral de vincular N linguagens parece provavelmente gerar muitas discussões abertas (e possivelmente não encerradas), especialmente considerando que ninguém está fazendo isso já ( o que, obviamente, é um problema do ovo e da galinha). Em contraste, os problemas endereçados por Web IDL Bindings são bastante concretos e prontamente demonstrados com Rust / C ++ hoje, permitindo-nos motivar o esforço (não trivial) para padronizar / implementar e também prototipar / validar avidamente a solução proposta.

Mas minha esperança é que Web IDL Bindings nos permita quebrar esse problema da galinha e do ovo e começar a obter alguma experiência com vinculação interlinguística que poderia motivar uma próxima onda de extensão ou algo novo e não específico para Web IDL . (Observe que, como proposto atualmente, se dois módulos wasm usando Web IDL Bindings compatíveis se chamam, um impl otimizador pode fazer as otimizações que você está mencionando aqui; apenas sem a expressividade total do Cap'n Proto.)

Devo declarar desde já que ainda não tive tempo para grocar totalmente a proposta.
A razão para isso é que acredito que a tarefa é impossível. Existem duas razões fundamentais para isso:
uma. Idiomas diferentes têm semânticas diferentes que não são necessariamente capturadas em uma anotação de tipo. Caso em questão, a avaliação do Prolog é radicalmente diferente da avaliação do C ++: ao ponto em que as linguagens não são essencialmente interoperáveis. (Para Prolog, você pode substituir vários outros idiomas)

b. Por definição, não é garantido que qualquer LCD de sistemas de tipo capture toda a linguagem de tipo de um determinado idioma. Isso deixa o implementador da linguagem com uma escolha profundamente desconfortável: suportar seu próprio idioma ou abrir mão dos benefícios do sistema de tipos de seu idioma. Caso em questão: Haskell tem 'classes de tipo'. Qualquer implementação de Haskell que envolvesse o não suporte a classes de tipo iria eliminá-lo e torná-lo inutilizável.
Outro exemplo: o suporte do C ++ a genéricos requer a eliminação da genericidade em tempo de compilação; por outro lado, ML, Java (e um monte de outras linguagens) usam uma forma de representação universal - que não é compatível com a abordagem feita pelo C ++.

Por outro lado, ter duas expressões de um tipo exportado / importado parece trazer à tona seus próprios problemas: o sistema de linguagem deve verificar se as duas expressões são consistentes em algum sentido? De quem é a responsabilidade de fazer este trabalho?

@lukewagner Obrigado pelos links! Definitivamente estou feliz por ter a chance de ler aquele documento!

Parece-me que há duas coisas meio misturadas nesta discussão em particular - parte do que está abaixo está escrito para que eu possa ter meu entendimento verificado novamente, então sinta-se à vontade para apontar qualquer coisa que eu possa ter entendido mal ou esquecido:

  1. Ligações de host eficientes

    • Basicamente, o problema WebIDL se destina a resolver, pelo menos para ambientes de navegador - uma descrição de interface que mapeia de módulo-> host e host-> módulo, essencialmente delegando o trabalho de tradução de um para outro para o mecanismo host. Esta tradução não é necessariamente ideal, ou mesmo otimizada, mas os mecanismos de otimização podem fazer uso dela para isso. No entanto, mesmo otimizada, a tradução ainda é realizada em algum grau, mas isso é aceitável porque a alternativa ainda é a tradução, apenas mais lenta.

  2. Ligações de módulo a módulo heterogêneas eficientes.

    • Em outras palavras, dados dois módulos, um escrito em source e o outro em dest , compartilhando tipos entre eles, chamando de source-> dest e / ou dest-> source

    • Se nenhum FFI comum estiver disponível, e for dado algo como WebIDL, ou seja, pegando carona em 1, o caminho não otimizado seria traduzir por meio de algum tipo de denominador comum fornecido pelo ambiente de host ao chamar através das barreiras de idioma, por exemplo, source type -> common type -> dest type .



      • Um mecanismo de otimização poderia teoricamente fazer esta tradução direta de source para dest sem o intermediário, mas ainda impõe sobrecarga de tradução.



    • Se um FFI comum estiver disponível, ou seja, source e dest compartilham um ABI (por exemplo, C ABI), então source e dest podem ligar um para o outro diretamente com sem sobrecarga, por meio do FFI. Este é provavelmente o cenário mais provável na prática.

Minha opinião é que há definitivamente benefícios em alavancar WebIDL ou algo parecido (ou seja, um superconjunto que oferece suporte a um conjunto mais amplo de APIs / ambientes de host), mas é realmente apenas uma solução para o problema descrito em 1, e o subconjunto de 2 que lida com ligações entre idiomas onde nenhum FFI está disponível. O subconjunto de 2, onde FFI _está_ disponível, é claramente preferível às alternativas, uma vez que não incorre em overhead per se.

Existem boas razões para usar um IDL mesmo quando o FFI é uma opção? Para ser claro, eu definitivamente concordo em usar um IDL para os outros casos de uso mencionados, mas estou perguntando especificamente no contexto de interoperabilidade de linguagem, não em ligações de host.

Algumas perguntas adicionais que eu tenho, se C FFI (por exemplo, uma vez que é mais comum) e IDL são usados ​​/ presentes ao mesmo tempo:

  • Se as linguagens source e dest fornecerem diferentes definições de tipo para um tipo compartilhado com a mesma representação subjacente na memória de acordo com sua ABI comum (por exemplo, uma representação comum para uma matriz de comprimento variável ) - o mecanismo host tentará realizar uma tradução entre esses tipos apenas porque as diretivas IDL estão presentes, mesmo que eles possam chamar uns aos outros com segurança usando seu FFI padrão?

    • Se não, e é opt-in, esse parece ser o cenário ideal, já que você pode adicionar IDL para oferecer suporte à interoperabilidade com idiomas sem FFI, enquanto oferece suporte a idiomas com FFI ao mesmo tempo. Não tenho certeza de como um mecanismo host faria isso funcionar. Eu não pensei muito sobre isso, então provavelmente estou perdendo algo

    • Em caso afirmativo, como o mecanismo host unifica os tipos ?:



      • Se o mecanismo só se preocupa com o layout, como a análise estática pode detectar quando um chamador fornece tipos de argumento incorretos para um receptor? Se esse tipo de análise não for um objetivo, então parece que o IDL é realmente adequado apenas para ligações de host, e menos ainda entre linguagens.


      • Se o motor se preocupa mais do que o layout, em outras palavras, o sistema de tipo requer compatibilidade nominal e estrutural:





        • Quem define o tipo autoritativo para alguma função? Como faço para fazer referência ao tipo autoritativo de algum idioma? Por exemplo, digamos que estou chamando uma biblioteca compartilhada escrita em outra linguagem que define uma função add/2 e add/2 espera dois argumentos de algum tipo size_t . Minha linguagem não conhece necessariamente size_t nominalmente, ela tem sua própria representação compatível com ABI de inteiros não assinados da largura da máquina, usize , então as ligações FFI para essa função em minha linguagem usam meu tipos de idiomas. Dado isso, como meu compilador pode saber como gerar IDL que mapeia usize a size_t .






  • Existem exemplos de interfaces IDL usadas para chamar entre módulos em um programa, onde FFI está disponível, mas explicitamente não usado em favor da interface descrita por IDL? Especificamente algo que não é WebAssembly, principalmente interessado em estudar os benefícios nesses casos.

Admito que ainda estou tentando pesquisar todos os detalhes do WebIDL e seus predecessores, como tudo isso se encaixa com os diferentes hosts (navegador versus não navegador) e assim por diante, definitivamente me avise se eu esqueci algo.

@bitwalker

Isso é realmente interessante!

Que bom que gostou!

mas minha primeira e principal pergunta seria perguntar por que o mecanismo FFI existente que a maioria das linguagens já fornece / usa não é suficiente para WebAssembly.

O sistema de tipo C tem alguns problemas como um IDL interlinguístico:

  • Ele opera sob o pressuposto de um espaço de endereço compartilhado, que não é seguro e deliberadamente não se mantém no WebAssembly. (minha própria experiência com um JS-to-C FFI sugere que as implementações tendem a apenas trocar segurança por velocidade)

  • Ele não tem suporte nativo para matrizes de comprimento dinâmico, uniões marcadas, valores padrão, genéricos, etc.

  • Não há um equivalente direto para tipos de referência.

C ++ resolve alguns desses problemas (não o maior, espaço de endereço compartilhado), mas adiciona um monte de conceitos que não são realmente úteis no IPC. Claro, você sempre pode usar um superconjunto de C ou um subconjunto de C ++ como seu IDL e, em seguida, criar regras de vinculação em torno dele, mas nesse ponto você quase não está obtendo benefícios do código existente, então você também pode usar um existente IDL.

Em particular, para coisas que você planeja passar de um lado para outro na FFI

Não entendi bem como você quis dizer isso, mas para ser claro: não acho que a transferência de dados mutáveis ​​entre os módulos seja possível no caso geral. Esta proposta tenta delinear uma forma de enviar dados imutáveis ​​e obter dados imutáveis ​​de volta, entre módulos que não possuem nenhuma informação sobre como o outro armazena seus dados.

O benefício de escolher um layout diferente da ABI comum da plataforma provavelmente não valerá a pena, mas vou admitir prontamente que posso estar entendendo mal o que você entende por layouts alternativos.

A questão é, agora, a ABI comum é uma fatia de bytes armazenados na memória linear. Mas no futuro, quando a proposta do GC for implementada, algumas linguagens (Java, C #, Python) irão armazenar muito pouco ou nada na memória linear. Em vez disso, eles irão armazenar todos os seus dados em estruturas de GC. Se duas dessas linguagens tentarem se comunicar, serializar essas estruturas em um fluxo de bytes apenas para desserializá-las imediatamente seria uma sobrecarga desnecessária.


@KronicDeth Obrigado, vou dar uma olhada nisso.

Embora, olhando rapidamente o documento, este pareça ser um superconjunto de Flatbuffers, especificamente destinado a melhorar o desempenho. De qualquer forma, quais são suas qualidades que podem ajudar exclusivamente a interoperabilidade do módulo WebAssembly, em comparação com Flatbuffers ou Capnproto?


@lukewagner

Mas minha esperança é que Web IDL Bindings nos permita quebrar esse problema da galinha e do ovo e começar a obter alguma experiência com vinculação interlinguística que poderia motivar uma próxima onda de extensão ou algo novo e não específico para Web IDL.

Concordou. Minha suposição ao escrever esta proposta foi que qualquer implementação de capnproto bindings seria baseada no feedback da implementação da proposta WebIDL.

Minha principal preocupação (e motivo para omitir essa entrada do FAQ) é o escopo: o problema geral de vincular N linguagens parece provavelmente gerar muitas discussões abertas (e possivelmente não encerradas), especialmente considerando que ninguém está fazendo isso já ( o que, obviamente, é um problema do ovo e da galinha).

Eu acho que discutir a implementação de um capnproto tem valor, entretanto, mesmo tão cedo.

Em particular, tentei delinear quais requisitos a implementação deveria / poderia tentar cumprir. Acho que também seria útil listar os casos de uso comuns que um sistema de tipos entre idiomas pode tentar abordar.

Com relação ao problema N-to-N, estou me concentrando nestas soluções:

  • Se preocupe apenas com a transferência de dados no estilo RPC. Não tente passar dados mutáveis ​​compartilhados, classes, tempos de vida do ponteiro ou qualquer outro tipo de informação mais complicada do que "um vetor tem três campos: 'x', 'y' e 'z', que são todos flutuantes".

  • Tente agrupar linguagens e casos de uso em "clusters" de estratégias de tratamento de dados. Estabelecer estratégias no centro desses clusters; compiladores de linguagem vinculam-se a uma dada estratégia, e o interpretador faz o resto do trabalho NxN.


@fgmccabe

A razão para isso é que acredito que a tarefa é impossível. Existem duas razões fundamentais para isso:
uma. Idiomas diferentes têm semânticas diferentes que não são necessariamente capturadas em uma anotação de tipo. Caso em questão, a avaliação do Prolog é radicalmente diferente da avaliação do C ++: ao ponto em que as linguagens não são essencialmente interoperáveis. (Para Prolog, você pode substituir vários outros idiomas)

Qualquer implementação de Haskell que envolvesse o não suporte a classes de tipo iria eliminá-lo e torná-lo inutilizável.

Sim, a ideia não é definir uma abstração perfeita "facilmente compatível com todas as linguagens".

Dito isso, acho que a maioria dos idiomas tem algumas semelhanças na forma como estruturam seus dados (por exemplo, eles têm uma maneira de dizer "cada pessoa tem um nome, um e-mail e uma idade" ou "cada grupo tem uma lista de pessoas de tamanho arbitrário ").

Acho que é possível explorar essas semelhanças para reduzir significativamente o atrito entre os módulos. (veja também minha resposta a morno)

b. Por definição, não é garantido que qualquer LCD de sistemas de tipo capture toda a linguagem de tipo de um determinado idioma. Isso deixa o implementador da linguagem com uma escolha profundamente desconfortável: suportar seu próprio idioma ou abrir mão dos benefícios do sistema de tipos de seu idioma.

Sim. Acho que a regra aqui é "Se for um limite de biblioteca compartilhada, torne-o um tipo capnproto, caso contrário, use seus tipos nativos".

Por outro lado, ter duas expressões de um tipo exportado / importado parece trazer à tona seus próprios problemas: o sistema de linguagem deve verificar se as duas expressões são consistentes em algum sentido? De quem é a responsabilidade de fazer este trabalho?

Sim, inicialmente eu queria incluir uma seção sobre verificação de invariáveis ​​e outra sobre compatibilidade de tipo, mas perdi a coragem.

A resposta para "cuja responsabilidade é" geralmente é "o receptor" (porque ele deve assumir que todos os dados recebidos é suspeito), mas as verificações podem ser omitidas se o interpretador puder provar que o chamador respeita os invariantes de tipo.

O sistema de tipo C tem alguns problemas como um IDL entre idiomas

Só para ficar claro, não estou sugerindo isso como um IDL. Em vez disso, estou sugerindo que a interface binária (C ABI) já existe, está bem definida e já possui amplo suporte a idiomas. A implicação então é que o WebAssembly não precisa fornecer outra solução, a menos que o problema a ser resolvido vá além da interoperabilidade entre linguagens.

Ele opera sob o pressuposto de um espaço de endereço compartilhado, que não é seguro e deliberadamente não se mantém no WebAssembly.

Então, acho que vejo parte do mal-entendido aqui. Existem duas classes de FFI das quais estamos falando aqui, uma que envolve o compartilhamento de memória linear (FFI de memória compartilhada mais tradicional) e outra que não (IPC / RPC mais tradicional). Tenho falado sobre o primeiro e acho que você está mais focado no segundo.

Compartilhar memória entre módulos quando você está no controle deles (como no caso em que você está vinculando vários módulos independentes como parte de um aplicativo geral) é desejável para eficiência, mas sacrifica a segurança. Por outro lado, é possível compartilhar uma memória linear designada especificamente para FFI, embora eu não saiba como isso é prático com as ferramentas padrão que existem hoje.

Interop de módulo cruzado que _não_ usa memória compartilhada FFI, ou seja, IPC / RPC, definitivamente parece uma boa combinação para WebIDL, capnproto ou uma das outras sugestões nesse sentido, já que esse é o seu pão com manteiga.

A parte sobre a qual não tenho certeza é como combinar as duas categorias de forma que você não sacrifique os benefícios de nenhuma delas, já que a escolha de seguir um caminho ou outro depende muito do caso de uso. Pelo menos como dito parece que só poderíamos ter um ou outro, se for possível apoiar os dois, acho que seria o ideal.

Ele não tem suporte nativo para matrizes de comprimento dinâmico, uniões marcadas, valores padrão, genéricos, etc.

Acho que isso provavelmente não é relevante agora que percebi que estávamos falando sobre duas coisas diferentes, mas apenas para a posteridade: A ABI certamente tem uma _representação_ para matrizes de comprimento variável e uniões marcadas, mas você está certo em que C tem uma sistema de tipo fraco, mas esse não é realmente o ponto, os idiomas não estão visando C FFI para o sistema de tipo C. A razão pela qual o C ABI é útil é que ele fornece um denominador comum que as linguagens podem usar para se comunicar com outras que podem não ter nenhum conceito do sistema de tipo com o qual estão interagindo. A falta de recursos de sistema de tipo de nível superior não é ideal e limita o tipo de coisas que você pode expressar via FFI, mas as limitações também são parte do motivo pelo qual ele tem tanto sucesso no que faz, praticamente qualquer linguagem pode encontrar uma maneira para representar as coisas expostas a ele por meio dessa interface e vice-versa.

C ++ resolve alguns desses problemas (não o maior, espaço de endereço compartilhado), mas adiciona um monte de conceitos que não são realmente úteis no IPC. Claro, você sempre pode usar um superconjunto de C ou um subconjunto de C ++ como seu IDL e, em seguida, criar regras de vinculação em torno dele, mas nesse ponto você quase não está obtendo benefícios do código existente, então você também pode usar um existente IDL.

De acordo, para IPC / RPC, C é uma linguagem péssima para definir interfaces.

A questão é, agora, a ABI comum é uma fatia de bytes armazenados na memória linear.

Essa é certamente a base com a qual estamos trabalhando, mas o C ABI define muito além disso.

Mas no futuro, quando a proposta do GC for implementada, algumas linguagens (Java, C #, Python) irão armazenar muito pouco ou nada na memória linear. Em vez disso, eles irão armazenar todos os seus dados em estruturas de GC. Se duas dessas linguagens tentarem se comunicar, serializar essas estruturas em um fluxo de bytes apenas para desserializá-las imediatamente seria uma sobrecarga desnecessária.

Não estou convencido de que essas linguagens irão adiar o GC para o host, mas isso é apenas especulação da minha parte. Em qualquer caso, as linguagens que entendem as estruturas gerenciadas do host GC poderiam apenas decidir sobre uma representação comum para essas estruturas usando o C ABI tão facilmente quanto poderiam ser representadas usando capnproto, a única diferença é onde reside a especificação dessa representação. Dito isso, tenho apenas uma compreensão muito tênue dos detalhes da proposta GC e como ela se relaciona com a proposta de ligações de host, então, se eu estiver errado aqui, sinta-se à vontade para desconsiderar.

TL; DR: Acho que concordamos com relação à interoperabilidade do módulo, onde a memória linear compartilhada não está em jogo. Mas acho que a memória compartilhada _é_ importante suportar, e o C ABI é a escolha mais sensata para esse caso de uso devido ao suporte de idioma existente. Minha esperança é que esta proposta, à medida que evolui, apoie ambos.

O que precisamos é simplesmente uma maneira eficiente ao máximo de trocar buffers de bytes e uma maneira para as linguagens concordarem sobre o formato. Não há necessidade de corrigir isso em um sistema de serialização específico. Se o Capitão Proto for o mais adequado para esse propósito, pode surgir como um padrão comum organicamente, em vez de ser ordenado por wasm.

É claro que sou tendencioso, pois criei FlatBuffers , que é semelhante ao Cap'n Proto em eficiência, mas mais flexível e mais amplamente suportado. No entanto, eu também não recomendaria esse formato a ser obrigatório por wasm.

Existem muitos outros formatos que poderiam ser preferíveis a esses dois, dados certos casos de uso.

Observe que ambos Cap'n Proto e FlatBuffers são cópia zero, acesso aleatório e são eficientes em formatos de aninhamento (o que significa que um formato agrupado em outro não é menos eficiente do que não ser agrupado), que são as propriedades reais a serem consideradas para inter-idiomas comunicação. Você pode imaginar um IDL que permite especificar layouts de bytes muito precisos para um buffer, incluindo "os bytes seguintes são o esquema X do Cap'n Proto".

Embora eu seja sutilmente auto-promocional, posso apontar as pessoas para o FlexBuffers, que é uma espécie de FlatBuffers sem esquema. Ele tem as mesmas propriedades desejáveis ​​de cópia zero, acesso aleatório e aninhamento barato, mas pode permitir que as linguagens se comuniquem sem concordar com um esquema, sem fazer codegen, semelhante a como o JSON é usado.

@aardappel

O que precisamos é simplesmente uma maneira eficiente ao máximo de trocar buffers de bytes e uma maneira para as linguagens concordarem sobre o formato. Não há necessidade de corrigir isso em um sistema de serialização específico. Se o Capitão Proto for o mais adequado para esse propósito, pode surgir como um padrão comum organicamente, em vez de ser ordenado por wasm.

Eu entendo o ponto implícito, que o wasm não deve ser usado como uma forma de impor um padrão aos seus concorrentes, e eu pessoalmente sou indiferente a qual IDL é escolhido.

Dito isso, no final das contas, a borracha precisa encontrar a estrada em algum ponto. Se o wasm quer facilitar a comunicação entre idiomas (o que, claro, não é uma suposição que todos compartilham), então ele precisa de um formato padrão que possa expressar mais do que "esses bytes constituem números". Esse formato pode ser capnproto, estruturas C, flatbuffers ou mesmo algo específico para wasm, mas não pode ser um subconjunto de todos eles ao mesmo tempo, pelos motivos @fgmccabe delineados.

Embora eu seja sutilmente auto-promocional, posso apontar as pessoas para o FlexBuffers, que é uma espécie de FlatBuffers sem esquema. Ele tem as mesmas propriedades desejáveis ​​de cópia zero, acesso aleatório e aninhamento barato, mas pode permitir que as linguagens se comuniquem sem concordar com um esquema, sem fazer codegen, semelhante a como o JSON é usado.

Eu vejo o apelo, não acho que é isso que você quer na maioria das vezes, ao escrever uma biblioteca. O problema com JSON (além do terrível tempo de análise) é que quando você escreve para importar um objeto JSON em algum lugar do seu código, acaba escrevendo muito código de limpeza antes de usar seus dados, por exemplo:

assert(myObj.foo);
assert(isJsonObject(myObj.foo));
assert(myObj.foo.bar);
assert(isString(myObj.foo.bar));
loadUrl(myObj.foo.bar);

com potenciais vulnerabilidades de segurança se você não o fizer.

Consulte também 6 - Tratamento de erros em tempo de compilação acima.


@bitwalker

Certo, eu realmente não considerei a possibilidade de memória linear compartilhada. Eu precisaria de alguém mais familiarizado com o design de webassembly do que eu ( @lukewagner ?) Para discutir como isso é viável e se é uma boa maneira de realizar chamadas entre módulos; também dependeria de quantas suposições FFIs confiam que são invalidadas pelo layout de memória do wasm.

Por exemplo, os FFIs geralmente contam com o fato de que sua linguagem hospedeira usa a biblioteca C e fornecem acesso direto às bibliotecas nativas para a função malloc. Quão bem essa estratégia pode ser traduzida para wasm, no contexto de dois módulos mutuamente suspeitos?

Eu acho que deveria dizer algo neste tópico, como o criador do Capitão Proto, mas estranhamente, eu não descobri que tenho uma opinião muito boa. Deixe-me expressar alguns pensamentos adjacentes que podem ou não ser interessantes.

Também sou o líder de tecnologia do Cloudflare Workers, um ambiente "sem servidor" que executa JavaScript e WASM.

Estamos considerando apoiar o Cap'n Proto RPC como um protocolo para que os trabalhadores conversem entre si. Atualmente, eles são limitados a HTTP, portanto, a barra é definida bem baixa. :)

Em Workers, quando um Worker chama outro, é muito comum que ambos sejam executados na mesma máquina, mesmo no mesmo processo. Por essa razão, uma serialização de cópia zero como Cap'n Proto obviamente faz muito sentido, especialmente para WASM Workers, uma vez que operam em uma memória linear que poderia, em teoria, ser fisicamente compartilhada entre eles.

Uma segunda razão, menos conhecida, pela qual achamos que este é um bom ajuste é o sistema RPC. Cap'n Proto apresenta um protocolo RPC de capacidade total de objeto com pipelining de promessa, modelado após CapTP. Isso facilita a expressão de interações ricas e orientadas a objetos de maneira segura e eficiente. Cap'n Proto RPC não é apenas um protocolo ponto a ponto, mas sim modelos de interações entre qualquer número de partes em rede, o que achamos que será um grande negócio.

Enquanto isso, na terra do WASM, o WASI está introduzindo uma API baseada em capacidade. Parece que pode haver alguma "sinergia" interessante aqui.

Com tudo isso dito, vários objetivos de design do Cap'n Proto podem não fazer sentido para o caso de uso específico da FFI:

  • As mensagens Cap'n Proto são projetadas para serem independentes da posição e contíguas, de modo que possam ser transmitidas e compartilhadas entre os espaços de endereço. Os ponteiros são relativos e todos os objetos em uma mensagem precisam ser alocados em uma memória contígua, ou pelo menos um pequeno número de segmentos. Isso complica significativamente o modelo de uso em comparação com objetos nativos. Ao usar FFI no mesmo espaço de memória linear, essa sobrecarga é desperdiçada, pois você poderia estar passando ponteiros nativos para objetos de heap soltos muito bem.
  • As mensagens Cap'n Proto são projetadas para serem compatíveis com versões anteriores e posteriores entre as versões do esquema, incluindo a capacidade de copiar objetos e subárvores sem perdas, sem conhecer o esquema. Isso requer que algumas informações de tipo de luz sejam armazenadas diretamente no conteúdo, que o Cap'n Proto codifica como metadados em cada ponteiro. Se dois módulos que se comunicam por meio de um FFI forem compilados ao mesmo tempo, não há necessidade desses metadados.
  • As garantias de pipelining de promessa, encurtamento de caminho e ordenação do Cap'n Proto RPC fazem sentido quando há latência não desprezível entre um chamador e um receptor. O FFI em uma única CPU não tem essa latência e, nesse caso, a promessa de maquinário de pipelining provavelmente apenas desperdiça ciclos.

Resumindo, eu acho que quando você tem módulos implantados independentemente em sandboxes separados conversando uns com os outros, o Capitão Proto faz muito sentido. Mas, para módulos implantados simultaneamente em uma única sandbox, provavelmente é um exagero.

Obrigado pelo feedback!

Os ponteiros são relativos e todos os objetos em uma mensagem precisam ser alocados em uma memória contígua, ou pelo menos um pequeno número de segmentos. Isso complica significativamente o modelo de uso em comparação com objetos nativos. Ao usar FFI no mesmo espaço de memória linear, essa sobrecarga é desperdiçada, pois você poderia estar passando ponteiros nativos para objetos de heap soltos muito bem.

Não sei o quão viável é uma abordagem de memória linear compartilhada para wasm (veja acima).

Dito isso, de qualquer forma, não acho que a sobrecarga de indicadores relativos seria tão ruim. WebAssembly já usa deslocamentos relativos ao início da memória linear, e as implementações têm truques para otimizar as instruções ADD na maioria dos casos (eu acho), então a sobrecarga de usar ponteiros relativos provavelmente também poderia ser otimizada.

As mensagens Cap'n Proto são projetadas para serem compatíveis com versões anteriores e posteriores entre as versões do esquema, incluindo a capacidade de copiar objetos e subárvores sem perdas, sem conhecer o esquema. [...] Se dois módulos que se comunicam por meio de um FFI são compilados ao mesmo tempo, não há necessidade desses metadados.

Eu não acho que isso seja verdade. Ter uma maneira para os módulos definirem tipos compatíveis com versões anteriores em seus limites permite que o wasm use um modelo de árvore de dependência, evitando principalmente o problema do diamante de dependência de Haskell.

Uma fonte maior de sobrecarga inútil seria a maneira como capnproto xor s suas variáveis ​​contra seus valores padrão, o que é útil quando bytes zero são compactados, mas contraproducente em fluxos de trabalho de cópia zero.

Não sei o quão viável é uma abordagem de memória linear compartilhada para wasm (veja acima).

Ah, TBH, acho que não tenho contexto suficiente para acompanhar essa parte da discussão. Se você não tem um espaço de endereço compartilhado, sim, o Capitão Proto começa a fazer muito sentido.

Estou feliz em fornecer conselhos sobre como criar formatos como este. FWIW, há algumas pequenas coisas que eu mudaria no Capitão Proto se eu não me importasse com a compatibilidade com aplicativos que já existem hoje ... é principalmente, como, detalhes de codificação de ponteiro de baixo nível, no entanto.

Uma fonte maior de sobrecarga inútil seria a maneira como capnproto xors suas variáveis ​​contra seus valores padrão, o que é útil quando bytes zero são compactados, mas contraproducente em fluxos de trabalho de cópia zero.

Um pouco fora do assunto, mas a coisa do XOR é uma otimização, não uma sobrecarga, mesmo no caso de cópia zero. Isso garante que todas as estruturas sejam inicializadas com zero, o que significa que você não precisa fazer nenhuma inicialização na alocação de objetos se o buffer já estiver zerado (o que normalmente seria de qualquer maneira). Um XOR contra uma constante de tempo de compilação provavelmente custa 1 ciclo, enquanto qualquer tipo de acesso à memória custará muito mais.

@lukewagner Alguma

Acho que há casos de uso para compartilhar e não compartilhar memória linear e, em última análise, as ferramentas precisam oferecer suporte a ambos:

O compartilhamento faz sentido onde um aplicativo nativo hoje usaria vinculação estática ou dinâmica: quando todo o código que está sendo combinado é totalmente confiável e sua combinação usa o mesmo conjunto de ferramentas ou uma ABI rigorosamente definida. É mais um modelo de composição de software mais frágil, no entanto.

Não compartilhar memória faz sentido para uma coleção de módulos mais fracamente acoplados, onde o design clássico no estilo Unix colocaria o código em processos separados conectados por tubos. Pessoalmente, acho que esta é a direção mais excitante / futurística para um ecossistema de software mais composicional e, portanto, defendi que esse seja o padrão para qualquer conjunto de ferramentas destinado a participar do ecossistema ESM / npm por meio de integração ESM (e de fato que é o caso hoje com o wasm-pack / wasm-bindgen de Rust). Usar um mecanismo na vizinhança geral de Web IDL Bindings ou a extensão que você propôs faz muito sentido para mim como uma forma de RPC eficiente, ergonômica, digitada (sincronizada ou assíncrona).

Tendo finalmente lido isso na íntegra, parece muito com o meu pensamento nesta área (que esta caixa de comentário é muito curta para conter?).

Em particular, tenho pensado sobre o problema de comunicação entre os módulos como sendo melhor descrito com um esquema. Ou seja, não precisamos do formato de serialização Cap'nProto, podemos apenas usar o esquema. Não tenho nenhuma opinião sobre a linguagem de esquema do Cap'nProto especificamente neste momento.

Da perspectiva do WASI / ESM + npm, uma solução dessa forma faz mais sentido para mim. É uma abstração sobre ABIs, sem depender de uma ABI compartilhada. Essencialmente, permite descrever uma interface com uma API de linguagem de esquema e chamar além desses limites de linguagem com ABIs de aparência nativa em ambas as extremidades, permitindo que o host controle a tradução.

Em particular, isso não inclui o caso de uso para ter mais coordenação com outro módulo: se você tiver certeza de que pode compartilhar uma ABI, pode, na verdade, apenas usar uma ABI, qualquer ABI, seja C ou Haskell. Se você controlar e compilar todo o wasm em questão, esse é um problema muito mais fácil de resolver. É apenas quando você entra no caso do npm em que está carregando um código desconhecido arbitrário e não conhece seu idioma de origem, que algo como ter interoperabilidade no nível do esquema entre os módulos se torna incrivelmente atraente. Porque podemos usar o LCD do próprio wasm - que eu prevejo que seguirá um arco semelhante às bibliotecas nativas, e usar o C ABI - ou podemos usar o LCD das linguagens, codificadas na linguagem do esquema. E o esquema pode ser mais flexível tornando o requisito 2) um requisito flexível, por exemplo, deve ser possível converter de C para Rust para Nim com eficiência, mas C para Haskell tendo mais sobrecarga não é um problema.

Em particular, tenho pensado sobre o problema de comunicação entre os módulos como sendo melhor descrito com um esquema. Ou seja, não precisamos de [um] formato de serialização, podemos apenas usar o esquema.

Eu tendo a concordar com o primeiro, mas não tenho certeza se o último segue. Quem implementa o esquema? Mesmo que o host faça o transporte, em algum ponto você terá que definir quais valores / bytes do Wasm são realmente consumidos / produzidos em ambas as extremidades, e cada módulo deve trazer seus próprios dados em uma forma que o host entenda. Pode até haver vários formulários disponíveis, mas ainda assim não é diferente de um formato de serialização, apenas um pouco mais de alto nível.

deve ser possível converter de C para Rust para Nim de forma eficiente, C para Haskell tendo mais overhead não é um problema.

Talvez não, mas você deve estar ciente das implicações. Privilegiar linguagens do tipo C significa que Haskell não usaria essa abstração para módulos Haskell, por causa da sobrecarga induzida. Isso, por sua vez, significa que ele não participaria do mesmo ecossistema "npm" para suas próprias bibliotecas.

E "Haskell" aqui é apenas um substituto para quase todas as linguagens de alto nível. A grande maioria dos idiomas não é semelhante ao C.

Não alego ter uma solução melhor, mas acho que temos que ser realistas sobre o quão eficiente e atraente qualquer ABI ou abstração de esquema pode ser para a população geral de linguagens, além do estilo FFI usual de interoperabilidade unilateral. Em particular, não estou convencido de que um ecossistema de pacotes pan-linguísticos seja um resultado excessivamente realista.

Privilegiar linguagens do tipo C significa que Haskell não usaria essa abstração para módulos Haskell, por causa da sobrecarga induzida. Isso, por sua vez, significa que ele não participaria do mesmo ecossistema "npm" para suas próprias bibliotecas.

E "Haskell" aqui é apenas um substituto para quase todas as linguagens de alto nível. A grande maioria dos idiomas não é semelhante ao C.

Pode dar alguns casos de uso específicos? Idealmente, as bibliotecas existentes em Haskell ou alguma outra linguagem que seria difícil de traduzir em um esquema de serialização?

Suspeito que tudo se resumirá principalmente a bibliotecas de utilitários em vez de bibliotecas de negócios. Por exemplo, contêineres, algoritmos de classificação e outros utilitários que dependem dos genéricos da linguagem não são traduzidos bem para o wasm, mas analisadores, widgets de gui e ferramentas de sistema de arquivos sim.

@PoignardAzur , não é difícil traduzi-los, mas requer que copiem (serializar / desserializar) todos os argumentos / resultados em ambas as extremidades de cada chamada de módulo cruzado. Claramente, você não quer pagar esse custo para cada chamada à biblioteca interna do idioma.

Especificamente em Haskell, você também tem o problema adicional de que a cópia é incompatível com a semântica da preguiça. Em outros idiomas, pode ser incompatível com dados com estado.

Quem implementa o esquema? Mesmo que o host faça o transporte, em algum ponto você terá que definir quais valores / bytes do Wasm são realmente consumidos / produzidos em ambas as extremidades, e cada módulo deve trazer seus próprios dados em uma forma que o host entenda. Pode até haver vários formulários disponíveis, mas ainda assim não é diferente de um formato de serialização, apenas um pouco mais de alto nível.

O host implementa o esquema. O esquema não descreve bytes em tudo e permite que seja um detalhe de implementação. Isso é emprestado do design da proposta WebIDL Bindings, em que a parte interessante está nas conversões de estruturas C para tipos WebIDL. Este tipo de design usa Tipos de Interface Abstrata Wasm (eu sugiro a sigla: WAIT) em vez de tipos WebIDL. Na proposta WebIDL, não precisamos ou queremos obrigar uma representação binária de dados quando eles foram "traduzidos para WebIDL", porque queremos ser capazes de ir direto do wasm para as APIs do navegador sem uma parada no meio.

Privilegiar linguagens do tipo C significa que Haskell não usaria essa abstração para módulos Haskell, por causa da sobrecarga induzida.

Oh, concordo 100%. Eu deveria ter terminado o exemplo para deixar isso mais claro: Enquanto isso, Haskell para Elm para C # pode ser similarmente eficiente (supondo que eles usem tipos wasm gc), mas C # para Rust pode ter sobrecarga. Não acho que haja uma maneira de evitar a sobrecarga ao saltar entre paradigmas de linguagem.

Eu acho que sua observação está correta de que precisamos tentar evitar privilegiar qualquer idioma, porque se não formos ergonômicos + desempenho suficiente para um determinado idioma, eles não verão tanto valor em usar a interface e, portanto, não participarão do ecossistema .

Acredito que, abstraindo os tipos e não especificando um formato de conexão, podemos dar muito mais margem de manobra para os hosts otimizarem. Eu acho que um não objetivo é dizer "strings de estilo C são eficientes", mas é um objetivo dizer "linguagens que [querem] raciocinar sobre strings de estilo C podem fazê-lo de forma eficiente". Ou, nenhum formato deve ser abençoado, mas certas cadeias de chamadas compatíveis devem ser eficientes e todas as cadeias de chamadas devem ser possíveis.

Por cadeias de chamadas, quero dizer:

  1. C -> Ferrugem -> Zig -> Fortran, eficiente
  2. Haskell -> C # -> Haskell, eficiente
  3. C -> Haskell -> Ferrugem -> Esquema, ineficiente
  4. Java -> Ferrugem, ineficiente

E "Haskell" aqui é apenas um substituto para quase todas as linguagens de alto nível. A grande maioria dos idiomas não é semelhante ao C.

Sim, essa era minha intenção ao usar Haskell como uma linguagem concreta. (Embora Nim seja provavelmente um mau exemplo de uma linguagem semelhante a C, porque também faz uso intenso de GC)

-

Outra maneira que tenho pensado sobre os tipos abstratos é como um RI. Da mesma forma que o LLVM descreve um relacionamento muitos-para-um-para-muitos (muitos idiomas -> um IR -> muitos destinos), os tipos abstratos wasm podem mediar um mapeamento muitos-para-muitos de idiomas + hosts -> idiomas + hosts. Algo neste espaço de projeto pega o problema de mapeamento N ^ 2 e o transforma em um N + N.

O host implementa o esquema.

Bem, isso não pode ser suficiente, cada módulo tem que implementar algo para que o host possa encontrar os dados. Se o host espera um layout C, você deve definir este layout C, e cada cliente deve fazer o empacotamento / desempacotamento de / para isso internamente. Isso não é tão diferente de um formato de serialização.

Mesmo se fizéssemos isso, ainda seria útil definir um formato de serialização, por exemplo, para aplicativos que precisam transferir dados entre mecanismos únicos, por exemplo, por meio de rede ou persistência baseada em arquivo.

Bem, isso não pode ser suficiente, cada módulo tem que implementar algo para que o host possa encontrar os dados. Se o host espera um layout C, então você deve definir este layout C

O anfitrião não deve esperar nada, mas precisa dar suporte a tudo. Mais concretamente, usando a proposta webidl-bindings como um exemplo ilustrativo , temos utf8-cstr e utf8-str , que levam i32 (ptr) e i32 (ptr), i32 (len) respectivamente. Não há necessidade de impor na especificação "o host representa internamente isso como strings C" para poder mapear concretamente entre elas.
Então, cada módulo implementa algo, sim, mas a representação dos dados não precisa ser expressa na camada de dados / esquema abstratos, que é como isso nos dá a propriedade de abstrair sobre aquele layout de dados.
Além disso, isso é extensível na camada de ligações que mapeia entre os tipos wasm concretos e os tipos intermediários abstratos. Para adicionar suporte a Haskell (que modela strings como listas de cons de chars e arrays de chars), podemos adicionar utf8-cons-str e utf8-array-str bindings, que esperam (e validam) tipos wasm de (usando current sintaxe da proposta gc) (type $haskellString (struct (field i8) (field (ref $haskellString)))) e (type $haskellText (array i8)) .

Ou seja, cada módulo decide como os dados se originam. Os tipos abstratos + ligações permitem conversões entre como os módulos visualizam os mesmos dados, sem abençoar uma única representação como sendo de alguma forma canônica.

Um formato de serialização para (um subconjunto de) os tipos abstratos seria útil, mas pode ser implementado como um consumidor do formato de esquema e acredito que seja uma preocupação ortogonal. Acredito que o FIDL tenha um formato de serialização para o subconjunto de tipos que podem ser transferidos pela rede, impede a materialização de alças opacas, ao mesmo tempo que permite a transferência de alças opacas dentro de um sistema (IPC sim, RPC não).

O que você está descrevendo é muito próximo do que eu tinha em mente, com uma grande advertência: o esquema deve ter um número pequeno e fixo de representações possíveis. Fazer a ponte entre diferentes representações é um problema N * N, o que significa que o número de representações deve ser mantido pequeno para evitar sobrecarregar os gravadores de VM.

Portanto, adicionar suporte a Haskell exigiria o uso de associações existentes, não adicionar associações personalizadas.

Algumas representações possíveis:

  • Estruturas e ponteiros estilo C.
  • Bytes capnproto reais.
  • Aulas de GC.
  • Fechamentos servindo como getters e setters.
  • Dicionários de estilo Python.

A ideia é que, embora cada idioma seja diferente, e haja alguns outliers extremos, você pode ajustar um número bastante grande de idiomas em um número bastante pequeno de categorias.

Portanto, adicionar suporte a Haskell exigiria o uso de associações existentes, não adicionar associações personalizadas.

Depende do nível de granularidade das associações existentes em que você está pensando. N <-> N linguagens que codificam cada ligação possível é 2 * N * N, mas N <-> IR é 2 * N, e ainda se você disser N <-> [estilos de ligação comuns] <-> IR, onde o número de formatos comuns é k, você está falando 2 * k, onde k <N.

Em particular, com o esquema que descrevo, você obtém o Scheme gratuitamente (ele reutilizaria utf8-cons-str ). Se o Java modelar strings como arrays de char também, isso é uma ligação utf8-array-str . Se Nim usa string_views sob o capô, utf8-str . Se o Zig estiver em conformidade com a C ABI, utf8-cstr . (Não conheço os ABIs de Java / Nim / Zig, então não os mencionei como exemplos concretos anteriormente)

Portanto, sim, não queremos adicionar um vínculo para cada linguagem possível, mas podemos adicionar alguns vínculos por tipo de IR para cobrir a grande maioria dos idiomas. Acho que o espaço para discordância aqui é: quantas ligações são "poucas", qual é o ponto ideal, quão rígidos devem ser os critérios para saber se apoiamos a ABI de um idioma?
Não tenho respostas específicas para essas perguntas. Estou tentando dar muitos exemplos concretos para ilustrar melhor o espaço de design.

Além disso, eu afirmaria que queremos absolutamente especificar várias ligações por tipo abstrato, para evitar o privilégio de qualquer estilo de dados. Se a única ligação que expomos a Strings é utf8-cstr , então todas as linguagens que não possuem C-ABI precisam lidar com essa incompatibilidade. Estou bem em aumentar a complexidade de escrita da VM, um fator não pequeno.
O trabalho total no ecossistema é O (esforço de VM + esforço de implementação de linguagem) e ambos os termos são escalonados de alguma forma com N = número de idiomas. Seja M = número de incorporadores, k = número de ligações e a = número médio de ligações que uma determinada linguagem precisa implementar, com a <= k. No mínimo, temos M + N implementações WASM separadas.
A abordagem ingênua, com cada linguagem N implementando independentemente ABI FFI com todas as outras linguagens N, é O (M + N * N). Isso é o que temos em sistemas nativos, que é um forte sinal de que qualquer O (N * N) levará a resultados não diferentes dos sistemas nativos.
Segunda abordagem ingênua, em que cada VM precisa implementar todas as ligações N * N: O (M * N * N + N), que é claramente ainda pior.
O que estamos tentando propor é que temos k ligações que mapeiam entre uma linguagem abstrata, que mapeia de volta para todas as linguagens. Isso implica k trabalho para cada VM. Para cada linguagem, precisamos apenas implementar um subconjunto das ligações. O trabalho total é M * k + N * a, que é O (M * k + N * k). Observe que, no caso de k = N, o lado VM é "apenas" M * N, então para qualquer VM dada é "apenas" linear no número de idiomas. É claro que queremos k << N, porque senão ainda é O (N * N), não melhor do que a primeira solução.
Ainda assim, O (M * k + N * k) é muito mais palatável. Se k for O (1), isso torna todo o ecossistema linear no número de implementações, que é nosso limite inferior na quantidade de esforço envolvida. Um limite mais provável é k sendo O (log (N)), com o qual ainda estou bastante satisfeito.

O que é uma longa maneira de dizer, estou completamente ok em aumentar a complexidade da VM para este recurso por algum fator constante.

podemos adicionar algumas ligações por tipo de IR para cobrir a grande maioria dos idiomas.

Esta é a suposição subjacente crucial que acredito simplesmente não ser verdadeira. Minha experiência é que existem (pelo menos!) Tantas opções de representação quanto implementações de linguagem. E eles podem ser arbitrariamente complicados.

Pegue o V8, que sozinho tem algumas dezenas (!) De representações para strings, incluindo codificações diferentes, cordas heterogêneas, etc.

O caso Haskell é muito mais complicado do que você descreve, porque as listas em Haskell são preguiçosas, o que significa que para cada caractere em uma string você pode precisar invocar um thunk.

Outras linguagens usam representações engraçadas para o comprimento de uma string ou não a armazenam explicitamente, mas exigem que seja computada.

Esses dois exemplos já mostram que um layout de dados declarativo não funciona, você frequentemente precisa ser capaz de invocar o código de tempo de execução, que por sua vez pode ter suas próprias convenções de chamada.

E isso são apenas strings, que são um tipo de dados bastante simples conceitualmente. Não quero nem pensar no número infinito de maneiras pelas quais as linguagens representam tipos de produtos (tuplas / estruturas / objetos).

E então há o lado receptor, onde você teria que ser capaz de criar todas essas estruturas de dados!

Então eu acho que é totalmente irreal chegarmos mesmo remotamente perto de apoiar a "vasta maioria das línguas". Em vez disso, começaríamos a privilegiar alguns, enquanto já cultivamos um grande zoológico de coisas arbitrárias. Isso parece fatal em vários níveis.

Minha experiência é que existem (pelo menos!) Tantas opções de representação quanto implementações de linguagem. E eles podem ser arbitrariamente complicados.

Eu concordo completamente. Acho que tentar projetar tipos que de alguma forma abranjam a maioria das representações internas de dados da linguagem simplesmente não é tratável e tornará o ecossistema excessivamente complicado.

No final das contas, há apenas um mínimo denominador comum entre as linguagens quando se trata de dados: o do "buffer". Todas as línguas podem ler e construir isso. Eles são eficientes e simples. Sim, eles preferem linguagens que são capazes de abordar diretamente seus conteúdos, mas não acho que seja uma desigualdade que seja resolvida promovendo células cons (preguiçosas) para o mesmo nível de suporte de alguma forma.

Na verdade, você pode ir muito longe com apenas um único tipo de dados: o par ponteiro + len. Então você só precisa de um "esquema" que diga o que está nesses bytes. Ele promete estar em conformidade com UTF-8? O último byte é garantido sempre 0? Os primeiros campos de comprimento / capacidade de 4/8 bytes? Todos esses bytes são pequenos floats endian que podem ser enviados diretamente para o WebGL? Esses bytes são talvez um esquema X de formato de serialização existente? etc?

Eu proporia uma especificação de esquema muito simples que pode responder a todas essas perguntas (não um formato de serialização existente, mas algo de nível mais baixo, mais simples e específico para wasm). Em seguida, torna-se o fardo de cada idioma ler e gravar com eficiência esses buffers no formato especificado. As camadas intermediárias podem então passar pelos buffers às cegas, sem processamento, seja por cópia ou, quando possível, por referência / visualização.

Esta é a suposição subjacente crucial que acredito simplesmente não ser verdadeira. Minha experiência é que existem (pelo menos!) Tantas opções de representação quanto implementações de linguagem. E eles podem ser arbitrariamente complicados.

Concordo que essa é a suposição subjacente crucial. Discordo que não seja verdade, embora eu ache que é por causa de uma nuance semântica que não acho que tenha deixado claro.

As ligações não se destinam a mapear todas as representações de linguagem perfeitamente , elas só precisam ser mapeadas para todas as linguagens bem o suficiente .

Essa é uma suposição subjacente crucial que torna tudo tratável, de forma alguma, independentemente da codificação. A proposta de @aardappel de ir na outra direção e realmente reificar os bytes em um buffer que é decodificável também é construída na suposição de que é uma codificação com perdas da semântica de qualquer programa, alguns com mais perdas do que outros.

O caso Haskell é muito mais complicado do que você descreve, porque as listas em Haskell são preguiçosas, o que significa que para cada caractere em uma string você pode precisar invocar um thunk.

Eu realmente tinha esquecido disso, mas não acho que isso importe. O objetivo não é representar Strings Haskell enquanto preserva todas as suas nuances semânticas através de um limite de módulo. O objetivo é converter uma String Haskell em uma String IR, por valor. Isso envolve necessariamente o cálculo de toda a corda.

Esses dois exemplos já mostram que um layout de dados declarativo não funciona, você frequentemente precisa ser capaz de invocar o código de tempo de execução, que por sua vez pode ter suas próprias convenções de chamada.

A maneira de modelar isso, independentemente de como especificamos os vínculos (ou mesmo SE especificarmos algo para os vínculos), é lidar com isso no espaço do usuário. Se a representação de um tipo de uma linguagem não for mapeada para uma vinculação diretamente, ela precisará ser convertida para uma representação que o faça. Por exemplo, se as Strings de Haskell são realmente representadas como (type $haskellString (struct (field i8) (field (func (result (ref $haskellString)))))) , será necessário converter para uma string estrita e usar uma vinculação do tipo Scheme, ou para um array de texto e usar uma vinculação do tipo Java, ou para um CFFIString e use uma ligação tipo C. A proposição de valor de ter vários tipos de vinculação imperfeita é que alguns deles são menos estranhos para Haskell do que outros, e é possível construir tipos Wasm-FFI sem a necessidade de modificar o compilador.

E isso são apenas strings, que são um tipo de dados bastante simples conceitualmente. Não quero nem pensar no número infinito de maneiras pelas quais as linguagens representam tipos de produtos (tuplas / estruturas / objetos).
E então há o lado receptor, onde você teria que ser capaz de criar todas essas estruturas de dados!

Estou confuso, vejo que, ao dizer "vincular as línguas é completamente impossível, não devemos tentar de jeito nenhum", mas acredito que o que você tem dito tem mais a ver com "Não acredito no abordagem descrita aqui é tratável ", o que parece muito mais razoável. Em particular, minha objeção a essa linha de argumento é que ela não descreve um caminho a seguir. Dado que este problema é "muito difícil", o que fazemos?

Em vez disso, começaríamos a privilegiar alguns

Quase com certeza. A questão é: qual é o grau em que as poucas linguagens com suporte otimizado são privilegiadas? Quanta margem de manobra as línguas desfavorecidas têm para encontrar uma solução ergonômica?

enquanto já está crescendo um grande zoológico de coisas arbitrárias.

Não tenho certeza do que você quer dizer com isso. Minha interpretação do que é arbitrário é "quais idiomas oferecemos suporte", mas isso é o mesmo que "privilegiar alguns", o que seria uma contagem dupla. E, portanto, isso só seria fatalmente defeituoso naquele nível, ao invés de múltiplo: D

@aardappel a versão resumida é que esse é meu plano de backup se a abordagem abstrata declarativa falhar: vá na direção totalmente oposta e descreva um formato de serialização. Observa-se que a própria Web é construída quase inteiramente sobre Texto, por se tratar de um denominador comum extremamente baixo. O texto é trivialmente compreensível por todas as ferramentas e, portanto, algo como a Web é possível em cima dele.

A maior preocupação que tenho com os dados em buffers que podem tornar essa abordagem intratável é como podemos lidar com os tipos de referência? A melhor ideia que tenho é compartilhar tabelas e serializar índices nelas, mas não tenho uma ideia completa de como isso realmente funcionaria.

@ jgravelle-google talvez os tipos de referência devam ser mantidos separados? Portanto, a assinatura bruta de uma determinada função pode ser ref ref i32 i32 i32 i32 que na verdade é 2 anyrefs seguidos por 2 buffers de um tipo particular (especificado no esquema hipotético acima).

(Como um aparte, eu estou familiarizado com Haskell, mas a idéia de cadeia como listas preguiçosos de caracteres soprar minha mente. Quando são lista de bytes ligada sempre a forma mais eficiente ou conveniente para fazer alguma coisa? Eu entendo isso Haskell precisa de tudo para ser imutável, e as listas vinculadas permitem prefixar barato, mas você pode obter e manipular strings imutáveis ​​sem usar listas vinculadas.)

A maior preocupação que tenho com os dados em buffers que podem tornar essa abordagem intratável é como podemos lidar com os tipos de referência? A melhor ideia que tenho é compartilhar tabelas e serializar índices nelas, mas não tenho uma ideia completa de como isso realmente funcionaria.

Essa é uma das razões pelas quais propus capnproto como uma codificação. As tabelas de referência são mais ou menos integradas.

Em qualquer caso, queremos que qualquer formato escolhido tenha tipos de referência como cidadãos de primeira classe, que podem ser colocados em qualquer lugar no gráfico de dados. (por exemplo, em opcionais, matrizes, variantes, etc)

Obrigado a todos por seus comentários.

Acho que estamos começando a chegar ao ponto em que estamos recauchutando os mesmos argumentos novamente com muito pouca variação, então vou atualizar a proposta e tentar abordar as preocupações de todos. Provavelmente vou começar com um novo problema assim que terminar de escrever a proposta atualizada.

Para resumir o feedback até agora:

  • Há muito pouco consenso sobre qual formato de serialização, se houver, seria o melhor para o wasm. As alternativas incluem FlatBuffers, gráficos de estrutura C ABI com ponteiros brutos, um formato WASM IDL feito sob medida ou alguma combinação dos anteriores.

  • A proposta precisa de um espaço negativo mais forte. Múltiplos leitores ficaram confusos com o escopo da proposta e quais casos de uso ela deveria facilitar (vinculação estática x dinâmica, módulo para host x host para host, compartilhamento de dados mutáveis ​​x passagem de mensagens imutáveis).

  • @lukewagner expressou algum entusiasmo pelo potencial de um sistema de módulo conectando módulos mutuamente desconfiados, quando combinado com integração ESM. A próxima iteração da proposta deve expandir esse potencial; em particular, acredito que ter um sistema de tipo compatível com versões anteriores permitiria ao wasm usar um modelo de árvore de dependência semelhante ao npm, evitando o peso do problema do diamante de dependência.

  • Houve pouco feedback sobre o assunto de recursos, ou seja, valores opacos que podem ser retornados e passados, mas não criados a partir de dados brutos. Eu entendo isso como um sinal de que a próxima iteração deve ter muito mais ênfase neles.

  • Vários leitores expressaram preocupação sobre a viabilidade de um sistema de tipos interlinguísticos. Essas preocupações são um tanto vagas e difíceis de definir, em parte porque o assunto é muito abstrato, em parte porque a proposta até agora é muito vaga, o que ecoa o problema do ovo e da galinha de @lukewagner . Os estados de falha específicos incluem:

    • Concentrando-se muito em idiomas altamente visíveis, deixando para trás mais idiomas de nicho.
    • Ter uma abstração com vazamento que tenta ser muito geral, mas não cobre o caso de uso de ninguém de maneira conveniente ou eficiente.
    • Cobrindo muitos casos, criando uma implementação N * N inchada que ainda sofre dos problemas acima.

A próxima iteração da proposta precisa abordar essas questões, de uma forma ou de outra.

Em particular, acho que a discussão se beneficiaria muito com alguns exemplos espantalhos para raciocinar. Esses exemplos incluiriam pelo menos duas bibliotecas, escritas em diferentes linguagens com diferentes layouts de dados (por exemplo, C ++ e Java), sendo consumidas por pelo menos dois programas mínimos em diferentes linguagens (por exemplo, Rust e Python), para ilustrar o problema n * n e estratégias para lidar com isso.

Além disso, como os leitores apontaram, a proposta atualmente confunde a ideia de um esquema de tipos com a ideia de sua representação. Embora a proposta faça um bom trabalho ao definir os requisitos de um formato de representação, ela precisa primeiro definir os requisitos de um esquema de tipo abstrato.

De qualquer forma, obrigado novamente a todos que participaram dessa discussão até agora. Tentarei apresentar uma proposta mais completa o mais rápido possível. Se alguém aqui estiver interessado em me ajudar a escrevê-lo, não deixe de me enviar um e-mail!

@ jgravelle-google:

A maneira de modelar isso, independentemente de como especificamos os vínculos (ou mesmo SE especificarmos algo para os vínculos), é lidar com isso no espaço do usuário.

Sim, concordo, e meu argumento é semelhante ao de @aardappel : se isso é o que geralmente temos que fazer de qualquer maneira, devemos simplesmente aceitar isso e não tentar coisas ad-hoc para melhorar alguns casos estranhos. Userland é o lugar onde a conversão pertence, no espírito de tudo o mais em Wasm.

Estou confuso, vejo que, ao dizer "vinculação entre idiomas é completamente impossível, não devemos tentar de jeito nenhum",

Eu acho que é totalmente desejável definir um DDL (esquema de tipo) para interoperabilidade de dados entre linguagens. Só não acho que seja tratável construir conversões no Wasm. As conversões precisam ser implementadas no userland. A camada de ligação apenas prescreve um formato que o código do usuário deve produzir / consumir.

enquanto já está crescendo um grande zoológico de coisas arbitrárias.
Não tenho certeza do que você quer dizer com isso. Minha interpretação do que é arbitrário é "quais idiomas oferecemos suporte", mas isso é o mesmo que "privilegiar alguns", o que seria uma contagem dupla.

Desculpe, eu quis dizer que suspeito que não haverá nada terrivelmente canônico sobre essas conversões. Portanto, sua seleção é "arbitrária" e sua semântica individual.

como podemos lidar com tipos de referência?

Ah, essa é uma boa pergunta. FWIW, estamos tentando resolver esse mesmo problema agora para um IDL / DDL para a plataforma Dfinity. Contanto que haja apenas anyref, a solução é bastante simples: o formato de serialização define duas partes, uma fatia de memória projetando os dados transparentes e uma fatia de tabela projetando as referências contidas. Vários tipos de referência precisam de várias fatias de tabela de acordo. A questão complicada é o que fazer quando o conjunto de tipos ref não for mais finito (por exemplo, com referências de função digitadas).

Uma vez que temos os tipos de GC, deve haver uma maneira alternativa de fornecer os dados, que é como um valor de GC. Nesse caso, as referências não são um problema, porque podem ser misturadas livremente.

@PoignardAzur :

Como um aparte, eu não estou familiarizado com Haskell, mas a ideia de string como listas preguiçosas de chars me surpreende.

Sim, eu acredito que isso seja amplamente considerado um erro hoje em dia. Mas demonstra quanta diversidade existe até mesmo para tipos de dados "simples".

@rossberg

Eu acho que é totalmente desejável definir um DDL (esquema de tipo) para interoperabilidade de dados entre linguagens. Só não acho que seja tratável construir conversões no Wasm. As conversões precisam ser implementadas no userland.

Eu concordo, e para acrescentar a isso: estou cético quanto a adicionar algo à especificação do wasm para isso porque não acho que o wasm tenha uma necessidade maior de uma solução inter-linguagens do que outras plataformas, e não acho que o wasm tem uma capacidade maior de implementar essa solução do que outras plataformas. Não há nada obviamente especial para mim sobre o wasm aqui, então não tenho certeza por que podemos fazer melhor do que as soluções padrão nesta área, por exemplo, buffers como @aardappel mencionou. (Mas acho que a experimentação no espaço do usuário é muito interessante, pois é em todas as plataformas!)

A única coisa especial que o wasm tem, pelo menos na Web, são os tipos JavaScript / API da Web para strings e arrays e assim por diante. Ser capaz de interagir com eles é obviamente importante.

Não acho que o wasm tenha uma necessidade maior de uma solução inter-linguagens do que outras plataformas

Eu acho que sim. Ser usado na web por padrão significa que o código pode e será executado em diferentes contextos. Da mesma forma que alguém pode <script src="http://some.other.site/jquery.js"> , eu adoraria ver pessoas combinando bibliotecas wasm de uma forma de origem cruzada. Por causa das propriedades de efemeridade e composição que a web fornece, o valor agregado de ser capaz de fazer interface com um módulo estrangeiro é maior do que nunca em sistemas nativos.

e não acho que o wasm tenha maior capacidade de implementar essa solução do que outras plataformas.

E eu acho que sim. Como o wasm é executado por um incorporador / em um host, a geração de código é efetivamente abstraída. Por causa disso, uma VM tem muito mais ferramentas + margem de manobra para oferecer suporte a construções de nível superior que não são possíveis em sistemas nativos.

Então eu acho que algo neste espaço é mais valioso e mais possível do que em outros sistemas, então é por isso que o wasm é especial neste contexto. Para mim, a interoperabilidade JS é um caso especial da noção mais geral de que os módulos wasm precisam ser capazes de falar com coisas externas com visões muito diferentes do mundo.

Um caminho a seguir para isso é empurrar tudo para a interoperabilidade em nível de ferramenta por enquanto e adiar a padronização até que tenhamos um formato vencedor. Portanto, se o objetivo é ter o ecossistema do gerenciador de pacotes wasm predominante usando um determinado formato de interface (e esse NPM ou WAPM ou algum gerenciador de pacotes ainda criado?), Isso pode acontecer independentemente da padronização. Em teoria podemos padronizar o que as pessoas já estão fazendo, para obter um melhor desempenho, mas a ergonomia pode ser implementada na área do usuário. Um risco é que o formato vitorioso de interlíngua não leve à otimização e acabemos com um padrão de fato subótimo. Se pudermos projetar um formato com a intenção de padronizar mais tarde (o estilo declarativo em uma seção personalizada é mais suficiente?), Isso remove esse risco, mas também atrasa quaisquer melhorias de desempenho. Para mim, o desempenho é um dos motivadores menos empolgantes para ter esse tipo de coisa, então estou bem com isso, embora outros possam discordar.

(e isso é NPM ou WAPM ou algum gerenciador de pacotes ainda criado?)

Acho que é muito cedo para o WAPM ser um gerenciador de pacotes viável. Precisamos de recursos como integração ESM, WASI e alguma forma de vinculação entre idiomas a serem padronizados antes que um gerenciador de pacotes wasm se torne viável.

Do jeito que está, não acho que o WAPM tenha gerenciamento de dependências.

Não acho que o wasm tenha uma necessidade maior de uma solução inter-linguagens do que outras plataformas

Eu acho que sim. Ser usado na web por padrão significa que o código pode e será executado em diferentes contextos. Da mesma forma que alguém pode

As conversões precisam ser implementadas no userland. A camada de ligação apenas prescreve um formato que o código do usuário deve produzir / consumir.

Esse é um bom resumo para mim.

Enquanto escrevo um novo rascunho, tenho uma pergunta em aberto para todos neste tópico:

Existe alguma biblioteca existente que, se fosse compilada para WebAssembly, você gostaria de poder usar em qualquer linguagem?

Estou procurando essencialmente casos de uso em potencial para basear o design. Eu tenho meu próprio (especificamente, React, o mecanismo Bullet e sistemas de plug-in), mas gostaria de mais exemplos para trabalhar.

@PoignardAzur Muitas linguagens em C usam as mesmas bibliotecas Perl-Compatible Regular Expression (PCRE), mas na incorporação do navegador provavelmente devem usar a API Regex do JS.

@PoignardAzur BoringSSL e libsodium vêm à mente.

Também a implementação de Cap'n Proto RPC, mas esta é estranha: a camada de _serialização_ do Cap'n Proto realisticamente deve ser implementada de forma independente em cada idioma, já que a maioria é uma camada de API ampla, mas superficial, que precisa ser idiomática e embutida -amigáveis. A camada RPC, OTOH, é estreita, mas profunda. Em princípio, deve ser possível usar a implementação C ++ RPC por trás de qualquer implementação de serialização de linguagem arbitrária, passando referências de matriz de bytes codificados por capnp sobre o limite FFI ...

Acho que fazer o que é proposto, em última análise, exigiria algumas mudanças bastante invasivas no próprio Web Assembly, pois ele já existe - mas pode valer a pena.

Eu observaria que o mundo SmallTalk teve alguma experiência positiva com esse esforço que pode ser informativo no desenvolvimento de seu protocolo de replicação de estado (SRP), que é um protocolo de serialização eficiente que pode representar qualquer tipo de qualquer tamanho de forma bastante eficiente. Pensei em torná-lo um layout de memória nativo para uma VM ou mesmo FPGA, mas ainda não tentei. Eu sei que foi portado para pelo menos um outro idioma, Squeak, com bons resultados. Certamente algo a ser lido como tendo forte sobreposição com os problemas, desafios e experiências desta proposta.

Eu entendo por que o Web IDL era a proposta padrão como linguagem de ligação: é a linguagem de ligação histórica e de alguma forma madura para a web. Apoio fortemente essa decisão e é muito provável que teria feito o mesmo. No entanto, podemos reconhecer que ele mal se encaixa em outros contextos (entenda, outros hosts / idiomas). Wasm foi projetado para ser independente de host ou de linguagem / plataforma. Gosto da ideia de usar tecnologias da Web maduras e de encontrar um caso de uso para cenários não-Web, mas no caso de IDL da Web, parece realmente ligado à Web. Razão pela qual estou acompanhando de perto essas conversas aqui.

Abri https://github.com/WebAssembly/webidl-bindings/issues/40 , o que me levou a fazer uma pergunta aqui, já que não vi nenhuma menção a isso (ou não percebi).

Em toda a história de vinculação, não está claro "quem" é o responsável por gerar as vinculações:

  • É um compilador (que transforma um programa em um módulo Wasm)?
  • É um autor do programa (e, portanto, as ligações são escritas à mão)?

Acho que ambos são válidos. E no caso do Web IDL, parece apresentar algumas limitações (veja o link acima). Talvez eu tenha perdido uma etapa importante do processo e, portanto, considere esquecer minha mensagem.

Mesmo que o objetivo seja “redirecionar” o IDL da Web para ser menos centrado na Web, no momento, ele _é_ muito centrado na Web. E surgem propostas para propor alternativas, daí este segmento. Portanto, estou preocupado com uma potencial fragmentação. Idealmente (e é assim que o Wasm foi projetado até agora), dado um módulo Wasm incluindo suas ligações, é possível executá-lo em qualquer lugar como está. Com ligações escritas em Web IDL, Cap'n 'Proto, FlatBuffers, seja o que for, tenho certeza de que nem todos os compiladores ou autores de programas escreverão as mesmas ligações em sintaxes diferentes para serem verdadeiramente multiplataforma. Curiosamente, este é um argumento a favor das ligações escritas à mão: as pessoas podem contribuir para um programa escrevendo ligações para a plataforma P. Mas vamos admitir que isso não será ideal de forma alguma.

Resumindo: estou preocupado com uma possível fragmentação entre ligações da Web e não Web. Se uma linguagem não vinculada à Web for mantida, ela seria implementada de forma realista pelos navegadores da Web? Eles teriam que escrever ligações “Wasm ⟶ binding language B ⟶ Web IDL”. Observe que este é o mesmo cenário para todos os hosts: Wasm ⟶ binding language B ⟶ host API.

Para quem está curioso, trabalho na Wasmer e sou o autor das integrações PHP- , Python- , Ruby- e Go- Wasm. Começamos a ter um belo playground para hackear diferentes vínculos para hosts muito diferentes. Se alguém quiser que eu integre soluções diferentes, para coletar feedbacks ou tentar experimentar, estamos todos abertos e prontos para colaborar e colocar mais recursos nisso.

A direção atual em 'ligações webIDL' provavelmente está longe de webIDL
em si. No entanto, o dilema é este:

A linguagem 'natural' para expressar entre módulo e módulo-host
a interoperabilidade é significativamente mais rica do que a linguagem natural do WASM. Esse
significa que qualquer equivalente de IDL utilizável vai parecer bastante arbitrário de
Pov de WASM.

Por outro lado, para as pessoas que veem o mundo pelas lentes de C / C ++
(e Rust e seus semelhantes) qualquer coisa mais rica do que o modelo WASM corre o risco de ser
inutilizável. Já podemos ver isso com a dificuldade de integração ref
tipos na cadeia de ferramentas.

Além disso, não é um mandato direto da WASM apoiar
interoperabilidade interlinguística. Nem deveria ser IMO.

(Há uma versão mais limitada de interoperabilidade entre idiomas que eu
acreditar não é apenas suportável, mas também crítico: está em todas as nossas
interesse que provedores de recursos e usuários de recursos podem atender
com o mínimo de atrito. (Capacidade é falar de gerenciamento, mas eu tenho
não encontrou um termo melhor.) Isso requer um estilo diferente de IDL e um
que é mais fácil de implementar do que seria necessário para uma inter-linguagem completa
interoperabilidade.)

Resumindo: existe um caso para ter um IDL equivalente, e precisamos dele
para oferecer suporte à interoperação entre os limites de propriedade. O que acaba
ser não está claro no momento.

Na segunda-feira, 24 de junho de 2019 às 7h02 Ivan Enderlin [email protected]
escreveu:

Eu entendo por que o Web IDL era a proposta padrão como linguagem vinculativa:
É a linguagem de ligação histórica e de alguma forma madura para a web. eu
apoio muito essa decisão, e é muito provável que eu teria feito o
mesmo. No entanto, podemos reconhecer que mal se encaixa em outros contextos
(entenda, outros hosts / idiomas). Wasm foi projetado para ser agnóstico em relação ao host,
ou agnóstico de idioma / plataforma. Gosto da ideia de usar a web madura
tecnologias e para encontrar um caso de uso para cenários não-Web, mas no caso
da Web IDL, parece realmente ligado à web. Qual é a razão pela qual sou muito
acompanhando de perto essas conversas aqui.

Abri WebAssembly / webidl-bindings # 40
https://github.com/WebAssembly/webidl-bindings/issues/40 , o que me levou
para fazer uma pergunta aqui, já que não vi nenhuma menção a ela (ou não a vi).

Em toda a história de ligação, não está claro "quem" é responsável por
gerar as ligações:

  • É um compilador (que transforma um programa em um módulo Wasm)?
  • É um autor do programa (e, portanto, as ligações são escritas à mão)?

Acho que ambos são válidos. E no caso da Web IDL, parece mostrar alguns
limitações (veja o link acima). Talvez eu tenha perdido uma etapa importante
o processo e, portanto, considere esquecer minha mensagem.

Mesmo que o objetivo seja "redirecionar" o IDL da Web para ser menos centrado na Web, agora,
é muito centrado na web. E surgem propostas para propor alternativas,
daí este tópico. Portanto, estou preocupado com uma potencial fragmentação.
Idealmente (e é assim que Wasm foi projetado até agora), dado um módulo Wasm
incluindo suas ligações, é possível executá-lo em qualquer lugar como está. Com
ligações escritas em Web IDL, Cap'n 'Proto, FlatBuffers, seja o que for, estou
quase todos os compiladores ou autores de programas escreverão da mesma forma
ligações em sintaxes diferentes para serem verdadeiramente multiplataforma. Curiosamente, este é
um argumento a favor de encadernações escritas à mão: as pessoas podem contribuir para um
programa escrevendo ligações para a plataforma P. Mas vamos admitir que isso não será
ideal em tudo.

Resumindo: estou preocupado com uma possível fragmentação entre a Web e
ligações não-Web. Se uma linguagem de ligação não-Web for mantida, seria
implementado de forma realista por navegadores da Web? Eles teriam que escrever
bindings “Wasm ⟶ binding language B ⟶ Web IDL”. Observe que este é o mesmo
cenário para todos os hosts: Wasm ⟶ binding language B ⟶ host API.

Para quem tem curiosidade, trabalho na Wasmer e sou o autor do PHP-
https://github.com/wasmerio/php-ext-wasm , Python-
https://github.com/wasmerio/python-ext-wasm , Ruby-
https://github.com/wasmerio/ruby-ext-wasm e Go-
https://github.com/wasmerio/go-ext-wasm Integrações Wasm. Nós começamos
ter um bom parquinho para hackear diferentes ligações para diferentes
hospedeiros. Se alguém quiser que eu integre soluções diferentes, para coletar
feedbacks, ou tentar experimentar, estamos todos abertos e prontos para colaborar
e colocar mais recursos nele.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUD6WA22DDUS7PYQ6F3P4DHYRA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYNA2EA#issuecomment-505023760 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAQAXUGM66AWN7ZCIVBTXVTP4DHYRANCNFSM4HJUHVGQ
.

-
Francis McCabe
ICE

Já podemos ver isso com a dificuldade de integrar tipos ref na cadeia de ferramentas.

Não falo outros idiomas, mas Rust + wasm-bindgen já tem suporte para:

Então estou curioso: a que dificuldades você está se referindo?

Meu entendimento das dificuldades está mais no lado C ++. Rust tem uma metaprogramação suficientemente poderosa para tornar isso mais razoável no final da linguagem, mas a área de usuários C ++ tem mais dificuldade em raciocinar sobre anyrefs, por exemplo.

Eu ficaria curioso para ouvir mais sobre os problemas específicos do C ++ aqui. (Eles são específicos de C ++ ou específicos de LLVM?)

C ++ não sabe o que é um tipo de referência. Então você não pode ter isso dentro de um
objeto arbitrário. Realmente não faz parte da linguagem; mais como um arquivo
descritor. Lugar engraçado para uma corda.

Na segunda-feira, 24 de junho de 2019 às 15h07, Alon Zakai [email protected] escreveu:

Eu ficaria curioso para ouvir mais sobre os problemas específicos do C ++ aqui. (São eles
Específico de C ++ ou específico de LLVM?)

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUDW237MUBBUUJLKS6LP4FARJA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYOL2ZQ#issuecomment-505199974 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAQAXUB4O3ZX4LRQSQRL763P4FARJANCNFSM4HJUHVGQ
.

-
Francis McCabe
ICE

Falando com @fgmccabe offline, é verdade que tanto o C ++ quanto o Rust não podem armazenar diretamente um tipo ref em uma estrutura, já que a estrutura será armazenada na memória linear. Tanto C ++ quanto Rust podem, é claro, lidar com tipos ref indiretamente, da mesma forma que lidam com descritores de arquivo, texturas OpenGL, etc. - com identificadores de inteiros. Eu acho que seu ponto é que nenhuma dessas duas linguagens pode lidar com tipos ref "bem" / "nativamente" (me corrija se eu estiver errado!) Com o que eu concordo - essas línguas sempre estarão em desvantagem no tipo ref operações, em comparação com as linguagens GC.

Continuo curioso para saber se há algo específico para C ++ aqui. Eu não acho que existe?

Meu entendimento sobre o que torna o C ++ difícil aqui é se você disser:

struct Anyref; // opaque declaration
void console_log(Anyref* item); // declaration of ref-taking external JS API
Anyref* document_getElementById(const char* str);

void wellBehaved() {
  // This should work
  Anyref* elem = document_getElementById("foo");
  console_log(elem);
}

void notSoWellBehaved() {
  // ????
  Anyref* elem = document_getElementById("bar");
  Anyref* what = (Anyref*)((unsigned int)elem + 1);
  console_log(what);
}

A boa notícia é que o último exemplo é UB, eu acredito (ponteiros inválidos são UB assim que são criados), mas como tentamos modelar isso no LLVM IR?

@ jgravelle-google Acho que até struct Anyref; pressupõe que é algo que faz sentido na memória linear. Em vez disso, por que não modelá-lo com um identificador de inteiro conforme mencionado anteriormente, como texturas OpenGL, identificadores de arquivo e assim por diante?

using Anyref = uint32_t; // handle declaration
void console_log(Anyref item); // declaration of ref-taking external JS API
Anyref document_getElementById(const char* str);

void wellBehaved() {
  // This should work
  Anyref elem = document_getElementById("foo");
  console_log(elem);
}

O identificador de inteiro deve ser consultado na tabela quando for usado - novamente, isso é apenas uma desvantagem das linguagens que usam memória linear como C ++ e Rust. No entanto, ele definitivamente poderia ser otimizado pelo menos localmente - se não pelo LLVM, então no nível do wasm.

Isso funcionará, mas certifique-se de chamar table_free(elem) ou manterá uma referência a ele para sempre. O que não é estranho para C ++, certo.

É um mapeamento um tanto estranho porque não cria camadas bem, eu acho? Parece uma biblioteca à la OpenGL, mas depende da mágica do compilador para fornecer - eu não acho que você pode construir um anyref.h em C ++ mesmo com um inline-wasm, se você depender de declarar um tabela.

De qualquer forma, acho que é tudo factível / tratável, mas nada simples.

@kripken É verdade que o suporte nativo "adequado" anyref exigirá algumas mudanças no LLVM (e no rustc), mas isso não é realmente um obstáculo.

wasm-bindgen armazena wasm anyref s genuíno em uma tabela wasm e, em seguida, na memória linear, ele armazena um índice inteiro na tabela. Assim, ele pode acessar anyref usando a instrução wasm table.get .

Até que o wasm-gc seja implementado, as linguagens GC precisarão usar exatamente a mesma estratégia, então Rust (et al) não está perdendo.

Então, o que o suporte nativo de anyref no LLVM nos traria? Bem, isso permitiria passar / retornar anyref diretamente de funções, em vez de precisar indiretamente de anyref por meio de uma tabela wasm. Isso seria útil, sim, mas isso é apenas uma otimização de desempenho, na verdade não evita o uso de anyref .

@Pauan

wasm-bindgen armazena wasm anyrefs genuínos em uma tabela wasm e, em seguida, na memória linear, ele armazena um índice inteiro na tabela. Portanto, ele pode acessar o anyref usando a instrução wasm table.get.

Exatamente, sim, esse é o modelo a que me referia.

Até que o wasm-gc seja implementado, as linguagens GC precisarão usar exatamente a mesma estratégia, então Rust (et al) não está perdendo.

Sim, no momento, as linguagens GC não têm vantagem porque não temos um wasm GC nativo. Mas espero que isso mude! :) Eventualmente espero que as linguagens GC tenham uma vantagem clara aqui, pelo menos se fizermos GC corretamente.

Então, o que o suporte nativo de anyref no LLVM nos traria? Bem, isso habilitaria a passagem / retorno direta de anyref de funções, em vez de precisar indiretamente de anyref por meio de uma tabela wasm. Isso seria útil, sim, mas isso é apenas uma otimização de desempenho, na verdade não impede o uso de anyref.

Concordo, sim, esta será apenas uma vantagem de desempenho das linguagens GC (eventualmente) sobre C ++ e Rust etc. Não impede o uso.

Ciclos são um problema maior para C ++ e Rust, entretanto, uma vez que as tabelas são raiz. Talvez possamos ter uma API de rastreamento ou "objetos de sombra", basicamente alguma forma de mapear a estrutura dos links do GC dentro do C ++ / Rust para que o GC externo possa entendê-los. Mas não acho que haja uma proposta real para qualquer um deles ainda.

No final, espero que as linguagens GC tenham uma vantagem clara aqui, pelo menos se fizermos GC corretamente.

Posso estar errado, mas ficaria surpreso se fosse esse o caso: as linguagens GC teriam que alocar uma estrutura GC wasm e então o mecanismo wasm teria que acompanhar isso enquanto flui pelo programa.

Em comparação, o Rust não precisa de alocação (apenas atribuir a uma tabela) e só precisa armazenar um número inteiro, e o mecanismo wasm só precisa manter o controle de 1 tabela estática imóvel para fins de GC.

Suponho que seja possível que anyref access seja otimizável para linguagens de GC, já que não precisaria usar table.get , entretanto, espero que table.get seja bem rápido.

Então, você poderia explicar mais sobre como espera que um programa wasm-gc tenha um desempenho melhor do que um programa que usa uma tabela wasm?

PS: Isso está começando a sair do assunto, então talvez devêssemos mover essa discussão para um novo tópico?

Realmente apenas isso: evitando table.get/table.set . Com o GC, você deve ter o ponteiro bruto bem ali, economizando os caminhos indiretos. Mas sim, você está certo que Rust e C ++ só precisam armazenar um número inteiro, e eles são muito rápidos no geral, então qualquer vantagem de GC pode não ter importância!

Eu concordo que podemos sair do assunto, sim. Eu acho que o que está em questão é o ponto de @fgmccabe que tipos ref não se encaixam tão naturalmente em linguagens que usam memória linear. Isso pode nos influenciar de certas maneiras com as ligações (em particular os ciclos são preocupantes porque C ++ e Rust não podem lidar com eles, mas talvez as ligações possam ignorar isso?), Então apenas algo para se ter cuidado, eu acho - ambos para tentar fazer as coisas funcionarem para o maior número possível de idiomas e não ser excessivamente influenciado pelas limitações de qualquer idioma em particular.

@kentonv

A camada de serialização do Cap'n Proto realisticamente deve ser implementada de forma independente em cada idioma, já que a maior parte dela é uma camada de API ampla, mas superficial, que precisa ser idiomática e amigável em linha. A camada RPC, OTOH, é estreita, mas profunda

Qual pasta é?

@PoignardAzur Desculpe, não entendi sua pergunta.

@kentonv Estou procurando no repositório capnproto do Github. Onde está a camada de serialização?

@PoignardAzur Então, isso volta ao meu ponto. Não há realmente um único lugar para o qual você possa apontar e dizer "essa é a camada de serialização". Principalmente, a "serialização" do Cap'n Proto é apenas aritmética de ponteiro em torno de carregamentos / armazenamentos para um buffer subjacente. Dado um arquivo de esquema, você usa o gerador de código para gerar um arquivo de cabeçalho que define métodos sequenciais que fazem a aritmética de ponteiro certo para os campos específicos definidos no esquema. O código do aplicativo precisa então chamar esses acessadores gerados sempre que ler ou gravar qualquer campo.

É por isso que não faria sentido tentar chamar uma implementação escrita em uma linguagem diferente. Usar um FFI bruto para cada acesso de campo seria extremamente complicado, então, sem dúvida, você acabaria escrevendo um gerador de código que envolve o FFI em algo mais bonito (e específico para o seu esquema). Mas esse código gerado seria pelo menos tão complicado quanto o código que o Cap'n Proto já implementa - provavelmente mais complicado (e muito mais lento!). Portanto, faz mais sentido apenas escrever um gerador de código diretamente para o idioma de destino.

Talvez haja algumas funções auxiliares internas na implementação do Cap'n Proto que podem ser compartilhadas. Especificamente, layout.c++ / layout.h contém todo o código que interpreta a codificação do ponteiro do capnp, executa a verificação de limites, etc. Os acessadores de código gerados chamam esse código ao ler / escrever campos de ponteiro. Então, eu poderia imaginar envolver essa parte em um FFI para ser chamado de vários idiomas; mas ainda espero escrever geradores de código e alguma quantidade de biblioteca de suporte de tempo de execução em cada idioma de destino.

Sim, desculpe, eu quis dizer o oposto ^^ (a camada RPC)

@PoignardAzur Ohhh, e suponho que você esteja especificamente interessado em examinar as interfaces, já que está pensando em como envolvê-las em um FFI. Então você quer:

  • capability.h : interfaces abstratas para representar capacidades e invocação RPC, que em teoria poderiam ser apoiadas por uma variedade de implementações. (Esta é a parte mais importante.)
  • rpc.h : Implementação de RPC em uma rede.
  • rpc-twoparty.h : Adaptador de transporte para RPC em uma conexão simples.

Esta proposta foi substituída por # 1291: ligações OCAP.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

cretz picture cretz  ·  5Comentários

artem-v-shamsutdinov picture artem-v-shamsutdinov  ·  6Comentários

konsoletyper picture konsoletyper  ·  6Comentários

thysultan picture thysultan  ·  4Comentários

ghost picture ghost  ·  7Comentários