Design: ¿Puede WebAssembly.Instance volver a compilar un módulo ya compilado?

Creado en 27 oct. 2016  ·  94Comentarios  ·  Fuente: WebAssembly/design

Digamos que tengo este código:

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

¿Se permite bloquear WebAssembly.Instance durante un período de tiempo considerable? ¿Podría, por ejemplo, volver a compilar el WebAssembly.Module ?

En la mayoría de los casos, diría que no, pero ¿qué pasa si al código ya compilado no le gusta particularmente la memoria que recibe? Diga, ¿porque esa memoria es una memoria de modo lento y el código se compiló asumiendo el modo rápido? tal vez memories[0] era una memoria de modo rápido, pero memories[1023] seguro que no lo será.

¿Qué pasa con este código en su lugar:

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

¿Se permite que esas llamadas a WebAssembly.Instance provoquen una recompilación?

Suponiendo que lo anterior tenga sentido, aquí hay algunas preguntas relacionadas:

  • ¿Queremos una función asíncrona que devuelva promesas que pueda compilar _y_ instanciar? No estoy diciendo que debamos eliminar ninguna de las API síncronas y asíncronas que ya tenemos, estoy proponiendo una nueva API asíncrona.
  • ¿Cómo expone un navegador que el código compilado en un WebAssembly.Module es rápido y que una instancia WebAssembly.Memory es adecuada para un código tan rápido? Ahora mismo la respuesta parece ser "pruébalo y ve si puedes notarlo".
  • ¿Cómo sabe un usuario cuántas instancias WebAssembly.Memory tiene permitidas antes de obtener un código lento (contando las implícitas, por ejemplo, las creadas por el segundo ejemplo)?
JS embedding

Comentario más útil

@kgryte Debería haber aclarado que mi comentario se refería principalmente al navegador como contexto de ejecución. Hemos aterrizado en una superficie de API que aún expone las API síncronas. Los navegadores pueden imponer un límite de tamaño en los módulos pasados ​​a las API síncronas (Chrome ya lo hace, por ejemplo), pero ese límite lo puede configurar el incrustador y no debería ser necesario aplicarlo a Node.

Todos 94 comentarios

Sería bueno que WebAssembly.Instance a veces causara una recompilación, de esta manera las variables globales inmutables podrían plegarse constantemente en el código generado. Por ejemplo, Emscripten genera código reubicable al compensar todos los punteros a datos estáticos. El desplazamiento se pasa como una var global inmutable cuando se crea una instancia del módulo. Si WebAssembly.Instance puede recompilar, podría especializar el código generado.

La especificación no define qué es "compilación", ni haría
sentido para que lo haga, porque los enfoques de implementación pueden diferir enormemente
(incluidos intérpretes). Por lo tanto, no puede tener ninguna opinión normativa sobre esto.
de cualquier manera. Lo mejor que pudimos hacer es agregar una nota que
Se espera que WebAssembly.Instance sea ​​"rápido".

El 27 de octubre de 2016 a las 03:24, Michael Bebenita [email protected]
escribió:

Sería bueno que WebAssembly.Instance a veces provoque la recompilación,
De esta manera, las variables globales inmutables se podrían plegar constantemente en el
código. Por ejemplo, Emscripten genera código reubicable compensando todos
punteros a datos estáticos. El desplazamiento se pasa como una var global inmutable
cuando se crea una instancia del módulo. Si WebAssembly.Instance puede volver a compilar,
podría especializar el código generado.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment -256522163,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AEDOO9sJPgujK3k0f6P7laYV_zaJxES5ks5q3_1LgaJpZM4Kh1gM
.

Estuvo de acuerdo en que esto sería una nota no normativa a lo sumo.

En SM, también tenemos la intención de que la creación de instancias nunca se vuelva a compilar para que haya un modelo de costo de compilación predecible para los desarrolladores (en particular, para que los desarrolladores puedan usar WebAssembly.compile e IDB para controlar cuándo reciben el golpe de compilación) . La recompilación en tiempo de instanciación desde dentro del constructor síncrono Instance ciertamente rompería ese modelo de costos y podría conducir a un gran jank.

