Design: WebAssembly.Instance peut-il recompiler un module déjà compilé ?

Créé le 27 oct. 2016  ·  94Commentaires  ·  Source: WebAssembly/design

Disons que j'ai ce code :

let descriptor = /* ... */;
let memories = Array.apply(null, {length: 1024}).map(() => new WebAssembly.Memory(descriptor));
let instance = fetch('foo.wasm')
  .then(response => response.arrayBuffer())
  .then(buffer => WebAssembly.compile(buffer))
  .then(module => new WebAssembly.Instance(module, { memory: memories[1023] }));

Est-ce que WebAssembly.Instance autorisé à bloquer pendant un laps de temps substantiel. Pourrait-il par exemple recompiler le WebAssembly.Module ?

Dans la plupart des cas, je dirais non, mais que se passe-t-il si le code déjà compilé n'aime pas particulièrement la mémoire qu'il reçoit ? Dites, parce que cette mémoire est une mémoire en mode lent et que le code a été compilé en supposant un mode rapide ? peut-être que memories[0] était une mémoire en mode rapide, mais memories[1023] ne le sera certainement pas.

Qu'en est-il de ce code à la place :

let instances = [0,1,2,3,4,5,6,7].map(v => fetch(`foo${v}.wasm`)
  .then(response => response.arrayBuffer())
  .then(buffer => WebAssembly.compile(buffer))
  .then(module => new WebAssembly.Instance(module)));

Ces appels à WebAssembly.Instance autorisés à provoquer une recompilation ?

En supposant que ce qui précède est logique, voici quelques questions connexes :

  • Voulons-nous une fonction asynchrone à retour de promesse qui puisse compiler _et_ instancier ? Je ne dis pas que nous devrions abandonner l'une des API synchrones et asynchrones que nous avons déjà, je propose une nouvelle API asynchrone.
  • Comment un navigateur expose-t-il que le code compilé dans un WebAssembly.Module est rapide et qu'une instance de WebAssembly.Memory convient à un code aussi rapide ? À l'heure actuelle, la réponse semble être « essayez-le et voyez si vous pouvez le remarquer ».
  • Comment un utilisateur sait-il combien d'instances WebAssembly.Memory il est autorisé avant d'obtenir du code lent (en comptant les implicites, par exemple celles créées par le deuxième exemple) ?
JS embedding

Commentaire le plus utile

@kgryte J'aurais dû préciser que mon commentaire concernait principalement le navigateur en tant que contexte d'exécution. Nous avons atterri sur une surface d'API qui expose toujours les API synchrones. Les navigateurs peuvent imposer une limite de taille aux modules transmis aux API synchrones (Chrome le fait déjà, par exemple), mais cette limite est configurable par l'intégrateur et ne devrait pas avoir besoin de s'appliquer à Node.

Tous les 94 commentaires

Ce serait bien que WebAssembly.Instance provoque parfois une recompilation, de cette façon, des variables globales immuables pourraient être constamment repliées dans le code généré. Par exemple, Emscripten génère du code transférable en décalant tous les pointeurs vers des données statiques. L'offset est transmis en tant que variable globale immuable lorsque le module est instancié. Si WebAssembly.Instance peut recompiler, cela pourrait spécialiser le code généré.

La spécification ne définit pas ce qu'est la "compilation", ni ne
il est logique qu'il le fasse, car les approches de mise en œuvre peuvent être très différentes
(y compris les interprètes). Il ne peut donc avoir aucun mot à dire normatif à ce sujet
dans les deux cas. Le mieux que nous puissions faire est d'ajouter une note qui
WebAssembly.Instance devrait être "rapide".

Le 27 octobre 2016 à 03h24, Michael Bebenita [email protected]
a écrit:

Ce serait bien que WebAssembly.Instance provoque parfois une recompilation,
de cette façon, les variables globales immuables pourraient être constamment repliées dans le produit généré
code. Par exemple, Emscripten génère du code réadressable en décalant tous
pointeurs vers des données statiques. L'offset est transmis en tant que variable globale immuable
lorsque le module est instancié. Si WebAssembly.Instance peut recompiler,
il pourrait spécialiser le code généré.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment-256522163 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AEDOO9sJPgujK3k0f6P7laYV_zaJxES5ks5q3_1LgaJpZM4Kh1gM
.

D'accord, ce serait tout au plus une note non normative.

Dans SM, nous prévoyons également actuellement que l'instanciation ne recompile jamais afin qu'il existe un modèle de coût de compilation prévisible pour les développeurs (en particulier, afin que les développeurs puissent utiliser WebAssembly.compile et IDB pour contrôler quand ils prennent le coup de compilation) . La recompilation au moment de l'instanciation à partir du constructeur synchrone Instance briserait certainement ce modèle de coût et pourrait conduire à un jank majeur.

Mais j'apprécie que la compilation séparée soit fondamentalement en contradiction avec une variété d'optimisations que l'on pourrait vouloir faire pour spécialiser le code généré aux paramètres ambiants. Fusionner la compilation et l'instanciation en une seule opération asynchrone est logique et c'est quelque chose que nous avons envisagé dans le passé. L'inconvénient, bien sûr, est que cela inhibe la mise en cache explicite (il n'y a pas de Module ), donc le développeur doit faire un compromis désagréable. Quelques options :

  • L'impl pourrait faire une mise en cache implicite du contenu (qui pourrait inclure des paramètres ambiants dans la clé), comme nous le faisons avec asm.js actuellement dans FF. Ce serait un peu pénible et présente tous les problèmes de prévisibilité/heuristique de tout cache implicite.
  • Nous pourrions créer un nouveau moyen (par exemple, une nouvelle API WebAssembly.Cache où vous transmettez le bytecode et les paramètres d'instanciation et récupérez un Promise<Instance> .

Ce dernier m'intrigue et pourrait offrir une expérience de développement beaucoup plus agréable que l'utilisation d'IDB et peut-être une chance d'optimiser encore plus la mise en cache (puisque le cache est spécialisé à cet effet), mais c'est certainement une grande fonctionnalité et quelque chose que nous voudrions prendre un certain temps à envisager.

@rossberg-chromium J'ai l'impression d'avoir mal expliqué mon objectif : je ne veux pas ergoter sur ce que dit la spécification. J'essaie de souligner ce qui semble être une sérieuse surprise pour les développeurs, se cachant sous l'API. Un développeur ne s'attendra pas à ce que le résultat de .compile soit recompilé. Cela me semble être un défaut de conception.

@lukewagner, même avec une mise en cache implicite ou explicite, nous pouvons avoir le même problème : combien de WebAssembly.Memory peuvent être créés dans le même espace d'adressage / origine est une limitation du navigateur. J'aime ce que vous proposez, mais je pense que c'est orthogonal au problème. Faites-moi savoir si j'ai mal compris ce que vous suggérez.

Peut-être que .compile et Module pourraient recevoir un Memory , et Instance a une propriété .memory qui peut être transmise à d'autres compilations / instanciations ?

Je n'essaie pas d'éliminer la possibilité de recompilation, je pense que nous voulons plutôt une utilisation API idiomatique commune qui a des informations parfaites par rapport à Memory au moment de la première compilation (ou au moment de la récupération du cache) donc que la compilation émet ou non des vérifications de limites si besoin.

@jfbastien Avec la mise en cache implicite/explicite qui a été fournie les paramètres d'instanciation particuliers (donc Memory ), je ne vois pas comment il y aurait besoin de recompilation.

@jfbastien Avec la mise en cache implicite/explicite qui a été fournie les paramètres d'instanciation particuliers (donc Memory ), je ne vois pas comment il y aurait besoin de recompilation.

Il peut y avoir:

  1. Créez plusieurs Memory s.
  2. Compilez le code, avec une vérification des limites explicite (lente) car il y avait trop de Memory ies.
  3. Cachez ce code.
  4. Quitter la page.
  5. Chargez à nouveau la page.
  6. Allouez un seul Memory , qui obtient la version rapide.
  7. Obtenez de la cache.
  8. Recevez le code lent Instance .

À ce stade, je suis d'accord que vous n'avez pas besoin de recompilation, mais nous sommes un peu stupides de faire des vérifications de limites lentes lorsque nous n'en avons pas besoin.

Comme je l'ai dit : j'aime bien cette Cache API que vous proposez, je pense qu'elle rend WebAssembly plus utilisable, mais je pense que le problème est toujours là. ??

Eh bien, c'est mon propos d'avoir un cache amélioré qui accepte les paramètres d'instanciation et le bytecode : le cache est libre de se recompiler si ce qu'il a mis en cache ne correspond pas aux paramètres d'instanciation. Les étapes seraient donc simplement :

  1. créer plusieurs Memory s
  2. demander un Instance du cache, en passant l'un de ceux (lent) Memory s
  3. le code lent est compilé, mis en cache et renvoyé sous forme de Instance
  4. quitter la page
  5. charger à nouveau la page
  6. allouer un seul Memory
  7. demander un Instance du cache, en passant le rapide Memory
  8. le code rapide est compilé, mis en cache et renvoyé sous forme de Instance

et après l'étape 8, tous les futurs chargements de pages recevront un code rapide ou lent mis en cache.

@lukewagner Tout d'abord, vous proposez une atténuation qui va à l'encontre de l'objectif déclaré de WebAssembly fournissant des performances déterministes. La différence entre lent et le rapide a été citée pour la dernière fois à environ 20 %, donc ce serait vraiment puant si une spécification qui vise minutieusement des performances déterministes la laisse tomber sur le sol à cause d'une bizarrerie de l'API. Je ne pense pas que le navigateur doté d'un cache adressé au contenu soit la bonne solution, car la spécification se donne déjà beaucoup de mal ailleurs pour éviter le besoin d'optimisations de profil-recompile-cache. Par exemple, nous promettons la compilation précisément pour que l'application puisse avoir un comportement raisonnable même si le code n'est pas mis en cache. Si la façon dont cela est spécifié nous oblige tous à mettre en œuvre des caches ou d'autres mesures d'atténuation, nous aurons échoué notre objectif de donner aux gens un modèle de coût raisonnablement portable.

Pour moi, le problème est simplement le suivant : l'une des optimisations que nous devrons tous faire pour des raisons de concurrence (la vérification des limites de la mémoire virtuelle de 4 Go, que j'appellerai simplement le hack de 4 Go) ne peut pas être effectuée dans la spécification actuelle sans sacrifier une de ces choses :

  • Vous pouvez vous en tirer si vous allouez toujours 4 Go de mémoire virtuelle pour toute mémoire wasm. Cela découragera les gens d'utiliser WebAssembly pour de petits modules, car vous atteindrez les limites d'allocation de mémoire virtuelle, la fragmentation de la mémoire virtuelle ou d'autres problèmes si vous en allouez beaucoup. Je crains également que si vous autorisez l'allocation d'un grand nombre d'entre eux, vous réduisez l'efficacité des mesures d'atténuation de sécurité telles que l'ASLR. Notez que les API existantes ne partagent pas ce danger, car elles engagent la mémoire qu'elles allouent et elles seront soit en mode MOO, soit en panne avant de vous permettre d'allouer beaucoup plus que ce que permet la mémoire physique.
  • Vous pouvez vous en tirer si vous autorisez une recompilation lorsque vous trouvez une incompatibilité lors de l'instanciation (le code compilé veut un hack de 4 Go mais la mémoire n'a pas cette allocation de mémoire virtuelle). Vous pouvez également vous en tirer si l'instanciation déplace la mémoire dans une région de 4 Go, mais reportez-vous au point précédent. Donc, probablement à chaque fois que cela se produira, ce sera un bogue P1 pour le navigateur qui l'a rencontré.

