1 puntos por GN⁺ 2024-08-28 | 1 comentarios | Compartir por WhatsApp
  • El RV64 DynaRec de Box64 avanzó en un año desde ejecutar juegos nativos simples de Linux hasta llegar al punto de correr The Witcher 3 en una PC RISC-V
  • Un factor clave del progreso fue poder usar tarjetas gráficas AMD, lo que redujo las limitaciones de OpenGL y permitió probar más programas x86 y corregir errores
  • El backend de RV64 tiene menos instrucciones x86 implementadas que el backend de ARM64, y las instrucciones AVX siguen siendo una tarea pendiente importante en RISC-V
  • A RISC-V le faltan extracción e inserción de rangos de bits y instrucciones atómicas de 16 bytes, por lo que el costo de traducción en la emulación x86 es mayor que en AArch64 o LoongArch64
  • The Witcher 3 efectivamente corre con box64, alcanzando un máximo de 15 fps dentro del juego y velocidad completa en el menú principal

Cómo The Witcher 3 llegó a ejecutarse en RISC-V

  • Hace un año, el RV64 DynaRec solo podía correr juegos nativos de Linux relativamente fáciles de ejecutar, como Stardew Valley y World of Goo
  • En ese momento había dos cuellos de botella principales
    • Mientras se agregaban rápidamente instrucciones x86_64 al backend de RISC-V, todavía quedaban muchos bugs en el DynaRec
    • Las GPU integradas IMG del VisionFive 2 y el LicheePi 4A solo soportaban OpenGL ES y no OpenGL
  • Con gl4es se obtuvo algo de soporte para OpenGL y fue posible ejecutar juegos como Stardew Valley, pero no era suficiente para juegos de Linux más pesados ni para juegos de Windows en general
  • El Milk-V Pioneer de Sophgo es una PC RISC-V de 64 núcleos y ofrece una ranura PCIe donde se puede instalar una tarjeta gráfica
  • Otro colaborador, xctan, encontró una forma de “conectar” una tarjeta gráfica AMD al VisionFive 2 mediante la interfaz M.2
  • Al poder usar tarjetas gráficas AMD, se amplió el rango de programas x86 que podían probarse, y avanzaron en gran volumen las correcciones de bugs del RV64 DynaRec y la incorporación de instrucciones x86
  • Como resultado, The Witcher 3 funcionó desde el primer intento

Estado actual del RV64 DynaRec

  • El conjunto de instrucciones x86 es muy grande, y el nivel de implementación también varía entre backends
    • El backend de ARM64 implementa en total más de 1,600 instrucciones x86
    • El backend de RV64 implementa alrededor de 1,000 instrucciones x86
    • Más de 300 de ellas son instrucciones AVX añadidas recientemente, y en RISC-V todavía no están implementadas en absoluto
  • También en la implementación de instrucciones SSE, RISC-V está en desventaja de rendimiento
    • El backend de RV64 implementa las instrucciones SSE con instrucciones escalares
    • AArch64 usa la extensión Neon y LoongArch64 usa la extensión LSX
    • Por esta diferencia, el rendimiento es considerablemente menor que en esos otros dos backends
  • RISC-V sí cuenta con la extensión vectorial RVV
    • El Milk-V Pioneer soporta la extensión xtheadvector, una variante de RVV 0.7.1
    • El SoC SpacemiT K1/M1 soporta RVV 1.0 ya estandarizado
    • Ya se pueden comprar Banana Pi F3 y Milk-V Jupiter, que incorporan ese SoC
  • Hace poco se añadieron a box64 soporte básico para RVV y algunas implementaciones de instrucciones SSE comunes
  • El trabajo sobre RVV todavía está en una etapa muy temprana, así que por ahora no ayuda a mejorar el rendimiento