Pero aprecio que la compilación separada esté fundamentalmente en desacuerdo con una variedad de optimizaciones que uno podría querer hacer para especializar el código generado en los parámetros ambientales. Fusionar la compilación y la creación de instancias en una operación asincrónica tiene sentido y es algo que hemos considerado en el pasado. La desventaja, por supuesto, es que esto inhibe el almacenamiento en caché explícito (no hay Module ), por lo que el desarrollador tiene que hacer una compensación desagradable. Algunas opciones:

  • El impl podría hacer un almacenamiento en caché implícito con contenido dirigido (que podría incluir parámetros ambientales en la clave), como lo hacemos con asm.js actualmente en FF. Esto sería una especie de molestia y tiene todos los problemas de previsibilidad / heurística de cualquier caché implícito.
  • Podríamos crear una nueva forma (por ejemplo, una nueva WebAssembly.Cache API en la que se pasa el código de bytes y los parámetros de instanciación y se obtiene un Promise<Instance> .

Esto último me intriga y podría proporcionar una experiencia de desarrollador mucho mejor que usar IDB y quizás una oportunidad de optimizar aún más el almacenamiento en caché (ya que el caché está especializado para un propósito específico), pero ciertamente es una gran característica y algo que nos gustaría tomarnos un tiempo. considerar.

@ rossberg-chromium Parece que he explicado mal mi propósito: no quiero discutir sobre lo que dice la especificación. Estoy tratando de señalar lo que parece una gran sorpresa para los desarrolladores, escondidos debajo de la API. Un desarrollador no esperará que se vuelva a compilar el resultado de .compile . Eso me parece un defecto de diseño.

@lukewagner incluso con el almacenamiento en caché implícito o explícito, podemos tener el mismo problema: cuántos WebAssembly.Memory se pueden crear en el mismo espacio de direcciones / origen es una limitación del navegador. Me gusta lo que está sugiriendo, pero creo que es ortogonal al tema. Avísame si he entendido mal lo que sugieres.

Tal vez .compile y Module podrían recibir un Memory , y Instance tiene una propiedad .memory que se puede pasar a otras compilaciones / instanciaciones ?

No estoy tratando de eliminar la posibilidad de volver a compilar, creo que más bien queremos un uso de API idiomático común que tenga información perfecta wrt Memory en el momento de la primera compilación (o en el momento de la recuperación de la caché), así que que la compilación emite controles de límites o no si es necesario.

@jfbastien Con el almacenamiento en caché implícito / explícito que se proporcionó con los parámetros de instanciación particulares (por lo que Memory ), no veo cómo habría necesidad de recompilación.

@jfbastien Con el almacenamiento en caché implícito / explícito que se proporcionó con los parámetros de instanciación particulares (por lo que Memory ), no veo cómo habría necesidad de recompilación.

Puede haber:

  1. Cree muchos Memory s.
  2. Compile código, con verificación de límites explícita (lenta) porque había demasiados Memory ies.
  3. Almacene ese código en caché.
  4. Dejar página.
  5. Cargue la página de nuevo.
  6. Asigne solo un Memory , que obtiene la versión rápida.
  7. Obtener del caché.
  8. Reciba el código lento Instance .

En este punto, estoy de acuerdo en que no _necesitas_ recompilación, pero estamos siendo un poco tontos al hacer comprobaciones lentas de límites cuando no es necesario.

Como dije: me gusta esta Cache API que propones, creo que hace que WebAssembly sea más utilizable, pero creo que el problema sigue ahí. 😢

Bueno, ese es mi punto acerca de tener una caché mejorada que acepta parámetros de instanciación y código de bytes: la caché se puede volver a compilar libremente si lo que ha almacenado en caché no coincide con los parámetros de instanciación. Entonces los pasos serían simplemente:

  1. crear muchos Memory s
  2. solicitar un Instance del caché, pasando uno de esos Memory s (lento)
  3. el código lento se compila, almacena en caché y se devuelve como Instance
  4. dejar página
  5. cargar la página de nuevo
  6. asignar solo uno Memory
  7. solicitar un Instance del caché, pasando el Memory rápido
  8. el código rápido se compila, almacena en caché y se devuelve como Instance

y después del paso 8, todas las cargas de páginas futuras se almacenarán en caché con código rápido o lento.

@lukewagner En primer lugar, está proponiendo una mitigación que va en contra del objetivo declarado de WebAssembly que proporciona un rendimiento determinista. La diferencia entre lento y rápido se citó por última vez en alrededor del 20%, por lo que realmente apestaría si una especificación que apunta minuciosamente a un rendimiento determinista la deja caer por el suelo debido a una peculiaridad de la API. No creo que el navegador que tiene un caché dirigido al contenido sea la solución correcta, porque la especificación ya tiene muchos problemas en otros lugares para obviar la necesidad de optimizaciones de caché de recompilación de perfiles. Por ejemplo, prometemos la compilación precisamente para que la aplicación pueda tener un comportamiento razonable incluso si el código no está almacenado en caché. Si la forma en que se especifica esto requiere que todos implementemos cachés u otras mitigaciones, entonces habremos fallado en nuestro objetivo de brindar a las personas un modelo de costos razonablemente portátil.

Para mí, el problema es solo este: una de las optimizaciones que todos tendremos que hacer de manera efectiva por razones competitivas (la verificación de límites de memoria virtual de 4GB, que solo llamaré el truco de 4GB) no se puede hacer en la especificación actual sin sacrificar una de estas cosas:

  • Puede salirse con la suya si siempre asigna 4 GB de memoria virtual para cualquier memoria wasm. Esto disuadirá a las personas de usar WebAssembly para módulos pequeños, porque alcanzará los límites de asignación de memoria virtual, la fragmentación de la memoria virtual u otros problemas si asigna muchos de estos. También me temo que si permite asignar muchos de ellos, reducirá la eficacia de las mitigaciones de seguridad como ASLR. Tenga en cuenta que las API existentes no comparten este peligro, ya que comprometen la memoria que asignan y harán OOM o fallarán antes de permitirle asignar mucho más de lo que permite la memoria física.
  • Puede salirse con la suya si permite una recompilación cuando encuentre una falta de coincidencia durante la creación de instancias (el código compilado quiere pirateo de 4GB pero la memoria no tiene esa asignación de memoria virtual). También podría salirse con la suya si la creación de instancias moviera la memoria a una región de 4GB, pero vea el punto anterior. Entonces, probablemente cada vez que esto suceda, será un error P1 para el navegador que lo encontró.

Creo que esto significa que la especificación alentará a los proveedores a converger para permitir reservas de 4GB cada vez que se asigne memoria wasm, o tener optimizaciones de caché / compilación diferida / perfil para detectar esto.

Finalmente, no entiendo el punto de hacer nada de esto no normativo. Esto puede ser normativo, porque podríamos hacer que la API excluya la posibilidad de que el navegador tenga que compilar algo sin saber qué tipo de memoria tendrá. Imagino que hay muchas formas de hacer esto. Por ejemplo, la instanciación podría devolver una promesa y podríamos eliminar el paso de compilación independiente. Esto dejaría en claro que la creación de instancias es el paso que podría tomar un tiempo, lo que implica fuertemente para el cliente que este es el paso que realiza la compilación. En una API de este tipo, el compilador siempre sabe si la memoria para la que está compilando tiene el truco de 4GB o no.

Es triste que solo estemos notando esto ahora, pero me sorprende que ustedes no vean que este es un problema mayor. ¿Hay alguna mitigación además del almacenamiento en caché que estoy pasando por alto?

@jfbastien en su escenario motivador, señaló que el módulo fue creado para preferir la memoria rápida. Supongo que está persiguiendo principalmente habilitar la optimización rápida de la memoria cuando un módulo en particular lo quiere, y podría estar de acuerdo con no hacerlo cuando el módulo no lo quiere (nada malo con encontrarlo de manera oportunista en ese caso, también , solo tratando de separar las prioridades).

Si es así, ¿cómo se sentirían estas alternativas al almacenamiento en caché o la creación de instancias asíncronas?

  1. El autor del módulo debe requerir 4 GB como memoria mínima / máxima
  2. Una variante de compilación (asíncrona al menos, tal vez también sincronización) que produce una instancia que acepta solo memoria rápida.

Para el problema del "truco de 4GB" y las discrepancias entre la memoria que lo usa y el código que lo espera, ¿tendría sentido que la compilación emitiera internamente dos versiones del código? (Obviamente, esto usaría más memoria, lo cual es triste, pero es de esperar que el tiempo de compilación no sea mucho peor, ¿el escritor podría generar ambos a la vez?)

@mtrofin No creo que tenga sentido pedir 4GiB si no tiene la intención de usarlo. La asignación virtual está separada de la intención de uso, por lo que creo que deberíamos separar ambos.

En 2 .: todavía no es muy útil para el desarrollador: si usan esa variante y falla, ¿entonces qué?

@kripken No creo que la doble compilación sea una buena idea.

@kripken Creo que eso es lo que haríamos sin ninguna otra solución a este problema.

Quiero que WebAssembly sea genial en el caso de la navegación casual: me cuentas algo interesante, me envías la URL, hago clic en ella y me divierto unos minutos. Eso es lo que hace que la web sea genial. Pero eso significa que muchas compilaciones serán de código que no está almacenado en caché, por lo que el tiempo de compilación jugará un papel importante en la duración de la batería de un usuario. Entonces, la doble compilación me entristece.

@mtrofin

El autor del módulo debe requerir 4 GB como memoria mínima / máxima

Eso no es realmente práctico, ya que muchos dispositivos no tienen 4 GB de memoria física. Además, eso es difícil de especificar.

Una variante de compilación (asíncrona al menos, tal vez también sincronización) que produce una instancia que acepta solo memoria rápida.

No creo que queramos compilaciones dobles.

@pizlonator Hasta ahora, no hemos considerado diseños que requieran diferentes modos de codegen: siempre hemos asignado regiones de 4 GB en 64 bits y hemos observado que esto tiene éxito en muchos miles de memorias en Linux, OSX y Windows. Tenemos un límite superior conservador para evitar el agotamiento total trivial del espacio de direcciones disponible que espero será suficiente para admitir el caso de uso de muchas bibliotecas pequeñas. Entonces, creo que la nueva restricción que estamos abordando aquí es que iOS tiene algunas limitaciones de espacio de direcciones virtuales que podrían reducir la cantidad de asignaciones de 4 GB.

Entonces, una observación es que una gran parte de la eliminación de verificación de límites permitida por el hack de 4gb se puede evitar con solo tener una pequeña región de protección al final de la memoria wasm. Nuestros experimentos iniciales muestran que los análisis básicos (nada que ver con los bucles, simplemente eliminar las comprobaciones de cargas / almacenes con el mismo puntero base) ya pueden eliminar aproximadamente la mitad de las comprobaciones de límites. Y probablemente esto podría mejorar. Entonces, el truco de 4gb sería una aceleración más modesta y menos necesaria.

Otra idea que tuve antes sería compilar código de manera pesimista con comprobaciones de límites (usando la eliminación basada en la página de protección) y luego eliminarlas cuando se crea una instancia con una memoria de modo rápido. Combinado, la sobrecarga podría ser bastante pequeña en comparación con el código idealizado de modo rápido.

@lukewagner

Hasta ahora, no hemos considerado diseños que requieran diferentes modos de codegen: siempre hemos asignado regiones de 4 GB en 64 bits y hemos observado que esto tiene éxito en muchos miles de memorias en Linux, OSX y Windows. Tenemos un número total conservador para evitar el agotamiento total trivial del espacio de direcciones disponible que espero será suficiente para admitir el caso de uso de muchas bibliotecas pequeñas. Entonces, creo que la nueva restricción que estamos abordando aquí es que iOS tiene algunas limitaciones de espacio de direcciones virtuales que podrían reducir la cantidad de asignaciones de 4 GB.

Este no es un problema específico de iOS. El problema es que si permite muchas de estas asignaciones, representa un riesgo de seguridad porque cada una de estas asignaciones reduce la eficacia de ASLR. Entonces, creo que la VM debería tener la opción de establecer un límite muy bajo para la cantidad de espacios de 4GB que asigna, pero eso implica que la ruta alternativa no debería ser demasiado cara (es decir, no debería requerir recompilación).

¿Qué límite tiene en la cantidad de memorias de 4 GB que asignaría? ¿Qué haces cuando alcanzas este límite, renunciar por completo o volver a compilar en la creación de instancias?

Entonces, una observación es que una gran parte de la eliminación de verificación de límites permitida por el hack de 4gb se puede evitar con solo tener una pequeña región de protección al final de la memoria wasm. Nuestros experimentos iniciales muestran que los análisis básicos (nada que ver con los bucles, simplemente eliminar las comprobaciones de cargas / almacenes con el mismo puntero base) ya pueden eliminar aproximadamente la mitad de las comprobaciones de límites. Y probablemente esto podría mejorar. Entonces, el truco de 4gb sería una aceleración más modesta y menos necesaria.

Estoy de acuerdo en que el análisis nos permite eliminar más controles, pero el truco de 4GB es el camino a seguir si quieres un rendimiento máximo. Todo el mundo quiere un rendimiento máximo, y creo que sería genial hacer posible obtener el rendimiento máximo sin causar también problemas de seguridad, problemas de recursos y recompilaciones inesperadas.

Otra idea que tuve antes sería compilar código de manera pesimista con comprobaciones de límites (usando la eliminación basada en la página de protección) y luego eliminarlas cuando se crea una instancia con una memoria de modo rápido. Combinado, la sobrecarga podría ser bastante pequeña en comparación con el código idealizado de modo rápido.

El código que tiene comprobaciones de límites es mejor fijar un registro para el tamaño de la memoria y fijar un registro para la base de la memoria.

El código que usa el truco de 4GB solo necesita anclar un registro para la base de memoria.

Entonces, esta no es una gran solución.

Además de la molestia de tener que discutir las especificaciones y las implementaciones, ¿cuáles son las desventajas de combinar la compilación y la creación de instancias en una acción prometida?

El problema es que si permite muchas de estas asignaciones, representa un riesgo de seguridad porque cada una de ellas
La asignación reduce la eficacia de ASLR.

No soy un experto en ASLR pero, iiuc, incluso si no tuviéramos un límite conservador (es decir, si le permitimos seguir asignando hasta que mmap fallara porque el kernel alcanzó su número de rangos de direcciones máx.), solo se consumiría una pequeña fracción de todo el espacio direccionable de 47 bits, por lo que la ubicación del código continuaría siendo altamente aleatoria en este espacio de 47 bits. IIUC, la ubicación del código ASLR tampoco es completamente aleatoria; lo suficiente para que sea difícil predecir dónde estará cualquier cosa.

¿Qué límite tiene en la cantidad de memorias de 4 GB que asignaría? A qué te dedicas
cuando llegue a este límite, ¿darse por vencido por completo o volver a compilar en la creación de instancias?

Bueno, ya que es de asm.js días, solo 1000. Entonces la asignación de memoria simplemente arroja. Tal vez necesitemos superar esto, pero incluso con muchas aplicaciones súper modularizadas (con muchos módulos wasm separados cada una) que comparten el mismo proceso, no puedo imaginar que necesitemos mucho más. Creo que Memory es diferente a los viejos ArrayBuffer s en que las aplicaciones, naturalmente, no querrán crear miles.

Además de la molestia de tener que discutir las especificaciones y las implementaciones, ¿cuáles son las desventajas?
de combinar la compilación y la instanciación en una acción prometida?

Como mencioné anteriormente, agregar una API Promise<Instance> eval(bytecode, importObj) está bien, pero ahora coloca al desarrollador en una situación difícil porque ahora tienen que elegir entre un aumento de rendimiento en algunas plataformas frente a la posibilidad de almacenar en caché su código compilado en todas las plataformas. Parece que necesitamos una solución que se integre con el almacenamiento en caché y eso es lo que estaba haciendo una lluvia de ideas arriba con la API explícita Cache .

Nueva idea: ¿qué pasa si agregamos una versión asíncrona de new Instance , digamos WebAssembly.instantiate y, como con WebAssembly.compile , decimos que se supone que todos deben usar la versión asíncrona? Esto es algo que he estado considerando _ de todos modos_ ya que la creación de instancias puede tomar algunos ms si se usa el parche. Luego decimos en la especificación que el motor puede hacer un trabajo costoso en compile o instantiate (¡o en ninguno de los dos, si un motor hace una validación / compilación perezosa!).

Eso todavía deja la pregunta de qué hacer cuando un compile d Module se almacena en el BID, pero esa es solo una pregunta difícil cuando hay múltiples modos de codegen _ de todos modos_. Una idea es que los Module s que se almacenan o recuperan del BID se aferran a un identificador para su entrada del BID y agregan un nuevo código compilado a esta entrada. De esa manera, la entrada del BID acumularía perezosamente una o más versiones compiladas de su módulo y podría proporcionar la que fuera necesaria durante la instanciación.

La parte del BID es un poco más laboriosa, pero parece bastante cercana a lo ideal en cuanto al rendimiento. WDYT?

Creo que agregar async instantiate tiene sentido, pero también agregaría un parámetro Memory a compile . Si pasa una memoria diferente a instantiate entonces puede volver a compilar, de lo contrario, ya habrá "enlazado" la memoria al compilar.

No he pensado en el almacenamiento en caché lo suficiente como para tener una opinión completa todavía.

@lukewagner

No soy un experto en ASLR pero, iiuc, incluso si no tuviéramos un límite conservador (es decir, si le permitimos seguir asignando hasta que mmap fallara porque el kernel alcanzó su número máximo de rangos de direcciones) , solo se consumiría una pequeña fracción de todo el espacio direccionable de 47 bits, por lo que la ubicación del código continuaría siendo altamente aleatoria en este espacio de 47 bits. IIUC, la ubicación del código ASLR tampoco es completamente aleatoria; lo suficiente para que sea difícil predecir dónde estará cualquier cosa.

ASLR afecta tanto al código como a los datos. El punto es hacer que sea más costoso para un atacante meterse en una estructura de datos sin perseguir un puntero hacia ella. Si el atacante puede agotar la memoria, definitivamente tiene más influencia.

Bueno, ya que es de asm.js días, solo 1000. Entonces la asignación de memoria simplemente arroja. Tal vez necesitemos superar esto, pero incluso con muchas aplicaciones súper modularizadas (con muchos módulos wasm separados cada una) que comparten el mismo proceso, no puedo imaginar que necesitemos mucho más. Creo que Memory es diferente a los viejos ArrayBuffers en que las aplicaciones, naturalmente, no querrán crear miles.

1000 parece un límite sensato. Preguntaré con la gente de seguridad.

Como mencioné anteriormente, agregar una PromesaLa API eval (bytecode, importObj) está bien, pero ahora coloca al desarrollador en una situación difícil porque ahora tienen que elegir entre un aumento de rendimiento en algunas plataformas o poder almacenar en caché su código compilado en todas las plataformas. Parece que necesitamos una solución que se integre con el almacenamiento en caché y eso es lo que estaba haciendo una lluvia de ideas anteriormente con la API de caché explícita.

Derecha. Puedo ver algunas formas en que una API de este tipo podría funcionar. Una API cursi pero práctica sería sobrecargar eval:

  1. instancePromise = eval (bytecode, importObj)
  2. instancePromise = eval (módulo, importObj)

y luego Instance tiene un getter:

módulo = instancia.módulo

Donde el módulo es de estructura clonable.

¿Qué piensas de esto?

Nueva idea: ¿qué pasa si agregamos una versión asíncrona de la nueva Instancia, digamos WebAssembly.instantiate y, como con WebAssembly.compile, decimos que se supone que todos deben usar la versión asíncrona? Esto es algo que he estado considerando de todos modos, ya que la creación de instancias puede demorar algunos ms si se usa la aplicación de parches. Luego decimos en la especificación que el motor puede hacer un trabajo costoso ya sea en la compilación o instanciación (¡o en ninguna de las dos, si un motor hace una validación / compilación perezosa!).

Eso todavía deja la pregunta de qué hacer cuando un Módulo compilado se almacena en IDB, pero esa es solo una pregunta difícil cuando de todos modos hay múltiples modos de codegen. Una idea es que los módulos que están almacenados en o recuperados de IDB mantienen un identificador en su entrada de IDB y agregan un nuevo código compilado a esta entrada. De esa manera, la entrada del BID acumularía perezosamente una o más versiones compiladas de su módulo y podría proporcionar la que fuera necesaria durante la instanciación.

La parte del BID es un poco más laboriosa, pero parece bastante cercana a lo ideal en cuanto al rendimiento. WDYT?

Intrigante. Relativo a mi idea anterior:

Ventaja: la tuya es una abstracción fácil de entender que es conceptualmente similar a lo que decimos ahora.
Con: el tuyo no genera tanta sinergia entre lo que hace el usuario y lo que hace el motor como lo permite mi propuesta.

Hay tres áreas en las que su propuesta no le da al usuario tanto control como la mía:

  1. El trabajo costoso podría ocurrir en uno de dos lugares, por lo que el usuario debe planificar que cualquiera de ellos sea costoso. Probablemente tengamos contenido web que se comporte mal si uno de ellos es caro, porque estaba ajustado para casos en los que resultaba barato. Mi propuesta tiene un lugar donde suceden cosas caras, lo que lleva a una mayor uniformidad entre las implementaciones.
  2. No hay una ruta claramente garantizada para que se almacenen en caché todas las versiones del código compilado. Por otro lado, mi uso de enhebrar el módulo a través de la API significa que la VM puede construir el módulo con más cosas cada vez, al mismo tiempo que permite al usuario administrar el caché. Entonces, si la primera vez hacemos 4GB, esto es lo que almacenaremos en caché, pero si no logramos hacer 4GB la segunda vez, podremos almacenar en caché ambos (si el usuario almacena en caché instance.module después de cada compilación).
  3. Los casos de esquina inusuales en el navegador u otros problemas a veces pueden conducir a una doble compilación en su esquema, porque compilaríamos una cosa en el paso de compilación pero luego nos damos cuenta de que necesitamos otra cosa en el paso de creación de instancias. Mi versión nunca requiere una doble compilación.

Entonces, me gusta más el mío. Dicho esto, creo que tu propuesta es una progresión, así que definitivamente me suena bien.

Este problema se basa en la frecuencia con la que la fragmentación hace que la asignación de
la memoria (por cierto, tendrá 4 GB + desplazamiento máximo admitido, u 8 GB) falla. Si el
probablemente sea mucho menos del 1%, entonces podría no ser del todo descabellado
que sea una situación OOM.

En el caso de que el usuario navegue por la web y utilice muchos
pequeños módulos WASM en rápida sucesión, presumiblemente no están todos en vivo en
una vez. En ese caso, una pequeña caché de fragmentos reservados de 4 GB mitigaría la
asunto.

Otra posible estrategia es generar una versión del código con
comprueba los límites, y si hay memoria rápida disponible, simplemente sobrescriba los límites
cheques con nops. Eso es feo, pero es muchísimo más rápido que un
recompilar, y menos espacio que dos compilaciones.

El jueves 27 de octubre de 2016 a las 9:03 p.m., pizlonator [email protected]
escribió:

@mtrofin https://github.com/mtrofin

El autor del módulo debe requerir 4 GB como memoria mínima / máxima

Eso no es realmente práctico, ya que muchos dispositivos no tienen 4 GB de espacio físico.
memoria. Además, eso es difícil de especificar.

Una variante de compilación (asíncrona al menos, tal vez también sincronizada) que produce una
instancia que acepta solo memoria rápida.

No creo que queramos compilaciones dobles.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment -256738329,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ALnq1F6CYUaq0unla0H6RYivUC8jfxIAks5q4PWIgaJpZM4Kh1gM
.

No es solo ASLR: también es contaminación paginable / asignador / etc. Todos necesitamos hablar con nuestra gente de seguridad, así como con la gente del kernel / sistemas. O podemos ser francos con los desarrolladores sobre los límites que cada motor impone a los Memory "rápidos", y convertirlos en idiomáticos en la API para que sea difícil usarlos incorrectamente.

Hay todas estas muletas que podemos usar, como nop o doble compilación, pero ¿por qué incluso tener muletas?

@jfbastien No creo que la memoria PROT_NONE cueste las entradas de la tabla de páginas; Creo que hay una estructura de datos separada que contiene asignaciones a partir de las cuales la tabla de páginas se llena de manera perezosa.

@pizlonator Me gusta esa idea, y puedo ver que es lo que alentamos a todos a usar por defecto en tutoriales, herramientas, etc. También es más conciso y fácil de enseñar si simplemente puedes ignorar Module . Esto también podría abordar la preocupación de @ s3ththompson sobre desalentar el uso de las API de sincronización haciendo que la API más agradable sea la asíncrona.

Sin embargo, creo que no deberíamos quitar WebAssembly.compile y el constructor Module : estoy imaginando escenarios en los que tienes un "servidor de código" (que proporciona almacenamiento en caché de código de origen cruzado a través de IDB + postMessage ; esto ya se ha discutido específicamente con algunos usuarios) que quiere compilar y almacenar en caché el código sin tener que "falsificar" los parámetros de instanciación. (También podría haber una sobrecarga innecesaria (basura, parches, etc.) para una instanciación innecesaria). Y, para los mismos casos de esquina que desean una compilación sincrónica (a través de new Module ), necesitaríamos mantener new Instance .

Entonces, si está de acuerdo en eso, entonces esto se reduce a una propuesta puramente aditiva de las dos sobrecargas WebAssembly.eval que menciona. ¿Sí?

Sin embargo, un ajuste: creo que no deberíamos tener un module getter ya que esto requeriría que Instance mantenga algunos datos internos (es decir, código de bytes) durante la vida útil del Instance ; ahora mismo, Module generalmente se puede generar GC inmediatamente después de la instanciación. Esto sugeriría una propiedad de datos (que el usuario puede eliminar, aunque probablemente se olvide), o tal vez una tercera versión de eval que devuelva un {instance, module} par ...

Tener una API asíncrona de un paso como el caso recomendado para la aplicación monolítica típica tiene sentido como patrón recomendado.

Estuvo de acuerdo con
Además, el servidor de compilación en segundo plano (asíncrono) con instanciación de sincronización también parece útil.

Agregar las dos variantes de evaluación propuestas parece una buena forma de introducir esto.

Sin embargo, no me gusta el nombre, porque se combinará en la mente de la gente (de seguridad) con js eval (que se parece de alguna manera, pero no en términos de captura de alcance).
¿Qué hay de WebAssembly.instantiate?

Ja, buen punto, eval tiene un poco de reputación . +1 a WebAssembly.instantiate .

¿Cuál sería la guía para el desarrollador sobre cuándo usar la instanciación asíncrona?

@mtrofin Para usar WebAssembly.instantiate de forma predeterminada a menos que tuvieran algún esquema especial de carga / uso compartido de código que requiera compilar Module s independientemente de cualquier uso en particular.

@lukewagner Esto parece razonable.

Ja, buen punto, eval tiene un poco de reputación. +1 a WebAssembly.instantiate.

Acordado.

Entonces, si está de acuerdo en eso, entonces esto se reduce a una propuesta puramente aditiva de las dos sobrecargas de WebAssembly.eval que menciona. ¿Sí?

Eso es lo que parece.

Creo que no deberíamos tener un captador de módulo, ya que esto requeriría que la Instancia mantuviera algunos datos internos (es decir, código de bytes) durante la vida útil de la Instancia; ahora mismo, el módulo generalmente se puede convertir en GC inmediatamente después de la creación de instancias. Esto sugeriría una propiedad de datos (que el usuario puede eliminar, aunque probablemente se olvide), o tal vez una tercera versión de eval que devuelva un par {instancia, módulo} ...

Seguro que parece que una propiedad de datos es mejor. O hacer que WebAssembly.instantiate siempre devuelva una instancia, un par de módulos.

¿Es esto correcto? Suponga que WebAssembly.instantiate con el objetivo de obtener una variante del módulo de memoria rápida. Ahora obtiene el módulo y lo estructura-clona. Ahora, este módulo está obligado a necesitar una instancia de Memory -es compatible con fastmemory.

@pizlonator Sí, puedo Module no utilizado.

@mtrofin La recompilación aún puede ser necesaria cuando se quita el Module de una llamada instantiate y instantiate con nuevas importaciones; Creo que el punto de esta adición de API es que no será el caso común y solo sucederá cuando sea fundamentalmente necesario (es decir, tienes 1 módulo accediendo a dos tipos de memorias).

Este hilo se está volviendo largo, parece que está convergiendo, pero para estar 100% seguros, necesitamos escribir el código que esperamos que escriban diferentes usuarios:

  1. Creación de instancias asincrónicas de un solo módulo.
  2. Creación de instancias asincrónicas de un módulo, con memoria compartida con otros módulos.
  3. Creación de instancias síncronas de un solo módulo (¿no creo que los módulos múltiples síncronos sean útiles?).
  4. Almacenamiento en caché de todos estos (tanto poner en el caché, como recuperar y crear instancias, con memoria).
  5. Actualización de un solo módulo .wasm y cargas en caché de los otros módulos.

¿Algo más? Parece que @lukewagner tiene ideas sobre las importaciones que no entiendo del todo.

Eso significa que los usos posteriores de este módulo deben crearse de forma asincrónica, o se corre el riesgo de bloquear el subproceso de la interfaz de usuario con una instanciación sincrónica sorprendentemente larga.

@jfbastien Me gustaría entender para cada fragmento que esperamos que escriban los desarrolladores, qué los motivaría a seguir ese camino en particular y qué información debe tener disponible el desarrollador para tomar una decisión.

@mtrofin Correcto, dado un Module m , llamarías WebAssembly.instantiate(m) que es asíncrono. _Podría_ llamar new Instance(m) y podría ser caro, pero eso no es diferente de new Module(m) .

@jfbastien Suponiendo que cuando dice "instanciación asíncrona" se refiere a "compilación e instanciación asíncrona", aquí está la versión corta:

  1. WebAssembly.instantiate(bytecode, imports)
  2. WebAssembly.instantiate(bytecode, imports) , donde imports incluye la memoria compartida
  3. new Instance(new Module(bytecode), imports)
  4. En todos los casos, puede obtener un Module , luego put eso en un IDBObjectStore . Más tarde, get a Module m regresa y llama WebAssembly.instantiate(m, imports) .
  5. Nada realmente especial aquí: usted WebAssembly.instantiate un módulo del bytecode y instantiate el resto de los Module s extraídos del BID.

¿Deberíamos recomendar el uso de la instanciación de sincronización si cree que puede usar la compilación de sincronización, y la instanciación de asíncrona si cree que debería usar la compilación asíncrona?

Aparte de eso, me preocupa que el desarrollador ahora se enfrente a un sistema más complejo: más opciones que trasciendan las optimizaciones que planeamos hacer, y no estoy seguro de que el desarrollador tenga la información correcta disponible para hacer las compensaciones. Pensando desde la perspectiva del desarrollador, ¿hay un conjunto más pequeño de preocupaciones que les preocupan y se sentirían cómodos expresando? Hablamos en un momento acerca de que los desarrolladores tienen una "optimización a expensas de puntos de falla precisos" (esto era volver a elevar las verificaciones de los límites). ¿Una alternativa sería una bandera "optimizar"?

@mtrofin El 99% de lo que los desarrolladores escribirían (o hubieran generado para ellos mediante la cadena de herramientas) sería WebAssembly.instantiate . Solo usaría las API de sincronización para el especial "Estoy escribiendo un JIT en wasm" y WebAssembly.compile si está escribiendo algún sistema de intercambio de código, por lo que creo que los tutoriales de "Introducción" cubrirían exclusivamente WebAssembly.instantiate .

@lukewagner Noto que agregaste importaciones al # 3 nuevo Módulo () arriba. Creo que agregarlo a WebAssembly.compile es una buena idea y redondea las posibilidades.
De esa manera, si desea dar pistas sobre la memoria en tiempo de compilación, puede hacerlo.
Si luego vuelve a crear una instancia con diferentes importaciones, especialmente de forma sincrónica, es posible que tenga un problema.

Así que resumen de los cambios (para que quede claro):

  • Agregar WebAssembly.instantiate (bytes, importaciones) devuelve la promesa de {instancia :, módulo:}
  • Agregar WebAssembly.instantiate (módulo, importaciones) devuelve la promesa de {instancia :, módulo:}
  • Cambiar a un nuevo módulo (bytes , importaciones ) devuelve el módulo
  • Cambiar a WebAssembly.compile (bytes , importaciones ) devuelve la promesa de la instancia

Indique en algún lugar la expectativa de que la instanciación sea rápida si las importaciones desde la compilación coinciden con la instanciación.

WDYT?

Oh, vaya, quise poner las importaciones como un argumento en Instance . No estoy convencido de que sea necesario para Module o compile . [Editar: porque si los tuvieras, simplemente llamarías instantiate ]

Entonces, eso significaría que para el caso asíncrono de extremo a extremo, puede saber que se vinculará a una memoria de pirateo de 4GB, pero no para un kernel de filtro JITed o un elemento compilado en segundo plano (a menos que también cree un throw- instancia de distancia)?

+1 en centrar la guía en el par asíncrono de compilar y crear instancias: simplifica el mensaje y oculta las complejidades del problema de decisión al desarrollador.

Sí, creo que todos estamos de acuerdo en que señalaríamos a la gente:
Primera vez:
WebAssembly.instantiate (bytes, importaciones) -> promesa de {módulo, instancia} (módulo de caché a indexeddb)
Segunda vez:
WebAssembly.instantiate (módulo, importaciones) -> promesa de {módulo, instancia}

¿Alguna objeción a que ese sea el patrón principal?

Estoy desgarrado por las importaciones para compilar / nuevo módulo. Parece que podría ser una pista útil.
Sin embargo, estaría dispuesto a mencionarlo como una posibilidad y aplazar la adición de ese argumento (podría ser opcional) a Post-MVP.

¿Pensamientos?

@mtrofin (Bueno, técnicamente, solo instantiate .)

@lukewagner (creo que eso es lo que quería decir

@lukewagner , @flagxor OK, pero mantenemos la API de compilación asíncrona, ¿verdad?

¿Qué tal este escenario? Obtienes una aplicación como PhotoShop con toneladas de complementos. Cada complemento es un módulo wasm. Inicia la aplicación principal y logra asignar el tamaño de memoria mágica que activa la memoria rápida (parece razonable para este escenario: una aplicación, hambre de memoria).

Desea compilar varios complementos en paralelo, por lo que despide a algunos trabajadores para que lo hagan. No puede pasar esas compilaciones la memoria real que usará (¿correcto?). Entonces, dependiendo de los valores predeterminados, obtiene una compilación de memoria lenta para los complementos, que luego será seguida por una costosa serie de recompilaciones asíncronas para la memoria rápida cuando los complementos se conectan a la aplicación.

Si compramos este escenario, entonces creemos que puede ser útil pasar algún descriptor de memoria (para ser claros, sin memoria de respaldo real) a la API de compilación.

Sí, debería ser posible (incluso recomendado) pasar Memory a la compilación.

@mtrofin Derecha, compile para usos avanzados. Supongo que el ejemplo del complemento es un caso válido en el que querrás _compilar_, _y_ tienes un Memory , pero no quieres crear una instancia (todavía).

@pizlonator Por cierto , quise preguntar antes, asumiendo que el truco "lanzar si más de 1000 mapas de 4gb por proceso" es suficiente para abordar los problemas de seguridad / ASLR, ¿todavía hay necesidad de modo lento / modo rápido debido a la plataforma? restricciones de cuota de direcciones virtuales? Porque si no lo hubiera, ciertamente sería bueno si esto ni siquiera fuera una consideración de rendimiento incluso para usuarios avanzados. (Las API instantiate parecen útiles de agregar por las otras razones que mencionamos, por supuesto).

También hay aplicaciones que podrían beneficiarse de un tamaño de memoria que es una potencia de dos más un área de derrame, donde la aplicación ya enmascara punteros para eliminar el etiquetado, por lo que puede enmascarar los bits altos para evitar la verificación de límites. Estas aplicaciones necesitan razonar sobre el tamaño de asignación de memoria que pueden recibir antes de la compilación, ya sea modificando las constantes globales utilizadas para enmascaramiento o para hornear en constantes adecuadas mientras se descomprimen a wasm.

También existe la optimización del búfer en cero que algunos tiempos de ejecución podrían querer aprovechar, y solo habrá un búfer de este tipo por proceso.

También haría que la plataforma fuera más fácil de usar si pudiera razonar sobre la memoria requerida y la memoria disponible antes de compilar la aplicación. Por ejemplo, para permitir que el navegador o la aplicación informe al usuario que necesita cerrar algunas pestañas para ejecutar la aplicación, o para ejecutarla sin capacidad degradada.

Es posible que un navegador web desee tener un modo de aplicación dedicada que sea una opción para los usuarios, donde pueden estar ejecutándose en un dispositivo limitado y necesitan toda la memoria y el rendimiento que pueden obtener solo para ejecutar bien una aplicación. Para ello, debe poder razonar sobre los requisitos con anticipación.

No es necesario asignar la memoria antes de la compilación, sino hacer una reserva razonada. En un dispositivo limitado, la compilación por sí sola puede usar mucha memoria, por lo que incluso una gran asignación de VM podría ser un obstáculo.

Estos no son temas nuevos, se han debatido durante años. La administración de recursos de memoria es necesaria y debe coordinarse con la generación de código.

@lukewagner Creo que sí, porque si

  • Me preocuparía por un ataque que saliera a la superficie y que necesitara que este techo bajara.
  • Me preocuparía que las optimizaciones en otras partes de la pila reduzcan la cantidad de espacio de direcciones virtuales que está disponible para nosotros, lo que luego nos obligaría a reevaluar si el límite máximo es lo suficientemente bajo.
  • Me preocuparía evitar estilos de programación que crean deliberadamente miles de módulos. Por ejemplo, sé que la mayoría de los clientes del marco JavaScriptCore crean una máquina virtual, hacen un pequeño trabajo y luego la destruyen. Si WebAssembly se usa de la misma manera desde JS que JSC desde Objective-C, entonces para que funcione en sistemas de 64 bits, el GC debería saber que si asigna 1000 memorias, incluso si cada una es pequeña, entonces debe GC en caso de que la siguiente asignación se realice correctamente debido a que esas 1000 memorias ahora son inalcanzables. La capacidad de asignar memorias de pirateo que no sean de 4GB después de que ya haya, digamos, 10 memorias de pirateo de 4GB en vivo significaría que el GC no tendría que cambiar mucho sus heurísticas. No tendría que hacer un GC cuando asigne el módulo 1001 en su bucle de instancia-> ejecutar-> die. Esto sería beneficioso para los patrones que usan una memoria pequeña. Cualquier cosa por debajo de 1 MB, y comienza a tener sentido tener 1000 de ellos. Me imagino a la gente haciendo cosas útiles en 64 KB.
  • Me preocuparía que esto sea menos útil para otros contextos de JavaScript. Quiero dejar la puerta abierta para que los clientes de JSC C API y Objective-C API tengan acceso a WebAssembly API desde su código JS. Esos clientes probablemente preferirían un pequeño límite en la cantidad de memorias de 4GB que asignamos. Es tentador incluso hacer que esa cuota sea configurable en ese contexto.

Me gusta que la API mejorada elimina la necesidad de tener un techo artificial en la cantidad de memorias, o la necesidad de recompilar u otras cosas indeseables. No me gustan los techos artificiales a menos que las tolerancias sean muy grandes, y no creo que ese sea el caso aquí.

@pizlonator Bastante justo, y es una API más limpia / simple, así que creo que está bien agregar.

En cuanto a por qué no me preocupan los elementos que ha mencionado (en este momento):

  • Es posible que sea necesario elevar el límite; eso es fácil.
  • En cualquier límite razonable, sólo se utilizará una pequeña fracción del espacio total de direcciones de 64 bits, por lo que no sé cuál es ese vector de ataque aquí; determinado contenido tiene muchas formas de OOM en sí mismo
  • Mejoramos las heurísticas de GC de acuerdo con el tamaño de la reserva y, por lo tanto, agitar Memory s solo conduce a GC más agresivo. Más GC no es genial, pero no estoy seguro de que este sea un patrón común.

Pero quién sabe qué veremos en el futuro, así que supongo que es útil tener la flexibilidad incorporada ahora.

Creo que es una mala idea tratar de mostrar demasiados detalles de implementación en la API js (especialmente detalles específicos de la arquitectura / plataforma). Creo que debería ser suficiente que las implementaciones tengan sus propios límites internos para una memoria rápida.

Tener una función de instanciación asíncrona parece razonable, pero creo que deberíamos usar algo más si queremos usarlo para dar pistas del compilador. Por ejemplo, podríamos extender los módulos para tener una sección de banderas que solicite optimizar para singleton, optimizar para muchas instancias, optimizar para el tiempo de carga, optimizar para un rendimiento predecible, etc. Por supuesto, lo que hacen los motores (si es que hacen algo) depende totalmente de la implementación, pero les da a los desarrolladores una perilla para girar y la competencia mantendrá a los proveedores de navegadores honestos.

El viernes 28 de octubre de 2016 a las 2:15 a. M., JF Bastien [email protected]
escribió:

Sí, debería ser posible (incluso alentado) pasar la memoria al
Compilacion.

Creo que más bien debería desalentarse en favor de no
importar / exportar memorias en absoluto. Después de todo, si un módulo no se importa
o exportar memoria, la memoria se puede reservar en el momento de la compilación. Yo se que nosotros
quieren poder manejar eficientemente el sofisticado módulo-fu que algunos
las aplicaciones querrán hacerlo, pero espero que las aplicaciones WASM monolíticas
ser más común de lo que anticipamos. Quizás estoy en minoría aquí, pero
Prefiero ver menos módulos con enlaces menos dinámicos.

-

Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/WebAssembly/design/issues/838#issuecomment -256805006,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ALnq1KXrUWaegRZwEhznmT1YcyI33IN9ks5q4T6TgaJpZM4Kh1gM
.

Estoy totalmente de acuerdo con usted, creo que las aplicaciones monolíticas serán el caso de uso principal, al menos durante los dos años posteriores al mvp. Preocuparse por lo que sucede cuando tiene decenas de miles de módulos cargados es asumir mucho sobre un ecosistema wasm que aún no existe.

Entonces, ¿qué queda por hacer para resolver este problema? Una cosa sería actualizar http://webassembly.org/getting-started/js-api , lo cual puedo hacer. Otro sería que binaryen emitiera esto por defecto (¿suena bien @kripken?). @titzer ¿Canary implementa WebAssembly.instantiate ?

¿Algo más?

@lukewagner : no estoy seguro de lo que está preguntando, la discusión sobre este tema es muy larga. ¿Quiere cambiar binaryen de la API de sincronización WebAssembly.Instance actual que utiliza para usar una de las nuevas API basadas en promesas propuestas aquí? ¿Es necesario? ¿Estamos eliminando el método antiguo?

@kripken Correcto, cambiando para usar WebAssembly.instantiate . No eliminaremos la forma anterior, pero la nueva es más eficiente y está diseñada para usarse de forma predeterminada.

Podríamos usar la API basada en promesas al generar HTML, pero muchos usuarios generan JS y agregar automáticamente pasos asíncronos no es trivial. Sin embargo, podemos documentar esto para las personas. Pero supongo que deberíamos hacer todo eso solo una vez que llegue a todos los navegadores.

@kripken No estoy seguro de entender: ¿el enfoque actual es no usar ninguna API asíncrona?

. Vea este problema para agregar cosas asincrónicas.

@kripken Node tendría promesas de trabajo, supongo. Incluso los shells tienen una forma de drenar explícitamente la cola de promesas (y así ejecutar todas las resoluciones de forma sincrónica); en SM es drainJobQueue() y en V8 escuché que hay un %RunMicroTasks() . Parece que podría simplemente presentar la prueba WebAssembly.instantiate y usarla cuando esté presente de forma predeterminada.

Claro, pero primero, el soporte de Promise podría estar en la última versión de node.js, pero no en las versiones de uso común (la versión predeterminada en las distribuciones de Linux, por ejemplo). Y segundo, el problema más grande es que cuando emitimos HTML, tenemos control sobre cómo se carga la página (emcc emite el código de carga para el usuario) mientras que cuando emitimos JS, se supone que JS se ejecuta linealmente, y los usuarios dependen en eso, por ejemplo, pueden tener otra etiqueta de script justo después de ese JS. En ese caso, el usuario escribe su propio código de carga.

Como resultado de ambos, como se mencionó anteriormente, podemos usar las API de Promise al emitir HTML (entonces definitivamente no está en el shell y tiene control sobre la carga), pero no cuando emite JS. Allí solo podemos documentarlo.

¿Node tiene versiones que admiten WebAssembly pero no Promise? ¿A la gente de Node le importan estas versiones?

No entiendo lo de JS en línea recta. Si siempre devuelve una promesa, ¿no funcionará eso simplemente (los usuarios del código deben consumir la promesa)?

No sé la respuesta a la primera pregunta, pero un polyfill podría permitir que wasm se ejecute en una versión de nodo que carece de promesas. Aunque, también podría ser posible hacer polyfill promesas ya que node ha tenido una versión de setTimeout por un tiempo, creo, pero tampoco estoy seguro de eso.

Acerca del problema de la línea recta: emcc emite JS que configura el tiempo de ejecución y se conecta al código compilado. Algunos JS en una etiqueta de secuencia de comandos inmediatamente después pueden llamar a ese código compilado, por ejemplo, usando ccall . En otras palabras, la salida JS de emcc no es una promesa, por lo que no estoy seguro de lo que quiere decir con "devolver una promesa": ¿quién la devolvería y quién la recibiría? Pero en cualquier caso, como se mencionó anteriormente, la ruta recomendada es para que emcc emita HTML, en cuyo caso podemos crear un código de carga asíncrono. Es solo que algunos usuarios prefieren controlar la carga de forma más directa. Tendremos que animarlos a que usen las cosas async wasm si es mejor.

El nodo IMO con WebAssembly pero sin Promises no es una restricción de diseño de la que debamos preocuparnos. Un polyfill para WebAssembly es bastante tonto en ese contexto.

Estás describiendo lo que hace el código hoy. No lo entiendo completamente, pero me gustaría hacer una copia de seguridad: quiero que el código de las páginas web use la API de Promise. Emscripten es un productor de dicho código. No entiendo qué le impide emitir código que usa promesas. Estoy totalmente bien si dices "eso es un trabajo importante porque no es así como funciona hoy, pero llegaremos a eso". Pero de nuestra discusión no estoy seguro si eso es lo que estás diciendo.

¿ El problema que señala es solo sobre el uso de las API asíncronas para el almacenamiento en caché? Supongo que es un comienzo, pero el estado final al que me gustaría llegar es donde se usa la API asíncrona incluso en la primera carga.

¿Por qué es un polyfill to wasm tonto en el nodo? Todavía parece útil en ese contexto, aunque menos que en otros casos :)

Una vez más: emscripten utilizará la API promesa cuando emite HTML. Y ese es el camino recomendado. Así que la respuesta a tu pregunta es sí". No es un trabajo significativo. Está esbozado en ese número, que sí, se centra en el almacenamiento en caché, pero agregué notas de la (antigua) discusión fuera de línea de que, mientras hacemos eso, también deberíamos hacer un montón de otras optimizaciones asincrónicas allí, ya que podemos y es trivial.

¿Eso responde a sus preocupaciones?

Todo lo que digo es que cuando Emscripten emite JS, la ruta menos recomendada, entonces las garantías sobre esa salida no son consistentes con el código que hace algo de magia asíncrona internamente. Estaríamos rompiendo el código de las personas, lo cual no queremos hacer. Como dije, alguien puede escribir JS que se ejecute sincrónicamente justo después de ese JS que asume que está listo. Entonces no podremos usar promesas en ese caso. Imagina esto:

`` ``

`` ``

doSomething necesita Module.my_func para existir. Si output.js acaba de devolver una promesa, entonces aún no existe. Entonces este sería un cambio rotundo.

¿Eso tiene sentido ahora?

¿Por qué es un polyfill to wasm tonto en el nodo? Todavía parece útil en ese contexto, aunque menos que en otros casos :)