Je pense que cela signifie que la spécification encouragera les fournisseurs à converger pour autoriser uniquement des réservations de 4 Go à chaque fois que la mémoire wasm est allouée, ou à avoir des optimisations de cache/lazy-compile/profile pour détecter cela.

Enfin, je ne comprends pas l'intérêt de rendre tout cela non normatif. Cela peut être normatif, car nous pourrions faire en sorte que l'API exclue la possibilité pour le navigateur d'avoir à compiler quelque chose sans savoir de quel type de mémoire il disposera. J'imagine qu'il y a plusieurs façons de le faire. Par exemple, l'instanciation pourrait renvoyer une promesse et nous pourrions supprimer l'étape de compilation séparée. Cela indiquerait clairement que l'instanciation est l'étape qui peut prendre un certain temps, ce qui implique fortement pour le client que c'est l'étape qui effectue la compilation. Dans une telle API, le compilateur sait toujours si la mémoire pour laquelle il compile a le hack de 4 Go ou non.

C'est triste que nous ne le remarquions que maintenant, mais je suis surpris que vous ne voyiez pas que c'est un problème plus grave. Y a-t-il une atténuation autre que la mise en cache que je néglige ?

@jfbastien dans votre scénario motivant, vous avez souligné que le module a été créé pour préférer la mémoire rapide. Je suppose que vous recherchez principalement l'activation de l'optimisation rapide de la mémoire lorsqu'un module particulier le souhaite, et que vous pourriez être d'accord avec le fait de ne pas le faire lorsque le module ne le veut pas (rien de mal à tomber dessus de manière opportuniste dans ce cas aussi , essayant juste de distinguer les priorités).

Si c'est le cas, à quoi ressembleraient ces alternatives à la mise en cache ou à l'instanciation asynchrone :

  1. L'auteur du module doit avoir besoin de 4 Go comme mémoire min/max
  2. Une variante de compilation (async au moins, peut-être aussi sync) qui produit une instance n'acceptant que la mémoire rapide.

Pour le problème du "piratage de 4 Go" et des incohérences entre la mémoire qui l'utilise et le code qui l'attend, serait-il logique que la compilation émette en interne deux versions du code ? (Évidemment, cela utiliserait plus de mémoire, ce qui est triste, mais j'espère que le temps de compilation ne serait pas bien pire, l'écrivain pourrait générer les deux à la fois ?)

@mtrofin Je ne pense pas qu'il soit logique de demander 4GiB si vous n'avez pas l'intention de l'utiliser. L'allocation virtuelle est distincte de l'intention d'utilisation, donc je pense que nous devrions séparer les deux.

Sur 2. : ce n'est toujours pas très utile pour le développeur : s'ils utilisent cette variante et qu'elle échoue, alors quoi ?

@kripken Je ne pense pas que la double compilation soit une bonne idée.

@kripken Je pense que c'est ce que nous ferions sans aucune autre solution à ce problème.

Je veux que WebAssembly soit génial dans le cas d'une navigation occasionnelle : tu me parles d'un truc sympa, tu m'envoies l'URL, je clique dessus, et je m'amuse quelques minutes. C'est ce qui rend le Web cool. Mais cela signifie que de nombreuses compilations seront du code qui n'est pas mis en cache, donc le temps de compilation jouera un grand rôle dans la durée de vie de la batterie d'un utilisateur. Donc, la double compilation me rend triste.

@mtrofin

L'auteur du module doit avoir besoin de 4 Go comme mémoire min/max

Ce n'est pas vraiment pratique, car de nombreux appareils n'ont pas 4 Go de mémoire physique. De plus, c'est difficile à spécifier.

Une variante de compilation (async au moins, peut-être aussi sync) qui produit une instance n'acceptant que la mémoire rapide.

Je ne pense pas que nous voulions des compilations doubles.

@pizlonator Jusqu'à présent, nous n'avons pas envisagé de conceptions nécessitant différents modes de codegen : nous avons juste toujours alloué des régions de 4 Go sur 64 bits et observé que cela réussissait pour plusieurs milliers de mémoires sous Linux, OSX et Windows. Nous avons une limite supérieure conservatrice pour éviter un épuisement total trivial de l'espace d'adressage disponible qui, je pense, sera suffisant pour prendre en charge le cas d'utilisation de nombreuses petites bibliothèques. Je pense donc que la nouvelle contrainte que nous abordons ici est qu'iOS a des limitations d'espace d'adressage virtuel qui pourraient réduire le nombre d'allocations de 4 Go.

Ainsi, une observation est qu'une grande partie de l'élimination de la vérification des limites permise par le piratage de 4 Go peut être évitée en ayant simplement une petite région de garde à la fin de la mémoire wasm. Nos premières expériences montrent que les analyses de base (rien à voir avec les boucles, éliminant simplement les vérifications des charges/magasins avec le même pointeur de base) peuvent déjà éliminer environ la moitié des vérifications de limites. Et cela pourrait probablement s'améliorer. Ainsi, le hack de 4 Go serait une accélération plus modeste et moins nécessaire.

Une autre idée que j'ai eue plus tôt serait de compiler de manière pessimiste le code avec des vérifications de limites (en utilisant l'élimination basée sur la page de garde), puis de les supprimer lors de l'instanciation avec une mémoire en mode rapide. Combinés, les frais généraux pourraient être assez faibles par rapport au code en mode rapide idéalisé.

@lukewagner

Jusqu'à présent, nous n'avons pas considéré les conceptions qui nécessitaient différents modes de codegen : nous avons juste toujours alloué des régions de 4 Go sur 64 bits et observé que cela réussissait pour plusieurs milliers de mémoires sous Linux, OSX et Windows. Nous avons un nombre total prudent pour éviter un épuisement total trivial de l'espace d'adressage disponible qui, je pense, sera suffisant pour prendre en charge le cas d'utilisation de nombreuses petites bibliothèques. Je pense donc que la nouvelle contrainte que nous abordons ici est qu'iOS a des limitations d'espace d'adressage virtuel qui pourraient réduire le nombre d'allocations de 4 Go.

Ce n'est pas un problème spécifique à iOS. Le problème est que si vous autorisez beaucoup de telles allocations, cela pose un risque de sécurité car chacune de ces allocations réduit l'efficacité de l'ASLR. Donc, je pense que la VM devrait avoir la possibilité de définir une limite très basse pour le nombre d'espaces de 4 Go qu'elle alloue, mais cela implique que le chemin de secours ne devrait pas être trop cher (c'est-à-dire qu'il ne devrait pas nécessiter de recompilation).

Quelle limite avez-vous sur le nombre de mémoires de 4 Go que vous alloueriez ? Que faites-vous lorsque vous atteignez cette limite : abandonner complètement ou recompiler à l'instanciation ?

Ainsi, une observation est qu'une grande partie de l'élimination de la vérification des limites permise par le piratage de 4 Go peut être évitée en ayant simplement une petite région de garde à la fin de la mémoire wasm. Nos premières expériences montrent que les analyses de base (rien à voir avec les boucles, éliminant simplement les vérifications des charges/magasins avec le même pointeur de base) peuvent déjà éliminer environ la moitié des vérifications de limites. Et cela pourrait probablement s'améliorer. Ainsi, le hack de 4 Go serait une accélération plus modeste et moins nécessaire.

Je suis d'accord que l'analyse nous permet d'éliminer plus de contrôles, mais le hack de 4 Go est la voie à suivre si vous voulez des performances optimales. Tout le monde veut des performances de pointe, et je pense qu'il serait formidable de permettre d'obtenir des performances de pointe sans également causer de problèmes de sécurité, de ressources et de recompilations inattendues.

Une autre idée que j'ai eue plus tôt serait de compiler de manière pessimiste le code avec des vérifications de limites (en utilisant l'élimination basée sur la page de garde), puis de les supprimer lors de l'instanciation avec une mémoire en mode rapide. Combinés, les frais généraux pourraient être assez faibles par rapport au code en mode rapide idéalisé.

Le code qui a des vérifications de limites est préférable d'épingler un registre pour la taille de la mémoire et d'épingler un registre pour la base de mémoire.

Le code qui utilise le hack de 4 Go n'a besoin que d'épingler un registre pour la base de mémoire.

Donc, ce n'est pas une excellente solution.

Outre l'ennui d'avoir à débattre des spécifications et des implémentations, quels sont les inconvénients de combiner la compilation et l'instanciation en une seule action promise ?

Le problème est que si vous autorisez beaucoup de telles allocations, cela pose un risque de sécurité car chacune de ces
l'allocation réduit l'efficacité de l'ASLR.

Je ne suis pas un expert en ASLR mais, iiuc, même si nous n'avions pas de limite conservatrice (c'est-à-dire si nous vous permettions de continuer à allouer jusqu'à ce que mmap échoue parce que le noyau a atteint son nombre de- plages d'adresses max), seule une petite fraction de l'ensemble de l'espace adressable de 47 bits serait consommée, de sorte que le placement du code continuerait d'être hautement aléatoire sur cet espace de 47 bits. IIUC, le placement du code ASLR n'est pas complètement aléatoire non plus; juste assez pour qu'il soit difficile de prédire où quelque chose sera.

Quelle limite avez-vous sur le nombre de mémoires de 4 Go que vous alloueriez ? Que fais-tu
lorsque vous atteignez cette limite - abandonner complètement ou recompiler à l'instanciation ?

