Plan de optimizaciones agresivas
Este documento concentra el recorrido recomendado para reducir la diferencia de rendimiento entre Bagron, C y Rust en el benchmark de redirección.
Objetivo inmediato
Eliminar el registro temporal y la copia byte a byte de la ruta de buffer:
build_record(record, value)
record -> output
debe aproximarse a:
serialize_direct(output + output_position, value)
La optimización debe conservar las comprobaciones de rango, la semántica de
unique, Region_t, Len_t y Size_t, y la observabilidad completa del
buffer fuera de la ventana medida.
Estado de la fase básica
La primera fase queda implementada en el benchmark canónico: build_record
escribe el prefijo, el entero y el salto de línea en la región final, y
serialize_int64_at se expande inline junto con su núcleo. La lectura posterior
declara unique para hacer explícita la exclusividad de la región consumida.
Las referencias C y Rust usan el mismo recorrido directo para que la comparación
no mida una copia adicional solo en Bagron.
En una ejecución local, la mediana del buffer fue de aproximadamente 320 ticks
en Bagron prod, 279 en C -O2 y 314 en Rust -O3. Son cifras orientativas,
no umbrales: WriteFile y el planificador introducen variación.
La capa reutilizable del backend también quedó incorporada: prod y high
expanden inline de Bagron antes del pipeline LLVM, promueven iteradores y
metadatos escalares cuando no escapan, internan const char[N] literal en
rodata, y reutilizan una condición while index < Len_t para el acceso a un
buffer[]. Los atributos nonnull y dereferenceable describen el contrato
de arrays fijos; noalias sigue siendo exclusivo de unique.
Orden de revisión
1. Benchmark y referencias
examples/canon/benchmark_redirect.bgexamples/canon/benchmark_redirect.cexamples/canon/benchmark_redirect.rs
Comparar siempre prod, C -O2 y Rust -O3. Repetir varias ejecuciones: los
ticks de WriteFile tienen variación externa y no deben convertirse en un
umbral absoluto.
2. Emisión de funciones y valores
Archivo: core/backend/llvm/functions/statements.py
Revisar primero:
_prepare_values: decidir qué valores son argumentos SSA y cuáles necesitan dirección._emit_local_declaration: evitar temporales cuando el valor no escapa._emit_for: conservar iteradores SSA y sus límites exclusivos._emit_while: reutilizar solo pruebas de rango dominantes parabuffer[]; retirar el hecho si el descriptor se reasigna o escapa._emit_large_zero_initialization: mantener arrays grandes direccionables sin expandir su inicialización en stores individuales.
3. Memoria, índices y copias
Archivo: core/backend/llvm/functions/expressions/memory.py
Revisar:
_load_addressy_store_address;_compile_placey la emisión de GEP;_buffer_data_and_length;- detección de regiones contiguas;
- conversión segura de ciclos de copia a
llvm.memcpy.
Un memcpy solo debe emitirse cuando el origen, destino, longitud y ausencia
de solapamiento estén demostrados. unique puede aportar noalias; un array
normal no debe recibir ese atributo por inferencia.
4. Serialización y llamadas
Archivo: core/backend/llvm/functions/expressions/calls.py
Revisar:
_compile_forwarded_expansion;_compile_array_argument;_compile_length_argument;_compile_size_argument;- llamadas con
unique,Region_ty punteros a arrays.
La serialización ideal escribe directamente en el destino y devuelve el número
de bytes escritos. No debe crear digits[32] salvo que la dirección del
temporal se escape o la API lo exija.
5. Pipeline LLVM
Archivos:
core/backend/llvm/module.pycore/backend/llvm/functions/base.py
Verificar en el IR antes y después de cada cambio:
- ausencia de llamadas a helpers marcados
inlineen rutas calientes; - ausencia de
allocapara escalares promovibles; - presencia de
llvm.memcpycuando corresponda; - conservación de
llvm.memsetpara arrays grandes; - supervivencia de
volatile; noaliassolo en parámetros demostrablementeunique.
No aplicar SROA completo sobre buffers grandes si la inicialización o la copia pueden expandirse a miles de instrucciones. El agregado debe seguir en memoria; solo sus escalares auxiliares deben promocionarse.
6. Invariantes semánticos
Archivos:
core/frontend/tools/function.pycore/frontend/tools/types/calls.pycore/frontend/objects/function.py
Confirmar que las optimizaciones no alteren:
- la expansión obligatoria de
Len_tySize_t; - la inmutabilidad de esos metadatos;
- la exigencia de
Region_tparaunique; - la prohibición de buffers superpuestos en rutas
unique; - los límites de arrays y el límite exclusivo de
for; break,continue, paso cero y prevención de desbordamiento.
Pruebas obligatorias
tests_aggressive/test_backend.pytests/test_buffer.pytests/test_functions.pytests_aggressive/test_unsafe.pytests_aggressive/test_standard.py
Añadir pruebas de IR cuando una optimización dependa de una propiedad concreta:
- el buffer grande conserva su
alloca; Len_tySize_tno crean slots enprod/high;- una copia segura genera
memcpy; - una ruta con alias posible no recibe
noalias; - un iterador direccionable conserva su almacenamiento.
Protocolo de validación
- Ejecutar
vnma run main.py examples/canon/benchmark_redirect.bg. - Ejecutar el binario Bagron varias veces.
- Compilar C con
clang -O0yclang -O2. - Compilar Rust con
rustc -C opt-level=0yrustc -C opt-level=3. - Comparar mínimo y mediana de
control,bufferyfile. - Inspeccionar el
.llgenerado y, si hace falta, el ensamblador. - Ejecutar las pruebas rápidas y después
vnma test tests_aggressive.
La meta de esta fase no es prometer una cifra fija: es que la ruta de buffer
se acerque reproduciblemente a C -O2 y Rust -O3 sin relajar la seguridad
del lenguaje.