Un polyfill para wasm no es tonto. Es una tontería abastecer a instalaciones de Node que no tienen wasm, lo rellenan y no tienen promesa, pero no lo rellenan. Es una tontería. Deberían tener la otra mitad del culo 😁

Nuevamente: Emscripten usará la API de promesa cuando emita HTML. Y ese es el camino recomendado. Así que la respuesta a tu pregunta es sí". No es un trabajo significativo. Está esbozado en ese número, que sí, se centra en el almacenamiento en caché, pero agregué notas de la (antigua) discusión fuera de línea de que, mientras hacemos eso, también deberíamos hacer un montón de otras optimizaciones asincrónicas allí, ya que podemos y es trivial.

¿Eso responde a sus preocupaciones?

¡Ok, eso es bueno! Siempre que la mayoría de las páginas web utilicen la API de promesa, estoy contento.

[recorte]

¿Eso tiene sentido ahora?

Si. Gracias por la explicación.

Sin embargo, desde mi punto de vista, ¡Emscripten ya no está solo en el negocio de emitir JS! Su ejemplo tiene sentido para Ye Olden Codes, pero las cosas nuevas deberían asumir promesas en mi opinión.

Por cierto, miré cambiar webassembly.org/demo para usar instantiate y es un poco complicado porque el actual síncrono new Instance ocurre en un contexto que quiere un resultado síncrono. Entonces, una vez que tengamos Binaryen actualizado para emitir instantiate de forma predeterminada, sería bueno reconstruir la demostración de AngryBots desde cero.

