PGO: el perfil sellado y firefox consumiéndolo (SDD 26, unidad 3.a)

merged.profdata: 501.521 funciones, 3.734.991 bloques, 5.885.254.799 ejecuciones
registradas. Recogido corriendo firefox-instrumentado sobre el corpus de
entrenamiento de Mozilla (build/pgo: blueprint para maquetación, js-input y
sunspider para el motor JS), 35 de 36 páginas servidas.

VIVE COMO FUENTE PINEADA, NO COMO RECETA QUE LO GENERA. El perfil NO es
determinista —los contadores dependen del timing—, así que una receta que lo
produjera tendría un ArtifactHash estable sobre bytes cambiantes: firefox
consumiría cosas distintas en la misma dirección y dejaría de reproducir sin que
nada lo dijera. Es el modo de fallo del lab fuera de hash_inputs, fabricado a
propósito. Sellarlo una vez y pinearlo por sha256 hace que el insumo sea no
determinista y el build vuelva a serlo.

El blob (17.426.112 bytes, xz) está publicado en hammer/fuentes/<sha256>.tar del
Storage Box y verificado bajándolo de vuelta por el mismo camino que usa hammer.
La URL upstream no resuelve por DNS a propósito: es el patrón que el ADR 0013 ya
prueba, y como la URL nunca estuvo en hash_inputs, mover el objeto de origen no
re-hashea nada.

Dos cosas que costaron y quedan escritas:

 · strip_components = 0. hammer recorta un componente al extraer (los releases
   GNU traen un proyecto-version/ de más) y este tarball lleva el fichero en la
   raíz, así que el default se llevaba lo único que había. El error era
   «cp: cannot stat merged.profdata» y no menciona el recorte por ningún lado.

 · SIN --with-pgo-jarlog, y es una mitad que falta, no un olvido. Alpine pasa
   además un jarlog que reordena omni.ja para acelerar el ARRANQUE; lo emite el
   profileserver.py de Mozilla con su extensión Quitter, que la corrida headless
   no tiene. Hoy se gana la disposición de CÓDIGO y no el orden del omni.ja.

Y VIAJA UN ARREGLO QUE NO ES DE PGO, porque firefox se reconstruye igual: EL
ARTEFACTO NO ARRANCABA. Medido sobre el sellado: `firefox --version` moría con
«Error loading shared library libnspr4.so / Couldn't load XPCOM». mach install
deja /usr/bin/firefox como symlink pelado, el binario no trae RUNPATH y el
cierre no publica ld-musl-x86_64.path, así que las librerías que viven junto al
binario no las encuentra nadie. atuq andaba sólo porque su receta añade este
mismo lanzador; firefox, que va en las CUATRO imágenes de escritorio, no lo
tenía. Es el NEEDED colgante un escalón más allá —no falta la librería, falta el
modo de encontrarla— y por eso vigia-sonames.py no puede verlo: las librerías SÍ
están en el artefacto y las cuenta como propias.

