diff --git a/recipes/firefox-pgo-profile.toml b/recipes/firefox-pgo-profile.toml new file mode 100644 index 00000000..28218e9b --- /dev/null +++ b/recipes/firefox-pgo-profile.toml @@ -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/.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" +''' diff --git a/recipes/firefox.toml b/recipes/firefox.toml index f0646b1e..076a24f3 100644 --- a/recipes/firefox.toml +++ b/recipes/firefox.toml @@ -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", ]