C no es un lenguaje de bajo nivel: tu computadora no es un PDP-11 rápido
C Is Not a Low-Level Language (2018)
A raíz de las vulnerabilidades Meltdown y Spectre, este artículo analiza sus causas raíz: los procesadores modernos ejecutan instrucciones especulativamente y permiten ataques de canal lateral. Estas características se añadieron para que los programadores de C siguieran creyendo que programaban en un lenguaje de bajo nivel, cuando ya no es así. El artículo explica cómo la búsqueda de alto paralelismo a nivel de instrucción y la memoria plana, pilares del modelo abstracto de C, han llevado a procesadores increíblemente complejos y a compiladores que requieren transformaciones enormes para generar código rápido, violando a menudo las reglas del lenguaje.
Las características que condujeron a estas vulnerabilidades, junto con otras varias, se añadieron para que los programadores de C siguieran creyendo que programaban en un lenguaje de bajo nivel, cuando esto no ha sido así durante décadas.
- weitendorf
Este es un punto tan pedante, en mi opinión. C es de bajo nivel porque hace muy fácil trabajar con lenguaje de máquina/ensamblador y hacer cosas como esta (ejemplo asistido por LLM a continuación):
int main() {
__m512i vecA = _mm512_setr_epi32(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
__m512i vecB = _mm512_setr_epi32(0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75);
unsigned short mask = 0;
__asm__ (
"vp2intersectd %[B], %[A], %%k2"
: "=@cck2" (mask)
: [A] "v" (vecA), [B] "v" (vecB)
: "k3"
);
printf("Intersection Mask: 0x%04X\n", mask);
return 0;
}
Esto es algo que los programadores de "bajo nivel" usan muy a menudo para aprovechar los beneficios de un lenguaje de alto nivel mientras ejercen control explícito sobre el uso de instrucciones de hardware específicas (vp2intersectd es una instrucción AVX-512 utilizada en implementaciones de algoritmos de búsqueda altamente optimizados).
Obviamente, si dependes del comportamiento implícito del compilador para optimizar tu código, ya no eres de "bajo nivel". Pero si puedes recurrir rápida y fácilmente a instrucciones a nivel de máquina para proporcionar semántica de implementación explícita, y el lenguaje de hecho hace que eso sea relativamente simple y fácil de hacer, eso ciertamente me parece de "bajo nivel".
- bee_rider
"Lenguaje de bajo nivel" es uno de esos términos como "VLSI" (integración a muy gran escala) donde lo definieron en los años 70 o algo así, por lo que la definición académica está desincronizada con lo que la mayoría de la gente esperaría.
Esto está bien, es un término técnico y esos no necesitan ser inmediatamente obvios.
Sin embargo, no me gusta el título de este artículo por esa razón. Realmente, un mejor título sería algo como "un procesador x86 moderno no es un PDP-11". El subtítulo es perfecto básicamente.
Edición: además, en mi opinión, no es justo atacar a C por esto, el problema no es realmente de bajo nivel. Un lenguaje que realmente expusiera la complejidad de la ejecución especulativa y todo eso podría ser bastante de alto nivel. Simplemente sería más difícil de leer en un editor de texto lineal, ¿verdad? Sería mejor dibujar el grafo de dependencias o algo así.
- legobmw99
He sido fanático de este artículo durante años, aunque a menudo me hace pensar que realmente no hay lenguajes verdaderamente de bajo nivel para nuestras CPUs superescalares modernas. ¿Alguien conoce alguno?
- melodyogonna
Bueno, C te da el control cuando importa, incluso si se sienten añadidos para características más modernas.
De hecho, creo que Mojo es el único lenguaje moderno que no está diseñado para pretender que cada computadora es un PDP-11. C ha tenido tanto éxito que muchos lenguajes sucesores simplemente hicieron cosas de C como algo natural.
En Mojo, todo está diseñado teniendo en cuenta la complejidad de la computadora moderna, y en cada etapa el programador tiene control total sobre los resultados. Tú decides qué se inlinea, qué se pasa en registros, qué se desenrolla, etc. El lenguaje tiene un excelente... bueno, probablemente el mejor soporte SIMD portátil que existe; todos los enteros están construidos sobre SIMD, y los enteros escalares son solo SIMD con longitud 1. Tienes control total sobre lo que se compila también debido a una poderosa programación en tiempo de compilación que es similar, pero más potente que la de Zig (en mi opinión, porque puedes proporcionar mucha más información). Mientras que C y Rust permiten ensamblador inline, Mojo va más allá al permitirte proporcionar MLIR y LLVM inline también, por lo que en situaciones que lo justifiquen, puedes decirle al compilador que compile a un intrínseco específico de LLVM. El lenguaje tampoco asume que estás compilando para ejecutarte en una sola máquina; cada computadora moderna es heterogénea por naturaleza y puede contener múltiples unidades programables, por lo que el pipeline de compilación está diseñado para permitir compilar ciertas partes del código para un objetivo y otras partes para otros objetivos... como una unidad de compilación.
- glouwbug
Quizás no entonces, pero básicamente tenemos nuestro propio sistema de plantillas pobre ahora:
#define array(T, N) struct array##T##N { T value[N]; }
void copy(array(int, 32)* x, array(int, 32)* y) {
*x = *y;
}
int main() {
array(int, 32) x;
array(int, 32) y = { 1, 2, 3, 4 };
copy(&x, &y);
}
Con (rumores de) lambdas y defer en camino, C va por el camino del WoW clásico.