Sí, pero tenga en cuenta que una reconstrucción desde cero puede no ser suficiente; creo que Unity usa su propia carga y código HTML. Por lo tanto, necesitaremos documentar y comunicar este problema como se mencionó anteriormente para que puedan hacer las cosas necesarias (o tal vez podamos hacer que permitan que emcc emita el html, pero no sé si eso es factible para ellos).

Sí, pero tenga en cuenta que una reconstrucción desde cero puede no ser suficiente; creo que Unity usa su propia carga y código HTML. Por lo tanto, necesitaremos documentar y comunicar este problema como se mencionó anteriormente para que puedan hacer las cosas necesarias (o tal vez podamos hacer que permitan que emcc emita el html, pero no sé si eso es factible para ellos).

Dadas las posibles desventajas de no usar la API WebAssembly.instantiate , creo que vale la pena pedirles que consideren su uso.

¿Están documentadas esas desventajas? Una guía clara sobre esto en el sitio web principal de wasm o en la página wiki de emscripten wasm sería conveniente para señalar a la gente.

Acabo de leer este largo número yo mismo, y aún no tengo claras las desventajas, así que también quiero leerlo :)

@kripken El desafío con el código actual es que binaryen / emscripten solo proporcionan las importaciones necesarias (necesarias como argumentos para instantiate ) al mismo tiempo que las exportaciones se requieren sincrónicamente. Si las importaciones pueden estar disponibles "por adelantado", entonces es bastante trivial agregar un WebAssembly.instantiate al final del XHR asíncrono (como he hecho en la demostración actual con el asíncrono compile ). Así que no creo que esto requiera mucho trabajo por parte de Unity. Además, por lo que he visto , nuestra compilación wasm actual de AngryBots no es óptima y debe actualizarse de todos modos.