Eh bien, puisqu'il s'agit de jours asm.js, seulement 1000. Ensuite, l'allocation de mémoire est lancée. Nous devrons peut-être corriger cela, mais même avec de nombreuses applications super-modularisées (avec de nombreux modules wasm distincts chacune) partageant le même processus, je ne peux pas imaginer que nous aurions besoin de trop de choses. Je pense que Memory est différent des vieux ArrayBuffer en ce sens que les applications ne voudront pas naturellement créer des milliers.

Outre l'ennui d'avoir à se disputer les spécifications et les implémentations, quels sont les inconvénients
de combiner compilation et instanciation en une seule action promise ?

Comme je l'ai mentionné ci-dessus, l'ajout d'une API Promise<Instance> eval(bytecode, importObj) est bien, mais maintenant cela place le développeur dans une situation difficile car il doit désormais choisir entre un boost de performances sur certaines plates-formes et la possibilité de mettre en cache son code compilé sur toutes les plateformes. Il semble que nous ayons besoin d'une solution qui s'intègre à la mise en cache et c'est ce à quoi je réfléchissais ci-dessus avec l'API explicite Cache .

Nouvelle idée : et si nous ajoutions une version asynchrone de new Instance , disons WebAssembly.instantiate et, comme pour WebAssembly.compile , nous disons que tout le monde est censé utiliser la version asynchrone ? C'est quelque chose que j'ai envisagé _de toute façon_ car l'instanciation peut prendre quelques ms si un correctif est utilisé. Ensuite, nous disons dans la spécification que le moteur peut effectuer un travail coûteux en compile ou en instantiate (ou ni l'un ni l'autre, si un moteur effectue une validation/compilation paresseuse !).

Cela laisse toujours la question de savoir quoi faire lorsqu'un compile d Module est stocké dans IDB, mais c'est juste une question difficile lorsqu'il existe plusieurs modes de codegen _anyway_. Une idée est que les Module qui sont stockés dans ou récupérés de l'IDB conservent un handle sur leur entrée IDB et ils ajoutent un nouveau code compilé à cette entrée. De cette façon, l'entrée IDB accumulerait paresseusement une ou plusieurs versions compilées de son module et serait capable de fournir ce qui était nécessaire lors de l'instanciation.

La partie IDB est un peu plus de travail, mais cela semble assez proche de l'idéal, en termes de performances. WDYT ?

Je pense que l'ajout de instantiate asynchrone est logique, mais j'ajouterais également un paramètre Memory à compile . Si vous passez une mémoire différente à instantiate vous pouvez être recompilé, sinon vous avez déjà "lié" la mémoire lors de la compilation.

Je n'ai pas encore assez pensé à la mise en cache pour avoir une opinion bien formée.

@lukewagner

Je ne suis pas un expert en ASLR mais, iiuc, même si nous n'avions pas de limite conservatrice (c'est-à-dire si nous vous permettions de continuer à allouer jusqu'à ce que mmap échoue parce que le noyau a atteint son nombre de plages d'adresses max) , seule une petite fraction de l'ensemble de l'espace adressable de 47 bits serait consommée, de sorte que le placement du code continuerait d'être hautement aléatoire sur cet espace de 47 bits. IIUC, le placement du code ASLR n'est pas complètement aléatoire non plus; juste assez pour qu'il soit difficile de prédire où quelque chose sera.

ASLR affecte à la fois le code et les données. Le but est de rendre plus coûteux pour un attaquant de se frayer un chemin dans une structure de données sans poursuivre un pointeur vers celle-ci. Si l'attaquant peut épuiser la mémoire, il a certainement plus d'influence.

Eh bien, puisqu'il s'agit de jours asm.js, seulement 1000. Ensuite, l'allocation de mémoire est lancée. Nous devrons peut-être corriger cela, mais même avec de nombreuses applications super-modularisées (avec de nombreux modules wasm distincts chacune) partageant le même processus, je ne peux pas imaginer que nous aurions besoin de trop de choses. Je pense que Memory est différent des anciens ArrayBuffers dans le sens où les applications ne voudront pas naturellement en créer des milliers.

1000 semble être une limite raisonnable. Je vais demander aux gens de la sécurité.

Comme je l'ai mentionné ci-dessus, l'ajout d'une promesseL'API eval(bytecode, importObj) est bien, mais maintenant, elle place le développeur dans une position difficile car il doit désormais choisir entre un boost de performances sur certaines plates-formes et la possibilité de mettre en cache son code compilé sur toutes les plates-formes. Il semble que nous ayons besoin d'une solution qui s'intègre à la mise en cache et c'est ce à quoi je réfléchissais ci-dessus avec l'API Cache explicite.

Droit. Je peux voir plusieurs façons de faire fonctionner une telle API. Une API ringard mais pratique serait de surcharger eval :

  1. instancePromise = eval(bytecode, importObj)
  2. instancePromise = eval(module, importObj)

puis Instance a un getter :

module = instance.module

Où module est structure clonable.

Que pensez-vous de cela?

Nouvelle idée : et si nous ajoutions une version asynchrone de la nouvelle instance, disons WebAssembly.instantiate et, comme avec WebAssembly.compile, nous disons que tout le monde est censé utiliser la version asynchrone ? C'est quelque chose que j'ai envisagé de toute façon car l'instanciation peut prendre quelques ms si le patch est utilisé. Ensuite, nous disons dans la spécification que le moteur peut effectuer un travail coûteux en compilation ou en instanciation (ou ni l'un ni l'autre, si un moteur effectue une validation/compilation paresseuse !).

Cela laisse toujours la question de savoir quoi faire lorsqu'un module compilé est stocké dans IDB, mais c'est juste une question difficile lorsqu'il existe de toute façon plusieurs modes de codegen. Une idée est que les modules qui sont stockés dans ou récupérés de l'IDB conservent un handle sur leur entrée IDB et ils ajoutent un nouveau code compilé à cette entrée. De cette façon, l'entrée IDB accumulerait paresseusement une ou plusieurs versions compilées de son module et serait capable de fournir ce qui était nécessaire lors de l'instanciation.

La partie IDB est un peu plus de travail, mais cela semble assez proche de l'idéal, en termes de performances. WDYT ?

Intrigant. Par rapport à mon idée ci-dessus :

Pro : la vôtre est une abstraction facile à comprendre qui est conceptuellement similaire à ce que nous disons maintenant.
Inconvénient : la vôtre n'entraîne pas autant de synergie entre ce que fait l'utilisateur et ce que fait le moteur que ma proposition le permet.

Il y a trois domaines dans lesquels votre proposition ne donne pas autant de contrôle à l'utilisateur que le mien :

  1. Le travail coûteux pourrait se produire dans l'un des deux endroits, de sorte que l'utilisateur doit prévoir que l'un ou l'autre d'entre eux soit coûteux. Nous aurons probablement du contenu Web qui se comporte mal si l'un d'entre eux est cher, car il a été réglé pour les cas où il se trouvait être bon marché. Ma proposition a un endroit où des choses coûteuses se produisent, conduisant à plus d'uniformité entre les implémentations.
  2. Il n'y a pas de chemin clairement garanti pour que toutes les versions du code compilé soient mises en cache. D'un autre côté, mon utilisation du threading du module via l'API signifie que la VM peut créer le module avec plus de choses à chaque fois, tout en permettant à l'utilisateur de gérer le cache. Donc, si la première fois nous faisons 4 Go, c'est ce que nous mettrons en cache, mais si nous ne parvenons pas à faire 4 Go la deuxième fois, nous pourrons potentiellement mettre en cache les deux (si l'utilisateur met en cache instance.module après chaque compilation).
  3. Des cas de coin inhabituels dans le navigateur ou d'autres problèmes peuvent parfois conduire à une double compilation dans votre schéma, car nous compilons une chose à l'étape de compilation, mais nous réalisons ensuite que nous avons besoin d'une autre chose à l'étape d'instanciation. Ma version ne nécessite jamais une double compilation.

Alors, j'aime mieux le mien. Cela dit, je pense que votre proposition est une progression, donc cela me semble vraiment bien.

