Design: Proposition : ajouter des liaisons de type inter-langues

Créé le 1 mai 2019  ·  61Commentaires  ·  Source: WebAssembly/design

WebAssembly est actuellement très efficace pour exécuter du code écrit dans des langages arbitraires à partir d'un interpréteur donné (généralement JS), mais il lui manque plusieurs fonctionnalités clés lorsqu'il s'agit de combiner plusieurs langages arbitraires .

L'une de ces fonctionnalités est un système de type indépendant de la langue. Je voudrais proposer qu'un ou plusieurs de ces systèmes soient ajoutés à WebAssembly.

En passant, dans les discussions précédentes sur les fonctionnalités, certains contributeurs ont exprimé que l'interopérabilité des langages ne devrait pas être un objectif de conception de WebAssembly. Bien que je convienne que cela ne devrait pas nécessairement être un objectif prioritaire , je pense que c'est un objectif à atteindre à long terme. Donc, avant d'entrer dans les objectifs de conception, je vais exposer les raisons pour lesquelles je pense que l'interopérabilité des langages en vaut la peine.

Pourquoi se soucier de l'interopérabilité des langues ?

Les avantages d'une réduction des barrières linguistiques comprennent :

  • Plus de bibliothèques pour les utilisateurs de wasm : Cela va sans dire, mais l'amélioration de l'interopérabilité des langages signifie que les utilisateurs peuvent utiliser plus souvent les bibliothèques existantes, même si la bibliothèque est écrite dans un langage différent de celui qu'ils utilisent.

  • Adoption plus facile des petites langues : sur le marché actuel, il est souvent difficile pour les langues sans le soutien de l'entreprise de gagner du terrain. Les nouvelles langues (et même les langues comme le D avec des années de raffinement) doivent rivaliser avec les langues avec de grands écosystèmes et souffrent de leur propre manque de bibliothèques. L'interopérabilité des langages leur permettrait d'utiliser des écosystèmes existants comme Python ou Java.

  • De meilleures chaînes d'outils indépendantes du langage : à l'heure actuelle, la plupart des langages ont leur propre schéma de chargement de bibliothèque et leur propre gestionnaire de packages (ou, dans le cas du C/C++, plusieurs non officiels). Écrire un générateur de projet indépendant du langage est difficile, car ces langages ont souvent des dépendances subtiles et des incompatibilités ABI, qui nécessitent une solution monolithique à l'échelle du projet pour être résolus. Un système de type inter-langue robuste permettrait de diviser plus facilement les projets en modules plus petits, pouvant être gérés par une solution de type npm.

Dans l'ensemble, je pense que le premier point est le plus important, de loin. Un meilleur système de types signifie un meilleur accès à d'autres langages, ce qui signifie plus d'opportunités de réutiliser le code au lieu de l'écrire à partir de zéro. Je ne peux pas exagérer à quel point c'est important.

Conditions

Dans cet esprit, je veux décrire les exigences qu'un système de types inter-langues devrait satisfaire.

J'écris en partant du principe que le système de types serait strictement utilisé pour annoter les fonctions transmises entre les modules et ne vérifierait en aucun cas comment les langages utilisent leur propre mémoire linéaire ou gérée.

Pour être vraiment utile dans un environnement wasm, un tel système de types aurait besoin de :

1 - Sécurité

  • Type-safe : l'appelé ne doit avoir accès qu'aux données spécifiées par l'appelant, de style object-capabilities-style.

    • La mémoire doit être "oubliée" à la fin d'un appel. Un appelé ne devrait pas être en mesure d'accéder aux données d'un appelant, de revenir, puis d'accéder à nouveau à ces données sous quelque forme que ce soit.

2 - Frais généraux

  • Les développeurs doivent être à l'aise pour faire des appels inter-modules régulièrement, par exemple, dans une boucle de rendu.

    • Copie zéro : le système de types doit être suffisamment expressif pour permettre aux interprètes d'implémenter des stratégies de copie zéro s'ils le souhaitent, et suffisamment expressif pour que ces implémenteurs sachent quand la copie zéro est optimale.

3 - Graphes de structure

  • Le système de types doit inclure des structures, des pointeurs facultatifs, des tableaux de longueur variable, des tranches, etc.

    • Idéalement, l'appelant doit pouvoir envoyer un graphe d'objet dispersé en mémoire tout en respectant les exigences 1 et 2.

4 - Types de références

  • Les modules devraient pouvoir échanger des types de référence imbriqués profondément dans les graphes de structure.

5 - Pont entre les configurations de mémoire

  • C'est un point très important. Différentes catégories de langues ont des exigences différentes. Les langages s'appuyant sur la mémoire linéaire voudraient transmettre des tranches de mémoire, tandis que les langages s'appuyant sur GC voudraient transmettre des références GC.

    • Un système de types idéal devrait exprimer des types sémantiques et laisser les langages décider comment les interpréter en mémoire. Bien que la transmission de données entre des langues avec des dispositions de mémoire incompatibles entraîne toujours une surcharge, la transmission de données entre des langues similaires devrait idéalement être bon marché (par exemple, les intégrateurs doivent éviter les étapes de sérialisation-désérialisation si un memcpy peut faire le même travail).

    • Des liaisons supplémentaires peuvent également permettre la mise en cache et d'autres stratégies d'optimisation.

    • Le travail de conversion lors du passage de données entre deux modules doit être transparent pour le développeur, tant que les types sémantiques sont compatibles.

6 - Gestion des erreurs à la compilation

  • Toute erreur liée à des arguments d'appel de fonction non valides doit être détectable et exprimable au moment de la compilation, contrairement à, par exemple, JS, où les TypeErrors sont renvoyés au moment de l'exécution lors de la tentative d'évaluation de l'argument.
  • Idéalement, les compilateurs de langage eux-mêmes devraient détecter les erreurs de type lors de l'importation de modules wasm et générer des erreurs expressives et idiomatiques pour l'utilisateur. La forme que devrait prendre cette vérification d'erreur devrait être détaillée dans le référentiel tool-conventions .
  • Cela signifie qu'un IDL avec des convertisseurs existants vers d'autres langues serait un plus.

7 - Fournir un point Schelling pour l'interaction inter-langue

  • C'est plus facile à dire qu'à faire, mais je pense que wasm devrait envoyer un signal à tous les auteurs de compilateurs, que le moyen standard d'interopérer entre les langues est X. Pour des raisons évidentes, avoir plusieurs normes concurrentes pour l'interopérabilité des langues n'est pas souhaitable.

Mise en œuvre proposée

Ce que je propose, c'est que les liaisons à l' IDL Cap'n'Proto par

Ils fonctionneraient de la même manière que les liaisons WebIDL : les modules wasm exporteraient des fonctions et utiliseraient des instructions spéciales pour les lier à des signatures tapées ; d'autres modules importeraient ces signatures et les lieraient à leurs propres fonctions.

La pseudo-syntaxe suivante est destinée à donner une idée de ce à quoi ces liaisons ressembleraient ; il est approximatif et fortement inspiré de la proposition WebIDL, et se concentre plus sur les défis techniques que sur la fourniture de listes exhaustives d'instructions.

Les instructions de liaison Capnproto seraient toutes stockées dans une nouvelle section de liaisons Cap'n'proto .

Types de cap'n'proto

La norme aurait besoin d'une représentation interne du langage de schéma de