Oh, no entendí que tener las importaciones disponibles antes de que comience el wasm XHR es clave aquí. Eso es mucho más complicado entonces. Así que la mayor parte de lo que dije antes está mal.

Para que tengamos las importaciones, necesitamos haber descargado, analizado y ejecutado todo el pegamento JS. Si hacemos todo eso antes de que comience el wasm XHR, entonces es un esquema de carga y un conjunto de compensaciones muy diferente al que tenemos ahora. En particular, para proyectos pequeños y medianos, tal vez esto ni siquiera sería una aceleración, supongo que tendríamos que medir esto, ¿si aún no lo hemos hecho?

Esto no sería algo sencillo de hacer para Unity. Requeriría cambios significativos en el código que emcc emite, de modo que el pegamento JS pueda ejecutarse antes de que el código compilado esté listo.

¿Quizás nos gustaría considerar un nuevo modelo de JS emitido, un archivo JS para las importaciones, un archivo JS para el resto? Y sería opt-in, por lo que no romperíamos a nadie. De todos modos, hay mucho que considerar, y sin medidas es difícil adivinar qué es lo óptimo.

@kripken No antes del XHR, sino después de que se complete, y algún tiempo antes de que comencemos a ejecutar el script que quiere acceder sincrónicamente al objeto de exportación. Esperaría que pudiera ser tan simple como poner ese código de uso de exportaciones en alguna devolución de llamada llamada cuando se resuelve la instanciación.