Ce problème repose sur la fréquence à laquelle la fragmentation rend l'allocation rapide
la mémoire (d'ailleurs vous aurez 4 Go + décalage maximal pris en charge, ou 8 Go) échoue. Si la
est probablement bien inférieur à 1%, alors il n'est peut-être pas tout à fait déraisonnable de
que ce soit une situation OOM.

Dans le cas où l'utilisateur navigue sur le Web et utilise beaucoup de
petits modules WASM en succession rapide, vraisemblablement ils ne sont pas tous en direct à
une fois que. Dans ce cas, un petit cache de blocs de 4 Go réservés atténuerait le
problème.

Une autre stratégie possible consiste à générer une version du code avec
vérifie les limites, et si la mémoire rapide est disponible, écrasez simplement les limites
vérifie avec nops. C'est moche, mais c'est beaucoup plus rapide qu'un
recompiler, et moins d'espace que deux compilations.

Le jeu. 27 oct. 2016 à 21:03, pizlonator [email protected]
a écrit:

@mtrofin https://github.com/mtrofin

L'auteur du module doit avoir besoin de 4 Go comme mémoire min/max

Ce n'est pas vraiment pratique, car de nombreux appareils n'ont pas 4 Go de mémoire physique
Mémoire. De plus, c'est difficile à spécifier.

Une variante de compile (async au moins, peut-être aussi sync) qui produit un
instance n'acceptant que la mémoire rapide.

Je ne pense pas que nous voulions des compilations doubles.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment -256738329,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ALnq1F6CYUaq0unla0H6RYivUC8jfxIAks5q4PWIgaJpZM4Kh1gM
.

Ce n'est pas seulement ASLR : c'est aussi la pollution pagetable/allocator/etc. Nous devons tous parler à nos responsables de la sécurité _ainsi qu'aux_ responsables du noyau/des systèmes. Ou nous pouvons être clairs avec les développeurs sur les limites que chaque moteur impose au Memory "rapide" et le rendre idiomatique dans l'API afin qu'il soit difficile de l'utiliser mal.

Il y a toutes ces béquilles qu'on peut utiliser, comme le nop ou la double compilation, mais pourquoi même avoir des béquilles ?

@jfbastien Je ne pense pas que la mémoire PROT_NONE coûte les entrées de table de page; Je pense qu'il existe une structure de données distincte qui contient des mappages à partir desquels la table de page est peuplée paresseusement.

@pizlonator J'aime cette idée, et je peux voir que c'est ce que nous encourageons tout le monde à utiliser par défaut dans les tutoriels, la chaîne d'outils, etc. C'est aussi plus succinct et plus facile à enseigner si vous pouvez simplement ignorer Module . Cela pourrait également répondre à la préoccupation de @s3ththompson de décourager l'utilisation des API de synchronisation en faisant de la plus belle API l'async.

Cependant, je pense que nous ne devrions pas supprimer WebAssembly.compile et le constructeur Module : j'imagine des scénarios où vous avez un "serveur de code" (fournissant une mise en cache de code cross-origin via IDB + postMessage ; cela a déjà été spécifiquement discuté avec certains utilisateurs) qui veut compiler et mettre en cache le code sans avoir à "fausser" les paramètres d'instanciation. (Il pourrait aussi y avoir des frais généraux inutiles (ordures, rapiéçage, etc.) pour une instanciation inutile.) Et, pour les cas mêmes d'angle qui veulent la compilation synchrone (via new Module ), nous devons garder new Instance .

Donc, si vous êtes d'accord là-dessus, cela se résume à une proposition purement additive des deux surcharges WebAssembly.eval vous mentionnez. Oui?

Un tweak, cependant: je pense que nous ne devrions pas avoir un getter module car cela nécessiterait que le Instance conserve certaines données internes (c'est-à-dire le bytecode) pendant la durée de vie du Instance ; À l'heure actuelle, les Module peuvent généralement être GCed immédiatement après l'instanciation. Cela suggérerait soit une propriété de données (que l'utilisateur peut supprimer, même s'il l'oubliera probablement), ou peut-être une troisième version de eval qui renvoie une paire {instance, module} ...

Avoir une API asynchrone en une étape comme cas recommandé pour l'application monolithique typique est logique en tant que modèle recommandé.

Convenu avec @lukewagner que le cas de synchronisation complète (compilation en ligne) couvert par le nouveau module + la nouvelle instance est utile.
De plus, le serveur de compilation en arrière-plan (async) avec instanciation de synchronisation semble également utile.

L'ajout des deux variantes d'évaluation proposées semble être une bonne façon d'introduire cela.

Cependant, je n'aime pas le nom, car il sera confondu dans l'esprit des gens (de sécurité) avec js eval (auquel il ressemble d'une certaine manière, mais pas en termes de capture de portée).
Et WebAssembly.instantiate ?

Hah, bon point, eval a une certaine réputation . +1 à WebAssembly.instantiate .

Quelle serait la directive au développeur quand utiliser l'instanciation asynchrone ?

@mtrofin Pour utiliser WebAssembly.instantiate par défaut à moins qu'ils n'aient un schéma spécial de partage/chargement de code qui nécessitait la compilation de Module indépendamment de toute utilisation particulière.

@lukewagner Cela semble raisonnable.

Hah, bon point, eval a une certaine réputation. +1 à WebAssembly.instantiate.

D'accord.

Donc, si vous êtes d'accord là-dessus, cela se résume à une proposition purement additive des deux surcharges WebAssembly.eval que vous mentionnez. Oui?

C'est ce que cela ressemble.

Je pense que nous ne devrions pas avoir de module getter car cela nécessiterait que l'instance conserve certaines données internes (c'est-à-dire le bytecode) pendant toute la durée de vie de l'instance ; à l'heure actuelle, le module peut généralement être GCed immédiatement après l'instanciation. Cela suggérerait soit une propriété de données (que l'utilisateur peut supprimer, même s'il l'oubliera probablement), ou peut-être une troisième version d'eval qui renvoie une paire {instance, module} ...

Bien sûr, une propriété de données est meilleure. Ou si WebAssembly.instantiate renvoie toujours une instance, une paire de modules.

Est-ce correct : supposons que vous ayez WebAssembly.instantiate dans le but d'obtenir une variante de module fastmemory. Vous obtenez maintenant le module et structurez-le. Maintenant, ce module doit nécessairement être instancié avec Memory -es supportant fastmemory.

@pizlonator Ouais, je peux le faire dans ma tête de plusieurs manières. Je pense que j'aime mieux retourner la paire car cela conduira probablement à moins de personnes à entraîner accidentellement un Module inutilisé.

