Design: Propuesta: agregar enlaces de tipo entre idiomas

Creado en 1 may. 2019  ·  61Comentarios  ·  Fuente: WebAssembly/design

Actualmente, WebAssembly es muy bueno para ejecutar código escrito en lenguajes arbitrarios desde un intérprete dado (generalmente JS), pero carece de varias características clave cuando se trata de combinar varios lenguajes arbitrarios juntos .

Una de estas características es un sistema de tipo independiente del idioma. Me gustaría proponer que uno o varios de estos sistemas se agreguen a WebAssembly.

Como acotación al margen, en discusiones de características anteriores, algunos colaboradores han expresado que la interoperabilidad de lenguajes no debería ser un objetivo de diseño de WebAssembly. Si bien estoy de acuerdo en que no debería ser necesariamente un objetivo de alta prioridad , creo que es un objetivo por el que se lucha a largo plazo. Entonces, antes de entrar en los objetivos de diseño, voy a exponer las razones por las que creo que la interoperabilidad de los lenguajes vale la pena.

¿Por qué preocuparse por la interoperabilidad de idiomas?

Los beneficios de reducir las barreras de idioma a idioma incluyen:

  • Más bibliotecas para los usuarios de wasm : no hace falta decirlo, pero mejorar la interoperabilidad de los idiomas significa que los usuarios pueden usar las bibliotecas existentes con más frecuencia, incluso si la biblioteca está escrita en un idioma diferente al que están usando.

  • Adopción más fácil de lenguajes pequeños: en el mercado actual, a menudo es difícil que los lenguajes sin soporte corporativo obtengan tracción. Los nuevos lenguajes (e incluso lenguajes como D con años de refinamiento) tienen que competir con lenguajes con grandes ecosistemas y sufren su propia falta de bibliotecas. La interoperabilidad de los lenguajes les permitiría utilizar ecosistemas existentes como Python o Java.

  • Mejores cadenas de herramientas independientes del lenguaje : en este momento, la mayoría de los lenguajes tienen su propio esquema de carga de bibliotecas y administrador de paquetes (o, en el caso de C / C ++, varios no oficiales). Escribir un constructor de proyectos independiente del lenguaje es difícil, porque estos lenguajes a menudo tienen dependencias sutiles e incompatibilidades ABI, que requieren una solución monolítica para todo el proyecto para resolverse. Un sistema robut de tipo inter-lenguaje facilitaría la división de proyectos en módulos más pequeños, que pueden ser manejados por una solución similar a npm.

En general, creo que el primer punto es el más importante, por un amplio margen. Un mejor sistema de tipos significa un mejor acceso a otros lenguajes, lo que significa más oportunidades para reutilizar el código en lugar de escribirlo desde cero. No puedo exagerar lo importante que es eso.

Requisitos

Con eso en mente, quiero resumir los requisitos que un sistema de tipos entre idiomas necesitaría aprobar.

Estoy escribiendo bajo la suposición de que el sistema de tipos se usaría estrictamente para anotar funciones pasadas entre módulos, y no verificaría cómo los lenguajes usan su propia memoria lineal o administrada de ninguna manera.

Para ser realmente útil en un entorno de wasm, dicho sistema de tipos necesitaría:

1 - Seguridad

  • Tipo seguro: el destinatario de la llamada solo debe tener acceso a los datos especificados por el llamador, estilo de capacidades de objeto.

    • La memoria debe "olvidarse" al final de una llamada. Una persona que llama no debería poder obtener acceso a los datos de una persona que llama, regresar y luego acceder a esos datos nuevamente en cualquier forma.

2 - Sobrecarga

  • Los desarrolladores deben sentirse cómodos haciendo llamadas entre módulos con regularidad, por ejemplo, en un bucle de renderizado.

    • Copia cero: el sistema de tipos debe ser lo suficientemente expresivo para permitir a los intérpretes implementar estrategias de copia cero si así lo desean, y lo suficientemente expresivo para que estos implementadores sepan cuándo la copia cero es óptima.

3 - Gráficos de estructura

  • El sistema de tipos debe incluir estructuras, punteros opcionales, matrices de longitud variable, rebanadas, etc.

    • Idealmente, la persona que llama debería poder enviar un gráfico de objeto disperso en la memoria respetando los requisitos 1 y 2.

4 - Tipos de referencia

  • Los módulos deben poder intercambiar tipos de referencia anidados profundamente dentro de los gráficos de estructura.

5 - Puente entre diseños de memoria

  • Este es un punto muy importante. Las diferentes categorías de idiomas tienen diferentes requisitos. Los lenguajes que dependen de la memoria lineal querrían pasar porciones de memoria, mientras que los lenguajes que dependen de GC querrían pasar referencias de GC.

    • Un sistema de tipos ideal debería expresar tipos semánticos y dejar que los lenguajes decidan cómo interpretarlos en la memoria. Si bien el paso de datos entre lenguajes con diseños de memoria incompatibles siempre supondrá una sobrecarga, el paso de datos entre lenguajes similares debería ser idealmente económico (por ejemplo, los embebidores deberían evitar los pasos de serialización-deserialización si una memcpy puede hacer el mismo trabajo).

    • Los enlaces adicionales también pueden permitir el almacenamiento en caché y otras estrategias de optimización.

    • El trabajo de conversión al pasar datos entre dos módulos debe ser transparente para el desarrollador, siempre que los tipos semánticos sean compatibles.

6 - Manejo de errores en tiempo de compilación

  • Cualquier error relacionado con argumentos de llamada de función no válidos debe ser detectable y expresable en tiempo de compilación, a diferencia de, por ejemplo, JS, donde se lanzan TypeErrors en tiempo de ejecución cuando se intenta evaluar el argumento.
  • Idealmente, los propios compiladores de lenguajes deberían detectar errores de tipo al importar módulos wasm y enviar errores idiomáticos expresivos al usuario. La forma que debería tomar esta comprobación de errores debería detallarse en el repositorio de convenciones de herramientas .
  • Esto significa que un IDL con convertidores existentes a otros idiomas sería una ventaja.

7 - Proporcionar un punto de Schelling para la interacción entre idiomas

  • Es más fácil decirlo que hacerlo, pero creo que wasm debería enviar una señal a todos los redactores de compiladores de que la forma estándar de interoperar entre lenguajes es X. Por razones obvias, no es deseable tener múltiples estándares en competencia para la interoperabilidad de lenguajes.

Implementación propuesta

Lo que propongo es que se agreguen enlaces a @kentonv a Webassembly.

Funcionarían de forma similar a los enlaces WebIDL: los módulos wasm exportarían funciones y usarían instrucciones especiales para enlazarlas con firmas mecanografiadas; otros módulos importarían estas firmas y las vincularían a sus propias funciones.

La siguiente pseudo-sintaxis está destinada a dar una idea de cómo se verían estos enlaces; es aproximada y está muy inspirada en la propuesta de WebIDL, y se centra más en los desafíos técnicos que en proporcionar listas exhaustivas de instrucciones.

Las instrucciones de enlace de Capnproto se almacenarían todas en una nueva sección de enlaces de Cap'n'proto .

Tipos Cap'n'proto

El estándar necesitaría una representación interna del lenguaje de esquema de

`` Capitán Proto
struct Person {
nombre @ 0 : Texto;
fecha de nacimiento a las 3 : fecha;

email @ 1 : Texto;
teléfonos @ 2 : Lista (Número de teléfono);

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

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

}
}

estructura Fecha {
año @ 0 : Int16;
mes @ 1 : UInt8;
día @ 2 : UInt8;
}

might be represented as

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

Serializar desde memoria lineal

Los mensajes de Capnproto transmiten dos tipos de datos: segmentos (bytes sin procesar) y capacidades.

Estos se asignan aproximadamente a la memoria lineal y las tablas de WebAssembly. Como tal, la forma más sencilla posible para que el ensamblaje web cree mensajes capnproto sería pasar un desplazamiento y una longitud a la memoria lineal para los segmentos, y un desplazamiento y una longitud a una tabla para las capacidades.

(Se podría idear un mejor enfoque para las capacidades, para evitar verificaciones de tipo en tiempo de ejecución).

Tenga en cuenta que los cálculos de serialización reales se llevarían a cabo en el código de pegamento, en todo caso (consulte Generación del código de pegamento ).

Operadores de enlace

| Operador | Inmediatos | Niños | Descripción |
| : --- | : --- | : --- | : --- |
| segmento | off-idx
len-idx | | Toma los valores off-idx 'th y len-idx ' th wasm de la tupla de origen, que deben ser ambos i32 s, como el desplazamiento y la longitud de un segmento de memoria lineal en que se almacena un segmento. |
| captable | off-idx
len-idx | | Toma los valores off-idx 'th y len-idx ' th wasm de la tupla de origen, que deben ser i32 s, como el desplazamiento y la longitud de un segmento de tabla en el que se almacena la tabla de capacidad. |
| mensaje | tipo capnproto
capacidad-tabla | segmentos | Crea un mensaje capnproto con el formato capnproto-type , utilizando la tabla de capacidades y los segmentos proporcionados. |

Serializar desde la memoria administrada

Es difícil precisar un comportamiento específico antes de que aterrice la propuesta de GC. Pero la implementación general es que los enlaces de capnproto usarían un solo operador de conversión para obtener los tipos de capnproto de los tipos de GC.

Las reglas de conversión para tipos de bajo nivel serían bastante sencillas: i8 se convierte en Int8, UInt8 y bool, i16 se convierte en Int16, etc. Los tipos de alto nivel se convertirían a sus equivalentes capnproto: las referencias de estructura y matriz se convierten en punteros, referencias opacas convertir a capacidades.

Una propuesta más completa necesitaría definir una estrategia para enum y uniones.

Operadores de enlace

| Operador | Inmediatos | Niños | Descripción |
| : --- | : --- | : --- | : --- |
| como | tipo capnproto
idx | | Toma el idx 'th wasm de la tupla fuente, que debe ser una referencia, y produce un valor capnproto de capnproto-type . |

Deserializando a la memoria lineal

Deserializar en memoria lineal es principalmente similar a serializar a partir de ella, con una advertencia adicional: el código wasm a menudo no sabe de antemano cuánta memoria ocupará el tipo capnproto y necesita proporcionar al host algún tipo de método de administración de memoria dinámica .

En la propuesta de enlaces de WebIDL, la solución propuesta es pasar devoluciones de llamada del asignador a la función del host. Para los enlaces de capnproto, este método sería insuficiente, porque las asignaciones dinámicas deben ocurrir tanto en el lado del llamante como en el lado del destinatario.

Otra solución sería permitir que los mapas de enlace entrantes se vinculen a dos expresiones de enlace entrantes (y, por lo tanto, a dos funciones): una que asigna la memoria para los datos de capnproto y otra que realmente toma los datos.

Deserializando a la memoria administrada

La deserialización a la memoria administrada usaría el mismo tipo de operador de conversión que en la dirección opuesta.

Generando el código de pegamento

Al vincular dos módulos wasm juntos (ya sea de forma estática o dinámica), el incrustador debe enumerar todos los tipos de capnproto comunes a ambos módulos, las vinculaciones entre los tipos de función y los tipos de capnproto, y generar código de unión entre cada par diferente de tipos de función.

El código de pegado dependería de los tipos de datos encuadernados. El código de pegamento entre enlaces de memoria lineal se reduciría a llamadas de memoria. El código de pegamento entre los enlaces de memoria administrados se reduciría a pasar referencias. Por otro lado, el código de unión entre la memoria lineal y administrada implicaría operaciones de conversión anidadas más complicadas.

Por ejemplo, un módulo de Java podría exportar una función, tomando los argumentos como tipos de GC y vincular esa función a una firma escrita; el intérprete debe permitir que un módulo de Python y un C ++ importen ese tipo de firma; el enlace de C ++ pasaría datos de la memoria lineal, mientras que el enlace de Python pasaría datos de la memoria de GC. Las conversiones necesarias serían transparentes para los compiladores de Java, Python y C ++.

Soluciones alternativas

En esta sección, examinaré formas alternativas de intercambiar datos y cómo se clasifican en las métricas definidas en la sección Requisitos .

Intercambiar mensajes JSON

Es la solución de fuerza bruta. No voy a dedicar mucho tiempo a eso, porque sus defectos son bastante obvios. No cumple con los requisitos 2, 4 y 6.

Envíe bytes sin procesar codificados en un formato de serialización

Es una solución parcial. Defina una forma para que los módulos wasm pasen porciones de memoria lineal y tablas a otros módulos, y los escritores de módulos pueden usar un formato de serialización (capnproto, protobuff o algún otro) para codificar un gráfico estructurado en una secuencia de bytes, pasar los bytes, y usa el mismo formato para decodificarlo.

Pasa 1 y 3, y puede pasar 2 y 4 con algunos ajustes (por ejemplo, pasar las referencias como índices a una tabla). Puede pasar 6 si el usuario se asegura de exportar el tipo de serialización a una definición de tipo en el idioma de la persona que llama.

Sin embargo, falla en los requisitos 5 y 7. No es práctico cuando se vinculan dos implementaciones de GC; por ejemplo, un módulo de Python que llame a una biblioteca de Java a través de Protobuf necesitaría serializar un diccionario como memoria lineal, pasar esa porción de memoria y luego deserializarla como un objeto de Java, en lugar de hacer algunas búsquedas de tablas hash que se pueden optimizar en una implementación JIT.