Hmm, supongo que todavía no entiendo completamente esto, lo siento (en particular, no entiendo por qué la asincronía interactúa con la obtención de las importaciones: ¿podría el beneficio de que las importaciones no funcionen sincrónicamente?). Pero parece que los problemas que mencioné anteriormente siguen siendo relevantes incluso si esto es después de que se completa el XHR, es decir, ahora tenemos un único script que genera las importaciones y también recibe las exportaciones. Dividir eso puede dar lugar a compensaciones: solo necesitaremos medir cuando tengamos tiempo.

Acerca de poner el código que usa las exportaciones en una devolución de llamada, lamentablemente no solo funcionará por las razones mencionadas anteriormente, sino que podríamos investigar como parte de esas medidas agregando un nuevo modo de compilación.

La firma de instantiate es WebAssembly.instantiate(bytes, importObj) , por lo que para iniciar la compilación asíncrona + instanciar, debe pasar importObj .

Hablando sin conexión, @lukewagner me impresionó que el problema principal aquí es tener la memoria mientras se compila el módulo. Entonces, en general, parece que hay tres problemas que se relacionan con lo fácil que es usar esta nueva API en la práctica:

  1. Proporcionar la memoria al compilar.
  2. Proporcionar todas las importaciones al compilar (superconjunto del anterior).
  3. Haciendo todo esto de forma asincrónica.