Instrucciones de RISC-V especialmente ausentes para la emulación x86

  • Desde la perspectiva de la emulación x86, RISC-V es la menos expresiva de las tres arquitecturas soportadas
  • Frente a AArch64 y LoongArch64, le faltan instrucciones convenientes, por lo que se necesitan más instrucciones para emular la misma operación
  • Faltan en particular dos capacidades importantes
    • La posibilidad de tomar un rango de bits específico de un registro y llevarlo a otro registro
    • La posibilidad de insertar parte de los bits de un registro en un rango específico de otro registro
  • LoongArch64 y AArch64 sí cuentan con instrucciones equivalentes
    • LoongArch64 usa BSTRPICK.D y BSTRINS.D
    • ARM64 usa los opcodes UBFX y BFI
  • RISC-V no tiene instrucciones equivalentes ni en extensiones oficiales ni en extensiones de fabricantes

El costo de traducción que muestra el ejemplo ADD AH, BL

  • La ISA x86 tiende a conservar los bits no modificados, por lo que la manipulación de registros parciales es importante
  • En el caso de ADD AH, BL, box64 tiene que hacer lo siguiente
    • Extraer el byte menos significativo de RBX
    • Sumarlo al segundo byte menos significativo de RAX
    • Volver a insertar el resultado en el segundo byte menos significativo de RAX
    • Mantener intactos los demás bytes de RAX
  • En LoongArch64, esto puede implementarse de forma simple e intuitiva usando BSTRPICK.D, ADD y BSTRINS.D
  • En RISC-V, para hacer lo mismo hay que combinar desplazamientos, máscaras, AND, OR y otros elementos, por lo que se necesitan 10 instrucciones
  • Este caso no es aislado; x86 tiene muchas instrucciones de forma similar, así que la implementación en RISC-V resulta más engorrosa

Limitaciones de las instrucciones atómicas de 16 bytes

  • x86 cuenta con instrucciones con prefijo LOCK para operaciones atómicas lock-free
  • box64 las emula principalmente con secuencias LR/SC
    • LR/SC significa Load-Reserved / Store-Conditionally
    • Por ejemplo, LOCK ADD [RAX], RCX se genera como una secuencia de LR.D, ADD, SC.D y una rama condicional
  • Si la dirección de RAX no está alineada, la situación se complica más, pero en general este método funciona bien
  • El problema es LOCK CMPXCHG16B
    • Esta instrucción compara RDX:RAX con 16 bytes de memoria
    • Según el resultado, intercambia RCX:RBX con esa dirección de memoria
  • AArch64 y LoongArch64 sí tienen algunas instrucciones atómicas de 16 bytes que pueden usarse para implementarla
  • RISC-V no tiene una instrucción equivalente, así que no puede implementarse de forma tan completa como en las otras arquitecturas
  • Muchos programas, incluidos juegos hechos con Unity, usan LOCK CMPXCHG16B

Resultado real de la ejecución

  • A pesar de las limitaciones que siguen existiendo, The Witcher 3 corre en RISC-V con box64
  • El rendimiento llega a un máximo de 15 fps dentro del juego
  • En el menú principal funciona a velocidad completa
  • No es un mal resultado para una máquina que no fue diseñada con el objetivo de ejecutar juegos AAA

