- 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.DyBSTRINS.D - ARM64 usa los opcodes
UBFXyBFI
- LoongArch64 usa
- 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
- Extraer el byte menos significativo de
- En LoongArch64, esto puede implementarse de forma simple e intuitiva usando
BSTRPICK.D,ADDyBSTRINS.D - En RISC-V, para hacer lo mismo hay que combinar desplazamientos, máscaras,
AND,ORy 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], RCXse genera como una secuencia deLR.D,ADD,SC.Dy una rama condicional
- Si la dirección de
RAXno 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:RAXcon 16 bytes de memoria - Según el resultado, intercambia
RCX:RBXcon esa dirección de memoria
- Esta instrucción compara
- 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
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
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
popcountni el conteo de ceros iniciales/finales están en rv64gc base; se necesita Zbb. Además, una selección sin ramas comoa ? b : crequiere 4 o 5 instrucciones en rv64gc base, o 3 con Zicond, mientras que en x86-64 y aarch64 se puede hacer con 1 solaLos 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
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
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
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
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ó.
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.
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.
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.
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?
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.
¿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.
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.
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.
Ojalá este feedback a nivel de ISA les llegue a los de RVI
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 = rbxslli t0, a1, 64-8rori a0, a0, 16add a0, a0, t0rori a0, a0, 64-16[1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri...
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....