Dado cómo funciona actualmente la cadena de herramientas, hacer 2 + 3 es difícil, como se describió anteriormente, porque romperá a los usuarios existentes. Podemos hacerlo, pero posiblemente no lo queramos de forma predeterminada, al menos no inicialmente; tendríamos que consultar a los miembros de la comunidad, etc. Y hacerlo realmente bien, sin nuevos gastos generales, requerirá tiempo y trabajo (hacerlo rápidamente puede puede hacerse agregando una capa de indirección en las importaciones o exportaciones).

Pero algunas otras opciones son trivialmente fáciles de usar:

  • 1 + 3 requeriría una nueva API como instantiate(bytes, Memory) -> Promise . La razón por la que esto es fácil de usar es que la memoria se puede crear temprano de todos modos (mientras que casi todas las demás importaciones son funciones JS, que no podemos tener al principio).
  • 2 por sí mismo, sin 3, es decir, new Instance(bytes, imports) . Es decir, compilación sincrónica de datos binarios + importaciones. La razón por la que esto es fácil de usar es que nuestro código actual hace esto: instance = new WebAssembly.Instance(new WebAssembly.Module(getBinary()), imports) por lo que simplemente lo doblamos en 1 llamada API.

Creo que la última opción tiene sentido. Básicamente significa agregar una versión sincronizada de la nueva API, haciendo las cosas más simétricas con nuestra opción de compilación sincrónica existente. ¿Y no veo una razón para vincular la nueva optimización de conocer la memoria en tiempo de compilación a la compilación asincrónica? (async es genial, pero ¿un problema separado?)

Sugiero metaobjetos que describen las importaciones (la memoria) para romper la restricción de que las importaciones deben asignarse antes de compilar. Entonces, el método instantiate podría arrojar un error si la memoria suministrada no coincidía con la esperada y no se retrasaría la recompilación.

También sería muy útil poder compilar antes de asignar la memoria lineal en un dispositivo con restricción de memoria, también para poder optimizar la compilación para la memoria asignada a cero en el espacio de direcciones lineales, y quizás para la memoria con zonas de protección, y para optimizar la memoria con un tamaño máximo fijo que puede ser una potencia de dos más una zona de derrame. Este metaobjeto también podría ser utilizado por un decodificador / reescritor de usuario de wasm para emitir código optimizado para las características de la memoria negociada.

¡Esto es algo que se sugiere como necesario al comienzo de este proyecto hace muchos años!

@kripken Si bien las API de sincronización son, creo, técnicamente necesarias, definitivamente deberían ser la ruta no recomendada o, de lo contrario, presentaremos toneladas de jank innecesarios del hilo principal, una regresión en comparación con asm.js +

He querido proporcionar un mejor ejemplo de por qué debemos desalentar explícitamente (o, como he argumentado en el pasado, incluso desautorizar) la compilación / instanciación sincrónica. Es de esperar que esto proporcione material adicional para la discusión.

Alejemos y hablemos sobre la experiencia del usuario por un segundo.


Aquí hay una comparación entre cargar la demostración de AngryBots de forma asincrónica (a través de asm.js), a la izquierda y sincrónicamente (a través de WebAssembly en V8), a la derecha.

comparison

La compilación sincrónica proporciona una experiencia de usuario terrible y rompe las barras de progreso o cualquier otra indicación visual de la carga de la aplicación.

Sea cual sea el esfuerzo, creo que debemos trabajar juntos para asegurarnos de que Emscripten, Unity y otras herramientas exporten de forma asincrónica y cargue WebAssembly de forma predeterminada. cc @jechter @juj No solo eso, creo que debemos hacer que sea prohibitivamente difícil para los desarrolladores caer en la trampa de escribir código de carga síncrona.

Necesitamos alentar explícitamente WebAssembly.compile & WebAssembly.instantiate y desalentar new WebAssembly.Module & new WebAssembly.Instance .


Profundicemos un poco más y veamos qué tan mala es la barra de progreso de la derecha para WebAssembly.

Aquí hay un seguimiento capturado con el panel de rendimiento de DevTools:

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

Mi MacBook Air tarda 30 segundos en mostrar cualquier barra de progreso.

¿Qué toma ese tiempo? Acerquémonos a los ~ 20 después de que se haya descargado el módulo wasm cuando el hilo principal está totalmente bloqueado:

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

La compilación tarda ~ 20 segundos y la creación de instancias ~ 2 segundos.

Tenga en cuenta que, en este caso, el cargador de Unity ya está llamando al WebAssembly.compile asíncrono si es compatible, por lo que los años 20 se deben a que V8 todavía hace una compilación síncrona bajo el capó. cc @titzer @flagxor esto es algo que V8 realmente necesita arreglar.

El jank de instanciación de 2s se debe a que el código del cargador de Unity llama al síncrono new WebAssembly.Instance . Esto es lo que realmente necesitamos corregir tanto en el código de Unity como en Emscripten.


Creo que también vale la pena mencionar que esto no es _meramente_ el riesgo de que un desarrollador se pegue un tiro en el pie o no. La página web promedio incluye docenas de scripts de terceros: todos esos scripts tienen el potencial de incluir WebAssembly con los mejores documentos.

(Si desea explorar la traza con más detalle, puede ver la línea de tiempo completa aquí: https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=https://www.dropbox.com/s/ noqjwql0pq6c1wy / wasm? dl = 0)

Estoy 100% de acuerdo en que async es genial :) Pero estoy diciendo que es ortogonal a la optimización de obtener la memoria al mismo tiempo que se compila el módulo. Y que no podemos obtener trivialmente async + esa optimización; podemos obtener ese combo, pero ya sea a costa de algunos gastos generales o de tiempo si introducimos una nueva marca para él y encontramos una manera de introducirlo en fases para que sea el predeterminado. .

Entonces, si queremos esa optimización de memoria / compilación ahora, activada de manera predeterminada y con la máxima eficiencia, entonces vincularla para que sea también asincrónica es un problema. Pero si no es urgente, o estamos de acuerdo con que no esté activado de forma predeterminada, o estamos de acuerdo con algunos gastos generales nuevos, entonces no hay problema.

Dado que los desarrolladores están optando por wasm, creo que tiene sentido aprovechar la oportunidad para cambiar un poco la estructura de nivel superior con un nuevo valor predeterminado (con una forma de optar por el comportamiento anterior si es necesario). Personas que usan la sincronización

@kripken Creo que llega a conclusiones diferentes a las que yo, @lukewagner y @ s3ththompson tenemos porque para usted la parte más importante es ofrecer una transición sin problemas desde asm.js para los desarrolladores existentes.

¿Es esto correcto?

Estoy de acuerdo en que esta es una preocupación válida. Lo pongo más bajo porque, en mi opinión, con WebAssembly estamos trayendo muchos más desarrolladores que asm.js. Me gustaría evitar quemar a los primeros usuarios, pero los desarrolladores existentes de IIUC son bastante flexibles y quieren una compilación asincrónica.

Si asumimos que quieren una compilación asincrónica y están dispuestos a refactorizar algunos para obtenerla, entonces todo lo que queda es tener memoria en el momento de la compilación, que es lo que proporciona esta API. Es muy deseable porque evita una trampa.

Anteriormente, expresó su preocupación por la cantidad de trabajo involucrado por parte de Emscripten para respaldar esto. ¿Sigues pensando que esto es un problema?

FWIW, Unity 5.6 utilizará WebAssembly.instantiate.

Jonás

El 28 de diciembre de 2016, a las 11:42 p.m., Seth Thompson [email protected] escribió:

He querido proporcionar un mejor ejemplo de por qué debemos desalentar explícitamente (o, como he argumentado en el pasado, incluso desautorizar) la compilación / instanciación sincrónica. Es de esperar que esto proporcione material adicional para la discusión.

Alejemos y hablemos sobre la experiencia del usuario por un segundo.

Aquí hay una comparación entre cargar la demostración de AngryBots con llamadas asincrónicas (a través de asm.js), llamadas a la izquierda y sincrónicas (implementación actual de WebAssembly), a la derecha.

La compilación sincrónica proporciona una experiencia de usuario terrible y rompe las barras de progreso o cualquier otra indicación visual de la carga de la aplicación.

Sea cual sea el esfuerzo, creo que debemos trabajar juntos para asegurarnos de que Emscripten, Unity y otras herramientas exporten de forma asincrónica y cargue WebAssembly de forma predeterminada. cc @jechter @juj No solo eso, creo que debemos hacer que sea prohibitivamente difícil para los desarrolladores caer en la trampa de escribir código de carga síncrona.

Necesitamos fomentar explícitamente WebAssembly.compile y WebAssembly.instantiate y desalentar el nuevo WebAssembly.Module y el nuevo WebAssembly.Instance.

Profundicemos un poco más y veamos qué tan mala es la barra de progreso de la derecha para WebAssembly.

Aquí hay un seguimiento capturado con el panel de rendimiento de DevTools:

Mi MacBook Air tarda 30 segundos en mostrar cualquier barra de progreso.

¿Qué toma ese tiempo? Acerquémonos a los ~ 20 después de que se haya descargado el módulo wasm cuando el hilo principal está totalmente bloqueado:

La compilación tarda ~ 20 segundos y la creación de instancias ~ 2 segundos.

Tenga en cuenta que en este caso, el cargador de Unity ya está llamando a la compilación asincrónica de WebAssembly.com si es compatible, por lo que los años 20 se deben a que V8 todavía hace una compilación sincrónica bajo el capó. cc @titzer @flagxor esto es algo que V8 realmente necesita arreglar.

El jank de creación de instancias de 2s se debe a que el código del cargador de Unity llama al nuevo WebAssembly. Esto es lo que realmente necesitamos corregir tanto en el código de Unity como en Emscripten.