``` Cap'n Proto
struct Personne {
nom @0 :Texte;
date de naissance @3 :Date;

email @1 :Texte;
téléphones @2 :Liste(Numéro de téléphone);

struct PhoneNumber {
nombre @0 :Texte;
tapez @1 : tapez;

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

}
}

Date de structure {
année @0 :Int16;
mois @1 :UInt8;
jour @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)
))

Sérialisation à partir de la mémoire linéaire

Les messages Capnproto transmettent deux types de données : des segments (octets bruts) et des capacités.

Ceux-ci correspondent approximativement à la mémoire linéaire et aux tables de WebAssembly. En tant que tel, le moyen le plus simple pour l'assemblage Web de créer des messages capnproto serait de transmettre un décalage et une longueur à la mémoire linéaire pour les segments, et un décalage et une longueur à une table pour les capacités.

(Une meilleure approche pourrait être conçue pour les capacités, afin d'éviter les vérifications de type à l'exécution.)

Notez que les calculs de sérialisation réels auraient lieu dans le code de la colle, le cas échéant (voir Génération du code de la colle ).

Opérateurs de liaison

| Opérateur | Immédiats | Enfants | Descriptif |
| :--- | :--- | :--- | :--- |
| segment | hors-idx
len‑idx | | Prend les off-idx 'th et len-idx 'th wasm valeurs du tuple source, qui doivent toutes les deux être i32 s, comme décalage et longueur d'une tranche de mémoire linéaire dans lequel un segment est stocké. |
| captable | hors-idx
len‑idx | | Prend les off-idx 'th et len-idx 'th wasm valeurs du tuple source, qui doivent toutes les deux être i32 s, comme décalage et longueur d'une tranche de table dans laquelle la table de capabilité est stockée. |
| message | capnproto-type
table des capacités | segments | Crée un message capnproto au format capnproto-type , en utilisant la table de capacité et les segments fournis. |

Sérialisation à partir de la mémoire gérée

Il est difficile d'identifier un comportement spécifique avant que la proposition du GC n'arrive. Mais l'implémentation générale est que les liaisons capnproto utiliseraient un seul opérateur de conversion pour obtenir les types capnproto des types GC.

Les règles de conversion pour les types de bas niveau seraient assez simples : i8 convertit en Int8, UInt8 et bool, i16 convertit en Int16, etc. Les types de haut niveau seraient convertis en leurs équivalents capnproto : les références de structure et de tableau sont converties en pointeurs, références opaques convertir en capacités.

Une proposition plus complète devrait définir une stratégie pour l'enum et les syndicats.

Opérateurs de liaison

| Opérateur | Immédiats | Enfants | Descriptif |
| :--- | :--- | :--- | :--- |
| comme | capnproto-type
idx | | Prend la idx 'th wasm value du tuple source, qui doit être une référence, et produit une valeur capnproto de capnproto-type . |

Désérialisation en mémoire linéaire

La désérialisation en mémoire linéaire est principalement similaire à la sérialisation à partir de celle-ci, avec une mise en garde supplémentaire : le code wasm ne sait souvent pas à l'avance combien de mémoire le type capnproto prendra et doit fournir à l'hôte une sorte de méthode de gestion dynamique de la mémoire .

Dans la proposition de liaisons WebIDL, la solution proposée consiste à transmettre les rappels de l'allocateur à la fonction hôte. Pour les liaisons capnproto, cette méthode serait insuffisante, car les allocations dynamiques doivent se produire à la fois du côté de l'appelant et du côté de l'appelé.

Une autre solution serait de permettre aux cartes de liaison entrantes de se lier à deux expressions de liaison entrantes (et donc à deux fonctions) : une qui alloue la mémoire pour les données capnproto et une qui prend réellement les données.

Désérialisation vers la mémoire gérée

La désérialisation en mémoire gérée utiliserait le même type d'opérateur de conversion que la direction opposée.

Génération du code de la colle

Lors de la liaison de deux modules wasm (que ce soit de manière statique ou dynamique), l'embedder doit répertorier tous les types capnproto communs aux deux modules, les liaisons entre les types de fonction et les types capnproto, et générer du code de collage entre chaque paire différente de types de fonction.

Le code de collage dépendrait des types de données liées. Le code de collage entre les liaisons de mémoire linéaire se résumerait à des appels memcpy. Le code de collage entre les liaisons de mémoire gérées se résumerait à passer des références. D'un autre côté, le code de collage entre la mémoire linéaire et la mémoire gérée impliquerait des opérations de conversion imbriquées plus compliquées.

Par exemple, un module Java pourrait exporter une fonction, en prenant les arguments comme types GC, et lier cette fonction à une signature typée ; l'interpréteur doit permettre à un module Python et à un C++ d'importer cette signature de type ; la liaison C++ transmettrait les données de la mémoire linéaire, tandis que la liaison Python transmettrait les données de la mémoire GC. Les conversions nécessaires seraient transparentes pour les compilateurs Java, Python et C++.

Solutions alternatives

Dans cette section, j'examinerai d'autres façons d'échanger des données et leur évaluation par rapport aux métriques définies dans la section Exigences .

Échanger des messages JSON

C'est la solution de la force brute. Je ne vais pas passer trop de temps sur celui-là, car ses défauts sont assez évidents. Il ne répond pas aux exigences 2, 4 et 6.

Envoyer des octets bruts encodés dans un format de sérialisation

C'est une solution partielle. Définissez un moyen pour les modules wasm de transmettre des tranches de mémoire linéaire et des tables à d'autres modules, et les rédacteurs de modules peuvent ensuite utiliser un format de sérialisation (capnproto, protobuff ou autre) pour encoder un graphique structuré en une séquence d'octets, passer les octets, et utilisez le même format pour le décoder.

Il passe 1 et 3, et il peut passer 2 et 4 avec quelques ajustements (par exemple, passer les références en tant qu'indices à une table). Il peut passer 6 si l'utilisateur s'assure d'exporter le type de sérialisation vers une définition de type dans la langue de l'appelant.

Cependant, il échoue aux exigences 5 et 7. C'est peu pratique lors de la liaison entre deux implémentations GC ; par exemple, un module Python appelant une bibliothèque Java via Protobuf aurait besoin de sérialiser un dictionnaire en tant que mémoire linéaire, de transmettre cette tranche de mémoire, puis de la désérialiser en tant qu'objet Java, au lieu de faire quelques recherches de table de hachage qui peuvent être optimisées dans une implémentation JIT.

Et cela encourage chaque rédacteur de bibliothèque à utiliser son propre format de sérialisation (JSON, Protobuf, FlatBuffer, Cap'n Proto, SBE), ce qui n'est pas idéal pour l'interopérabilité ; bien que cela puisse être atténué en définissant un format de sérialisation canonique dans tool-conventions .

Cependant, ajouter la possibilité de passer des tranches arbitraires de mémoire linéaire serait une bonne première étape.

Envoyer des objets GC

Il serait possible de s'appuyer sur des modules s'envoyant des objets GC .

La solution présente certains avantages : la proposition du GC est déjà en cours ; il passe 1, 3, 4 et 7. Les données collectées par le GC sont coûteuses à allouer, mais bon marché à faire circuler.

Cependant, cette solution n'est pas idéale pour les langages de type C. Par exemple, un module D transmettant des données à un module Rust devrait sérialiser ses données dans un graphique GC, passer le graphique à la fonction Rust, ce qui le désérialiserait dans sa mémoire linéaire. Ce processus alloue des nœuds GC qui sont immédiatement mis au rebut, pour beaucoup de frais généraux inutiles.

Cela mis à part, la proposition actuelle du GC n'a pas de support intégré pour les enums et les unions; et la gestion des erreurs se ferait soit au moment de la liaison, soit au moment de l'exécution au lieu du moment de la compilation, à moins que le compilateur ne puisse lire et comprendre les types de GC wasm.

Utiliser d'autres encodages

Toute bibliothèque de sérialisation qui définit un système de types pourrait fonctionner pour wasm.

Capnproto semble le plus approprié, en raison de l'accent mis sur la copie zéro et de ses capacités d'objet intégrées qui correspondent parfaitement aux types de référence.

Le travail restant

Les concepts suivants devront être étoffés pour transformer cette proposition rudimentaire en un document pouvant être soumis au groupe communautaire.

  • Opérateurs de liaison
  • Équivalences de type GC
  • Capacités de l'objet
  • Tableaux de booléens
  • Tableaux
  • Constantes
  • Génériques
  • Évolution du type
  • Ajoutez un troisième type de liaison "getters and setters".
  • Stratégies de mise en cache possibles
  • Prise en charge de plusieurs tables et mémoires linéaires

En attendant, tout commentaire sur ce que j'ai déjà écrit serait le bienvenu. La portée ici est assez vaste, j'apprécierais donc de l'aide pour préciser les questions auxquelles cette proposition doit répondre.

Commentaire le plus utile

nous pouvons ajouter quelques liaisons par type d'IR pour couvrir la grande majorité des langues.

C'est l'hypothèse sous-jacente cruciale qui, à mon avis, n'est tout simplement pas vraie. Mon expérience est qu'il y a (au moins !) autant de choix de représentation qu'il y a d'implémentations de langage. Et ils peuvent être arbitrairement compliqués.

Prenez V8, qui à lui seul possède quelques dizaines (!) de représentations pour les chaînes, y compris des encodages différents, des cordes hétérogènes, etc.

Le cas Haskell est bien plus compliqué que vous ne le décrivez, car les listes en Haskell sont paresseuses, ce qui signifie que pour chaque caractère d'une chaîne, vous devrez peut-être invoquer un thunk.

D'autres langages utilisent des représentations amusantes pour la longueur d'une chaîne, ou ne la stockent pas explicitement mais nécessitent qu'elle soit calculée.

Ces deux exemples montrent déjà qu'une disposition de données déclarative ne suffit pas, vous auriez souvent besoin de pouvoir invoquer du code d'exécution, qui à son tour peut avoir ses propres conventions d'appel.

Et ce ne sont que des chaînes, qui sont conceptuellement un type de données assez simple. Je ne veux même pas penser au nombre infini de façons dont les langages représentent les types de produits (tuples/structs/objects).

Et puis il y a le côté réception, où il faudrait pouvoir créer toutes ces structures de données !

Je pense donc qu'il est tout à fait irréaliste que nous soyons un jour proches du soutien de la "grande majorité des langues". Au lieu de cela, nous commencerions à en privilégier quelques-uns, tout en développant déjà un grand zoo de trucs arbitraires. Cela semble fatal à plusieurs niveaux.

Tous les 61 commentaires

C'est vraiment intéressant ! J'ai seulement lu rapidement et j'ai juste quelques idées initiales, mais ma première question serait de demander pourquoi le mécanisme FFI existant que la plupart des langages fournissent/utilisent déjà n'est pas suffisant pour WebAssembly. Pratiquement toutes les langues que je connais ont une forme de C FFI, et sont donc déjà capables d'interopérer aujourd'hui. Beaucoup de ces langages sont également capables d'effectuer une vérification de type statique basée sur ces liaisons. De plus, il existe déjà de nombreux outillages autour de ces interfaces (par exemple, la caisse bindgen pour Rust, erl_nif pour Erlang/BEAM, etc.). C FFI répond déjà aux exigences les plus importantes et a le principal avantage d'être déjà largement éprouvé et utilisé dans la pratique.

5 - Pont entre les configurations de mémoire

Un système de types idéal devrait exprimer des types sémantiques et laisser les langages décider comment les interpréter en mémoire. Bien que la transmission de données entre des langues avec des dispositions de mémoire incompatibles entraîne toujours une surcharge, la transmission de données entre des langues similaires devrait idéalement être bon marché (par exemple, les intégrateurs doivent éviter les étapes de sérialisation-désérialisation si un memcpy peut faire le même travail).

Le travail de conversion lors du passage de données entre deux modules doit être transparent pour le développeur, tant que les types sémantiques sont compatibles.

La traduction transparente d'une mise en page à une autre lors de la transmission de données à travers la barrière FFI me semble vraiment être un travail pour les backends du compilateur ou les environnements d'exécution de langage, et probablement pas du tout souhaitable dans les langages sensibles aux performances comme C/C++/Rust/etc. En particulier, pour les choses que vous prévoyez de faire passer dans FFI, il me semble qu'il est toujours préférable d'utiliser une ABI commune, plutôt que de faire n'importe quel type de traduction, car la traduction entraînerait probablement un coût trop élevé. L'avantage de choisir une mise en page autre que l'ABI commune de la plate-forme n'en vaut probablement pas la peine, mais j'admettrai volontiers que je comprends peut-être mal ce que vous entendez par mises en page alternatives.

Soit dit en passant, mettre le fardeau d'un outil FFI solide sur les compilateurs/environnements d'exécution présente un avantage supplémentaire, en ce que toutes les améliorations apportées sont applicables sur d'autres plates-formes, et vice versa, car les améliorations apportées à FFI pour les plates-formes non Wasm profitent à Wasm. Je pense que l'argument doit être vraiment convaincant pour commencer essentiellement à partir de la case départ et construire un nouveau mécanisme FFI.

Je m'excuse si j'ai mal compris le but de la proposition, ou si j'ai raté quelque chose de critique, comme je l'ai mentionné ci-dessus, je dois relire plus attentivement, mais j'ai senti que je devais poser mes questions initiales pendant que j'avais un peu de temps.

Apache Arrow existe aussi pour cela, mais est plus axé sur les applications hautes performances.

Je pense que je suis d'accord avec la motivation générale ici et cela correspond essentiellement aux discussions que nous avons eues sur la façon dont les liaisons Web IDL pourraient être généralisées à l'avenir. En effet, les versions antérieures de l'explicateur contenaient une entrée de FAQ mentionnant ce cas d'utilisation inter-langue.

Ma principale préoccupation (et la raison de l'omission de cette entrée de la FAQ) est la portée : le problème général de la liaison des langages N semble susceptible de générer beaucoup de discussions ouvertes (et peut-être sans fin), d'autant plus que personne ne le fait déjà ( ce qui est bien sûr un problème de poule et d'œuf). En revanche, les problèmes abordés par les liaisons Web IDL sont assez concrets et facilement démontrés avec Rust/C++ aujourd'hui, nous permettant de motiver l'effort (non trivial) de standardiser/implémenter et également de prototyper/valider avec empressement la solution proposée.

Mais j'espère que Web IDL Bindings nous permettra de résoudre ce problème de la poule et de l'œuf et de commencer à acquérir une certaine expérience avec la liaison inter-langage qui pourrait motiver une prochaine vague d'extension ou quelque chose de nouveau et non spécifique à Web IDL . (Notez que, comme actuellement proposé, si deux modules wasm utilisant des liaisons Web IDL compatibles s'appellent, un impl d'optimisation peut effectuer les optimisations que vous mentionnez ici, sans la pleine expressivité de Cap'n Proto.)

Je dois dire d'emblée que je n'ai pas encore eu le temps d'étudier pleinement la proposition.
La raison en est que je crois que la tâche est impossible. Il y a deux raisons fondamentales à cela :
une. Différentes langues ont des sémantiques différentes qui ne sont pas nécessairement capturées dans une annotation de type. Par exemple, l'évaluation de Prolog est radicalement différente de l'évaluation de C++ : au point où les langages ne sont essentiellement pas interopérables. (Pour Prolog, vous pouvez remplacer un certain nombre d'autres langues)

b. Par définition, aucun système de type LCD n'est garanti pour capturer tout le langage de type d'un langage donné. Cela laisse à l'implémenteur du langage un choix profondément inconfortable : prendre en charge son propre langage ou renoncer aux avantages du système de types de son langage. Exemple concret : Haskell a des « classes de type ». Toute implémentation d'Haskell qui impliquait de ne pas prendre en charge les classes de type l'éviscérerait et la rendrait inutilisable.
Autre exemple : la prise en charge des génériques par C++ nécessite l'élimination de la généricité au moment de la compilation ; d'autre part, ML, Java (et un tas d'autres langages) utilisent une forme de représentation universelle -- qui n'est pas compatible avec l'approche adoptée par C++.

D'un autre côté, le fait d'avoir deux expressions d'un type exporté/importé semble poser ses propres problèmes : le système de langage est-il censé vérifier que les deux expressions sont cohérentes dans un certain sens ? A qui revient la responsabilité de faire ce travail ?

@lukewagner Merci pour les liens ! Je suis vraiment content d'avoir eu la chance de lire ce document!

Il me semble qu'il y a deux choses un peu mélangées dans cette discussion particulière - une partie de ce qui est ci-dessous est écrite afin que je puisse vérifier ma compréhension, alors n'hésitez pas à signaler tout ce que j'ai pu mal comprendre ou manquer :

  1. Liaisons hôtes efficaces

    • Fondamentalement, le problème que WebIDL est destiné à résoudre, au moins pour les environnements de navigateur - une description d'interface qui mappe module->hôte et hôte->module, déléguant essentiellement le travail de traduction de l'un à l'autre au moteur hôte. Cette traduction n'est pas forcément garantie d'être idéale, ni même optimisée du tout, mais les moteurs d'optimisation peuvent s'en servir pour le faire. Cependant, même optimisée, la traduction est toujours effectuée dans une certaine mesure, mais cela est acceptable car l'alternative est toujours la traduction, juste plus lente.

  2. Liaisons hétérogènes efficaces de module à module.

    • En d'autres termes, étant donné deux modules, l'un écrit en source et l'autre en dest , partageant les types entre eux, appelant depuis source->dest et/ou dest->source

    • Si aucun FFI commun n'est disponible, et compte tenu de quelque chose comme WebIDL, c'est-à-dire du ferroutage sur 1, le chemin non optimisé serait de traduire via un type de dénominateur commun fourni par l'environnement hôte lors de l'appel à travers les barrières linguistiques, par exemple source type -> common type -> dest type .



      • Un moteur d'optimisation pourrait théoriquement rendre cette traduction directe de source à dest sans intermédiaire, mais impose toujours une surcharge de traduction.



    • Si un FFI commun est disponible, c'est- source dire que dest partagent un ABI (par exemple C ABI), alors source et dest peuvent s'appeler directement avec pas de frais généraux du tout, via le FFI. C'est probablement le scénario le plus probable en pratique.

Donc, mon point de vue est qu'il y a certainement des avantages à tirer parti de WebIDL, ou quelque chose comme ça (c'est-à-dire un sur-ensemble qui prend en charge un ensemble plus large d'API/environnements hôtes), mais ce n'est vraiment qu'une solution au problème décrit dans 1, et le sous-ensemble de 2 qui traite des liaisons inter-langues où aucun FFI n'est disponible. Le sous-ensemble de 2 où FFI _is_ disponible, est clairement préférable aux alternatives, car il n'encourt pas de frais généraux en soi.

Existe-t-il de bonnes raisons d'utiliser un IDL même lorsque FFI est une option ? Pour être clair, je suis tout à fait d'accord avec l'utilisation d'un IDL pour les autres cas d'utilisation mentionnés, mais je demande spécifiquement dans le contexte de l'interopérabilité des langages, pas des liaisons d'hôte.

J'ai quelques questions supplémentaires, si C FFI (par exemple, car c'est le plus courant) et IDL sont utilisés/présents en même temps :

  • Si les langages source et dest fournissent des définitions de type différentes pour un type partagé avec la même représentation en mémoire sous-jacente en fonction de leur ABI commune (par exemple, une représentation commune pour un tableau de longueur variable ) - le moteur hôte essaiera-t-il d'effectuer une traduction entre ces types simplement parce que les directives IDL sont présentes, même s'ils pourraient s'appeler en toute sécurité en utilisant leur FFI standard ?

    • Sinon, et c'est opt-in, cela semble être le scénario idéal, car vous pouvez ajouter IDL pour prendre en charge l'interopérabilité avec les langues sans FFI, tout en prenant en charge les langues avec FFI en même temps. Je ne sais pas comment un moteur hôte ferait fonctionner cela. Je n'y ai pas réfléchi complètement, alors il me manque probablement quelque chose

    • Si oui, comment le moteur hôte unifie-t-il les types ? :



      • Si le moteur ne se soucie que de la mise en page, comment l'analyse statique peut-elle détecter lorsqu'un appelant fournit des types d'arguments incorrects à un appelé ? Si ce type d'analyse n'est pas un objectif, alors il semblerait que l'IDL ne soit vraiment idéal que pour les liaisons d'hôtes, et moins pour les langues croisées.


      • Si le moteur se soucie plus que de la configuration, en d'autres termes, le système de typage nécessite à la fois une compatibilité nominale et structurelle :





        • Qui définit le type faisant autorité pour une fonction ? Comment puis-je même référencer le type faisant autorité à partir d'une langue ? Par exemple, disons que j'appelle une bibliothèque partagée écrite dans un autre langage qui définit une fonction add/2 et que add/2 attend deux arguments d'un certain type size_t . Mon langage ne connaît pas nécessairement size_t nominalement, il a sa propre représentation compatible ABI des entiers non signés de la largeur de la machine, usize , donc les liaisons FFI pour cette fonction dans mon langage utilisent mon types de langues. Compte tenu de cela, comment mon compilateur peut-il générer un IDL qui mappe usize à size_t .






  • Existe-t-il des exemples d'interfaces IDL utilisées pour appeler entre les modules d'un programme, où FFI est disponible mais explicitement laissé inutilisé en faveur de l'interface décrite par IDL ? Plus précisément quelque chose qui n'est pas WebAssembly, principalement intéressé à étudier les avantages dans ces cas.

J'admets que j'essaie toujours de parcourir tous les détails de WebIDL et de ses prédécesseurs, comment tout cela s'intègre avec les différents hôtes (navigateur vs non-navigateur) et ainsi de suite, faites-moi savoir si j'ai oublié quelque chose.

@bitwalker

C'est vraiment intéressant !

Content que tu aies aimé!

mais ma première et principale question serait de demander pourquoi le mécanisme FFI existant que la plupart des langages fournissent/utilisent déjà n'est pas suffisant pour WebAssembly.

Le système de type C a quelques problèmes en tant qu'IDL inter-langage :

  • Il fonctionne sous l'hypothèse d'un espace d'adressage partagé, qui n'est pas sûr et ne tient délibérément pas dans WebAssembly. (ma propre expérience avec un FFI JS-to-C suggère que les implémentations ont tendance à échanger la sécurité contre la vitesse)

  • Il n'a pas de support natif pour les tableaux de longueur dynamique, les unions étiquetées, les valeurs par défaut, les génériques, etc.

  • Il n'y a pas d'équivalent direct aux types de référence.

C++ résout certains de ces problèmes (pas le plus gros, l'espace d'adressage partagé), mais ajoute un tas de concepts qui ne sont pas vraiment utiles dans IPC. Bien sûr, vous pouvez toujours utiliser un sur-ensemble de C ou un sous-ensemble de C++ comme IDL, puis concevoir des règles de liaison autour de celui-ci, mais à ce stade, vous n'obtenez presque aucun avantage du code existant, vous pouvez donc aussi bien utiliser un code existant. IDL.

En particulier, pour les choses que vous prévoyez de passer dans FFI

Je ne comprends pas tout à fait ce que vous voulez dire, mais pour être clair : je ne pense pas que le transfert de données mutables entre les modules soit possible dans le cas général. Cette proposition tente de décrire un moyen d'envoyer des données immuables et d'obtenir des données immuables en retour, entre des modules qui n'ont aucune information sur la façon dont l'autre stocke ses données.

L'avantage de choisir une mise en page autre que l'ABI commune de la plate-forme n'en vaut probablement pas la peine, mais j'admettrai volontiers que je comprends peut-être mal ce que vous entendez par mises en page alternatives.

Le fait est qu'actuellement, l'ABI commune est une tranche d'octets stockée dans une mémoire linéaire. Mais à l'avenir, lorsque la proposition GC sera implémentée, certains langages (Java, C#, Python) stockeront très peu voire rien en mémoire linéaire. Au lieu de cela, ils stockeront toutes leurs données dans des structures GC. Si deux de ces langages essaient de communiquer, la sérialisation de ces structures en un flux d'octets uniquement pour les désérialiser immédiatement serait une surcharge inutile.


@KronicDeth Merci, je vais me

Bien que, en parcourant la doc, cela semble être un sur-ensemble de Flatbuffers, spécifiquement destiné à améliorer les performances ? Quoi qu'il en soit, quelles sont ses qualités qui peuvent contribuer de manière unique à l'interopérabilité du module WebAssembly, par rapport à Flatbuffers ou Capnproto ?


@lukewagner

Mais j'espère que Web IDL Bindings nous permettra de résoudre ce problème de la poule et de l'œuf et de commencer à acquérir une certaine expérience avec la liaison inter-langage qui pourrait motiver une prochaine vague d'extension ou quelque chose de nouveau et non spécifique à Web IDL.

D'accord. Mon hypothèse lors de la rédaction de cette proposition était que toute implémentation de liaisons capnproto serait basée sur les commentaires de l'implémentation de la proposition WebIDL.

Ma principale préoccupation (et la raison de l'omission de cette entrée de la FAQ) est la portée : le problème général de la liaison des langages N semble susceptible de générer beaucoup de discussions ouvertes (et peut-être sans fin), d'autant plus que personne ne le fait déjà ( ce qui est bien sûr un problème de poule et d'œuf).

Je pense que discuter d'une implémentation de capnproto a de la valeur, même si tôt.

En particulier, j'ai essayé de décrire les exigences que la mise en œuvre devrait/pourrait essayer de remplir. Je pense qu'il serait également utile de lister les cas d'utilisation courants qu'un système de type inter-langue pourrait essayer de résoudre.

En ce qui concerne le problème N-to-N, je me concentre sur ces solutions :

  • Ne vous souciez que du transfert de données de type RPC. N'essayez pas de transmettre des données mutables partagées, des classes, des durées de vie de pointeur ou tout autre type d'informations plus compliquées que "un vecteur a trois champs : 'x', 'y' et 'z', qui sont tous des nombres flottants".

  • Essayez de regrouper les langues et les cas d'utilisation en « clusters » de stratégies de gestion des données. Établir des stratégies au centre de ces clusters ; les compilateurs de langage se lient à une stratégie donnée, et l'interpréteur fait le reste du travail NxN.


@fgmccabe

La raison en est que je crois que la tâche est impossible. Il y a deux raisons fondamentales à cela :
une. Différentes langues ont des sémantiques différentes qui ne sont pas nécessairement capturées dans une annotation de type. Par exemple, l'évaluation de Prolog est radicalement différente de l'évaluation de C++ : au point où les langages ne sont essentiellement pas interopérables. (Pour Prolog, vous pouvez remplacer un certain nombre d'autres langues)

Toute implémentation d'Haskell qui impliquait de ne pas prendre en charge les classes de type l'éviscérerait et la rendrait inutilisable.

Oui, l'idée n'est pas de définir une abstraction parfaite "facilement compatible avec toutes les langues".

Cela dit, je pense que la plupart des langues ont des similitudes dans la façon dont elles structurent leurs données (par exemple, elles ont un moyen de dire "chaque personne a un nom, un e-mail et un âge", ou "chaque groupe a une liste de personnes de taille arbitraire").

Je pense qu'il est possible de puiser dans ces similitudes pour réduire considérablement les frictions entre les modules. (voir aussi ma réponse à lukewagner)

b. Par définition, aucun système de type LCD n'est garanti pour capturer tout le langage de type d'un langage donné. Cela laisse à l'implémenteur du langage un choix profondément inconfortable : prendre en charge son propre langage ou renoncer aux avantages du système de types de son langage.

Oui. Je pense que la règle de base ici est "S'il s'agit d'une limite de bibliothèque partagée, faites-en un type capnproto, sinon, utilisez vos types natifs".

D'un autre côté, le fait d'avoir deux expressions d'un type exporté/importé semble poser ses propres problèmes : le système de langage est-il censé vérifier que les deux expressions sont cohérentes dans un certain sens ? A qui revient la responsabilité de faire ce travail ?

Oui, je voulais initialement inclure une section sur la vérification des invariants et une autre sur la compatibilité des types, mais j'ai perdu courage.

La réponse à "à qui incombe la responsabilité" est généralement "l'appelé" (parce qu'il doit supposer que toutes les données qu'il reçoit sont suspectes), mais les contrôles pourraient être élidés si l'interpréteur peut prouver que l'appelant respecte les invariants de type.

Le système de type C a quelques problèmes en tant qu'IDL inter-langage

Juste pour être clair, je ne le suggère pas comme un IDL. Je suggère plutôt que l'interface binaire (l'ABI C) existe déjà, qu'elle est bien définie et qu'elle prend déjà en charge un langage étendu. L'implication est alors que WebAssembly n'a pas besoin de fournir une autre solution à moins que le problème à résoudre ne dépasse l'interopérabilité entre les langues.

Il fonctionne sous l'hypothèse d'un espace d'adressage partagé, qui n'est pas sûr et ne tient délibérément pas dans WebAssembly.

Je pense donc que je vois une partie du malentendu ici. Il existe deux classes de FFI dont nous parlons ici, l'une qui implique le partage de mémoire linéaire (FFI à mémoire partagée plus traditionnelle) et l'autre qui ne le fait pas (IPC/RPC plus traditionnel). J'ai parlé du premier, et je pense que vous êtes plus concentré sur le dernier.

Le partage de mémoire entre les modules lorsque vous en avez le contrôle (comme dans le cas où vous reliez plusieurs modules indépendants dans le cadre d'une application globale) est souhaitable pour l'efficacité, mais sacrifie la sécurité. D'un autre côté, il est possible de partager une mémoire linéaire désignée spécifiquement pour FFI, bien que je ne sache pas à quel point c'est pratique avec l'outillage par défaut disponible aujourd'hui.

L'interopérabilité entre modules qui _n'utilise pas_ la mémoire partagée FFI, c'est-à-dire IPC/RPC, semble définitivement être un bon choix pour WebIDL, capnproto ou l'une des autres suggestions dans cette veine, puisque c'est leur pain et le beurre.

La partie dont je ne suis pas sûr alors est de savoir comment mélanger les deux catégories de manière à ne pas sacrifier les avantages de l'une ou l'autre, car le choix d'aller dans un sens ou dans l'autre dépend fortement du cas d'utilisation. Au moins comme indiqué, il semble que nous ne puissions avoir que l'un ou l'autre, s'il est possible de soutenir les deux, je pense que ce serait l'idéal.

Il n'a pas de support natif pour les tableaux de longueur dynamique, les unions étiquetées, les valeurs par défaut, les génériques, etc.

Je pense que ce n'est probablement plus pertinent maintenant que je me rends compte que nous parlions de deux choses différentes, mais juste pour la postérité : système de type faible, mais ce n'est pas vraiment le problème, les langues ne ciblent pas C FFI pour le système de type C. La raison pour laquelle le C ABI est utile est qu'il fournit un dénominateur commun que les langages sont capables d'utiliser pour communiquer avec d'autres qui peuvent n'avoir aucun concept du système de types avec lequel ils interagissent. Le manque de fonctionnalités système de niveau supérieur n'est pas idéal et limite le genre de choses que vous pouvez exprimer via FFI, mais les limitations font également partie des raisons pour lesquelles il réussit si bien dans ce qu'il fait, à peu près n'importe quel langage peut trouver un moyen pour représenter les choses qui lui sont exposées via cette interface, et vice versa.

C++ résout certains de ces problèmes (pas le plus gros, l'espace d'adressage partagé), mais ajoute un tas de concepts qui ne sont pas vraiment utiles dans IPC. Bien sûr, vous pouvez toujours utiliser un sur-ensemble de C ou un sous-ensemble de C++ comme IDL, puis concevoir des règles de liaison autour de celui-ci, mais à ce stade, vous n'obtenez presque aucun avantage du code existant, vous pouvez donc aussi bien utiliser un code existant. IDL.

D'accord, pour IPC/RPC, C est un langage terrible pour définir des interfaces.

Le fait est qu'actuellement, l'ABI commune est une tranche d'octets stockée dans une mémoire linéaire.

C'est certainement la primitive avec laquelle nous travaillons, mais le C ABI définit beaucoup de choses en plus de cela.

Mais à l'avenir, lorsque la proposition GC sera implémentée, certains langages (Java, C#, Python) stockeront très peu voire rien en mémoire linéaire. Au lieu de cela, ils stockeront toutes leurs données dans des structures GC. Si deux de ces langages essaient de communiquer, la sérialisation de ces structures en un flux d'octets uniquement pour les désérialiser immédiatement serait une surcharge inutile.

Je ne suis pas convaincu que ces langues vont sauter en reportant GC à l'hôte, mais ce ne sont que des spéculations de ma part. Dans tous les cas, les langages qui comprennent les structures gérées par le GC hôte pourraient simplement décider d'une représentation commune pour ces structures à l'aide de C ABI tout aussi facilement qu'elles pourraient être représentées à l'aide de capnproto, la seule différence est l'endroit où réside la spécification de cette représentation. Cela dit, je n'ai qu'une compréhension très limitée des détails de la proposition GC et de la façon dont cela est lié à la proposition de liaisons hôte, donc si je suis loin du compte ici, n'hésitez pas à ne pas en tenir compte.

TL;DR : Je pense que nous sommes d'accord en ce qui concerne l'interopérabilité des modules où la mémoire linéaire partagée n'est pas en jeu. Mais je pense que la mémoire partagée _est_ importante à prendre en charge, et l'ABI C est le choix le plus sain pour ce cas d'utilisation en raison de la prise en charge des langues existantes. J'espère que cette proposition, au fur et à mesure de son évolution, soutiendra les deux.

Ce dont nous avons besoin, c'est simplement d'un moyen le plus efficace d'échanger des tampons d'octets et d'un moyen pour les langues de s'entendre sur le format. Il n'est pas nécessaire de régler ce problème sur un système de sérialisation particulier. Si Cap'n Proto est le plus adapté à cette fin, il peut apparaître comme un défaut commun de manière organique, plutôt que d'être mandaté par wasm.

Je suis bien sûr biaisé, car j'ai fait des FlatBuffers , qui sont similaires à Cap'n Proto en efficacité, mais plus flexibles et plus largement supportés. Cependant, je ne recommanderais pas non plus que ce format soit mandaté par wasm.

Il existe de nombreux autres formats qui pourraient être préférables à ces deux, compte tenu de certains cas d'utilisation.

Notez que Cap'n Proto et FlatBuffers sont tous deux sans copie, à accès aléatoire et sont efficaces pour imbriquer des formats (ce qui signifie qu'un format encapsulé dans un autre n'est pas moins efficace que de ne pas être encapsulé), qui sont les vraies propriétés à considérer pour l'inter-langue la communication. Vous pourriez imaginer un IDL qui vous permet de spécifier des dispositions d'octets très précises pour un tampon, y compris "les octets suivants sont le schéma Cap'n Proto X".

Bien que je fasse subtilement l'auto-promotion, je pourrais diriger les gens vers

@aardappel

Ce dont nous avons besoin, c'est simplement d'un moyen le plus efficace d'échanger des tampons d'octets et d'un moyen pour les langues de s'entendre sur le format. Il n'est pas nécessaire de régler ce problème sur un système de sérialisation particulier. Si Cap'n Proto est le plus adapté à cette fin, il peut apparaître comme un défaut commun de manière organique, plutôt que d'être mandaté par wasm.

Je comprends le point implicite, que wasm ne devrait pas être utilisé comme un moyen d'imposer une norme à ses concurrents, et je suis personnellement indifférent à l'IDL qui est choisi.

Cela dit, en fin de compte, le caoutchouc doit rencontrer la route à un moment donné. Si wasm veut faciliter la communication inter-langue (ce qui, bien sûr, n'est pas une hypothèse partagée par tout le monde), alors il a besoin d'un format standard qui peut exprimer plus que "ces octets composent des nombres". Ce format peut être capnproto, des structures C, des flatbuffers ou même quelque chose de spécifique à wasm, mais il ne peut pas être un sous-ensemble de tout cela en même temps, pour les raisons décrites par @fgmccabe .

Alors que je fais subtilement l'auto-promotion, je pourrais diriger les gens vers FlexBuffers qui est un peu comme des FlatBuffers sans schéma. Il a les mêmes propriétés souhaitables de copie zéro, d'accès aléatoire et d'imbrication bon marché, mais peut permettre aux langues de communiquer sans se mettre d'accord sur un schéma, sans faire de codegen, de la même manière que JSON est utilisé.

Je vois l'attrait, je ne pense pas que ce soit ce que l'on souhaite la plupart du temps, lorsqu'on écrit une bibliothèque. Le problème avec JSON (mis à part le terrible temps d'analyse) est que lorsque vous écrivez importer un objet JSON quelque part dans votre code, vous finissez par écrire beaucoup de code de désinfection avant de pouvoir utiliser vos données, par exemple :

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

avec des vulnérabilités de sécurité potentielles si vous ne le faites pas.

Voir aussi 6 - Gestion des erreurs au moment de la compilation ci- dessus.


@bitwalker

D'accord, je n'ai pas vraiment envisagé la possibilité d'une mémoire linéaire partagée. J'aurais besoin de quelqu'un de plus familier que moi avec la conception d' assemblages Web (

Par exemple, les FFI s'appuieront souvent sur le fait que leur langage hôte utilise la bibliothèque C et donneront aux bibliothèques natives un accès direct à la fonction malloc. Dans quelle mesure cette stratégie peut-elle être traduite en wasm, dans le contexte de deux modules mutuellement suspects ?

Je suppose que je devrais dire quelque chose sur ce fil, en tant que créateur de Cap'n Proto, mais assez étrangement, je n'ai pas trouvé d'opinion. Permettez-moi d'exprimer quelques pensées adjacentes qui peuvent ou non être intéressantes.

Je suis également le responsable technique de Cloudflare Workers, un environnement « sans serveur » qui exécute JavaScript et WASM.

Nous avons envisagé de prendre en charge Cap'n Proto RPC en tant que protocole permettant aux travailleurs de se parler. Actuellement, ils sont limités à HTTP, la barre est donc placée assez bas. :)

Dans Workers, lorsqu'un Worker en appelle un autre, il arrive très souvent que les deux s'exécutent sur la même machine, même dans le même processus. Pour cette raison, une sérialisation à copie zéro comme Cap'n Proto a évidemment beaucoup de sens, en particulier pour les travailleurs WASM car ils fonctionnent sur une mémoire linéaire qui pourrait, en théorie, être physiquement partagée entre eux.

Une deuxième raison moins connue pour laquelle nous pensons que cela convient bien est le système RPC. Cap'n Proto dispose d'un protocole RPC à pleine capacité d'objet avec un pipeline de promesse, calqué sur CapTP. Cela facilite l'expression d'interactions riches et orientées objet de manière sécurisée et performante. Cap'n Proto RPC n'est pas seulement un protocole point à point, mais modélise plutôt les interactions entre un certain nombre de parties en réseau, ce qui, à notre avis, sera un gros problème.

Pendant ce temps, dans le pays WASM, WASI introduit une API basée sur les capacités. Il semble qu'il pourrait y avoir une "synergie" intéressante ici.

Cela dit, plusieurs objectifs de conception de Cap'n Proto peuvent ne pas avoir de sens pour le cas d'utilisation spécifique de FFI :

  • Les messages Cap'n Proto sont conçus pour être indépendants de la position et contigus afin qu'ils puissent être transmis et partagés entre les espaces d'adressage. Les pointeurs sont relatifs et tous les objets d'un message doivent être alloués en mémoire contiguë, ou au moins un petit nombre de segments. Cela complique considérablement le modèle d'utilisation par rapport aux objets natifs. Lorsque vous utilisez FFI dans le même espace mémoire linéaire, cette surcharge est gaspillée, car vous pourriez très bien passer des pointeurs natifs pour perdre des objets de tas.
  • Les messages Cap'n Proto sont conçus pour être compatibles en amont et en aval entre les versions de schéma, y ​​compris la possibilité de copier des objets et des sous-arbres sans perte sans connaître le schéma. Cela nécessite que certaines informations de type léger soient stockées directement dans le contenu, que Cap'n Proto encode sous forme de métadonnées sur chaque pointeur. Si deux modules communiquant sur un FFI sont compilés en même temps, alors il n'y a pas besoin de ces métadonnées.
  • Les garanties de pipeline, de raccourcissement de chemin et de commande de Cap'n Proto RPC ont du sens lorsqu'il existe une latence non négligeable entre un appelant et un appelé. FFI sur un seul processeur n'a pas une telle latence, auquel cas la machine de pipeline de promesse ne fait probablement que gaspiller des cycles.

En bref, je pense que lorsque vous avez des modules déployés indépendamment dans des bacs à sable séparés qui se parlent, Cap'n Proto a beaucoup de sens. Mais pour les modules déployés simultanément dans un seul bac à sable, c'est probablement exagéré.

Merci pour les commentaires!

Les pointeurs sont relatifs et tous les objets d'un message doivent être alloués en mémoire contiguë, ou au moins un petit nombre de segments. Cela complique considérablement le modèle d'utilisation par rapport aux objets natifs. Lorsque vous utilisez FFI dans le même espace mémoire linéaire, cette surcharge est gaspillée, car vous pourriez très bien passer des pointeurs natifs pour perdre des objets de tas.

Je ne sais pas dans quelle mesure une approche de mémoire linéaire partagée est réalisable pour wasm (voir ci-dessus).

Cela dit, de toute façon, je ne pense pas que la surcharge des pointeurs relatifs serait si mauvaise. WebAssembly utilise déjà des décalages par rapport au début de la mémoire linéaire, et les implémentations ont des astuces pour optimiser les instructions ADD dans la plupart des cas (je pense), donc la surcharge liée à l'utilisation de pointeurs relatifs pourrait probablement être également optimisée.

Les messages Cap'n Proto sont conçus pour être compatibles en amont et en aval entre les versions de schéma, y ​​compris la possibilité de copier des objets et des sous-arbres sans perte sans connaître le schéma. [...] Si deux modules communiquant sur un FFI sont compilés en même temps, alors il n'y a pas besoin de ces métadonnées.

Je ne pense pas que ce soit vrai. Avoir un moyen pour les modules de définir des types rétrocompatibles à leurs limites permet à wasm d'utiliser un modèle d'arbre de dépendance, tout en évitant principalement le problème de diamant de dépendance de Haskell.

Une plus grande source de surcharge inutile serait la façon dont capnproto xor compare ses variables à leurs valeurs par défaut, ce qui est utile lorsque les octets zéro sont compressés, mais contre-productif dans les workflows à copie zéro.

Je ne sais pas dans quelle mesure une approche de mémoire linéaire partagée est réalisable pour wasm (voir ci-dessus).

Ah, TBH Je ne pense pas avoir assez de contexte pour suivre cette partie de la discussion. Si vous n'avez pas d'espace d'adressage partagé, alors oui, Cap'n Proto commence à avoir beaucoup de sens.

Je suis heureux de fournir des conseils sur la façon de concevoir des formats comme celui-ci. FWIW, il y a quelques petites choses que je changerais dans Cap'n Proto si je ne me souciais pas de la compatibilité avec les applications qui existent déjà aujourd'hui... c'est surtout, comme, les détails d'encodage de pointeur de bas niveau, cependant.

Une plus grande source de surcharge inutile serait la façon dont capnproto xe ses variables par rapport à leurs valeurs par défaut, ce qui est utile lorsque les octets zéro sont compressés, mais contre-productif dans les flux de travail à copie zéro.

Un peu hors sujet, mais la chose XOR est une optimisation, pas une surcharge, même dans le cas de zéro copie. Cela garantit que toutes les structures sont initialisées à zéro, ce qui signifie que vous n'avez pas à effectuer d'initialisation sur l'allocation d'objets si le tampon est déjà mis à zéro (ce qui serait souvent le cas de toute façon). Un XOR contre une constante de compilation coûte probablement 1 cycle alors que tout type d'accès mémoire coûtera beaucoup plus cher.

@lukewagner Des réflexions sur la partie "partage de la mémoire linéaire" ?

Je pense qu'il existe des cas d'utilisation pour le partage et le non-partage de la mémoire linéaire et, en fin de compte, les outils doivent prendre en charge les deux :

Le partage a du sens là où une application native utiliserait aujourd'hui une liaison statique ou dynamique : lorsque tout le code combiné est entièrement fiable et que sa combinaison utilise la même chaîne d'outils ou une ABI rigoureusement définie. C'est plus un modèle de composition logicielle plus fragile, cependant.

Ne pas partager la mémoire est logique pour une collection de modules couplés plus lâchement, où la conception classique de style Unix placerait le code dans des processus séparés connectés par des tuyaux. Personnellement, je pense que c'est la direction la plus excitante/futuriste pour un écosystème logiciel plus compositionnel et j'ai donc préconisé que ce soit la valeur par défaut pour toute chaîne d'outils visant à participer à l'écosystème ESM/npm via l' intégration ESM (et en effet que c'est le cas aujourd'hui avec le wasm-pack/wasm-bindgen de Rust). L'utilisation d'un mécanisme dans le voisinage général de Web IDL Bindings ou de l'extension que vous avez proposée a beaucoup de sens pour moi en tant que forme de RPC efficace, ergonomique, typé (sync ou async).

Après avoir finalement lu ceci en entier, cela ressemble beaucoup à ma réflexion dans ce domaine (que cette zone de commentaire est trop courte pour contenir ?).

En particulier, j'ai pensé au problème de communication inter-module comme étant mieux décrit avec un schéma. C'est-à-dire que nous n'avons pas besoin du format de sérialisation Cap'nProto, nous pouvons simplement utiliser le schéma. Je n'ai pas d'opinion sur le langage de schéma de Cap'nProto spécifiquement pour le moment.

Du point de vue WASI/ESM+npm, une solution de cette forme me semble la plus logique. C'est une abstraction sur les ABI, sans dépendre d'une ABI partagée. Il permet essentiellement de décrire une interface avec une API schema-lang et d'appeler au-delà de ces frontières linguistiques avec des ABI d'apparence native aux deux extrémités, laissant l'hôte gérer la traduction.

En particulier, cela ne subsume pas le cas d'utilisation pour avoir plus de coordination avec un autre module : si vous savez avec certitude que vous pouvez partager une ABI, vous pouvez en fait simplement utiliser une ABI, n'importe quelle ABI, que ce soit C ou Haskell. Si vous contrôlez et compilez tous les wasm en question, c'est un problème beaucoup plus facile à résoudre. Ce n'est que lorsque vous entrez dans le cas npm où vous chargez du code inconnu arbitraire et que vous ne connaissez pas sa langue source, que quelque chose comme l'interopérabilité au niveau du schéma entre les modules devient incroyablement attrayant. Parce que nous pouvons soit utiliser l'écran LCD de wasm lui-même - qui, je prédis, suivra un arc similaire aux bibliothèques natives, et utiliser le C ABI - ou nous pouvons utiliser l'écran LCD des langues, codé dans le langage de schéma. Et le schéma peut être plus flexible en faisant de l'exigence 2) une exigence souple, par exemple, il devrait être possible de convertir efficacement de C à Rust en Nim, mais C à Haskell ayant plus de frais généraux n'est pas un problème.

En particulier, j'ai pensé au problème de communication inter-module comme étant mieux décrit avec un schéma. C'est-à-dire que nous n'avons pas besoin d'un [a] format de sérialisation, nous pouvons simplement utiliser le schéma.

J'ai tendance à être d'accord avec le premier, mais je ne suis pas sûr que le second suive. Qui implémente le schéma ? Même si l'hôte effectue le transport, à un moment donné, vous devez définir quelles valeurs/octets Wasm sont réellement consommés/produits aux deux extrémités, et chaque module doit apporter ses propres données sous une forme que l'hôte comprend. Il peut même y avoir plusieurs formulaires disponibles, mais ce n'est toujours pas différent d'un format de sérialisation, juste un peu plus de haut niveau.

il devrait être possible de convertir efficacement de C à Rust à Nim, C à Haskell ayant plus de frais généraux n'est pas un dealbreaker.

Peut-être pas, mais vous devez être conscient des implications. Privilégier les langages de type C signifie que Haskell n'utiliserait pas cette abstraction pour les modules Haskell, en raison de la surcharge induite. Cela signifie à son tour qu'il ne participerait pas au même écosystème "npm" pour ses propres bibliothèques.

Et "Haskell" ici n'est qu'un substitut pour à peu près tous les langages de haut niveau. La grande majorité des langages ne sont pas de type C.

Je ne prétends pas avoir une meilleure solution, mais je pense que nous devons rester réalistes quant à l'efficacité et à l'attractivité d'une seule ABI ou abstraction de schéma pour la population générale des langues, au-delà du style FFI habituel d'interopérabilité à sens unique. En particulier, je ne suis pas convaincu qu'un écosystème de paquets panlinguistiques soit un résultat trop réaliste.

Privilégier les langages de type C signifie que Haskell n'utiliserait pas cette abstraction pour les modules Haskell, en raison de la surcharge induite. Cela signifie à son tour qu'il ne participerait pas au même écosystème "npm" pour ses propres bibliothèques.

Et "Haskell" ici n'est qu'un substitut pour à peu près tous les langages de haut niveau. La grande majorité des langages ne sont pas de type C.

Pourriez-vous donner des cas d'utilisation spécifiques? Idéalement, des bibliothèques existantes en Haskell ou dans un autre langage qui seraient difficiles à traduire en schéma de sérialisation ?

Je soupçonne que cela se résumera principalement aux bibliothèques utilitaires par rapport aux bibliothèques commerciales. Par exemple, les conteneurs, les algorithmes de tri et d'autres utilitaires reposant sur les génériques du langage ne se traduiront pas bien en wasm, mais les analyseurs, les widgets d'interface graphique et les outils de système de fichiers le feront.

@PoignardAzur , il n'est pas difficile de les traduire, mais cela les oblige à copier (sérialiser/désérialiser) tous les arguments/résultats aux deux extrémités de chaque appel intermodule. De toute évidence, vous ne voulez pas payer ce coût pour chaque appel à la bibliothèque interne à la langue.

Dans Haskell en particulier, vous avez également le problème supplémentaire que la copie est incompatible avec la sémantique de la paresse. Dans d'autres langues, il peut être incompatible avec les données avec état.

Qui implémente le schéma ? Même si l'hôte effectue le transport, à un moment donné, vous devez définir quelles valeurs/octets Wasm sont réellement consommés/produits aux deux extrémités, et chaque module doit apporter ses propres données sous une forme que l'hôte comprend. Il peut même y avoir plusieurs formulaires disponibles, mais ce n'est toujours pas différent d'un format de sérialisation, juste un peu plus de haut niveau.

L'hôte implémente le schéma. Le schéma ne décrit pas du tout les octets et laisse cela être un détail d'implémentation. Cela emprunte à la conception de la proposition de liaisons WebIDL, dans laquelle le bit intéressant réside dans les conversions des structures C en types WebIDL. Ce type de conception utilise les types d'interface abstraite Wasm (je suggère l'acronyme : WAIT) au lieu des types WebIDL. Dans la proposition WebIDL, nous n'avons pas besoin ou ne voulons pas imposer une représentation binaire des données lorsqu'elles ont été "traduites en WebIDL", car nous voulons pouvoir passer directement de wasm aux API de navigateur sans arrêt entre les deux.

Privilégier les langages de type C signifie que Haskell n'utiliserait pas cette abstraction pour les modules Haskell, en raison de la surcharge induite.

Ah, d'accord à 100 %. J'aurais dû terminer l'exemple pour que cela soit plus clair : pendant ce temps, Haskell to Elm to C# peut être tout aussi efficace (en supposant qu'ils utilisent les types wasm gc), mais C# to Rust peut avoir une surcharge. Je ne pense pas qu'il y ait un moyen d'éviter les frais généraux lors du saut à travers les paradigmes linguistiques.

Je pense que votre observation est correcte que nous devons essayer d'éviter de privilégier des langues, car si nous ne parvenons pas à être suffisamment ergonomiques + performants pour une langue donnée, ils ne verront pas autant d'intérêt à utiliser l'interface, et ne participeront donc pas à l'écosystème .

Je pense qu'en faisant abstraction des types et en ne spécifiant pas de format de fil, nous sommes en mesure de donner beaucoup plus de latitude aux hôtes pour optimiser. Je pense qu'un non-objectif est de dire "les chaînes de style C sont efficaces", mais c'est un objectif de dire "les langages qui [veulent] raisonner sur les chaînes de style C peuvent le faire efficacement". Ou, aucun format ne devrait être béni, mais certaines chaînes d'appels compatibles devraient être efficaces, et toutes les chaînes d'appels devraient être possibles.

Par chaînes d'appel, j'entends :

  1. C -> Rouille -> Zig -> Fortran, efficace
  2. Haskell -> C# -> Haskell, efficace
  3. C -> Haskell -> Rust -> Scheme, inefficace
  4. Java -> Rust, inefficace

Et "Haskell" ici n'est qu'un substitut pour à peu près tous les langages de haut niveau. La grande majorité des langages ne sont pas de type C.

Oui, c'était mon intention d'utiliser Haskell comme langage concret. (Bien que Nim soit probablement un mauvais exemple de langage de type C, car il utilise également beaucoup GC)

--

Une autre façon dont j'ai pensé aux types abstraits est en tant qu'IR. De la même manière que LLVM décrit une relation plusieurs-à-un-à-plusieurs (plusieurs langues -> une IR -> plusieurs cibles), les types abstraits wasm peuvent arbitrer un mappage plusieurs-à-plusieurs, de langues + hôtes -> langues+hôtes. Quelque chose dans cet espace de conception prend le problème de mappage N^2 et le transforme en un problème N+N.

L'hôte implémente le schéma.

Eh bien, cela ne peut pas suffire, chaque module doit implémenter quelque chose pour que l'hôte puisse trouver les données. Si l'hôte attend une disposition C, vous devez définir cette disposition C, et chaque client doit marshaler/désorganiser vers/depuis cela en interne. Ce n'est pas si différent d'un format de sérialisation.

Même si nous l'avons fait, il est toujours utile de définir un format de sérialisation, par exemple, pour les applications qui ont besoin de transférer des données entre des moteurs uniques, par exemple via un réseau ou une persistance basée sur des fichiers.

Eh bien, cela ne peut pas suffire, chaque module doit implémenter quelque chose pour que l'hôte puisse trouver les données. Si l'hôte attend une mise en page C, vous devez définir cette mise en page C

L'hôte ne doit rien attendre , mais doit tout supporter . Plus concrètement, en utilisant la proposition webidl-bindings comme exemple illustratif , nous avons utf8-cstr et utf8-str , qui prennent respectivement i32 (ptr) et i32 (ptr), i32 (len) . Il n'est pas nécessaire d'exiger dans la spécification que "l'hôte représente cela en interne sous forme de chaînes C" pour pouvoir concrètement mapper entre eux.
Donc, chaque module implémente quelque chose, oui, mais la représentation des données n'a pas besoin d'être exprimée dans la couche de données/schéma abstraite, c'est ainsi que cela nous donne la propriété d'abstraire sur cette disposition de données.
De plus, cela est extensible au niveau de la couche de liaisons qui établit une correspondance entre les types wasm concrets et les types intermédiaires abstraits. Pour ajouter le support Haskell (qui modélise les chaînes à la fois comme des listes de caractères et des tableaux de caractères), nous pouvons ajouter des liaisons utf8-cons-str et utf8-array-str , qui attendent (et valident) les types wasm de (en utilisant syntaxe de proposition gc) (type $haskellString (struct (field i8) (field (ref $haskellString)))) et (type $haskellText (array i8)) .

C'est-à-dire que chaque module décide de la provenance des données. Les types abstraits + les liaisons permettent des conversions entre la façon dont les modules voient les mêmes données, sans considérer une seule représentation comme étant en quelque sorte canonique.

Un format de sérialisation pour (un sous-ensemble de) les types abstraits serait utile, mais peut être implémenté en tant que consommateur du format de schéma, et je pense qu'il s'agit d'une préoccupation orthogonale. Je crois que FIDL a un format de sérialisation pour le sous-ensemble de types qui peuvent être transférés sur le réseau, interdit la matérialisation des poignées opaques, tout en permettant aux poignées opaques de transférer au sein d'un système (IPC oui, RPC non).

Ce que vous décrivez est assez proche de ce que j'avais en tête, avec une grande mise en garde : le schéma doit avoir un petit nombre fixe de représentations possibles. Le pontage entre différentes représentations est un problème N*N, ce qui signifie que le nombre de représentations doit être réduit pour éviter de surcharger les écrivains VM.

Ainsi, l'ajout de la prise en charge de Haskell nécessiterait l'utilisation de liaisons existantes, et non l'ajout de liaisons personnalisées.

Quelques représentations possibles :

  • Structures et pointeurs de style C.
  • Octets capnproto réels.
  • cours de GC.
  • Fermetures servant de getters et setters.
  • Dictionnaires de style Python.

L'idée étant que bien que chaque langue soit différente et qu'il existe des valeurs aberrantes extrêmes, vous pouvez placer un assez grand nombre de langues dans un assez petit nombre de catégories.

Ainsi, l'ajout de la prise en charge de Haskell nécessiterait l'utilisation de liaisons existantes, et non l'ajout de liaisons personnalisées.

Cela dépend du niveau de granularité des liaisons existantes auquel vous pensez. N<->N langues codant chaque liaison possible est 2*N*N, mais N<->IR est 2*N, et plus loin si vous dites N<->[styles de liaison communs]<->IR, où le nombre des formats courants est k, vous parlez de 2*k, où k < N.

En particulier, avec le schéma que je décris, vous obtenez Scheme gratuitement (il réutiliserait utf8-cons-str ). Si Java modélise également les chaînes sous forme de tableaux de caractères, il s'agit d'une liaison utf8-array-str . Si Nim utilise string_views sous le capot, utf8-str . Si Zig est conforme à l'ABI C, utf8-cstr . (Je ne connais pas les ABI de Java/Nim/Zig, donc je ne les ai pas mentionnés comme exemples concrets plus tôt)

Donc, oui nous ne voulons pas ajouter de binding pour chaque langue possible, mais nous pouvons ajouter quelques bindings par type IR pour couvrir la grande majorité des langues. Je pense que l'espace de désaccord ici est, combien de liaisons sont "quelques-uns", quel est le point idéal, à quel point les critères doivent-ils être stricts pour savoir si nous soutenons l'ABI d'une langue ?
Je n'ai pas de réponses précises à ces questions. J'essaie de donner plein d'exemples concrets pour mieux illustrer l'espace de conception.

De plus, j'affirmerais que nous voulons absolument spécifier plusieurs liaisons par type abstrait, pour éviter de privilégier un style de données. Si la seule liaison que nous exposons à Strings est utf8-cstr , alors tous les langages n'ayant pas C-ABI doivent gérer cette incompatibilité. Je suis d'accord avec l'augmentation de la complexité de l'écriture de VM, un facteur non négligeable.
Le travail total dans l'écosystème est O (effort VM + effort d'implémentation du langage), et ces deux termes s'échelonnent d'une manière ou d'une autre avec N = nombre de langues. Soit M=nombre d'embedders, k=nombre de liaisons et a=nombre moyen de liaisons qu'un langage donné doit implémenter, avec a<=k. Au minimum, nous avons M+N implémentations wasm séparées.
L'approche naïve, avec chaque langage N implémentant indépendamment ABI FFI avec tous les autres langages N, est O(M + N*N). C'est ce que nous avons sur les systèmes natifs, ce qui est un signal fort que tout O(N*N) conduira à des résultats non différents des systèmes natifs.
Deuxième approche naïve, où chaque VM doit implémenter toutes les liaisons N*N : O(M*N*N + N), ce qui est clairement encore pire.
Ce que nous essayons de proposer, c'est que nous avons k liaisons qui correspondent à un langage abstrait, qui renvoient à toutes les langues. Cela implique k travail pour chaque VM. Pour chaque langue, nous avons seulement besoin d'implémenter un sous-ensemble des liaisons. Le travail total est M*k + N*a, qui est O(M*k + N*k). Notez que dans le cas où k=N, le côté VM est "seulement" M*N, donc pour une VM donnée, il est "seulement" linéaire dans le nombre de langues. Il est clair que nous voulons k << N, car sinon c'est toujours O(N*N), pas mieux que la première solution.
Pourtant, O(M*k + N*k) est beaucoup plus agréable au goût. Si k est O (1), cela rend l'ensemble de l'écosystème linéaire dans le nombre d'implémentations, qui est notre limite inférieure sur la quantité d'effort impliqué. Une limite plus probable est que k soit O(log(N)), ce dont je suis toujours assez satisfait.

Ce qui est une longue façon de dire, je suis tout à fait d'accord avec l'augmentation de la complexité de la machine virtuelle pour cette fonctionnalité par un facteur constant.

nous pouvons ajouter quelques liaisons par type d'IR pour couvrir la grande majorité des langues.

C'est l'hypothèse sous-jacente cruciale qui, à mon avis, n'est tout simplement pas vraie. Mon expérience est qu'il y a (au moins !) autant de choix de représentation qu'il y a d'implémentations de langage. Et ils peuvent être arbitrairement compliqués.

Prenez V8, qui à lui seul possède quelques dizaines (!) de représentations pour les chaînes, y compris des encodages différents, des cordes hétérogènes, etc.

Le cas Haskell est bien plus compliqué que vous ne le décrivez, car les listes en Haskell sont paresseuses, ce qui signifie que pour chaque caractère d'une chaîne, vous devrez peut-être invoquer un thunk.

D'autres langages utilisent des représentations amusantes pour la longueur d'une chaîne, ou ne la stockent pas explicitement mais nécessitent qu'elle soit calculée.

Ces deux exemples montrent déjà qu'une disposition de données déclarative ne suffit pas, vous auriez souvent besoin de pouvoir invoquer du code d'exécution, qui à son tour peut avoir ses propres conventions d'appel.

Et ce ne sont que des chaînes, qui sont conceptuellement un type de données assez simple. Je ne veux même pas penser au nombre infini de façons dont les langages représentent les types de produits (tuples/structs/objects).

Et puis il y a le côté réception, où il faudrait pouvoir créer toutes ces structures de données !

Je pense donc qu'il est tout à fait irréaliste que nous soyons un jour proches du soutien de la "grande majorité des langues". Au lieu de cela, nous commencerions à en privilégier quelques-uns, tout en développant déjà un grand zoo de trucs arbitraires. Cela semble fatal à plusieurs niveaux.

Mon expérience est qu'il y a (au moins !) autant de choix de représentation qu'il y a d'implémentations de langage. Et ils peuvent être arbitrairement compliqués.

Je suis complètement d'accord. Je pense qu'essayer de concevoir des types qui couvriront d'une manière ou d'une autre la plupart des représentations internes des données de la langue n'est tout simplement pas traitable et rendra l'écosystème trop compliqué.

Au final, il n'y a qu'un plus petit dénominateur commun entre les langues en matière de données : celui du "tampon". Toutes les langues peuvent les lire et les construire. Ils sont efficaces et simples. Oui, ils privilégient les langues qui sont directement capables d'adresser leur contenu, mais je ne pense pas que ce soit une inégalité qui soit résolue en promouvant (paresseux) contre les cellules au même niveau de support d'une manière ou d'une autre.

En fait, vous pouvez aller très loin avec un seul type de données : le couple pointeur + lentille. Ensuite, vous avez juste besoin d'un "schéma" qui dit ce qu'il y a dans ces octets. Est-ce qu'il promet d'être conforme à l'UTF-8 ? Le dernier octet garanti est-il toujours 0 ? Les premiers 4/8 octets sont-ils des champs de longueur/capacité ? Est-ce que tous ces octets sont des petits flottants endian qui peuvent être envoyés directement à WebGL ? Ces octets sont-ils peut-être le schéma X d'un format de sérialisation existant ? etc?

Je proposerais une spécification de schéma très simple qui peut répondre à toutes ces questions (pas un format de sérialisation existant, mais quelque chose de plus bas niveau, plus simple et spécifique à wasm). Il devient alors la charge de chaque langage de lire et d'écrire efficacement ces tampons dans le format spécifié. Les couches intermédiaires peuvent ensuite contourner les tampons à l'aveuglette sans traitement, soit par copie, soit, si possible, par référence/vue.

C'est l'hypothèse sous-jacente cruciale qui, à mon avis, n'est tout simplement pas vraie. Mon expérience est qu'il y a (au moins !) autant de choix de représentation qu'il y a d'implémentations de langage. Et ils peuvent être arbitrairement compliqués.

Je suis d'accord que c'est l'hypothèse sous-jacente cruciale. Je ne suis pas d'accord pour dire que ce n'est pas vrai, bien que je pense à cause d'une nuance sémantique que je ne pense pas avoir clairement expliquée.

Les liaisons ne sont pas censées correspondre parfaitement à toutes les représentations linguistiques, elles doivent seulement correspondre suffisamment à toutes les langues.

C'est une hypothèse sous-jacente cruciale qui rend cela du tout traitable, quel que soit l'encodage. La proposition de @aardappel d'aller dans l'autre sens et de réifier réellement les octets dans un tampon décodable repose également sur l'hypothèse qu'il s'agit d'un codage avec perte de la sémantique d'un programme donné, certains plus avec perte que d'autres.

Le cas Haskell est bien plus compliqué que vous ne le décrivez, car les listes en Haskell sont paresseuses, ce qui signifie que pour chaque caractère d'une chaîne, vous devrez peut-être invoquer un thunk.

En fait, j'avais oublié cela, mais je ne pense pas que ce soit important. L'objectif n'est pas de représenter les chaînes Haskell tout en préservant toutes leurs nuances sémantiques à travers une frontière de module. L'objectif est de convertir une chaîne Haskell en une chaîne IR, par valeur. Cela implique nécessairement le calcul de la chaîne entière.

Ces deux exemples montrent déjà qu'une disposition de données déclarative ne suffit pas, vous auriez souvent besoin de pouvoir invoquer du code d'exécution, qui à son tour peut avoir ses propres conventions d'appel.

La façon de modéliser cela, quelle que soit la façon dont nous spécifions les liaisons (ou même SI nous spécifions quoi que ce soit pour les liaisons), est de gérer cela dans l'espace utilisateur. Si la représentation d'un type par un langage ne correspond pas directement à une liaison, elle devra être convertie en une représentation qui le fait. Par exemple, si les chaînes de Haskell sont réellement représentées par (type $haskellString (struct (field i8) (field (func (result (ref $haskellString)))))) , il devra soit convertir en une chaîne stricte et utiliser une liaison de type Scheme, soit en un tableau Text et utiliser une liaison de type Java, soit en un CFFIString et utilisez une liaison de type C. La proposition de valeur d'avoir plusieurs types de liaison imparfaits est que certains d'entre eux sont moins gênants pour Haskell que d'autres, et il est possible de construire des types Wasm-FFI sans avoir besoin de modifier le compilateur.

Et ce ne sont que des chaînes, qui sont conceptuellement un type de données assez simple. Je ne veux même pas penser au nombre infini de façons dont les langages représentent les types de produits (tuples/structs/objects).
Et puis il y a le côté réception, où il faudrait pouvoir créer toutes ces structures de données !

Je suis confus, je vois qu'en disant « la liaison entre les langues est complètement impossible, nous ne devrions donc pas essayer du tout », mais je pense que ce que vous avez dit ressemble davantage à « Je ne crois pas que le l'approche décrite ici est traitable", ce qui semble beaucoup plus raisonnable. En particulier, mon objection à cette argumentation est qu'elle ne décrit pas une voie à suivre. Étant donné que ce problème est « très difficile », que faisons-nous ?

Au lieu de cela, nous commencerions à privilégier quelques

Presque sûrement. La question est de savoir dans quelle mesure les quelques langues supportées de manière optimale sont privilégiées ? Quelle marge de manœuvre les langues défavorisées ont-elles pour trouver une solution ergonomique ?

tout en développant déjà un grand zoo de trucs arbitraires.

Je ne sais pas ce que vous entendez par là. Mon interprétation de ce qui est arbitraire est "quelles langues supportons-nous", mais c'est la même chose que "privilégier quelques-unes", ce qui serait un double comptage. Et donc cela ne serait fatalement défectueux qu'à un seul niveau, plutôt que plusieurs :D

@aardappel la version courte est que c'est mon plan de sauvegarde si l'approche abstraite déclarative échoue: allez dans la direction totalement opposée et décrivez un format de sérialisation. L'observation est faite que le Web lui-même est construit presque entièrement sur le texte, car il s'agit d'un dénominateur commun extrêmement faible. Le texte est trivialement compréhensible par tous les outils, et donc quelque chose comme le Web est possible en plus.

La plus grande préoccupation que j'ai avec les données dans les tampons qui pourraient rendre cette approche insoluble est de savoir comment pouvons-nous gérer les types de référence ? La meilleure idée que j'ai là-bas est de partager des tables et de sérialiser des index dans celles-ci, mais je n'ai pas une idée complète de la façon dont cela fonctionnerait réellement.

@jgravelle-google peut-être que les types de référence devraient être séparés? Ainsi, une signature brute de fonction donnée peut être ref ref i32 i32 i32 i32 qui est en fait 2 anyrefs suivis de 2 tampons d'un type particulier (spécifié dans le schéma hypothétique ci-dessus).

(Soit dit en passant, je ne connais pas Haskell, mais l'idée d'une chaîne en tant que listes de caractères paresseuses m'épate. Quand la liste chaînée d'octets est-elle toujours le moyen le plus efficace ou le plus pratique de faire quoi que ce

La plus grande préoccupation que j'ai avec les données dans les tampons qui pourraient rendre cette approche insoluble est de savoir comment pouvons-nous gérer les types de référence ? La meilleure idée que j'ai là-bas est de partager des tables et de sérialiser des index dans celles-ci, mais je n'ai pas une idée complète de la façon dont cela fonctionnerait réellement.

C'est l'une des raisons pour lesquelles j'ai proposé capnproto comme encodage. Les tables de référence sont plus ou moins intégrées.

Dans tous les cas, nous voudrions que n'importe quel format choisi ait des types de référence en tant que citoyens de première classe, qui puissent être placés n'importe où dans le graphique de données. (par exemple dans les options, les tableaux, les variantes, etc.)

Merci à tous pour vos retours.

Je pense que nous commençons à atteindre le point où nous rechapons la plupart du temps les mêmes arguments avec très peu de variations, je vais donc mettre à jour la proposition et essayer de répondre aux préoccupations de chacun. Je recommencerai probablement avec un nouveau problème une fois que j'aurai fini d'écrire la proposition mise à jour.

Pour résumer les commentaires jusqu'à présent:

  • Il y a très peu de consensus sur le format de sérialisation, le cas échéant, qui serait le meilleur pour wasm. Les alternatives incluent FlatBuffers, les graphes de structure C ABI avec des pointeurs bruts, un format IDL wasm sur mesure, ou une combinaison de ce qui précède.

  • La proposition a besoin d'un espace négatif plus fort. Plusieurs lecteurs ont été confondus par la portée de la proposition et quels cas d'utilisation elle était censée faciliter (liaison statique vs dynamique, module à hôte vs hôte à hôte, partage de données mutables vs transmission de messages immuables).

  • @lukewagner a exprimé un certain enthousiasme pour le potentiel d'un système de modules reliant des modules mutuellement méfiants, lorsqu'il est combiné à l'intégration ESM. La prochaine itération de la proposition devrait développer ce potentiel; en particulier, je pense qu'avoir un système de type rétrocompatible permettrait à wasm d'utiliser un modèle d'arbre de dépendance de type npm, tout en évitant le gros du problème du diamant de dépendance.

  • Il y a eu peu de retours sur le sujet des capacités, c'est-à-dire des valeurs opaques qui peuvent être renvoyées et transmises, mais pas créées à partir de données brutes. Je considère cela comme un signe que la prochaine itération devrait mettre beaucoup plus l'accent sur eux.

  • Plusieurs lecteurs ont exprimé des inquiétudes quant à la faisabilité d'un système de types inter-langues. Ces préoccupations sont quelque peu vagues et difficiles à définir, en partie parce que le sujet est très abstrait, en partie parce que la proposition jusqu'à présent est assez vague elle-même, ce qui fait écho au problème de la poule et de l'œuf de

    • Se concentrer trop sur les langues très visibles, laissant plus de langues de niche derrière.
    • Avoir une abstraction qui fuit qui essaie d'être trop générale, mais ne couvre pas le cas d'utilisation de quiconque de manière pratique ou efficace.
    • Couvrant trop de cas, créant une implémentation N*N pléthorique qui souffre toujours des problèmes ci-dessus.

La prochaine itération de la proposition doit répondre à ces préoccupations, d'une manière ou d'une autre.

En particulier, je pense que la discussion bénéficierait beaucoup de quelques exemples d'hommes de paille pour raisonner. Ces exemples incluraient au moins deux bibliothèques, écrites dans des langages différents avec des mises en page de données différentes (par exemple C++ et Java), consommées par au moins deux programmes minimaux dans des langages différents (par exemple Rust et Python), pour illustrer le problème n*n et stratégies pour y remédier.

En outre, comme les lecteurs l'ont souligné, la proposition mêle actuellement l'idée d'un schéma de type avec l'idée de sa représentation. Alors que la proposition fait un bon travail pour exposer les exigences d'un format de représentation, elle doit d'abord exposer les exigences d'un schéma de type abstrait.

Quoi qu'il en soit, merci encore à tous ceux qui ont participé à cette discussion jusqu'à présent. Je vais essayer de faire une proposition plus complète dès que possible. Si quelqu'un ici souhaite m'aider à l'écrire, n'hésitez pas à m'envoyer un e-mail !

@jgravelle-google :

La façon de modéliser cela, quelle que soit la façon dont nous spécifions les liaisons (ou même SI nous spécifions quoi que ce soit pour les liaisons), est de gérer cela dans l'espace utilisateur.

Oui, je suis d'accord, et mon argument est similaire à celui de @aardappel : si c'est ce que nous devons généralement faire de toute façon, nous devrions simplement l'accepter et ne pas essayer des choses ad hoc pour améliorer certains cas étranges. Userland est l'endroit où la conversion appartient, dans l'esprit de tout le reste de Wasm.

Je suis confus, je vois que dire "la liaison entre les langues est complètement impossible donc nous ne devrions pas essayer du tout",

Je pense qu'il est totalement souhaitable de définir un DDL (schéma de types) pour l'interopérabilité des données entre les langues. Je ne pense tout simplement pas qu'il soit possible de créer des conversions dans Wasm. Les conversions doivent être mises en œuvre dans l'espace utilisateur. La couche de liaison prescrit simplement un format que le code utilisateur doit produire/consommer.

tout en développant déjà un grand zoo de trucs arbitraires.
Je ne sais pas ce que vous entendez par là. Mon interprétation de ce qui est arbitraire est "quelles langues supportons-nous", mais c'est la même chose que "privilégier quelques-unes", ce qui serait un double comptage.

Désolé, je voulais dire que je soupçonne qu'il n'y aura rien de terriblement canonique à propos de ces conversions. Donc à la fois leur sélection est « arbitraire » et leur sémantique individuelle.

comment pouvons-nous gérer les types de référence?

Ah, c'est une bonne question. FWIW, nous essayons de résoudre ce problème pour un IDL/DDL pour la plate-forme Dfinity. Tant qu'il n'y a que anyref, la solution est assez simple : le format de sérialisation définit deux morceaux, une tranche mémoire projetant les données transparentes et une tranche table projetant les références contenues. Plusieurs types de référence nécessitent plusieurs tranches de table en conséquence. La question délicate est que faire une fois que l'ensemble des types ref n'est plus fini (par exemple avec des références de fonction typées).

Une fois que nous avons les types de GC, il devrait y avoir un autre moyen de fournir les données, qui est une valeur GC. Dans ce cas, les références ne sont pas un problème, car elles peuvent être librement mélangées.

@PoignardAzur :

Soit dit en passant, je ne connais pas Haskell, mais l'idée de chaîne en tant que listes paresseuses de caractères m'épate.

Oui, je crois que c'est largement considéré comme une erreur de nos jours. Mais cela montre à quel point il y a de la diversité même pour les types de données "simples".

@rossberg

Je pense qu'il est totalement souhaitable de définir un DDL (schéma de types) pour l'interopérabilité des données entre les langues. Je ne pense tout simplement pas qu'il soit possible de créer des conversions dans Wasm. Les conversions doivent être mises en œuvre dans l'espace utilisateur.

Je suis d'accord, et pour ajouter à cela: je suis sceptique quant à l'ajout de quelque chose à la spécification wasm pour cela parce que je ne pense pas que wasm ait plus besoin d'une solution inter-langue que d'autres plates-formes, et je ne pense pas que wasm a une plus grande capacité à mettre en œuvre une telle solution que d'autres plates-formes. Il n'y a évidemment rien de spécial pour moi à propos de wasm ici, et je ne sais donc pas pourquoi nous pouvons faire mieux que les solutions standard dans ce domaine, par exemple les tampons comme @aardappel l'a mentionné. (Mais je pense que l'expérimentation dans l'espace utilisateur est très intéressante, comme c'est le cas sur toutes les plateformes !)

La particularité de wasm, au moins sur le Web, ce sont les types d'API JavaScript/Web pour les chaînes et les tableaux, etc. Pouvoir interagir avec eux est évidemment important.

Je ne pense pas que wasm ait plus besoin d'une solution inter-langue que d'autres plates-formes

Je pense que oui. Être utilisé par défaut sur le Web signifie que le code peut et sera exécuté dans différents contextes. De la même manière que l'on pourrait <script src="http://some.other.site/jquery.js"> , j'adorerais voir des gens combiner les bibliothèques wasm de manière cross-origin. En raison des propriétés d'éphémère et de composabilité offertes par le Web, la valeur ajoutée de la possibilité de s'interfacer avec un module étranger est plus élevée qu'elle ne l'a jamais été sur les systèmes natifs.

et je ne pense pas que wasm ait une plus grande capacité à mettre en œuvre une telle solution que d'autres plates-formes.

Et je pense que oui. Étant donné que wasm est exécuté par un intégrateur / dans un hôte, la génération de code est effectivement abstraite. Pour cette raison, une machine virtuelle a beaucoup plus d'outils et de marge de manœuvre pour prendre en charge des constructions de niveau supérieur qui ne sont pas possibles sur les systèmes natifs.

Je pense donc que quelque chose dans cet espace est plus précieux et plus possible que sur d'autres systèmes, c'est pourquoi wasm est spécial dans ce contexte. Pour moi, l'interopérabilité JS est un cas particulier de la notion plus générale selon laquelle les modules wasm doivent être capables de parler à des choses externes avec des vues très différentes du monde.

Une voie à suivre pour cela est de pousser cela entièrement dans l'interopérabilité au niveau de l'outil pour le moment, et de différer la normalisation jusqu'à ce que nous ayons un format gagnant. Donc, si l'objectif est d'avoir l'écosystème prédominant du gestionnaire de packages wasm utilisant un format d'interface donné (et s'agit-il de NPM ou WAPM ou d'un gestionnaire de packages encore créé ?), alors cela peut se produire indépendamment de la normalisation. En théorie, nous pouvons standardiser ce que les gens font déjà, afin d'obtenir de meilleures performances, mais l'ergonomie peut être implémentée en mode utilisateur. Un risque est que le format interlangue gagnant ne se prête pas à l'optimisation, et nous nous retrouvons avec un standard de facto sous-optimal. Si nous pouvons concevoir un format avec l'intention de le standardiser plus tard (le style déclaratif dans une section personnalisée est généralement suffisant ?), cela supprime ce risque, mais retarde également toute amélioration des performances. Pour moi, la performance est l'une des motivations les moins excitantes pour avoir ce genre de chose, donc je suis plutôt d'accord avec ça, même si d'autres peuvent ne pas être d'accord.

(et s'agit-il de NPM ou WAPM ou d'un gestionnaire de packages encore créé ?)

Je pense qu'il est bien trop tôt pour que WAPM soit un gestionnaire de paquets viable. Nous avons besoin que des fonctionnalités telles que l'intégration ESM, WASI et certaines formes de liaisons inter-langues soient standardisées avant qu'un gestionnaire de packages wasm ne devienne réalisable.

En l'état, je ne pense pas que WAPM ait même une gestion des dépendances.

Je ne pense pas que wasm ait plus besoin d'une solution inter-langue que d'autres plates-formes

Je pense que oui. Être utilisé par défaut sur le Web signifie que le code peut et sera exécuté dans différents contextes. De la même manière que l'on pourrait

Les conversions doivent être mises en œuvre dans l'espace utilisateur. La couche de liaison prescrit simplement un format que le code utilisateur doit produire/consommer.

C'est un bon résumé pour moi.

Pendant que j'écris un nouveau brouillon, j'ai une question ouverte pour tout le monde sur ce fil :

Existe-t-il une bibliothèque existante que, si elle était compilée en WebAssembly, vous voudriez pouvoir l'utiliser à partir de n'importe quel langage ?

Je recherche essentiellement des cas d'utilisation potentiels sur lesquels baser la conception. J'ai le mien (en particulier, React, le moteur Bullet et les systèmes de plugins), mais j'aimerais plus d'exemples avec lesquels travailler.

@PoignardAzur De nombreux langages en C utilisent les mêmes bibliothèques d'expressions régulières compatibles Perl (PCRE), mais dans l'intégration du navigateur, ils devraient probablement utiliser l'API Regex de JS.

@PoignardAzur BoringSSL et libsodium me viennent à l'esprit.

Également l'implémentation RPC de Cap'n Proto, mais celle-ci est étrange : la couche _serialization_ de Cap'n Proto doit de manière réaliste être implémentée indépendamment dans chaque langue, car la majeure partie est une couche d'API large mais peu profonde qui doit être idiomatique et en ligne -amical. La couche RPC, OTOH, est étroite mais profonde. En principe, il devrait être possible d'utiliser l'implémentation RPC C++ derrière l'implémentation de sérialisation de n'importe quel langage arbitraire en passant des références de tableau d'octets codés capnp sur la limite FFI ...

Je pense que faire ce qui est proposé en fin de compte nécessiterait des modifications assez invasives de l'assemblage Web lui-même tel qu'il existe déjà - mais cela peut sans doute en valoir la peine.

Je voudrais noter que le monde SmallTalk a eu une expérience positive avec un tel effort qui pourrait être instructif dans leur développement de leur State Replication Protocol (SRP) qui est un protocole de sérialisation efficace qui peut représenter n'importe quel type de n'importe quelle taille assez efficacement. J'ai envisagé d'en faire une configuration de mémoire native pour une VM ou même un FPGA, mais je n'ai pas eu le temps de l'essayer. Je sais qu'il a été porté sur au moins un autre langage, Squeak, avec de bons résultats. Certainement quelque chose à lire comme ayant un fort chevauchement avec les problèmes, les défis et les expériences de cette proposition.

Je comprends pourquoi Web IDL était la proposition par défaut en tant que langage de liaison : c'est le langage de liaison historique et en quelque sorte mature pour le Web. Je soutiens grandement cette décision, et il est très probable que j'aurais fait la même chose. Néanmoins, nous pouvons reconnaître qu'il s'adapte à peine à d'autres contextes (comprendre, autres hôtes/langues). Wasm est conçu pour être indépendant de l'hôte ou indépendant de la langue/de la plate-forme. J'aime l'idée d'utiliser des technologies Web matures et de trouver un cas d'utilisation pour des scénarios non Web, mais dans le cas du Web IDL, cela semble vraiment lié au Web. C'est la raison pour laquelle je suis de très près ces conversations ici.

J'ai ouvert https://github.com/WebAssembly/webidl-bindings/issues/40 , ce qui m'a amené à poser une question ici car je n'en ai vu aucune mention (ou je l'ai manquée).

Dans toute l'histoire des liaisons, il n'est pas clair « qui » ​​est responsable de la génération des liaisons :

  • Est-ce un compilateur (qui transforme un programme en module Wasm) ?
  • Est-ce un auteur de programme (et donc, les reliures sont-elles écrites à la main) ?

Je pense que les deux sont valables. Et dans le cas de Web IDL, il semble montrer quelques limitations (voir le lien ci-dessus). Peut-être que je viens de manquer une étape importante du processus, et donc, pensez à oublier mon message.

Même si l'objectif est de « recentrer » le Web IDL pour qu'il soit moins centré sur le Web, à l'heure actuelle, il _est_ très centré sur le Web. Et des propositions surgissent pour proposer des alternatives, d'où ce fil conducteur. Par conséquent, je suis préoccupé par une fragmentation potentielle. Idéalement (et c'est ainsi que Wasm a été conçu jusqu'à présent), étant donné un module Wasm comprenant ses liaisons, il est possible de l'exécuter n'importe où tel quel. Avec des liaisons écrites en Web IDL, Cap'n' Proto, FlatBuffers, peu importe, je suis presque sûr que tous les compilateurs ou auteurs de programmes n'écriront pas les mêmes liaisons dans différentes syntaxes pour être vraiment multiplateformes. Curieusement, c'est un argument en faveur des liaisons manuscrites : les gens peuvent contribuer à un programme en écrivant des liaisons pour la plate-forme P. Mais admettons que ce ne sera pas du tout idéal.

Donc pour résumer : je suis préoccupé par une possible fragmentation entre les liaisons Web et non Web. Si un langage de liaison non Web est utilisé, serait-il implémenté de manière réaliste par les navigateurs Web ? Ils auraient à écrire des liaisons « Wasm ⟶ langage de liaison B ⟶ Web IDL ». Notez qu'il s'agit du même scénario pour tous les hôtes : Wasm ⟶ langage de liaison B ⟶ API hôte.

Pour ceux qui sont curieux, je travaille à Wasmer et je suis l'auteur des PHP- , Python- , Ruby- et Go- intégrations wasm. Nous commençons à avoir un beau terrain de jeu pour pirater différentes fixations pour des hôtes très différents. Si quelqu'un veut que j'intègre différentes solutions, que je recueille des retours ou que j'essaye d'expérimenter, nous sommes tous ouverts et prêts à collaborer et à y consacrer plus de ressources.

La direction actuelle dans les « liaisons webIDL » est probablement éloignée de webIDL
lui-même. Cependant, le dilemme est le suivant :

Le langage « naturel » pour exprimer inter-module et module-hôte
l'interopérabilité est nettement plus riche que le langage naturel de WASM. Cette
signifie que tout équivalent IDL utilisable va sembler assez arbitraire de
Le pov de WASM.

D'un autre côté, pour les gens qui voient le monde du point de vue du C/C++
(et Rust et ses semblables) tout ce qui est plus riche que le modèle de WASM risque d'être
inutilisable. On le voit déjà avec la difficulté d'intégrer ref
types dans la chaîne d'outils.

De plus, ce n'est pas un mandat direct de la WASM de soutenir
interopérabilité inter-langues. Ce ne devrait pas non plus être l'OMI.

(Il existe une version plus limitée de l'interopérabilité inter-langues que je
croire est non seulement supportable mais aussi critique : c'est dans tous nos
l'intérêt que les fournisseurs de capacités et les utilisateurs de capacités peuvent rencontrer
avec un minimum de frottement. (La capacité, c'est la gestion, mais j'ai
pas trouvé de meilleur terme.) Cela nécessite un style différent d'IDL et un
qui est plus facile à mettre en œuvre que ce qui serait nécessaire pour une inter-langue complète
interopérabilité)

Conclusion : il y a lieu d'avoir un équivalent IDL, et nous en avons besoin
pour soutenir l'interopérabilité au-delà des frontières de propriété. Qu'est-ce que ça finit
être n'est pas clair pour le moment.

Le lundi 24 juin 2019 à 7h02 Ivan Enderlin [email protected]
a écrit:

Je comprends pourquoi Web IDL était la proposition par défaut en tant que langage de liaison :
C'est le langage de liaison historique et en quelque sorte mature pour le Web. je
appuie grandement cette décision, et il est fort probable que j'aurais pris la
même. Néanmoins, nous pouvons reconnaître qu'il s'adapte à peine à d'autres contextes
(comprendre, autres hôtes/langues). Wasm est conçu pour être indépendant de l'hôte,
ou langue/plate-forme-agnostique. J'aime l'idée d'utiliser le Web mature
technologies et de trouver un cas d'utilisation pour les scénarios non Web, mais dans le cas
du Web IDL, il semble vraiment lié au Web. C'est la raison pour laquelle je suis très
suivre de près ces conversations ici.

J'ai ouvert WebAssembly/webidl-bindings#40
https://github.com/WebAssembly/webidl-bindings/issues/40 , qui m'a conduit
poser une question ici puisque je n'en ai vu aucune mention (ou je l'ai ratée).

Dans toute l'histoire de la liaison, il n'est pas clair « qui » est responsable de
générer les liaisons :

  • Est-ce un compilateur (qui transforme un programme en module Wasm) ?
  • Est-ce un auteur de programme (et donc, les reliures sont-elles écrites à la main) ?

Je pense que les deux sont valables. Et dans le cas de Web IDL, il semble
limitations (voir le lien ci-dessus). Peut-être que je viens de manquer une étape importante dans
le processus, et donc, pensez à oublier mon message.

Même si l'objectif est de « recentrer » le Web IDL pour qu'il soit moins Web-centric, en ce moment,
il est très centré sur le Web. Et des propositions surgissent pour proposer des alternatives,
d'où ce fil. Par conséquent, je suis préoccupé par une fragmentation potentielle.
Idéalement (et c'est ainsi que Wasm a été conçu jusqu'à présent), étant donné un module Wasm
y compris ses fixations, il est possible de l'exécuter n'importe où tel quel. Avec
liaisons écrites en Web IDL, Cap'n' Proto, FlatBuffers, peu importe, je suis
à peu près sûr que tous les compilateurs ou auteurs de programmes n'écriront pas la même chose
des liaisons dans différentes syntaxes pour être vraiment multiplateformes. Curieusement, c'est
un argument en faveur des reliures manuscrites : les gens peuvent contribuer à une
programme en écrivant des liaisons pour la plate-forme P. Mais admettons que ce ne sera pas
idéal du tout.

Donc pour résumer : je suis préoccupé par une possible fragmentation entre le Web et
liaisons non Web. Si un langage non lié au Web est utilisé, serait-il
implémenté de manière réaliste par les navigateurs Web ? ils devraient écrire
liaisons « Wasm ⟶ langage de liaison B ⟶ Web IDL ». Notez que c'est la même chose
scénario pour tous les hôtes : Wasm ⟶ langage de liaison B ⟶ API hôte.

Pour les curieux, je travaille chez Wasmer et je suis l'auteur du PHP-
https://github.com/wasmerio/php-ext-wasm , Python-
https://github.com/wasmerio/python-ext-wasm , Ruby-
https://github.com/wasmerio/ruby-ext-wasm et Go-
https://github.com/wasmerio/go-ext-wasm Intégrations Wasm. Nous commençons
avoir un beau terrain de jeu pour bidouiller différentes fixations pour très différentes
hôtes. Si quelqu'un veut que j'intègre différentes solutions, que je collecte
retours d'expérience, ou essayer d'expérimenter, nous sommes tous ouverts et prêts à collaborer
et y mettre plus de ressources.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUD6WA22DDUS7PYQ6F3P4DHYRA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJZKTDNEA50 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAQAXUGM66AWN7ZCIVBTXVTP4DHYRANCNFSM4HJUHVGQ
.

--
Francis McCabe
SUE

On le voit déjà avec la difficulté d'intégrer les types ref dans la chaîne d'outils.

Je ne peux pas parler d'autres langues, mais Rust + wasm-bindgen prend déjà en charge :

Alors je suis curieux : de quelles difficultés parlez-vous ?

Ma compréhension des difficultés se situe davantage du côté du C++. Rust dispose d'une métaprogrammation suffisamment puissante pour rendre cela plus raisonnable du côté du langage, mais l'environnement utilisateur C++ a plus de mal à raisonner sur anyrefs par exemple.

Je serais curieux d'en savoir plus sur les problèmes spécifiques à C++ ici. (Sont-ils spécifiques à C++ ou spécifiques à LLVM ?)

C++ ne sait pas ce qu'est un type ref. Vous ne pouvez donc pas l'avoir à l'intérieur d'un
objet arbitraire. Ne fait pas vraiment partie de la langue ; plus comme un fichier
descripteur. Endroit amusant pour une chaîne.

Le lundi 24 juin 2019 à 15h07, Alon Zakai [email protected] a écrit :

Je serais curieux d'en savoir plus sur les problèmes spécifiques à C++ ici. (Sont-ils
Spécifique à C++ ou spécifique à LLVM ?)

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUDW237MUBBUUJLKS6LP4FARJA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJZKTDN5WW2G2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJZKTDN5WW2W2GO1999
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAQAXUB4O3ZX4LRQSQRL763P4FARJANCNFSM4HJUHVGQ
.

--
Francis McCabe
SUE

En parlant à @fgmccabe hors ligne, il est vrai que C++ et Rust ne peuvent pas stocker directement un type ref dans une structure, car la structure sera stockée dans une mémoire linéaire. C++ et Rust peuvent bien sûr gérer les types ref indirectement, de la même manière qu'ils gèrent les descripteurs de fichiers, les textures OpenGL, etc. - avec des poignées entières. Je pense que son point est qu'aucun de ces deux langages ne peut gérer les types ref "bien"/"nativement" (corrigez-moi si je me trompe !), ce avec quoi je suis d'accord - ces langages seront toujours désavantagés sur le type ref opérations, par rapport aux langages GC.

Je reste curieux de savoir s'il y a quelque chose de spécifique au C++ ici. Je ne pense pas qu'il y ait?

Ma compréhension de ce qui rend le C++ difficile ici est si vous avez dit :

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);
}

La bonne nouvelle est que ce dernier exemple est UB, je crois (les pointeurs non valides sont UB dès qu'ils sont créés), mais comment essayons-nous de modéliser cela dans LLVM IR?

@jgravelle-google Je pense que même struct Anyref; présuppose que c'est quelque chose qui a du sens dans la mémoire linéaire. Au lieu de cela, pourquoi ne pas le modéliser avec un descripteur d'entier comme mentionné précédemment, en tant que textures OpenGL, descripteurs de fichiers, etc.

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);
}

Le handle d'entier doit être recherché dans la table lorsqu'il doit être utilisé - encore une fois, ce n'est qu'un inconvénient des langages qui utilisent la mémoire linéaire comme C++ et Rust. Cependant, il pourrait certainement être optimisé au moins localement - sinon par LLVM, alors au niveau wasm.

Cela fonctionnera, mais vous devez ensuite vous assurer d'appeler table_free(elem) ou vous en garderez une référence pour toujours. Ce qui n'est pas -c'est-à-dire bizarre pour C++, d'accord.

C'est quelque chose d'un mappage étrange parce qu'il ne se superpose pas bien, je pense? Comme si cela ressemblait à une bibliothèque à la OpenGL, mais cela repose sur la magie du compilateur pour fournir - je ne pense pas que vous puissiez construire un anyref.h en C++ même avec un inline-wasm, si vous dépendez de la déclaration d'un table.

Quoi qu'il en soit, je pense que tout est faisable/traitable, mais pas simple, c'est tout.

@kripken Il est vrai que la prise en charge native "correcte" de anyref nécessitera quelques modifications de LLVM (et de rustc), mais ce n'est pas réellement un obstacle.

wasm-bindgen stocke les véritables anyref s wasm dans une table wasm, puis dans la mémoire linéaire, il stocke un index entier dans la table. Il peut donc accéder au anyref en utilisant l'instruction wasm table.get .

Jusqu'à ce que wasm-gc soit implémenté, les langages GC devront utiliser exactement la même stratégie, donc Rust (et al) ne manque pas.

Alors, qu'est-ce que le support natif de anyref dans LLVM nous apporterait ? Eh bien, cela permettrait de passer/renvoyer directement anyref partir de fonctions, plutôt que d'avoir à détourner les anyref via une table wasm. Ce serait utile, oui, mais ce n'est qu'une optimisation des performances, cela n'empêche pas d'utiliser anyref .

@Pauan

wasm-bindgen stocke les véritables wasm anyrefs dans une table wasm, puis dans la mémoire linéaire, il stocke un index entier dans la table. Il peut donc accéder à anyref en utilisant l'instruction wasm table.get.

Exactement, oui, c'est le modèle dont je parlais.

Jusqu'à ce que wasm-gc soit implémenté, les langages GC devront utiliser exactement la même stratégie, donc Rust (et al) ne manque pas.

Oui, pour le moment, les langues GC n'ont aucun avantage car nous n'avons pas de GC wasm natif. Mais j'espère que ça va changer ! :) Finalement, je m'attends à ce que les langages GC aient un net avantage ici, du moins si nous faisons GC correctement.

Alors, qu'est-ce que la prise en charge native d'anyref dans LLVM nous apporterait ? Eh bien, cela permettrait de passer/retourner directement anyref à partir de fonctions, plutôt que d'avoir besoin d'indirecter l'anyref via une table wasm. Ce serait utile, oui, mais ce n'est qu'une optimisation des performances, cela n'empêche pas d'utiliser anyref.

D'accord, oui, ce ne sera qu'un avantage de perf des langages GC (à terme) par rapport à C++ et Rust etc. Cela n'empêche pas l'utilisation.

Les cycles sont un problème plus important pour C++ et Rust, car les tables sont root. Peut-être que nous pouvons avoir une API de traçage ou des "objets fantômes", essentiellement un moyen de mapper la structure des liens GC à l'intérieur de C++/Rust afin que le GC extérieur puisse les comprendre. Mais je ne pense pas qu'il y ait encore de proposition réelle pour l'un d'entre eux.

Finalement, je m'attends à ce que les langages GC aient un net avantage ici, du moins si nous le faisons correctement.

Je peux me tromper, mais je serais surpris si c'était le cas : les langages GC devraient allouer une structure GC wasm, puis le moteur wasm devrait garder une trace de cela tout au long du programme.

En comparaison, Rust n'a besoin d'aucune allocation (il suffit de l'affecter à une table) et n'a besoin que de stocker un entier, et le moteur wasm n'a besoin de garder une trace que d'une table statique immobile à des fins de GC.

Je suppose qu'il est possible que l' accès anyref soit optimisable pour les langages GC, car il n'aurait pas besoin d'utiliser table.get , mais je m'attends table.get ce que

Alors, pourriez-vous nous en dire plus sur la façon dont vous vous attendez à ce qu'un programme wasm-gc fonctionne mieux qu'un programme utilisant une table wasm ?

PS Cela commence à être assez hors sujet, alors peut-être devrions-nous déplacer cette discussion vers un nouveau fil?

Vraiment juste ça : éviter table.get/table.set . Avec GC, vous devriez avoir le pointeur brut juste là, en sauvegardant les indirections. Mais oui, vous avez raison, Rust et C++ n'ont besoin de stocker qu'un entier, et ils sont assez rapides dans l'ensemble, donc tout avantage de GC pourrait ne pas avoir d'importance !

Je suis d'accord qu'on s'éloigne peut-être du sujet, ouais. Je pense que le sujet est le point de @fgmccabe selon lequel les types ref ne s'intègrent pas aussi naturellement dans les langages linéaires utilisant la mémoire. Cela peut nous influencer d'une certaine manière avec les liaisons (en particulier les cycles sont inquiétants parce que C++ et Rust ne peuvent pas les gérer, mais peut-être que les liaisons peuvent l'ignorer ?), donc juste quelque chose à faire attention je suppose - à la fois pour essayer de faire fonctionner les choses pour autant de langues que possible, et ne pas être trop influencé par les limitations d'une langue particulière.

@kentonv

La couche de sérialisation de Cap'n Proto doit de manière réaliste être implémentée indépendamment dans chaque langue, car la majeure partie est une couche d'API large mais peu profonde qui doit être idiomatique et conviviale en ligne. La couche RPC, OTOH, est étroite mais profonde

De quel dossier s'agit-il ?

@PoignardAzur Désolé, je ne comprends pas votre question.

@kentonv Je regarde dans le référentiel capnproto Github. Où est la couche de sérialisation ?

@PoignardAzur Donc, cela revient à mon point. Il n'y a pas vraiment un seul endroit vers lequel vous pouvez pointer et dire "c'est la couche de sérialisation". La plupart du temps, la "sérialisation" de Cap'n Proto n'est qu'une arithmétique de pointeur autour des charges/magasins vers un tampon sous-jacent. Étant donné un fichier de schéma, vous utilisez le générateur de code pour générer un fichier d'en-tête qui définit des méthodes en ligne qui effectuent la bonne arithmétique de pointeur pour les champs particuliers définis dans le schéma. Le code de l'application doit ensuite appeler ces accesseurs générés chaque fois qu'il lit ou écrit un champ.

C'est pourquoi cela n'aurait aucun sens d'essayer d'appeler une implémentation écrite dans un langage différent. L'utilisation d'un FFI brut pour chaque accès au champ serait extrêmement fastidieux, vous finirez donc sans aucun doute par écrire un générateur de code qui enveloppe le FFI dans quelque chose de plus joli (et spécifique à votre schéma). Mais ce code généré serait au moins aussi compliqué que le code que Cap'n Proto implémente déjà -- probablement plus compliqué (et beaucoup plus lent !). Il est donc plus logique d'écrire directement un générateur de code pour le langage cible.

Il existe peut-être des fonctions d'assistance internes dans l'implémentation Cap'n Proto qui pourraient être partagées. Plus précisément, layout.c++ / layout.h contient tout le code qui interprète l'encodage du pointeur de capnp, effectue la vérification des limites, etc. Les accesseurs de code générés appellent ce code lors de la lecture/écriture des champs de pointeur. Donc, je pourrais peut-être imaginer envelopper cette partie dans un FFI à appeler à partir de plusieurs langues ; mais je m'attendrais toujours à écrire des générateurs de code et une certaine quantité de bibliothèque de support d'exécution dans chaque langue cible.

Ouais, désolé, je voulais dire le contraire ^^ (la couche RPC)

@PoignardAzur Ohhh, et je suppose que vous êtes particulièrement intéressé à regarder les interfaces, puisque vous réfléchissez à la façon de les envelopper dans un FFI. Alors tu veux :

  • capability.h : interfaces abstraites pour représenter les capacités et l'invocation RPC, qui en théorie pourraient être soutenues par une variété d'implémentations. (C'est la partie la plus importante.)
  • rpc.h : Implémentation de RPC sur un réseau.
  • rpc-twoparty.h : Adaptateur de transport pour RPC sur une simple connexion.

Cette proposition est maintenant remplacée par la #1291 : liaisons OCAP.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

Artur-A picture Artur-A  ·  3Commentaires

artem-v-shamsutdinov picture artem-v-shamsutdinov  ·  6Commentaires

badumt55 picture badumt55  ·  8Commentaires

arunetm picture arunetm  ·  7Commentaires

ghost picture ghost  ·  7Commentaires