Y anima a cada escritor de biblioteca a utilizar su propio formato de serialización (JSON, Protobuf, FlatBuffer, Cap'n Proto, SBE), que no es ideal para la interoperabilidad; aunque eso podría aliviarse definiendo un formato de serialización canónico en las convenciones de herramientas .

Sin embargo, agregar la posibilidad de pasar porciones arbitrarias de memoria lineal sería un buen primer paso.

Enviar objetos GC

Sería posible confiar en que los módulos se envíen entre sí objetos GC .

La solución tiene algunas ventajas: la propuesta de GC ya está en marcha; pasa 1, 3, 4 y 7. Los datos recopilados por GC son costosos de asignar, pero baratos de transmitir.

Sin embargo, esa solución no es ideal para lenguajes similares a C. Por ejemplo, un módulo D que pasa datos a un módulo Rust necesitaría serializar sus datos en un gráfico GC, pasar el gráfico a la función Rust, que lo deserializaría en su memoria lineal. Este proceso asigna nodos de GC que se descartan inmediatamente, por una gran cantidad de gastos generales innecesarios.

Aparte de eso, la propuesta actual de la CG no tiene un apoyo incorporado para enumeraciones y uniones; y el manejo de errores sería en tiempo de enlace o tiempo de ejecución en lugar de tiempo de compilación, a menos que el compilador pueda leer y comprender los tipos de GC de wasm.

Usa otras codificaciones

Cualquier biblioteca de serialización que defina un sistema de tipos podría funcionar para wasm.

Capnproto parece más apropiado, debido a su énfasis en la copia cero y sus capacidades de objeto integradas que se asignan perfectamente a los tipos de referencia.

Trabajo restante

Los siguientes conceptos deberían desarrollarse para convertir esta propuesta básica en un documento que se pueda enviar al Grupo Comunitario.

  • Operadores de enlace
  • Equivalencias de tipo GC
  • Capacidades de objetos
  • Matrices bool
  • Matrices
  • Constantes
  • Genéricos
  • Evolución de tipo
  • Agregue un tercer tipo de enlace "captadores y definidores".
  • Posibles estrategias de almacenamiento en caché
  • Soporte para múltiples tablas y memorias lineales

Mientras tanto, cualquier comentario sobre lo que ya he escrito será bienvenido. El alcance aquí es bastante amplio, por lo que agradecería ayuda para reducir las preguntas que esta propuesta debe responder.

Comentario más útil

podemos agregar algunos enlaces por tipo de IR para cubrir la gran mayoría de idiomas.

Este es el supuesto fundamental fundamental que creo que simplemente no es cierto. Mi experiencia es que hay (¡al menos!) Tantas opciones de representación como implementaciones de lenguaje. Y pueden complicarse arbitrariamente.

Tome V8, que solo tiene algunas docenas (!) Representaciones para cadenas, incluidas diferentes codificaciones, cuerdas heterogéneas, etc.

El caso Haskell es mucho más complicado de lo que describe, porque las listas en Haskell son perezosas, lo que significa que para cada carácter de una cadena es posible que deba invocar un procesador.

Otros lenguajes usan representaciones divertidas para la longitud de una cadena, o no la almacenan explícitamente, sino que requieren que se calcule.

Estos dos ejemplos ya muestran que un diseño de datos declarativos no es suficiente, a menudo necesitaría poder invocar el código de tiempo de ejecución, que a su vez podría tener sus propias convenciones de llamada.

Y eso es solo cadenas, que son un tipo de datos bastante simple conceptualmente. Ni siquiera quiero pensar en el número infinito de formas en que los lenguajes representan tipos de productos (tuplas / estructuras / objetos).

¡Y luego está el lado receptor, donde tendrías que poder crear todas estas estructuras de datos!

Así que creo que es totalmente irreal que alguna vez nos acerquemos ni remotamente a admitir la "gran mayoría de los idiomas". En cambio, comenzaríamos a privilegiar a unos pocos, mientras ya crecíamos un gran zoológico de cosas arbitrarias. Eso parece fatal en múltiples niveles.

Todos 61 comentarios

¡Esto es realmente interesante! Solo lo he leído rápidamente y solo tengo algunas ideas iniciales, pero mi primera y más importante pregunta sería preguntar por qué el mecanismo FFI existente que la mayoría de los lenguajes ya proporcionan / usan no es suficiente para WebAssembly. Prácticamente todos los idiomas con los que estoy familiarizado tienen alguna forma de C FFI y, por lo tanto, ya son capaces de interoperar hoy. Muchos de esos lenguajes también pueden realizar comprobaciones de tipos estáticas basadas en esos enlaces. Además, ya hay una gran cantidad de herramientas en torno a estas interfaces (por ejemplo, la caja bindgen para Rust, erl_nif para Erlang / BEAM, etc.). C FFI ya aborda los requisitos más importantes y tiene la ventaja clave de que ya se ha probado y utilizado ampliamente en la práctica.

5 - Puente entre diseños de memoria

Un sistema de tipos ideal debería expresar tipos semánticos y dejar que los lenguajes decidan cómo interpretarlos en la memoria. Si bien el paso de datos entre lenguajes con diseños de memoria incompatibles siempre supondrá una sobrecarga, el paso de datos entre lenguajes similares debería ser idealmente económico (por ejemplo, los embebidores deberían evitar los pasos de serialización-deserialización si una memcpy puede hacer el mismo trabajo).

El trabajo de conversión al pasar datos entre dos módulos debe ser transparente para el desarrollador, siempre que los tipos semánticos sean compatibles.

La traducción transparente de un diseño a otro cuando se pasan datos a través de la barrera FFI realmente me parece un trabajo para los backends del compilador o los tiempos de ejecución del lenguaje, y probablemente no sea deseable en absoluto en lenguajes sensibles al rendimiento como C / C ++ / Rust / etc. En particular, para las cosas que planea pasar de un lado a otro entre FFI, me parece que siempre es preferible utilizar un ABI común, en lugar de hacer cualquier tipo de traducción, ya que la traducción probablemente incurriría en un costo demasiado alto. Es poco probable que valga la pena el beneficio de elegir un diseño que no sea el ABI común de la plataforma, pero admitiré que puedo estar malinterpretando lo que quiere decir con diseños alternativos.

Además, poner la carga de las herramientas FFI sólidas en los compiladores / tiempos de ejecución tiene un beneficio adicional, ya que cualquier mejora realizada es aplicable en otras plataformas, y viceversa, ya que las mejoras en FFI para plataformas que no son de Wasm benefician a Wasm. Creo que el argumento tiene que ser realmente convincente para comenzar esencialmente desde el punto de partida y construir un nuevo mecanismo de FFI.

Disculpas si he entendido mal el propósito de la propuesta o si me he perdido algo crítico, como mencioné anteriormente, necesito leer de nuevo con más atención, pero sentí que necesitaba plantear mis preguntas iniciales mientras tuviera algo de tiempo.

Apache Arrow también existe para esto, pero está más enfocado en aplicaciones de alto rendimiento.

Creo que estoy de acuerdo con la motivación general aquí y básicamente se alinea con las discusiones que hemos tenido sobre cómo los enlaces de IDL web podrían generalizarse en el futuro. De hecho, los borradores anteriores del explicador contenían una entrada de preguntas frecuentes que mencionaba este caso de uso entre idiomas.

Mi principal preocupación (y la razón para omitir esa entrada de preguntas frecuentes) es el alcance: el problema general de vincular N lenguajes parece probable que genere una gran cantidad de discusiones abiertas (y posiblemente no terminantes), especialmente dado que nadie lo está haciendo ya ( que por supuesto es un problema del huevo y la gallina). En contraste, los problemas abordados por Web IDL Bindings son bastante concretos y se demuestran fácilmente con Rust / C ++ en la actualidad, lo que nos permite motivar el esfuerzo (no trivial) de estandarizar / implementar y también prototipar / validar con entusiasmo la solución propuesta.

Pero mi esperanza es que Web IDL Bindings nos permita romper este problema del huevo y la gallina y comenzar a obtener algo de experiencia con el enlace entre idiomas que podría motivar una próxima ola de extensión o algo nuevo y no específico de Web IDL . (Tenga en cuenta que, como se propone actualmente, si dos módulos wasm que utilizan enlaces Web IDL compatibles se llaman entre sí, un impl de optimización puede hacer las optimizaciones que está mencionando aquí; solo que sin la expresividad completa de Cap'n Proto).

Debo decir desde el principio que todavía no he tenido tiempo de asimilar completamente la propuesta.
La razón de esto es que creo que la tarea es imposible. Hay dos razones fundamentales para esto:
una. Los diferentes lenguajes tienen una semántica diferente que no se captura necesariamente en una anotación de tipo. Por ejemplo, la evaluación de Prolog es radicalmente diferente a la evaluación de C ++: hasta el punto en que los lenguajes esencialmente no son interoperables. (Para Prolog, puede sustituir varios otros idiomas)

B. Por definición, no se garantiza que cualquier LCD de sistemas de tipos capture todo el lenguaje de tipos de un idioma determinado. Eso deja al implementador del lenguaje con una opción profundamente incómoda: apoyar su propio lenguaje o renunciar a los beneficios del sistema de tipos de su lenguaje. Caso en cuestión: Haskell tiene 'clases de tipos'. Cualquier implementación de Haskell que implique no admitir clases de tipos lo destriparía de manera efectiva y lo dejaría inutilizable.
Otro ejemplo: el soporte de genéricos de C ++ requiere la eliminación de genéricos en tiempo de compilación; por otro lado, ML, Java (y un montón de otros lenguajes) utilizan una forma de representación universal, que no es compatible con el enfoque adoptado por C ++.

Por otro lado, tener dos expresiones de un tipo exportado / importado parece plantear sus propios problemas: ¿se supone que el sistema de lenguaje debe verificar que las dos expresiones son consistentes en algún sentido? ¿De quién es la responsabilidad de hacer este trabajo?

@lukewagner ¡ Gracias por los enlaces! ¡Definitivamente me alegro de haber tenido la oportunidad de leer ese documento!

Me parece que hay dos cosas un poco mezcladas en esta discusión en particular: algo de lo que está a continuación está escrito para que pueda verificar mi comprensión, así que siéntase libre de señalar cualquier cosa que haya entendido mal o me haya perdido:

  1. Enlaces de host eficientes

    • Básicamente, el problema que WebIDL está destinado a resolver, al menos para entornos de navegador: una descripción de interfaz que se asigna desde módulo-> host y host-> módulo, esencialmente delegando el trabajo de traducir de uno a otro al motor del host. No se garantiza necesariamente que esta traducción sea ideal, o incluso optimizada en absoluto, pero los motores de optimización pueden utilizarla para hacerlo. Sin embargo, incluso optimizada, la traducción todavía se realiza hasta cierto punto, pero esto es aceptable porque la alternativa sigue siendo la traducción, simplemente más lenta.

  2. Enlaces de módulo a módulo heterogéneos eficientes.

    • En otras palabras, dados dos módulos, uno escrito en source y el otro en dest , compartiendo tipos entre ellos, llamando desde fuente-> dest y / o dest-> fuente

    • Si no hay una FFI común disponible, y se le da algo como WebIDL, es decir, a cuestas en 1, la ruta no optimizada sería traducir a través de algún tipo de denominador común proporcionado por el entorno anfitrión al llamar a través de las barreras del idioma, por ejemplo, source type -> common type -> dest type .



      • Un motor de optimización podría, teóricamente, hacer esta traducción directamente de source a dest sin el intermediario, pero aún impone una sobrecarga de traducción.



    • Si una FFI común está disponible, es decir, source y dest comparten un ABI (p. Ej. C ABI), entonces source y dest pueden llamarse directamente con sin gastos generales, a través de la FFI. Este es probablemente el escenario más probable en la práctica.

Entonces, mi opinión es que definitivamente hay beneficios al aprovechar WebIDL, o algo así (es decir, un superconjunto que admite un conjunto más amplio de API / entornos de host), pero en realidad es solo una solución al problema descrito en 1, y el subconjunto de 2, que trata sobre las vinculaciones entre idiomas donde no hay FFI disponible. El subconjunto de 2 donde FFI _está_ disponible, es claramente preferible a las alternativas, ya que no incurre en gastos generales per se.

¿Existen buenas razones para usar una IDL incluso cuando FFI es una opción? Para ser claros, definitivamente estoy de acuerdo con usar un IDL para los otros casos de uso mencionados, pero lo pregunto específicamente en el contexto de la interoperabilidad del lenguaje, no en los enlaces de host.

Tengo un par de preguntas adicionales, si tanto C FFI (como ejemplo, ya que es más común) como IDL se usan / están presentes al mismo tiempo:

  • Si los lenguajes source y dest proporcionan diferentes definiciones de tipo para un tipo compartido con la misma representación en memoria subyacente de acuerdo con su ABI común (por ejemplo, una representación común para una matriz de longitud variable ) - ¿Intentará el motor de host realizar una traducción entre esos tipos solo porque las directivas IDL están presentes, a pesar de que podrían llamarse entre sí de forma segura utilizando su FFI estándar?

    • Si no es así, y es opt-in, ese parece ser el escenario ideal, ya que puede agregar IDL para admitir la interoperabilidad con idiomas sin FFI, mientras admite idiomas con FFI al mismo tiempo. Sin embargo, no estoy seguro de cómo un motor de host haría que eso funcione. No lo he pensado completamente, así que probablemente me esté perdiendo algo.

    • Si es así, ¿cómo unifica el motor de host los tipos ?:



      • Si el motor solo se preocupa por el diseño, ¿cómo puede el análisis estático detectar cuando una persona que llama proporciona tipos de argumentos incorrectos a una persona que llama? Si ese tipo de análisis no es un objetivo, entonces parecería que IDL solo es ideal para enlaces de host, y menos para idiomas cruzados.


      • Si al motor le importa más que el diseño, en otras palabras, el sistema de tipos requiere compatibilidad nominal y estructural:





        • ¿Quién define el tipo autoritativo para alguna función? ¿Cómo puedo hacer referencia al tipo autorizado de algún idioma? Por ejemplo, digamos que estoy llamando a una biblioteca compartida escrita en otro idioma que define una función add/2 , y add/2 espera dos argumentos de algún tipo size_t . Mi idioma no necesariamente conoce size_t nominalmente, tiene su propia representación compatible con ABI de enteros sin signo de ancho de máquina, usize , por lo que los enlaces FFI para esa función en mi idioma usan my tipos de idiomas. Dado eso, ¿cómo puede mi compilador saber cómo generar IDL que mapea usize a size_t ?






  • ¿Hay ejemplos de interfaces IDL utilizadas para llamar entre módulos en un programa, donde FFI está disponible pero no se usa explícitamente a favor de la interfaz descrita por IDL? En concreto algo que no es WebAssembly, mayoritariamente interesado en estudiar los beneficios en esos casos.

Admito que todavía estoy tratando de profundizar en todos los detalles de WebIDL y sus predecesores, cómo todo esto encaja con los diferentes hosts (navegador vs no navegador) y así sucesivamente, definitivamente avíseme si lo he pasado por alto alguna cosa.

@bitwalker

¡Esto es realmente interesante!

¡Me alegra que te haya gustado!

pero mi primera y más importante pregunta sería preguntar por qué el mecanismo FFI existente que la mayoría de los lenguajes ya proporcionan / usan no es suficiente para WebAssembly.

El sistema de tipo C tiene algunos problemas como IDL entre idiomas:

  • Funciona bajo la suposición de un espacio de direcciones compartido, que no es seguro y deliberadamente no se mantiene en WebAssembly. (Mi propia experiencia con una FFI JS-to-C sugiere que las implementaciones tienden a intercambiar seguridad por velocidad)

  • No tiene soporte nativo para matrices de longitud dinámica, uniones etiquetadas, valores predeterminados, genéricos, etc.

  • No existe un equivalente directo a los tipos de referencia.

C ++ resuelve algunos de estos problemas (no el más grande, espacio de direcciones compartido), pero agrega un montón de conceptos que no son realmente útiles en IPC. Por supuesto, siempre puede usar un superconjunto de C o un subconjunto de C ++ como su IDL y luego diseñar reglas vinculantes a su alrededor, pero en ese momento casi no obtiene beneficios del código existente, por lo que también puede usar un código existente. IDL.

En particular, para las cosas que planea pasar de un lado a otro a través de FFI

No sé lo que quiere decir con eso, pero para ser claro: no creo que en el caso general sea posible pasar datos mutables entre módulos. Esta propuesta intenta delinear una forma de enviar datos inmutables y obtener datos inmutables a cambio, entre módulos que no tienen información sobre cómo el otro almacena sus datos.

Es poco probable que valga la pena el beneficio de elegir un diseño que no sea el ABI común de la plataforma, pero admitiré que puedo estar malinterpretando lo que quiere decir con diseños alternativos.

El caso es que, en este momento, la ABI común es una porción de bytes almacenados en la memoria lineal. Pero en el futuro, cuando se implemente la propuesta de GC, algunos lenguajes (Java, C #, Python) almacenarán muy poco o nada en la memoria lineal. En su lugar, almacenarán todos sus datos en estructuras GC. Si dos de estos lenguajes intentan comunicarse, serializar estas estructuras en un flujo de bytes solo para deserializarlas inmediatamente sería una sobrecarga innecesaria.


@KronicDeth Gracias, lo investigaré.

Aunque, por leer el documento, parece ser un superconjunto de Flatbuffers, específicamente destinado a mejorar el rendimiento. De cualquier manera, ¿cuáles son sus cualidades que pueden ayudar de manera única a la interoperabilidad del módulo WebAssembly, en comparación con Flatbuffers o Capnproto?


@lukewagner

Pero mi esperanza es que Web IDL Bindings nos permita romper este problema del huevo y la gallina y comenzar a adquirir algo de experiencia con el enlace entre idiomas que podría motivar una próxima ola de extensión o algo nuevo y no específico de Web IDL.

Acordado. Mi suposición al escribir esta propuesta fue que cualquier implementación de enlaces de capnproto se basaría en los comentarios de la implementación de la propuesta de WebIDL.

Mi principal preocupación (y la razón para omitir esa entrada de preguntas frecuentes) es el alcance: el problema general de vincular N lenguajes parece probable que genere una gran cantidad de discusiones abiertas (y posiblemente no terminantes), especialmente dado que nadie lo está haciendo ya ( que por supuesto es un problema del huevo y la gallina).

Sin embargo, creo que discutir la implementación de capnproto tiene valor, incluso tan pronto.

En particular, traté de describir qué requisitos debería / podría intentar cumplir la implementación. Creo que también sería útil enumerar los casos de uso comunes que un sistema de tipos entre idiomas podría intentar abordar.

Con respecto al problema N-to-N, me estoy enfocando en estas soluciones:

  • Solo preocúpese por la transferencia de datos al estilo RPC. No intente pasar datos mutables compartidos, clases, tiempos de vida de punteros o cualquier otro tipo de información más complicada que "un vector tiene tres campos: 'x', 'y' y 'z', que son todos flotantes".

  • Intente agrupar lenguajes y casos de uso en "grupos" de estrategias de manejo de datos. Establecer estrategias en el centro de estos grupos; Los compiladores de lenguajes se unen a una estrategia determinada y el intérprete hace el resto del trabajo de NxN.


@fgmccabe

La razón de esto es que creo que la tarea es imposible. Hay dos razones fundamentales para esto:
una. Los diferentes lenguajes tienen una semántica diferente que no se captura necesariamente en una anotación de tipo. Por ejemplo, la evaluación de Prolog es radicalmente diferente a la evaluación de C ++: hasta el punto en que los lenguajes esencialmente no son interoperables. (Para Prolog, puede sustituir varios otros idiomas)

Cualquier implementación de Haskell que implique no admitir clases de tipos lo destriparía de manera efectiva y lo dejaría inutilizable.

Sí, la idea no es definir una abstracción perfecta "fácilmente compatible con todos los idiomas".

Dicho esto, creo que la mayoría de los idiomas tienen algunas similitudes en la forma en que estructuran sus datos (p. Ej., Tienen una forma de decir "cada persona tiene un nombre, un correo electrónico y una edad" o "cada grupo tiene una lista de personas de tamaño arbitrario ").

Creo que es posible aprovechar estas similitudes para reducir significativamente la fricción entre módulos. (ver también mi respuesta a lukewagner)

B. Por definición, no se garantiza que cualquier LCD de sistemas de tipos capture todo el lenguaje de tipos de un idioma determinado. Eso deja al implementador del lenguaje con una opción profundamente incómoda: apoyar su propio lenguaje o renunciar a los beneficios del sistema de tipos de su lenguaje.

Sí. Creo que la regla de oro aquí es "Si es un límite de biblioteca compartida, conviértalo en un tipo capnproto, de lo contrario, use sus tipos nativos".

Por otro lado, tener dos expresiones de un tipo exportado / importado parece plantear sus propios problemas: ¿se supone que el sistema de lenguaje debe verificar que las dos expresiones son consistentes en algún sentido? ¿De quién es la responsabilidad de hacer este trabajo?

Sí, inicialmente quería incluir una sección sobre verificación invariante y otra sobre compatibilidad de tipos, pero perdí el valor.

La respuesta a "de quién es la responsabilidad" suele ser "la persona que llama" (porque deben asumir que cualquier dato que reciben es sospechoso), pero las comprobaciones pueden eludirse si el intérprete puede demostrar que la persona que llama respeta los invariantes de tipo.

El sistema de tipo C tiene algunos problemas como IDL entre idiomas

Para que quede claro, no lo estoy sugiriendo como una IDL. Más bien estoy sugiriendo que la interfaz binaria (la C ABI) ya existe, está bien definida y ya tiene un amplio soporte de idiomas. La implicación entonces es que WebAssembly no necesita proporcionar otra solución a menos que el problema que se resuelva vaya más allá de la interoperabilidad entre lenguajes.

Funciona bajo la suposición de un espacio de direcciones compartido, que no es seguro y deliberadamente no se mantiene en WebAssembly.

Así que creo que veo parte del malentendido aquí. Hay dos clases de FFI de las que estamos hablando aquí, una que implica compartir memoria lineal (más tradicional FFI de memoria compartida) y otra que no (más tradicional IPC / RPC). He estado hablando de lo primero y creo que estás más concentrado en lo segundo.

Compartir memoria entre módulos cuando usted tiene el control de ellos (como el caso en el que está vinculando varios módulos independientes como parte de una aplicación general) es deseable para la eficiencia, pero sacrifica la seguridad. Por otro lado, es posible compartir una memoria lineal designada específicamente para FFI, aunque no sé qué tan práctico es eso con las herramientas predeterminadas que existen en la actualidad.

La interoperabilidad entre módulos que _no_ usa memoria compartida FFI, es decir, IPC / RPC, definitivamente parece una buena combinación para WebIDL, capnproto o una de las otras sugerencias en ese sentido, ya que ese es su pan y mantequilla.

La parte de la que no estoy seguro es cómo combinar las dos categorías de tal manera que no sacrifique los beneficios de ninguna de las dos, ya que la elección de ir de una manera u otra depende en gran medida del caso de uso. Al menos como se dijo, parece que solo podríamos tener uno u otro, si es posible apoyar a ambos, creo que sería ideal.

No tiene soporte nativo para matrices de longitud dinámica, uniones etiquetadas, valores predeterminados, genéricos, etc.

Creo que esto probablemente no sea relevante ahora que me doy cuenta de que estábamos hablando de dos cosas diferentes, pero solo para la posteridad: el ABI ciertamente tiene una _representación_ para matrices de longitud variable y uniones etiquetadas, pero tienes razón en que C tiene una sistema de tipo débil, pero ese no es realmente el punto, los lenguajes no están apuntando a C FFI para el sistema de tipo C. La razón por la que el C ABI es útil es que proporciona un denominador común que los lenguajes pueden usar para comunicarse con otros que pueden no tener un concepto del sistema de tipos con el que están interactuando. La falta de funciones de sistema de tipo de nivel superior no es ideal y limita el tipo de cosas que puede expresar a través de FFI, pero las limitaciones también son parte de por qué es tan exitoso en lo que hace, prácticamente cualquier idioma puede encontrar una manera para representar las cosas expuestas a él a través de esa interfaz, y viceversa.

C ++ resuelve algunos de estos problemas (no el más grande, espacio de direcciones compartido), pero agrega un montón de conceptos que no son realmente útiles en IPC. Por supuesto, siempre puede usar un superconjunto de C o un subconjunto de C ++ como su IDL y luego diseñar reglas vinculantes a su alrededor, pero en ese momento casi no obtiene beneficios del código existente, por lo que también puede usar un código existente. IDL.

De acuerdo, para IPC / RPC, C es un lenguaje terrible para definir interfaces.

El caso es que, en este momento, la ABI común es una porción de bytes almacenados en la memoria lineal.

Ese es ciertamente el primitivo con el que estamos trabajando, pero el C ABI define mucho además de eso.

Pero en el futuro, cuando se implemente la propuesta de GC, algunos lenguajes (Java, C #, Python) almacenarán muy poco o nada en la memoria lineal. En su lugar, almacenarán todos sus datos en estructuras GC. Si dos de estos lenguajes intentan comunicarse, serializar estas estructuras en un flujo de bytes solo para deserializarlas inmediatamente sería una sobrecarga innecesaria.

No estoy convencido de que esos lenguajes se apresuren a aplazar GC al host, pero eso es solo una especulación de mi parte. En cualquier caso, los lenguajes que entienden las estructuras administradas por GC del host podrían simplemente decidir sobre una representación común para esas estructuras usando el C ABI tan fácilmente como podrían representarse usando capnproto, la única diferencia es dónde vive la especificación de esa representación. Dicho esto, solo tengo una comprensión muy tenue de los detalles de la propuesta de la CG y cómo eso se relaciona con la propuesta de vinculación del anfitrión, así que si me equivoco aquí, no dude en ignorarla.

TL; DR: Creo que estamos de acuerdo con respecto a la interoperabilidad de módulos donde la memoria lineal compartida no está en juego. Pero creo que la memoria compartida _es_ importante para admitir, y C ABI es la opción más sensata para ese caso de uso debido al soporte de idiomas existente. Mi esperanza es que esta propuesta, a medida que evoluciona, apoye a ambos.

Lo que necesitamos es simplemente una forma máximamente eficiente de intercambiar búferes de bytes y una forma para que los idiomas se pongan de acuerdo sobre el formato. No es necesario arreglar esto en un sistema de serialización en particular. Si Cap'n Proto es el más adecuado para este propósito, puede surgir como un defecto común de forma orgánica, en lugar de ser un mandato de wasm.

Por supuesto, soy parcial, ya que hice FlatBuffers , que es similar a Cap'n Proto en eficiencia, pero más flexible y con más soporte. Sin embargo, tampoco recomendaría que wasm imponga este formato.

Hay muchos otros formatos que podrían ser preferibles a estos dos dados ciertos casos de uso.

Tenga en cuenta que tanto Cap'n Proto como FlatBuffers son de copia cero, acceso aleatorio y son eficientes para anidar formatos (lo que significa que un formato envuelto en otro no es menos eficiente que no estar envuelto), que son las propiedades reales a considerar para inter-idiomas. comunicación. Podrías imaginar un IDL que te permita especificar diseños de bytes muy precisos para un búfer, incluyendo "los siguientes bytes son el esquema X de Cap'n Proto".

Si bien me autopromociono sutilmente, podría señalar a la gente FlexBuffers, que es algo así como FlatBuffers sin esquema. Tiene las mismas propiedades deseables de copia cero, acceso aleatorio y anidamiento económico, pero puede permitir que los lenguajes se comuniquen sin acordar un esquema, sin hacer codegen, similar a cómo se usa JSON.

@aardappel

Lo que necesitamos es simplemente una forma máximamente eficiente de intercambiar búferes de bytes y una forma para que los idiomas se pongan de acuerdo sobre el formato. No es necesario arreglar esto en un sistema de serialización en particular. Si Cap'n Proto es el más adecuado para este propósito, puede surgir como un defecto común de forma orgánica, en lugar de ser un mandato de wasm.

Entiendo el punto implícito, que wasm no debe usarse como una forma de imponer un estándar a sus competidores, y personalmente soy indiferente a qué IDL se elige.

Dicho esto, cuando todo está dicho y hecho, la goma debe llegar a la carretera en algún momento. Si wasm quiere facilitar la comunicación entre idiomas (lo que, por supuesto, no es una suposición que todos compartan), entonces necesita un formato estándar que pueda expresar más que "estos bytes constituyen números". Ese formato puede ser capnproto, estructuras C, búferes planos o incluso algo específico de wasm, pero no puede ser un subconjunto de todos estos al mismo tiempo, por las razones que @fgmccabe describió.

Si bien me autopromociono sutilmente, podría señalar a la gente FlexBuffers, que es algo así como FlatBuffers sin esquema. Tiene las mismas propiedades deseables de copia cero, acceso aleatorio y anidamiento económico, pero puede permitir que los lenguajes se comuniquen sin acordar un esquema, sin hacer codegen, similar a cómo se usa JSON.

Veo el atractivo, no creo que esto sea lo que quieres la mayor parte del tiempo, cuando escribes una biblioteca. El problema con JSON (aparte del terrible tiempo de análisis) es que cuando escribe importar un objeto JSON en algún lugar de su código, termina escribiendo mucho código de desinfección antes de poder usar sus datos, por ejemplo:

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

con posibles vulnerabilidades de seguridad si no lo hace.

Vea también 6 - Manejo de errores en tiempo de compilación arriba.


@bitwalker

Correcto, realmente no consideré la posibilidad de una memoria lineal compartida. Necesitaría a alguien más familiarizado con el diseño de ensamblaje web que yo (¿

Por ejemplo, las FFI a menudo se basarán en el hecho de que su lenguaje anfitrión usa la biblioteca C y les dará a las bibliotecas nativas acceso a la función malloc directamente. ¿Qué tan bien se puede traducir esa estrategia a wasm, en el contexto de dos módulos mutuamente sospechosos?

Supongo que debería decir algo en este hilo, como creador de Cap'n Proto, pero por extraño que parezca, no me he dado cuenta de que tengo mucha opinión. Permítanme expresar algunos pensamientos adyacentes que pueden ser interesantes o no.

También soy el líder tecnológico de Cloudflare Workers, un entorno "sin servidor" que ejecuta JavaScript y WASM.

Hemos estado considerando apoyar a Cap'n Proto RPC como un protocolo para que los trabajadores se comuniquen entre sí. Actualmente, están limitados a HTTP, por lo que la barra es bastante baja. :)

En Workers, cuando un Worker llama a otro, es muy común que ambos se ejecuten en la misma máquina, incluso en el mismo proceso. Por esa razón, una serialización de copia cero como Cap'n Proto obviamente tiene mucho sentido, especialmente para los trabajadores de WASM, ya que operan en una memoria lineal que, en teoría, podría compartirse físicamente entre ellos.

Una segunda razón, menos conocida, por la que creemos que se ajusta bien es el sistema RPC. Cap'n Proto presenta un protocolo RPC de capacidad de objeto completo con canalización de promesas, modelado a partir de CapTP. Esto facilita la expresión de interacciones enriquecidas y orientadas a objetos de forma segura y eficaz. Cap'n Proto RPC no es solo un protocolo de punto a punto, sino que modela las interacciones entre cualquier número de partes en red, lo que creemos que será un gran problema.

Mientras tanto, en la tierra de WASM, WASI está introduciendo una API basada en capacidades. Parece que podría haber una "sinergia" interesante aquí.

Con todo lo dicho, varios objetivos de diseño de Cap'n Proto pueden no tener sentido para el caso de uso específico de FFI:

  • Los mensajes de Cap'n Proto están diseñados para ser independientes de la posición y contiguos, de modo que puedan transmitirse y compartirse entre espacios de direcciones. Los punteros son relativos y todos los objetos de un mensaje deben asignarse en una memoria contigua, o al menos en una pequeña cantidad de segmentos. Esto complica significativamente el modelo de uso en comparación con los objetos nativos. Cuando se usa FFI dentro del mismo espacio de memoria lineal, esta sobrecarga se desperdicia, ya que podría estar pasando punteros nativos a objetos de montón sueltos sin problemas.
  • Los mensajes de Cap'n Proto están diseñados para ser compatibles con versiones anteriores y posteriores del esquema, incluida la capacidad de copiar objetos y subárboles sin pérdidas sin conocer el esquema. Esto requiere que cierta información de tipo ligero se almacene directamente en el contenido, que Cap'n Proto codifica como metadatos en cada puntero. Si dos módulos que se comunican a través de una FFI se compilan al mismo tiempo, entonces no hay necesidad de estos metadatos.
  • Las garantías de canalización, acortamiento de rutas y pedidos de Cap'n Proto RPC tienen sentido cuando hay una latencia no despreciable entre una persona que llama y una persona que llama. FFI en una sola CPU no tiene tal latencia, en cuyo caso la maquinaria de canalización prometida probablemente solo desperdicia ciclos.

En resumen, creo que cuando tienes módulos implementados de forma independiente en cajas de arena separadas hablando entre sí, Cap'n Proto tiene mucho sentido. Pero para los módulos implementados simultáneamente en un solo espacio aislado, probablemente sea excesivo.

¡Gracias por la respuesta!

Los punteros son relativos y todos los objetos de un mensaje deben asignarse en una memoria contigua, o al menos en una pequeña cantidad de segmentos. Esto complica significativamente el modelo de uso en comparación con los objetos nativos. Cuando se usa FFI dentro del mismo espacio de memoria lineal, esta sobrecarga se desperdicia, ya que podría estar pasando punteros nativos a objetos de montón sueltos sin problemas.

No sé qué tan factible es un enfoque de memoria lineal compartida para wasm (ver arriba).

Dicho esto, de cualquier manera, no creo que la sobrecarga de los indicadores relativos sea tan mala. WebAssembly ya usa compensaciones relativas al inicio de la memoria lineal, y las implementaciones tienen trucos para optimizar las instrucciones ADD en la mayoría de los casos (creo), por lo que la sobrecarga de usar punteros relativos probablemente también podría optimizarse.

Los mensajes de Cap'n Proto están diseñados para ser compatibles con versiones anteriores y posteriores del esquema, incluida la capacidad de copiar objetos y subárboles sin pérdidas sin conocer el esquema. [...] Si dos módulos que se comunican a través de una FFI se compilan al mismo tiempo, entonces no hay necesidad de estos metadatos.

No creo que eso sea cierto. Tener una forma de que los módulos definan tipos compatibles con versiones anteriores en sus límites permite a wasm usar un modelo de árbol de dependencia, evitando principalmente el problema del diamante de dependencia de Haskell.

Una fuente mayor de gastos generales inútiles sería la forma en que capnproto xor s sus variables contra sus valores predeterminados, lo cual es útil cuando se comprimen los bytes cero, pero contraproducente en los flujos de trabajo de copia cero.

No sé qué tan factible es un enfoque de memoria lineal compartida para wasm (ver arriba).

Ah, TBH, no creo que tenga suficiente contexto para seguir esa parte de la discusión. Si no tiene un espacio de direcciones compartido, entonces, sí, Cap'n Proto comienza a tener mucho sentido.

Me complace brindar consejos sobre cómo diseñar formatos como este. FWIW, hay algunas pequeñas cosas que cambiaría en Cap'n Proto si no me importara la compatibilidad con aplicaciones que ya existen en la actualidad ... sin embargo, se trata principalmente de detalles de codificación de puntero de bajo nivel.

Una fuente mayor de gastos generales inútiles sería la forma en que capnproto modifica sus variables contra sus valores predeterminados, lo cual es útil cuando se comprimen los bytes cero, pero es contraproducente en los flujos de trabajo de copia cero.

Un poco fuera de tema, pero lo de XOR es una optimización, no una sobrecarga, incluso en el caso de copia cero. Garantiza que todas las estructuras se inicialicen en cero, lo que significa que no tiene que realizar ninguna inicialización en la asignación de objetos si el búfer ya está en cero (lo que a menudo sería de todos modos). Un XOR contra una constante de tiempo de compilación probablemente cuesta 1 ciclo, mientras que cualquier tipo de acceso a la memoria costará mucho más.

@lukewagner ¿ Alguna idea sobre la parte de "compartir memoria lineal"?

Creo que hay casos de uso para compartir y no compartir memoria lineal y, en última instancia, las herramientas deben admitir ambos:

Compartir tiene sentido donde una aplicación nativa hoy en día usaría enlaces estáticos o dinámicos: cuando todo el código que se combina es totalmente confiable y su combinación usa la misma cadena de herramientas o una ABI rigurosamente definida. Sin embargo, es un modelo de composición de software más frágil.

No compartir memoria tiene sentido para una colección de módulos más débilmente acoplados, donde el diseño clásico de estilo Unix colocaría el código en procesos separados conectados por tuberías. Personalmente, creo que esta es la dirección más emocionante / futurista para un ecosistema de software más compositivo y, por lo tanto, he abogado por que esto sea el predeterminado para cualquier cadena de herramientas destinada a participar en el ecosistema ESM / npm a través de la integración ESM (y de hecho que es el caso actual del wasm-pack / wasm-bindgen de Rust). El uso de un mecanismo en la vecindad general de Web IDL Bindings o la extensión que ha propuesto tiene mucho sentido para mí como una forma de RPC eficiente, ergonómico, mecanografiado (sincronizado o asincrónico).

Habiendo finalmente leído esto en su totalidad, se parece mucho a mi pensamiento en esta área (¿que este cuadro de comentarios es demasiado corto para contenerlo?).

En particular, he estado pensando en que el problema de la comunicación entre módulos se describe mejor con un esquema. Es decir, no necesitamos el formato de serialización Cap'nProto, solo podemos usar el esquema. No tengo ninguna opinión sobre el lenguaje de esquema de Cap'nProto específicamente en este momento.

Desde la perspectiva de WASI / ESM + npm, una solución de este formulario tiene más sentido para mí. Es una abstracción de las ABI, sin depender de una ABI compartida. Básicamente, le permite a uno describir una interfaz con una API de lenguaje de esquema y llamar a través de estos límites de lenguaje con ABI de apariencia nativa en ambos extremos, permitiendo que el host maneje la traducción.

En particular, esto no subsume el caso de uso de tener más coordinación con otro módulo: si está seguro de que puede compartir una ABI, de hecho puede usar una ABI, cualquier ABI, ya sea C o Haskell. Si controla y compila todo el wasm en cuestión, será un problema mucho más fácil de resolver. Solo cuando entra en el caso de npm en el que está cargando código desconocido arbitrario y no conoce su idioma de origen, algo como tener interoperabilidad a nivel de esquema entre módulos se vuelve increíblemente atractivo. Porque podemos usar la pantalla LCD de wasm en sí, que predigo que seguirá un arco similar a las bibliotecas nativas, y usar la ABI C, o podemos usar la pantalla LCD de idiomas, codificada en el lenguaje de esquema. Y el esquema puede ser más flexible al hacer que el requisito 2) sea un requisito suave, por ejemplo, debería ser posible convertir de C a Rust a Nim de manera eficiente, pero que C a Haskell tenga más gastos generales no es un factor decisivo.

En particular, he estado pensando en que el problema de la comunicación entre módulos se describe mejor con un esquema. Es decir, no necesitamos [un] formato de serialización, solo podemos usar el esquema.

Tiendo a estar de acuerdo con lo primero, pero no estoy seguro de que lo segundo siga. ¿Quién implementa el esquema? Incluso si el host realiza el transporte, en algún momento debe definir qué valores / bytes de Wasm se consumen / producen realmente en ambos extremos, y cada módulo tiene que traer sus propios datos en una forma que el host comprenda. Incluso puede haber varios formularios disponibles, pero aún así, eso no es diferente de un formato de serialización, solo un poco más de alto nivel.

debería ser posible convertir de C a Rust a Nim de manera eficiente, C a Haskell tener más gastos generales no es un factor decisivo.

Quizás no, pero debes ser consciente de las implicaciones. Privilegiar lenguajes similares a C significa que Haskell no usaría esta abstracción para los módulos de Haskell, debido a la sobrecarga inducida. Eso, a su vez, significa que no participaría en el mismo ecosistema "npm" para sus propias bibliotecas.

Y "Haskell" aquí es solo un sustituto de casi todos los idiomas de alto nivel. La gran mayoría de lenguajes no son similares a C.

No pretendo tener una solución mejor, pero creo que tenemos que ser realistas sobre cuán eficiente y atractivo puede ser cualquier ABI o abstracción de esquema para la población general de idiomas, más allá del estilo habitual de FFI de interoperabilidad unidireccional. En particular, no estoy convencido de que un ecosistema de paquetes panlingüísticos sea un resultado demasiado realista.

Privilegiar lenguajes similares a C significa que Haskell no usaría esta abstracción para los módulos de Haskell, debido a la sobrecarga inducida. Eso, a su vez, significa que no participaría en el mismo ecosistema "npm" para sus propias bibliotecas.

Y "Haskell" aquí es solo un sustituto de casi todos los idiomas de alto nivel. La gran mayoría de lenguajes no son similares a C.

¿Podría dar algunos casos de uso específicos? Idealmente, ¿bibliotecas existentes en Haskell o en algún otro lenguaje que sería difícil de traducir a un esquema de serialización?

Sospecho que se reducirá principalmente a las bibliotecas de servicios públicos frente a las bibliotecas comerciales. Por ejemplo, los contenedores, los algoritmos de clasificación y otras utilidades que se basan en los genéricos del lenguaje no se traducirán bien en wasm, pero los analizadores, los widgets de interfaz gráfica de usuario y las herramientas del sistema de archivos sí lo harán.

@PoignardAzur , no es difícil traducirlos, pero requiere que copien (serialicen / deserialicen) todos los argumentos / resultados en ambos extremos de cada llamada entre módulos. Claramente, no desea pagar ese costo por cada llamada a la biblioteca interna del idioma.

En Haskell específicamente también tiene el problema adicional de que copiar es incompatible con la semántica de la pereza. En otros idiomas, puede ser incompatible con datos con estado.

¿Quién implementa el esquema? Incluso si el host realiza el transporte, en algún momento debe definir qué valores / bytes de Wasm se consumen / producen realmente en ambos extremos, y cada módulo tiene que traer sus propios datos en una forma que el host comprenda. Incluso puede haber varios formularios disponibles, pero aún así, eso no es diferente de un formato de serialización, solo un poco más de alto nivel.

El anfitrión implementa el esquema. El esquema no describe bytes en absoluto, y eso es un detalle de implementación. Esto está tomando prestado del diseño de la propuesta de enlaces WebIDL, en la que lo interesante está en las conversiones de estructuras C a tipos de WebIDL. Este tipo de diseño utiliza tipos de interfaz abstracta de Wasm (sugiero el acrónimo: WAIT) en lugar de tipos de WebIDL. En la propuesta de WebIDL, no necesitamos ni queremos exigir una representación binaria de datos cuando se han "traducido a WebIDL", porque queremos poder pasar directamente de wasm a las API del navegador sin una parada en el medio.

Privilegiar lenguajes similares a C significa que Haskell no usaría esta abstracción para los módulos de Haskell, debido a la sobrecarga inducida.

Oh, de acuerdo al 100%. Debería haber terminado el ejemplo para dejarlo más claro: mientras tanto, Haskell a Elm a C # puede ser igualmente eficiente (asumiendo que usan los tipos wasm gc), pero C # a Rust puede tener una sobrecarga. No creo que haya una manera de evitar los gastos generales al saltar entre paradigmas del lenguaje.

Creo que su observación es correcta de que debemos intentar evitar privilegiar cualquier idioma, porque si no somos lo suficientemente ergonómicos + eficientes para un idioma dado, no verán tanto valor en el uso de la interfaz y, por lo tanto, no participarán en el ecosistema. .

Creo que al abstraer los tipos y no especificar un formato de cable, podemos dar mucho más margen a los hosts para optimizar. Creo que un objetivo no es decir "las cadenas de estilo C son eficientes", pero es un objetivo decir "los lenguajes que [quieren] razonar sobre las cadenas de estilo C pueden hacerlo de manera eficiente". O bien, ningún formato debería ser bendecido, pero ciertas cadenas de llamadas compatibles deberían ser eficientes y todas las cadenas de llamadas deberían ser posibles.

Por cadenas de llamadas me refiero a:

  1. C -> Óxido -> Zig -> Fortran, eficiente
  2. Haskell -> C # -> Haskell, eficiente
  3. C -> Haskell -> Rust -> Esquema, ineficiente
  4. Java -> óxido, ineficiente

Y "Haskell" aquí es solo un sustituto de casi todos los idiomas de alto nivel. La gran mayoría de lenguajes no son similares a C.

Sí, esa era mi intención detrás de usar Haskell como lenguaje concreto. (Aunque Nim probablemente fue un mal ejemplo de un lenguaje similar a C porque también hace un uso intensivo de GC)

-

Otra forma en que he estado pensando en los tipos abstractos es como un IR. De la misma manera que LLVM describe una relación de muchos a uno a muchos (muchos idiomas -> un IR -> muchos objetivos), los tipos abstractos de wasm pueden mediar en un mapeo de muchos a muchos, de idiomas + hosts -> idiomas + hosts. Algo en este espacio de diseño toma el problema de mapeo N ^ 2 y lo convierte en uno N + N.

El anfitrión implementa el esquema.

Bueno, eso no puede ser suficiente, cada módulo tiene que implementar algo para que el host pueda encontrar los datos. Si el host espera un diseño en C, entonces debe definir este diseño en C, y cada cliente tiene que ordenar / desarmar a / desde eso internamente. Eso no es tan diferente de un formato de serialización.

Incluso si hiciéramos eso, sigue siendo útil definir un formato de serialización, por ejemplo, para aplicaciones que necesitan transferir datos entre motores únicos, por ejemplo, a través de redes o persistencia basada en archivos.

Bueno, eso no puede ser suficiente, cada módulo tiene que implementar algo para que el host pueda encontrar los datos. Si el anfitrión espera un diseño C, entonces debe definir este diseño C

El anfitrión no debe esperar nada, pero necesita apoyarlo todo. Más concretamente, usando la propuesta de webidl-bindings como ejemplo ilustrativo , tenemos utf8-cstr y utf8-str , que toman i32 (ptr) y i32 (ptr), i32 (len) respectivamente. No hay necesidad de ordenar en la especificación "el host lo representa internamente como C-strings" para poder mapear concretamente entre ellos.
Entonces, cada módulo implementa algo, sí, pero la representación de los datos no necesita expresarse en la capa de esquema / datos abstractos, que es como esto nos da la propiedad de abstraer ese diseño de datos.
Además, esto es extensible en la capa de enlaces que se asigna entre tipos de wasm concretos y tipos intermedios abstractos. Para agregar compatibilidad con Haskell (que modela cadenas como listas de contras de caracteres y matrices de caracteres), podemos agregar enlaces utf8-cons-str y utf8-array-str , que esperan (y validan) tipos de wasm de (usando current sintaxis de propuesta gc) (type $haskellString (struct (field i8) (field (ref $haskellString)))) y (type $haskellText (array i8)) .

Es decir, cada módulo decide cómo se originan los datos. Los tipos abstractos + enlaces permiten conversiones entre cómo los módulos ven los mismos datos, sin bendecir una sola representación como algo canónico.

Un formato de serialización para (un subconjunto de) los tipos abstractos sería útil, pero se puede implementar como consumidor del formato de esquema, y ​​creo que es una preocupación ortogonal. Creo que FIDL tiene un formato de serialización para el subconjunto de tipos que se pueden transferir a través de la red, no permite materializar identificadores opacos, mientras que permite que los identificadores opacos se transfieran dentro de un sistema (IPC sí, RPC no).

Lo que está describiendo está bastante cerca de lo que tenía en mente, con una gran advertencia: el esquema debe tener un número pequeño y fijo de representaciones posibles. La creación de puentes entre diferentes representaciones es un problema N * N, lo que significa que la cantidad de representaciones debe mantenerse pequeña para evitar sobrecargar a los escritores de VM.

Por lo tanto, agregar compatibilidad con Haskell requeriría usar enlaces existentes, no agregar enlaces personalizados.

Algunas posibles representaciones:

  • Estructuras y punteros de estilo C.
  • Bytes capnproto reales.
  • Clases de GC.
  • Cierres que sirven como getters y setters.
  • Diccionarios de estilo Python.

La idea es que, si bien cada idioma es diferente y existen algunos valores atípicos extremos, puede incluir un número bastante grande de idiomas en un número bastante pequeño de categorías.

Por lo tanto, agregar compatibilidad con Haskell requeriría usar enlaces existentes, no agregar enlaces personalizados.

Depende del nivel de granularidad de los enlaces existentes en los que esté pensando. N <-> N idiomas que codifican cada posible enlace es 2 * N * N, pero N <-> IR es 2 * N, y además si dice N <-> [estilos de enlace comunes] <-> IR, donde el número de formatos comunes es k, estás hablando de 2 * k, donde k <N.

En particular, con el esquema que describo, obtienes Scheme gratis (reutilizaría utf8-cons-str ). Si Java también modela cadenas como matrices de caracteres, es un enlace utf8-array-str . Si Nim usa string_views debajo del capó, utf8-str . Si Zig cumple con C ABI, utf8-cstr . (No conozco las ABI de Java / Nim / Zig, por lo que no las mencioné como ejemplos concretos antes)

Entonces, sí, no queremos agregar un enlace para cada idioma posible, pero podemos agregar algunos enlaces por tipo de IR para cubrir la gran mayoría de los idiomas. Creo que el espacio para el desacuerdo aquí es, ¿cuántas vinculaciones son "unas pocas", cuál es el punto óptimo, qué tan estrictos deben ser los criterios para admitir la ABI de un idioma?
No tengo respuestas específicas a estas preguntas. Estoy tratando de dar muchos ejemplos concretos para ilustrar mejor el espacio de diseño.

También afirmaría que absolutamente queremos especificar múltiples enlaces por tipo abstracto, para evitar privilegiar cualquier estilo de datos. Si el único enlace que exponemos a Strings es utf8-cstr , entonces todos los idiomas que no tienen C-ABI tienen que lidiar con esa discrepancia. Estoy de acuerdo con el aumento de la complejidad de escritura de VM, un factor no pequeño.
El trabajo total en el ecosistema es O (esfuerzo de VM + esfuerzo de implementación del lenguaje), y ambos términos escalan de alguna manera con N = número de idiomas. Sea M = número de incorporaciones, k = número de enlaces y a = número medio de enlaces que un lenguaje determinado necesita implementar, con a <= k. Como mínimo, tenemos implementaciones de wasm separadas M + N.
El enfoque ingenuo, con cada idioma N implementando ABI FFI de forma independiente con todos los demás idiomas N, es O (M + N * N). Esto es lo que tenemos en los sistemas nativos, que es una fuerte señal de que cualquier O (N * N) conducirá a resultados no diferentes a los de los sistemas nativos.
Segundo enfoque ingenuo, donde cada VM necesita implementar todos los enlaces N * N: O (M * N * N + N), que es claramente incluso peor.
Lo que estamos tratando de proponer es que tenemos enlaces k que se mapean entre un lenguaje abstracto, que se mapea a todos los lenguajes. Esto implica k trabajo para cada VM. Para cada idioma, solo necesitamos implementar un subconjunto de los enlaces. El trabajo total es M * k + N * a, que es O (M * k + N * k). Tenga en cuenta que en el caso de que k = N, el lado de la VM sea "solo" M * N, por lo que para cualquier VM dada es "solo" lineal en el número de idiomas. Claramente, aunque queremos k << N, porque de lo contrario esto sigue siendo O (N * N), no es mejor que la primera solución.
Aún así, O (M * k + N * k) es mucho más apetecible. Si k es O (1), eso hace que todo el ecosistema sea lineal en el número de implementaciones, que es nuestro límite inferior en la cantidad de esfuerzo involucrado. Un límite más probable es que k sea O (log (N)), con el que todavía estoy bastante satisfecho.

Lo cual es una larga forma de decirlo, estoy completamente de acuerdo con aumentar la complejidad de la máquina virtual para esta función en un factor constante.

podemos agregar algunos enlaces por tipo de IR para cubrir la gran mayoría de idiomas.

Este es el supuesto fundamental fundamental que creo que simplemente no es cierto. Mi experiencia es que hay (¡al menos!) Tantas opciones de representación como implementaciones de lenguaje. Y pueden complicarse arbitrariamente.

Tome V8, que solo tiene algunas docenas (!) Representaciones para cadenas, incluidas diferentes codificaciones, cuerdas heterogéneas, etc.

El caso Haskell es mucho más complicado de lo que describe, porque las listas en Haskell son perezosas, lo que significa que para cada carácter de una cadena es posible que deba invocar un procesador.

Otros lenguajes usan representaciones divertidas para la longitud de una cadena, o no la almacenan explícitamente, sino que requieren que se calcule.

Estos dos ejemplos ya muestran que un diseño de datos declarativos no es suficiente, a menudo necesitaría poder invocar el código de tiempo de ejecución, que a su vez podría tener sus propias convenciones de llamada.

Y eso es solo cadenas, que son un tipo de datos bastante simple conceptualmente. Ni siquiera quiero pensar en el número infinito de formas en que los lenguajes representan tipos de productos (tuplas / estructuras / objetos).

¡Y luego está el lado receptor, donde tendrías que poder crear todas estas estructuras de datos!

Así que creo que es totalmente irreal que alguna vez nos acerquemos ni remotamente a admitir la "gran mayoría de los idiomas". En cambio, comenzaríamos a privilegiar a unos pocos, mientras ya crecíamos un gran zoológico de cosas arbitrarias. Eso parece fatal en múltiples niveles.

Mi experiencia es que hay (¡al menos!) Tantas opciones de representación como implementaciones de lenguaje. Y pueden complicarse arbitrariamente.

Estoy completamente de acuerdo. Creo que tratar de diseñar tipos que cubran de alguna manera las representaciones internas de datos de la mayoría del lenguaje simplemente no es manejable y hará que el ecosistema sea demasiado complicado.

Al final, solo hay un mínimo común denominador entre los idiomas cuando se trata de datos: el del "búfer". Todos los idiomas pueden leerlos y construirlos. Son eficientes y simples. Sí, favorecen los lenguajes que pueden abordar directamente sus contenidos, pero no creo que sea una desigualdad que se resuelva promoviendo las células de cons (perezosas) al mismo nivel de soporte de alguna manera.

De hecho, puede llegar muy lejos con un solo tipo de datos: el par puntero + len. Entonces solo necesita un "esquema" que diga lo que hay en esos bytes. ¿Promete cumplir con UTF-8? ¿El último byte está garantizado siempre en 0? ¿Son los primeros campos de capacidad / longitud de 4/8 bytes? ¿Son todos estos bytes flotantes little endian que se pueden enviar directamente a WebGL? ¿Son estos bytes quizás el esquema X de un formato de serialización existente? etc?

Propondría una especificación de esquema muy simple que pueda responder a todas estas preguntas (no un formato de serialización existente, sino algo más de bajo nivel, más simple y específico para wasm). Entonces se convierte en la carga de cada idioma leer y escribir de manera eficiente estos búferes en el formato especificado. Las capas intermedias pueden luego pasar por los búferes a ciegas sin procesar, ya sea por copia o, cuando sea posible, por referencia / vista.

Este es el supuesto fundamental fundamental que creo que simplemente no es cierto. Mi experiencia es que hay (¡al menos!) Tantas opciones de representación como implementaciones de lenguaje. Y pueden complicarse arbitrariamente.

Estoy de acuerdo en que este es el supuesto fundamental fundamental. No estoy de acuerdo con que no sea cierto, aunque creo que por un matiz semántico que no creo haber dejado claro.

Los enlaces no están destinados a mapear perfectamente todas las representaciones de idiomas, solo necesitan mapear todos los idiomas lo suficientemente bien .

Esa es una suposición subyacente crucial que hace que esto sea manejable, en absoluto, independientemente de la codificación. La propuesta de @aardappel de ir en la otra dirección y realmente cosificar los bytes en un búfer que es decodificable también se basa en el supuesto de que es una codificación con pérdida de la semántica de cualquier programa, algunas con más pérdidas que otras.

El caso Haskell es mucho más complicado de lo que describe, porque las listas en Haskell son perezosas, lo que significa que para cada carácter de una cadena es posible que deba invocar un procesador.

De hecho, lo había olvidado, pero no creo que importe. El objetivo no es representar Haskell Strings conservando todos sus matices semánticos en el límite de un módulo. El objetivo es convertir una cadena Haskell en una cadena IR, por valor. Esto implica necesariamente calcular toda la cadena.

Estos dos ejemplos ya muestran que un diseño de datos declarativos no es suficiente, a menudo necesitaría poder invocar el código de tiempo de ejecución, que a su vez podría tener sus propias convenciones de llamada.

La forma de modelar eso, independientemente de cómo especifiquemos los enlaces (o incluso SI especificamos algo para los enlaces), es manejar eso en el área de usuario. Si la representación de un idioma de un tipo no se asigna directamente a un enlace, deberá convertirse en una representación que sí lo haga. Por ejemplo, si las cadenas de Haskell están realmente representadas como (type $haskellString (struct (field i8) (field (func (result (ref $haskellString)))))) , necesitará convertir a una cadena estricta y usar un enlace tipo Scheme, o una matriz de texto y usar un enlace similar a Java, o un CFFIString y use un enlace tipo C. La propuesta de valor de tener múltiples tipos de enlaces imperfectos es que algunos de ellos son menos incómodos para Haskell que otros, y es posible construir tipos Wasm-FFI sin necesidad de modificar el compilador.

Y eso es solo cadenas, que son un tipo de datos bastante simple conceptualmente. Ni siquiera quiero pensar en el número infinito de formas en que los lenguajes representan tipos de productos (tuplas / estructuras / objetos).
¡Y luego está el lado receptor, donde tendrías que poder crear todas estas estructuras de datos!

Estoy confundido, veo que decir "la vinculación entre idiomas es completamente imposible, por lo que no deberíamos intentarlo", pero creo que lo que ha estado diciendo ha sido más como "No creo que El enfoque aquí descrito es manejable ", lo que parece mucho más razonable. En particular, mi objeción a esta línea de argumentación es que no describe un camino a seguir. Dado que este problema es "muy difícil", ¿qué hacemos?

En cambio, comenzaríamos a privilegiar algunos

Casi seguro. La pregunta es, ¿en qué grado se privilegian los pocos lenguajes con soporte óptimo? ¿Qué margen de maniobra tienen las lenguas desfavorecidas para encontrar una solución ergonómica?

mientras ya crecía un gran zoológico de cosas arbitrarias.

No estoy seguro de a qué te refieres con eso. Mi interpretación de lo que es arbitrario es "qué idiomas admitimos", pero eso es lo mismo que "privilegiar unos pocos", lo que sería una doble contabilización. Y, por lo tanto, esto solo sería fatalmente defectuoso en ese nivel, en lugar de en múltiples: D

@aardappel, la versión corta es que ese es mi plan de respaldo si el enfoque abstracto declarativo falla: vaya en la dirección totalmente opuesta y describa un formato de serialización. Se hace la observación de que la Web en sí se basa casi en su totalidad en texto, porque es un denominador común extremadamente bajo. El texto es trivialmente comprensible con todas las herramientas, por lo que algo como la Web es posible además.

La mayor preocupación que tengo con los datos en búfer que podrían hacer que ese enfoque sea intratable es ¿cómo podemos manejar los tipos de referencia? La mejor idea que tengo es compartir tablas y serializar índices en ellas, pero no tengo una idea completa de lo bien que funcionaría.

@ jgravelle-google ¿tal vez los tipos de referencia deberían mantenerse separados? Por lo tanto, la firma sin procesar de una función determinada podría ser ref ref i32 i32 i32 i32 que en realidad es 2 anyrefs seguidos de 2 búferes de un tipo en particular (especificado en el esquema hipotético anterior).

(Dicho sea de paso, estoy familiarizado con Haskell, pero la idea de cadena como listas de descanso de caracteres volar mi mente. Cuando se lista de bytes ligado siempre la forma más eficiente o conveniente para hacer cualquier cosa? Tengo que necesita todo para Haskell ser inmutable, y que las listas vinculadas permiten una anteposición económica, pero puede obtener y manipular cadenas inmutables sin usar listas vinculadas).

La mayor preocupación que tengo con los datos en búfer que podrían hacer que ese enfoque sea intratable es ¿cómo podemos manejar los tipos de referencia? La mejor idea que tengo es compartir tablas y serializar índices en ellas, pero no tengo una idea completa de lo bien que funcionaría.

Esa es una de las razones por las que propuse capnproto como codificación. Las tablas de referencia están más o menos integradas.

En cualquier caso, queremos que cualquier formato que elijamos tenga tipos de referencia como ciudadanos de primera clase, que se puedan colocar en cualquier lugar del gráfico de datos. (por ejemplo, en opcionales, matrices, variantes, etc.)

Gracias a todos por sus comentarios.

Creo que estamos comenzando a llegar al punto en el que en su mayoría estamos recabando los mismos argumentos una vez más con muy poca variación, así que actualizaré la propuesta y trataré de abordar las preocupaciones de todos. Probablemente empezaré de nuevo con un nuevo número una vez que termine de escribir la propuesta actualizada.

Para resumir los comentarios hasta ahora:

  • Hay muy poco consenso sobre qué formato de serialización, si lo hay, sería el mejor para wasm. Las alternativas incluyen FlatBuffers, gráficos de estructura C ABI con punteros en bruto, un formato IDL wasm hecho a medida o alguna combinación de los anteriores.

  • La propuesta necesita un espacio negativo más fuerte. Varios lectores se sintieron confundidos por el alcance de la propuesta y los casos de uso que se pretendía facilitar (vinculación estática frente a dinámica, módulo a host frente a host a host, compartir datos mutables frente a pasar mensajes inmutables).

  • @lukewagner ha expresado cierto entusiasmo por el potencial de un sistema de módulos que conecta módulos que desconfían mutuamente, cuando se combina con la integración de ESM. La próxima iteración de la propuesta debería ampliar ese potencial; en particular, creo que tener un sistema de tipos compatible con versiones anteriores permitiría a wasm usar un modelo de árbol de dependencia similar a npm, evitando al mismo tiempo la peor parte del problema del diamante de dependencia.

  • Ha habido pocos comentarios sobre el tema de las capacidades, es decir, valores opacos que pueden devolverse y pasarse, pero no crearse a partir de datos sin procesar. Lo tomo como una señal de que la próxima iteración debería tener mucho más énfasis en ellos.

  • Varios lectores expresaron su preocupación acerca de la viabilidad de un sistema de tipos entre idiomas. Estas preocupaciones son algo vagas y difíciles de definir, en parte porque el tema es muy abstracto, en parte porque la propuesta hasta ahora es bastante vaga en sí misma, lo que se hace eco del problema del huevo y la gallina de @lukewagner . Los estados de falla específicos incluyen:

    • Centrarse demasiado en lenguajes muy visibles, dejando atrás más lenguajes especializados.
    • Tener una abstracción con fugas que intenta ser demasiado general, pero que no cubre el caso de uso de nadie de manera conveniente o eficiente.
    • Cubriendo demasiados casos, creando una implementación de N * N inflada que todavía sufre los problemas anteriores.

La próxima iteración de la propuesta debe abordar estas preocupaciones, de una forma u otra.

En particular, creo que la discusión se beneficiaría mucho de algunos ejemplos de muñecos de paja para razonar. Estos ejemplos incluirían al menos dos bibliotecas, escritas en diferentes lenguajes con diferentes diseños de datos (por ejemplo, C ++ y Java), consumidas por al menos dos programas mínimos en diferentes lenguajes (por ejemplo, Rust y Python), para ilustrar el problema n * n y estrategias para abordarlo.

Además, como han señalado los lectores, la propuesta actualmente entrelaza la idea de un esquema tipográfico con la idea de su representación. Si bien la propuesta hace un buen trabajo al establecer los requisitos de un formato de representación, primero debe establecer los requisitos de un esquema de tipo abstracto.

De todos modos, gracias de nuevo a todos los que han participado en esta discusión hasta ahora. Intentaré presentar una propuesta más completa lo antes posible. Si alguien aquí está interesado en ayudarme a escribirlo, ¡asegúrese de enviarme un correo electrónico!

@ jgravelle-google:

La forma de modelar eso, independientemente de cómo especifiquemos los enlaces (o incluso SI especificamos algo para los enlaces), es manejar eso en el área de usuario.

Sí, estoy de acuerdo, y mi argumento es similar al de @aardappel : si eso es lo que generalmente tenemos que hacer de todos modos, simplemente deberíamos aceptarlo y no intentar cosas ad-hoc para mejorar algunos casos extraños. Userland es donde pertenece la conversión, en el espíritu de todo lo demás en Wasm.

Estoy confundido, veo que decir "la vinculación entre idiomas es completamente imposible, por lo que no deberíamos intentarlo"

Creo que es totalmente deseable definir un DDL (esquema de tipos) para la interoperabilidad de datos entre lenguajes. Simplemente no creo que sea manejable construir conversiones en Wasm. Las conversiones deben implementarse en la tierra de los usuarios. La capa de enlace simplemente prescribe un formato que el código de usuario tiene que producir / consumir.

mientras ya crecía un gran zoológico de cosas arbitrarias.
No estoy seguro de a qué te refieres con eso. Mi interpretación de lo que es arbitrario es "qué idiomas admitimos", pero eso es lo mismo que "privilegiar unos pocos", lo que sería una doble contabilización.

Lo siento, quise decir que sospecho que no habrá nada terriblemente canónico en estas conversiones. De modo que tanto su selección es "arbitraria" como su semántica individual.

¿Cómo podemos manejar los tipos de referencia?

Ah, esa es una buena pregunta. FWIW, estamos tratando de abordar este mismo problema en este momento para un IDL / DDL para la plataforma Dfinity. Siempre que solo haya alguna referencia, la solución es bastante simple: el formato de serialización define dos piezas, una porción de memoria que proyecta los datos transparentes y una porción de tabla que proyecta las referencias contenidas. En consecuencia, varios tipos de referencia requieren múltiples cortes de tabla. La pregunta delicada es qué hacer una vez que el conjunto de tipos de referencia ya no sea finito (por ejemplo, con referencias de funciones escritas).

Una vez que tengamos los tipos de GC, debería haber una forma alternativa de suministrar los datos, que es como un valor de GC. En ese caso, las referencias no son un problema, porque se pueden mezclar libremente.

@PoignardAzur :

Aparte, no estoy familiarizado con Haskell, pero la idea de cadenas como listas de caracteres perezosos me deja boquiabierto.

Sí, creo que hoy en día se considera un error. Pero demuestra cuánta diversidad hay incluso para tipos de datos "simples".

@rossberg

Creo que es totalmente deseable definir un DDL (esquema de tipos) para la interoperabilidad de datos entre lenguajes. Simplemente no creo que sea manejable construir conversiones en Wasm. Las conversiones deben implementarse en la tierra de los usuarios.

Estoy de acuerdo, y para agregar a eso: soy escéptico acerca de agregar algo a la especificación de wasm para esto porque no creo que wasm tenga una mayor necesidad de una solución entre idiomas que otras plataformas, y no creo que wasm tiene una mayor capacidad para implementar una solución de este tipo que otras plataformas. No hay nada obviamente especial para mí sobre wasm aquí, por lo que no estoy seguro de por qué podemos hacerlo mejor que las soluciones estándar en esta área, por ejemplo, los búferes como mencionó @aardappel . (¡Pero creo que la experimentación en el espacio de usuario es muy interesante, como lo es en todas las plataformas!)

Lo único que tiene wasm, al menos en la Web, son los tipos de API de JavaScript / Web para cadenas y matrices, etc. Obviamente, poder interactuar con ellos es importante.

No creo que wasm tenga una mayor necesidad de una solución entre idiomas que otras plataformas

Creo que sí. Su uso en la web de forma predeterminada significa que el código se puede ejecutar y se ejecutará en diferentes contextos. De la misma manera que uno podría <script src="http://some.other.site/jquery.js"> , me encantaría ver a la gente combinando bibliotecas wasm de una manera de origen cruzado. Debido a las propiedades efímeras y de componibilidad que proporciona la web, el valor agregado de poder interactuar con un módulo externo es mayor que nunca en los sistemas nativos.

y no creo que wasm tenga una mayor capacidad para implementar una solución de este tipo que otras plataformas.

Y creo que sí. Debido a que wasm lo ejecuta un incrustador / en un host, la generación de código se abstrae de manera efectiva. Debido a eso, una máquina virtual tiene muchas más herramientas + margen de maniobra para admitir construcciones de nivel superior que no son posibles en sistemas nativos.

Entonces creo que algo en este espacio es más valioso y más posible que en otros sistemas, por eso wasm es especial en este contexto. Para mí, la interoperabilidad JS es un caso especial de la noción más general de que los módulos wasm deben poder hablar con cosas externas con visiones del mundo muy diferentes.

Un camino a seguir para esto es llevar esto completamente a la interoperabilidad a nivel de herramienta por ahora, y diferir la estandarización hasta que tengamos un formato ganador. Entonces, si el objetivo es tener el ecosistema del administrador de paquetes wasm predominante utilizando un formato de interfaz dado (¿y es NPM o WAPM o algún administrador de paquetes aún creado?), Entonces eso puede suceder independientemente de la estandarización. En teoría, podemos estandarizar lo que la gente ya está haciendo para obtener un mejor rendimiento, pero la ergonomía se puede implementar en la tierra de los usuarios. Existe el riesgo de que el formato interlenguaje ganador no se preste a la optimización, y terminemos con un estándar de facto subóptimo. Si podemos diseñar un formato con la intención de estandarizarlo más tarde (¿el estilo declarativo en una sección personalizada es mayormente suficiente?), Eso elimina ese riesgo, pero también retrasa cualquier mejora de rendimiento. Para mí, el rendimiento es uno de los motivadores menos emocionantes para tener este tipo de cosas, así que estoy bastante de acuerdo con eso, aunque otros pueden no estar de acuerdo.

(¿Y es NPM o WAPM o algún administrador de paquetes aún creado?)

Creo que es demasiado pronto para que WAPM sea un administrador de paquetes viable. Necesitamos características como la integración de ESM, WASI y alguna forma de enlaces entre idiomas para estandarizar antes de que un administrador de paquetes wasm sea factible.

Tal como están las cosas, no creo que WAPM siquiera tenga gestión de dependencias.

No creo que wasm tenga una mayor necesidad de una solución entre idiomas que otras plataformas

Creo que sí. Su uso en la web de forma predeterminada significa que el código se puede ejecutar y se ejecutará en diferentes contextos. De la misma manera que uno podría

Las conversiones deben implementarse en la tierra de los usuarios. La capa de enlace simplemente prescribe un formato que el código de usuario tiene que producir / consumir.

Ese es un buen resumen para mí.

Mientras escribo un nuevo borrador, tengo una pregunta abierta para todos en este hilo:

¿Existe alguna biblioteca que, si se compilara en WebAssembly, le gustaría poder usar desde cualquier idioma?

Básicamente, estoy buscando casos de uso potenciales en los que basar el diseño. Tengo el mío propio (específicamente, React, el motor Bullet y los sistemas de complementos), pero me gustaría trabajar con más ejemplos.

@PoignardAzur Muchos lenguajes en C usan las mismas bibliotecas de expresiones regulares compatibles con Perl (PCRE), pero en la incrustación del navegador probablemente deberían usar la API Regex de JS.

@PoignardAzur Me vienen a la mente BoringSSL y libsodium.

También la implementación de Cap'n Proto RPC, pero esta es extraña: la capa de _serialización_ de Cap'n Proto de manera realista debe implementarse de forma independiente en cada idioma, ya que la mayor parte es una capa de API amplia pero poco profunda que debe ser idiomática e integrada -simpático. La capa de RPC, OTOH, es estrecha pero profunda. En principio, debería ser posible utilizar la implementación de C ++ RPC detrás de la implementación de serialización de cualquier lenguaje arbitrario pasando referencias de matriz de bytes codificadas en capnp sobre el límite de FFI ...

Creo que hacer lo que se propone en última instancia requeriría algunos cambios bastante invasivos en el propio Web Assembly, ya que ya existe, pero podría decirse que valdría la pena.

Me gustaría señalar que el mundo SmallTalk tuvo alguna experiencia positiva con tal esfuerzo que podría ser informativo en el desarrollo de su Protocolo de replicación estatal (SRP), que es un protocolo de serialización eficiente que puede representar cualquier tipo de cualquier tamaño de manera bastante eficiente. He considerado convertirlo en un diseño de memoria nativa para una máquina virtual o incluso una FPGA, pero no he podido probarlo. Sé que fue portado al menos a otro idioma, Squeak, con buenos resultados. Ciertamente, algo para leer, ya que tiene una fuerte superposición con los problemas, desafíos y experiencias de esta propuesta.

Entiendo por qué Web IDL fue la propuesta predeterminada como lenguaje vinculante: es el lenguaje vinculante histórico y de alguna manera maduro para la Web. Apoyo mucho esa decisión y es muy probable que yo hubiera tomado lo mismo. No obstante, podemos reconocer que apenas se adapta a otros contextos (entender, otros hosts / lenguajes). Wasm está diseñado para ser independiente del host o del idioma / plataforma. Me gusta la idea de utilizar tecnologías web maduras y encontrar un caso de uso para escenarios no web, pero en el caso de Web IDL, parece realmente vinculado a la web. Esa es la razón por la que estoy siguiendo muy de cerca esas conversaciones aquí.

Abrí https://github.com/WebAssembly/webidl-bindings/issues/40 , lo que me llevó a hacer una pregunta aquí ya que no vi ninguna mención de ella (o la perdí).

En toda la historia de la vinculación, no está claro "quién" es responsable de generar las vinculaciones:

  • ¿Es un compilador (que transforma un programa en un módulo Wasm)?
  • ¿Es un autor del programa (y por tanto, las encuadernaciones están escritas a mano)?

Creo que ambos son válidos. Y en el caso de Web IDL, parece mostrar algunas limitaciones (consulte el enlace de arriba). Tal vez me perdí un paso importante en el proceso y, por lo tanto, considere olvidar mi mensaje.

Incluso si el objetivo es "reenfocar" Web IDL para que sea menos centrado en la Web, en este momento, _es_ muy centrado en la Web. Y surgen propuestas para proponer alternativas, de ahí este hilo. Por tanto, me preocupa una posible fragmentación. Idealmente (y así es como se diseñó Wasm hasta ahora), dado un módulo Wasm que incluye sus enlaces, es posible ejecutarlo en cualquier lugar tal como está. Con enlaces escritos en Web IDL, Cap'n 'Proto, FlatBuffers, lo que sea, estoy bastante seguro de que no todos los compiladores o autores de programas escribirán los mismos enlaces en diferentes sintaxis para ser verdaderamente multiplataforma. Curiosamente, este es un argumento a favor de los enlaces escritos a mano: la gente puede contribuir a un programa escribiendo enlaces para la plataforma P. Pero admitamos que esto no será ideal en absoluto.

Entonces, para resumir: me preocupa una posible fragmentación entre enlaces web y no web. Si se mantiene un lenguaje de vinculación no web, ¿los navegadores web lo implementarán de manera realista? Tendrían que escribir enlaces “Wasm ⟶ lenguaje de enlace B ⟶ Web IDL”. Tenga en cuenta que este es el mismo escenario para todos los hosts: Wasm ⟶ lenguaje de enlace B ⟶ API de host.

Para aquellos que tengan curiosidad, trabajo en Wasmer y soy el autor de las integraciones PHP , Python , Ruby y Go Wasm. Empezamos a tener un bonito patio de juegos para hackear diferentes enlaces para hosts muy diferentes. Si alguien quiere que integre diferentes soluciones, que recopile opiniones o intente experimentar, todos estamos abiertos y listos para colaborar y poner más recursos en ello.

La dirección actual en 'enlaces webIDL' probablemente esté lejos de webIDL
sí mismo. Sin embargo, el dilema es este:

El lenguaje 'natural' para expresar inter-módulo y módulo-host
la interoperabilidad es significativamente más rica que el lenguaje natural de WASM. Esta
significa que cualquier equivalente IDL utilizable se verá bastante arbitrario desde
Punto de vista de WASM.

Por otro lado, para la gente que ve el mundo desde la lente de C / C ++
(y Rust y sus semejantes) cualquier cosa más rica que el modelo de WASM corre el riesgo de ser
inutilizable. Ya podemos ver esto con la dificultad de integrar ref.
tipos en la cadena de herramientas.

Además, no es un mandato directo de WASM apoyar
interoperabilidad entre idiomas. Tampoco debería ser IMO.

(Existe una versión más limitada de interoperabilidad entre idiomas que yo
Creemos que no solo es compatible sino también fundamental: está en todos nuestros
interés que los proveedores de capacidades y los usuarios de capacidades puedan satisfacer
con el mínimo de fricción. (La capacidad es hablar de la gerencia, pero tengo
no encontré un término mejor.) Esto requiere un estilo diferente de IDL y una
que es más fácil de implementar de lo que sería necesario para un lenguaje interlingüístico completo
interoperabilidad.)

En pocas palabras: existe un caso para tener un equivalente de IDL, y lo necesitamos
para apoyar la interoperación a través de los límites de propiedad. Que termina
el ser no está claro en este momento.

El lunes, 24 de junio de 2019 a las 7:02 a.m. Ivan Enderlin [email protected]
escribió:

Entiendo por qué Web IDL era la propuesta predeterminada como lenguaje vinculante:
Es el lenguaje vinculante histórico y de alguna manera maduro para la Web. I
apoyo enormemente esa decisión, y es muy probable que hubiera tomado la
mismo. No obstante, podemos reconocer que apenas se adapta a otros contextos.
(entender, otros hosts / idiomas). Wasm está diseñado para ser independiente del host,
o idioma / plataforma-agnóstico. Me gusta la idea de usar Web para adultos.
tecnologías y para encontrar un caso de uso para escenarios no web, pero en el caso
de Web IDL, parece realmente ligado a la Web. Que es la razón por la que soy muy
siguiendo de cerca esas conversaciones aquí.

Abrí WebAssembly / webidl-bindings # 40
https://github.com/WebAssembly/webidl-bindings/issues/40 , que me llevó
hacer una pregunta aquí, ya que no he visto ninguna mención de ella (o la he perdido).

En toda la historia vinculante, no está claro "quién" es responsable de
generar los enlaces:

  • ¿Es un compilador (que transforma un programa en un módulo Wasm)?
  • ¿Es un autor del programa (y por tanto, las encuadernaciones están escritas a mano)?

Creo que ambos son válidos. Y en el caso de Web IDL, parece mostrar algunos
limitaciones (ver el enlace de arriba). Tal vez me perdí un paso importante en
el proceso, y así, considere olvidar mi mensaje.

Incluso si el objetivo es "reenfocar" Web IDL para que esté menos centrado en la Web, ahora mismo,
está muy centrado en la Web. Y surgen propuestas para proponer alternativas,
de ahí este hilo. Por tanto, me preocupa una posible fragmentación.
Idealmente (y así es como se diseñó Wasm hasta ahora), dado un módulo Wasm
incluidos sus enlaces, es posible ejecutarlo en cualquier lugar como está. Con
enlaces escritos en Web IDL, Cap'n 'Proto, FlatBuffers, lo que sea, soy
bastante seguro que no todos los compiladores o autores de programas escribirán lo mismo
enlaces en diferentes sintaxis para ser verdaderamente multiplataforma. Curiosamente, esto es
un argumento a favor de las encuadernaciones escritas a mano: las personas pueden contribuir a
programa escribiendo enlaces para la plataforma P. Pero admitamos que esto no será
ideal en absoluto.

En resumen: me preocupa una posible fragmentación entre Web y
enlaces no web. Si se mantiene un lenguaje no vinculante a la Web, ¿sería
implementado de manera realista por los navegadores web? Tendrían que escribir
enlaces “Wasm ⟶ lenguaje de enlace B ⟶ Web IDL”. Tenga en cuenta que esto es lo mismo
escenario para todos los hosts: Wasm, lenguaje de enlace B, API de host.

Para aquellos que tienen curiosidad, trabajo en Wasmer y soy el autor de PHP-
https://github.com/wasmerio/php-ext-wasm , Python-
https://github.com/wasmerio/python-ext-wasm , Ruby-
https://github.com/wasmerio/ruby-ext-wasm y Go-
https://github.com/wasmerio/go-ext-wasm Integraciones de Wasm. Empezamos
tener un bonito patio de juegos para piratear diferentes enlaces para muy diferentes
Hospedadores. Si alguien quiere que integre diferentes soluciones, recopile
retroalimentaciones, o intentar experimentar, todos estamos abiertos y listos para colaborar
y ponerle más recursos.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUD6WA22DDUS7PYQ6F3P4DHYRA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXWHG43WTMVMVDFLOGDFVREXG43WNMVMV
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAQAXUGM66AWN7ZCIVBTXVTP4DHYRANCNFSM4HJUHVGQ
.

-
Francis McCabe
SWE

Ya podemos ver esto con la dificultad de integrar tipos de referencia en la cadena de herramientas.

No puedo hablar en otros idiomas, pero Rust + wasm-bindgen ya tiene soporte para:

Entonces tengo curiosidad: ¿a qué dificultades te refieres?

Mi comprensión de las dificultades está más en el extremo de C ++. Rust tiene una metaprogramación lo suficientemente potente como para hacer esto más razonable en el extremo del lenguaje, pero el área de usuario C ++ tiene más dificultades para razonar sobre cualquier referencia, por ejemplo.

Tendría curiosidad por saber más sobre los problemas específicos de C ++ aquí. (¿Son específicos de C ++ o específicos de LLVM?)

C ++ no sabe qué es un tipo de referencia. Entonces no puedes tenerlo dentro de un
objeto arbitrario. No es realmente parte del idioma; más como un archivo
descriptor. Lugar divertido para una cuerda.

El lunes, 24 de junio de 2019 a las 3:07 p.m., Alon Zakai [email protected] escribió:

Tendría curiosidad por saber más sobre los problemas específicos de C ++ aquí. (Son ellos
¿Específico de C ++ o específico de LLVM?)

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/WebAssembly/design/issues/1274?email_source=notifications&email_token=AAQAXUDW237MUBBUUJLKS6LP4FARJA5CNFSM4HJUHVG2YY3PNVWWK3TUL52HS4DFVREXG43VMDVN5H4DFVREXG43VMDMVBW5H4DFVREXG43VMDMVBW19 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAQAXUB4O3ZX4LRQSQRL763P4FARJANCNFSM4HJUHVGQ
.

-
Francis McCabe
SWE

Hablando con @fgmccabe sin conexión, es cierto que tanto C ++ como Rust no pueden almacenar directamente un tipo de referencia en una estructura, ya que la estructura se almacenará en la memoria lineal. Tanto C ++ como Rust pueden, por supuesto, manejar tipos de ref indirectamente, de la misma manera que manejan descriptores de archivos, texturas OpenGL, etc. - con identificadores de enteros. Creo que su punto es que ninguno de esos dos lenguajes puede manejar tipos de referencia "bien" / "nativamente" (¡corrígeme si me equivoco!), Con lo cual estoy de acuerdo: esos lenguajes siempre estarán en desventaja en el tipo de referencia. operaciones, en comparación con los lenguajes GC.

Sigo sintiendo curiosidad por saber si hay algo específico para C ++ aquí. No creo que lo haya

Mi comprensión de lo que hace que C ++ sea difícil aquí es si ha dicho:

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

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

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

La buena noticia es que el último ejemplo es UB, creo (los punteros inválidos son UB tan pronto como se crean), pero ¿cómo intentamos modelar eso en LLVM IR?

@ jgravelle-google Creo que incluso struct Anyref; presupone que es algo que tiene sentido en la memoria lineal. En su lugar, ¿por qué no modelarlo con un identificador de número entero como se mencionó anteriormente, como texturas OpenGL, identificadores de archivos, etc.?

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

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

El identificador de enteros debe buscarse en la tabla cuando se va a usar; nuevamente, esto es solo una desventaja de los lenguajes que usan memoria lineal como C ++ y Rust. Sin embargo, definitivamente podría optimizarse al menos localmente, si no por LLVM, entonces a nivel de wasm.

Eso funcionará, pero luego debes asegurarte de llamar a table_free(elem) o mantendrás una referencia para siempre. Lo cual no es -eso- extraño para C ++, por supuesto.

Es un mapeo algo extraño porque creo que no se superpone bien. Como si se sintiera como una biblioteca a la OpenGL, pero depende de la magia del compilador para proporcionar; no creo que pueda construir un anyref.h en C ++ incluso con un wasm en línea, si depende de declarar un mesa.

De todos modos, creo que todo es factible / manejable, pero no sencillo, eso es todo.

@kripken Es cierto que el soporte nativo "adecuado" de anyref requerirá algunos cambios en LLVM (y en rustc), pero eso en realidad no es un obstáculo.

wasm-bindgen almacena wasm anyref s genuino en una tabla wasm, y luego en la memoria lineal almacena un índice entero en la tabla. Entonces puede acceder a anyref usando la instrucción wasm table.get .

Hasta que se implemente wasm-gc, los lenguajes GC necesitarán usar exactamente la misma estrategia, por lo que Rust (et al) no se está perdiendo.

Entonces, ¿qué nos ganaría el soporte nativo de anyref en LLVM? Bueno, permitiría pasar / devolver directamente anyref de funciones, en lugar de necesitar indirectamente anyref través de una tabla wasm. Eso sería útil, sí, pero eso es solo una optimización del rendimiento, en realidad no evita el uso de anyref .

@Pauan

wasm-bindgen almacena wasm anyrefs genuino en una tabla wasm, y luego, en la memoria lineal, almacena un índice entero en la tabla. Entonces puede acceder a anyref usando la instrucción wasm table.get.

Exactamente, sí, ese es el modelo al que me refería.

Hasta que se implemente wasm-gc, los lenguajes GC necesitarán usar exactamente la misma estrategia, por lo que Rust (et al) no se está perdiendo.

Sí, en este momento los lenguajes GC no tienen ninguna ventaja porque no tenemos wasm GC nativo. ¡Pero espero que eso cambie! :) Eventualmente espero que los lenguajes GC tengan una clara ventaja aquí, al menos si hacemos GC correctamente.

Entonces, ¿en qué nos ganaría el soporte de anyref nativo en LLVM? Bueno, permitiría pasar / devolver directamente anyref de las funciones, en lugar de necesitar indirectamente el anyref a través de una tabla wasm. Eso sería útil, sí, pero eso es solo una optimización del rendimiento, en realidad no evita el uso de anyref.

De acuerdo, sí, esto solo será una ventaja de rendimiento de los lenguajes GC (eventualmente) sobre C ++ y Rust, etc. No impide su uso.

Sin embargo, los ciclos son un problema mayor para C ++ y Rust, ya que las tablas raíz. Tal vez podamos tener una API de rastreo u "objetos de sombra", básicamente alguna forma de mapear la estructura de los enlaces GC dentro de C ++ / Rust para que el GC externo pueda entenderlos. Pero no creo que haya una propuesta real para ninguno de ellos todavía.

Finalmente, espero que los lenguajes GC tengan una clara ventaja aquí, al menos si hacemos GC correctamente.

Podría estar equivocado, pero me sorprendería si ese fuera el caso: los lenguajes GC tendrían que asignar una estructura wasm GC y luego el motor wasm tendría que realizar un seguimiento de eso a medida que fluye a través del programa.

En comparación, Rust no necesita asignación (solo asignar a una tabla), y solo necesita almacenar un número entero, y el motor wasm solo necesita realizar un seguimiento de 1 tabla inmóvil estática para propósitos de GC.

Supongo que es posible que el acceso anyref sea ​​optimizable para los lenguajes GC, ya que no necesitaría usar table.get , sin embargo, espero que table.get sea ​​bastante rápido.

Entonces, ¿podría explicar más acerca de cómo espera que un programa wasm-gc funcione mejor que un programa que usa una tabla wasm?

PD: Esto está empezando a alejarse bastante del tema, así que tal vez deberíamos mover esta discusión a un nuevo hilo.

Realmente solo eso: evitar table.get/table.set . Con GC, debería tener el puntero sin procesar allí mismo, guardando las direcciones indirectas. Pero sí, tienes razón en que Rust y C ++ solo necesitan almacenar un número entero, y son bastante rápidos en general, por lo que cualquier ventaja de GC podría no importar.

Estoy de acuerdo en que podríamos estar saliendo del tema, sí. Creo que lo que está en el tema es el punto de @fgmccabe de que los tipos de referencia no encajan tan naturalmente en los lenguajes que usan memoria lineal. Eso puede influirnos de cierta manera con los enlaces (en particular, los ciclos son preocupantes porque C ++ y Rust no pueden manejarlos, pero ¿tal vez los enlaces pueden ignorar eso?), Así que supongo que algo con lo que tener cuidado, ambos para intentar hacer que las cosas funcionen para tantos idiomas como sea posible y no estar demasiado influenciado por las limitaciones de ningún idioma en particular.

@kentonv

De manera realista, la capa de serialización de Cap'n Proto debe implementarse de forma independiente en cada idioma, ya que la mayor parte es una capa de API amplia pero poco profunda que debe ser idiomática y compatible con las líneas. La capa de RPC, OTOH, es estrecha pero profunda

¿Qué carpeta es?

@PoignardAzur Lo siento, no entiendo tu pregunta.

@kentonv Estoy revisando el repositorio de capnproto Github. ¿Dónde está la capa de serialización?

@PoignardAzur Entonces, esto vuelve a mi punto. Realmente no hay un solo lugar al que pueda señalar y decir "esa es la capa de serialización". En su mayoría, la "serialización" de Cap'n Proto es solo aritmética de punteros alrededor de cargas / almacenes a un búfer subyacente. Dado un archivo de esquema, utiliza el generador de código para generar un archivo de encabezado que define métodos en línea que hacen la aritmética de puntero correcta para los campos particulares definidos en el esquema. El código de la aplicación necesita llamar a estos descriptores de acceso generados cada vez que lee o escribe cualquier campo.

Es por eso que no tendría sentido intentar llamar a una implementación escrita en un idioma diferente. Usar un FFI sin procesar para cada acceso de campo sería extremadamente engorroso, por lo que sin duda terminaría escribiendo un generador de código que envuelva el FFI en algo más bonito (y específico para su esquema). Pero ese código generado sería al menos tan complicado como el código que ya implementa Cap'n Proto, probablemente más complicado (¡y mucho más lento!). Por lo tanto, tiene más sentido escribir un generador de código para el idioma de destino directamente.

Quizás haya algunas funciones de ayuda internas dentro de la implementación de Cap'n Proto que podrían compartirse. Específicamente, layout.c++ / layout.h contiene todo el código que interpreta la codificación del puntero de capnp, realiza la comprobación de límites, etc. Los accesores de código generados llaman a ese código cuando leen / escriben campos de puntero. Así que quizás podría imaginarme envolver esa parte en un FFI para que se llame desde varios idiomas; pero todavía espero escribir generadores de código y cierta cantidad de biblioteca de soporte de tiempo de ejecución en cada idioma de destino.

Sí, lo siento, quise decir lo contrario ^^ (la capa RPC)

@PoignardAzur Ohhh, y supongo que estás específicamente interesado en mirar las interfaces, ya que estás pensando en cómo envolverlas en una FFI. Entonces quieres:

  • capability.h : interfaces abstractas para representar capacidades e invocación de RPC, que en teoría podrían estar respaldadas por una variedad de implementaciones. (Esta es la parte más importante.)
  • rpc.h : Implementación de RPC en una red.
  • rpc-twoparty.h : Adaptador de transporte para RPC a través de una conexión simple.

Esta propuesta ahora es reemplazada por # 1291: enlaces OCAP.

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

Temas relacionados

badumt55 picture badumt55  ·  8Comentarios

jfbastien picture jfbastien  ·  6Comentarios

dpw picture dpw  ·  3Comentarios

arunetm picture arunetm  ·  7Comentarios

chicoxyzzy picture chicoxyzzy  ·  5Comentarios