mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
dce9953d36a224eb06ca81b49560334043741db7
3495
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dce9953d36 |
[MG_Backend, MG_State, MG_Pipe, MG_Remote, MG_Impl, MG_IntegrationTest, docs] (P7): magma installs a state-object death table, and the inproc arm stops losing every object_death
CONTRACT-P7 §5.5. DirectVulkan had no StateObjectDeathOps at all, so under a Magma
inproc session NotifyStateObjectDestroyed reached nobody and the server was never told
that a frontend object had died. Package L measured the shape: with the WireTables
install condition made backend-agnostic, CtWireScenario's two death cases PASS on Magma
spawn and tcp - the remote client has no local backend, so the client-side emitter
installed there answers the notice - and FAIL on inproc, where the install condition
deliberately stands aside for "the backend's own dispatcher" and Magma had none.
g_magmaStateObjectDeathOps is that dispatcher. It mirrors g_glesStateObjectDeathOps
(DirectGLES/Managers.cpp:335) with ONE arm rather than two: Espryt's other arm destroys
a backend twin keyed by lifetime id, and Magma keeps no such twin - its two
client-minted kinds are keyed on {slot, gen} by MagmaPipeIdentityTables and the other
six are still reached from their frontend objects - so what is left is exactly the emit
of object_death off the apply thread. Installed from
BackendObject_DirectVulkan::Initialize(), i.e. ServerLoop::CreateBackend, step 1 of
InitServerRoleCommon: the one place BOTH Magma server roles pass through, and before any
client session exists. #if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH, so the
pull build compiles nothing new.
The two skip guards in CtWireScenario go with it. Every other "Magma installs none" site
the audit found is comment text whose ARGUMENT survives, and each is reworded rather than
deleted: the table emits the wire record and frees no client slot, so P4a's
backend-neutral NotifyAndFree is still the only thing that returns one, HandleRecycle's
C-1 case still measures the only protection the allocator has, and MagmaPipeArms' age
sweep is still the only thing that retires an identity slot.
The framebuffer death case now reads the client's allocator beside the server's tally.
That correction matters: the plan called this a slot leak provable with PipeSlotPeek, and
it is not - it has been backend-neutral since P4a. What leaked was the SERVER's twin, and
a framebuffer has no wire delete opcode at all, so object_death is its only delivery.
Two installers cannot race although the global is last-writer-wins with no stacking:
Magma's runs only in a process that owns a DirectVulkan backend, and
InstallClientWireTables' arm runs only under Transport == Spawn, which is by construction
the process that owns none.
RED-ONCE: `if (false) InstallStateObjectDeathOps();` reds the two Split cases alone
(`Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0`, CtWireScenario.cpp:264)
and leaves spawn and tcp green - the exact split of responsibility the comment claims.
Restored: 8/8. Recorded in docs/Disaggregated/notes/p7/magma-c.md.
|
||
|
|
07079c3c0e |
[docs] (P7): X1 verified on the phone, F1 verified but acquitted, OpenRA is a cold-cache timing race (ID-P7-18/19)
X1: 26.3 and create-instancing pass under inproc on both backends, zero declined faults, tracker line confirms tag=0xb4. F1: the R-8 line is gone in every run, yet OpenRA reads 0.976494 on a cold driver cache (three pm-clear runs identical), 1.0 warm, 1.0 cold monolith, and FRAMESINFLIGHT=1 flips it half the time - a GPU-timing race on the wire readback/present path, handed to package B as slice 0. |
||
|
|
3179c4972d |
[docs] (P7): F1's emitter, its three red-once arms and the one thing lavapipe cannot prove are on record
`notes/p7/f1-caps-placeholder.md`, per CONTRACT-P7 §9's "one red-once per package recorded in notes/p7/". It carries the call chain from `MobileGL::Initialize` down to `CapsMirror::ServerConsumes(0x400)`, the three corrections to the brief's reading (it is not a race; P6.5's fix already covers inproc; a bounded wait alone deadlocks), the three red-once arms, and the two things still owed to the integrator. The arm worth reading twice is A. On lavapipe the BASE build carries the R-8 line on every transport, with and without the delay knob, and OpenRA still scores ssim 1.000000 / mismatch 0. So the eleven dropped resource_creates cost nothing visible there, F1's red-once is the LOG LINE and the new Fatal rather than an SSIM, and this package must not be read as having explained the device's 0.976494 / 0.988998. That still needs the phone. |
||
|
|
22c7361c5f |
[MG_Test] (P7): the placeholder caps cases assert the refusal and the wait instead of the answer
`CapsMirrorTest.APlaceholderMirrorConsumesNothing` asserted the defect F1 closes -
"with no snapshot the mask is zero, every family answers no consumer, the client
emits nothing and the legacy pull path runs" - and it was green for five packages
because nothing on the client ever checked whether that pull path exists. It does
not. Four cases replace it, and each one is the control for a different way the
new gate could go wrong:
* `APlaceholderMirrorRefusesToAnswerAndNamesTheFamily` forks, asks a placeholder
mirror about TextureResources with no session anywhere, and requires the death
to carry `Fatal{CapsBeforeFirstSnapshot, "TextureResources"}` AND `0x400` -
R-16's rule, because "something aborted" is satisfied by any of the other 43
family words.
* `ThePlaceholderWaitsForASnapshotAnotherThreadIsFetching` drives the PRODUCTION
bring-up (`MG_Backend::Init`) with `MOBILEGL_TEST_DELAY_FIRST_CAPS_MS=50` and
asks from a second thread. The wait must return the server's real answer, not
the placeholder's false and not a death. It is the only shape in which "block
with a bounded wait" is a correct answer to F1.
* `ARealSnapshotThatWithholdsAFamilyStillAnswersFalse` is what keeps this from
being a callMask defaulted to all-ones: a server that really consumes nothing
is still answered "no", counted and named. Only the PLACEHOLDER is refused.
* `TheReadAccessorsStillAnswerFromThePlaceholder` pins the half of P5's ruling
that F1 does NOT narrow.
`ResetCapsMirrorForTest()` is new and test-only: a fork child inherits whatever the
previous case adopted, so without it a case about the placeholder would be a case
about that snapshot.
`ServerLoopTest`'s main gains no code, only the sentence that explains why it has
none: it sets `MG_Config::Transport` by hand and never runs `MG_ConfigLoader::Init`,
which is exactly what keeps the client-side gate disarmed for a binary that builds
server sessions and the server's own frontend GLContext and never a client at all.
|
||
|
|
df17c416c0 |
[MobileGL, MG_Backend, MG_Remote, scripts] (P7): the split wire comes up before the first frontend object, and the caps mirror stops deciding a family's fate from a placeholder
F1. `MG_State::Init()` runs before `MG_Backend::Init()`, and the second is what
starts the session - so `GLContext`'s `TextureState` constructor materialised its
eleven default texture objects, each of them emitted `resource_create` through
`FamilyIsLive`, and `CapsMirror::ServerConsumes(0x400)` answered every one of them
from a generation-0 placeholder with `callMask=0`. All eleven records were dropped
and the whole event was one WARN line:
MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this
family emits NOTHING and the legacy pull path runs for it (R-8). callMask=0x0,
caps generation 0
It is NOT a race. The line reproduces byte-for-byte on lavapipe and on the Adreno
830, because the two Init calls are two statements on one thread. And "the legacy
pull path runs for it" is false on the client under split: there is no pull path
on this side of the wire, so withholding is not a fallback, it is a drop that only
a later respecify's self-healing create ever undoes.
Two of the three options the brief offered do not apply, and both facts are worth
recording. (b) "extend the P6.5 fix to inproc" is already the tree: `FinishStartup`
step 7 is the one funnel every transport reaches and it will not return without a
real snapshot. (a) "a bounded wait in Consumes()" cannot work alone and would
deadlock: at `MG_State::Init()` no handshake is in flight, and the one that will
land is started later BY THE SAME THREAD.
So the fix is the ORDER, with the rule behind it as the gate:
* `MobileGL::Initialize` calls `MG_Backend::InitSplitRolesBeforeState()` before
`MG_State::Init()`. `Init()`'s split arm becomes one body, `InitSplitBackend()`,
shared with the direct callers that still reach `Init()`; the monolith arm keeps
its place after `MG_State::Init()`, because moving it would move pull `.text`.
* `CapsMirror::ServerConsumes()` refuses to answer from a placeholder. It waits,
bounded, when a bring-up is genuinely in flight on ANOTHER thread, and otherwise
dies as `Fatal{CapsBeforeFirstSnapshot, "<family>"}` through `SessionFail` -
a new family word, one row in `FatalFamilies.def`, no new abort site.
* Two arms are exempt and say why in place: the server arm (`MGPipeServerArm()` -
the apply thread and the spawn server, where the client's mirror is the wrong
object rather than a late one), and a process whose own configuration never
asked for a split transport (`MG_Config::SplitTransportRequestedByConfig`, set
by `MG_ConfigLoader::InitTransport`, which is what tells a real run apart from a
fixture that assigns `MG_Config::Transport` by hand).
The read accessors keep P5's ruling untouched: `LogBackendInfo` reads
`GetRendererInfo()` during `MG_Backend::Init()` and an abort there is a process that
cannot start. Only the DECISION changed. The warning was not made quieter and the
callMask was not defaulted to all-ones.
`MOBILEGL_TEST_DELAY_FIRST_CAPS_MS` (server side, disaggregated builds, inert unless
set) holds back the FIRST CapsSnapshot. On spawn/tcp that is a genuinely late
snapshot in another process and it exercises the client's step-7 wait; on inproc it
only makes `Start` slower, which is itself the proof that the only window a client
can read a placeholder in is the one before `Start` is called.
G1: `.text` 0xa52203 and the 30570-symbol list identical to p7-before/pull-syms.txt.
Census: 79 abort sites unchanged, 44 family words (was 43). Link ratchet: 186,
unchanged.
|
||
|
|
206abdfa44 |
[docs] (P7): the gate-5 CTS base reading is on record - monolith x DirectVulkan x KHR-GL46 on the Redmi, five complete blocks
shader-image 63/56/6, ssbo 124/0/0, dsa 368/0/2, texture 815/221/18, packed-pixels 4732/0/0 (Pass/NotSupported/Fail); every block complete (no unrun, hung or crashed); library p7w1-d260f110, glcts from VK-GL-CTS 0b04c470e with the FBO colour-texture patch, API probe confirms GL 4.6. The AFTER reading compares inproc against this at 0.5 pp per block with new crashes as a separate hard red. |
||
|
|
5c4235d7ba | [docs, MG_Remote] (P7): ID-P7-12..17 - gate 3 per case against monolith, photon excluded, wave-0 naming ratified, X1 on the critical path, gate 5 is five blocks at GL46, E2/E0b folded into the F1 re-test | ||
|
|
ec3ba00af3 |
[tools] (P7): the CTS skip list the runbook already passes exists
run_cts.py silently tolerates a missing --skip-file, so the known device-killers from the skill (clip_distance.functional, the multisampled_to_singlesampled blit family, the rgb565 repeat-mode case) would each have cost a reboot in the gate-5 window. |
||
|
|
4cbf78c269 |
[MG_Remote, MG_Test, docs] (P7): the persistent-map tracker answers faults in the kernel's address space, and a map hands back a range it can write
Wave 2 package X1. Three CI split-subset traces died on the Redmi under inproc and
passed under monolith on the same device and APK, on the client thread, in the
replayer's memcpy into a pointer MobileGL had handed it: SIGSEGV / SEGV_ACCERR on the
destination's FIRST byte, destination page-aligned.
The tombstone names the same address twice, in two different address spaces:
x0 b4000073ac9a5000 ... x2 0000000001800000
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x00000073ac9a5000
x0 is the pointer the application writes through, carrying bionic's 0xb4 heap tag
(the same tombstone reports tagged_addr_ctrl PR_TAGGED_ADDR_ENABLE). fault addr is
si_addr, which the kernel reports UNTAGGED.
TrackWriteMap took shadowBase from MappedData() - a tagged heap pointer - kept the tag
through the page masking, mprotect()ed the mapped range PROT_READ and published the
TAGGED base/end into the slot. The handler compared si_addr against it:
if (base <= kSlotSettingUp || address < base) continue;
0x73ac9a5000 < 0xb4000073ac9a5000 is true for every fault, forever. Ownership test (2)
never matched, and every fault on a page the tracker had itself protected was chained
to debuggerd. mprotect was fine throughout - do_mprotect_pkey untags its own argument -
so the right pages really were read-only; only the recognition was broken. x86-64 has
no top-byte tag, which is why every host lane has always been green.
The fix is two rows and one ordering:
* UntagAddress(), applied to the shadow base at registration, to the shadow base in
PushBlocksFor (so trackedBase - shadowBase stays in one space) and to si_addr in
the handler. Written WITHOUT an #if defined(__aarch64__): masking bits 56-63 is
the identity for every valid userspace address off arm64, and an arch guard would
have put the only gate that can catch this on the one platform CI never runs.
Dereferences keep the tagged pointer they came from.
* SlotOwnsFault(), the ownership row the handler and the unit case both read.
* REGISTRATION ARMS NOTHING. AcquireMemoryRange registered and only then returned
Bytes() + range.start, so glMapBufferRange handed the application a PROT_READ
pointer whose writability depended on a handler reaching the writing thread - and
the application is free to write a mapped arena from a worker that blocks every
signal. Nothing is lost by waiting: every page bit is already set at registration,
so the first push ships the whole interior regardless, and it is that push which
arms the pages. 6144 pointless faults per 24 MB arena go with it, and the
invariant the file rests on (bit set <=> page writable) is now true from
registration onward instead of only from the first push.
Diagnostic, independent of the fix: one MGLOG_I_ONCE at the first registration naming
the mapping the way the device run needed and did not have (allocator, pointer, ITS
TAG, extent, range, published span, pages, alignment arm), and a decline record in the
handler - relaxed atomic stores plus one async-signal-safe write(2) - readable through
DeclinedFaults(). A non-zero decline count whose si_addr lies inside nearest-tracked
once the top byte is masked off is this defect's signature and nothing else's.
Not the mechanism, and checked: libsigchain (the tombstone's "F libc : Fatal signal 11"
is bionic's own handler, so the chain ran), the 16 KiB-page refusal (the device is a
4 KiB kernel and the protection demonstrably took), RefaultOfANonWriteAccess (matched
is false long before it is consulted), and the P5d shadow allocator (correct - it is
what makes the destination page-aligned in the tombstone).
Red-once, two and orthogonal (MG_Test/Wire/PersistentMapFaultTest.cpp, 6 cases):
dropping the two UntagAddress calls in SlotOwnsFault reds only
AKernelsUntaggedFaultAddressIsOwnedByATaggedShadowsSlot, which runs the device's own
two addresses through the real predicate on an x86-64 host; restoring the mprotect at
the end of TrackWriteMap reds only AMapHandsBackARangeWritableWithoutTheFaultHandler
(the child dies by SIGSEGV) and RegistrationLeavesEveryPageOfTheRangeReadWrite.
AnArmedPageIsStillAnsweredByTheHandlerAndMadeWritable is the anti-vacuity case.
Gates: ctest -L unit 2408/2408; integration-split 186/186; integration-spawn 102/102;
integration-magma-split 73/73 (PersistentCoherentMapScenario green in the Split, Spawn,
PersistentMapArm and SmallRing variants); fatal_census.py 0 unmarked. G1 holds - pull
.text 10822147 = 0xa52203 unchanged, symbol report 0 added / 0 removed / 0 resized /
0 renamed, nm --defined-only identical at 30570 and nm -D at 11933. Every line is under
MOBILEGL_BUILD_DISAGGREGATED (CMakeLists.txt:592-659), so the pull build sees none of it.
Evidence and the device experiment: docs/Disaggregated/notes/p7/x1-persistent-map-segv.md
|
||
|
|
4f2d4c4e83 |
[MG_Remote, docs] (P7): CONTRACT-P7 carries wave 0's numbers, and device window #1a is on record
CONTRACT-P7: the lane counts (magma-split 73 / spawn 52 / tcp 54; full-suite 510 x 3 with one StreamLink x Magma red), the census baseline (79 abort sites over 20 files, 43 family words, 0 unmarked), the ratchet baseline (186 = p7-magma 101 / espryt 14 / both 60 / core 11; espryt's clearable count is 9) and the @P7 count (15 sites / 4 files) replace the 待填 markers. Device window #1a (Redmi 2f7cbe2e, APK p7w1-d260f110, reboot-clean): E0a monolith 39/39 deterministic, three below threshold on monolith itself (create-indirect, 1.21.11 main-menu, iris-photon - Adreno refuses its pipelines with VK_ERROR_UNKNOWN); E0a inproc: OpenRA 0.976494 (run-ahead) / E1 lockstep 0.988998 - both historical numbers were OpenRA, and its client log shows a TextureResources verb answered from caps generation 0 (package F1); 26.3 / create-* SIGSEGV in the replayer's memcpy on BOTH backends and with the persistent-map push off (package X1); iterationt x2 die by name on Magma:mipmap-shader-format-or-shape (package B); bsl-esc-menu hits scudo OOM under inproc. Gate 3's denominator is 36; run-ahead and present credit are off the suspect list. |
||
|
|
801f8edaa6 |
[scripts] (P7): the parity self-test patches the seam the Magma tiers read through
Package D added a test that patches lane_cases and hands spawn_lane_parity.py a placeholder build dir; package L then made the three Magma tiers read through a new lane_names, so the placeholder reached a real ctest and the unit label went red on the integrated tree. Patching lane_names to an empty set on every arm makes those tiers report "nothing to compare" and leaves the assertion about lane_cases intact. |
||
|
|
8bb6309a1c |
[scripts, CI, docs] (P7): the 184-symbol link-closure ratchet lands as a gate
CONTRACT-P6.md 12.1 has carried "a ratchet recomputes the 184 in CI and asserts it only
ever falls" as an obligation since P6. The roadmap handed it to P6.5; the P6.5 line never
received it, and scripts/ci/ has never had anything that computes this number. ID-P7-7
gives it to P7 wave 0. This is it.
scripts/link_ratchet.py mechanises a6's hand experiment: partition the per-object output
of the single MobileGL target into SERVER / FRONTEND / SHARED, then compute
(SERVER undefined) - (SERVER defined) - (SHARED defined) intersect (FRONTEND defined),
which is the undefined-symbol list a real link of a server-only image would print. The
partition is an explicit table with a rationale per row, because it IS the module boundary
the build system does not have (SOURCE_FILES is one flat list; boundaries are P13's work).
An object matching no row is a hard failure, as is a build whose CMakeCache lacks the three
disaggregated options, or an empty role set - every way this gate could go green without
having measured anything is an explicit exit 2.
Measured on
|
||
|
|
1de8f02ebe |
[MG_IntegrationTest, docs] (P7): the whole integration binary runs under Magma split, and it is one red
Tier 2 replays the split-arm registrations - ~100 scenarios, the set Espryt's disaggregation work
has carried since P5 - and it measured green on Magma across all three transports. That is a real
result but it is NOT exit gate 1. The gate says the INTEGRATION BINARY is green under Magma split,
and the set that names is the one the monolith DirectVulkan. arm runs: the whole suite minus
MGL_ITEST_MONOLITH_MECHANISM_FILTER. 510 cases against tier 2's 102, and the readback matrices,
the same-texture copy shapes and the odd-offset uniform ranges are in it and nowhere else in the
disaggregated registry.
integration-magma-full-{split,spawn,tcp}, in no gating step, private log per entry through a list
of its own. THE RESULT: 453/0/57 on inproc, 453/0/57 on spawn, 454/1/57 on tcp - and not one of
the 1530 entries reaches any of the tree's fifteen @P7 named refusals. The single red is
IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput on the TCP arm:
one texel of 262656, texel 0, which is the case's own exposure value
(IterationRPProgram203Scenario.cpp:371, actual half bits (0x357a, 0x4b65) against golden
(0x3a74, 0x4821)). Deterministic 5/5 on tcp; 5/5 GREEN on inproc, on spawn and on the monolith
control. spawn and tcp differ only in MOBILEGL_IPC_DATA, so the suspicion is the STREAM data path
and not "two processes"; the GLES tcp lane is green in the same run, so it is not the fixture.
Not fixed here - it is what the census is for.
TWO ENVIRONMENT DIFFERENCES FROM THE GATED TIERS, both measured rather than assumed:
* NO MGITEST_SPLIT_LANE=1. That marker arms ScenarioFixture's destructor assertion - a case in
an armed split lane must have moved the client encoder's record ordinal - which is right for
a curated lane and wrong for a whole-binary census. With it set, the first run produced 13
reds, all `EmitSeq was 20 at SetUp and is 20 now`: AdvertisedLimitsScenario (8),
SpirvShaderBinaryScenario (4) and ProgramPipelineScenario.AReservedName - a limits query is
answered from the client's CapsMirror and a shader-binary error surface never reaches a verb.
Those 13 would red on a DirectGLES census too. What it gives up is stated in the comment: this
tier does not prove its records crossed, and the tcp topology proof is inert in it.
* DualBlockScenario.* filtered out. Its green REQUIRES MOBILEGL_IPC_ROLE_SPLIT_STATE=1 and a
census must not set that knob; without it the case is red by name, which is its documented
negative-control behaviour. The monolith arm skips it for the same class of reason.
CtWireScenario gets MOBILEGL_TEST_SERVER_COUNTERS=1 in this tier - a switch that makes a counter
readable, not an oracle that asserts - because without it the spawn arm's ApplierReset case reds
on "server control-record telemetry missing", which says nothing about Magma. The strict-arming,
pmap and persistent-map-arm markers stay out.
The inproc-only exclusion in spawn_lane_parity.py is now PER TIER: in the gated tier the two
scenarios are registered on the inproc arm alone, in the census they are on every arm and simply
skip, so excluding them there would manufacture a spurious "extra".
Measured after the change: every pre-existing lane unchanged and green (magma-split 73,
spawn 52, tcp 54, all-* 102/102/104, GLES 186/102/104, buffers 19, runahead 14, caches 7), the
dual-block census 257 green / 2 skipped / 0 red with an empty ratchet, both new CI steps green,
parity RC=0 across all three tiers, G14 4155 -> 6095 with removed=0.
The note now carries the grouped red list, the 57 named skips by reason, the four pieces of
evidence that the tcp red is a defect and not plumbing, and six findings - the first being that
wave 2's clusters cannot be scheduled from this lane's red list, because on lavapipe there is
essentially none.
|
||
|
|
78e10ccf80 |
[CI] (P7): retrace-split exports every Magma knob the monolith retrace job exports
Integrator ruling, completing the alignment package L started. The split retrace job now also exports MOBILEGL_MAGMA_R11G11B10F_FALLBACK for every DirectVulkan row and MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE for improved-transparency-minecraft-26.3, copied from the monolith job (:2180 / :2195) with the same guards. The point is comparability, not the knobs: a split retrace whose monolith control ran a different repair set cannot say whether a divergence is a transport fact. With the three iterationRP repairs from the previous commit, every DirectVulkan row of the 154-cell split matrix now runs the quirk configuration its control runs. retrace-verify is left alone, as the ruling says. |
||
|
|
a7425d95e0 |
[MG_IntegrationTest] (P7): every Magma lane pins the ICD the monolith arm has always pinned
Integrator ruling on the asymmetry package L reported. The six hand-laid Magma lanes (Fm,
Buffers, RunAhead, Caches, NamedBlit, P5fRsp) appended ${MGL_ITEST_COMMON_ENV}, so the monolith
DirectVulkan. arm ran with VK_ICD_FILENAMES pinned and the three lavapipe iterationRP repairs
forced on, while the 73 integration-magma-split entries ran against whatever ICD the loader
found first with those repairs at their device auto-detection default.
That is a latent flake rather than a semantic: the day a runner or a workstation grows a second
ICD, the lane measures a different driver from the gate it mirrors and nothing in its output
says so. Consistency wins - all six now append ${MGL_ITEST_VULKAN_ENV}, the same list the new
MGL_ITEST_VULKAN_{SPLIT,SPAWN,TCP}_ENVIRONMENT use.
The ICD pin follows the BACKEND, not the lane, in the two blocks that register both: NamedBlit
and P5fRsp loop over DirectGLES and DirectVulkan, and handing VK_ICD_FILENAMES to the DirectGLES
half would pin an ICD for a lane that never opens Vulkan. Verified in the discovery: all 73
integration-magma-split entries carry VK_ICD_FILENAMES and all three repair variables, and the
two DirectGLES.Split.NamedBlit. entries carry neither.
Re-run after the change, as the ruling asks: integration-magma-split 71 PASS / 0 FAIL / 2 SKIP
out of 73, unchanged - the two skips are the same ClipDistance pair with the same reason.
|
||
|
|
faca09d4ad |
[CI, docs] (P7): retrace-split exports the iterationrp knobs, and the two new Magma lanes gate
Three changes, one theme - making the two halves comparable, then gating what measured green.
retrace-split's "Retrace and validate under MOBILEGL_TRANSPORT" step exported no Magma knob at
all, while the monolith retrace job (:2183) exports three iterationRP repairs for
minecraft-1.21.4-fabric-iris-iterationrp-in-world x DirectVulkan. Those repairs are tri-state
quirks defaulting to device auto-detection and lavapipe is on no auto list, so that cell of the
split matrix (154 cells, and it contains that case x DirectVulkan x {inproc, spawn}) was being
compared against a monolith control running a different repair set. The same backend+case guard
is now copied verbatim into the split job.
Two gating steps for the lanes this wave added, and ONLY because they measured green on the
first run: integration-magma-{spawn,tcp} run like the existing magma-split step, and the
nineteen application-buffer cases get require_green BY NAME on both new arms - the tcp arm's
required list includes TcpServer.Start / Stop on purpose, because ctest lists the fixture
entries under the label and a lane whose supervisor failed to start has nothing to say about the
nineteen. The informational integration-magma-all-* tier is deliberately in no gating step.
Both steps were executed locally, not merely linted: spawn 19/19 and tcp 19/19 with
"require-green: N PASS, 0 skip, 0 failed, complete discovery". All 99 run: blocks in the
workflow pass bash -n.
The note records the first run in full: per-lane PASS/FAIL/SKIP, the two blocker groups the
first registration attempt produced (both lane plumbing, neither a server abort, wrong pixels
nor a hang), the arming evidence, both red-once demonstrations, the parity and G14 numbers,
what was NOT verified (no device, no retrace run, no G1) and six findings for the integrator -
the first of which is that P7 exit gate 1 has no host-side red list at all, which the wave 2
plan is built on top of.
|
||
|
|
e7e3265c47 |
[MG_Remote] (P7): the wire client's death notice is installed for any backend, not just Espryt
WireTables.cpp:740 installed the client-side object_death emitter only when
ActiveBackendType == DirectGLES. P6.5 wrote that because DirectGLES was the only backend with a
two-process lane, so the condition read as "the case this can happen in" rather than as a policy
- and under a Magma spawn or tcp client it is a silent hole of exactly the shape P6.5 closed for
Espryt: no notice installed, NotifyAndFree drops every object_death before freeing the slot, and
CtWireScenario's two death cases report ObjectDeaths=0 and prove nothing.
The two remaining conditions carry P6.5's intent, both halves of it: Transport == Spawn (this is
the remote client, the one with no local twin table; under inproc the backend's own dispatcher
is the right one) and GetStateObjectDeathOps() == nullptr (whoever installed first keeps the
notice).
RED-ONCE: with the install site short-circuited, both DirectGLES spawn death cases red on
CtWireScenario.cpp:187/:246 with
Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0
which is the silent zero this closes. Restored and green.
WHAT IT DOES NOT DO is give Magma a death table - that is wave 2 package C. But it already does
more than the plan predicted: with CtWireScenario's own BackendName() != "DirectGLES" skip guard
temporarily lifted (a probe, not committed), both DirectVulkan SPAWN death cases PASS, while the
DirectVulkan inproc arm still fails. So the two-process Magma death path works today and the
remaining gap is the inproc arm alone. Recorded in the package note; narrowing that skip guard
is package C's call and its file.
|
||
|
|
3d9a38a979 |
[MG_IntegrationTest, CI] (P7): the split arms take a backend, so Magma has two-process lanes at last
P7 exit gate 1 asks for integration green "under Magma push and split". Until now it was not
merely unmet, it was UNMEASURABLE: every arm of mgl_itest_register_split_arms hardcoded
MOBILEGL_BACKEND_TYPE=DirectGLES (:2002/:2025/:2031) and MGL_ITEST_MAGMA_WIRE_ENVIRONMENT
hardcoded MOBILEGL_TRANSPORT=inproc (:2456), so the shared integration-split / -spawn / -tcp
labels held ZERO DirectVulkan entries and the 73 integration-magma-split cases only ever ran in
one process.
The registration macro now takes the backend as its first argument; the old name is a thin
wrapper, so all 32 call sites are untouched and every DirectGLES name, label and private log
path is byte-for-byte what it was. Two tiers come out of it, and the CMake comment states both:
* GATED - integration-magma-{split,spawn,tcp}. The six hand-laid Magma lanes (Fm, Buffers,
RunAhead, Caches, NamedBlit, P5fRsp) grow arms through one shared shape macro; the inproc
arm keeps its names, its labels and its log paths, so the CI steps and the two magma_*
checker scripts that name them by hand keep working unchanged.
* INFORMATIONAL - integration-magma-all-{split,spawn,tcp}, in no gating step. Every scenario
the split arms run, on Magma, on all three transports, generated by REPLAYING the macro's
recorded call list - so a scenario added to the split arms tomorrow joins the Magma census
the same day. Its job is to be able to go red while CI stays green; a case moves up into the
gated tier with the commit that retires its blocker.
The label spellings are chosen against ctest -L's regex (ID-131 again): the tier word goes in
the MIDDLE, because integration-magma-spawn-all WOULD have been selected by
-L integration-magma-spawn and pulled ~100 expected-red entries into a gated lane. Sub-labels
mirror it: the inproc arm keeps integration-magma-buffers, the others put the arm word first.
MagmaRunAheadScenario and MagmaWireCacheScenario stay on the inproc arm alone, and that is
MEASURED rather than assumed: with all three arms registered, all 21 entries red on spawn and
tcp with no Fatal in either role log - the first arms its hold through
ServerLoopInstance().SetBeforeRetireHookForTesting, a hook on THIS process's server loop, and
the second asserts transportName == "inproc" in its SetUp. Both are named in the CMakeLists and
in spawn_lane_parity.py rather than allowed to widen a tolerance.
split_log_paths.py's is_split prefixes gain DirectVulkan.Spawn. / .Tcp. - on those arms two
processes append to one path, so a shared log does not merely blur attribution, it interleaves
two sessions. spawn_lane_parity.py gains the Magma dimension, normalising only the ARM segment:
RunAhead.Credit1. and .Credit3. are two entries of one case and a tail-stripping normaliser
would fold them together and then report an equal-sized set that had lost one.
Measured on this tree (lavapipe): magma-split 73 unchanged and green, magma-spawn 52/52,
magma-tcp 52/52, the three informational lanes 102 each, GLES split 186 / spawn 102 / tcp 104
all unchanged and green. G14 against ~/w7/p7-before: 4155 to 4565, removed 0, added 410.
|
||
|
|
9e8fdacd21 |
[MG_Remote] (P7): gate 3 is judged per case against the monolith arm, and its denominator names what monolith already fails
Device window #1 E0a: monolith x DirectVulkan on the Redmi is 0.996-1.000 against the desktop goldens (only OpenRA is 1.0), so "monolith 1.0" was an OpenRA fact, not a rule. Gate 3 now reads: per case, inproc and spawn bit-identical over three repeats, within the case threshold, and within 0.0005 of the same-session monolith reading. The denominator excludes what monolith itself fails - create-indirect (OQ-17), the 1.21.11 main-menu trace artifact, and iris-photon, whose pipelines Adreno 830 refuses with VK_ERROR_UNKNOWN (PipelineFactory.cpp:660) - which leaves 36. Gate 5 records the real block sources: UBO lives only in the GTF module, SSBO joins, gl33 is the fallback. |
||
|
|
8b02feb245 |
[android-plugin] (P7): the result verdict hands native python a host path whatever the caller set MSYS_NO_PATHCONV to
On Git Bash the script protects its own adb calls with MSYS_NO_PATHCONV=1, but the two python invocations relied on the caller NOT having exported it; a session that did (for the adb-side /data/... arguments the runbook mentions) got json.load failing on /c/... for a result.json that existed - every case reported "trace replay failed" over a passed result. host_path_for_adb already exists for exactly this; use it. |
||
|
|
c890f0ad1a |
[docs] (P7): the runbook spells the side-by-side package the way the flavour builds it, and its two shell traps are removed
The application id is top.mobilegl.plugin.p7w1.trace (flavour suffix after the id suffix), pgrep -f dex2oat matched the shell running the loop and never exited, and adb.exe cannot stat an MSYS /c/ path once MSYS_NO_PATHCONV is set. All three were hit on the first execution of section 3. |
||
|
|
9be62cbc21 |
[tools] (P7): the UBO caselist comes from the GTF module, and the SSBO block joins gate 5
KHR-GL46 has no uniform_buffer_object group; the 90 UBO cases are GTF-GL46.gtf31.GL3Tests.uniform_buffer_object*, present only when the deployed glcts was built with GTF - the device probe decides. KHR-GL46.shader_storage_buffer_object (124) is the block that exercises what cluster A actually refuses today: unaligned and over-range SSBO bindings, not only UBO byte tails. |
||
|
|
4e7989f1c2 |
[tools] (P7): the five CTS caselists for exit gate 5 are committed with their provenance
Cut from VK-GL-CTS 0b04c470ed78569b0b778ece1262d88455ab2b00 (main; the deployed glcts build's source) mustpass gl46-main.txt and gl33-main.txt: texture_*, shader_image*, packed_pixels, direct_state_access, uniform_buffer*. The gl33 variants are the fallback the runbook names for an Android runner that cannot open a 4.6 core context; whichever version the probe selects, BASE and AFTER read the same list. A 0.5 pp gate needs a committed denominator, not a glob over someone's checkout. |
||
|
|
484a56a5ae |
[scripts, MG_Pipe, MG_Backend, MG_Test] (P7): the census docstring's triage follows what the same wave retired, and the comments stop joining the @P7 census
Two corrections to the two commits before this one. The widening's docstring was written against the 83 sites it found, and the commit that routed five of them through Session::Fail did not revisit the prose. It now describes the 79-site floor the baseline actually records, says which five went and where, and states that the enforceable artefact is the baseline's per-file map rather than the counts in the prose - so a reader who finds the two disagreeing knows which one to believe. The three buckets are restated too: `(ii)` is no longer "owned by a Ph / P7 row", because with the Magma wire arms routed what is left there is the peer-reachable deaths in the applier and the two backends, which is exactly the set ID-P7-1 scopes to Ph's fuzz arm 2 AFTER P7. And the routing commit's own comments spelled the at-sign refusal marker while explaining what routes through them, which put SEVEN comment lines into the retirement census: `grep @P7` went from 15 sites in 4 files to 22 in 6, and that grep is how wave 2 will measure which refusals it has retired. The prose says the word instead, and the count is 15/4 again. |
||
|
|
a5dcbb444f |
[MG_Remote, scripts, MG_IntegrationTest] (P7): the supervisor names a faulted session, and the smoke that proves it is finally a ctest entry
Ph slice (1) (ID-P7-1). Three `::waitpid(active, nullptr, WNOHANG)` calls in the `--serve` loop threw the status away, so a session child that died of SIGABRT inside SessionFail and one that returned from its control loop and _exit(0)'d were the SAME EVENT to the supervisor: `active = -1`, no line, nothing to count. ID-P7-1 declines the literal 98-site latch translation because P6.5's fork-per-session already delivers "the next connection is served as usual"; what was missing is that anyone could TELL a faulted session from a finished one, which is what an operator, a fuzz arm and a flake triage all actually need. One `ReapActiveSession()` where there were three call sites, asking WIFEXITED / WIFSIGNALED / WTERMSIG - the same three questions ServerSpawn.cpp:289 asks of the same kind of child - and logging `pid=<n> reaped exit=<c>` or `signal=<n>` with the supervisor's lifetime `sessionsFaulted` beside it. A refused handshake exits 0 and is NOT a fault, which is right: protocol.fbs:86 says a rejection is a normal session outcome. The running total is on every reap line as well as on the summary, because the fixtures end a supervisor with SIGTERM and a figure only printed at a chosen exit would never be seen. THE IDENTIFIER ROW'S PREMISE IS WRONG, MEASURED. The plan asks for CtrlEnvelopeBufferHasIdentifier at ServerMain.cpp:193 because ServerSession::ParseEnvelope asks it and this loop did not - but the generated VerifyCtrlEnvelopeBuffer already IS `VerifyBuffer<CtrlEnvelope>(CtrlEnvelopeIdentifier())` (protocol_generated.h:2378), so a wrong identifier was refused here and ParseEnvelope's second call is redundant too. Recorded for the integrator. What was real is that both refusals were SILENT: a peer whose post-Welcome frame was garbage had its session ended with no line in the log, and a fuzz arm that cannot tell "refused, by name" from "the child went away" measures nothing. So the identifier is now asked first - it is the specific diagnosis where the verifier's is the general one - with the length guard that asking it first requires, and both say so. scripts/ci/tcp_supervisor_smoke.py has been in the tree since P6.5 asserting four supervisor controls and was referenced by NOTHING - no ctest entry, no workflow step - so nobody could have said whether those four were green. It is now `TcpLane.SupervisorProtocolControls` beside the TCP lane's own fixtures, labelled integration-tcp, under the lane's resource lock (a second supervisor's child would answer the other's peers with Busy). It needs flatc, which is deliberately not in the build graph, so it resolves one gen_protocol.py's way and exits 77 - ctest SKIP - when there is none, rather than passing on a run that did not happen. Two cases added to it: a session child killed with SIGABRT must be named by pid and `signal=6` with `sessionsFaulted` one higher than before, and the next Hello must still be welcomed; and a steady-state frame carrying the wrong file identifier must be named rather than silently dropped. Red-once, both ways, against the real image: making the reap discard the status again reds with "the supervisor did not name session pid=N as killed by signal=6", and deleting the identifier branch reds with "read a frame with the wrong file identifier without saying so". |
||
|
|
e1ec69341a |
[MG_Pipe, MG_Backend, MG_Remote, MG_Test, scripts] (P7): the Magma wire fatals and the two Staged aborts go through Session::Fail
Five deaths in the split server image raised their own std::abort(). Three of them are funnels - MagmaWireFatal (Renderer/WireFramebuffer.inc), WireDescriptorFatal (Renderer/UniformManager.cpp) and WireBufferLegacyFatal (Renderer/WireDraw.inc) - and the fifteen `@P7` refusals on the tree (8 in UniformManager.cpp, 4 in WireFramebuffer.inc, 2 in WireDraw.inc, 1 in VulkanRenderer.cpp; plan section 1.3's count confirmed) all die through two of them. Every one of those deaths published NO SessionFault frame, so the peer read a bare EOF; bumped NO SessionFaultCount(), so exit gate S8 could neither confirm nor falsify it; and carried a hand-typed family word with no path to FatalFamilies.def's projection. MG_Backend may not name an MG_Remote symbol on its own account, so the route is a seam in MG_Pipe with MGP_TRIP_WIRE_REPORT's shape (PipeApply.cpp:66, and the new code sits beside it): MG_Pipe/PipeSessionFail.h declares MGPipeSessionFail, PipeApply.cpp defines it inside `#if MOBILEGL_BUILD_DISAGGREGATED`, the DEFAULT behaviour is byte-identical to what the three funnels did on their own, and MG_Remote::InstallPipeSessionFailHook() points it at SessionFail. The install is in InitServerRoleCommon (MG_Backend/Init.cpp), step 0, because that is the one function BOTH server roles run - the inproc role and the spawn/TCP session child - so there is no per-transport install to get wrong. MGPipeFatalFamily is a two-value boundary enum, NOT MGFatalFamily cast to an integer: the .def's enumerators are positional and a row inserted in its middle would silently re-point every hook call at a different family. FatalFunnel.cpp's adapter is the one place the two vocabularies meet, as a switch with a named default. The family word stays UnmigratedVerb rather than becoming a new MagmaWireRefusal. Three reasons: the message strings are required to stay byte-identical (5.2), and the retrace refusal censuses recorded since P5b count these deaths by that word - a rename invalidates every `@P7` measurement taken against them; `Magma:` in the detail already separates the Magma arm from the GLES UnmigratedVerb deaths, so a new word adds no discrimination a grep does not have; and its wire projection would be ProtocolCorruption, exactly UnmigratedVerb's. Recorded for the integrator. Ph slice (2) in the same commit: Server/StagedShadow.h's RequireCovered and Server/StagedTextureStore.h's RequireLevelBytes were MGLOG_F + bare abort. Both are inside MG_Remote already, so they call SessionFail directly, message verbatim; check_include_closure.py's four probes do not reach either header and the gate is green. Census: 83 abort sites -> 79 over 20 files (five sites retired, one gained for the seam's no-hook default, which is argued into FUNNEL_SITES), baseline rewritten in this commit - the downward ratchet's notice is what asked for it. Two gate bugs the widening exposed and this commit fixes: the SessionFail call regex also matched `MGPipeSessionFail(` (a lookbehind now), and a funnel's anchor needed a wider window than a marker's (kFunnelAnchorWindow). Red-once, three ways. Pointing MagmaWireFatal back at its own abort reds the census naming WireFramebuffer.inc 0 -> 1. Making the seam skip the hook reds both new death cases - `TheMagmaWireVerbDeathReachesSessionFailAndBumpsTheFaultCount` on the exit code, because it reads SessionFaultCount() out of a SIGABRT handler and gets 0 instead of 1, and `TheSeamHandsTheInstalledHookItsFamilyAndItsLineVerbatim` on the line the hook echoed. |
||
|
|
f141918aa1 |
[scripts, MG_Remote, MG_Impl, MG_Test] (P7): the fatal census gate widened to the split server image
`scripts/ci/fatal_census.py` scanned `MG_Remote/` and saw four abort sites. CONTRACT-P6
§12.1 already says what the server image is - "P6's server image is the whole
libMobileGL.so" - and the server process was executing seventy-nine more deaths the gate
could not see: 15 in DirectGLES.cpp, 12 in Managers.cpp, 11 in VkBufferManager.cpp, 7 in
PipeRoute.cpp. SCAN_ROOTS is now MG_Remote / MG_Pipe / MG_Backend / MG_Impl/Pipe /
MG_State, `.inc` joins `.cpp`/`.h` (the Magma wire arms and the generated wire records are
.inc files, and 20 sites are reachable only through one), and MG_Benchmark /
MG_IntegrationTest join MG_Test in the exclusion.
Four rules now, where there were two:
1. an abort names its family within twelve lines, OR it is argued into FUNNEL_FILES /
the new FUNNEL_SITES table. FUNNEL_SITES exempts an individual funnel inside a file
that has ordinary named deaths around it, and only when one of the file's anchors
appears in the same window - so the exemption follows the funnel if it moves, expires
if it is deleted, and does not cover an unrelated abort added to the same file. Two
entries, one line of reason each: PipeApply.cpp's MGP_TRIP_WIRE_REPORT and
PipeWire.inc's MGPipeWireProtocolFatal.
2. unchanged: a WireLogFatal / SessionFail call carries a `Fatal{` in its string.
3. NEW: every family word found in the perimeter has a row in FatalFamilies.def. Eleven
words did not, and now do (see below).
4. NEW: every `Refuse{Word}` names a RefuseCode enumerator. This is what catches
`Refuse{AuthenticationRequired}` (SocketTransport.cpp:161), a word protocol.fbs has
never had - PH-7 (2). Renamed to the enum's own `Authentication`, and
SocketTransportTest pins the spelling to `EnumNameRefuseCode` so the two cannot
drift again.
`abort_sites` was recorded and never compared. It is now a DOWNWARD ratchet with a
per-file map beside it, so "83 became 84" reports as "VkBufferManager.cpp went 11 -> 12,
here are its lines"; fewer sites - the direction `@P7` retirement moves the number - asks
for a re-baseline rather than blocking one.
Eleven new `.def` rows for the eleven rowless words. Ten are ProtocolCorruption for the
reason that bucket already states. ResourceUnavailable is ServerCrashed, not SegmentMismatch:
SegmentMismatch means a shared SEGMENT was the wrong size, and telling a peer that when a
VkBuffer allocation failed would be a lie about whose geometry was wrong. A twelfth word was
collapsed rather than given a row - `InitialBytesNotCarried` (PipeFill.cpp) said the same
thing as `UncarriedInitialBytes` (WireTables.cpp) about the same record, and two words for
one family is the drift a6 censused.
Red-once, four ways: an unnamed `std::abort()` appended under MG_State reds naming the file
and the line; deleting the `Fatal{` from PipeRoute.cpp's ResidualBlockSize reds; putting
`Refuse{AuthenticationRequired}` back reds rule 4; a typo'd family word reds rule 3 and the
vocabulary ratchet together.
|
||
|
|
1e2d076633 |
[docs] (P7): the wave-1 device window runbook
One reboot-clean session on Redmi 2f7cbe2e, copy-pasteable, with the shell each block runs in marked: pin and wake/Doze, APK install identity, P7-7 E0-E3, the P6.5 residual 39-case DirectGLES TCP golden matrix, the CTS $BASE for monolith x DirectVulkan, and the temp-environment restoration. Every step states in one line which result supports which hypothesis, and where the evidence is archived. Four things the runbook had to settle rather than restate: E1's proof. `running lockstep` is NOT the line that proves lockstep took effect: ClientSession.cpp:1873 guards it with `RunAhead != 0 && m_barrierArmed`, so it is the "the knob is set but the server lacks kCapRunAheadApply" warning and MOBILEGL_IPC_RUN_AHEAD=0 prints nothing at all from LatchRunAheadFromCaps. The proof is a line present and a line absent: `Config: IPC ... run-ahead=0 ...` (ConfigLoader.cpp:459) with zero `run-ahead ARMED` - the shape p5f/magma-runahead.md already recorded for the RA=0 arm. E1 will exit non-zero, by design. --transport inproc adds --require-inproc, whose gate demands run-ahead=1 in the Config: IPC line (trace-replay-ci.sh:601), and the proof runs AFTER result.json is pulled and checked. So the SSIM and the archive are usable and the arm is verified by hand against the table above; dropping the --env to make it green would delete the experiment. E0's budget. 39 cases x 2 transports x 3 repeats is 234 replays, roughly 3-4 h, which does not fit beside the TCP matrix and CTS in one window. It is split into a one-pass census that produces the divergent-case list and a three-repeat pass over that list plus two controls - the determinism question only has meaning for cases that already diverge. CTS is flagged, not promised. No caselist is in-tree, and the local VK-GL-CTS checkout has an empty external/openglcts/data with no mustpass file anywhere, so the five blocks cannot be cut in-tree; the exact pre-window procedure to fetch a tagged checkout and commit tools/cts/caselists/p7-*.txt is written out instead. And KHR-GL46 is unproven on the Android runner - every device reference result in the skill is KHR-GL33, the 4.5 numbers are desktop lavapipe, and the platform hands the package's version straight to eglCreateContext - so the runbook carries a five-minute pre-window probe and an explicit integrator decision: if 4.6 is unreachable the baseline is taken on KHR-GL33 and the AFTER reading must use the same version, because 0.5 pp between two different API versions means nothing. |
||
|
|
3ba150d021 |
[tools, android-plugin, CI] (P7): the device matrix runner selects DirectVulkan, repeats are archived, and the spawn env parse is entry-aware
wave 0 item 5. Three instruments P7's device window needs, and the CI steps that
can make each of them red.
run_tcp_matrix.py --backend DirectVulkan end to end. --backend already existed and
already filtered by each case's ci_backends; what did not exist is a per-backend
GOLDEN. The two backends shared `golden` plus an optional `alternate_golden`, and
alternate_golden is an OR across backends, not a per-backend key - so a
DirectVulkan run that matched the DirectGLES picture passed, which is the "went
green on the wrong arm" shape the exit gate exists to catch. trace_cases.json
gains `backend_overrides.<backend>` ({golden, alternate_golden, ssim_threshold}
only: restating target_call or a crop would stop the two backends retracing the
same frame through the same window). A per-backend golden REPLACES the shared
pair, matching another backend's declared image is refused BY NAME, and a --case
named explicitly that does not run a requested backend is an error instead of one
plan fewer and no message. The CMake emitter and the APK matrix refuse a case that
declares the key rather than silently emitting one golden for both arms.
DirectGLES is unchanged and that is asserted, not asserted-in-prose: a case with no
override resolves to itself, and the key is dropped from the resolved case even for
the backend that overrides nothing - so giving DirectVulkan its own golden does not
move the DirectGLES identity fingerprint and does not invalidate its checkpoints.
Each results.json row now records the golden, alternate golden and threshold it was
scored against.
run_android_retrace_local.py gets --matrix (the CI split subset, 39 cases, each
running only its own ci_backends - not --all, which adds the rd12 workload ID-P7-4
excludes), --repeat N, and --archive-dir. The archive is the point: the result root
is keyed by case and backend and by NOTHING else, so repeat 2 overwrites repeat 1
and the only artefact that survives a three-repeat run is the last one, which makes
"were the three repeats the same picture?" unanswerable after the fact - in the one
place where re-running to find out is expensive. It also refuses --benchmark with a
non-monolith --transport, which has always produced a monolith run under a spawn
label (run_benchmark_case passes args.env alone; p6/README.md records the pitfall).
compare_actuals.py computes the pair result.json cannot: SSIM between two actual
PNGs, plus a case x repeat x ssim-vs-golden x ssim-vs-first-repeat table over an
archive. It is the retrace gate's OWN SSIM - a transcription of ComputeChannelSsim
/ ComputeRgbSsim (trace_replay_core.cpp:630/:671), the only SSIM in the tree - and
the crop comes from each repeat's own result.json, so the two columns describe the
same rectangle. PNG decode is stdlib zlib, because an unavailable Pillow in the
device window would turn "the repeats diverged" into "the tool did not run".
TraceReplayActivity: resolveSpawnServerPath's two decisions were String.contains()
on the whole joined K=V;K=V list and neither was entry-aware. The one that bit:
`--env MOBILEGL_IPC_SERVER_PATH` with no '=' is the documented UNSET spelling
(trace_env_overrides_test.cpp:92) and the only way to ask for "spawn with no image"
and watch LaunchServer refuse by name - it did not match "MOBILEGL_IPC_SERVER_PATH="
so a resolved path was appended AFTER it and, because ApplyEnvOverrides applies in
order with setenv(...,1), the deliberate unset was silently undone. That is exactly
the "silently replacing it would make that knob untestable" the method's own comment
forbids. Two more: the key inside another entry's VALUE flipped both decisions the
wrong way, and the transport match was case-sensitive while ConfigLoader lower-cases
the value before matching (ConfigLoader.cpp:322). The parse moves to
SpawnServerPath, a class with no android.* import, so test_android_lifecycle.py
compiles and runs it with plain javac - the parse had no host-side coverage at all,
and its on-device failure mode is "the knob had no effect".
Only spawn gets a path, deliberately: it is the only transport that execs an image.
Red-once, all four: a stub case whose result names the DirectGLES golden fails a
--backend DirectVulkan run with "matched a golden another backend declares"; a
synthetic PNG pair with a closed-form SSIM (identical = 1.0 exactly, constant
black vs white = C1/(255^2+C1), inverted halves = (C2-2v)/(2v+C2)); the pre-fix
resolveSpawnServerPath body fails eight of SpawnServerPathTest's checks; and the
DirectVulkan TCP proof battery is the DirectGLES one under the other prefix,
because an empty prefix selection raises nothing and reads as a clean pass.
CI: test_run_tcp_matrix.py and test_compare_actuals.py join the R-16 step. The
matrix driver's stub regressions had NO CI step - they were run by hand and
recorded in a note - and they guard the tool that decides whether a device golden
run passed.
Counts in WSL: test_run_tcp_matrix 12 -> 18, tcp_lane_tools 4 -> 6,
test_compare_actuals 12 new, test_android_lifecycle green with the new Java unit.
|
||
|
|
e19095269b |
[docs, MG_Remote] (P7): the roadmap rows say what the tree says, and CONTRACT-P7 is drafted
ROADMAP.md: the Ph / P3b-P4b / P7 rows carry the audit's verdicts and the factual
corrections it found - the @P7 refusal list is 15 sites in 4 files (the row printed 8),
the 29-commit DirectVulkan shape wave was never recorded, the device divergence is two
observations with no per-case attribution and no lockstep control, SetupDrawSnapshot /
content-addressed CSO are declined for this stage, "XFB scatter to client" is retired
in favour of the two real holes, readback/pack state splits into closed and open
halves, the NDK size pin is obsolete, PACK-PBO readback landed in
|
||
|
|
e8b2c4bd05 |
[docs, MG_Test] (P7): the Ph / P3b-P4b / P7 program plan, and the pull flavour builds its tests again
Plan for driving the roadmap to P7 close-out, from a per-item audit of the three
rows against
|
||
|
|
b95f5f1a20 | [docs] (P6.5): the first wave recorded as landed - review + fixes + representative re-validation, residual to CI | ||
|
|
fe28bdb5f3 | [MG_Remote, MG_Pipe, MG_Test, CI, tools, docs] (P6.5): the first wave completed and reviewed - archive v2, publication boundary, half-close, AcceptPair/role fixes, device re-validation | ||
|
|
018fab0f5b | [WIP] (MG_Remote): implement P6.5 TCP and stream transport | ||
|
|
242fd45cd7 | [Fix] (CI): read role logs and retry device disconnects | ||
|
|
9043442454 |
[MG_Test, CI] (P6): the last two readers the role split left behind
build-linux-verify could not compile PipeStatsTest: RoleDerivedJsonDumpPathForTesting is DISAGGREGATED-only and the verify flavour is deliberately monolith. The case name stays registered and GTEST_SKIPs there, matching the PIPE_PUSH pattern, so ctest -N still matches between flavours. runtime_mode_proof.py logs opened MOBILEGL_LOG_FILE_PATH as a file. After the split that name is a base and the sink writes <base>.client.log / <base>.server.log, so the inproc proof died on FileNotFoundError after a green lane. It now reads through split_log_paths' role helpers and names the paths it looked for when none exist. |
||
|
|
35b3283bd9 |
[docs] (P6): the closeout - gates 7 and 8 are measured and P6 is closed
The device gate: Redmi 2f7cbe2e, reboot-clean, paired A/B in one thermal window (arms interleaved, best-of-3 then best-of-2), rd12 + DirectGLES. inproc vs spawn client CPU p50 7.880/7.889 vs 7.876/7.869 (-0.15%) and 7.987/7.989 vs 7.992/8.009 (+0.16%) - a tie inside the 0.2-4.2% within- arm spread, sign flipping with arm order, recorded and not gated. monolith is ~30% dearer. 22/22 runner exits clean, 44/44 PINNED, zero Fatal. The three mandatory numbers: socket doorbell vs condvar is a tie too - client 121.1 waits/frame identical across transports, server 10 866-11 963 (inproc) vs 10 415-10 416 (spawn) waits/frame, and the contract's 'converting that into ~4 blocking reads may be cheaper' did not pay off; the only column where the socket is worse is clipark/f (6.07 vs 5.60/5.75). SEG_STAGE bytes/frame: 130.8 KB median / 8.01 MB steady (OpenRA), 816.2 KB / 1.53 MB (rd12). Records/frame post-chunking: 211/206 and 7 497/7 504. maxrec stays 1808 - the 8 MiB chunk budget is never reached. Both framings reported throughout (the startup window runs ~7x the steady state) rather than the flattering one. MEASUREMENTS.md gains section 12 with the full protocol and tables; the 6.3 'no post-chunking distribution exists' gap now points there. CURRENT_STAGE_PROGRESS.md marks gates 7/8 met and records the closeout work plus two structural limits (spawn client windowed fields are zero by construction - OnPresent runs in the server process - though its run totals are valid stores; the stats dump is role-derived). ROADMAP marks P6 closed in the stage table, the header and the milestone. Evidence: notes/p6/gate7-device-ab.md, gate8-doorbell-device.md, gate8-wire-counters.md, git-worktree-fix.md. check_doc_citations: 82 citations in the three documents, 0 problems. |
||
|
|
06b61bd84e |
[MG_Util, MG_Remote, tools] (P6): the wire counters and the spawn server's stats latch
Gate 8's last two numbers (SEG_STAGE bytes/frame, records/frame post- chunking) had no producer at all: the summary line's byte classes are backend-to-driver bytes and maxrec is a max, not a per-frame. Two push- guarded counters answer them at their single choke points - ByteClass:: StageSegmentBytes at PipeWireCodec's StageAllocate (the only allocation behind StageBytes, so every buffer walk, texture slab and CSO archive lands there and emitters cannot double-count) and CallClass::WireRecords on EncodeRecord's committed path (post-chunking an emitter no longer knows how many records its one call produced). A staged-blob histogram rides along. G1 holds: symbols 0/0/0/0, .text byte-identical, pull build carries no MG_Remote symbols. The spawn server never counted anything: PipeStats::Init() is a step of MobileGL::Initialize(), which ServerMain never reaches, so every Enabled() site in that process - including ServerLoop's own ServerWaits/ServerParks publish - stayed false and the server log carried no summary line at all (a zero that means 'another process' printed in the shape of one that means 'never waited' is the milder misreading; the line was simply absent). ServerMain now arms and closes PipeStats at its role boundary - after MG_ConfigLoader::Init (Init latches from the parsed config), after the server-role latch (the log sink's role), and before ::_exit(0). Red-once: 0 server summary lines before, 29 after, SSIM 1.0 unchanged. MOBILEGL_PIPE_STATS_FILE now derives per role through the log sink's exported RoleLogPath rule - run.client.json / run.server.json, the bare name resolves to no file - because both roles reach FormatJson() and ofstream trunc made the second writer replace the first. G1 caught three separate leaks on the way (a string temporary in the monolith body, one resized Shutdown() from unequal #if arms, two rewritten MGLOG_W literals in .rodata); the pull arm is statement-for-statement the original. Device tooling for the paired A/B lands with it: pin_device.sh gains a measured 2f7cbe2e entry (this board clamps the GPU at 1050 MHz - thermal_pwrlevel writes 0 and reads back 1, max_gpuclk is read-only; absolute times are not comparable with the 1100 MHz-era rows), and tools/device_bench/p6/ carries the session collector and renderer beside run_android_retrace_local.py's MOBILEGL_TRACE_PACKAGE override. Unit 2367/2367 (PipeStats 25/25), spawn integration 102/102. |
||
|
|
4e7bf0fe65 |
[docs] (P6.5): the design for the first wave - all-TCP transport, WSL client to adb-device server
ROADMAP gains the section "the immediate next item": a design, not an implementation, for running the frontend on an x86_64 WSL process and the backend on the Redmi over TCP, with the integration lane and the CI retrace subset as the acceptance. The P6.5 row now points at it. What the design rests on, verified in the tree: the control plane already treats the rendezvous as a NAME with two ordered connections (SocketTransport), ClientSession::StartOverSocket already separates "how the socket came" from the handshake, MGPBlobRef addresses kSegStage only through one ResolveOrFatal, the reply header and the event heads are already wire-shaped, LogLine exists in CtrlMsg with no producer, goldens are shared between desktop and device, and the trace APK can already exec libMobileGLServer.so from nativeLibraryDir. What it does not rest on, stated rather than assumed: ILink is declared and unused beyond LinkProgress, ShmLink does not exist, both sessions hold SessionSegments directly - the lk seam body from the end-state review was never built and is this wave's first package (lk2). The manifest has no INTERNET permission. The mechanism: StreamLink as mirrored private segments replicated by messages - same sizes both ends, same offsets, so the codec, the watermark rules, the apply thread and the encoder do not change; RingControl becomes Progress frames sent on flush-on-idle (the recorded amendment to "appliedSeq is never lazy": late by at most one flush, never early); replies become seq-stamped messages; the event ring is mirrored with the P5e blocking rule intact. Cross-architecture makes the PipeFields.def layout digest mandatory (wf). Server logs on another machine make LogLine forwarding mandatory (lf) so the 34 server-log readers stay unchanged. A --serve supervisor forks one child per session on the device, started by a foreground Service in the trace APK. Packages lk2 -> wf -> ct -> sl -> lf -> sv -> t65, each with red-once; the gate list, the mandatory numbers (kWaitReply per frame x RTT, SEG_STAGE bytes/frame against the link, PRESENT_CREDIT 1..3), the two-sided configuration runbook and the risks (RTT per reply row, single large uploads, AP isolation) are in the section. check_doc_citations: 74 citations, 0 problems. |
||
|
|
872370ba9d |
[docs, MG_Remote] (P6->P6.5): the end state is TCP across machines, and the transport stack has two axes
The end state is re-ruled: client and server may sit on different machines, OSes and architectures, joined over TCP. The AVF pVM / AF_VSOCK end state the P6 end-state review assumed is withdrawn (retraction at its head, form D added to its table, the vsock probe and both external unknowns struck). The transport stack becomes two independently selectable axes, negotiated in the handshake and mixable: control plane (ITransport: fork / unix:<path> / tcp://host:port) and data plane (ILink: ShmLink / StreamLink). On one machine control over TCP with data over shared segments is a legal pair; ShmLink brings its own AF_UNIX aux rendezvous for SCM_RIGHTS, so ITransport narrows to control frames. `auto` is decided by a successful segment delivery, never by inspecting addresses, and a fall-back is named. Nothing above Transport/ may branch on the link kind. ARCHITECTURE.md gains 11.9 for this; CONTRACT-P6.md gains 10.3. ROADMAP: P6.5 and Ph get rows (both were referenced by the contract and absent from the stage table); the IPC track becomes P6 -> P6.5 -> Ph -> P12 -> P9 -> P10 -> P11(same-machine arm). Every later row is reconciled against the tree: items already landed are struck (client vertex arrays, client-minted query handles, credit default 1, SEG_EVENT overflow mechanism), the unreachable P7 gate (`nm | grep glslang` on a server image that IS libMobileGL.so) is replaced by the 184-symbol ratchet share, fixed-width wire form moves from P7 to P6.5, P12 is cut down per the review (server-owned display, no Surface passing), and the debt table's stale claims are corrected: class C is down to two slots, not 15 and not 511; PACK-PBO and the 2 MiB reply cap are P9's, not P6's. Verified against the tree and recorded honestly: the contract's three hs rows (LinkTerms, Refuse, the fingerprint split) never landed - protocol.fbs has neither - so CURRENT_STAGE_PROGRESS.md's claim that S6 ran red is withdrawn and the rows move to P6.5; the 184-symbol ratchet gate does not exist in CI; the server process never initialises PipeStats, so gate 8's `srv=0 srvpark=0` is a structural zero (fix in progress in the tree, not in this commit). check_doc_citations.py: 159 citations, one pre-existing basename ambiguity. |
||
|
|
94c130d639 |
[MG_Util, MG_IntegrationTest, CI] (P6): the readers the role split left behind
The per-role log split renamed BOTH halves so that an un-updated reader would fail loudly instead of silently reading half a session. It worked: CI found four readers this way. This is them. - MG_Util/Debug/Log.h: the role vocabulary now exists in the PULL build too, header-only. It was wholly inside #if MOBILEGL_BUILD_DISAGGREGATED, so PipeInputsTest - which builds in the verify flavour - could not name ReadRoleLogs and took the whole build-linux-verify job down at compile time. Inline rather than out-of-line because of G1: nothing in the library calls these, so an inline definition emits code into the readers and NOTHING into libMobileGL.so. Verified: pull symbols 30570 -> 30570, 0 added / 0 removed, .text still 0xa52203, and zero role-log symbols in the pull library. - PipeStatsWindow.h: the harness copy of the naming rule is now flavour-aware. It derived `.client` in BOTH flavours, so in the verify build - one role, MOBILEGL_LOG_FILE_PATH written unchanged - it opened a file that never existed. The copy has to stay a copy: MG_Util's RoleLogPath is `t` (local) in the shipping .so this module links, so calling it does not link. Added LogMark/MarkLaneLog/ReadLaneLogSince: one offset PER ROLE, because a single scalar over the concatenation slides by whatever the other role wrote. - The four arming scenarios: they asserted on lines the BACKEND emits, and under inproc the backend runs on the apply thread - the server role - so the line lands in the server's log. Reading only the client's half reported the emulation unarmed, i.e. accused the product of a defect the reader invented. Four copies of the same private helper pair now delegate to the harness. - scripts/ci/magma_runahead_checks.py: it unlinked and read the base name as if it were a file, so the negative arm saw an empty log and could not prove the knob took. It now imports split_log_paths' derivation - the authoritative one - rather than becoming a third copy. Local verification (WSL, lk-split + c6-pull): - the 4 CI-red entries: 10/10 PASS under inproc, 10/10 under monolith - unit role-log readers: 510/510 PASS - magma_runahead_checks negative: run_ahead_0 EXPECTED_RED, restored GREEN - the 5 affected TUs compile clean with MOBILEGL_BUILD_DISAGGREGATED=0 - G1: 0/0/0/0, .text 0xa52203 Red-once is the CI run itself (35677991453), which failed for each of these reasons before the change. |
||
|
|
75dd0cb1ca |
[docs] (P6): the roadmap and the current-stage doc record P6 as landed-not-closed
All ten P6 packages (a6/c6/lk/so/sm/cp/hs/dl/st/t6) are on feat/disaggregated
through
|
||
|
|
77cbd17636 |
[MG_Remote] (P6/dl): record the Session::Fail funnel as landed in the contract
§5.2 gains a LANDED note: the three steps (the FatalFamilies.def vocabulary, the SessionFail funnel that byte-identically routes ~90 aborts through one place, and the SessionFault frame that names the family to the peer), and the one deliberate deviation from the prose - seq/op are not threaded through the 90 sites because the message already names the op, so the frame carries code, family and message rather than a field that is always zero. |
||
|
|
b46da7002c |
[MG_Remote] (P6/dl): a dying session names itself to the peer
Step three of CONTRACT-P6 5.2, the funnel's whole point: instead of the peer
reading a bare EOF and guessing, the session publishes a SessionFault frame
that names the family before it aborts.
THE WIRE CHANGE IS D2's, exactly: `table Fatal` appends `family: string`,
append-only, so an older peer still reads `code` and `message`. `code` stays
the coarse seven-value projection; `family` carries the full word.
SessionFail publishes it before the abort, on whichever session's control
plane is active - the client has a ClientSession, the server a ServerSession,
so under spawn the frame reaches the far side whichever role is dying. Under
inproc both exist and it goes to an in-process transport the aborting process
will never read, which is harmless and cheaper than a special case that would
then be the untested one. Best-effort by nature: this thread is about to die,
so a send that blocks or fails changes nothing.
seq/op from the prose are NOT threaded through the 90 sites: the message
already names the op where it matters, so the frame carries what it can name
honestly (code, family, message) rather than a field that is always zero.
The client recognizes it: RunRemoteSurfaceControlFrame's reply loop, which
already demuxes CapsSnapshot from SurfaceReply, gains a Fatal arm that logs the
family and detail, latches device-lost from it, and returns transport-closed.
A server that DETECTS a fatal condition and aborts itself now tells the client
why; a kill -9 still bypasses abort entirely and is caught by dl's hangup
witness instead - the two cover the two ways a server can die.
The census learns SessionFail: a SessionFail call, like a WireLogFatal call,
must carry a Fatal{ word in its string, so the enum and the string cannot
disagree. FatalFunnel.{h,cpp} join the funnel exemption. Baseline is 4 aborts.
FatalFamilyTest gains the wire round-trip: every family's frame carries its
word and its projected code across encode/verify/decode.
G1 re-measured after the schema change: 0 symbol diff, .text unchanged at
0xa52203. Unit 2359 (one pre-existing environmental failure), integration-split
186/186, integration-spawn 102/102, retrace 6/6, census 0 unmarked.
|
||
|
|
4bb4d4fa4f |
[MG_Remote] (P6/dl): every session death goes through one funnel
Step two of CONTRACT-P6 5.2. The ~90 scattered `MGLOG_F("...Fatal{...}"); std::abort();`
pairs under MG_Remote/ become one call each to SessionFail(MGFatalFamily, fmt, ...),
which logs, echoes to stderr, counts, and aborts in one place. a6's census
found no single chokepoint existed; now there is one, and step three hangs the
SessionFault-to-peer publish off it.
THE MESSAGE STRING IS PASSED VERBATIM. The contract requires the log lines, the
CI greps and every red-once byte-identical, so SessionFail does not compose the
line from the family - it takes the SAME string the site handed MGLOG_F and
forwards it unchanged. The family enum rides alongside, redundantly, so the
funnel has the coarse wire FatalCode without parsing the string. The census now
checks that redundancy the way it checks WireLogFatal: every SessionFail call's
string must carry a Fatal{ word, so the enum and the string cannot silently
disagree.
The conversion was mechanical - 89 sites by a transform that injects the family
as MGLOG_F's new first argument and drops the trailing abort - with three sites
fixed by hand that the transform would have broken: a stub macro, and two
if/else-then-shared-abort blocks (PersistentMapTracker's adopt-tier and
SurfaceOpCodec's three surface-error branches) where a naive rewrite stole the
abort from the earlier branches and would have let them fall through. The
death tests are the safety net for byte-identity, and every one stayed green.
Four std::abort() remain under MG_Remote/, all inside the two funnels
(WireLog.cpp, FatalFunnel.cpp); the census exempts both files because their
callers carry the family word, and its baseline drops from 93 aborts to 4.
SessionFaultCount() is the one telemetry point S8 will assert zero over a good
run.
G1 re-measured: 0 symbol diff, .text unchanged at 0xa52203 (FatalFunnel and
FatalFamily live under MG_Remote/, compiled only in the disaggregated build).
Unit 2358 (one pre-existing environmental failure), integration-split 186/186,
integration-spawn 102/102, retrace 6/6, census 0 unmarked.
|
||
|
|
3a12a48add |
[MG_Remote] (P6/dl): the Fatal-family vocabulary as a table, projected onto the wire's seven codes
CONTRACT-P6 5.2's D2 ruling, step one of the Session::Fail funnel: the internal vocabulary before the funnel that routes through it. a6 censused thirty family words against a seven-value wire FatalCode, so Session::Fail(FatalCode, detail) cannot carry the vocabulary in its first argument - the word would collapse to one of seven and the log would say ProtocolCorruption where the code said UnmigratedVerb. FatalFamilies.def is the internal enum; the wire keeps FatalCode; each row carries the coarse code the PEER is told when that family ends the session. THE PROJECTION IS A TOTAL TABLE, NOT A SWITCH WITH A DEFAULT. This build has no -Werror anywhere, so a default arm would silently swallow a family added without a code. A row IS the arm; a family with no row is not a value of the enum. FatalFamily.h generates the enum, the projection and the name from the one .def, so a family, its word and its code cannot drift apart. The buckets are documented per row in the .def: ProtocolCorruption for malformed records / unimplemented verbs / role invariants / reply-contract breaks; RingOverrun for a ring or event channel overrun; SegmentMismatch for a segment too small for what a role must place in it; AbiMismatch for layout or version disagreement; ServerCrashed for the liveness ends a WAITING peer attributes to the other side. DeviceLost gets no row on purpose - it is armed from the doorbell (5.3), never raised as a Fatal, and a test asserts no family projects onto it so an abort can never masquerade as a device loss. Five tests, all walking the table so a new row is covered without editing them: count agreement, every family projects to a non-None code, none projects onto DeviceLost, names distinct and non-empty, and a handful of anchors pinned by meaning so a bulk-edit drift is caught. No call site changes yet; the funnel that uses this is the next step. |
||
|
|
5fb54f03fe |
[MG_Util, MG_Test, MG_IntegrationTest, CI] (P6): one log per role, and every reader moved with it
The spawn work had the two roles append to one MOBILEGL_LOG_FILE_PATH under O_APPEND. It was correct - two processes at one offset would corrupt the file, O_APPEND made each write atomic - but it put two sessions' lines in one place and made every per-side assertion a search. This splits the sink: the client role writes <base>.client.log and the server role writes <base>.server.log. THE BASE NAME NAMES NO FILE ANY MORE, AND THAT IS THE POINT. Leaving the client at the old unsuffixed path would have been the compatible choice and the wrong one: a reader that was not updated would go on finding a file, go on passing, and go on seeing only half the session - and a refusal census that reports a confident zero for the half it cannot see is worse than one that fails. With both names moved, an un-updated reader gets "file not found" and says so. There were 34 such readers; every one is moved here. THE ROLE IS PER-THREAD, not per-process, because under inproc both roles are threads of one process. Log.cpp keys the sink on a thread-local role: the apply thread declares itself server (ServerLoop.cpp), a spawned image is server for its whole life (MOBILEGL_IPC_ROLE=server, read once), and everything else is the client. The naming rule (RoleLogPath) and the two whole-log operations (ReadRoleLogs / TruncateRoleLogs) are EXPORTED from the library, so no reader reimplements the derivation - a second copy is the copy that falls behind. Readers, by how they read: - unit mains set MOBILEGL_LOG_FILE_PATH and read a role file: they keep the base in the env and derive the client path from the library helper. - the death-test child runners (ReadLog) now concatenate BOTH roles: a death test asserts the CHILD said something, and the applier's refusals - the very diagnostics these check - are written under the server role. - the offset-based Emit readers derive the client path once after setenv. - PipeStatsWindow reads both, because draws/vbs increment in the backend's PrepareForDraw - the apply thread, the server role - so the summary line is in the server file even under inproc. - run_trace_case.cmake's split census reads both files; the client keeps the transport markers. - split_log_paths.py (and its self-test, and the control stub) derive both role files from the declared base. - the Android script pulls both role files off the device and concatenates them, so every downstream check is unchanged - and it judges a role file by CONTENT, because `run-as test -f` through `exec-out` returned 0 for a missing file on the device and `exec-out` folds cat's own not-found line into stdout. The monolith arm legitimately has no server file, and that is a silent skip rather than a one-line server log. Guarded so a pull build is untouched: RoleLogPath and the role sink are wholly inside MOBILEGL_BUILD_DISAGGREGATED; a non-disaggregated build writes the base path exactly as before. G1 re-measured - 0 symbol diff, .text unchanged at 0xa52203. Verified on device 2f7cbe2e: monolith writes only the client log (no phantom server log), inproc and spawn each write both and pass their transport proofs (spawn: server role in pid 16125). Unit 2353 (one pre-existing environmental failure), integration-split 186/186, integration-spawn 102/102, retrace 6/6, control smoke 12/12, split-log-paths self-test 9/9. |
||
|
|
15f5b54bbd |
[MG_IntegrationTest, CI] (P6/t6): integration-spawn, and the set identity made structural instead of maintained
Exit gates 9.2 and 9.3. Until now nothing ran the integration suite across a
process boundary at all - the spawn shape was proven only by the retrace lane,
which exercises one trace per case and no scenario assertions.
integration-spawn: 102 cases, 102 green.
THE SET IDENTITY IS THE POINT, AND IT IS NOT MAINTAINED BY HAND. 9.2 asks that
the spawn lane's case-name set equal the split lane's. Registering the spawn
arm as a second copy of each block would satisfy that today and drift the first
time somebody adds a scenario to one of them - and a spawn lane covering LESS
goes green while proving less, with nothing about a smaller green lane looking
wrong. The eleven split-environment blocks become eleven calls to one macro
that emits BOTH arms from one filter, so a case added to either is added to
both or to neither.
scripts/ci/spawn_lane_parity.py asserts it anyway, because a macro is one edit
from a hand-written block. It compares SETS rather than counts - a count can
agree while the membership does not - and carries exactly one sanctioned
asymmetry, named in the file: integration-split also labels the PUSH-MONOLITH
arm, whose scenario is monolith-only by construction. R-16: introduce a
split-only block and it reds naming the case and the macro to use.
THREE CASES SKIP UNDER SPAWN, BY NAME. CtWireScenario reads PipeApplier's
in-process tallies through ServerVerbs(); under spawn those live in the SERVER's
process and this one's copy is 0 forever, so the assertions were failing about
somebody else's memory. That is a harness limit, not a product defect - the
records themselves cross, which the retrace lane proves. They are SKIPPED and
not deregistered: a case dropped from the registration would make the two sets
differ silently, which is the drift the macro exists to prevent. What retires
the skip is cross-process telemetry, which is also what S8's SessionFaultCount
needs.
TWO REAL REGRESSIONS THE EXISTING GATES CAUGHT, both from this refactor:
* the macro dropped the per-block TEST_LIST variables, which were what
SplitLogPaths fed to give every entry a PRIVATE MOBILEGL_LOG_FILE_PATH.
SplitLogPaths.PrivateAndDistinct went red naming the first orphan. The
macro accumulates list NAMES now, so a block added later is covered without
anyone remembering to extend a list - and the names have to be unique,
because gtest_discover_tests' TEST_LIST is populated when CTEST runs, not
at configure time, so one shared variable held only the last block's
entries. That matters more on the spawn arm: there TWO PROCESSES append to
the path, so a shared file interleaves two sessions rather than merely
being ambiguous.
* the configure_file that substitutes those names ran BEFORE the F1 and Ct
blocks registered, so it captured neither. Same gate, naming
F1WireScenario.ClearBufferfiPixels. It now runs last.
junit_tally gains --require-spawn-ran beside --require-split-ran: the prefix IS
the arm, and a shared flag would let one lane tally the other's entries and call
itself run. Verified to discriminate - the spawn junit reads 96/0/5 under its
own flag and 0/0/0 under the split flag, which correctly fails.
Two death tests follow the ADOPT_TIER message the census commit retagged with
Fatal{UnimplementedAdoptTier}; their regexes asserted the old wording.
Unit 2353 (one pre-existing environmental failure), integration-split 186/186,
integration-spawn 102/102, retrace 6/6, fatal census 0 unmarked, G1 0 symbols
and .text unchanged at 0xa52203.
|
||
|
|
3399b8791c |
[MG_Remote, CI] (P6/dl): the Fatal census gate, and the one death under MG_Remote that named nothing
Exit gate S7, and the precondition for CONTRACT-P6 5.2's funnel rather than
its replacement.
S7 says "a bare abort() added outside Session::Fail -> the census gate goes
red". Session::Fail does not exist: it is the 93-site refactor 5.2 describes,
and it is not in this commit. What IS checkable today is the property that
funnel exists to guarantee and that every consumer already depends on - every
death under MG_Remote/ names a FAMILY, in a Fatal{Word} marker a grep can
find. run_trace_case.cmake reds a split retrace on any `Fatal{` line, the
retrace lane's refusal census counts them by name, and MEASUREMENTS quotes
the distinct words. A death with no word is invisible to all three.
TWO RULES, BECAUSE ONE WOULD LAUNDER THE OTHER. An abort must carry a marker
within twelve lines; and every WireLogFatal call must carry one in its format
string. WireLog.cpp's own abort is exempt from the first rule ONLY because its
five callers satisfy the second - a funnel that accepted an unmarked string
would be a hole shaped exactly like the rule.
THE BASELINE BOUNDS SILENCE, NOT DEATHS. Abort sites may grow freely; a new
family word may not appear unrecorded. Freezing the total would make every new
guard a gate change, which is how a gate stops being run. The wire's FatalCode
has seven values and the internal vocabulary is thirty-two, which is the
divergence D2 exists to bound - so growing it has to be an act, not an
accident.
THE CENSUS ALSO CORRECTS ITSELF. Its first run reported WireLog.h's own PROSE
as an unmarked death: the comment explaining why the stderr echo exists
contains the words std::abort(). So the scanner strips comments before
matching, with a character-level pass rather than a regex - a `"http://"`
inside a string literal is not a comment - while KEEPING string literals,
because the markers it looks for live in them. Real numbers after that fix:
93 abort sites, 32 family words, 0 unmarked. a6 had counted 92 and 30 from a
scan that did not make the distinction.
AND THE ONE REAL FINDING. a6 named two aborts with no marker at all;
WireLog.cpp's is the sanctioned funnel, so the genuine one is
PersistentMapTracker's invalid-MOBILEGL_IPC_ADOPT_TIER death. It now carries
Fatal{UnimplementedAdoptTier}, which is what makes the census zero.
R-16 both ways, run: a bare std::abort() reds it naming the file and line, and
a new family word reds it naming the word and how to record it.
|
||
|
|
e3bb6d8b82 |
[MG_Pipe, MG_Backend, MG_Impl] (P6/sm, P6/st): four guards that were compiled in and could never arm, and the crash behind one of them
CONTRACT-P6 3.2 (D1c), 3.3 (D13) and 3.4 (D10).
Every guard below asked a question a SERVER PROCESS answers wrongly - "is a
ClientSession active" (never, there) or "am I on the apply thread" (every
thread is, there) - so all four were present, compiled, and permanently
disarmed in the one shape they exist for. That is worse than absent: it reads
as coverage.
D13 FIRST, THEN THE WIDENING. MGPipeRoleSplitActive() meant "the REHEARSAL
knob is on AND the transport is real", so widening it in place would have left
a narrow name describing a wide predicate. It is renamed to
MGPipeRoleSplitRehearsalActive() across its 12 sites - including the
DualBlockScenario assertion message that embeds it - and the wider predicate
is introduced BESIDE it.
MGPipeBlocksAreDistinct() IS TRUE FOR TWO DIFFERENT REASONS, which is the
whole point: under the rehearsal the two blocks are distinct objects in one
process; under SPAWN they are distinct because they are in different ADDRESS
SPACES, and no knob is involved. The spawn retrace runs with
role-split-state=0 - measured off the lane's own log - so every guard gated on
the rehearsal was off there.
THE CRASH. MGPipeServerBlockNoteIdentity early-returned on the rehearsal, so a
spawn server's gPipeInputs.ContextIdentity() stayed nullptr for the life of the
process. DirectGLES' framebuffer binding-slot memo compares that identity FIRST
and with NO generation, against a cache variable that also starts nullptr - so
nullptr == nullptr reads as a CACHE HIT and the function returns a slot pointer
nothing ever filled. `sm` widens the gate; `st` lands the named Fatal
PipeInputs.h had already asked for, so a reopening says which memo and which
identity instead of corrupting memory three frames later. Guarded on
MOBILEGL_PIPE_PUSH: in a pull build that identity is the live GLContext's
address and is never null, so the test would be dead code and G1 measures that.
DISJUNCTS, NOT SUBSTITUTIONS - and this was measured the hard way. The first
attempt REPLACED OnApplyThread() with MGPipeServerArm() and
ClientSession::Active() with MGPipeSessionLive(). 14 RemoteGuards and
RemoteClientControls cases went green by omission: a ServerLoop fixture with no
client session has only the original facts, so swapping them out disarmed every
test that drives the apply thread directly. The originals stay; the new
predicates are added beside them.
AND THE PROBE HAD TO BE THE RIGHT ONE. Installing
ClientSession::ApplyThreadIsInsideApplier as the apply-thread probe made every
GL-thread call a role violation whenever the applier happened to be busy - the
inproc retrace died on Fatal{RoleViolation, "MGPipeSlots"} raised from the GL
thread. "Am I the apply thread" and "is the apply thread inside the applier"
are different questions; the second is R-1's mutual-exclusion probe and is
about another thread entirely. ServerLoop::OnApplyThread is the one the guards
mean.
Three of D1c's six predicates land here, the three the four guards need. The
other three (MGPipeSplitActive, MGPipeShouldDial, MGPipeBackendIsLocal) have no
D10 site and are not added as dead code; their call sites are ContextEpoch and
ActiveXfbState, which this commit does not touch.
D14 holds: the non-disaggregated build gets constexpr false definitions, not
runtime-false predicates, so every consult folds at compile time. G1
re-measured - 0 symbol diff, .text unchanged at 0xa52203.
Two test names added, both falsifiable by construction: the spawn case asserts
the blocks are distinct with the rehearsal knob OFF, and the monolith case
asserts the predicate is not simply a constant. Unit suite 2353 tests, one
pre-existing environmental failure. All six OpenRA arms green.
|