Creo que también vale la pena mencionar que esto no es solo el riesgo de que un desarrollador se pegue un tiro en el pie o no. La página web promedio incluye docenas de scripts de terceros: todos esos scripts tienen el potencial de incluir WebAssembly con los mejores documentos.

(Si desea explorar la traza con más detalle, puede ver la línea de tiempo completa aquí: https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=https://www.dropbox.com/s/ noqjwql0pq6c1wy / wasm? dl = 0)

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub o silencie el hilo.

@jfbastien Quizás no entiendo lo que quieres decir con "conclusiones". En su mayoría, solo estoy presentando opciones aquí. Yo mismo no tengo una conclusión.

Si queremos la opción Memory-at-Compile-time en este momento, propuse algunas opciones que pueden permitirnos tenerla lo antes posible, modificando trivialmente nuestra compilación de sincronización actual.

O si queremos que optemos ahora de forma asíncrona, propuse una opción ("1 + 3", es decir, no recibir todas las importaciones en tiempo de compilación, solo la Memoria) que nos puede permitir tenerlo lo antes posible.

O si queremos centrarnos ahora en una versión asincrónica de esa opción con la API actual (no "1 + 3"), entonces también podemos obtener eso, solo será necesario planificarlo cuidadosamente porque sería un cambio importante. No creo que sea una opción para nosotros decidir romper a los usuarios existentes, por lo que tendríamos que consultar a la comunidad. Posiblemente habrá pocas preocupaciones y podemos hacerlo, en cuyo caso, cuánto esfuerzo dependerá de cuántos gastos generales estemos dispuestos a tolerar; si no podemos aceptar ningún gasto general, entonces podría ser mucho. O posiblemente habrá preocupaciones, que es mi intuición personal: cualquier cambio importante debe hacerse gradualmente, en ese caso tomará más tiempo, probablemente comenzaríamos con una nueva opción y eventualmente planearíamos convertirla en la predeterminada.

Una vez más, no hay conclusiones mías. Todo lo anterior son opciones. Realmente depende de lo que más le interese: memoria en tiempo de compilación, asincrónica o ambas; y la cuestión de tolerar nuevos gastos generales o no; y si lo desea con urgencia o puede esperar, etc. etc. Me complace ayudar en el lado de emscripten con cualquiera de los anteriores, como la gente decida.

O si queremos centrarnos ahora en una versión asincrónica de esa opción con la API actual (no "1 + 3"), entonces también podemos obtener eso, solo será necesario planificarlo cuidadosamente porque sería un cambio importante. No creo que sea una opción para nosotros decidir romper a los usuarios existentes, por lo que tendríamos que consultar a la comunidad. Posiblemente habrá pocas preocupaciones y podemos hacerlo, en cuyo caso, cuánto esfuerzo dependerá de cuántos gastos generales estemos dispuestos a tolerar; si no podemos aceptar ningún gasto general, entonces podría ser mucho. O posiblemente habrá preocupaciones, que es mi intuición personal: cualquier cambio importante debe hacerse gradualmente, en ese caso tomará más tiempo, probablemente comenzaríamos con una nueva opción y eventualmente planearíamos convertirla en la predeterminada.

Para estar seguro de que entiendo:

  1. ¿Cuáles son los gastos generales?

    • ¿Son estos gastos generales inherentes a Async + Memory-at-compile-time, o son restricciones de implementación que se pueden solucionar más adelante? En mi opinión, si son inherentes a A + M, entonces deberíamos arreglar la API lo antes posible.

    • IIUC, los gastos generales son algo temporal, ya sea en importaciones o exportaciones, y no son inherentes a A + M, ¿es correcto? ¿Podrías detallar esto un poco más?

  2. ¿De qué usuarios estamos hablando rompiendo? ¿Usuarios de asm.js que quieren que el mismo código también apunte a wasm?

Dicho de otra manera: ¿cuál es el estado final ideal para WebAssembly si tuviéramos un tiempo infinito y no un "legado"? Creo que hemos diseñado para ese estado final ideal, y estás señalando obstáculos en el camino, pero todavía no estoy seguro de que ese sea el caso. Si es así, entonces no estoy seguro de tener suficiente información para averiguar si WebAssembly necesita una solución provisional para facilitar la transición, o si la carga temporal es aceptable para un subconjunto de usuarios de Emscripten (y qué usuarios).

Acerca de los gastos generales en algunas de las opciones: como se describió anteriormente, lo complicado es que debemos enviar las importaciones y recibir las exportaciones de forma síncrona actualmente, y que no tenemos las importaciones de JS hasta que el JS esté listo (pero tenemos la memoria !). Una forma de evitarlo es agregar una capa de direccionamiento indirecto en las importaciones o exportaciones. Por ejemplo, podríamos proporcionar procesadores en lugar de las verdaderas importaciones muy temprano (en el HTML, antes de JS), por lo que parece que los proporcionamos de forma sincrónica (pero son solo los procesadores, que son triviales de crear incluso antes de que tengamos el JS). . Luego, más tarde, cuando tengamos el JS, podemos actualizar los procesadores para que apunten al lugar correcto. Esto agregaría la sobrecarga de otra llamada JS y una búsqueda de objetos JS para cada llamada de importación. También algunos gastos generales en el tamaño del código y el inicio.

Acerca de los usuarios que rompen: Queremos que los usuarios existentes de emscripten puedan dar la vuelta a una bandera y ponerse a trabajar . Muchos usuarios y socios están contentos con eso. Les permite comparar asm.js y wasm y permite que los proyectos en los que dedicaron esfuerzos a portar se beneficien de wasm sin un nuevo esfuerzo de portar. Y evita el riesgo de que si también necesitan asincificar su código de inicio mientras se transfieren a wasm, y algo se rompe, podrían culpar a wasm innecesariamente.

que no tenemos las importaciones de JS hasta que el JS esté listo (¡pero sí tenemos la memoria!)

Eso parece incidental y no es algo que debamos asimilar para siempre con una API. Además, por las razones que explicó @ s3ththompson , incluso ignorando el problema de la memoria en tiempo de compilación, necesitamos que la instanciación sea asincrónica de todos modos . Así que estoy de acuerdo con @jfbastien en que tenemos nuestra API de estado final ideal aquí y eso no debería ser lo que comprometemos aquí.

Acerca de los usuarios avanzados: si ofrecemos una ruta de migración simple (como un evento / promesa "onload"), debería ser simple para los usuarios migrar, y la zanahoria es la principal ventaja. No creo que tengamos ninguna evidencia de que el backcompat exacto de la fuente asm.js sea un requisito estricto para la configuración predeterminada; tenemos muchos ejemplos de todo lo contrario: usuarios dispuestos a hacer cualquier cosa para lograr el rendimiento óptimo, ya que ese es, por supuesto, el punto.

Hablando con @lukewagner sin conexión, creemos que lo mejor es comenzar habilitando la creación de instancias para wasm en la configuración predeterminada recomendada en emscripten, agregando una capa de indirección (como se describe arriba, pero en las exportaciones). Probablemente tendrá una sobrecarga insignificante para la mayoría de los casos de uso. Así que esto nos brinda los beneficios que todos queremos aquí.

Eventualmente, podemos deshacernos de esa pequeña sobrecarga, pero será un cambio importante que requerirá un montón de planificación y discusión de la lista de correo, etc., ya que obviamente no hay una manera buena / correcta de hacerlo, nos dimos cuenta. Pero como creemos que la sobrecarga de la capa de indirección es muy baja, esta parte no es urgente.

@kripken ¡genial! Sería útil tener un diagrama que muestre diferentes instancias / JS / importaciones / exportaciones / memoria / tablas. ¿Desea mover esta discusión a otra parte (repositorio de Emscripten / binaryen)? Tengo una imagen mental de lo que creo que debería organizarse el código C ++, ¡pero es bastante obvio a estas alturas que no tienes la misma imagen! Tienes más experiencia allí, así que me encantaría aprender de él y ayudar en lo que pueda.

@jfbastien : seguro. Todavía no tengo claro lo que está buscando en un diagrama, pero sí, tal vez otro lugar sea mejor. Para la implementación de esto en emscripten existe el problema mencionado anteriormente, https://github.com/kripken/emscripten/issues/4711 , también siéntase libre de abrir otro si eso no cubre lo que desea.

IIUC Emscripten usa esto por defecto ahora. Clausura.

Para dar seguimiento al comentario de @ s3ththompson , tenga en cuenta que la compilación y la v7.7.2 ). Si solo están disponibles las API que devuelven promesas, esto significa que no podría proporcionar una exportación sincrónica.

Al decidir si proporcionar asíncrono, sincronización o ambas API, recuerde que el contexto de un navegador no es el único entorno en el que la gente quiere usar WebAssembly. Varias personas, incluido yo mismo, están interesadas en el potencial de WebAssembly como un objetivo de compilación eficiente combinado con el tiempo de ejecución de Node.js, similar a la JVM. Si bien las importaciones / exportaciones asíncronas pueden aterrizar en Node.js, la importación y exportación sincrónicas seguirá siendo el patrón dominante en el futuro previsible. En cuyo caso, la capacidad de cargar un módulo WebAssembly y compilar e instanciar de forma síncrona ese módulo es muy útil.

@kgryte Debería haber aclarado que mi comentario se refería principalmente al navegador como contexto de ejecución. Hemos aterrizado en una superficie de API que aún expone las API síncronas. Los navegadores pueden imponer un límite de tamaño en los módulos pasados ​​a las API síncronas (Chrome ya lo hace, por ejemplo), pero ese límite lo puede configurar el incrustador y no debería ser necesario aplicarlo a Node.

@ s3ththompson Gracias por la aclaración.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

dpw picture dpw  ·  3Comentarios

konsoletyper picture konsoletyper  ·  6Comentarios

void4 picture void4  ·  5Comentarios

artem-v-shamsutdinov picture artem-v-shamsutdinov  ·  6Comentarios

badumt55 picture badumt55  ·  8Comentarios