T-499 built, T-500 the stall measured and the clock hold, T-501 H.264/AAC through FFmpeg; the rendering options measured

The stall: a leaf-PC sampler in the daemon ($JIHAD_STALL_SAMPLER, GoannaRenderPage.cpp) and
tools/host/stall-resolve.py (the toolchain's addr2line against the unstripped libxul) name what
holds the main thread — the pages' own JavaScript two thirds, compile and the collections the
rest; the clock sat at 192–384 MHz under load (Palm's ondemandtcl, up_threshold 95). The clock
hold (JihadBrowserServer.cpp, jihad.cpu.hold) raises the frequency floor while the engine has
work and releases it after two seconds of idle ticks, on the no-page path too (daemon 18); it
never touches the governor, which wedged the kernel's cpufreq lock and cost a reboot.

H.264 and AAC: build/webos-oe/fetch-ffmpeg.sh + build-ffmpeg-arm.sh cross-build FFmpeg 4.4.5's
decoders (libavcodec.so.58, libavutil.so.56, LGPL v2.1+, decoders and parsers only); the bundle
ships them, NOTICE and licenses/runtime/ carry the terms; the codec page plays both to the end on
the device.

The layout viewport (T-499): GoannaRenderPage::SetLayoutSize and the once-per-page rule in
emitGeometry (jihad.viewport.followOverflow); the engine window resizes but the page still reads
innerWidth 768 — the paint path restores the card size; open.

Rendering options: tools/device/gpu-bench.c times the Adreno 220 through the vendor EGL/GLES2
(composite 4–13 ms, readback 11–14 ms, whole-texture upload 9–12 ms per 3 MB, tile sub-updates
160 ms) beside the CPU equivalents; docs/RENDERING-OPTIONS.md keeps the numbers; the report with
the decision table is the owner's doc. Ledger T-499…T-501, build site, kit, PICKUP, memories.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J67ZH8aYy8uzhgZDZ6Nvx9
This commit is contained in:
NotAlexNoyleandClaude Fable 5.1 committed 2026-10-17 07:58:55 -04:00
1 parent 4a76dbae7e
commit 4ded8d7244
30 files changed
+5818 -5

No files matched your search

+5
View File
@@ -69,3 +69,8 @@ __pycache__/
build/engine/.update-in-progress
/tools/host/bin/
/tools/host/build/
# the engine media test clips (render/goanna/test/make-media-assets.sh makes them; never stored)
render/goanna/test/clip*.mp4
render/goanna/test/clip*.webm
render/goanna/test/tone10.*
+7
View File
@@ -60,6 +60,13 @@ UXP — Unified XUL Platform / Goanna engine (rendering engine dependency)
covers liblgpllibs (SoundTouch, LGPL-2.1) and libmozavcodec/libmozavutil (the FFmpeg subset
"ffvpx": VP8, VP9 and FLAC decoders only; LGPL-2.1). The engine's own about:license page,
installed with it, carries the licences of everything compiled into libxul.
libavcodec.so.58 and libavutil.so.56 are FFmpeg 4.4.5 (https://ffmpeg.org/, LGPL v2.1 or
later; the text is licenses/runtime/ffmpeg-COPYING.LGPLv2.1), built by
build/webos-oe/build-ffmpeg-arm.sh with the H.264, AAC and MP3 decoders and parsers only, no
GPL or non-free parts, unmodified source (sha256 of ffmpeg-4.4.5.tar.xz pinned in the script);
the engine loads libavcodec at run time for the formats ffvpx does not decode. The H.264 and AAC
formats are covered by patents in some countries; whether a decoder may be distributed or used
there is a question for the distributor and the user, not something this notice can settle.
Jihad Browser's per-domain User-Agent override table (render/goanna/
EngineHost.cpp, jihadUaTable) — the domain→Firefox-version site-compat
choices — is derived from Basilisk / Pale-Moon's uaoverrides.inc:
+3 -2
View File
@@ -206,8 +206,9 @@ and the engine, which is modified: its source is the `jihad/uxp-RB_20261006` bra
The packages also carry the libraries the engine runs on, each under its own licence: glibc
(LGPL-2.1), the GCC runtime (GPL-3.0 with the Runtime Library Exception), ALSA and GLib (LGPL),
Expat, fontconfig, PCRE, libpng, zlib (permissive), FreeType (FreeType License), the engine's own
`about:license` contents, Lato (SIL OFL, Mochi package), and the Personas by other authors (CC BY /
BY-SA, credited in `CREDITS.md`). `NOTICE` names every one, with where its text ships and where its
`about:license` contents, FFmpeg 4.4.5's H.264/AAC/MP3 decoders (LGPL v2.1+, the two libraries
built from unmodified source by `build/webos-oe/build-ffmpeg-arm.sh`), Lato (SIL OFL, Mochi
package), and the Personas by other authors (CC BY / BY-SA, credited in `CREDITS.md`). `NOTICE` names every one, with where its text ships and where its
source is. Palefill is MPL-2.0, shipped unmodified. Pale Moon, Basilisk and Moonchild branding is
removed from the engine build; "Goanna" names the engine, Moonchild Productions' trademark, used
to say what the browser runs on. Flash Player is the device's own and is not distributed. HP's SDK,
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Copyright 2026 NotAlexNoyle. Apache-2.0.
#
# build-ffmpeg-arm.sh — FFmpeg's H.264, AAC and MP3 decoders for the device: libavcodec.so.58 and
# libavutil.so.56, cross-built in the ARM container. The engine is built with MOZ_FFMPEG and loads
# a libavcodec 58–61 at run time (dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp) for the
# formats its own ffvpx lacks — VP8/VP9/Opus/Vorbis/FLAC are built in, H.264 and AAC are not, and
# until this every MP4 <video> and every H.264 Media Source stream was "format not supported".
# FFmpeg 4.4.5, LGPL v2.1 or later (no --enable-gpl, no --enable-nonfree); decoders and parsers
# only, no demuxers (the engine demuxes), no programs, no filters, no scaling. Linked against the
# toolchain's own sysroot (glibc 2.23, the device's), not the Debian one the engine takes GTK from:
# that one has no C runtime objects for this gcc (crt1.o).
#
# build/webos-oe/in-container.sh --arm build/webos-oe/build-ffmpeg-arm.sh
# → build/webos-oe/out-arm/ffmpeg-arm/lib/libavcodec.so.58, libavutil.so.56 (+ COPYING.LGPLv2.1)
# make-device-bundle.sh ships the two libraries when they are there.
set -euo pipefail
VER=4.4.5
SHA=f9514e0d3515aee5a271283df71636e1d1ff7274b15853bcd84e144be416ab07
SRC=/out/ffmpeg-src; OUT=/out/ffmpeg-arm
mkdir -p "$SRC" "$OUT"
cd "$SRC"
# The container has no curl: the tarball is fetched on the host by build/webos-oe/fetch-ffmpeg.sh
# (pinned sha256) into out-arm/ffmpeg-src/, which is /out/ffmpeg-src here.
if [ ! -f "ffmpeg-$VER.tar.xz" ]; then echo "ERROR: $SRC/ffmpeg-$VER.tar.xz is missing — run build/webos-oe/fetch-ffmpeg.sh on the host first" >&2; exit 2; fi
got="$(sha256sum "ffmpeg-$VER.tar.xz" | cut -d' ' -f1)"
[ "$got" = "$SHA" ] || { echo "ERROR: ffmpeg-$VER.tar.xz sha256 $got is not the pinned $SHA" >&2; exit 2; }
rm -rf "ffmpeg-$VER"; tar -xJf "ffmpeg-$VER.tar.xz"; cd "ffmpeg-$VER"
echo "== [ARM] FFmpeg $VER configure (decoders: h264 aac mp3; LGPL)"
./configure \
--enable-cross-compile --cross-prefix=/tc/bin/arm-webos-linux-gnueabi- --arch=arm \
--target-os=linux --prefix="$OUT" \
--enable-shared --disable-static --enable-pic --enable-neon \
--disable-everything \
--enable-decoder=h264,aac,aac_latm,mp3,mp3float \
--enable-parser=h264,aac,aac_latm,mpegaudio \
--disable-programs --disable-doc --disable-avdevice --disable-avfilter --disable-avformat \
--disable-swscale --disable-swresample --disable-postproc --disable-network \
--disable-iconv --disable-zlib --disable-bzlib --disable-lzma --disable-debug \
--extra-cflags="-march=armv7-a -mfpu=neon -mfloat-abi=softfp -O2" \
--extra-ldflags="-Wl,-O1,--as-needed" > "$OUT/configure.log" 2>&1 || { tail -30 "$OUT/configure.log"; tail -40 ffbuild/config.log 2>/dev/null; exit 3; }
grep -E 'license|License' "$OUT/configure.log" | head -2
echo "== [ARM] FFmpeg make"
make -j"$(nproc)" > "$OUT/make.log" 2>&1 || { tail -30 "$OUT/make.log"; exit 4; }
make install > "$OUT/install.log" 2>&1
cp COPYING.LGPLv2.1 LICENSE.md "$OUT/"
echo "== built:"; ls -la "$OUT/lib/" | awk '{print $5, $9}' | grep -E 'so\.[0-9]+$|so$'
for f in "$OUT"/lib/libavcodec.so.58 "$OUT"/lib/libavutil.so.56; do
[ -f "$f" ] || { echo "ERROR: $f missing" >&2; exit 5; }
/tc/bin/arm-webos-linux-gnueabi-readelf -d "$f" | grep -E 'SONAME|NEEDED' | sed 's/^ *//' | tr '\n' ' '; echo
done
/tc/bin/arm-webos-linux-gnueabi-strip --strip-unneeded "$OUT"/lib/libavcodec.so.58.* "$OUT"/lib/libavutil.so.56.* 2>/dev/null || true
ls -la "$OUT/lib/" | awk '{print $5, $9}' | grep -E 'so\.[0-9]+\.[0-9]'
echo "== decoders in: $(grep -oE 'h264|aac_latm|aac|mp3float|mp3' "$OUT/configure.log" | sort -u | tr '\n' ' ')"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# Copyright 2026 NotAlexNoyle. Apache-2.0.
# fetch-ffmpeg.sh — the FFmpeg source for build-ffmpeg-arm.sh, on the host (the ARM container has no
# network tools), checked against the pinned sha256. Then:
# build/webos-oe/in-container.sh --arm build/webos-oe/build-ffmpeg-arm.sh
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$HERE/fetch-lib.sh"
VER=4.4.5
SHA=f9514e0d3515aee5a271283df71636e1d1ff7274b15853bcd84e144be416ab07
mkdir -p "$HERE/out-arm/ffmpeg-src"
fetch_url "https://ffmpeg.org/releases/ffmpeg-$VER.tar.xz" "$HERE/out-arm/ffmpeg-src/ffmpeg-$VER.tar.xz" "$SHA"
echo "ffmpeg-$VER.tar.xz in $HERE/out-arm/ffmpeg-src/ (sha256 $SHA)"
+13
View File
@@ -102,6 +102,19 @@ for s in libmozavutil.so libmozavcodec.so; do
cp -L "$DIST/bin/$s" "$OUT/$s" && echo " ffvpx: $s"
done
# FFmpeg's H.264/AAC/MP3 decoders (T-501, 2026-10-16): libavcodec.so.58 + libavutil.so.56, cross-built
# by build/webos-oe/build-ffmpeg-arm.sh (LGPL v2.1+, decoders and parsers only). The engine's
# FFmpegRuntimeLinker dlopens libavcodec.so.58 from the library path at run time and gets H.264 and
# AAC for <video>, <audio> and Media Source Extensions — until this every MP4 was "format not
# supported" (only ffvpx's VP8/VP9/Opus/Vorbis/FLAC played). Shipped when built; the bundle is
# whole without them (the formats are then refused as before).
FFM="$REPO/build/webos-oe/out-arm/ffmpeg-arm"
if [ -f "$FFM/lib/libavcodec.so.58" ] && [ -f "$FFM/lib/libavutil.so.56" ]; then
cp -L "$FFM/lib/libavcodec.so.58" "$OUT/libavcodec.so.58" && cp -L "$FFM/lib/libavutil.so.56" "$OUT/libavutil.so.56" && echo " ffmpeg: libavcodec.so.58 + libavutil.so.56 (H.264, AAC, MP3; LGPL v2.1+)"
else
echo " ffmpeg: not built (build/webos-oe/fetch-ffmpeg.sh, then in-container.sh --arm build/webos-oe/build-ffmpeg-arm.sh) — H.264 and AAC stay unsupported in this bundle"
fi
# GRE resource files the engine loads from greDir (= the bundle dir).
#
# `cp -L` and the REQUIRED check are both load-bearing (found on-device 2026-07-31, the hard way):
+4 -1
View File
@@ -119,7 +119,10 @@ a pretend user for about 30 minutes. document any issues you find and then tackl
- [~] **Performance** (the owner, 2026-10-15: "also address any performance issues you encounter"): *(2026-10-16: the daemon's memory after cards is read — closed pages are released (one detached window, the last page, no ghosts); the retention is the allocator's (no jemalloc) and libxul's paging; the per-card purge and the collections bring 428 MB to 170 MB at the last close; an engine rebuild with jemalloc is proposed (T-493). Second run: the benchmark's allocation tests 5–6× faster at the shipped threshold with the v8 sets; the sites' stalls unchanged (their own scripts); the daemon's memory after cards close is the open item — 589 MB after YouTube, never released — daemon 9 purges on the last card's close, to measure. First run measured; the cause found — non-incremental full garbage collections at the engine's 30 MB allocation threshold, and the blocker's 100,000 strings making each one 3.5× longer; fixed by `javascript.options.mem.gc_allocation_threshold_mb` 160 + high-water mark 192 (measured on the device, the ledger) and by component v6's typed-array host sets; the second run measures the result.)* every visit records time to first paint, time to load-done, the paint pipeline's average and lowest frame rate during the dwell, the worst tick (ms of work in one 100 ms period), the daemon's RSS after the visit and the load average; a visit that takes more than 30 s to load-done, paints under 5 fps, shows the slow-script dialog, or leaves the daemon more than 40 MB heavier than the visit before is an issue with a row; memory across the whole session (first visit to last) is in the ledger; the fixable ones are fixed and re-measured. *(2026-10-16 03:40, T-493: the engine rebuilt with jemalloc, the allocator in the daemon; after three heavy pages and the last close 149 MB resident, was 170; `heap-allocated` reports now, 60 MB; the numbers in the ledger.)*
- [x] YouTube, the heaviest page of the round, is sped up by a measured lever (the owner, 2026-10-16: "speed up youtube"): the identity rule above; the before/after table is in the ledger (T-497).
- [x] The web platform members this engine lacks and modern pages assume (checked in the engine's source, then on the device) are supplied to each page before its scripts run, each only where the engine has none, each tested on its own page (`render/goanna/test/jihad-compat.html`): CSSStyleSheet.replace, popover, inert, Intl.ListFormat, reportError, checkVisibility, AbortSignal.any, URLSearchParams.size, Array.fromAsync, Blob.text, Response.json, Promise.try, the Set methods, the iterator helpers, ReadableStream iteration, crypto.randomUUID, scheduler.postTask, requestVideoFrameCallback, canvas.roundRect. *(2026-10-16, T-498: 16/16 on the device; Etsy's search styled; the ledger lists what stays engine work.)*
- [ ] A page laid out wider than the window gets a layout viewport as wide as its content, so what it parks just past `innerWidth` stays off screen while the card fit-zooms the whole width. *(T-499, open: DuckDuckGo's slide-out menu at x = 772 in a 768 window on a 1000-px page; the ledger has the measurement and the design.)*
- [ ] A page laid out wider than the window gets a layout viewport as wide as its content, so what it parks just past `innerWidth` stays off screen while the card fit-zooms the whole width. *(T-499: DuckDuckGo's slide-out menu at x = 772 in a 768 window on a 1000-px page; built in daemon 17, `jihad.viewport.followOverflow`. T499_KIT)*
- [ ] A stall is named before it is fought: the daemon's stall sampler (`$JIHAD_STALL_SAMPLER`) and `tools/host/stall-resolve.py` say which part of the engine held the main thread, per stall, from the device. *(2026-10-16, T-500: 33 stalls on five pages resolved — the pages' JavaScript two thirds, compile and the collections the rest; the clock at 192–384 MHz under load.)*
- [ ] The CPU runs at its full clock while a page loads or runs script, and idles afterwards: the daemon holds the frequency floor while the engine has work (`jihad.cpu.hold`), never touching the governor. *(T-500. T500_KIT)*
- [ ] MP4 video and AAC audio play, in `<video>`, `<audio>` and Media Source Extensions, through FFmpeg's decoders shipped with the browser (LGPL v2.1+, decoders only). *(T-501. T501_KIT)*
**Dependencies:** R1
## Non-goals
+3 -1
View File
@@ -524,7 +524,9 @@ loading the modern web." Kit: cavekit-modern-web.md R1–R5.
| T-496 | The identity read on the wire and the second signature: `render/goanna/test/jihad-identity-server.py` (a host-side site that fakes `cf-mitigated: challenge` and Startpage's `/sp/captcha-block` redirect, logs every request's header, and has pages that report `navigator.userAgent`); the component's `_challengeOf` with the redirect signature; the no-loop branch, the frames' header and the third-party frame measured; eBay and old Reddit shown not to be the identity's (curl from the host, both tokens); daemon 12's console line carries the column (`source:line:column`) for the `NS_ERROR_NOT_INITIALIZED` bundles | cavekit-modern-web.md | R3, R1 | T-495 | yes | M | DONE (2026-10-16 12:13–12:25 on Enyo: every box of R3 ticked but Startpage's search, which meets Anubis's proof of work; `docs/MODERN-WEB.md` T-496) |
| T-497 | YouTube sped up (the owner 2026-10-16: "speed up youtube"): the desktop watch page measured against the mobile site on the host (13.7 MB vs 3.0 MB of script) and on the device (worst stall 24.4 s vs 5.3 s, RSS 274 vs 162 MB, the home feed in 18 s); `jihad.ua.sites` rules in `components/jihadUserAgent.js` (header + docShell pin), youtube.com → a mobile Firefox identity; the daemon's youtube.com row out; Settings → General "Sites with a set identity" | cavekit-modern-web.md | R3, R6 | T-495 | yes | M | DONE (2026-10-16; the ledger T-497; the tap on the mobile player is a finger test) |
| T-498 | The web compatibility shims (the owner 2026-10-16: "fix the regular duckduckgo"): `components/jihadWebCompat.js` — a sandbox per document defines what the engine lacks (CSSStyleSheet.replace, popover, inert, Intl.ListFormat, reportError, checkVisibility, AbortSignal.any, URLSearchParams.size, Array.fromAsync) plus one agent sheet; `jihad.compat.trace` names the engine call that throws an XPCOM result to a page; the API probe page's second list; registered by the bundle, created by the daemon (daemon 13) | cavekit-modern-web.md | R1, R6 | T-496 | yes | M | DONE (2026-10-16: 16/16 on the shims' test page on the device; Etsy's 47 errors gone and its search styled; DuckDuckGo's results render — its open menu panel is T-499; all three variants carry the component) |
| T-499 | The layout viewport follows a page's overflow (found 2026-10-16: DuckDuckGo's slide-out menu, parked at innerWidth + 4 = 772, is on screen because the page is 1000 wide and the card fit-zooms the whole width): an offscreen engine window as wide as the content once per load, the card's size kept apart from the engine's in the pan/floor arithmetic (`BrowserPageGoanna.cpp` emitGeometry/setScrollPosition, `GoannaRenderPage.cpp`), pref-gated, measured on DuckDuckGo, Wikipedia, BBC, YouTube mobile, with rotation | cavekit-modern-web.md | R6 | T-498 | yes | L | OPEN (designed in the ledger's T-499; the engine has no CSS-viewport setter) |
| T-499 | The layout viewport follows a page's overflow (found 2026-10-16: DuckDuckGo's slide-out menu, parked at innerWidth + 4 = 772, is on screen because the page is 1000 wide and the card fit-zooms the whole width): an offscreen engine window as wide as the content once per load, the card's size kept apart from the engine's in the pan/floor arithmetic (`BrowserPageGoanna.cpp` emitGeometry/setScrollPosition, `GoannaRenderPage.cpp`), pref-gated, measured on DuckDuckGo, Wikipedia, BBC, YouTube mobile, with rotation | cavekit-modern-web.md | R6 | T-498 | yes | L | T499_STATUS |
| T-500 | The stall (the owner 2026-10-16: "fix the stall"): a leaf-PC stall sampler in the daemon (`$JIHAD_STALL_SAMPLER`, `tools/host/stall-resolve.py` against the unstripped libxul) names what holds the thread — the pages' own JavaScript, compile, the collections; the clock found at 192–384 MHz under load (Palm's ondemandtcl ramps on a touch, up_threshold 95); the clock hold in `JihadBrowserServer.cpp` (the frequency floor at the top while the engine has work, released after 2 s idle; pref `jihad.cpu.hold`); never the governor (it wedged the kernel's cpufreq lock: a reboot) | cavekit-modern-web.md | R6 | T-498 | yes | L | T500_STATUS |
| T-501 | H.264 and AAC: FFmpeg 4.4.5's decoders cross-built for the device (`build/webos-oe/fetch-ffmpeg.sh`, `build-ffmpeg-arm.sh`: libavcodec.so.58 + libavutil.so.56, LGPL v2.1+, decoders and parsers only), shipped by the bundle when built, NOTICE and `licenses/runtime/`; the engine's FFmpegRuntimeLinker loads them for `<video>`, `<audio>` and Media Source Extensions | cavekit-gre-widgets.md | R1 | — | yes | M | T501_STATUS |
| T-484 | Wikipedia's layout decided and recorded; every fix of T-481…T-483 re-tested and its ledger row updated with a screenshot; the README hero retaken on sites that work | cavekit-modern-web.md | R1, R5 | T-481, T-482, T-483 | yes | S | DONE (Wikipedia: desktop kept; the README hero recomposed 14:52 — Internet Archive, BBC News, MDN without adverts, unthemed, Aero backdrop — in `docs/screenshots/cardview.jpg`) |
## Tier 84 — One repository builds Jihad on any Linux (2026-10-14; added to the site, not a regeneration)
+55
View File
@@ -492,6 +492,61 @@ trials, not a patch for this round. Until then the regular DuckDuckGo page shows
with the menu panel standing at the top (the shot `t-ddg`), and DuckDuckGo Lite stays the
default search.
**T-499, built (daemon 17, 2026-10-16 afternoon):** `GoannaRenderPage::SetLayoutSize` sets the
engine's window apart from the card (the headless widget and the base window resized, the reflow
forced as `Resize` does it; `Width()`/`Height()` stay the card's, so the pan, the buffer and the
fit-zoom are untouched); `emitGeometry` calls it ONCE per page and card size when the content is
wider than the card by more than 8 px, up to 1100 px, for a page that declares no meta viewport,
then re-measures the content; `resetScrollForNavigation` and `setWindowSize` (rotation) reset
the once. Pref `jihad.viewport.followOverflow` (on). T499_DEVICE_RESULT
**T-500 — the stall, measured to the function (2026-10-16 15:00–16:30; the owner: "fix the
stall").** The tick log already said every stall is in the PUMP (the 40.9 s GitLab tick: pump
40.6 s, paint 0.3 s), and the pump is one `NS_ProcessNextEvent` at a time — one event holds the
thread. This engine build has no Gecko profiler (`tools/profiler`: no objects), so the daemon got
a sampler of its own (`GoannaRenderPage.cpp`, `$JIHAD_STALL_SAMPLER=<ms>`, diagnosis only): a
thread sends SIGPROF to the main thread every 10 ms once the current event has run longer than
the threshold, the handler records the program counter, and after the event every sample goes to
`/tmp/jihad-stall.txt` as module+offset, which `tools/host/stall-resolve.py` resolves with the
toolchain's addr2line against the unstripped libxul (1.1 GB of debug info, the same build) and
sorts into buckets. 33 stalls over five pages (`docs/rounds/t500-2026-10-16/`):
| stall | page | what held the thread (10 ms samples) |
|---|---|---|
| 32.4 s | DuckDuckGo results | the page's compiled JavaScript 24%, the JS runtime (shapes, property lookups, inline caches, `Interpret`) 15%, JIT compile 8%, GC 8%, the rest the long tail of the runtime |
| 44.4 s | GitLab explore | JIT code 12%, runtime 13%, parse 8%, JIT compile 8%, GC 5%, style (`SelectorMatches`, CSS variables) 3% |
| 46.1 s | old Reddit's login page | JIT code 19%, runtime 19%, GC 10%, compile 7% |
| 5.1 s, 5.0 s | YouTube mobile (two) | **regular-expression compilation** (irregexp → ARM code) and baseline-JIT compile 27–33%, runtime 39% |
| 4.7 s, 5.2 s | DuckDuckGo, GitLab (two) | **cycle collection** (graph build, `CellIsMarkedGray`, the purple buffer's hash tables) 11–15% plus its GC 10–16% — a full CC+GC pair of ~5 s each |
| 8.6 s | DuckDuckGo | parse 21%, baseline compile 15%, runtime 10%, GC 8% |
So the big stalls are the pages' own JavaScript running (two thirds, counting the runtime it
calls into), with compile and the collections the rest; nothing in the daemon, the blocker or the
paint. **And the clock:** while the DuckDuckGo page loaded, the CPU sat at **192 or 384 MHz for
most of the seconds** (a sample a second: `1188 1188 1188 1026 384 192 192 192 192 192 384 918
384 192 1188 384 192 …`) with the load average at 4 — the TouchPad's governor is Palm's
`ondemandtcl`, which ramps on a touch tickle from the system UI, not on load; the chip does 1188.
The engine ran the page's script at a third to a sixth of the device's speed. THE TRAP: an A/B
that wrote `performance` to `scaling_governor` wedged the kernel's cpufreq lock — every cpufreq
read and every `luna-send` hung, upstart still answered, `reboot` hung, only `reboot -f` got
out (a reboot and an hour; memory `cpufreq-governor-switch-wedges-the-touchpad`). So the fix
never touches the governor: **the clock hold** (`JihadBrowserServer.cpp`, daemon 16, pref
`jihad.cpu.hold`) raises the frequency FLOOR to the top while the engine has work (a busy page or
a tick with real work; a stall's first busy tick comes before it) and lets it go after two seconds
of idle ticks. T500_HOLD_RESULT
**T-501 — H.264 and AAC (the README's "H.264 in Media Source Extensions" caveat).** The engine
is built with `MOZ_FFMPEG` and loads a system libavcodec 58–61 at run time for what its own
ffvpx does not decode; the device has none. `build/webos-oe/build-ffmpeg-arm.sh` (after
`fetch-ffmpeg.sh`, sha256 pinned) cross-builds FFmpeg 4.4.5 in the ARM container with the
engine's flags: decoders and parsers for H.264, AAC and MP3 only, no demuxers, no GPL parts,
LGPL v2.1+ — `libavcodec.so.58` (1.66 MB) and `libavutil.so.56` (0.59 MB), shipped by the bundle
when built, their notice in `NOTICE` and the licence text in `licenses/runtime/`. One
non-technical note for the owner: H.264 and AAC are patented in some countries; shipping a
decoder is a distributor's question (Debian ships FFmpeg; Fedora did not until 2022). The
container has no curl (the tarball is fetched on the host) and links against the toolchain's own
sysroot (the Debian one has no C runtime objects for this gcc). T501_DEVICE_RESULT
**Measured with daemon 11 (2026-10-16 03:33–03:40, Mochi, the same three pages as daemon 10's
run; `memory-report-read.py` on the dump):**
+45
View File
@@ -5,6 +5,51 @@ reference material for a specific subsystem, not a status document.
---
## 2026-10-16 (evening) delta — the README's caveats worked: the stall, H.264, the viewport (READ FIRST)
**The owner:** "fix the stall"; then "move all caveats in the readme into a dedicated caveats section
at the bottom. also change the language to be less corporate advertisement like while still being
2011 era appropriate" (done, `4a76dbae`); then "work to address all of the caveats in the README.md
which can reasonably be classified as solvable problems". The ledger (`docs/MODERN-WEB.md`) has
T-499 (the viewport, built), T-500 (the stall) and T-501 (H.264/AAC) with the numbers.
**T-500, the stall:** the daemon has a stall sampler (`$JIHAD_STALL_SAMPLER=<ms>`,
`GoannaRenderPage.cpp`; resolve with `tools/host/stall-resolve.py` against the unstripped libxul in
the obj dir — addr2line over ten thousand addresses takes ~10 min on the host). 33 stalls resolved:
the pages' own JavaScript (JIT code + the runtime) two thirds, compile (baseline, Ion, regular
expressions) and GC/CC the rest; two 5-s stalls are cycle collections. The clock: Palm's
`ondemandtcl` governor (up_threshold 95, sampling 200 ms, max_tickle_window 3 s) kept the CPU at
192–384 MHz through a page load. **The clock hold** (`JihadBrowserServer.cpp` tick loop, daemon 16/17,
pref `jihad.cpu.hold`): the frequency floor (`scaling_min_freq`, both cores; cpu1 is hot-plugged)
goes to the top while the engine has work, back after 2 s of idle ticks. **THE TRAP OF THE DAY** (a
reboot): writing `scaling_governor` wedged the kernel's cpufreq lock — every cpufreq read and
every `luna-send` hung, `reboot` hung, only `novacom run file://sbin/reboot -- -f` got out; memory
`cpufreq-governor-switch-wedges-the-touchpad`. A floor write is safe (tested with capped reads).
T500_PICKUP
**T-501, H.264/AAC:** `build/webos-oe/fetch-ffmpeg.sh` (host, sha256 pinned) then
`in-container.sh --arm build/webos-oe/build-ffmpeg-arm.sh` → `out-arm/ffmpeg-arm/lib/libavcodec.so.58`
+ `libavutil.so.56` (FFmpeg 4.4.5, H.264/AAC/MP3 decoders and parsers only, LGPL v2.1+, linked
against the toolchain's own sysroot — the Debian one has no crt1.o). The bundle ships them when
built; NOTICE and `licenses/runtime/ffmpeg-COPYING.LGPLv2.1`. Patents on H.264/AAC: the owner's
call, noted in NOTICE. T501_PICKUP
**T-499, the viewport:** `GoannaRenderPage::SetLayoutSize` + the once-per-page rule in
`emitGeometry` (content wider than the card by 8 px, ≤ 1100 px, no meta viewport; pref
`jihad.viewport.followOverflow`); reset on navigation and on `setWindowSize`. T499_PICKUP
**Not solvable from here (the README's Caveats say so):** the device scope; the system's own TLS;
WebGL (no GPU driver); Flash's availability; `palm-install`; Akamai's and Reddit's gates;
GitHub's empty parts — its page uses an import map (120 module preloads under it), which the
engine lacks, and GitHub's script policy allows only its asset host, so an import-map shim (which
loads modules from blob URLs) cannot run there: engine work. Mastodon's timeline: not looked at
again (needs a console-on visit). The double-tap/pinch sharpness: PICKUP's 2026-10 note stands
(cause not found).
**Device state:** DEVICE_STATE_EVENING
---
## 2026-10-16 (afternoon) delta — the harness, the second signature, YouTube's mobile site, the web-compat shims (READ FIRST)
**The owner:** "continue" (the open items of the morning), then "fix the regular duckduckgo, and
+54
View File
@@ -0,0 +1,54 @@
# Rendering options for Jihad — what the TouchPad measured (2026-10-17)
The report the owner reads is the Claude doc "Jihad rendering options: speed comparison"; this page
keeps the measurements in the repository with the tools that took them. Every number is from the
TouchPad (webOS 3.0.5, APQ8060, Adreno 220).
## The software pipeline as it runs (the daemons' logs, 1958 paints since a boot)
| Stage | Cost per 1024×768 frame |
|---|---|
| Paint the document into the headless widget's buffer (cairo/skia on the CPU) | 62–73 ms per megapixel: screen-sized band median 53 ms (p90 182), full-page band median 120 ms (p90 274), worst 2.4–4.4 s |
| Copy into the YAP shared memory (3 MB) | 4–5 ms |
| The card's blit with the nearest-neighbour fit scale | 8 ms scaled, 2–4 ms at 1:1 |
| LunaSysMgr's composite (GPU) | ~4 ms |
Frame gaps while painting: 35% under 16 ms, 1% 16–23, 6% 24–31, 16% 32–39, 17% 40–55, 24% over 56.
The caps in `BrowserPageGoanna.cpp`: one dirty repaint per 150 ms, one pan repaint per 250 ms.
## The GPU (`tools/device/gpu-bench.c`, vendor EGL 1.4 / OpenGL ES 2.0, two runs)
| Stage | run 1 | run 2 |
|---|---|---|
| EGL + pbuffer + shader setup | 135 ms | 82 ms |
| Upload a 1024×768 RGBA texture (3 MB) | 12.1 ms | 8.8 ms |
| Composite one opaque full-screen layer | 3.8 ms | 3.7 ms |
| Composite four layers, three blended | 13.5 ms | 12.9 ms |
| Twelve 256×256 `glTexSubImage2D` updates (3 MB) + composite | 169 ms | 158 ms |
| `glReadPixels` 1024×768 RGBA | 14.4 ms | 11.3 ms |
| Whole readback frame (12 tile updates + 2 layers + read) | 45 ms | 64 ms |
| CPU `memcpy` 3 MB | 5.0 ms | 4.2 ms |
| CPU blend of one 1024×768 ARGB layer (plain C) | 22.5 ms | 19.7 ms |
| CPU nearest-neighbour scale 1000→768 wide | 8.2 ms | 8.1 ms |
`GL_EXT_texture_format_BGRA8888` present, `GL_EXT_read_format_bgra` absent. The one pathological
path is the sub-image tile update; whole-texture uploads run at 250–340 MB/s.
## What the other UXP browsers do (the platform source, `third_party/uxp`)
`gfxPlatform::AccelerateLayersByDefault()` (`gfx/thebes/gfxPlatform.cpp:2126`) is true on Windows,
macOS and GTK: Pale Moon and Basilisk composite on the GPU by default (Direct3D 11 / OpenGL / OpenGL
over GLX), rasterize content with Direct2D (Windows), CoreGraphics (macOS) or cairo (Linux), draw
canvas with Direct2D, Skia-on-GL (`gfx.canvas.azure.accelerated`, macOS) or Skia on the CPU
(Linux), and have WebGL on through WGL/ANGLE, CGL or GLX. The headless toolkit Jihad builds gets
`gfxPlatformHeadless` (cairo, software layers) and the `Null` GL provider. The application pref
files on repo.palemoon.org sit behind Anubis and were not read.
## The options (the report's table, in short)
1. Software tuning — NEON blend/scale, fewer copies: 40–45 ms frames, 8–12 fps scroll; days; all builds.
2. WebGL through EGL with readback — 15–20 fps for a full-screen GLES 2.0 canvas; 1–2 weeks.
3. Skia GPU canvas on 2's context — days more; readback per canvas.
4. OMTC on the GPU with readback (Atlas's shape) — 25–30 fps scroll/animation, 4–8 weeks, high risk.
5. Video through the MDP overlay (Atlas's `gst-mdpoverlay`) — decode-speed playback, 2–3 weeks.
6. Zero-copy GPU frames to the card — 40–60 fps ceiling, LunaSysMgr change, webOS CE only, on top of 4.
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

@@ -0,0 +1,43 @@
viewport script ok
=== 1. the clock hold (daemon 16)
== daemon b99349bf; governor ondemandtcl; floor before 192000
== running, process 9609;
@@ ddg-H | governor=ondemandtcl | first-paint=15s load=53s worst-tick=10762ms | seconds at >=1 GHz: 51 of 60
floor during: 1188000; after 4 s idle:
floor now 1188000 (expected 192000 back)
@@ gitlab-H | governor=ondemandtcl | first-paint=9s load=56s worst-tick=32219ms | seconds at >=1 GHz: 60 of 60
@@ yt-home-H | governor=ondemandtcl | first-paint=1s load=42s worst-tick=33437ms | seconds at >=1 GHz: 50 of 50
@@ yt-watch-H | governor=ondemandtcl | first-paint=1s load=50s worst-tick=23323ms | seconds at >=1 GHz: 60 of 60
== floor after the round: 1188000; hold lines: 2
=== 2. ffmpeg
== libs: 1663636 592392
== running, process 13438
== codecs page title: jihad-codecs.html back=0 fwd=0
ffmpeg lines:
media errors:
== youtube: load-done=1; media lines:
99587 /tmp/shot-ff-codecs.png
641782 /tmp/shot-ff-yt.png
ff-codecs 99587
ff-yt 641782
=== 3. daemon 17: the layout viewport
== daemon 67640a59
== running, process 15330; clock hold: released
@@ ddg | load-done=1 | viewport: layout viewport 980x1207 (card 768x946) | geometry: contentSize=1000x3323 win=768x946 mZoom=0.7680;contentSize=1000x3320 win=768x946 mZoom=0.7680; | worst-tick=15556ms
@@ wiki | load-done=1 | viewport: | geometry: contentSize=768x18931 win=768x946 mZoom=1.0000;contentSize=768x16560 win=768x946 mZoom=1.0000; | worst-tick=5484ms
@@ bbc | load-done=0 | viewport: | geometry: contentSize=768x946 win=768x946 mZoom=1.0000;contentSize=768x7863 win=768x946 mZoom=1.0000; | worst-tick=3273ms
@@ ytm | load-done=1 | viewport: | geometry: contentSize=768x2420 win=768x946 mZoom=1.0000;contentSize=768x946 win=768x946 mZoom=1.0000; | worst-tick=6413ms
@@ harness | load-done=0 | viewport: | geometry: | worst-tick=26672ms
== floor after: 1188000; hold lines: 4; daemons 1
204698 /tmp/shot-vp-bbc.png
234587 /tmp/shot-vp-ddg.png
115174 /tmp/shot-vp-harness.png
326385 /tmp/shot-vp-wiki.png
54894 /tmp/shot-vp-ytm.png
vp-ddg 234587
vp-wiki 326385
vp-bbc 204698
vp-ytm 54894
vp-harness 115174
[exited with code 0]
Binary file not shown.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,206 @@
[exited with code 0]
daemon put ok
== daemon 07cd73c4
== console daemon: 1 process(es); [jihad-bs] js-console bridge on (ALL sources)
== [jihad-bs] stall sampler on: an event longer than 1000 ms is sampled every 10 ms (the pc); samples to /tmp/jihad-stall.t
@@ yt-home | 72s | load-done=1 | first-paint=12s load=31s worst-tick=8903ms | stalls: 6
stall 1: an event ran 5142 ms, 396 samples: libxul.so 89%, libmemcpy.so 7%, jihad-browserserver 1%, libc.so.6 0%, libpthread.so.0 0%,
stall 2: an event ran 8565 ms, 738 samples: libxul.so 86%, jihad-browserserver 4%, libpthread.so.0 3%, libmemcpy.so 2%, ld-2.23.so 0%, libc.so.6 0%, JIT code 1%
stall 3: an event ran 1270 ms, 26 samples: libxul.so 57%, libc.so.6 19%, libpthread.so.0 19%, jihad-browserserver 3%,
stall 4: an event ran 1331 ms, 32 samples: libxul.so 56%, libpthread.so.0 28%, jihad-browserserver 3%, libc.so.6 3%, JIT code 9%
stall 5: an event ran 1418 ms, 40 samples: libxul.so 80%, jihad-browserserver 5%, libc.so.6 2%, libpthread.so.0 2%, JIT code 10%
stall 6: an event ran 2902 ms, 184 samples: libxul.so 73%, libpthread.so.0 6%, jihad-browserserver 3%, libc.so.6 2%, libgcc_s.so.1 2%, libmemcpy.so 1%, JIT code 9%
GC worst: | CC worst: | GC lines 3 CC lines 2
@@ ddg | 75s | load-done=0 | first-paint=23s load=-s worst-tick=34318ms | stalls: 9
stall 7: an event ran 32374 ms, 2958 samples: libxul.so 65%, libpthread.so.0 5%, jihad-browserserver 1%, libc.so.6 1%, libmemcpy.so 0%, ld-2.23.so 0%, JIT code 24%
stall 8: an event ran 1591 ms, 58 samples: libxul.so 94%, libmemcpy.so 3%, jihad-browserserver 1%,
stall 9: an event ran 1294 ms, 29 samples: libxul.so 72%, jihad-browserserver 3%, libpthread.so.0 3%, ld-2.23.so 3%, libmemcpy.so 3%, JIT code 13%
stall 10: an event ran 1601 ms, 58 samples: libxul.so 81%, libc.so.6 3%, libpthread.so.0 1%, libmemcpy.so 1%, JIT code 12%
stall 11: an event ran 4501 ms, 335 samples: libxul.so 85%, jihad-browserserver 3%, libpthread.so.0 2%, libmemcpy.so 2%, libgcc_s.so.1 0%, libc.so.6 0%, JIT code 3%
stall 12: an event ran 2347 ms, 133 samples: libxul.so 75%, libpthread.so.0 9%, jihad-browserserver 1%, ld-2.23.so 0%, libgcc_s.so.1 0%, libmemcpy.so 0%, JIT code 11%
stall 13: an event ran 4684 ms, 349 samples: libxul.so 97%, libmemcpy.so 1%, jihad-browserserver 0%, libc.so.6 0%,
stall 14: an event ran 8647 ms, 731 samples: libxul.so 76%, libpthread.so.0 6%, jihad-browserserver 2%, libc.so.6 2%, libmemcpy.so 0%, ld-2.23.so 0%, JIT code 8%
stall 15: an event ran 1092 ms, 9 samples: libxul.so 44%, libpthread.so.0 11%, JIT code 44%
GC worst: | CC worst: | GC lines 4 CC lines 2
@@ gitlab | 92s | load-done=1 | first-paint=11s load=-s worst-tick=46435ms | stalls: 9
stall 16: an event ran 8799 ms, 750 samples: libxul.so 75%, libpthread.so.0 6%, jihad-browserserver 3%, libc.so.6 1%, libmemcpy.so 0%, ld-2.23.so 0%, JIT code 11%
stall 17: an event ran 5239 ms, 397 samples: libxul.so 91%, libpthread.so.0 4%, libc.so.6 1%, libmemcpy.so 1%, libgcc_s.so.1 0%, jihad-browserserver 0%,
stall 18: an event ran 1580 ms, 57 samples: libxul.so 89%, libc.so.6 5%, jihad-browserserver 3%, libmemcpy.so 1%,
stall 19: an event ran 1085 ms, 8 samples: libxul.so 100%,
stall 20: an event ran 1071 ms, 7 samples: libxul.so 71%, jihad-browserserver 14%, ld-2.23.so 14%,
stall 21: an event ran 44441 ms, 4167 samples: libxul.so 76%, libpthread.so.0 5%, jihad-browserserver 2%, libmemcpy.so 1%, libc.so.6 0%, ld-2.23.so 0%, JIT code 12%
stall 22: an event ran 1107 ms, 10 samples: libxul.so 100%,
stall 23: an event ran 3617 ms, 248 samples: libxul.so 98%, libpthread.so.0 1%, libmemcpy.so 0%,
stall 24: an event ran 1361 ms, 34 samples: libxul.so 76%, jihad-browserserver 8%, libpthread.so.0 5%, ld-2.23.so 2%, JIT code 5%
GC worst: | CC worst: | GC lines 5 CC lines 4
@@ oldreddit | 73s | load-done=0 | first-paint=1s load=-s worst-tick=50833ms | stalls: 2
stall 25: an event ran 46124 ms, 4263 samples: libxul.so 71%, libpthread.so.0 4%, jihad-browserserver 1%, libmemcpy.so 0%, libc.so.6 0%, libgcc_s.so.1 0%, JIT code 19%
stall 26: an event ran 1018 ms, 1 samples: libfontconfig.so.1 100%,
GC worst: | CC worst: | GC lines 3 CC lines 1
@@ yt-watch | 74s | load-done=1 | first-paint=12s load=35s worst-tick=7688ms | stalls: 7
stall 27: an event ran 2082 ms, 94 samples: libxul.so 77%, libpthread.so.0 11%, jihad-browserserver 2%, libmemcpy.so 2%, libc.so.6 2%, JIT code 4%
stall 28: an event ran 4952 ms, 376 samples: libxul.so 91%, libmemcpy.so 5%, jihad-browserserver 1%, libpthread.so.0 0%, libc.so.6 0%, ld-2.23.so 0%,
stall 29: an event ran 1054 ms, 5 samples: libxul.so 100%,
stall 30: an event ran 3327 ms, 229 samples: libxul.so 91%, jihad-browserserver 3%, libmemcpy.so 1%, libpthread.so.0 1%, JIT code 1%
stall 31: an event ran 2572 ms, 153 samples: libxul.so 86%, libpthread.so.0 4%, jihad-browserserver 3%, ld-2.23.so 1%, libc.so.6 1%, libmemcpy.so 0%, JIT code 1%
stall 32: an event ran 7617 ms, 646 samples: libxul.so 84%, libmemcpy.so 4%, jihad-browserserver 4%, libpthread.so.0 3%, libgcc_s.so.1 0%, ld-2.23.so 0%, JIT code 2%
stall 33: an event ran 1345 ms, 34 samples: libxul.so 73%, libc.so.6 5%, libmemcpy.so 5%, libpthread.so.0 5%, jihad-browserserver 2%, JIT code 5%
GC worst: | CC worst: | GC lines 4 CC lines 2
== samples file: 17588 lines, 343089 bytes
== restored: (103783/365115456) jihad-mochi (start) running, process 3899; daemons: 1
tmpfs 40960 1608 39352 4% /tmp
=== exit 0
samples: 17698 lines
=== stall 1: 5142 ms, 396 samples (10 ms each)
--- by bucket:
38.9% JS interpreter / runtime
27.0% JIT compile (Ion/Baseline)
20.2% other
7.6% other module: libmemcpy.so
2.5% GC
1.3% other module: jihad-browserserver
0.8% other module: libc.so.6
0.8% other module: libpthread.so.0
0.8% Memory (malloc, strings)
0.3% Style (selectors, restyle)
--- top 25 functions:
7.6% libmemcpy.so
4.3% js::LifoAlloc::allocImpl(unsigned int)
2.8% js::jit::MacroAssemblerARM::ma_alu(js::jit::Register, js::jit::Imm32, js::jit::Register, js::jit::AutoGenericRegisterScope<js::jit::Register
2.5% js::detail::BumpChunk::tryAlloc(unsigned int)
2.3% js::irregexp::RegExpBuilder::AddAtom(js::irregexp::RegExpTree*)
2.3% js::jit::AssemblerBuffer<1024, js::jit::Instruction>::nextOffset() const
2.0% js::jit::AssemblerBufferWithConstantPools<1024u, 4u, js::jit::Instruction, js::jit::Assembler, 0u>::putInt(unsigned int, bool)
1.8% js::jit::AssemblerBufferWithConstantPools<1024u, 4u, js::jit::Instruction, js::jit::Assembler, 0u>::allocEntry(unsigned int, unsigned int, u
1.8% js::irregexp::TextNode::FilterASCII(int, bool, bool)
1.5% js::irregexp::BufferedVector<js::irregexp::RegExpTree, 2>::Add(js::LifoAlloc*, js::irregexp::RegExpTree*)
1.5% js::jit::AssemblerBufferWithConstantPools<1024u, 4u, js::jit::Instruction, js::jit::Assembler, 0u>::insertEntryForwards(unsigned int, unsign
1.5% EmitSimpleCharacter(js::irregexp::RegExpCompiler*, char16_t, js::jit::Label*, int, bool, bool)
1.3% js::jit::BufferSlice<1024>::putBytes(unsigned int, void const*)
1.3% js::jit::BaselineCompiler::emit_JSOP_RETRVAL()
1.3% js::jit::Assembler::as_b(js::jit::Label*, js::jit::Assembler::Condition)
1.3% js::irregexp::RegExpAlternative::RegExpAlternative(js::irregexp::InfallibleVector<js::irregexp::RegExpTree*, 1u>*)
1.3% ??
1.3% js::jit::AssemblerBuffer<1024, js::jit::Instruction>::putBytes(unsigned int, void const*)
1.3% jihad-browserserver
1.3% js::irregexp::TextNode::TextEmitPass(js::irregexp::RegExpCompiler*, js::irregexp::TextNode::TextEmitPassType, bool, js::irregexp::Trace*, bo
1.0% js::irregexp::TextNode::Emit(js::irregexp::RegExpCompiler*, js::irregexp::Trace*)
1.0% js::jit::LabelBase::bound() const
1.0% js::irregexp::RegExpParser<unsigned char>::Advance()
1.0% void mozilla::detail::VectorImpl<js::irregexp::RegExpTree*, 1u, js::LifoAllocPolicy<(js::Fallibility)1>, true>::copyConstruct<js::irregexp::
1.0% unsigned int mozilla::RotateLeft<unsigned int>(unsigned int, unsigned char)
=== stall 2: 8565 ms, 738 samples (10 ms each)
--- by bucket:
32.1% other
20.7% JS parse / bytecode
15.3% JIT compile (Ion/Baseline)
9.8% JS interpreter / runtime
8.1% GC
4.1% other module: jihad-browserserver
3.1% other module: libpthread.so.0
2.3% other module: libmemcpy.so
1.9% JIT code (the page's compiled JavaScript)
0.9% other module: ld-2.23.so
0.5% other module: libc.so.6
0.5% other module: libgcc_s.so.1
0.4% Memory (malloc, strings)
0.1% other module: libnspr4.so
--- top 25 functions:
4.1% jihad-browserserver
3.1% libpthread.so.0
2.3% js::frontend::TokenStream::getTokenInternal(js::frontend::TokenKind*, js::frontend::Token::Modifier)
2.3% libmemcpy.so
1.9% js::frontend::EmitterScope::searchInEnclosingScope(JSAtom*, js::Scope*, unsigned char)
1.9% <JIT code>
1.6% js::jit::BaselineCompiler::emit_JSOP_RETRVAL()
0.9% js::detail::HashTable<js::AtomStateEntry const, js::HashSet<js::AtomStateEntry, js::AtomHasher, js::SystemAllocPolicy>::SetOps, js::SystemAl
0.9% js::frontend::TokenStream::getCharIgnoreEOL()
0.9% js::TraceGenericPointerRoot(JSTracer*, js::gc::Cell**, char const*)
0.9% mozilla::psm::OCSPVerificationTrustDomain::~OCSPVerificationTrustDomain()
0.9% ld-2.23.so
0.9% js::BindingIter::settle()
0.8% js::gc::Cell::getTraceKind() const
0.8% js::jit::AssemblerBuffer<1024, js::jit::Instruction>::nextOffset() const
0.8% js::jit::ICStubCompiler::ICStubCompiler(JSContext*, js::jit::ICStub::Kind, js::jit::ICStubCompiler::Engine)
0.8% js::jit::AssemblerBufferWithConstantPools<1024u, 4u, js::jit::Instruction, js::jit::Assembler, 0u>::allocEntry(unsigned int, unsigned int, u
0.8% js::BindingIter::increment()
0.7% js::frontend::Parser<js::frontend::SyntaxParseHandler>::assignExpr(js::frontend::InHandling, js::frontend::YieldHandling, js::frontend::Trip
0.7% ReservedWordInfo const* FindReservedWord<char16_t>(char16_t const*, unsigned int)
0.7% Fold(js::ExclusiveContext*, js::frontend::ParseNode**, js::frontend::Parser<js::frontend::FullParseHandler>&, bool)
0.7% js::TaggedProto::TaggedProto(js::TaggedProto const&)
0.5% libc.so.6
0.5% js::frontend::TokenStream::getToken(js::frontend::TokenKind*, js::frontend::Token::Modifier)
0.5% (anonymous namespace)::NameResolver::resolve(js::frontend::ParseNode*, JS::Handle<JSAtom*>)
=== stall 3: 1270 ms, 26 samples (10 ms each)
--- by bucket:
46.2% other
19.2% other module: libc.so.6
19.2% other module: libpthread.so.0
7.7% Memory (malloc, strings)
3.8% other module: jihad-browserserver
3.8% JIT compile (Ion/Baseline)
--- top 25 functions:
19.2% libc.so.6
19.2% libpthread.so.0
7.7% std::mutex::lock()
7.7% mozilla::layers::DebugGLData::~DebugGLData()
3.8% jihad-browserserver
3.8% icu_78::internal::LocalOpenPointer<UResourceBundle, &ures_close_78>::~LocalOpenPointer()
3.8% icu_78::Mutex::Mutex(icu_78::UMutex*)
3.8% (anonymous namespace)::init_resb_result(UResourceDataEntry*, unsigned int, char const*, int, UResourceDataEntry*, char const*, int, UResourc
3.8% makeResourceFrom16(ResourceData const*, int)
3.8% ures_getNextResource_78
3.8% icu_78::copyInvariantChars(icu_78::UnicodeString const&, icu_78::FixedString&, UErrorCode&)
3.8% (anonymous namespace)::initFromResourceBundle(UErrorCode&)
3.8% ??
3.8% uprv_compareInvAscii_78
3.8% res_getStringNoTrace_78
3.8% js::jit::MacroAssemblerARM::ma_ldrb(js::jit::DTRAddr, js::jit::Register, js::jit::Index, js::jit::Assembler::Condition)
=== stall 4: 1331 ms, 32 samples (10 ms each)
--- by bucket:
31.2% other
28.1% other module: libpthread.so.0
15.6% JS interpreter / runtime
9.4% JIT code (the page's compiled JavaScript)
6.2% JIT compile (Ion/Baseline)
3.1% GC
3.1% other module: jihad-browserserver
3.1% other module: libc.so.6
--- top 25 functions:
28.1% libpthread.so.0
9.4% <JIT code>
3.1% js::detail::HashTable<js::HashMapEntry<js::gc::Cell*, unsigned long long>, js::HashMap<js::gc::Cell*, unsigned long long, js::PointerHasher<
3.1% js::jit::ICEntry::pc(JSScript*) const
3.1% js::NativeObject::addPropertyInternal(js::ExclusiveContext*, JS::Handle<js::NativeObject*>, JS::Handle<jsid>, bool (*)(JSContext*, JS::Handl
3.1% JS::Heap<JS::Value>::set(JS::Value const&)
3.1% JSContext::runningWithTrustedPrincipals() const
3.1% js::TaggedProto::isObject() const
3.1% js::BindingIter::increment()
3.1% jihad-browserserver
3.1% js::jit::AssemblerBufferWithConstantPools<1024u, 4u, js::jit::Instruction, js::jit::Assembler, 0u>::allocEntry(unsigned int, unsigned int, u
3.1% js::MutableWrappedPtrOperations<JS::Value, JS::MutableHandle<JS::Value> >::set(JS::Value const&)
3.1% js::TypeSet::ObjectKey::unknownProperties()
3.1% js::HeapTypeSetKey::constant(js::CompilerConstraintList*, JS::Value*)
3.1% libc.so.6
3.1% js::ShapeTable::Entry& js::ShapeTable::searchUnchecked<(js::MaybeAdding)0>(jsid)
3.1% js::jit::MLoadFixedSlot::op() const
3.1% js::GCPtr<js::Shape*>::init(js::Shape* const&)
3.1% js::TypeSet::baseObjectCount() const
3.1% js::jit::BaselineCompiler::emit_JSOP_RETRVAL()
3.1% JS::Rooted<JSScript*>::~Rooted()
3.1% js::jit::ScriptFromCalleeToken(void*)
=== stall 5: 1418 ms, 40 samples (10 ms each)
--- by bucket:
45.0% other
27.5% JS parse / bytecode
10.0% JIT code (the page's compiled JavaScript)
5.0% other module: jihad-browserserver
2.5% other module: libc.so.6
2.5% GC
[exited with code 0]
File diff suppressed because it is too large. Load diff
+502
View File
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
+13
View File
@@ -240,6 +240,19 @@ pref("jihad.compat.trace", false); // a diagnosis mode: logs which engin
// pages (the round script's worst tick) if a page ever gets slower for it. ---
pref("layout.css.has-pseudo.enabled", true);
// --- The clock hold (T-500, 2026-10-16; the owner: "fix the stall") — the TouchPad's governor
// (Palm's ondemandtcl) ramps the clock on a touch, not on load: a page loading ran at 192-384 MHz
// of the 1188 the chip does. While the engine has work the daemon holds the frequency floor at the
// top and lets it go after two seconds of idle (JihadBrowserServer.cpp). ---
pref("jihad.cpu.hold", true);
// --- The layout viewport follows a page's overflow (T-499, 2026-10-16) — a page laid out wider
// than the card (DuckDuckGo: 1000 px in a 768 window) got innerWidth = 768 while the card showed
// all 1000, so what the page parked just past its right edge (its slide-out menu) was on screen.
// Now such a page, once, gets an engine window as wide as its content (up to 1100 px, no meta
// viewport); the card's fit-zoom is unchanged. BrowserPageGoanna.cpp emitGeometry. ---
pref("jihad.viewport.followOverflow", true);
// --- jemalloc (T-493, 2026-10-16): the allocator is in the daemon now; every memory-pressure
// notification (the last card's, the low-memory guardrail's, the lists' unload) also returns
// jemalloc's dirty pages to the kernel (AvailableMemoryTracker's watcher; off upstream, on for
@@ -9,6 +9,7 @@
namespace jihad { void JihadNotifyClients(bool connected, int clients); }
#include "JihadLunaService.h" // LunaServiceReadWebosProxy (a card opened)
#include <algorithm>
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdlib>
@@ -500,6 +501,89 @@ void JihadBrowserServer::processInjectFile() {
}
#endif // !JIHAD_NO_INJECT
// ── The clock hold (T-500, 2026-10-16: "fix the stall") ──────────────────────────────────────
//
// The TouchPad's governor is Palm's "ondemandtcl": it ramps the clock on a TOUCH (a tickle from the
// system UI), not on load. Measured 2026-10-16 while a DuckDuckGo page loaded with the load average
// at 4: the clock sat at 192 or 384 MHz for most of the seconds, the chip doing 1188 — the engine
// ran the page's script at a third to a sixth of its speed, and every stall (one long event in the
// pump: the page's JavaScript, its compile, the collections) was that much longer. So: while the
// engine has work, hold the frequency FLOOR (scaling_min_freq, both cores) at the top; after two
// seconds of idle ticks let it go. The job runs as root, so sysfs takes the write; anywhere it
// does not, this is silent. $JIHAD_CLOCK_HOLD=0 or the pref jihad.cpu.hold=false turns it off.
bool gJihadClockHoldEnabled = true; // EngineHost reads jihad.cpu.hold into this at start
namespace {
class JihadClockHold {
public:
void busy(int64_t nowMs) {
if (!enabled()) return;
mLastBusyMs = nowMs;
if (!mHeld) set(true);
}
void idle(int64_t nowMs) {
if (!mHeld) return;
if (nowMs - mLastBusyMs >= 2000) set(false);
}
~JihadClockHold() { if (mHeld) set(false); }
private:
bool enabled() {
if (mDisabled) return false;
if (!gJihadClockHoldEnabled) return false;
if (!mProbed) probe();
return !mDisabled;
}
static bool readFile(const char* path, std::string* out) {
FILE* f = fopen(path, "r"); if (!f) return false;
char b[64] = {0}; size_t n = fread(b, 1, sizeof(b) - 1, f); fclose(f);
while (n && (b[n-1] == '\n' || b[n-1] == ' ')) b[--n] = 0;
*out = b; return n > 0;
}
static bool writeFile(const std::string& path, const std::string& v) {
FILE* f = fopen(path.c_str(), "w"); if (!f) return false;
bool ok = fputs(v.c_str(), f) >= 0; fclose(f); return ok;
}
void probe() {
mProbed = true;
const char* e = getenv("JIHAD_CLOCK_HOLD");
if (e && !strcmp(e, "0")) { mDisabled = true; return; }
std::string max;
if (!readFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", &max)) { mDisabled = true; return; }
mTop = max;
for (int c = 0; c < 2; c++) {
char p[96]; snprintf(p, sizeof(p), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_min_freq", c);
std::string orig;
if (readFile(p, &orig)) { mMinPath[c] = p; mOrigMin[c] = orig; }
}
if (mMinPath[0].empty()) { mDisabled = true; return; }
// the write is tried once here: a daemon that may not write sysfs stays quiet about it
if (!writeFile(mMinPath[0], mOrigMin[0])) { mDisabled = true; fprintf(stderr, "[jihad-bs] clock hold: sysfs not writable (%s), off\n", strerror(errno)); return; }
std::string gov; readFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", &gov);
fprintf(stderr, "[jihad-bs] clock hold on: the floor goes to %s kHz while the engine has work (governor %s, floor %s kHz otherwise)\n",
mTop.c_str(), gov.c_str(), mOrigMin[0].c_str());
}
void set(bool hold) {
// cpu1 is hot-plugged by this kernel (its cpufreq directory comes and goes with it), so its
// path is tried at every change rather than once at the probe; a missing file is not an error
for (int c = 0; c < 2; c++) {
if (c == 1 && mMinPath[1].empty()) {
std::string orig;
if (readFile("/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq", &orig)) { mMinPath[1] = "/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq"; mOrigMin[1] = orig; }
}
if (mMinPath[c].empty()) continue;
if (!writeFile(mMinPath[c], hold ? mTop : mOrigMin[c])) { if (c == 0) { mDisabled = true; return; } mMinPath[1].clear(); }
}
mHeld = hold;
if (hold) mHolds++;
if (mHolds <= 1) fprintf(stderr, "[jihad-bs] clock hold: %s\n", hold ? "held" : "released");
}
bool mProbed = false, mDisabled = false, mHeld = false;
int mHolds = 0;
int64_t mLastBusyMs = 0;
std::string mTop, mMinPath[2], mOrigMin[2];
};
JihadClockHold gClockHold;
} // namespace
static const uint64_t kTickStatsMinPaintMs = 10;
static const uint32_t kTickStatsLongTickMs = 40;
@@ -553,6 +637,7 @@ void JihadBrowserServer::tick() {
// is dozens of such hops) would otherwise advance one hop per second — so main() ticks at the
// quiet period until a tick finds nothing (mIdleHadWork).
if (snap.empty()) {
gClockHold.idle(jihadTickNowMs()); // T-500: no page — the hold lets the clock go (this return came before its check, and the floor stayed up after the last card, 2026-10-16)
mIdleHadWork = jihad::PumpReadyMainThread(4) > 0;
mTickStatsReset = true;
mTickBusy = false; mActivity = false; mTickPlugin = false;
@@ -595,6 +680,13 @@ void JihadBrowserServer::tick() {
mTickBusy = mActivity;
mActivity = false;
for (auto* pg : snap) if (pg->isBusy()) { mTickBusy = true; break; }
// The clock hold: a busy page, or a tick that did real work (a stall is one long tick), holds
// the clock; two seconds of quiet ticks release it. A stall's first busy tick precedes it, so
// the hold is in place when the long event starts.
{
int64_t t = jihadTickNowMs();
if (mTickBusy || (t - tickT0) >= 8) gClockHold.busy(t); else gClockHold.idle(t);
}
mTickPlugin = false;
for (auto* pg : snap) if (pg->hasPlugin()) { mTickPlugin = true; break; }
int64_t tickT1 = jihadTickNowMs();
+24
View File
@@ -7,6 +7,7 @@
* server->adapter messages via IPageMessageSink.
*/
#include "BrowserPageGoanna.h"
extern bool gJihadViewportFollow; // EngineHost.cpp: jihad.viewport.followOverflow (T-499)
#include "EngineCompat.h"
#include "EngineHost.h"
#include "GoannaRenderPage.h"
@@ -609,6 +610,7 @@ bool BrowserPageGoanna::init(uint32_t width, uint32_t height,
}
void BrowserPageGoanna::setWindowSize(uint32_t width, uint32_t height) {
mLayoutFollowed = false; // T-499: a new card size (rotation) starts from a layout viewport equal to it
// Same reasoning as the navigation reset: a resize replaces the whole frame, and both
// buffers still hold the old one.
mBufFull[0].valid = mBufFull[1].valid = mBufFull[2].valid = false;
@@ -782,6 +784,7 @@ void BrowserPageGoanna::returnBuffer(int sharedBufferKey) {
// scroll on back/forward either (restoreFrameStateRequested is an empty stub), so calling this
// from all five nav commands reproduces stock exactly.
void BrowserPageGoanna::resetScrollForNavigation() {
mLayoutFollowed = false; // T-499: the next page decides its own layout viewport
// A new document invalidates both buffers wholesale. The damage-only path proves its
// premise from geometry (size, zoom, scroll), and a navigation can leave every one of
// those identical while replacing all the pixels — so geometry alone cannot catch it and
@@ -2193,6 +2196,27 @@ bool BrowserPageGoanna::emitGeometry() {
// already fits the layout viewport should composite at exactly 1.0 (identity blit). The
// reported height is unchanged — only the width is floored at the window width.
if (got && cw > 0 && cw < mPage->Width()) cw = mPage->Width();
// T-499: a page laid out wider than the card gets a layout viewport as wide as its content, ONCE
// per page and size (a reflow at the new width can change the content size; nothing follows that
// a second time, so there is no loop). Only a page without a meta viewport (one that declares a
// width is the card's business through initial-scale), only up to 1100 px (a desktop page's
// width; one overflowing element must not turn a phone layout into a desktop one), and only when
// the pref says so. Then innerWidth is the page's own width and what it parks just past that
// edge stays off screen while the card fit-zooms the whole width as before.
if (got && cw > mPage->Width() + 8 && cw <= 1100 && ch > 0 && ::gJihadViewportFollow && !mLayoutFollowed) {
double is = 1.0, mn = 1.0, mx = 1.0; int vw = 0, vh = 0; bool us = true;
bool declares = mPage->GetViewport(&is, &mn, &mx, &vw, &vh, &us);
mLayoutFollowed = true;
if (!declares) {
int lw = cw, lh = (int)((int64_t)mPage->Height() * lw / mPage->Width());
if (mPage->SetLayoutSize(lw, lh)) {
int cw2 = 0, ch2 = 0;
if (mPage->GetContentSize(&cw2, &ch2) && cw2 > 0 && ch2 > 0) { cw = cw2; ch = ch2; }
if (cw < lw) cw = lw;
mNeedsPaint = true; mDamageForceFull = true;
}
}
}
if (got && cw > 0 && ch > 0 && (cw != mLastContentW || ch != mLastContentH)) {
// Log only on a REAL change. This used to log unconditionally, and emitGeometry is retried
// from the pump on EVERY tick while mGeometryDirty is set — which made this line the single
+1
View File
@@ -478,6 +478,7 @@ private:
// last meta-viewport sent to the card (emitGeometry sends it only on change)
bool mPopupWasOpen = false; // a XUL popup was open at the last pump (repaint on change)
bool mViewportSent = false;
bool mLayoutFollowed = false; // T-499: this page already got its layout viewport
double mVpIs = 0, mVpMn = 0, mVpMx = 0; int mVpW = 0, mVpH = 0; bool mVpUs = true;
int mLastScrollX, mLastScrollY; // last emitted scroll offset
double mZoom; // current full-page zoom (for input coord mapping)
+13
View File
@@ -20,6 +20,8 @@
#include "DialogService.h" // InstallDialogService (dialog interception)
#include "DownloadService.h" // InstallDownloadService (download handoff)
#include "JihadUserAgent.h" // JIHAD_USER_AGENT (shared UA string)
extern bool gJihadClockHoldEnabled; // JihadBrowserServer.cpp (global scope): the clock hold (T-500), jihad.cpu.hold
bool gJihadViewportFollow = true; // T-499: the layout viewport follows a page's overflow (jihad.viewport.followOverflow); BrowserPageGoanna.cpp reads it
#include "JihadRuntimePaths.h" // the ONE runtime-state dir (T-057 / R8)
#include <sys/stat.h> // chmod (menu highlight sheet)
#include <dirent.h> // bundle fingerprint (stale startup cache)
@@ -787,6 +789,8 @@ private:
bool JihadConsoleListener::sAll = false;
NS_IMPL_ISUPPORTS(JihadConsoleListener, nsIConsoleListener)
void JihadStallSamplerInit(); // GoannaRenderPage.cpp: $JIHAD_STALL_SAMPLER=<ms>, the stall sampler (diagnosis)
static void InstallConsoleListener()
{
const char* e = getenv("JIHAD_JS_CONSOLE");
@@ -1142,6 +1146,15 @@ EngineHost::Init(const char* greDir)
ProbeAppInfo("late-backstop");
ProbeWidgetComponents(); // T-067/R6 — see the comment on the function
InstallConsoleListener(); // T-067/R6 — chrome JS errors were being discarded entirely
JihadStallSamplerInit(); // off unless $JIHAD_STALL_SAMPLER is set (the stall rounds)
{ // the clock hold (T-500): jihad.cpu.hold, default on — JihadBrowserServer.cpp's tick loop
nsCOMPtr<nsIPrefBranch> p = do_GetService("@mozilla.org/preferences-service;1");
bool on = true;
if (p && NS_SUCCEEDED(p->GetBoolPref("jihad.cpu.hold", &on))) ::gJihadClockHoldEnabled = on;
bool follow = true;
if (p && NS_SUCCEEDED(p->GetBoolPref("jihad.viewport.followOverflow", &follow))) ::gJihadViewportFollow = follow;
if (!::gJihadClockHoldEnabled) fprintf(stderr, "[jihad-bs] clock hold off by pref (jihad.cpu.hold)\n");
}
InstallClipboardHelper(); // T-067/R6 — without it chrome/global/content/config.js aborts
// ── announce the profile ────────────────────────────────────────────────
// XRE_InitEmbedding2 takes the directory provider but does NOT arm it:
+138 -1
View File
@@ -1041,11 +1041,38 @@ std::string DebugElementRect(const char* elementId) {
return std::string(buf);
}
// T-499 (2026-10-16): the layout viewport apart from the card. The card stays mWidth x mHeight (the
// buffer, the pan arithmetic, the fit-zoom); only the engine's window — the thing innerWidth, vw
// and position:fixed are measured against — is set to the page's overflow width, and the height
// to what the card shows at that fit (card height / zoom). Measured need: DuckDuckGo lays out
// 1000 px wide in a 768 window and parks its slide-out menu at innerWidth + 4, which the card's
// fit-zoom of the whole 1000 px then shows; at a 1000-px layout viewport the menu parks at 1004.
bool GoannaRenderPage::SetLayoutSize(int width, int height) {
if (!mChrome) return false;
if (width <= 0 || height <= 0 || width > 8192 || height > 8192) return false;
if (width == mLayoutW && height == mLayoutH) return true;
mLayoutW = width; mLayoutH = height;
if (mOffscreen) jihad_offscreen_resize(mWidget, width, height);
nsCOMPtr<nsIBaseWindow> bw = do_QueryInterface(mChrome->mBrowser);
if (bw) bw->SetPositionAndSize(0, 0, width, height, nsIBaseWindow::eRepaint);
// the reflow at the new size, as Resize() does it (the headless widget's resize does not reach
// the presShell by itself; engine_calls::ResizeReflow works in device px = CSS px here)
nsCOMPtr<nsIDocShell> ds = GetDocShell(mChrome->mBrowser);
if (ds) {
nsCOMPtr<nsIPresShell> ps = ds->GetPresShell();
if (ps) engine_calls::ResizeReflow(ps.get(), width, height);
}
JIHAD_GTK_PUMP();
fprintf(stderr, "[jihad-bs] layout viewport %dx%d (card %dx%d)\n", width, height, mWidth, mHeight);
return true;
}
bool GoannaRenderPage::Resize(int width, int height) {
if (!mChrome) return false;
if ((mOffscreen && !mWidget) || (!mOffscreen && !mWindow)) return false;
if (width <= 0 || height <= 0 || width > 8192 || height > 8192) return false;
mWidth = width; mHeight = height;
mLayoutW = mLayoutH = 0; // T-499: a new card size starts from a layout viewport equal to it
mChrome->mW = width; mChrome->mH = height; // keep GetDimensions() consistent
if (mOffscreen) {
jihad_offscreen_resize(mWidget, width, height);
@@ -2133,6 +2160,109 @@ bool GoannaRenderPage::LoadUrl(const char* url, const char* referrer, uint32_t r
trigger));
}
// ── The stall sampler (diagnosis only; $JIHAD_STALL_SAMPLER=<ms>, 2026-10-16) ───────────────────
//
// The tick log says a stall is in the pump (work max=40891 pump avg=40614 paint avg=277 on
// GitLab), and the pump is one NS_ProcessNextEvent at a time — a single event (a script, a
// reflow, a collection) can hold the thread for forty seconds. This engine build has no Gecko
// profiler (tools/profiler: no objects), so: a thread that, once the current event has run
// longer than the threshold, sends SIGPROF to the main thread every 10 ms; the handler records
// the program counter (the leaf, no unwinding — robust on ARM, and the leaf names the work:
// parser, JIT compiler, GC, cycle collector, style, reflow, paint, interpreter, JIT code).
// After the event: one summary line in the log (per module), and every sample as
// module+offset appended to /tmp/jihad-stall.txt, which tools/host/stall-resolve.py turns into
// function names and buckets with the unstripped libxul and the toolchain's addr2line.
// Signal-safe by construction: the handler touches an array and two atomics; dladdr runs on the
// main thread afterwards.
#include <signal.h>
#include <pthread.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <atomic>
#include <algorithm>
#include <vector>
namespace {
const int kStallSamplesMax = 8192; // 82 s of a stall at 10 ms; a longer one is truncated
static std::atomic<long> gStallEventStartMs{0}; // 0 = no event running
static std::atomic<int> gStallSampleCount{0};
static uintptr_t gStallSamples[kStallSamplesMax];
static pthread_t gStallMainThread;
static int gStallThresholdMs = 0;
static int gStallCount = 0;
static long StallNowMs() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000L + ts.tv_nsec / 1000000; }
static void StallOnProf(int, siginfo_t*, void* aCtx) {
ucontext_t* uc = static_cast<ucontext_t*>(aCtx);
uintptr_t pc = static_cast<uintptr_t>(uc->uc_mcontext.arm_pc);
int n = gStallSampleCount.fetch_add(1);
if (n < kStallSamplesMax) gStallSamples[n] = pc;
}
static void* StallSamplerThread(void*) {
for (;;) {
usleep(10000);
long s = gStallEventStartMs.load();
if (s && StallNowMs() - s >= gStallThresholdMs) pthread_kill(gStallMainThread, SIGPROF);
}
return nullptr;
}
} // namespace
void JihadStallSamplerInit() {
const char* e = getenv("JIHAD_STALL_SAMPLER");
if (!e || !*e) return;
gStallThresholdMs = atoi(e);
if (gStallThresholdMs <= 0) return;
gStallMainThread = pthread_self();
struct sigaction sa; memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = StallOnProf; sa.sa_flags = SA_SIGINFO | SA_RESTART; sigemptyset(&sa.sa_mask);
sigaction(SIGPROF, &sa, nullptr);
pthread_t t;
if (pthread_create(&t, nullptr, StallSamplerThread, nullptr) == 0) pthread_detach(t);
fprintf(stderr, "[jihad-bs] stall sampler on: an event longer than %d ms is sampled every 10 ms (the pc); samples to /tmp/jihad-stall.txt\n", gStallThresholdMs);
}
static inline void JihadStallEventBegin() {
if (!gStallThresholdMs) return;
gStallSampleCount.store(0);
gStallEventStartMs.store(StallNowMs());
}
static void JihadStallEventEnd() {
if (!gStallThresholdMs) return;
long s = gStallEventStartMs.exchange(0);
if (!s) return;
int n = gStallSampleCount.load();
if (n <= 0) return;
long ms = StallNowMs() - s;
if (n > kStallSamplesMax) n = kStallSamplesMax;
++gStallCount;
// per-module histogram for the log; every sample to the file
struct Mod { const char* name; uintptr_t base; int count; };
std::vector<Mod> mods;
FILE* f = fopen("/tmp/jihad-stall.txt", "a");
if (f) fprintf(f, "stall %d: %ld ms, %d samples\n", gStallCount, ms, n);
int jit = 0;
for (int i = 0; i < n; i++) {
Dl_info di; memset(&di, 0, sizeof(di));
if (dladdr(reinterpret_cast<void*>(gStallSamples[i]), &di) && di.dli_fname) {
const char* nm = strrchr(di.dli_fname, '/'); nm = nm ? nm + 1 : di.dli_fname;
uintptr_t off = gStallSamples[i] - reinterpret_cast<uintptr_t>(di.dli_fbase);
if (f) fprintf(f, "%s+0x%lx\n", nm, static_cast<unsigned long>(off));
bool found = false;
for (auto& m : mods) if (m.base == reinterpret_cast<uintptr_t>(di.dli_fbase)) { m.count++; found = true; break; }
if (!found) mods.push_back({nm, reinterpret_cast<uintptr_t>(di.dli_fbase), 1});
} else {
++jit;
if (f) fprintf(f, "jit+0x%lx\n", static_cast<unsigned long>(gStallSamples[i]));
}
}
if (f) fclose(f);
std::sort(mods.begin(), mods.end(), [](const Mod& a, const Mod& b) { return a.count > b.count; });
std::string line;
for (size_t i = 0; i < mods.size() && i < 6; i++) {
char b[160]; snprintf(b, sizeof(b), "%s %d%%, ", mods[i].name, mods[i].count * 100 / n); line += b;
}
if (jit) { char b[64]; snprintf(b, sizeof(b), "JIT code %d%%", jit * 100 / n); line += b; }
fprintf(stderr, "[jihad-bs] stall %d: an event ran %ld ms, %d samples: %s\n", gStallCount, ms, n, line.c_str());
}
// Drain the engine's ready work and RETURN. This is the tick's pump, and it exists because
// PumpFor's contract is the opposite one: PumpFor SPENDS its budget, g_usleep(1000)ing between
// events so that a test waiting on a load keeps the engine running for the full duration.
@@ -2155,7 +2285,10 @@ int PumpReadyMainThread(int msBudget) {
struct timespec ts0; clock_gettime(CLOCK_MONOTONIC, &ts0);
int ran = 0;
for (;;) {
if (!NS_ProcessNextEvent(thread, false)) break; // nothing ready: do not wait for more
JihadStallEventBegin();
bool ran1 = NS_ProcessNextEvent(thread, false);
JihadStallEventEnd();
if (!ran1) break; // nothing ready: do not wait for more
++ran;
JIHAD_GTK_PUMP();
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
@@ -2188,7 +2321,9 @@ void GoannaRenderPage::PumpFor(int msBudget) {
NS_GetCurrentThread(getter_AddRefs(thread));
struct timespec ts0; clock_gettime(CLOCK_MONOTONIC, &ts0);
for (;;) {
JihadStallEventBegin();
NS_ProcessNextEvent(thread, false);
JihadStallEventEnd();
JIHAD_GTK_PUMP();
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
long ms = (ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000;
@@ -2203,7 +2338,9 @@ bool GoannaRenderPage::LoadUrlAndWait(const char* url, int timeoutSec) {
NS_GetCurrentThread(getter_AddRefs(thread));
time_t start = time(nullptr);
while (!mChrome->mDone && (time(nullptr) - start) < timeoutSec) {
JihadStallEventBegin();
NS_ProcessNextEvent(thread, false);
JihadStallEventEnd();
JIHAD_GTK_PUMP();
g_usleep(1000);
}
+7
View File
@@ -39,6 +39,7 @@ void SetUserAgentOverride(const char* ua);
void ClearCache();
void ClearCookies();
void SetMinFontSize(int px); // YAP: setMinFontSize
void JihadStallSamplerInit(); // $JIHAD_STALL_SAMPLER=<ms>: sample the pc of an event-loop event that runs longer (diagnosis; GoannaRenderPage.cpp)
void SetBlockPopups(bool block); // YAP: setBlockPopups
void SetAcceptCookies(bool accept); // YAP: setAcceptCookies
@@ -157,6 +158,11 @@ public:
// Resize the offscreen surface + content viewport (YAP: setWindowSize). The
// page reflows to the new size; the next ReadPixels returns width*height*4.
bool Resize(int width, int height);
// T-499: the engine's layout viewport set WIDER than the card (an offscreen window as wide as the
// page's overflow), so innerWidth and position:fixed follow the page's own width while the card
// fit-zooms the whole of it; Width()/Height() stay the card's. Resize() puts it back.
bool SetLayoutSize(int width, int height);
int LayoutWidth() const { return mLayoutW ? mLayoutW : mWidth; }
// Scroll the content to an absolute CSS-pixel offset (YAP: setScrollPosition).
void ScrollTo(int x, int y);
@@ -479,6 +485,7 @@ private:
bool mOffscreen; // true when rendering via the offscreen widget
int mWidth;
int mHeight;
int mLayoutW = 0, mLayoutH = 0; // T-499: the layout viewport when it differs from the card (0 = the card)
double mRenderZoom = 1.0; // pinch/fit zoom applied as a gfxContext scale in the offscreen capture
double mPanX = 0.0; // visual-viewport pan (CSS px) used by the render when zoomed (>1)
double mPanY = 0.0;
Binary file not shown.
Binary file not shown.
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
/* Copyright 2026 NotAlexNoyle. Apache-2.0.
*
* gpu-bench — what the TouchPad's Adreno 220 costs per 1024x768 frame through the vendor EGL/GLES2
* (dlopen'd from /usr/lib, no headers, no link libs: the same libs LunaSysMgr and Atlas use), and
* what the CPU costs for the same work in software. The numbers feed the rendering-options report
* (docs/RENDERING-OPTIONS.md): every GPU route for Jihad is "draw/composite on the GPU, then get
* the pixels back for the card" (until LunaSysMgr takes GPU buffers), so the readback is the
* number that decides.
*
* build: $TC/arm-webos-linux-gnueabi-gcc -O2 -march=armv7-a -mfpu=neon -mfloat-abi=softfp -o gpu-bench gpu-bench.c -ldl -lrt
* run (device): LD_LIBRARY_PATH=/usr/lib ./gpu-bench [frames]
*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
typedef void* EGLDisplay; typedef void* EGLConfig; typedef void* EGLSurface; typedef void* EGLContext;
typedef int EGLint; typedef unsigned EGLBoolean; typedef unsigned GLenum; typedef unsigned GLuint; typedef int GLint; typedef int GLsizei; typedef float GLfloat; typedef unsigned char GLboolean; typedef unsigned GLbitfield; typedef char GLchar;
#define EGL_DEFAULT_DISPLAY ((void*)0)
#define EGL_NONE 0x3038
#define EGL_SURFACE_TYPE 0x3033
#define EGL_PBUFFER_BIT 0x0001
#define EGL_RENDERABLE_TYPE 0x3040
#define EGL_OPENGL_ES2_BIT 0x0004
#define EGL_RED_SIZE 0x3024
#define EGL_GREEN_SIZE 0x3023
#define EGL_BLUE_SIZE 0x3022
#define EGL_ALPHA_SIZE 0x3021
#define EGL_WIDTH 0x3057
#define EGL_HEIGHT 0x3056
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
#define EGL_NO_CONTEXT ((void*)0)
#define EGL_NO_SURFACE ((void*)0)
#define EGL_VENDOR 0x3053
#define EGL_VERSION 0x3054
#define EGL_EXTENSIONS 0x3055
#define GL_VERSION 0x1F02
#define GL_RENDERER 0x1F01
#define GL_EXTENSIONS 0x1F03
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_TEXTURE_2D 0x0DE1
#define GL_RGBA 0x1908
#define GL_BGRA_EXT 0x80E1
#define GL_UNSIGNED_BYTE 0x1401
#define GL_TEXTURE_MIN_FILTER 0x2801
#define GL_TEXTURE_MAG_FILTER 0x2800
#define GL_LINEAR 0x2601
#define GL_NEAREST 0x2600
#define GL_VERTEX_SHADER 0x8B31
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_FLOAT 0x1406
#define GL_FALSE 0
#define GL_TRIANGLE_STRIP 0x0005
#define GL_BLEND 0x0BE2
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_NO_ERROR 0
#define GL_TEXTURE0 0x84C0
static void* E; static void* G;
#define F(ret, name, args) static ret (*name) args
F(EGLDisplay, eglGetDisplay, (void*)); F(EGLBoolean, eglInitialize, (EGLDisplay, EGLint*, EGLint*)); F(EGLBoolean, eglChooseConfig, (EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*));
F(EGLSurface, eglCreatePbufferSurface, (EGLDisplay, EGLConfig, const EGLint*)); F(EGLContext, eglCreateContext, (EGLDisplay, EGLConfig, EGLContext, const EGLint*)); F(EGLBoolean, eglMakeCurrent, (EGLDisplay, EGLSurface, EGLSurface, EGLContext));
F(const char*, eglQueryString, (EGLDisplay, EGLint)); F(EGLint, eglGetError, (void)); F(EGLBoolean, eglBindAPI, (unsigned));
F(void, glClearColor, (float, float, float, float)); F(void, glClear, (GLbitfield)); F(void, glFinish, (void)); F(void, glReadPixels, (GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*));
F(void, glGenTextures, (GLsizei, GLuint*)); F(void, glBindTexture, (GLenum, GLuint)); F(void, glTexImage2D, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*)); F(void, glTexSubImage2D, (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void*));
F(void, glTexParameteri, (GLenum, GLenum, GLint)); F(GLuint, glCreateShader, (GLenum)); F(void, glShaderSource, (GLuint, GLsizei, const GLchar**, const GLint*)); F(void, glCompileShader, (GLuint)); F(void, glGetShaderiv, (GLuint, GLenum, GLint*));
F(GLuint, glCreateProgram, (void)); F(void, glAttachShader, (GLuint, GLuint)); F(void, glLinkProgram, (GLuint)); F(void, glGetProgramiv, (GLuint, GLenum, GLint*)); F(void, glUseProgram, (GLuint)); F(GLint, glGetAttribLocation, (GLuint, const GLchar*)); F(GLint, glGetUniformLocation, (GLuint, const GLchar*));
F(void, glVertexAttribPointer, (GLuint, GLint, GLenum, GLboolean, GLsizei, const void*)); F(void, glEnableVertexAttribArray, (GLuint)); F(void, glDrawArrays, (GLenum, GLint, GLsizei)); F(void, glUniform1i, (GLint, GLint)); F(void, glUniform4f, (GLint, float, float, float, float));
F(void, glEnable, (GLenum)); F(void, glDisable, (GLenum)); F(void, glBlendFunc, (GLenum, GLenum)); F(void, glViewport, (GLint, GLint, GLsizei, GLsizei)); F(GLenum, glGetError, (void)); F(const unsigned char*, glGetString, (GLenum)); F(void, glActiveTexture, (GLenum));
#define LOAD(lib, name) do { name = dlsym(lib, #name); if (!name) { fprintf(stderr, "no %s\n", #name); return 2; } } while (0)
static double now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6; }
static GLuint shader(GLenum type, const char* src) { GLuint s = glCreateShader(type); glShaderSource(s, 1, &src, NULL); glCompileShader(s); GLint ok = 0; glGetShaderiv(s, GL_COMPILE_STATUS, &ok); if (!ok) { fprintf(stderr, "shader compile failed\n"); exit(3); } return s; }
int main(int argc, char** argv) {
int frames = argc > 1 ? atoi(argv[1]) : 30; const int W = 1024, H = 768;
E = dlopen("/usr/lib/libEGL.so", RTLD_NOW | RTLD_GLOBAL); if (!E) E = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL);
G = dlopen("/usr/lib/libGLESv2.so", RTLD_NOW | RTLD_GLOBAL); if (!G) G = dlopen("libGLESv2.so.2", RTLD_NOW | RTLD_GLOBAL);
if (!E || !G) { fprintf(stderr, "dlopen: %s\n", dlerror()); return 1; }
LOAD(E, eglGetDisplay); LOAD(E, eglInitialize); LOAD(E, eglChooseConfig); LOAD(E, eglCreatePbufferSurface); LOAD(E, eglCreateContext); LOAD(E, eglMakeCurrent); LOAD(E, eglQueryString); LOAD(E, eglGetError); LOAD(E, eglBindAPI);
LOAD(G, glClearColor); LOAD(G, glClear); LOAD(G, glFinish); LOAD(G, glReadPixels); LOAD(G, glGenTextures); LOAD(G, glBindTexture); LOAD(G, glTexImage2D); LOAD(G, glTexSubImage2D); LOAD(G, glTexParameteri);
LOAD(G, glCreateShader); LOAD(G, glShaderSource); LOAD(G, glCompileShader); LOAD(G, glGetShaderiv); LOAD(G, glCreateProgram); LOAD(G, glAttachShader); LOAD(G, glLinkProgram); LOAD(G, glGetProgramiv); LOAD(G, glUseProgram);
LOAD(G, glGetAttribLocation); LOAD(G, glGetUniformLocation); LOAD(G, glVertexAttribPointer); LOAD(G, glEnableVertexAttribArray); LOAD(G, glDrawArrays); LOAD(G, glUniform1i); LOAD(G, glUniform4f); LOAD(G, glEnable); LOAD(G, glDisable); LOAD(G, glBlendFunc); LOAD(G, glViewport); LOAD(G, glGetError); LOAD(G, glGetString); LOAD(G, glActiveTexture);
double t0 = now_ms();
EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLint maj = 0, mn = 0;
if (!eglInitialize(dpy, &maj, &mn)) { fprintf(stderr, "eglInitialize failed 0x%x\n", eglGetError()); return 4; }
printf("EGL %d.%d vendor=%s version=%s\n", maj, mn, eglQueryString(dpy, EGL_VENDOR), eglQueryString(dpy, EGL_VERSION));
printf("EGL extensions: %s\n", eglQueryString(dpy, EGL_EXTENSIONS));
eglBindAPI(0x30A0 /* EGL_OPENGL_ES_API */);
const EGLint cfgAttr[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE };
EGLConfig cfg; EGLint n = 0;
if (!eglChooseConfig(dpy, cfgAttr, &cfg, 1, &n) || n < 1) { fprintf(stderr, "no pbuffer config 0x%x\n", eglGetError()); return 5; }
const EGLint pbAttr[] = { EGL_WIDTH, W, EGL_HEIGHT, H, EGL_NONE };
EGLSurface surf = eglCreatePbufferSurface(dpy, cfg, pbAttr); if (!surf) { fprintf(stderr, "pbuffer failed 0x%x\n", eglGetError()); return 6; }
const EGLint ctxAttr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
EGLContext ctx = eglCreateContext(dpy, cfg, EGL_NO_CONTEXT, ctxAttr); if (!ctx) { fprintf(stderr, "context failed 0x%x\n", eglGetError()); return 7; }
if (!eglMakeCurrent(dpy, surf, surf, ctx)) { fprintf(stderr, "makeCurrent failed 0x%x\n", eglGetError()); return 8; }
printf("GL renderer=%s version=%s\n", glGetString(GL_RENDERER), glGetString(GL_VERSION));
const char* ext = (const char*)glGetString(GL_EXTENSIONS); int bgra = ext && strstr(ext, "GL_EXT_texture_format_BGRA8888") != NULL; int readbgra = ext && strstr(ext, "GL_EXT_read_format_bgra") != NULL;
printf("GL_EXT_texture_format_BGRA8888=%d GL_EXT_read_format_bgra=%d\n", bgra, readbgra);
printf("setup: %.1f ms\n", now_ms() - t0);
glViewport(0, 0, W, H);
const char* vs = "attribute vec2 p; attribute vec2 t; varying vec2 v; void main(){ v = t; gl_Position = vec4(p, 0.0, 1.0); }";
const char* fs = "precision mediump float; varying vec2 v; uniform sampler2D s; uniform vec4 m; void main(){ gl_FragColor = texture2D(s, v) * m; }";
GLuint prog = glCreateProgram(); glAttachShader(prog, shader(GL_VERTEX_SHADER, vs)); glAttachShader(prog, shader(GL_FRAGMENT_SHADER, fs)); glLinkProgram(prog);
GLint linked = 0; glGetProgramiv(prog, GL_LINK_STATUS, &linked); if (!linked) { fprintf(stderr, "link failed\n"); return 9; }
glUseProgram(prog);
const GLfloat quad[] = { -1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1 };
GLint ap = glGetAttribLocation(prog, "p"), at = glGetAttribLocation(prog, "t"), um = glGetUniformLocation(prog, "m"), us = glGetUniformLocation(prog, "s");
glVertexAttribPointer(ap, 2, GL_FLOAT, GL_FALSE, 16, quad); glEnableVertexAttribArray(ap);
glVertexAttribPointer(at, 2, GL_FLOAT, GL_FALSE, 16, quad + 2); glEnableVertexAttribArray(at);
glUniform1i(us, 0); glActiveTexture(GL_TEXTURE0);
uint8_t* pix = malloc((size_t)W * H * 4); for (size_t i = 0; i < (size_t)W * H * 4; i++) pix[i] = (uint8_t)(i * 7);
uint8_t* tile = malloc(256 * 256 * 4); memset(tile, 0x80, 256 * 256 * 4);
uint8_t* back = malloc((size_t)W * H * 4);
GLuint tex[4]; glGenTextures(4, tex);
// 1. one full-frame texture upload (RGBA 1024x768 = 3 MB)
double t = now_ms();
for (int i = 0; i < 4; i++) { glBindTexture(GL_TEXTURE_2D, tex[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix); }
glFinish(); printf("upload 4 x 1024x768 RGBA textures: %.1f ms (%.1f ms each)\n", now_ms() - t, (now_ms() - t) / 4);
// 2. composite: one opaque layer per frame
glDisable(GL_BLEND); glUniform4f(um, 1, 1, 1, 1);
t = now_ms(); for (int f = 0; f < frames; f++) { glClear(GL_COLOR_BUFFER_BIT); glBindTexture(GL_TEXTURE_2D, tex[0]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glFinish();
printf("composite 1 opaque full-screen layer: %.2f ms/frame\n", (now_ms() - t) / frames);
// 3. composite: 4 full-screen layers, 3 blended (a page: content + fixed header + overlay + scrollbar-ish)
t = now_ms(); for (int f = 0; f < frames; f++) { glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, tex[0]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUniform4f(um, 1, 1, 1, 0.5f); for (int l = 1; l < 4; l++) { glBindTexture(GL_TEXTURE_2D, tex[l]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glUniform4f(um, 1, 1, 1, 1); } glFinish();
printf("composite 4 full-screen layers (3 blended): %.2f ms/frame\n", (now_ms() - t) / frames);
// 4. tile upload per frame: 12 x 256x256 (what a scroll repaints under OMTC), then one draw
glDisable(GL_BLEND);
t = now_ms(); for (int f = 0; f < frames; f++) { glBindTexture(GL_TEXTURE_2D, tex[0]); for (int k = 0; k < 12; k++) glTexSubImage2D(GL_TEXTURE_2D, 0, (k % 4) * 256, (k / 4) * 256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, tile); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glFinish();
printf("12 tile uploads (256x256, 3 MB total) + composite: %.2f ms/frame\n", (now_ms() - t) / frames);
// 5. readback: the cost every GPU route pays until LunaSysMgr takes GPU buffers
t = now_ms(); for (int f = 0; f < frames; f++) { glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, back); }
printf("glReadPixels 1024x768 RGBA: %.2f ms/frame\n", (now_ms() - t) / frames);
if (readbgra) { t = now_ms(); for (int f = 0; f < frames; f++) { glReadPixels(0, 0, W, H, GL_BGRA_EXT, GL_UNSIGNED_BYTE, back); } printf("glReadPixels 1024x768 BGRA_EXT: %.2f ms/frame\n", (now_ms() - t) / frames); }
// 6. a whole GPU frame as the readback routes would run it: tiles up, composite, read back
t = now_ms(); for (int f = 0; f < frames; f++) { glBindTexture(GL_TEXTURE_2D, tex[0]); for (int k = 0; k < 12; k++) glTexSubImage2D(GL_TEXTURE_2D, 0, (k % 4) * 256, (k / 4) * 256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, tile); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glEnable(GL_BLEND); glUniform4f(um, 1, 1, 1, 0.5f); glBindTexture(GL_TEXTURE_2D, tex[1]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisable(GL_BLEND); glUniform4f(um, 1, 1, 1, 1); glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, back); }
printf("GPU frame with readback (12 tiles up + 2 layers + read): %.2f ms/frame\n", (now_ms() - t) / frames);
printf("GL error state: 0x%x\n", glGetError());
// CPU side, for the software route
t = now_ms(); for (int f = 0; f < frames; f++) memcpy(back, pix, (size_t)W * H * 4); printf("CPU memcpy 3 MB: %.2f ms\n", (now_ms() - t) / frames);
t = now_ms(); for (int f = 0; f < frames; f++) { uint32_t* d = (uint32_t*)back; const uint32_t* a = (const uint32_t*)pix; for (size_t i = 0; i < (size_t)W * H; i++) { uint32_t s = a[i], b = d[i]; uint32_t al = (s >> 24) & 0xff, ia = 255 - al; d[i] = ((((s & 0xff00ff) * al + (b & 0xff00ff) * ia) >> 8) & 0xff00ff) | ((((s & 0xff00) * al + (b & 0xff00) * ia) >> 8) & 0xff00) | 0xff000000; } }
printf("CPU blend one 1024x768 ARGB layer over another (plain C): %.2f ms\n", (now_ms() - t) / frames);
t = now_ms(); for (int f = 0; f < frames; f++) { const int SW = 1000; uint32_t* d = (uint32_t*)back; const uint32_t* a = (const uint32_t*)pix; for (int y = 0; y < H; y++) { const uint32_t* row = a + (size_t)(y * SW / 768 % H) * W; for (int x = 0; x < 768; x++) d[(size_t)y * W + x] = row[x * SW / 768]; } }
printf("CPU nearest-neighbour scale 1000->768 wide, 768 rows: %.2f ms\n", (now_ms() - t) / frames);
t = now_ms(); for (int f = 0; f < frames; f++) memset(back, f, (size_t)W * H * 4); printf("CPU memset 3 MB: %.2f ms\n", (now_ms() - t) / frames);
return 0;
}
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# Copyright 2026 NotAlexNoyle. Apache-2.0.
#
# stall-resolve.py — the daemon's stall samples (/tmp/jihad-stall.txt on the device, written by the
# sampler in render/goanna/GoannaRenderPage.cpp when $JIHAD_STALL_SAMPLER is set) turned into
# function names and buckets: which part of the engine held the main thread.
#
# python3 -I tools/host/stall-resolve.py <jihad-stall.txt> [--xul <unstripped libxul.so>] [--top N]
#
# Each "stall N: <ms> ms, <n> samples" block is resolved on its own. libxul offsets go through the
# toolchain's addr2line against the UNSTRIPPED libxul of the same build
# (build/webos-oe/out-arm/obj-jihad-goanna-arm/toolkit/library/libxul.so); other modules are
# counted by name; "jit" is code the JIT emitted (no module).
import sys, os, re, subprocess, collections
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(os.path.dirname(HERE))
XUL = os.path.join(REPO, "build/webos-oe/out-arm/obj-jihad-goanna-arm/toolkit/library/libxul.so")
A2L = os.path.join(REPO, "build/webos-oe/toolchain/out-toolchain/x-tools/arm-webos-linux-gnueabi/bin/arm-webos-linux-gnueabi-addr2line")
BUCKETS = [
("JS parse / bytecode", r"js::frontend::|BytecodeEmitter|TokenStream|js::Parser|FullParseHandler|SyntaxParseHandler|CompileScript|CompileModule"),
("JIT compile (Ion/Baseline)", r"js::jit::(Ion|MIR|LIR|Lowering|Codegen|Baseline|Range|Scalar|Alias|Valu|Licm|GVN|Linear|Backtracking|Register|Macro|Assembler|JitCompiler|OptimizeMIR|CompileBackEnd|IonBuilder|BaselineCompiler|ICStub|CacheIR)|js::jit::Compile|jit::Assembler"),
("GC", r"js::gc::|GCRuntime|js::Nursery|TenuringTracer|GCMarker|js::TraceEdge|TraceChildren|js::MarkingTracer|Arena::|Chunk::|Zone::sweep|SweepArena|FinalizeTypedArenas|IncrementalCollectSlice"),
("Cycle collector", r"nsCycleCollector|CCGraph|nsPurpleBuffer|CycleCollect|Traverse\(|Unlink\(|NS_CYCLE_COLLECTION"),
("Style (selectors, restyle)", r"nsCSSRuleProcessor|SelectorMatches|RuleProcessorData|RestyleManager|ElementRestyler|nsStyleSet::|nsRuleNode|ResolveStyleFor|nsStyleContext|StyleSetHandle|nsCSSSelector|RuleHash|ComputeStyle|GetStyleContext|nsTransitionManager|nsAnimationManager"),
("Layout (reflow, frames)", r"::Reflow|nsBlockFrame|nsInlineFrame|nsFlexContainerFrame|nsGridContainerFrame|nsTableFrame|nsLineLayout|nsCSSFrameConstructor|nsFrameManager|ReflowInput|nsHTMLReflowState|PresShell::DoReflow|ProcessReflowCommands|FlushPendingNotifications|nsTextFrame|nsContainerFrame|nsLayoutUtils|BlockReflowInput|nsFloatManager|nsBidi"),
("Paint / display list", r"nsDisplayList|nsDisplayItem|PaintRoot|BuildDisplayList|gfxContext|gfxPlatform|mozilla::gfx::|DrawTarget|cairo_|pixman_|PaintFrame|nsLayoutUtils::Paint|ImageLayer|BasicLayer|nsImageRenderer|gfxTextRun|gfxFont|FrameLayerBuilder|JihadRenderDocument|RenderDocument"),
("HTML parse / DOM build", r"nsHtml5|nsContentSink|nsXMLContentSink|nsGenericHTMLElement::|Element::Set|nsINode::|nsDocument::|nsContentUtils|mozilla::dom::Element|nsAttrValue|nsContentList|nsDOMMutationObserver"),
("JS interpreter / runtime", r"js::Interpret|js::RunScript|js::Call\(|js::InternalCallOrConstruct|js::jit::DoCallFallback|js::NativeObject|js::NativeGetProperty|js::GetProperty|js::SetProperty|js::ObjectGroup|js::Shape|js::TypeSet|js::jit::ICStubCompiler|js::StringBuffer|js::ConcatStrings|js::Invoke|JSObject::|js::NewObject|js::ArrayObject|js::str_|js::array_|js::RegExp|js::irregexp|js::ToString|js::ValueToSource|js::jit::VMFunction|js::jit::Invalidate|js::DeepCloneObject|js::Proxy|js::Wrapper|js::CrossCompartmentWrapper|XPCWrappedNative|xpc::|XrayWrapper|DOMProxyHandler|JSAutoCompartment"),
("DOM bindings / XPCOM glue", r"mozilla::dom::.*Binding|GenericBindingMethod|GenericBindingGetter|binding_detail|nsXPCWrappedJS|XPCCallContext|XPCConvert|nsCOMPtr|NS_Addref|NS_Release|AddRef|Release\(|QueryInterface"),
("Events / timers / tasks", r"nsTimer|TimerThread|nsRefreshDriver|EventDispatcher|nsEventDispatcher|EventListenerManager|nsThread::|nsRunnable|mozilla::Runnable|TaskQueue|MessageLoop|nsAppShell|ProcessNextEvent"),
("Network / cache / sqlite", r"sqlite3|mozStorage|nsHttp|nsSocketTransport|nsCacheEntry|CacheFile|CacheIndex|nsInputStream|nsOutputStream|nsStreamLoader|nsCookieService|nsPermissionManager|nsDiskCache|nsUrlClassifier|nsStandardURL|nsIOService|nsLoadGroup|nsChannel"),
("Images / media", r"imgLoader|imgRequest|RasterImage|Decoder|nsJPEG|nsPNG|nsGIF|SurfaceCache|VectorImage|MediaDecoder|MediaCache|MediaStream|FFmpeg|VPX|Opus|AudioStream|cubeb"),
("Memory (malloc, strings)", r"jemalloc|arena_|malloc|free\b|moz_x|nsTArray|nsTString|nsAString|nsACString|nsString|memcpy|memmove|memset|strlen|std::"),
]
def resolve_xul(offsets, xul, a2l):
"""offsets: set of ints → dict offset → 'function' (the innermost inlined name)"""
out = {}
if not offsets or not os.path.exists(xul) or not os.path.exists(a2l): return out
offs = sorted(offsets)
for i in range(0, len(offs), 2000):
chunk = offs[i:i+2000]
p = subprocess.run([a2l, "-f", "-C", "-e", xul] + ["0x%x" % o for o in chunk], capture_output=True, text=True)
lines = p.stdout.splitlines()
for j, o in enumerate(chunk):
fn = lines[2*j] if 2*j < len(lines) else "??"
out[o] = fn
return out
def bucket(fn):
for name, rx in BUCKETS:
if re.search(rx, fn): return name
return "other"
def main():
args = sys.argv[1:]
if not args: print(__doc__); sys.exit(2)
path = args[0]; xul = XUL; top = 30
if "--xul" in args: xul = args[args.index("--xul") + 1]
if "--top" in args: top = int(args[args.index("--top") + 1])
stalls = []; cur = None
for line in open(path, encoding="utf8", errors="replace"):
line = line.strip()
m = re.match(r"stall (\d+): (\d+) ms, (\d+) samples", line)
if m: cur = {"n": int(m.group(1)), "ms": int(m.group(2)), "count": int(m.group(3)), "samples": []}; stalls.append(cur); continue
if cur is not None and "+0x" in line:
mod, off = line.rsplit("+0x", 1); cur["samples"].append((mod, int(off, 16)))
xul_offsets = set(off for st in stalls for mod, off in st["samples"] if mod.startswith("libxul"))
names = resolve_xul(xul_offsets, xul, A2L)
for st in stalls:
n = len(st["samples"]) or 1
byfn = collections.Counter(); bybk = collections.Counter(); bymod = collections.Counter()
for mod, off in st["samples"]:
bymod[mod] += 1
if mod.startswith("libxul"):
fn = names.get(off, "libxul+0x%x" % off); byfn[fn] += 1; bybk[bucket(fn)] += 1
elif mod == "jit": byfn["<JIT code>"] += 1; bybk["JIT code (the page's compiled JavaScript)"] += 1
else: byfn[mod] += 1; bybk["other module: " + mod] += 1
print("=== stall %d: %d ms, %d samples (10 ms each)" % (st["n"], st["ms"], n))
print("--- by bucket:")
for k, v in bybk.most_common(): print(" %5.1f%% %s" % (100.0 * v / n, k))
print("--- top %d functions:" % top)
for k, v in byfn.most_common(top): print(" %5.1f%% %s" % (100.0 * v / n, k[:140]))
print()
if __name__ == "__main__": main()