Design: Desautorizar importações duplicadas?

Criado em 3 mar. 2021  ·  44Comentários  ·  Fonte: WebAssembly/design

(Esta edição captura a apresentação no CG-03-02 , com o objetivo de obter mais feedback na preparação para uma votação em alguma reunião futura do CG.)

A proposta do Módulo Linking propõe a reformulação das importações de dois níveis como açúcar para as importações de um único nível de instâncias . Por exemplo:

(module
  (import "a" "foo" (func))
  (import "a" "bar" (func))
)

seria dessugared ao mesmo AST como:

(module
  (import "a" (instance
    (export "foo" (func))
    (export "bar" (func)))
)

Um problema com isso é que atualmente o wasm permite importações duplicadas (com assinaturas iguais ou diferentes):

(module
  (import "a" "b" (func))
  (import "a" "b" (func))
)

enquanto as exportações duplicadas não são permitidas. Assim, dessugar o acima para:

(module
  (import "a" (instance
    (export "b" (func))
    (export "b" (func)))
)

não seria válido. module-linking / # 7 discute várias maneiras de reconciliar essa discrepância, mas a solução mais simples seria proibir retroativamente as importações duplicadas.

Bug 1647791 adicionou telemetria para medir importações duplicadas no Firefox, e os resultados para Firefox Beta 86 mostram 0,00014% dos carregamentos de página contêm importações duplicadas (que podem ser apenas testes de unidade em automação ou outras fontes sintéticas). Portanto, parece que essa é uma alteração importante que poderíamos fazer na prática.

Outro motivo de princípio para não permitir importações duplicadas é que importações duplicadas com assinaturas diferentes só podem ser implementadas pelo host, o que quebra o objetivo geral de permitir que o wasm sempre seja capaz de virtualizar (polyfill, mock, etc.) importações wasm.

A experiência concreta é que o conjunto de testes de especificações costumava ter importações duplicadas (para diferentes "sobrecargas" da função de impressão), mas isso foi removido porque causou problemas para as pessoas que queriam executar os testes.

Assim, pode-se argumentar que esta restrição é geralmente boa para o ecossistema wasm, mesmo deixando de lado as preocupações da proposta de Módulo Linking.

Comentários muito úteis

Importações duplicadas são bastante úteis para chamar importações de linguagens dinâmicas como JS, onde a mesma função pode ter várias assinaturas, e eu espero que seja usada um pouco mais do que menos quando o sistema de tipos na fronteira se tornar mais rico, digamos que se possa chamar a mesma função com um número ou um argumento de string, ou com um ou dois argumentos. Acho que remover esse recurso seria muito ruim para a interoperabilidade com JS.

Todos 44 comentários

Que coisas ruins aconteceriam na proposta de vinculação de módulo se não fizéssemos essa alteração? Significaria apenas que essa remoção não funcionaria bem e a descrição das especificações das importações seria, portanto, mais complicada? Ou iria interferir com a funcionalidade da proposta de vinculação de módulo mais fundamentalmente? (Não me oponho a essa mudança, estou apenas tentando entender melhor a motivação.)

Pelo que me lembro da apresentação de Luke, atualmente não permitimos exportações duplicadas.

E se suspendermos essa restrição e permitirmos exportações duplicadas? Isso abre o espaço de design para a vinculação de módulo para resolver esse problema de outra maneira.

Atualmente, os mecanismos só precisam inspecionar nomes de importação / exportação por dois motivos: boa formação de UTF-8 e verificação de exportação duplicada. Falamos na linha off-line sobre o levantamento da restrição de UTF-8, mas talvez pudéssemos suspender os dois.

Exportações duplicadas tornariam a vinculação baseada em nome ambígua.

@tlively Se não module-linking / # 7 parecem que adicionariam uma complexidade significativa à implementação da validação e vinculação, especialmente em torno de perguntar se um determinado módulo A na instanciação tempo é aceitável para importação do módulo B.

@titzer Acho que adicionar exportações duplicadas acabaria criando um monte de questões difíceis para cada integração de idioma de origem. Por exemplo, na API JS: uma exportação duplicada aparece como um único objeto de função JS que faz a resolução de sobrecarga dinâmica? O Web IDL faz isso e é bastante complicado . O que acontece se eu tentar importar esta função JS do wasm (via WebAssembly.instantiate )? Não estou dizendo que é um problema de design impossível, mas se houver uma opção para evitar essas questões difíceis / confusas por completo para integrações de linguagem N, isso parece preferível.

Importações duplicadas são bastante úteis para chamar importações de linguagens dinâmicas como JS, onde a mesma função pode ter várias assinaturas, e eu espero que seja usada um pouco mais do que menos quando o sistema de tipos na fronteira se tornar mais rico, digamos que se possa chamar a mesma função com um número ou um argumento de string, ou com um ou dois argumentos. Acho que remover esse recurso seria muito ruim para a interoperabilidade com JS.

@lukewagner , pode ser uma má ideia devido à minha falta de familiaridade com a proposta de vinculação de módulo, mas outra opção não seria manter as importações de MVP e as importações de instância distintas? Se eles não fossem unificados, as importações de MVP ainda poderiam ser duplicadas e não haveria necessidade de tipos de interseção complicados.

Importações duplicadas são bastante úteis para chamar importações de linguagens dinâmicas como JS, onde a mesma função pode ter várias assinaturas, e eu espero que seja usada um pouco mais do que menos quando o sistema de tipos na fronteira se tornar mais rico, digamos que se possa chamar a mesma função com um número ou um argumento de string, ou com um ou dois argumentos. Acho que remover esse recurso seria muito ruim para a interoperabilidade com JS.

Esse. Mil vezes isso. Sou o desenvolvedor principal em um projeto que fornece uma estrutura de teste para desenvolvedores de assemblyscript e vejo isso como provavelmente uma das partes mais importantes da web assembly no momento.

O 0,00014% inclui as- pect , o @jtenner , que é uma peça crítica do crescente ecossistema do AssemblyScript.

Importações duplicadas são bastante úteis para chamar importações de linguagens dinâmicas como JS, onde a mesma função pode ter várias assinaturas,

Se houver pelo menos uma alternativa para isso, será ótimo, porque a origem do WebAssembly está em navegadores com JavaScript. Vamos manter as coisas fáceis para esse alvo.

@tlively Essa é uma das opções alternativas possíveis, sim, mas tem a infeliz consequência de ter duas maneiras de fazer quase a mesma coisa, o que em geral é algo que é bom evitar e também cria mais casos para a implementação se preocupar durante a validação, verificação de subtipo e instanciação.

@jtenner @trusktr Para ser claro, você ainda pode importar a mesma função JS várias vezes, a única diferença é que cada assinatura distinta com a qual você importou precisaria de um nome diferente. Então, por exemplo, em vez de emitir:

(module
  (import "env" "log" (func (param i32)))
  (import "env" "log" (func (param f32)))
  (import "env" "log" (func (param i64)))
  (import "env" "log" (func (param f64)))
  ...
)

instanciado via:

WebAssembly.instantiate(module, { env: { log:log } });

você pode emitir:

(module
  (import "env" "log_i32" (func (param i32)))
  (import "env" "log_f32" (func (param f32)))
  (import "env" "log_i64" (func (param i64)))
  (import "env" "log_f64" (func (param f64)))
  ...
)

instanciado com:

WebAssembly.instantiate(module, { env: { log_i32:log, log_f32:log, log_i64:log, log_f64:log } })

Essa é uma mudança possível que você poderia fazer ou seria proibitiva de alguma forma?

Essa é uma mudança possível que você poderia fazer ou seria proibitiva de alguma forma?

Proibitivo? Não. Se tudo o que eu preciso fazer é alterar o nome da função à qual estou vinculando, tudo bem. Irritante, mas ótimo. Só quero ter certeza de que sou bem ouvido.

Vamos manter as coisas fáceis para esse alvo.

Os desenvolvedores de JavaScript serão os mais afetados por essa mudança. E uma vez que somos "web assembly", devemos ter muito cuidado como tratamos os desenvolvedores da web em relação a problemas futuros como este.

Eu sou a favor da mudança, se isso tornar as coisas melhores. Portanto, tenho as seguintes perguntas em ordem do que considero o mais importante para o menos:

  1. Isso está tornando a web um lugar melhor para o desenvolvimento?

Tão longe quanto eu consigo ver? Pode ser. Isso força os desenvolvedores a serem mais explícitos sobre as importações e desencoraja os desenvolvedores de JavaScript a fazer coisas menos javascripty. Talvez mais restrições levem a mais criatividade. Uma conversa que parece nunca ter acontecido é como isso afeta os desenvolvedores C # e Java. Eu acredito que isso pode afetar negativamente os desenvolvedores de C # que escrevem compiladores de assembly da Web também, mas talvez essa complexidade não seja tão ruim.

  1. Isso torna o WebAssembly mais fácil de implementar, manter e executar mais rápido?

Eu não sei a resposta para isso. Um desenvolvedor de compilador seria mais versado nesse tipo de coisa. Meu palpite é que é mais conveniente para desenvolvedores que tentam manter os motores wasm. E sim, seria mais rápido, ou pelo menos seria mais explícito, mais fácil de validar o estado do módulo.

  1. Essa mudança impactará negativamente os desenvolvedores que contam com o comportamento anteriormente inalterado?

Até onde eu posso dizer, as poucas pessoas que estão usando esse recurso, seu material irá quebrar e eles ficarão infelizes por ter que voltar e tentar descobrir como consertar seu software.

Depois de manter o software de estrutura de teste para AssemblyScript, percebi que pequenas mudanças como essa não afetam os desenvolvedores finais típicos. Essas mudanças afetam as pessoas que mantêm software de ecossistema, ferramentas de desenvolvimento e compiladores. Mudanças sutis na especificação como essa podem resultar em solicitações pull muito grandes que levam dias ou semanas para serem processadas.

Minha opinião final: não gosto dessa mudança, mas se você vai fazer a mudança, tire o band-aid. Comece a reação em cadeia agora e faça valer a pena.

Eu vejo mais uma desvantagem disso:

(module
  (import "env" "log_i32_f32_f64" (func (param i32 f32 f64)))
  (import "env" "log_f32_i32_f64" (func (param f32 i32 f64)))
  ...
)

ele pode potencialmente aumentar o tamanho do binário quando você deve manter os nomes de importação originais (para demangling) e não pode aplicar o passe de binaryen que compacta os nomes de importação.

No entanto, não é um problema muito grande para o caso geral

O principal problema que vejo é que um usuário tem que saber todos os arities e tipos de argumento de antemão para desfigurar manualmente o objeto de importações, enquanto atualmente um compilador pode assumir que por exemplo Math é importado como um todo e gerar importações com o mesmo nome, mas diferentes aridades ou tipos de argumento ao encontrar chamadas de importação durante a compilação. Um exemplo simples aqui é Math.max , um pouco mais complexos são os typed Array ctors com assinaturas totalmente diferentes , e ele só se torna mais complexo com, por exemplo, APIs Node.js, que muitas vezes permitem omitir argumentos no meio de coisas ao mesmo tempo que permite tipos totalmente diferentes e, em última análise, código do usuário, é claro, sobre o qual nunca se sabe ao certo. E, claro, sempre que algo relacionado nas fontes do módulo Wasm muda, também será necessário revisitar o objeto de importações novamente. Isso seria muito triste.

Acho que o que não consegui expressar na reunião de CG é que acho que a proposta de vinculação de módulo está tentando empurrar para baixo os requisitos da vinculação baseada em nome para o mecanismo e parece natural para mim que haja uma camada aqui para que esses requisitos não seja empurrado para baixo. Por exemplo, quando restringimos o formato binário do core wasm, estamos declarando que os mecanismos devem verificar e rejeitar módulos que não estejam em conformidade. Para segurança de tipo, limitações de tamanho razoáveis ​​e outras coisas que importam para um motor, isso faz todo o sentido.

Eu argumentaria que os nomes não importam para os motores; eles só importam para hospedar embeddings, e os engines realmente não deveriam precisar olhar (muito menos rejeitar) nomes. Isso é evidenciado na situação de sobrecarga de JS que outros comentaristas mencionaram.

Meu comentário sobre o UTF-8 foi nessa direção; um mecanismo literalmente não precisa de processamento UTF-8, exceto para rejeitar nomes ruins; mesmo para exportações duplicadas. Isso tinha um cheiro ainda mais engraçado de fazer o Wizard, pois de repente tive que implementar um validador UTF-8 do zero ... hmm. Além disso, a verificação de exportação duplicada é o único lugar onde o Assistente usa uma tabela de hash. Não que uma centena ou mais de linhas de código seja uma crise, mas é um pato estranho em um exercício de VM orientado para computação.

Eu entendo que os objetivos da proposta de vinculação de módulo são fornecer uma solução de vinculação baseada em nome padrão para Wasm, mas por uma série de razões eu acho que há algum risco de não funcionar de uma maneira geral o suficiente para que devemos recorrer à proibição de coisas que estão se aperfeiçoando muito bem do ponto de vista do motor, no nível do motor.

Portanto, mantenho minha sugestão anterior de que deveríamos ser mais liberais e talvez levantar a restrição às exportações duplicadas.

Se a proposta de vinculação de módulo deseja rejeitar módulos agora que não podem ser vinculados devido a problemas de nomenclatura, isso parece perfeitamente adequado como uma restrição de camada acima do núcleo wasm. Mas, como eu disse, existe o risco de que a proposta de vinculação de módulo não resolva os problemas de vinculação para muitas linguagens de programação e, em vez disso, precisaremos de uma solução mais programável que envolva uma API para módulos de primeira classe. Mas essa é provavelmente uma conversa mais longa e esta é uma questão bastante restrita. Esse é o contexto do meu pensamento aqui.

Ah, @titzer interessante. Tenho tido pensamentos semelhantes. Resumindo, a compilação e a vinculação estão conectadas, mas ainda distintas, e esse é um problema que eles estão intimamente ligados.

Poderíamos imaginar todos os aspectos de links relacionados do WebAssembly e colocá-los em uma seção separada. Uma seção de "compilação" ainda teria importações, mas elas não têm nome (além de seu índice) - apenas lacunas que uma seção de "ligação" cuidará de preencher. Uma seção de "compilação" ainda pode ter exportações, mas, novamente, elas não têm nome e uma seção de "vinculação" é responsável por fornecer os nomes.

As seções de link, então, poderiam ter conteúdo específico do incorporador. Por exemplo, uma seção ligando para tipos de interface poderia fornecer adapter_func s, ao passo que uma seção ligando para JS poderia instalar rtt dicionários como em @tebbi 's WebAssembly / gc # 132 em ambos os casos, esse conteúdo adicional só faz sentido para esse incorporador específico e é extremamente útil para esse incorporador específico. A proposta de tipos de módulo é essencialmente outro incorporador de vinculação, e parte da tensão é que o que faz mais sentido lá (por exemplo, nomes únicos para resolução de link simples) não é o que faz mais sentido em JS (por exemplo, nomes sobrecarregados).

Adendo: Isso pode ser útil para apoiar o jitting. Os módulos desejam que suas funções possam ser chamadas diretamente a partir de módulos adicionais que eles geram dinamicamente, sem também precisar expor essas funções ao sistema de vinculação incorporado.

Como @titzer disse, isso também está além do escopo desta discussão, mas eu queria misturar meu pensamento com a ideia também.

@dcodeIO Em todos os cenários que vi, o código JS que instancia um módulo wasm é gerado pela mesma cadeia de ferramentas que gera o módulo wasm (a instanciação b / c em geral tende a ter um monte de código cola necessário), portanto, eu não estou ciente de que este detalhe impl iria borbulhar para o usuário. Mas você viu de forma diferente?

Uma coisa que eu gostaria de esclarecer e enfatizar: de um POV de ecossistema puramente JS, as importações duplicadas são um perigo porque a API JS e a integração ESM não têm como conectar duas instâncias wasm A e B onde B tem uma importação duplicada que queremos satisfazer com A. A pode ser capaz de exportar duas funções com assinaturas adequadas, mas B só será capaz de importar 1 delas (com uma assinatura, que em geral não será capaz de satisfazer ambas as importações).

@titzer Eu entendo o desejo de manter os nomes fora do wasm central, mas para realizar seu objetivo, em vez de importar um módulo como proposto atualmente:

(module
  (import "other" (module
    (import "foo" (func))
    (export "bar" (func))
  ))
)

em vez disso, teríamos que remover "foo" e "bar" do tipo de importação, deixando algo puramente posicional:

(module
  (import "other" (module
    (import (func))
    (export (func))
  ))
)

Se aguardarmos cenários de compilação separada (por exemplo, fatorar bibliotecas compartilhadas em módulos que são amplamente reutilizados), isso levaria a uma versão wasm do antigo problema de base frágil do C ++, pois dependeríamos inteiramente da ordem posicional; é isso que os nomes de importação / exportação corrigem. É isso que você está propondo? (Observe que as referências de módulo de primeira classe e / ou instâncias / instanciação de primeira classe têm o mesmo problema; é ortogonal.)

Mas se não é

Não, não estou propondo vinculação posicional. Concordo que isso seria muito frágil.

Mas refletindo sobre o caso de uso do Jawa, e também sobre os tipos de interface, o que se destaca é a necessidade de realmente analisar e aplicar lógica às importações. Em Jawa, os nomes de importação não são strings, mas, na verdade, expressões em uma linguagem e, com argumentos de importação, eles podem assumir tipos e funções como importações. Não há solução wasm pura para vinculação tardia com Java ou qualquer linguagem de alto nível IMHO. Uma solução de vinculação baseada em nome de baixo nível pode servir a um ecossistema wasm de nicho construído com um conjunto muito limitado de linguagens, mas não resolve o caso de uso de linguagens de nível superior a longo prazo. Portanto, a IMO está construindo um mecanismo para permitir a criação de um ecossistema novo e muito limitado, ao invés de adicionar um mecanismo geral o suficiente para construir a ligação em um nível superior.

Daí meus comentários sobre os módulos de primeira classe. No longo prazo, vamos precisar de uma reflexão e JITing API que permitirá uma vinculação totalmente programável e um sistema de correspondência baseado em nomes muito rígido que realmente não resolva os problemas reais de nenhuma linguagem ou lhes dê corda suficiente.

E a correspondência baseada no nome também é muito frágil. Como ainda não temos nenhum mecanismo de parametrização para importações, as linguagens precisam recorrer a nomes mutilados, que são feios e frágeis, e não podem suportar nem mesmo coisas simples como subtipagem e sobrecarga.

Desculpe pelos comentários estendidos; na verdade, estamos discutindo uma questão maior agora e provavelmente não deveria ser tão restrita como aqui.

Em todos os cenários que vi, o código JS que instancia um módulo wasm é gerado pela mesma cadeia de ferramentas que gera o módulo wasm (instanciação b / c em geral tende a ter um monte de código de cola necessário)

O AssemblyScript, por exemplo, não gera código de adesão específico do módulo, mas tenta evitar isso. Ele apenas emite definições de tipo (para fins de desenvolvimento) para uso com uma pequena biblioteca JS complementar de uso geral (pode desmontar nomes de exportação de nível superior para um uso mais conveniente do JS) que chamamos apenas de "carregador", mas usá-lo geralmente é opcional , especialmente ao usar AS principalmente como uma sintaxe um pouco mais agradável para produzir um módulo Wasm mínimo, digamos para um algoritmo de compressão (apenas exportação de função semelhante a C, por exemplo), ou ao compilar para WASI. Minha esperança é de que não precisaremos mais alterar nomes, como MyClass#myMethod , mas não tenho certeza se isso está relacionado a esse problema.

as importações duplicadas são um perigo porque a API JS e a integração ESM não têm como conectar duas instâncias wasm A e B, onde B tem uma importação duplicada que queremos satisfazer com A

Eu teria esperado que este caso produzisse um erro em algum lugar no caminho, sim, provavelmente durante a vinculação de uma exportação Wasm a uma importação Wasm com uma incompatibilidade de assinatura que não pode ser satisfeita, embora resultasse em um comportamento muito divertido em tempo de execução ao usar uma assinatura incompatível com uma importação JS ou uma importação de qualquer outra linguagem dinâmica.

Não está claro para mim por que é necessário usar o mesmo nome para importar várias funções Wasm com assinaturas diferentes, uma vez que as funções Wasm têm assinaturas fixas (ao contrário das funções de host JS, que coagem todos os argumentos e retornam tipos para a assinatura fornecida). Eu entendi mal a premissa?

Parece que seria perfeitamente normal que esse cenário apresentasse um erro se a importação fornecida fosse uma função Wasm, a menos que o padrão fosse estendido para permitir a especificação de funções com assinaturas diferentes, invocando também a coerção automática.

Embora eu tenha sido cauteloso originalmente quanto à remoção de importações duplicadas, agora apóio fortemente essa mudança.

A observação crucial é que as importações duplicadas não podem ser tratadas em vários ambientes, como @lukewagner aponta. E, como ele diz, isso é problemático até mesmo para casos de uso de JS.

Mais importante ainda, ele quebra uma propriedade modular básica, ou seja, que todas as importações podem ser igualmente implementadas por Wasm ou por módulos de host. Isso é importante para casos de uso como virtualização.

As importações duplicadas efetivamente forçam uma sobrecarga ou um mecanismo de tipagem dinâmica no lado do cliente, o que é ruim, porque nem todo cliente tem meios para oferecer suporte a isso (incluindo outros módulos Wasm).

E este é um problema real na prática, como a história da suíte de teste demonstrou.

@titzer ,

Como eu disse, os nomes não importam para os motores, então sua singularidade também não importa para os motores. Eles são importantes para _linkers_, portanto, é uma escolha de solução de vinculação particular se ele deseja impor exclusividade em nomes de importação (ou nomes de exportação para esse assunto). E os linkers no limite exigirão uma solução totalmente programável no final, que não é apenas módulos e instâncias de primeira classe, mas JITing e a capacidade de construí-los de forma incremental.

Acho que todo o problema está em tentar transformar módulos em functores com tipo estático [*], de importações a exportações baseadas em nomes. O efeito disso é que os nomes se tornam parte de um tipo, e a introdução desse tipo na linguagem do wasm requer verificação de tipos e, portanto, regras de correspondência de nomes. Mas o problema com isso é que esses nomes são nomes de baixo nível, semelhantes a nomes C / C ++ mutilados. O resultado final disso são três coisas ruins:

  1. já vimos: o wasm não pode nem mesmo se conectar a ambientes de host que têm sobrecarga
  2. a vinculação é constantemente restringida pelo que pode e não pode ser expresso usando apenas nomes de baixo nível, levando a um problema de design constante com cada novo recurso do wasm
  3. não serve línguas que não podem usar qualquer esquema de nome do nível de deturpação baixa, o que vai precisar de uma solução programável e JITing.

Esses problemas decorrem diretamente do desejo de introduzir um tipo de módulo estático, porque isso é motivado pelos motores a fazerem correspondência baseada em nomes para uma solução de link simples. Mas, na realidade, os motores nunca devem olhar para os nomes; outra coisa - um vinculador - deve olhar os nomes e resolvê-los para um mecanismo. E sim, uma maneira de resolver esse problema é que essa API fornece vetores de importações para um vinculador e retorna um vetor de exportações - foi isso que fiz no Wizard e é assim que a API de incorporação C funciona.

[ ] Eu também costumava pensar assim, que os módulos wasm eram funções de importação para exportação. Mas está faltando algo importante: a lógica de vinculação que vem de uma linguagem de nível superior.

@titzer , concordo que a proposta de vinculação de módulo não fornece um modelo de vinculação poderoso o suficiente para muitos casos de uso (não será capaz de substituir arquivos de objeto C, por exemplo), mas não acho que fornecer uma vinculação de propósito geral modelo é um objetivo da proposta. Eu vejo isso como uma extensão do sistema atual de importação / exportação baseado em string que é suficiente e útil para vinculação de estilo bundler que é comum na Web e que já está embutido na estrutura de módulo do Wasm até certo ponto. Os vinculadores específicos de linguagem de nível superior podem continuar a fazer o que quiserem com relação à nomenclatura porque sua modularidade deve ser colocada em camadas sobre o WebAssembly de qualquer maneira.

@titzer Se os nomes de importações e exportações não fazem parte de um tipo de módulo, como você propõe evitar o problema de fragilidade que disse que também queria evitar?

Também não vejo como você está tratando do problema de virtualização. Lendo o comentário de @rossberg , percebo que a virtualização é o problema preexistente mais fundamental a ser resolvido aqui e, na verdade, não tem nada a ver com a proposta do Module Linking.

Eu simpatizo com a mudança, mas olhando para WebAssembly / module-linking # 7, não parece haver muitas opções consideradas. Há muita discussão sobre os tipos de interseção, mas essa discussão não menciona que as transformações usando tipos de interseção não funcionam para sistemas de vinculação coercitivos, como a API JS, que é o que alguns dos casos de uso que as pessoas mencionaram aqui dependem. Todas as opções consideradas parecem ser baseadas em construções de módulo de linguagem de superfície (principalmente com base no conhecimento de Andreas de módulos para linguagens funcionais específicas), mas não parece haver muita consideração para construções de módulo de baixo nível. Um sistema de módulo de baixo nível facilmente tornaria a questão discutível, ao mesmo tempo que atendia aos objetivos da proposta de vinculação de módulo (incluindo resolução baseada em nome). Ao mesmo tempo, seria mais fácil desenvolver uma série de extensões importantes.

@RossTate , não tenho certeza do que você está se referindo como "sistema de módulo de baixo nível" aqui ou como é diferente do que já foi discutido. Se você tiver ideias para soluções diferentes, talvez elas possam ser discutidas em https://github.com/WebAssembly/module-linking/issues/7 para manter esse problema focado na solução proposta específica de não permitir importações duplicadas.

Deixei uma nota em https://github.com/WebAssembly/module-linking/issues/7#issuecomment -791063260 perguntando se isso pode simplesmente ser parte da validação de vinculação do módulo sem causar problemas de compatibilidade com versões anteriores para o código existente que pode estar na selva.

Este é exatamente o problema que demorei tanto para enfrentar. Os módulos não têm tipos sem considerar o processador de importação que dá significado aos nomes e restrições associados às importações. É como perguntar qual é o tipo de um arquivo de texto. É claro que um arquivo de texto pode conter texto escrito em qualquer idioma, então essa pergunta não faz sentido sem contexto. Quando corrigimos a linguagem, podemos até começar a falar sobre os tipos, se houver, em um arquivo. É aqui novamente o mesmo com os módulos. Módulos não são functores, isto é, funções puras de importações (com nomes) para suas exportações. Eles requerem um sistema de ligação para dar significado às importações. Até passarmos desse ponto, nada do resto do que estou dizendo faz sentido. Acho que é uma suposição muito básica na proposta de vinculação de módulo que nem mesmo foi colocada em discussão. E eu sinto que não estamos realmente questionando isso. Parece que estou me repetindo porque, na verdade, estou questionando as coisas mais profundamente do que parece educado. Não quero ser indelicado, é claro, mas quero questionar que os módulos precisam ser parecidos com functores. E não estamos nem questionando isso.

Posso dar exemplo após exemplo de por que claramente não é o caso de módulo serem functores.

  1. Vincular módulos wasm a JavaScript passa por um objeto de importações - um objeto de importações que pode na verdade ser um proxy ou de outra forma ter comportamento para aplicar a lógica Turing-completa a nomes (por exemplo, decodificá-los), e até mesmo tem estado, o que torna impossível lançado como um functor. É literalmente definido operacionalmente como um algoritmo que interroga uma importação de objeto JavaScript por importação. Todo esse ecossistema de vinculação não pode ser expresso com a vinculação de módulos.

  2. A API de incorporação C é da mesma maneira. Ele fornece um módulo para o incorporador como uma entidade totalmente inspecionável, completa com nomes, tipos e assinaturas para inspeção. Para instanciar um módulo, um incorporador pode aplicar qualquer lógica que desejar, desde que simplesmente forneçam ligações ao mecanismo (sim, de forma posicional) para instanciação. Além disso, um ecossistema que é totalmente programável e não expresso com vinculação de módulo.

  3. Jawa e Pywa também são assim. Jawa é totalmente digitado estaticamente, mas suas importações são codificadas na linguagem de importação de uma forma em que a correspondência simples de nomes não é adequada; o tempo de execução Jawa deve sintetizar (JIT) novas funções, tipos e outras ligações para fornecer ao mecanismo. Pywa é totalmente tipado dinamicamente e a implementação JS dele usa sobrecarga de maneiras semelhantes.

Módulos e suas importações só fazem sentido no contexto do esquema de vinculação, da linguagem, do processador de importação, seja como você quiser chamá-lo. Novamente, eles não são functores. Eles não têm tipos desprovidos do contexto em que se encontram. As importações de módulos são descrições do que precisam do mundo externo, escritas em um idioma estrangeiro que não é, e não pode ser, compreendido pelo motor. Eles só têm tipos Wasm, de modo que o próprio módulo pode ser verificado separadamente, independentemente de suas importações, de acordo com o sistema de tipos do Wasm, e que as importações podem ser verificadas em relação às suposições explicitadas nas restrições de importação. Novamente, nada sobre nomes aparece aqui até começarmos a tentar transformar os módulos em um functor do tipo wasm-y. Mas esses nomes são roxos! É aí que todas as coisas do idioma de origem sobrevivem. É claro que se atrapalhar com uma comparação de strings não vai funcionar.

Com isso em mente, o desejo de fazer uma solução de vinculação "sem enfeites" não é ruim. Isso faz sentido quando os nomes não são muito loucos. É como criar um pequeno ecossistema onde os nomes são muito simples e a correspondência simples é suficiente. Ele simplesmente não precisa ser primitivo, porque não é o primitivo certo. Isso deve ficar claro porque está embutindo suas suposições diretamente no núcleo do wasm de uma forma que corre o risco de ser um obstáculo, ou uma verruga estranha, quando chegamos ao que é realmente necessário, que é uma API de vinculação totalmente programável. Se tivéssemos a API programável para vinculação, a proposta de vinculação de módulo poderia ser apenas uma biblioteca. Portanto, devemos pensar em como tornar a API de incorporação C em mais uma API wasm padrão e, em seguida, escrever a vinculação de módulo como uma biblioteca em relação a essa API.

@lukewagner O problema da "fragilidade": observe que não estou defendendo o descarte de nomes. Os nomes são obviamente necessários para o sistema de vinculação. Mas eles não são necessários para o motor. Para a API entre o mecanismo e o vinculador, quero dizer apenas que o mecanismo pode passar as importações para o vinculador como um vetor ordenado, o vinculador pode resolver (potencialmente um subconjunto deles) e retorná-los posicionalmente. Os próprios módulos não precisam depender de posições.

E sim, para este problema, há um caso de uso excelente para importações duplicadas que venho pensando há um tempo, e sim importações duplicadas até seus argumentos: compartilhamento (ou não) de caches embutidos. Se fôssemos, por exemplo, importar uma função que executa um acesso de propriedade JavaScript, poderíamos dar a ela o nome de "GetProperty [foo]". Se importarmos apenas uma vez, qual cache inline o mecanismo deve usar? Deve inspecionar o código wasm para gerar um novo site de chamada para eles? Bem, se um módulo pode importar a mesma função várias vezes com exatamente o mesmo nome, mas obter um novo IC a cada vez, então um módulo tem uma medida de controle sobre como o mecanismo se adapta a seus vários polimorfismos. (Claro, podemos sempre colocar algum lixo de uniquificação bobo no final da string, mas por que se preocupar? É apenas contornar uma limitação de uma coisa totalmente diferente que não estamos usando no wasm.

@titzer A visão que você está apresentando é um desvio bastante significativo de como o wasm é especificado, implementado e amplamente falado hoje, então acho que é sua responsabilidade propor essa mudança de perspectiva ao CG e conseguir alguma adesão - Não acho que seja algo que você possa afirmar como sendo a natureza do wasm. Eu entendo que é isso que você quer do wasm para o projeto JAWA; talvez, em vez disso, o que você queira é propor algum novo empacotamento alternativo de instruções wasm em alguma nova unidade que corresponda às suas necessidades, mas não seja um "módulo" - o que você está falando parece mais um "modelo" ou "macro" ou "planta" ou algo assim.

Independentemente, o que aprendi como a questão raiz permanece: os módulos do wasm devem ser capazes de virtualizar as exportações do wasm? Deixando completamente de lado a questão de "como os módulos wasm se unem?", Essa parece uma questão básica de princípios a ser apresentada ao CG. Eu me pergunto se talvez mereça um problema de design separado para enfocar apenas nesta questão, independente de questões sobre vinculação.

A virtualização é importante e os links programáveis ​​certamente devem oferecer suporte a isso.

Na verdade, não acho que o que estou falando seja muito diferente do wasm de hoje, e o que estou falando não é apenas voltado para Jawa. A mesma coisa surge quando se pensa em vincular .NET ou outros tempos de execução de linguagem. Dei três exemplos acima, dois dos quais são embeddings existentes que, por estarem embutidos em outra linguagem, fornecem programação total.

Estou perfeitamente disposto a montar uma apresentação para o CG.

Fwiw, pelo meu entendimento das sugestões de Titzer para tornar o Wasm viável para mais casos de uso, em vez de menos, está muito mais de acordo com minha expectativa do que a alternativa. Eu chegaria ao ponto de sugerir que uma discussão mais geral sobre a direção de Wasm pode ser necessária, dada toda a controvérsia que já ocorreu em questões semelhantes por razões semelhantes em outros lugares.

Por exemplo, encontro uma declaração como

A visão que você está apresentando é um afastamento bastante significativo de como o wasm é especificado, implementado e amplamente falado hoje, então acho que é sua responsabilidade propor essa mudança de perspectiva ao CG e conseguir alguma adesão - eu não não acho que seja algo que você possa afirmar como sendo a natureza do wasm.

profundamente preocupante no contexto. Pode haver um problema mais amplo aqui em como o wasm é especificado, e não está claro para mim de que lado está se afastando.

@titzer Eu estava me referindo principalmente a esta parte do que você estava dizendo (e todos os corolários):

Os módulos não têm tipos sem considerar o processador de importação que dá significado aos nomes e restrições associados às importações. É como perguntar qual é o tipo de um arquivo de texto.

o que seria um desvio significativo da especificação atual (por exemplo, esta parte ).

@lukewagner Obrigado por me indicar isso.

Nessa formulação (como eu acho que a discussão sobre o módulo de vinculação apontou), as importações e as exportações são vetores, portanto, posicionais. Para vincular as exportações (posicionais) de um módulo às importações (posicionais) de outro módulo, você precisa de uma função de mapeamento. Estou apenas apontando que a função exata de correspondência de nome "sem enfeites" é apenas uma escolha e já temos dois outros embeddings onde essa função de mapeamento é totalmente programável em uma linguagem host.

@titzer Isso é verdade, mas observe: apesar do fato de que a API JS é totalmente programável, se eu der a você um módulo wasm com importações duplicadas (de tipos diferentes), você não pode usar a API JS para implementar essas importações com exportações wasm devido ao uso de busca de nome em WebAssembly.instantiate() . O mesmo vale para integração com ESM . Embora eu possa imaginar extensões teóricas para esses dois esquemas de vinculação para oferecer suporte à virtualização de importação duplicada, tenho mais dificuldade em imaginar que todos estejam motivados o suficiente para realmente especificar e implementar a complexidade extra.

Assim, acho que a pergunta que devemos fazer é: "a miríade de esquemas de vinculação do wasm que inevitavelmente surgirá em todo o ecossistema do wasm acabará dando suporte à virtualização de importações duplicadas?" e, com base em nosso conjunto de amostra atual, parece improvável.

Imagine que as importações e exportações do WebAssembly não tivessem strings associadas a elas. A API JS usaria índices puramente posicionais para acessar importações e exportações. Pelo que posso dizer, seria fácil (no sentido relativo) fazer as cadeias de ferramentas existentes funcionarem com tal configuração. (Em alguns casos, pode ser mais fácil porque coisas como emscripten não teriam que gerar nomes arbitrários para conectar o código JS "inline" (a la EM_ASM ).)

Em tal mundo, não haveria problema de virtualização. Ou seja, o problema de virtualização é causado pela escolha de usar nomes em vez de índices posicionais. Pode-se até resolver isso de maneira compatível com versões anteriores, tornando ambos disponíveis na API JS.

A questão é que as importações / exportações não posicionais são, na verdade, decorações específicas do incorporador que são usadas para facilitar a integração do módulo wasm em um ecossistema. A maneira como essas decorações são especificadas realmente deve ser adaptada às normas (existentes ou esperadas) do ecossistema.

Por exemplo, com integração ESM, os valores JS exportados por outros módulos podem ser sobrecarregados, mas WebAssembly não suporta sobrecarga, portanto, um design de decoração de importação-exportação ESM deve preencher essa lacuna para que os módulos WebAssembly possam tomar o lugar de tantos JS módulos possíveis. Isso poderia ser feito tendo importações duplicadas e coerções direcionadas ao tipo para extrair diferentes sobrecargas de uma função importada de outro módulo JS. Pode-se até permitir exportações duplicadas, desde que os tipos exportados sejam todas funções com parâmetros suficientemente separados / distinguíveis para que possam ser combinados em uma única função JS sobrecarregada. E, claro, existem outras opções de design, cada uma com prós e contras.

Como um exemplo contrastante, suponha que a linguagem de incorporação primária para WebAssembly usasse principalmente parâmetros nomeados para funções em vez de parâmetros posicionais. Então, as decorações de importação-exportação pareceriam bem diferentes, com exportações de funções atribuindo nomes a cada parâmetro. Mas no nível mais baixo, um módulo WebAssembly ainda seria apenas importações e exportações posicionais.

É por isso que eu estava sugerindo que o problema fosse resolvido dando um passo abaixo. A vinculação do módulo "Core" WebAssembly, ou seja, o que se usaria para vincular os módulos WebAssembly definidos no mesmo arquivo (por exemplo, para vincular o módulo adaptador de Tipos de Interface ao módulo que ele adapta), deve ser posicional. A vinculação do módulo "incorporado" do WebAssembly, ou seja, o que se usaria para vincular os módulos WebAssembly e específicos do embedder definidos em arquivos separados dentro de um determinado embedder, seria uma seção específica do embedder e seria amplamente responsável por definir a conversão entre embedder- construções específicas e construções WebAssembly, como funções de coerção, adição de decorações a funções / instâncias / módulos exportados ou associação de nomes (simples / duplos / listas de) com importações posicionais. Então, o WebAssembly "principal" nunca se preocupa com nada além de números e posições, e problemas como importações duplicadas tornam-se problemas específicos do embutidor, em vez de problemas "principais".

Com essa mentalidade, esse problema poderia ser reformulado como "Para JS API ou para módulos ES, devemos permitir que duas importações sejam associadas à mesma string?" com a resposta não tendo significado para a proposta de vinculação de módulo. É uma pergunta útil, mas é difícil chegar a uma boa resposta no momento, enquanto o argumento principal é baseado em um acoplamento que (eu acho) não deveria existir.

Concordo que as importações / exportações posicionais evitam duplicatas de toda a questão. O problema que eu gostaria de evitar, porém, é quando você tem uma compilação separada em momentos separados, de modo que você tem que começar a se preocupar com o controle de versão, as importações / exportações posicionais têm o problema clássico de classe base frágil que foi sentido concretamente em sistemas de componentes como COM, que são baseados em layouts vtable C ++ (posicionais).

Agora, se eu entendo o que @RossTate e @titzer estão propondo, este é um problema que deve ser resolvido na camada de host, permitindo que o host introduza nomes apropriados para aquele host . O problema que vejo que permanece com essa resposta é que ela particiona de maneira inerente o ecossistema de ferramentas wasm por host. Por exemplo, agora eu não posso simplesmente ter "clang targeting wasm" se o clang desejar fazer qualquer forma de vinculação (por exemplo, para fatorar um libc (que é uma otimização importante nesta categoria emergente de plataformas de execução de multi-módulo wasm)) - Eu preciso ensinar o clang sobre todos os diferentes hosts concretos que ele tem como alvo e ter o host como um parâmetro para o clang. Além disso, agora a saída produzida só pode ser executada naquele host, impedindo a capacidade de produzir .wasm módulos que são reutilizáveis ​​entre hosts. Para obter ferramentas compartilhadas e módulos reutilizáveis, precisamos de algum tipo de alvo de compilação abstrato comum que diga respeito à vinculação (particularmente um que não assuma uma associação 1: 1 entre nomes e instâncias, que é insuficiente para vários casos de

Dito isso, posso imaginar uma solução híbrida na qual o Module Linking é adicionado como uma camada de especificação independente de host acima do core wasm e abaixo da API JS. Isso permitiria que ferramentas e módulos agnósticos de host fossem produzidos (visando essa camada de especificação agnóstica de host) enquanto permite que os embeddings de host evitem essa camada por completo, instanciando módulos centrais diretamente com esquemas de links especializados.

Isso soa como uma maneira razoável de resolver esses objetivos conflitantes?

Obrigado, @lukewagner , por ter

Acho que uma solução híbrida / de duas camadas é o que venho tentando sugerir, em que as duas camadas correspondem à ligação "interna" e "externa". O link interno seria posicional e especificar instruções para construir / conectar / instanciar módulos - é o que todo mecanismo WebAssembly (com link de módulo) teria que implementar. A vinculação externa seria específica do embedder e especifica como construir módulos (posicionais) a partir de outros arquivos e similares, por exemplo, associando nomes de arquivo e / ou nomes de string a importações / exportações - é o que os incorporadores WebAssembly teriam que implementar. Essa separação de interesses tornaria mais fácil reutilizar os aspectos "centrais" de um programa WebAssembly e os aspectos "centrais" de um mecanismo WebAssembly em vários ecossistemas. (Eu também acho que essa separação tem o potencial de limpar aspectos da vinculação de módulo interno.)

Mas, como você mencionou, ter duas personalizações "externas" pode levar a fraturas graves. Portanto, embora eu ache que os links "internos" e "externos" devem ser separados, também acho útil ter um padrão de link externo "comum". Alguns podem desenvolver sistemas de ligação externa mais especializados para melhor se ajustar ou tirar mais vantagem das especificidades de um determinado ecossistema (usando ferramentas mais especializadas para fazer isso), mas as necessidades de muitos programas se encaixam em um padrão comum simples.

Então, para resumir, acho que deve haver um sistema de link "interno" (de baixo nível, por exemplo, posicional) e deve haver o potencial para vários sistemas de link "externos" que ficam em cima dele, mas com um " comum "padronizado tal sistema no local. Isso faz sentido? (Nada disso se refere ao problema atual de importações duplicadas, infelizmente, mas pelo menos tenta caracterizar onde dentro deste espaço esse problema se encontra, se isso faz sentido.)

Ok, então pensar sobre o que esse tipo de mudança significaria concretamente no curto prazo: reformular o Module Linking como uma camada de "link comum" acima da especificação principal (e, se quiséssemos, sob as especificações JS / Web) poderia significar que nós não precisa adicionar nada à especificação principal. Em particular, as importações / exportações dos módulos principais podem permanecer posicionais, de forma que a camada de vinculação de módulo ignorou completamente esses nomes ao executar a instanciação. Com base nisso, poderíamos fechar este problema com a resposta "Não", caso em que estou chegando ao ponto de @titzer acima de que, por simetria, devemos permitir exportações duplicadas também para que, por exemplo, módulos principais que são instanciados exclusivamente pela camada de vinculação de módulo podem simplesmente fornecer strings de comprimento 0 para todas as strings de importação / exportação - não forçaríamos os módulos principais a incluir "" , "a" , "b" , "c" ... exportar nomes apenas para apaziguar a validação.

(No futuro, poderíamos falar sobre estender o wasm central com versões posicionais de módulos / instâncias, mas não acho que isso seria adicionalmente necessário para o conjunto inicial de casos de uso e requisitos enfocados pela proposta de vinculação de módulo.)

@rossberg @titzer @tlively Pensamentos?

Estou preocupado que a codificação de que as principais importações e exportações do Wasm devem ser estritamente posicionais fecharia a porta em https://github.com/WebAssembly/gc/issues/148 , o que sugere a introdução de um novo mecanismo importexport para resolver os problemas de importação e exportação são necessariamente assimétricos. O mecanismo sugerido essencialmente introduz uma ligação fraca de tipos no nível principal do Wasm, e não tenho certeza de como poderia funcionar posicionalmente sem identificadores de string.

Obviamente, isso é hipotético porque não há consenso para mover a proposta do CG nessa direção, mas pessoalmente acho que é uma opção atraente o suficiente para a proposta do CG que ficaria triste em vê-la menos viável por desenvolvimentos não relacionados como este. agora mesmo. Estou perdendo uma maneira de essa mudança proposta funcionar posicionalmente ou estou entendendo mal esse problema?

Ah, conexão interessante, @tlively! Isso me faz pensar em algumas idéias.

Uma ideia é relevante para links estáticos. Assim como as importações nomeadas ajudam na alterabilidade dos módulos vinculados externamente, o mesmo acontece com as importações

Outra ideia é relevante para links dinâmicos. Aqui, as strings são usadas como chaves globais. importexport essencialmente diz "importe o valor associado a esta chave e, se esse valor ainda não estiver definido, registre globalmente o seguinte como o valor correspondente a essa chave". (Novamente, tudo isso está na camada de vinculação externa.)

Embora WebAssemby / gc # 148 seja sobre tipos nominais, observarei que ambas as idéias acima se aplicam a exceções também. Para vinculação estática, espero que um padrão comum para um módulo seja importar um evento de exceção que, por padrão, é gerado recentemente, mas que pode ser instanciado para usar o mesmo evento que outros módulos (instâncias?) Para compartilhar o controle não local com eles. Para links dinâmicos, se alguém realmente quiser que não haja um módulo de tempo de execução central (sobre o qual as pessoas já sabem o que eu penso), então qualquer linguagem com exceções precisará coordenar um evento de exceção entre módulos vinculados dinamicamente e o importexport semântica de links externos é um bom meio de conseguir isso.

Arrumado! Se bem entendi, o mecanismo importexport pode ser pensado como parte da camada de vinculação de módulo que desugars para importações e exportações posicionais, criando implicitamente um módulo de exportação "padrão" do qual importar o tipo definido. Isso me parece razoável, então não tenho mais preocupações sobre essa direção.

Acho que o argumento das camadas serve para os dois lados. Da mesma forma, você pode argumentar que deve ser responsabilidade das camadas superiores mapear qualquer esquema de pares (nome de alto nível, informação extra) que elas empreguem para desambiguar as importações / exportações para um esquema inequívoco de nomes de nível inferior.

Para o nível inferior, sempre parece preferível ser o mais explícito e inequívoco possível. Isso mantém o sistema e a interface com ele simples - e coloca o fardo de lidar com esquemas mais elaborados nos ecossistemas que o desejam, em vez de em todos.

Quanto à proposta de vinculação como uma camada separada, parece bom em abstrato, mas estou um pouco cético de que poderíamos fazer isso funcionar sem problemas. Por exemplo, a proposta atualmente permite que os módulos internos se refiram a espaços de nomes externos para entidades estáticas como módulos ou tipos, o que parece bastante central e não pode ser mapeado para a especificação principal (exceto por uma transformação equivalente a lambda-levantamento, que quebraria os padrões de composição pretendidos, a menos que também introduzamos aplicação / instanciação parcial de importações, ou seja, uma forma de "fechamento de módulo").

Por exemplo, a proposta atualmente permite que módulos internos se refiram a espaços de nomes externos para entidades estáticas, como módulos ou tipos

Este foi um dos aspectos que achei que poderia ser simplificado.

a menos que também introduzamos aplicação / instanciação parcial de importações, ou seja, uma forma de "fechamento de módulo"

Essa foi uma das técnicas que achei que poderia ajudar a alcançar essa simplificação.

Em geral, apoio a ideia de mover uma especificação de vinculação uma camada acima; é basicamente o que venho defendendo. Os motores não precisam saber nomes.

@rossberg Eu geralmente acho que "claro e inequívoco" é ótimo para uma camada inferior, mas esta instância é mais sobre se deve ser prescritiva ou não, e as camadas inferiores devem fazer bem em não ser prescritivas sobre coisas que não lhes dizem respeito, IMO.

Há uma observação relacionada em https://github.com/WebAssembly/design/issues/1399#issuecomment -808401005, onde o problema descreve um problema com chamadas com falha ao chamar as exportações do Wasm com argumentos omitidos i64 , em que permitir exportações duplicadas e escolher aquele com a aridade correspondente ao chamar de JS para Wasm, seria uma solução preferível no contexto.

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