1 comentarios

 
GN⁺ 2024-08-28
Comentarios de Hacker News
  • Desde la perspectiva de alguien que no trabaja del lado de chips, me da curiosidad qué tendría que hacer distinto un ingeniero de software al crear software para RISC-V
    Me pregunto si el tamaño del ejecutable crece y entonces hay que optimizar de forma agresiva la localidad de caché, y también si hay tipos de software, como juegos o servidores web, que se adapten mejor a CISC o a RISC

    • Con la extensión de instrucciones comprimidas, RISC-V tiende a producir un tamaño de código menor en promedio que x86-64 o ARM
      En el enfoque de software no hay mucho que cambiar de forma esencial, y frente a x86-64 la mayor diferencia es que hay 32 registros, así que se pueden mantener más valores intermedios antes de que se derramen a la pila; ARM también tiene 32, así que es parecido. Normalmente no hay mucho de qué preocuparse salvo que estés haciendo microoptimizaciones
      Más en detalle, la extensión vectorial (V/RVV) no está en la ISA base rv64gc, así que según el objetivo podrías no beneficiarte de optimizaciones SIMD, y ni popcount ni el conteo de ceros iniciales/finales están en rv64gc base; se necesita Zbb. Además, una selección sin ramas como a ? b : c requiere 4 o 5 instrucciones en rv64gc base, o 3 con Zicond, mientras que en x86-64 y aarch64 se puede hacer con 1 sola
      Los perfiles de RISC-V resuelven en parte esos dos problemas. Por ejemplo, Android exige rva23, que requiere RVV, Zbb, Zicond, etc. Pero si una distribución de Linux apunta a rva20/rv64gc, entonces en código precompilado sin despacho dinámico esas extensiones en la práctica no se podrán usar por mucho tiempo. En x86-64 hay un problema parecido, pero en ARM hay muchas menos extensiones, así que se nota menos; la mayor excepción es SVE, que todavía no tiene soporte amplio
    • En la mayoría de los casos no hace falta hacer nada distinto. El código bien escrito en un lenguaje de alto nivel como C debería funcionar igual
      La mayor diferencia es el modelo de memoria débil, pero eso también existe en la mayoría de las arquitecturas no x86, como ARM, y de entrada el código no debería depender de un modelo de memoria fuerte
      La densidad del código ejecutable no es tan buena en x86 por razones históricas, así que el tamaño del ejecutable no necesariamente crece tanto como uno imaginaría. RISC-V con extensión de instrucciones comprimidas y ARM de 32 bits con extensión Thumb son bastante compactos
      Lo importante no es CISC vs RISC, sino la presencia y calidad de las instrucciones vectoriales y las extensiones criptográficas. La codificación/decodificación de video depende mucho de las instrucciones vectoriales para lograr buen rendimiento, y el cifrado completo de disco o el hashing pueden beneficiarse de instrucciones dedicadas que aceleran algoritmos específicos como AES y SHA256
    • Casi cualquier conjunto de instrucciones debería adaptarse de manera similar a casi cualquier tipo de carga de trabajo. Si programas en ensamblador sí habría diferencias, pero si usas Python o Unity, casi no importa
      La clave está más en liberarse de las patentes de ARM y hacer un nuevo comienzo con base en las lecciones aprendidas
  • Esto me hizo recordar cuando una persona rusa famosa ejecutó Atomic Heart en un Elbrus 8S
    Elbrus tiene un traductor nativo y, hasta donde sé, es bastante bueno. Atomic Heart corría a unos 15~25 fps, así que era más o menos jugable

  • Al artículo le falta un poco de explicación “básica”. Pensé que lo habían ejecutado con algo como un port de Wine, pero en realidad parece que implementan de alguna manera la ISA x86_64 sobre un chip RISC-V
    Estaría bueno que alguien pudiera explicar mejor cómo funciona esa parte

    • La explicación básica está aquí: https://box86.org/
      Es un emulador, pero usa versiones nativas de algunas bibliotecas “del sistema”, como libc, libm, SDL y OpenGL, así que es fácil integrarlo con la mayoría de las aplicaciones y, en algunos casos, el rendimiento puede ser sorprendentemente alto. Wine también se puede compilar y ejecutar de forma nativa
  • Es un resultado sorprendente. Es una cantidad enorme de trabajo y, en algunos casos, parece estar topándose con las limitaciones de RISC-V
    Las instrucciones de reunir/dispersar bits probablemente deberían entrar como extensión

    • También me gustaría ver resultados probando juegos que dependan más del núcleo gráfico que del CPU. Algo como Divinity 2 quizá sería adecuado
  • Es interesante la parte donde dice que, entre las 3 arquitecturas compatibles en el contexto de emulación x86, RISC-V es la menos expresiva.
    En clases de historia de la informática aprendí que RISC significaba computadora con conjunto de instrucciones reducido, pero hoy, al ver propuestas de perfiles y textos sobre RISC-V, muchas veces parece que dicen algo como “solo hacen falta unas cuantas instrucciones más para lograr equivalencia funcional”. Entiendo que para mucha gente RISC-V es una alternativa conveniente a otras plataformas, pero también me pregunto si eso significa que el sueño de RISC murió.

    • Entiendo que el RISC real no era tanto “un conjunto de instrucciones absolutamente mínimo”, sino más bien “no meter funciones ingeniosas para comodidad del programador en ensamblador y, si es posible, dejarle ese trabajo al compilador en vez de al silicio del frontend”.
      Según recuerdo de cuando leí la especificación de RISC-V, eran bastante estrictos con no agregar instrucciones “combo”, ya que las secuencias de instrucciones comunes pueden fusionarse en el frontend.
      Frente a x86/ARM, lo que le falta a RISC-V parece venir menos del fundamentalismo RISC y más de que la especificación partió desde chips embebidos muy básicos y, con el tiempo, fue agregando extensiones para CPUs de aplicación. El RV32I base ni siquiera tiene multiplicación entera. Por desgracia, tomó demasiado tiempo cerrar los debates sobre manipulación de bits y extensiones SIMD/vectoriales, y de ahí surge el vacío funcional del que se habla ahora.
    • Si quieres crear un conjunto de instrucciones que un estudiante pueda implementar en un semestre, hace falta simplificar cosas, por ejemplo que todas las instrucciones tengan 2 entradas y 1 salida. Eso también se vuelve mucho más fácil para los investigadores que experimentan con diseño de procesadores.
      Pero, a cambio, se dejan fuera algunas instrucciones convenientes para alto rendimiento.
      Una ventaja adicional es que un pipeline simple consume menos recursos de ingeniería en los equipos que diseñan procesadores de alto rendimiento, lo que les permite dedicar más tiempo a la optimización.
      RISC suele ser una filosofía de simplificación, pero el grado varía. MIPS está tan simplificado como RISC-V, mientras que ARM y POWER son más de compromiso, y no parece que tengan grandes problemas para competir con x86 en el segmento de alto rendimiento.
      El mercado de procesadores tiene muchos nichos aparte de ejecutar aplicaciones, como embebidos y aceleradores. En el nicho específico de los núcleos de aplicación soy algo pesimista con RISC-V, pero en un panorama más amplio tiene mucho potencial, incluso podría dominar algunos nichos comerciales, y como herramienta educativa y de investigación es excelente.
    • El sueño de RISC era simplificar el diseño del CPU, porque la mayor parte del software se escribe con compiladores y no directamente en ensamblador.
      Las características clásicas de RISC eran que la mayoría de las instrucciones de manipulación de datos operaban solo sobre registros, que las instrucciones de memoria normalmente se limitaban a load/store hacia registros, y por eso hacían falta muchos registros. Como había que manipular la pila directamente para pasar parámetros, también se construía la pila a mano, e incluso sin instrucciones CALL/JSR, usando instrucciones básicas que hacían load/store directo sobre el registro del puntero de instrucción. La codificación de instrucciones era predecible y todas las instrucciones tenían el mismo tamaño. Varias arquitecturas RISC también tenían un registro que siempre leía 0 y no se podía escribir, usado para poner valores en 0.
      Ese enfoque funcionó, pero después la ejecución fuera de orden y SIMD redujeron su importancia. El flujo de instrucciones en bruto se parece más a una declaración del camino hacia el resultado deseado, no a que el CPU realmente lo ejecute tal cual. Detrás vienen la ejecución especulativa, la predicción de saltos y el renombrado de registros. SIMD se acerca más a un espacio amplio de registros y a instrucciones que operan sobre todos los valores dentro de él. Al final, la ejecución fuera de orden y SIMD tomaron el control.
    • No estoy seguro de que exista algo así como un sueño de RISC. Hay sueños de eficiencia, de rendimiento y de costo, e incluso de baja complejidad respecto a costo, rendimiento y eficiencia, pero ¿hay alguien que valore RISC en sí por encima de costo, rendimiento, eficiencia y simplicidad?
    • En este contexto, lo que se busca es ejecutar en RISC-V código ya compilado para x86_64. La demanda de “hacen falta unas cuantas instrucciones más para lograr equivalencia funcional” aparece precisamente porque se intenta ejecutar código compilado para arquitecturas que ya cuentan con esas instrucciones adicionales.
      En teoría, si se compilara el código fuente original para RISC, saldría un binario completamente distinto y puede que esas instrucciones concretas no hicieran falta.
      En la práctica, no parece probable que alguien vaya a compilar estos juegos realmente para RISC-V.
  • En la captura de pantalla sale que la RAM es de 31 GB, que claramente es más de la especificación máxima de la placa de desarrollo mencionada. ¿Aquí están usando otra cosa?

    • Es la Pioneer, una placa más antigua.
      Hoy en día sería mejor usar alguna opción reciente con varios núcleos más rápidos que implementen RVA22 y RVV 1.0.
    • https://milkv.io/pioneer
    • La Milk-V Pioneer viene con 128 GB de RAM.
  • ¿Esto es 86Box? Fue divertido que me hiciera recordar la época en que compré un Amstrad PC1512.
    Se volvió mucho más divertido después de agregar 2 tarjetas de disco duro de 500 MB y una expansión de memoria de 128 KB para llegar a 640 KB. Al principio solo tenía 2 disqueteras de 360 KB, y unos años después le agregué una tarjeta de disco duro de 32 MB. También tenía Borland TurboPascal y Zortech C. Buenos tiempos.

    • No, esto es Box64, un proyecto completamente distinto.
      Igual sí recuerdo la época de usar un Amstrad PC1512.
  • Me pregunto si algún día aparecerá un sistema con unos cuantos CPUs RISC-V grandes y una “GPU” implementada como un montón de CPUs RISC-V pequeños.
    Sería algo con capacidades vectoriales adecuadas; y como pregunta derivada, también me intriga si el vector clásico podría ser útil en GPUs en lugar de packed SIMD.

  • Otro logro técnicamente impresionante con Witcher 3 fue el port para Switch, y corría realmente bien.
    Muestra cuánto se puede lograr con optimización, y cuántos recursos se desperdician en PC por mala optimización.

    • También influye mucho que usara texturas y modelos 3D de mucha menor calidad, reduciendo bastante la RAM dedicada a assets.
      No es una comparación de igual a igual, y como el rango de lo que se muestra en pantalla es muy distinto, es difícil afirmar que en PC se trate de mala optimización.
    • Si bajas la resolución de renderizado a 720p, o a 540p en modo portátil, pones la configuración incluso por debajo del mínimo y consideras que unos 30 fps son aceptables, entonces una PC con las especificaciones mínimas también puede mover Witcher 3 de forma parecida.
  • Ojalá este feedback a nivel de ISA les llegue a los de RVI

    • En el SIG de eficiencia escalar ya están discutiendo instrucciones de inserción/extracción de campos de bits
      Ayer lo verifiqué [1], y el ejemplo del texto ya se puede hacer con 4 instrucciones de RISC-V. Eso sí, no es tan fácil darse cuenta
      # a0 = rax, a1 = rbx
      slli t0, a1, 64-8
      rori a0, a0, 16
      add a0, a0, t0
      rori a0, a0, 64-16
      [1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri...
    • No hay nada nuevo entre esto
      De hecho, que falte la extracción de campos de bits es un error tan obvio que es mi ejemplo favorito de lo absurda que puede llegar a ser la ISA de RISC-V. El segundo es que no tiene un modo de direccionamiento decente
      Algunos diseños mejores de RISC-V de hecho implementan instrucciones personalizadas para esto. Por ejemplo, está BEXTM de Hazard3: https://github.com/Wren6991/Hazard3/blob/stable/doc/hazard3....