firefox: b3:8116bdec -> b3:1d730334.
This commit is contained in:
Sergio
2026-09-07 10:04:42 +00:00
parent 53180fa56b
commit e6e4d9260d
2 changed files with 111 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
# firefox-pgo-profile — el perfil de ejecución que optimiza a firefox. UNA MEDICIÓN CONGELADA.
#
# ══ QUÉ ES ═════════════════════════════════════════════════════════════════════════════════════
# `merged.profdata`: 501.521 funciones, 3.734.991 bloques y 5.885.254.799 ejecuciones registradas,
# recogidas corriendo el `firefox-instrumentado` sobre el corpus de entrenamiento de Mozilla
# (`build/pgo`: las 4 páginas de maquetación de blueprint y los benchmarks JS de js-input/sunspider).
# `firefox` lo consume con `--enable-profile-use=cross`.
#
# ══ POR QUÉ ES UNA FUENTE PINEADA Y NO UNA RECETA QUE LO GENERA ════════════════════════════════
# **El perfil NO ES DETERMINISTA**: los contadores dependen del timing de la corrida, así que dos
# generaciones no dan los mismos bytes. Si esto fuera una receta que lo produce, su ArtifactHash
# —input-addressed— no se movería entre generaciones, y firefox consumiría bytes distintos en la
# misma dirección: el build optimizado dejaría de reproducir sin que nada lo dijera. Es el mismo
# modo de fallo que el lab fuera de `hash_inputs`, fabricado a propósito.
#
# La salida es la que el SDD 26 §3.ter ya elegía: **generar el perfil UNA vez y sellarlo**, aquí como
# fuente pineada por sha256. Así el insumo es no determinista pero el build vuelve a serlo, que es
# exactamente lo que se quiere. Regenerarlo es una decisión explícita —subir un blob nuevo y cambiar
# el sha256—, nunca un accidente.
#
# ⚠ NO TIENE UPSTREAM, Y LA URL LO DICE. El blob es nuestro: sale de nuestra corrida sobre nuestro
# binario. La URL `.invalid` no resuelve por DNS a propósito (es el patrón que el ADR 0013 ya usa y
# prueba); los bytes vienen del mirror de fuentes, que sirve por sha256. Como la URL nunca estuvo en
# `hash_inputs` (ADR 0013), mover el objeto de origen no re-hashea nada.
#
# Publicado el 2026-09-07 en `hammer/fuentes/<sha256>.tar` del Storage Box
# (`scripts/fuentes/mirror-env.sh` trae `HAMMER_MIRROR` y su clave).
# Verificado bajándolo de vuelta por el mismo camino que usa hammer: 17.426.112 bytes, sha256 ok.
#
# ⚠ SI SE RECONSTRUYE `firefox-instrumentado`, ESTE PERFIL NO CADUCA SOLO. Un perfil recogido de un
# binario que ya no se parece al que se optimiza sigue siendo válido para clang —los contadores se
# aplican por nombre de función— pero vale menos: las funciones que ya no existen se ignoran y las
# nuevas quedan sin datos. Regenerarlo es su propia unidad de trabajo, no un efecto secundario.
name = "firefox-pgo-profile"
version = "2026.09.07"
license = "MPL-2.0"
[source]
tarball = "https://no-hay-upstream.invalid/hammer/firefox-pgo-2026-09-07.tar.xz"
sha256 = "92edb9c56c87c0b489e61ed722d97b7fe5d6dc37ce48ffa3f3cf7523cc4d2dbf"
# ⚠ `0` Y NO EL DEFAULT. hammer recorta UN componente al extraer, porque los releases GNU vienen
# con un `proyecto-version/` de más. Este tarball es nuestro y lleva el fichero en la raíz, así que
# con el default se recortaba lo único que hay y el install moría en `cp: cannot stat
# merged.profdata`. El error no menciona el recorte por ningún lado.
strip_components = 0
[build]
compiler = "clang"
[build.phases]
configure = "true"
compile = "true"
install = '''
set -e
mkdir -p /out/usr/share/firefox-pgo
cp merged.profdata /out/usr/share/firefox-pgo/merged.profdata
P=/out/usr/share/firefox-pgo/merged.profdata
# ── GUARDIÁN: QUE SEA UN PERFIL, NO UN FICHERO GRANDE ────────────────────────────────────────
# «Existe» no es «sirve», y acá el modo de fallo es especialmente callado: `--enable-profile-use`
# con un profdata vacío o de otra versión no rompe el build de firefox — lo hace más lento y ya.
# O sea que un perfil malo sella en verde, reproduce, y lo único que se pierde es justo lo que se
# fue a buscar. Se le pregunta a `llvm-profdata` cuántas funciones trae.
n=$(llvm-profdata show "$P" 2>/dev/null | sed -n 's/^Total functions: //p')
case "${n:-0}" in ''|*[!0-9]*) n=0 ;; esac
test "$n" -gt 100000 || {
echo "!! el profdata declara $n funciones (esperaba >100000): perfil vacío o ilegible" >&2
llvm-profdata show "$P" 2>&1 | head -5 >&2
exit 1
}
echo "guardián: perfil válido — $n funciones, $(stat -c%s "$P") bytes"
'''
+38
View File
@@ -237,6 +237,8 @@ ac_add_options --disable-debug-symbols
ac_add_options --disable-strip
ac_add_options --disable-install-strip
ac_add_options --disable-cargo-incremental
ac_add_options --enable-profile-use=cross
ac_add_options --with-pgo-profile-path=/usr/share/firefox-pgo/merged.profdata
ac_add_options --with-wasi-sysroot=/usr/share/wasi-sysroot
ac_add_options --enable-alsa
ac_add_options --enable-pulseaudio
@@ -362,6 +364,32 @@ if [ $((n_w2c + n_rlb + n_w2n)) -eq 0 ]; then
echo " /usr/share/wasi-sysroot/lib/wasm32-wasip1/libc.a existe durante el build." >&2
exit 1
fi
# ── EL LANZADOR: SIN ESTO EL ARTEFACTO NO ARRANCA ────────────────────────────────────────────
# Medido el 2026-09-07 sobre el artefacto SELLADO: `firefox --version` moría con
#
# Error loading shared library libnspr4.so: No such file or directory
# Couldn't load XPCOM.
#
# `mach install` deja `/usr/bin/firefox` como un symlink pelado al binario, el binario NO trae
# `RUNPATH`, y el cierre no publica `/etc/ld-musl-x86_64.path`. Así que las librerías que viven
# JUNTO al binario en `/usr/lib/firefox` no las encuentra nadie. `atuq` funcionaba sólo porque su
# receta añade este mismo lanzador; `firefox`, que va en las CUATRO imágenes de escritorio, no lo
# tenía. Es la familia del NEEDED colgante un escalón más allá: no falta la librería, falta el modo
# de encontrarla — y por eso `vigia-sonames.py` no puede verlo, porque las librerías SÍ están en el
# artefacto y las cuenta como propias.
#
# El `LD_LIBRARY_PATH` es además lo que necesitan los procesos HIJOS: firefox lanza uno por pestaña.
rm -f /out/usr/bin/firefox
cat > /out/usr/bin/firefox <<'LANZA'
#!/bin/sh
LD_LIBRARY_PATH="/usr/lib/firefox${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH
exec /usr/lib/firefox/firefox "$@"
LANZA
chmod 755 /out/usr/bin/firefox
test -x /out/usr/bin/firefox || { echo "!! no se creó el lanzador" >&2; exit 1; }
echo "guardián: lanzador puesto (sin él el artefacto no arranca fuera del lab)"
echo "guardián: RLBox en el binario (w2c_=$n_w2c rlbox=$n_rlb wasm2c=$n_w2n; con RLBox apagado los tres son 0)"
'''
@@ -425,4 +453,14 @@ build = [
# los 6 segundos con «'cstring' file not found», culpando a los headers de
# WASI, que están perfectos. Es el hueco que este build encontró.
"wasi-libc", "wasi-libcxx", "wasi-compiler-rt", "wasi-sdk",
# El PERFIL de ejecución (SDD 26 §3.a). Es una medición congelada y pineada por sha256, no algo
# que se recalcule: ver la cabecera de `recipes/firefox-pgo-profile.toml` para por qué un perfil
# generado en cada build rompería la reproducibilidad en vez de darla.
#
# ⚠ SIN `--with-pgo-jarlog`, Y ES UNA MITAD QUE FALTA, NO UN OLVIDO. Alpine pasa además un
# `jarlog` que reordena `omni.ja` para acelerar el ARRANQUE. Ese fichero lo emite el
# `profileserver.py` de Mozilla usando su extensión `Quitter`, que nuestra corrida headless no
# tiene. O sea que hoy se gana la disposición de CÓDIGO y no el orden del `omni.ja`. Es su propia
# unidad de trabajo y está escrito para que nadie lo lea como «PGO completo».
"firefox-pgo-profile",
]