@mtrofin La recompilation peut toujours être nécessaire lorsque vous retirez le Module d'un appel instantiate et instantiate avec de nouvelles importations ; Je pense que le but de cet ajout d'API est que ce ne sera pas le cas courant et cela ne se produira que lorsque cela sera fondamentalement nécessaire (c'est-à-dire que vous avez 1 module accédant à deux types de mémoires).

Ce fil devient long, on dirait qu'il converge, mais pour être sûr à 100%, nous devons écrire le code que nous attendons des différents utilisateurs :

  1. Instanciation asynchrone d'un seul module.
  2. Instanciation asynchrone d'un module, avec partage de mémoire avec d'autres modules.
  3. Instanciation synchrone d'un seul module (je ne pense pas que le multi-module synchrone soit utile ?).
  4. Mise en cache de tous ces éléments (à la fois pour la mise en cache, ainsi que pour la récupération et l'instanciation, avec de la mémoire).
  5. Mise à jour d'un seul module .wasm , et mises en cache des charges des autres modules.

Rien d'autre? On dirait que @lukewagner a des idées sur les importations que je ne saisis pas complètement.

Cela signifie que les utilisations ultérieures de ce module doivent être instanciées de manière asynchrone, ou risquer de bloquer le thread d'interface utilisateur avec une instanciation synchrone étonnamment longue.

@jfbastien Je voudrais comprendre pour chaque extrait que nous attendons des développeurs qu'ils écrivent, ce qui les motiverait à suivre cette voie particulière et quelles informations le développeur doit avoir à sa disposition pour prendre une décision.

@mtrofin Bon, étant donné un Module m , vous appelleriez WebAssembly.instantiate(m) qui est asynchrone. Vous _pourriez_ appeler new Instance(m) et cela peut coûter cher, mais ce n'est pas différent de new Module(m) .

@jfbastien En supposant que lorsque vous dites "instanciation asynchrone", vous voulez dire "compilation et instanciation asynchrone", voici la version courte :

  1. WebAssembly.instantiate(bytecode, imports)
  2. WebAssembly.instantiate(bytecode, imports) , où imports inclut la mémoire partagée
  3. new Instance(new Module(bytecode), imports)
  4. Dans tous les cas, vous pouvez obtenir un Module , puis vous put cela dans un IDBObjectStore . Plus tard, vous get a Module m et appelez WebAssembly.instantiate(m, imports) .
  5. Rien de vraiment spécial ici : vous WebAssembly.instantiate un module du bytecode et instantiate le reste des Module tirés de l'IDB.

Devrions-nous recommander d'utiliser l'instanciation de synchronisation si vous pensez pouvoir utiliser la compilation de synchronisation, et l'instanciation async si vous pensez devoir utiliser la compilation async ?

En dehors de cela, je crains que le développeur ne soit désormais confronté à un système plus complexe : plus de choix qui transpirent les optimisations que nous prévoyons de faire, et je ne suis pas sûr que le développeur dispose des bonnes informations disponibles pour faire les compromis. Du point de vue du développeur, y a-t-il un ensemble plus restreint de préoccupations dont ils se soucient et se sentiraient à l'aise d'exprimer ? Nous avons parlé à un moment donné des développeurs ayant une « optimisation au détriment des points de défaillance précis » (il s'agissait de re. hisser les vérifications des limites). Une alternative serait-elle un drapeau « optimiser » ?

@mtrofin 99% de ce que les développeurs écriraient (ou auraient généré pour eux par la chaîne d'outils) serait WebAssembly.instantiate . Vous n'utiliseriez que des API de synchronisation pour "J'écris un JIT dans wasm" et WebAssembly.compile si vous écrivez un système de partage de code, donc je pense que les didacticiels "Getting Started" couvriraient exclusivement WebAssembly.instantiate .

@lukewagner J'ai remarqué que vous avez ajouté des importations au n ° 3 du nouveau module () ci-dessus. Je pense que l'ajouter à WebAssembly.compile est une bonne idée et complète les possibilités.
De cette façon, si vous voulez faire allusion à la mémoire au moment de la compilation, vous le pouvez.
Si vous instanciez à nouveau plus tard avec différentes importations, en particulier de manière synchrone, vous pouvez avoir un problème.

Donc résumé des changements (juste pour que je sois clair):

  • Ajouter WebAssembly.instantiate(bytes, imports) renvoie la promesse de {instance :, module :}
  • Ajouter WebAssembly.instantiate(module, imports) renvoie la promesse de {instance :, module :}
  • Changer pour un nouveau module (octets , importations ) renvoie le module
  • Changer en WebAssembly.compile(bytes , imports ) renvoie la promesse d'instance

Indiquez quelque part l'attente que l'instanciation sera rapide si les importations de la compilation correspondent à l'instanciation.

WDYT ?

Oh oups, je voulais mettre les importations en tant qu'argument dans Instance . Je ne suis pas convaincu que ce soit nécessaire pour Module ou compile . [Edit : parce que si vous les aviez, vous appelleriez simplement instantiate ]

Cela signifierait donc que pour le cas asynchrone de bout en bout, vous pouvez savoir que vous serez lié à une mémoire de hack de 4 Go, mais pas pour un noyau de filtre JITed ou un élément compilé en arrière-plan (à moins que vous ne créiez également un throw- exemple à l'extérieur) ?

+1 sur la focalisation des conseils sur la paire asynchrone de compilation et d'instanciation - rend le message simple et cache les complexités du problème de décision du développeur.

Oui, je pense que nous sommes tous d'accord pour dire aux gens :
Première fois:
WebAssembly.instantiate(bytes, imports) -> promesse de {module, instance} (cache module vers indexeddb)
Deuxième fois:
WebAssembly.instantiate(module, imports) -> promesse de {module, instance}

Avez-vous des objections à ce que ce soit le modèle principal?

Je suis déchiré sur les importations pour compiler / nouveau module. Il semble que cela pourrait être un indice utile.
Cependant, je serais prêt à le mentionner comme une possibilité et à différer l'ajout de cet argument (cela pourrait être facultatif) à Post-MVP.

Les pensées?

@mtrofin (Eh bien, techniquement, juste instantiate .)

@lukewagner (je pense que c'est ce que @mtrofin voulait dire)

@lukewagner , @flagxor OK, mais nous gardons l'API de compilation asynchrone, n'est-ce pas ?

Que diriez-vous de ce scénario : vous obtenez une application comme PhotoShop avec des tonnes de plugins. Chaque plugin est un module wasm. Vous démarrez l'application principale et vous parvenez à allouer la taille de mémoire magique qui déclenche la mémoire rapide (semble raisonnable pour ce scénario - une application, gourmande en mémoire).

Vous souhaitez compiler un certain nombre de plugins en parallèle, vous devez donc envoyer des travailleurs pour le faire. Vous ne pouvez pas transmettre à ces compilations la mémoire réelle que vous utiliserez (correct ?). Ainsi, en fonction des valeurs par défaut, vous obtenez une compilation slowmemory pour les plugins, qui sera ensuite suivie d'une série coûteuse de recompilations asynchrones pour la fastmemory lorsque les plugins seront connectés à l'application.

Si nous achetons ce scénario, nous pensons qu'il peut être utile de transmettre un descripteur de mémoire (pour être clair, sans mémoire de sauvegarde réelle) à l'API de compilation.

Oui, il devrait être possible (même encouragé) de passer Memory à la compilation.

@mtrofin Right, compile pour les utilisations avancées. Je suppose que cet exemple de plugin est un cas valide où vous voudriez _compiler_, _et_ vous avez un Memory , mais vous ne voulez pas (encore) instancier.

@pizlonator Btw, je voulais demander plus tôt, en supposant que le hack "lancer si plus de 1000 cartes de 4 Go par processus" est suffisant pour résoudre les problèmes d'ASLR/de sécurité, y a-t-il encore un besoin de mode lent/mode rapide en raison de la plate-forme restrictions de quota d'adresses virtuelles ? Parce que s'il n'y en avait pas, ce serait certainement bien si ce n'était même pas une considération de performance, même pour les utilisateurs avancés. (Les API instantiate semblent utiles à ajouter pour les autres raisons que nous avons mentionnées, bien sûr.)

Il existe également des applications qui pourraient bénéficier d'une taille de mémoire qui est une puissance de deux plus une zone de débordement, où l'application masque déjà les pointeurs pour supprimer le balisage afin de masquer les bits de poids fort pour éviter la vérification des limites. Ces applications doivent raisonner sur la taille d'allocation de mémoire qu'elles peuvent recevoir avant la compilation, soit en modifiant les constantes globales utilisées pour le masquage, soit en ajoutant des constantes appropriées tout en décompressant en wasm.

Il existe également l'optimisation du tampon à zéro dont certains runtimes pourraient vouloir tirer parti, et il n'y aura qu'un seul tampon par processus.

Cela rendrait également la plate-forme plus conviviale si elle pouvait raisonner sur la mémoire requise et la mémoire disponible avant de compiler l'application. Par exemple, pour permettre au navigateur ou à l'application d'informer l'utilisateur qu'il doit fermer certains onglets pour exécuter l'application, ou pour l'exécuter sans capacité dégradée.

Un navigateur Web peut vouloir disposer d'un mode d'application dédiée qui est une option pour les utilisateurs, où ils peuvent s'exécuter sur un appareil limité et avoir besoin de toute la mémoire et les performances qu'ils peuvent obtenir juste pour bien exécuter une seule application. Pour cela, il doit être capable de raisonner tôt sur les exigences.

La mémoire ne devrait pas avoir besoin d'être allouée avant la compilation, mais plutôt une réservation raisonnée effectuée. Sur un périphérique limité, la compilation à elle seule peut utiliser beaucoup de mémoire, donc même une grande allocation de VM peut être un obstacle.

Ce ne sont pas des questions nouvelles, elles sont en discussion depuis des années maintenant. La gestion des ressources mémoire est nécessaire et doit être coordonnée avec la génération de code.

@lukewagner Je pense que oui, car si nous nous

  • Je m'inquiéterais d'une attaque qui aurait besoin de ce plafond pour baisser.
  • Je m'inquiéterais des optimisations ailleurs dans la pile réduisant la quantité d'espace d'adressage virtuel qui nous est disponible, ce qui nous obligerait ensuite à réévaluer si le plafond est suffisamment bas.
  • Je m'inquiéterais d'empêcher les styles de programmation qui créent délibérément des milliers de modules. Par exemple, je sais que la plupart des clients du framework JavaScriptCore créent une machine virtuelle, effectuent un petit travail, puis la détruisent. Si WebAssembly est utilisé de la même manière depuis JS que JSC depuis Objective-C, alors pour le faire fonctionner sur des systèmes 64 bits, le GC devrait savoir que si vous allouez 1000 mémoires - même si chacune était petite - alors vous devez GC au cas où la prochaine allocation aboutirait au motif que ces 1000 mémoires sont désormais inaccessibles. La possibilité d'allouer des mémoires de piratage autres que 4 Go après qu'il y ait déjà, disons, 10 mémoires de piratage de 4 Go en direct signifierait que le GC n'aurait pas à modifier beaucoup son heuristique. Il n'aurait pas besoin de faire un GC lorsque vous allouez le 1001e module dans votre boucle instanciate->run->die. Ce serait un avantage pour les modèles qui utilisent une petite mémoire. Tout ce qui est inférieur à 1 Mo, et cela commence à avoir du sens d'en avoir 1000. Je peux imaginer des gens faisant des choses utiles en 64 Ko.
  • Je m'inquiéterais que cela soit moins utile pour d'autres contextes JavaScript. Je souhaite laisser la porte ouverte aux clients de l'API JSC C et de l'API Objective-C pour avoir accès à l'API WebAssembly à partir de leur code JS. Ces clients préféreraient probablement une petite limite sur le nombre de mémoires de 4 Go que nous allouons. Il est même tentant de rendre ce quota configurable dans un tel contexte.

J'aime que l'API améliorée supprime le besoin d'avoir un plafond artificiel sur le nombre de mémoires, ou le besoin de recompiler, ou d'autres choses indésirables. Je n'aime pas les plafonds artificiels à moins que les tolérances ne soient très grandes, et je ne pense pas que ce soit le cas ici.

@pizlonator Assez bien, et c'est une API plus propre / plus simple, donc je pense que c'est bien d'ajouter.

Quant à savoir pourquoi je ne suis pas concerné par les éléments que vous avez mentionnés (pour le moment):

  • Il se peut fort bien que la limite doive être relevée; c'est facile.
  • Quelle que soit la limite raisonnable, seule une petite fraction de l'espace d'adressage total de 64 bits sera utilisée, donc je ne sais pas quel est ce vecteur d'attaque ici ; le contenu déterminé a de nombreuses façons de se MOO lui-même
  • Nous augmentons l'heuristique du GC en fonction de la taille de la réservation, et ainsi, le fait de passer Memory travers

Mais qui sait ce que nous verrons à l'avenir, alors je suppose qu'il est utile d'avoir la flexibilité intégrée maintenant.

Je pense que c'est une mauvaise idée d'essayer de faire apparaître trop de détails d'implémentation dans l'API js (en particulier les détails spécifiques à l'architecture/à la plate-forme). Je pense qu'il devrait être suffisant que les implémentations aient leurs propres limites internes pour la mémoire rapide.

Avoir une fonction d'instanciation async semble raisonnable, mais je pense que nous devrions utiliser autre chose si nous voulons l'utiliser pour donner des conseils au compilateur. Par exemple, nous pourrions étendre les modules pour avoir une section d'indicateurs qui demande d'optimiser pour le singleton, d'optimiser pour de nombreuses instances, d'optimiser le temps de chargement, d'optimiser les performances prévisibles, etc. Bien sûr, ce que font (le cas échéant) les moteurs dépend totalement de l'implémentation, mais cela donne aux développeurs un bouton à tourner et la concurrence gardera les fournisseurs de navigateurs honnêtes.

Le ven. 28 oct. 2016 à 02h15, JF Bastien [email protected]
a écrit:

Oui, il devrait être possible (même encouragé) de transmettre la Mémoire au
compilation.

Je pense que cela devrait plutôt être découragé en faveur de ne pas
importer/exporter des souvenirs du tout. Après tout si un module n'importe pas
ou exporter la mémoire, la mémoire peut être réservée au moment de la compilation. je sais que nous
veulent pouvoir gérer efficacement le module-fu sophistiqué que certains
les applications voudront faire, mais je m'attends à ce que les applications WASM monolithiques
être plus fréquent que prévu. Je suis peut-être en minorité ici, mais
Je préfère voir moins de modules avec moins de liaison dynamique.

-

Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment-256805006 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ALnq1KXrUWaegRZwEhznmT1YcyI33IN9ks5q4T6TgaJpZM4Kh1gM
.

Je suis tout à fait d'accord avec vous, je pense que les applications monolithiques seront le principal cas d'utilisation, du moins pour les deux années qui suivront immédiatement mvp. S'inquiéter de ce qui se passe lorsque vous avez des dizaines de milliers de modules chargés, c'est supposer beaucoup d'un écosystème wasm qui n'existe pas encore.

Alors que reste-t-il à faire pour résoudre ce problème ? Une chose serait de mettre à jour http://webassembly.org/getting-started/js-api , ce que je peux faire. Une autre serait que binaryen émette cela par défaut (ça sonne bien @kripken?). @titzer Canary implémente-t-il WebAssembly.instantiate ?

Rien d'autre?

@lukewagner : pas sûr de ce que vous demandez, la discussion dans ce numéro est très longue. Voulez-vous changer le binaire de l'API WebAssembly.Instance de synchronisation actuelle qu'il utilise pour utiliser l'une des nouvelles API basées sur les promesses proposées ici ? Est-ce nécessaire ? Est-ce que nous supprimons l'ancienne méthode ?

@kripken À droite, passer à l'utilisation de WebAssembly.instantiate . Nous ne supprimons pas l'ancienne méthode, mais la nouvelle méthode est plus efficace et est destinée à être utilisée par défaut.

Nous pourrions utiliser l'API basée sur les promesses lors de la génération de HTML, mais de nombreux utilisateurs génèrent du JS et l'ajout automatique d'étapes asynchrones n'est pas trivial. Nous pouvons cependant documenter cela pour les gens. Mais je suppose que nous ne devrions faire tout cela qu'une fois que cela atterrira dans tous les navigateurs.

@kripken Je ne suis pas sûr de comprendre : l'approche actuelle consiste à n'utiliser aucune API asynchrone ?

Oui . Consultez ce problème pour ajouter des éléments asynchrones.

@kripken Node aurait des promesses de travail, je suppose. Même les shells ont un moyen de drainer explicitement la file d'attente de promesses (et ainsi d'exécuter toutes les résolutions de manière synchrone) ; en SM c'est drainJobQueue() et en V8 j'entends qu'il y a un %RunMicroTasks() . On dirait que vous pouvez simplement tester WebAssembly.instantiate et l'utiliser lorsqu'il est présent par défaut.

Bien sûr, mais d'abord, la prise en charge de Promise peut être dans le dernier node.js, mais ce n'est pas dans les versions couramment utilisées (version par défaut dans les distributions Linux par exemple). Et deuxièmement, le plus gros problème est que lorsque nous émettons du HTML, nous contrôlons la façon dont la page est chargée (emcc émet le code de chargement pour l'utilisateur) tandis que lorsque nous émettons du JS, le JS est supposé s'exécuter de manière linéaire et les utilisateurs dépendent sur cela, par exemple, ils peuvent avoir une autre balise de script juste après ce JS. Dans ce cas, l'utilisateur écrit son propre code de chargement.

En raison de ces deux éléments, comme mentionné précédemment, nous pouvons utiliser les API Promise lors de l'émission de HTML (alors vous n'êtes certainement pas dans le shell et avez le contrôle du chargement), mais pas lors de l'émission de JS. Là, nous ne pouvons que le documenter.

Node a-t-il des versions qui prennent en charge WebAssembly mais pas Promise ? Les utilisateurs de Node se soucient-ils de ces versions ?

Je ne comprends pas le truc de JS en ligne droite. Si vous retournez toujours une promesse, cela ne fonctionnera-t-il pas (les utilisateurs du code doivent consommer la promesse) ?

Je ne connais pas la réponse à la première question, mais un polyfill pourrait laisser s'exécuter wasm sur une version de nœud qui manque de promesses. Bien qu'il soit également possible de polyremplir les promesses, car le nœud a une version de setTimeout depuis un certain temps maintenant, je pense, mais je n'en suis pas sûr non plus.

À propos du problème de ligne droite : emcc émet JS qui configure le runtime et se connecte au code compilé. Certains JS dans une balise de script juste après peuvent appeler ce code compilé, par exemple en utilisant ccall . En d'autres termes, la sortie JS d'emcc n'est pas une promesse, donc je ne suis pas sûr de ce que vous entendez par "retourner une promesse" - qui la retournerait et qui la recevrait ? Mais dans tous les cas, comme mentionné précédemment, le chemin recommandé est qu'emcc émette du HTML, auquel cas nous pouvons créer un code de chargement asynchrone. C'est juste que certains utilisateurs préfèrent contrôler le chargement plus directement. Nous devrons les encourager à utiliser les trucs wasm async si c'est mieux.

IMO Node avec WebAssembly mais sans Promises n'est pas une contrainte de conception dont nous devrions nous soucier. Un polyfill à WebAssembly est assez idiot dans ce contexte.

Vous décrivez ce que fait le code aujourd'hui. Je ne le comprends pas tout à fait, mais j'aimerais revenir en arrière : je veux que le code des pages Web utilise l'API Promise. Emscripten est un producteur d'un tel code. Je ne comprends pas ce qui l'empêche d'émettre du code qui utilise des promesses. Je suis tout à fait d'accord si vous dites "c'est un travail important parce que ce n'est pas comme ça que ça marche aujourd'hui, mais nous y arriverons". Mais d'après notre discussion, je ne sais pas si c'est ce que vous dites.

Le problème que vous signalez concerne-t-il uniquement l'utilisation des API asynchrones pour la mise en cache ? C'est un début, je suppose, mais l'état final auquel j'aimerais arriver est l'endroit où l'API asynchrone est utilisée même lors du premier chargement.

Pourquoi un polyfill est-il stupide sur le nœud? Semble toujours utile dans ce contexte, même si moins que dans d'autres cas :)

Encore une fois: emscripten utilisera l'API de promesse quand il émet HTML. Et c'est le chemin recommandé. La réponse à votre question est donc "oui". Ce n'est pas un travail important. C'est esquissé dans ce numéro, qui, oui, se concentre sur la mise en cache, mais j'ai ajouté des notes de la (ancienne) discussion hors ligne que, tout en faisant cela, nous devrions également y faire un tas d'autres optimisations asynchrones, car nous le pouvons et c'est trivial.

Cela répond-il à vos préoccupations?

Tout ce que je dis, c'est que lorsqu'Emscripten émet JS - le chemin le moins recommandé - alors les garanties concernant cette sortie ne sont pas cohérentes avec le code faisant de la magie asynchrone en interne. Nous briserions le code des gens, ce que nous ne voulons pas faire. Comme je l'ai dit, quelqu'un peut écrire un JS qui s'exécute de manière synchrone juste après ce JS qui suppose qu'il est prêt. Nous ne pourrons donc pas utiliser de promesses dans ce cas. Imagine ça:

````

````

doSomething besoin de Module.my_func pour exister. Si output.js vient de renvoyer une promesse, alors elle n'existe pas encore. Ce serait donc un changement radical.

Cela a-t-il du sens maintenant?

Pourquoi un polyfill est-il stupide sur le nœud? Semble toujours utile dans ce contexte, même si moins que dans d'autres cas :)

Un polyfill à wasm n'est pas idiot. Prendre en charge les installations de nœuds qui n'ont pas de wasm, le polyfill, et n'ont pas de promesse mais ne polyfill qui, est idiot. C'est à moitié idiot. Ils devraient avoir l'autre moitié du cul

Encore une fois : Emscripten utilisera l'API promise lorsqu'il émettra du HTML. Et c'est le chemin recommandé. La réponse à votre question est donc "oui". Ce n'est pas un travail important. C'est esquissé dans ce numéro, qui, oui, se concentre sur la mise en cache, mais j'ai ajouté des notes de la (ancienne) discussion hors ligne que, tout en faisant cela, nous devrions également y faire un tas d'autres optimisations asynchrones, car nous le pouvons et c'est trivial.

Cela répond-il à vos préoccupations?

OK c'est bon! Tant que la plupart des pages Web utilisent l'API promise, je suis content.

[couper]

Cela a-t-il du sens maintenant?

Oui. Merci d'avoir expliqué.

De mon point de vue cependant, Emscripten ne se contente plus d'émettre du JS ! Votre exemple est logique pour Ye Olden Codes, mais les nouveautés devraient assumer les promesses de l'OMI.

BTW, je regardais passer webassembly.org/demo utiliser instantiate et il est un peu difficile b / c le courant synchrone new Instance se produit dans un contexte qui veut un résultat synchrone. Donc, une fois que nous aurons mis à jour Binaryen pour émettre instantiate par défaut, ce serait bien de reconstruire la démo AngryBots à partir de zéro.

Oui, mais notez qu'une reconstruction à partir de zéro pourrait ne pas suffire - je pense qu'Unity utilise son propre chargement et son propre code HTML. Nous devrons donc documenter et communiquer ce problème comme mentionné précédemment afin qu'ils puissent faire les choses nécessaires (ou peut-être que nous pouvons leur faire laisser emcc émettre le code html, mais je ne sais pas si c'est faisable pour eux).

Oui, mais notez qu'une reconstruction à partir de zéro pourrait ne pas suffire - je pense qu'Unity utilise son propre chargement et son propre code HTML. Nous devrons donc documenter et communiquer ce problème comme mentionné précédemment afin qu'ils puissent faire les choses nécessaires (ou peut-être que nous pouvons leur faire laisser emcc émettre le code html, mais je ne sais pas si c'est faisable pour eux).

Étant donné les inconvénients potentiels de ne pas utiliser l'API WebAssembly.instantiate , je pense qu'il vaut la peine de leur demander d'envisager de l'utiliser.

Ces inconvénients sont-ils documentés ? Des conseils clairs à ce sujet sur le site Web principal de wasm ou sur la page wiki emscripten wasm seraient une chose pratique à indiquer aux gens.

Je viens de parcourir ce long numéro moi-même, et je ne suis pas encore clair sur les inconvénients, alors je veux le lire aussi :)

@kripken Le défi avec le code actuel est que binaryen/emscripten ne fournissent que les importations nécessaires (nécessaires comme arguments à instantiate ) en même temps que les exportations sont requises de manière synchrone. Si les importations peuvent être rendues disponibles "d'avance", alors il est assez trivial d'ajouter un WebAssembly.instantiate à la queue du XHR asynchrone (comme je l'ai fait dans la démo actuelle avec le compile asynchrone vu , notre version actuelle de wasm AngryBots est sous-optimale et doit être rafraîchie de toute façon.

Oh, je n'avais pas compris qu'il était essentiel ici d'avoir les importations disponibles avant même que le wasm XHR ne commence. C'est alors beaucoup plus délicat. Donc la plupart de ce que j'ai dit avant est faux.

Pour que nous ayons les importations, nous devons avoir téléchargé, analysé et exécuté toute la colle JS. Si nous faisons tout cela avant même que le wasm XHR ne commence, alors c'est un schéma de chargement et un ensemble de compromis très différents de ceux que nous avons maintenant. En particulier, pour les petits et moyens projets, ce ne serait peut-être même pas une accélération, je suppose que nous devrions mesurer cela - si nous ne l'avons pas déjà fait ?

Ce ne serait pas quelque chose de simple à faire pour Unity. Cela nécessiterait des modifications importantes du code émis par emcc, afin que la colle JS puisse être exécutée avant que le code compilé ne soit prêt.

Peut-être voudrions-nous envisager un nouveau modèle de JS émis, un fichier JS pour les importations, un fichier JS pour le reste ? Et ce serait opt-in, donc nous ne briserions personne. De toute façon, il y a beaucoup de considérations, et sans mesures, il est difficile de deviner ce qui est optimal.

@kripken Pas avant le XHR mais une fois celui-ci terminé, et quelque temps avant de commencer à exécuter le script qui souhaite accéder de manière synchrone à l'objet d'exportation. Je m'attendrais à ce que cela soit aussi simple que de mettre ce code utilisant les exportations dans un rappel appelé lors de la résolution de l'instanciation.

Hmm, je suppose que je ne comprends toujours pas tout à fait cela, désolé (en particulier, je ne comprends pas pourquoi l'asynchrone interagit avec l'obtention des importations - l'avantage d'avoir les importations ne fonctionnerait-il pas de manière synchrone ?). Mais il semble que les problèmes que j'ai mentionnés ci-dessus soient toujours d'actualité même si c'est après la fin du XHR - c'est-à-dire que nous avons maintenant un seul script qui génère les importations et reçoit également les exportations. Diviser cela peut conduire à des compromis - nous devrons simplement mesurer lorsque nous aurons le temps.

À propos de l'insertion du code utilisant les exportations dans un rappel, cela ne fonctionnera malheureusement pas uniquement pour les raisons mentionnées précédemment, mais nous pourrions enquêter dans le cadre de ces mesures en ajoutant un nouveau mode de compilation.

La signature de instantiate est WebAssembly.instantiate(bytes, importObj) , donc pour lancer la compilation+instanciation asynchrone, vous devez passer importObj .

En parlant hors ligne, @lukewagner m'a fait

  1. Fournir la mémoire lors de la compilation.
  2. Fournir tous les imports lors de la compilation (superset du précédent).
  3. Faire tout cela de manière asynchrone.

Étant donné le fonctionnement actuel de la chaîne d'outils, faire 2 + 3 est difficile, comme décrit ci-dessus, car cela brisera les utilisateurs existants. Nous pouvons le faire, mais peut-être ne le voulons-nous pas par défaut, du moins pas au début - nous aurions besoin de consulter les membres de la communauté, etc. Et pour le faire vraiment bien - pas de nouvelle surcharge - il faudra du temps et du travail (le faire rapidement être fait en ajoutant une couche d'indirection sur les importations ou les exportations).

Mais certaines autres options sont trivialement faciles à utiliser :

  • 1 + 3 nécessiterait une nouvelle API comme instantiate(bytes, Memory) -> Promise . La raison pour laquelle cela est facile à utiliser est que la mémoire peut être créée tôt de toute façon (alors que presque toutes les autres importations sont des fonctions JS, que nous ne pouvons pas avoir au début).
  • 2 par lui-même, sans 3, c'est-à-dire new Instance(bytes, imports) . C'est-à-dire la compilation synchrone sur les données binaires + les importations. La raison pour laquelle c'est facile à utiliser est que notre code actuel fait ceci : instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), imports) donc nous plierions simplement cela en 1 appel d'API.

Je pense que la dernière option est logique. Cela signifie essentiellement l'ajout d'une version de synchronisation de la nouvelle API, ce qui rend les choses plus symétriques avec notre option de compilation synchrone existante. Et je ne vois pas de raison de lier la nouvelle optimisation de la connaissance de la mémoire au moment de la compilation à la compilation asynchrone ? (async est génial, mais un problème distinct ?)

Je suggère des méta-objets qui décrivent les importations (la mémoire) pour briser la contrainte selon laquelle les importations doivent être allouées avant la compilation. Ensuite, la méthode instantiate pourrait générer une erreur si la mémoire fournie ne correspondait pas à celle attendue et ne serait pas retardée lors de la recompilation.

Il serait aussi très utile de pouvoir compiler avant d'allouer la mémoire linéaire sur un dispositif contraint en mémoire, de pouvoir également optimiser la compilation pour la mémoire allouée à zéro dans l'espace d'adressage linéaire, et peut-être pour la mémoire avec zones de garde, et de optimiser pour la mémoire avec une taille maximale fixe qui peut être une puissance de deux plus une zone de débordement. Ce méta-objet pourrait également être utilisé par un décodeur/ré-écrivain d'utilisateur wasm pour émettre un code optimisé pour les caractéristiques de mémoire négociées.

C'est quelque chose qui est suggéré comme nécessaire au début de ce projet il y a de nombreuses années maintenant !

@kripken Bien que les API de synchronisation soient, je pense, techniquement nécessaires, elles devraient certainement être le chemin non recommandé, sinon nous introduirons des tonnes de jank de thread principal inutiles, une régression par rapport à asm.js +

J'avais l'intention de fournir un meilleur exemple pour expliquer pourquoi nous devons explicitement décourager (ou, comme je l'ai soutenu dans le passé, même interdire) la compilation/instanciation synchrone. Espérons que cela fournisse des éléments supplémentaires pour la discussion.

Faisons un zoom arrière et parlons de l'expérience utilisateur pendant une seconde.


Voici une comparaison entre le chargement de la démo AngryBots de manière asynchrone (via asm.js), à gauche et de manière synchrone (via WebAssembly dans V8), à droite.

comparison

La compilation synchrone offre une expérience utilisateur terrible et casse les barres de progression ou toute autre indication visuelle du chargement de l'application.

Quel que soit l'effort, je pense que nous devons travailler ensemble pour nous assurer qu'Emscripten, Unity et d'autres outils exportent de manière asynchrone en chargeant WebAssembly par défaut. cc @jechter @juj Non seulement cela, je pense que nous devons rendre

Nous devons explicitement encourager WebAssembly.compile & WebAssembly.instantiate et décourager new WebAssembly.Module & new WebAssembly.Instance .


Plongeons un peu plus loin et voyons à quel point cette barre de progression à droite est mauvaise pour WebAssembly.

Voici une trace capturée avec le panneau de performances DevTools :

screen shot 2016-12-28 at 1 26 59 pm

Il faut 30 secondes sur mon MacBook Air pour afficher une barre de progression.

Qu'est-ce qui prend ce temps ? Zoomons sur les ~20s après le téléchargement du module wasm lorsque le thread principal est totalement verrouillé :

screen shot 2016-12-28 at 2 18 36 pm

La compilation prend environ 20 s et l'instanciation prend environ 2 s.

Notez que dans ce cas, le chargeur Unity appelle déjà le WebAssembly.compile asynchrone s'il est pris en charge, donc le 20s est dû au fait que V8 effectue toujours une compilation synchrone sous le capot. cc @titzer @flagxor c'est quelque chose que V8 doit vraiment corriger.

Le jank d'instanciation 2s est dû au code du chargeur Unity appelant le new WebAssembly.Instance synchrone. C'est ce que nous devons vraiment corriger dans le code Unity et Emscripten.


Je pense qu'il vaut également la peine de mentionner que ce n'est pas _simplement_ le risque qu'un développeur se tire une balle dans le pied ou non. La page Web moyenne comprend des douzaines de scripts tiers : ces scripts ont tous le potentiel d'inclure WebAssembly pour le janking de documents.

(Si vous souhaitez explorer la trace plus en détail, vous pouvez consulter la chronologie complète ici : https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=https://www.dropbox.com/s/ noqjwql0pq6c1wy/wasm?dl=0)

Je suis d'accord à 100% pour dire que l'async est génial :) Mais je dis que c'est orthogonal à l'optimisation de l'obtention de la mémoire en même temps que la compilation du module. Et que nous ne pouvons pas trivialement obtenir async + cette optimisation - nous pouvons obtenir ce combo, mais soit au prix d'une surcharge, soit de temps si nous introduisons un nouveau drapeau pour cela et trouvons un moyen de le mettre en place par défaut .

Donc, si nous voulons que l'optimisation de la mémoire/compilation soit activée par défaut et à pleine efficacité, alors la lier à ce qu'elle soit également async est un problème. Mais si ce n'est pas urgent, ou si nous sommes d'accord avec le fait qu'il ne soit pas activé par défaut, ou si nous sommes d'accord avec une nouvelle surcharge, alors pas de problème.

Étant donné que les développeurs optent pour wasm, je pense qu'il est logique de saisir l'opportunité de modifier un peu la structure de niveau supérieur avec une nouvelle valeur par défaut (avec un moyen d'opter pour l'ancien comportement si nécessaire). Personnes utilisant la synchronisation

@kripken Je pense que vous arrivez à des conclusions différentes de celles de moi, @lukewagner et @s3ththompson, car pour vous, la partie la plus importante est d'offrir une transition transparente depuis asm.js pour les développeurs existants.

Est-ce correct?

Je suis d'accord que c'est une préoccupation valable. Je le pèse plus bas car IMO avec WebAssembly, nous amenons beaucoup plus de développeurs que asm.js. J'aimerais éviter de brûler les premiers utilisateurs, mais les développeurs existants d'IIUC sont assez flexibles et veulent une compilation asynchrone.

Si nous supposons qu'ils veulent une compilation asynchrone et qu'ils sont prêts à en refactoriser certaines pour l'obtenir, alors tout ce qui reste est d'avoir de la mémoire au moment de la compilation, ce que fournit cette API. C'est hautement souhaitable car cela évite un écueil.

Plus tôt, vous avez exprimé votre inquiétude quant à la quantité de travail nécessaire du côté d'Emscripten pour soutenir cela. Pensez-vous toujours que c'est un problème?

FWIW, Unity 5.6 utilisera WebAssembly.instantiate.

jonas

Le 28 décembre 2016, à 23h42, Seth Thompson [email protected] a écrit :

J'avais l'intention de fournir un meilleur exemple pour expliquer pourquoi nous devons explicitement décourager (ou, comme je l'ai soutenu dans le passé, même interdire) la compilation/instanciation synchrone. Espérons que cela fournisse des éléments supplémentaires pour la discussion.

Faisons un zoom arrière et parlons de l'expérience utilisateur pendant une seconde.

Voici une comparaison entre le chargement de la démo AngryBots avec des appels asynchrones (via asm.js), à gauche, et des appels synchrones (implémentation WebAssembly actuelle), à ​​droite.

La compilation synchrone offre une expérience utilisateur terrible et casse les barres de progression ou toute autre indication visuelle du chargement de l'application.

Quel que soit l'effort, je pense que nous devons travailler ensemble pour nous assurer qu'Emscripten, Unity et d'autres outils exportent de manière asynchrone en chargeant WebAssembly par défaut. cc @jechter @juj Non seulement cela, je pense que nous devons rendre

Nous devons explicitement encourager WebAssembly.compile & WebAssembly.instantiate et décourager les nouveaux WebAssembly.Module & new WebAssembly.Instance.

Plongeons un peu plus loin et voyons à quel point cette barre de progression à droite est mauvaise pour WebAssembly.

Voici une trace capturée avec le panneau de performances DevTools :

Il faut 30 secondes sur mon MacBook Air pour afficher une barre de progression.

Qu'est-ce qui prend ce temps ? Zoomons sur les ~20s après le téléchargement du module wasm lorsque le thread principal est totalement verrouillé :

La compilation prend environ 20 s et l'instanciation prend environ 2 s.

Notez que dans ce cas, le chargeur Unity appelle déjà le WebAssembly.compile asynchrone s'il est pris en charge, donc le 20s est dû au fait que V8 effectue toujours une compilation synchrone sous le capot. cc @titzer @flagxor c'est quelque chose que V8 doit vraiment corriger.

Le jank d'instanciation 2s est dû au code du chargeur Unity appelant le nouveau WebAssembly.Instance synchrone. C'est ce que nous devons vraiment corriger dans le code Unity et Emscripten.

Je pense qu'il vaut également la peine de mentionner qu'il ne s'agit pas simplement du risque qu'un développeur se tire ou non une balle dans le pied. La page Web moyenne comprend des douzaines de scripts tiers : ces scripts ont tous le potentiel d'inclure WebAssembly pour le janking de documents.

(Si vous souhaitez explorer la trace plus en détail, vous pouvez consulter la chronologie complète ici : https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=https://www.dropbox.com/s/ noqjwql0pq6c1wy/wasm?dl=0)

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub ou coupez le fil de discussion.

@jfbastien Peut-être que je ne comprends pas ce que vous entendez par "conclusions". Je présente principalement des options ici. Je n'ai pas de conclusion moi-même.

Si nous voulons l'option Memory-at-Compile-time dès maintenant, j'ai proposé quelques options qui peuvent nous permettre de l'avoir dès que possible, en modifiant trivialement notre compilation de synchronisation actuelle.

Ou si nous voulons que cela opte maintenant de manière asynchrone, j'ai proposé une option ("1 + 3", c'est-à-dire ne pas recevoir toutes les importations au moment de la compilation, juste la mémoire) qui peut également nous permettre de l'avoir dès que possible.

Ou si nous voulons nous concentrer maintenant sur une version asynchrone de cette option avec l'API actuelle (pas "1 + 3"), nous pouvons l'obtenir aussi, il faudra juste une planification minutieuse car ce serait un changement radical. Je ne pense pas que ce soit une option pour nous ici de décider de casser les utilisateurs existants, nous aurions donc besoin de consulter la communauté. Il y aura peut-être peu de soucis et nous pouvons simplement le faire, auquel cas, l'effort dépend de la quantité de frais généraux que nous sommes prêts à tolérer - si nous ne pouvons accepter aucun surcoût, alors cela peut être beaucoup. Ou peut-être y aura-t-il des problèmes, ce qui est mon intuition personnelle - tout changement important doit être effectué progressivement - dans ce cas, cela prendra plus de temps, nous commencerons probablement par une nouvelle option et envisageons éventuellement d'en faire la valeur par défaut.

Encore une fois, aucune conclusion de ma part. Tous les éléments ci-dessus sont des options. Cela dépend vraiment de ce qui vous intéresse le plus : la mémoire au moment de la compilation, ou asynchrone, ou les deux ; et la question de tolérer de nouveaux frais généraux ou non ; et si vous voulez cela de toute urgence ou pouvez attendre, etc. etc. Je suis heureux d'aider du côté emscripten avec tout ce qui précède, quelle que soit la décision des gens.

Ou si nous voulons nous concentrer maintenant sur une version asynchrone de cette option avec l'API actuelle (pas "1 + 3"), nous pouvons l'obtenir aussi, il faudra juste une planification minutieuse car ce serait un changement radical. Je ne pense pas que ce soit une option pour nous ici de décider de casser les utilisateurs existants, nous aurions donc besoin de consulter la communauté. Il y aura peut-être peu de soucis et nous pouvons simplement le faire, auquel cas, l'effort dépend de la quantité de frais généraux que nous sommes prêts à tolérer - si nous ne pouvons accepter aucun surcoût, alors cela peut être beaucoup. Ou peut-être y aura-t-il des problèmes, ce qui est mon intuition personnelle - tout changement important doit être effectué progressivement - dans ce cas, cela prendra plus de temps, nous commencerons probablement par une nouvelle option et envisageons éventuellement d'en faire la valeur par défaut.

Pour être sûr d'avoir bien compris :

  1. Quels sont les frais généraux ?

    • Ces frais généraux sont-ils inhérents à Async + Memory-at-compile-time, ou s'agit-il de contraintes d'implémentation qui peuvent être corrigées ultérieurement ? OMI, si elles sont inhérentes à A+M, nous devrions corriger l'API dès que possible.

    • IIUC les frais généraux sont une chose temporaire sur les importations ou les exportations, et ne sont pas inhérents à A+M, n'est-ce pas ? Pourrais-tu détailler ça un peu plus ?

  2. De quels utilisateurs parlons-nous de casser ? Les utilisateurs d'asm.js qui souhaitent que le même code cible également wasm ?

En d'autres termes : quel est l'état final idéal pour WebAssembly si nous avions un temps infini et aucun « héritage » ? Je pense que nous avons conçu pour cet état final idéal, et vous signalez les obstacles sur le chemin, mais je ne suis toujours pas sûr que ce soit le cas. Si tel est le cas, je ne suis pas sûr d'avoir suffisamment d'informations pour déterminer si WebAssembly a besoin d'un palliatif pour faciliter la transition, ou si une charge temporaire est acceptable pour un sous-ensemble d'utilisateurs d'Emscripten (et quels utilisateurs).

À propos des frais généraux dans certaines des options : comme décrit ci-dessus, la difficulté est que nous devons envoyer les importations et recevoir les exportations de manière synchrone actuellement, et que nous n'avons pas les importations JS tant que le JS n'est pas prêt (mais nous avons la mémoire !). Une façon de contourner cela est d'ajouter une couche d'indirection sur les importations ou les exportations. Par exemple, nous pourrions fournir des thunks au lieu des véritables importations très tôt (dans le HTML, avant le JS), nous semblons donc les fournir de manière synchrone (mais ce ne sont que les thunks, qui sont triviaux à créer avant même d'avoir le JS) . Ensuite, plus tard, lorsque nous aurons le JS, nous pourrons mettre à jour les thunks pour qu'ils pointent au bon endroit. Cela ajouterait une surcharge d'un autre appel JS et une recherche d'objet JS pour chaque appel d'importation. Aussi quelques frais généraux dans la taille du code et le démarrage.

À propos des utilisateurs défaillants : nous voulons que les utilisateurs existants d'emscripten puissent retourner un drapeau et obtenir un wasm fonctionnel . De nombreux utilisateurs et partenaires en sont satisfaits. Il leur permet de comparer asm.js et wasm et permet aux projets qu'ils ont consacré des efforts au portage de bénéficier de wasm sans nouvel effort de portage. Et cela évite le risque que s'ils ont également besoin d'asynchroniser leur code de démarrage lors du portage vers wasm, et que quelque chose se brise, ils pourraient blâmer wasm inutilement.

que nous n'avons pas les importations JS tant que le JS n'est pas prêt (mais nous avons la mémoire !)

Cela semble accessoire et non quelque chose que nous devrions intégrer dans le temps pour toujours avec une API. En outre, pour les raisons @ s3ththompson expliqué, ignorant même la mémoire au-compilation problème, nous avons besoin instanciation d'être async de toute façon. Je suis donc d'accord avec @jfbastien pour

À propos des utilisateurs défaillants : si nous proposons un chemin de migration simple (comme un événement/promesse « onload »), il devrait être simple pour les utilisateurs de migrer, la carotte étant tous les principaux gains de performances. Je ne pense pas que nous ayons la preuve que le backcompat exact de la source asm.js est une exigence stricte pour la configuration par défaut; nous avons de nombreux exemples du contraire : des utilisateurs prêts à tout pour obtenir les performances optimales puisque c'est, bien sûr, tout l'intérêt.

En parlant à @lukewagner hors ligne, nous pensons que la meilleure chose à faire est de commencer par activer l'instanciation pour wasm dans le paramètre recommandé par défaut dans emscripten, en ajoutant une couche d'indirection (comme décrit ci-dessus, mais sur les exportations). Il aura probablement une surcharge négligeable pour la plupart des cas d'utilisation. Cela nous procure donc les avantages que nous voulons tous ici.

Finalement, nous pouvons nous débarrasser de ce petit surcoût, mais ce sera un changement radical qui nécessitera beaucoup plus de planification et de discussion sur la liste de diffusion, etc. Mais comme nous pensons que le surcoût de la couche d'indirection est très faible, cette partie n'est pas urgente.

@kripken super ! Il serait utile d'avoir un schéma montrant différentes instances / JS / importations / exportations / mémoire / tables. Voulez-vous déplacer cette discussion ailleurs (Emscripten / binaryen repo) ? J'ai une idée de ce à quoi je pense que le code C++ devrait être organisé, mais il est assez évident maintenant que vous n'avez pas la même image ! Vous avez plus d'expérience là-bas, alors j'aimerais en tirer des leçons et aider autant que je peux.

@jfbastien : bien sûr. Je ne suis pas encore clair sur ce que vous recherchez dans un diagramme, mais oui, peut-être qu'un autre endroit est mieux. Pour la mise en œuvre de cela dans emscripten, il y a le problème mentionné précédemment, https://github.com/kripken/emscripten/issues/4711 , n'hésitez pas à en ouvrir un autre si cela ne couvre pas ce que vous voulez.

IIUC Emscripten l'utilise par défaut maintenant. Fermeture.

Pour faire suite au commentaire de @s3ththompson , notez que la compilation et l'instanciation synchrones sont utiles. Notamment, j'ai récemment rencontré une situation dans laquelle je voulais charger et compiler de manière synchrone un module WebAssembly dans Node.js ( v7.7.2 ). Si seules les API qui renvoient des promesses sont disponibles, cela signifie que je serais incapable de fournir une exportation synchrone.

Lorsque vous décidez de fournir des API asynchrone, synchronisée ou les deux, n'oubliez pas qu'un contexte de navigateur n'est pas le seul environnement dans lequel les utilisateurs souhaitent utiliser WebAssembly. Un certain nombre de personnes, dont moi-même, sont intéressées par le potentiel de WebAssembly en tant que cible de compilation efficace combinée au runtime Node.js, semblable à la JVM. Alors que les importations/exportations asynchrones peuvent atterrir dans Node.js, l'importation et l'exportation synchrones resteront le modèle dominant dans un avenir prévisible. Dans ce cas, la possibilité de charger un module WebAssembly et de compiler et instancier de manière synchrone ce module est très utile.

@kgryte J'aurais dû préciser que mon commentaire concernait principalement le navigateur en tant que contexte d'exécution. Nous avons atterri sur une surface d'API qui expose toujours les API synchrones. Les navigateurs peuvent imposer une limite de taille aux modules transmis aux API synchrones (Chrome le fait déjà, par exemple), mais cette limite est configurable par l'intégrateur et ne devrait pas avoir besoin de s'appliquer à Node.

@s3ththompson Merci pour la clarification.

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

Questions connexes

dpw picture dpw  ·  3Commentaires

JimmyVV picture JimmyVV  ·  4Commentaires

cretz picture cretz  ·  5Commentaires

spidoche picture spidoche  ·  4Commentaires

nikhedonia picture nikhedonia  ·  7Commentaires