Recover entity clipping loop scheduling and sentinel normalization
Browse files- docs/ASSET_PIPELINE_TODO.md +2 -0
- docs/FINDINGS.md +6 -0
- docs/PROVISIONAL_TODO.md +1 -1
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +5 -0
- docs/research/COLLISION_LOOP.md +59 -0
- docs/research/README.md +2 -0
- docs/research/collision-loop.json +0 -0
- scripts/analyze_collision_composition.py +25 -12
- scripts/analyze_collision_loop.py +114 -0
- scripts/test_collision_loop.py +96 -0
docs/ASSET_PIPELINE_TODO.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
| 2 |
|
| 3 |
Purpose: analyse **every file/member variant**, then produce reproducible assets for the Rust/Bevy/Solari runtime. The originals remain immutable research inputs. Archive overrides and case-insensitive lookup must be established before producing a runtime bundle. PK3 extraction is now implemented in `scripts/unpack_pk3.py`; [verified findings and manifests](extraction/README.md) record its completed scope. Conversion tools remain pending.
|
| 4 |
|
|
|
|
|
|
|
| 5 |
## Planned tool catalogue
|
| 6 |
|
| 7 |
Names and output formats below are proposals to refine after the relevant research gates. They may become Python scripts, Rust CLI subcommands, or both. Runtime-critical parsers and shared schemas should have one authoritative implementation; orchestration scripts must not diverge from it.
|
|
|
|
| 2 |
|
| 3 |
Purpose: analyse **every file/member variant**, then produce reproducible assets for the Rust/Bevy/Solari runtime. The originals remain immutable research inputs. Archive overrides and case-insensitive lookup must be established before producing a runtime bundle. PK3 extraction is now implemented in `scripts/unpack_pk3.py`; [verified findings and manifests](extraction/README.md) record its completed scope. Conversion tools remain pending.
|
| 4 |
|
| 5 |
+
The [entity-clipping loop collector](research/COLLISION_LOOP.md) extends binary inspection with bounded count/order/sentinel evidence. Reproduce with `python3 scripts/analyze_collision_loop.py`; its fixtures inform future collision sidecar validation without executing original geometry.
|
| 6 |
+
|
| 7 |
## Planned tool catalogue
|
| 8 |
|
| 9 |
Names and output formats below are proposals to refine after the relevant research gates. They may become Python scripts, Rust CLI subcommands, or both. Runtime-critical parsers and shared schemas should have one authoritative implementation; orchestration scripts must not diverge from it.
|
docs/FINDINGS.md
CHANGED
|
@@ -300,3 +300,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
|
|
| 300 |
**F-080 — Observed byte-copy/status composition:** closer-hit paths copy 56 bytes, overwrite the candidate identifier from object word zero, and OR saved prior/intermediate status into result +4. Byte-addressed fixtures preserve copy boundaries and distinguish status from payload selection. Canonical field values and runtime conformance remain open. [MIX-002](research/COLLISION_COMPOSITION.md).
|
| 301 |
|
| 302 |
**F-081 — Observed world-result decisions:** fraction exactly one assigns identifier 1023; other tested finite values assign 1022. Positive/negative zero skips entity clipping toward result copying. These are supplied-result instruction slices, not executed world collisions or complete sentinel semantics. [MIX-003](research/COLLISION_COMPOSITION.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
**F-080 — Observed byte-copy/status composition:** closer-hit paths copy 56 bytes, overwrite the candidate identifier from object word zero, and OR saved prior/intermediate status into result +4. Byte-addressed fixtures preserve copy boundaries and distinguish status from payload selection. Canonical field values and runtime conformance remain open. [MIX-002](research/COLLISION_COMPOSITION.md).
|
| 301 |
|
| 302 |
**F-081 — Observed world-result decisions:** fraction exactly one assigns identifier 1023; other tested finite values assign 1022. Positive/negative zero skips entity clipping toward result copying. These are supplied-result instruction slices, not executed world collisions or complete sentinel semantics. [MIX-003](research/COLLISION_COMPOSITION.md).
|
| 303 |
+
|
| 304 |
+
**F-082 — Observed clip entry gates:** signed count <=0 or nonzero current status0 stops processing; positive odd counts start ordinary lane 0 and positive even counts start peeled lane 2. Paired synthetic instruction slices establish parity routing, not area-query counts/order or runtime reachability. [LOOP-001](research/COLLISION_LOOP.md).
|
| 305 |
+
|
| 306 |
+
**F-083 — Observed clipping scheduling invariant:** only lane 0 checks incremented counter == count; the paired and peeled continuations rely on parity and stop on nonzero status0. Supplied compositions visit consecutive four-byte array entries through counts 1023/1024. Count ownership, candidate bodies/callees and runtime safety remain open. [LOOP-002](research/COLLISION_LOOP.md).
|
| 307 |
+
|
| 308 |
+
**F-084 — Observed related sentinel normalization:** pass 1023 skips entity lookup and writes 0xffffffff; other passes request lookup, whose supplied object +0x200 word is independently normalized from 1023 to 0xffffffff. Complete entity bounds, ownership semantics and runtime validity remain unverified. [LOOP-003](research/COLLISION_LOOP.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -33,7 +33,7 @@ For implementation items, the minimum done condition is a documented contract, a
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
-
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. The [RE-277–279 location pass](research/CHAT_LOCATIONS.md) adds leader selection, cache/format boundaries and location-list/spatial evidence. The [RE-281–283 crosshair pass](research/CHAT_CROSSHAIR.md) adds finite geometry fixtures, trace arguments and result routing. The [RE-285–287 native spatial pass](research/NATIVE_SPATIAL.md) confirms Linux trig/spatial services, VM pointer branches and collision/query evidence. The [RE-289–291 collision pass](research/NATIVE_COLLISION.md) adds native candidate filters, result decisions and model/field evidence. The [RE-293–295 composition pass](research/COLLISION_COMPOSITION.md) adds three-lane byte-copy/status agreement and world-result routing. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
|
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
+
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. The [RE-277–279 location pass](research/CHAT_LOCATIONS.md) adds leader selection, cache/format boundaries and location-list/spatial evidence. The [RE-281–283 crosshair pass](research/CHAT_CROSSHAIR.md) adds finite geometry fixtures, trace arguments and result routing. The [RE-285–287 native spatial pass](research/NATIVE_SPATIAL.md) confirms Linux trig/spatial services, VM pointer branches and collision/query evidence. The [RE-289–291 collision pass](research/NATIVE_COLLISION.md) adds native candidate filters, result decisions and model/field evidence. The [RE-293–295 composition pass](research/COLLISION_COMPOSITION.md) adds three-lane byte-copy/status agreement and world-result routing. The [RE-297–299 loop pass](research/COLLISION_LOOP.md) adds signed count/status gates, parity scheduling and related sentinel normalization. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -157,3 +157,7 @@ Completed bounded/static RE-289–291 with `analyze_native_collision.py`: ten na
|
|
| 157 |
## 2026-10-11 — Collision copy/status and world-result composition
|
| 158 |
|
| 159 |
Completed bounded RE-293–295 with `analyze_collision_composition.py`: paired clip/trace graphs, 216 complete merge/copy slices spanning three lanes and twelve world-result decisions. MIX-001–003/F-079–081 retain supplied geometry results, raw noncanonical prior-bit fixtures and exceptional/runtime limits. Added a separate restricted byte-addressed evaluator for overlapping native 32/64-bit copies, OR/status branches and register alias behavior. Eight tests check final words, lane agreement, tie retention, copy canaries, world sentinels/zero decisions, register/byte aliasing and failure domains. RE-292 remains open; RE-296 tracks full loop scheduling, producer/layout/sentinel/geometry/runtime/platform behavior. No original process or collision callee executes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
## 2026-10-11 — Collision copy/status and world-result composition
|
| 158 |
|
| 159 |
Completed bounded RE-293–295 with `analyze_collision_composition.py`: paired clip/trace graphs, 216 complete merge/copy slices spanning three lanes and twelve world-result decisions. MIX-001–003/F-079–081 retain supplied geometry results, raw noncanonical prior-bit fixtures and exceptional/runtime limits. Added a separate restricted byte-addressed evaluator for overlapping native 32/64-bit copies, OR/status branches and register alias behavior. Eight tests check final words, lane agreement, tie retention, copy canaries, world sentinels/zero decisions, register/byte aliasing and failure domains. RE-292 remains open; RE-296 tracks full loop scheduling, producer/layout/sentinel/geometry/runtime/platform behavior. No original process or collision callee executes.
|
| 160 |
+
|
| 161 |
+
## 2026-10-11 — Entity-clipping loop and sentinel normalization
|
| 162 |
+
|
| 163 |
+
Completed bounded RE-297–299 with `analyze_collision_loop.py`: paired verified graphs, 72 entry/20 normalization/360 continuation slices and 70 scheduling compositions, including supplied counts 1023/1024. LOOP-001–003/F-082–084 distinguish equality-only count guards from a proven runtime safety invariant and pass lookup requests from executed callees. Added restricted signed TEST/CMP branches, simple LEA/ADDQ/low-byte tests and opt-in register capture to the byte evaluator. Eight tests independently check parity/order/counter/pointer/status/sentinel behavior, paired outputs, flag preservation and ambiguous/unmodeled inputs. The full suite passes 206 tests; old/new collectors regenerate byte-identical reports, documentation links and storage policy checks pass. RE-296 remains open; RE-300 tracks count/order/lookup ownership, candidate/callee state and full collision/runtime/platform coverage. No original process, area query, entity lookup or collision callee executes.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -352,3 +352,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 352 |
- [x] RE-294 Recover complete 56-byte copy, prior status OR, identifier/payload retention and register/byte-alias fixture semantics ([MIX-002](research/COLLISION_COMPOSITION.md)).
|
| 353 |
- [x] RE-295 Recover native world-result sentinel assignment and positive/negative-zero branch skipping entity clipping ([MIX-003](research/COLLISION_COMPOSITION.md)).
|
| 354 |
- [ ] RE-296 Validate whole multi-candidate/world/entity loops, prior status reachability and early exits, canonical result producers/layout/sentinels, exceptional floats and original-runtime/all-platform fixtures.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
- [x] RE-294 Recover complete 56-byte copy, prior status OR, identifier/payload retention and register/byte-alias fixture semantics ([MIX-002](research/COLLISION_COMPOSITION.md)).
|
| 353 |
- [x] RE-295 Recover native world-result sentinel assignment and positive/negative-zero branch skipping entity clipping ([MIX-003](research/COLLISION_COMPOSITION.md)).
|
| 354 |
- [ ] RE-296 Validate whole multi-candidate/world/entity loops, prior status reachability and early exits, canonical result producers/layout/sentinels, exceptional floats and original-runtime/all-platform fixtures.
|
| 355 |
+
|
| 356 |
+
- [x] RE-297 Recover paired signed-count/status entry gates and parity-selected first candidate lane ([LOOP-001](research/COLLISION_LOOP.md)).
|
| 357 |
+
- [x] RE-298 Recover three continuation count/pointer/status rules and bounded scheduling compositions through capacity boundary fixtures ([LOOP-002](research/COLLISION_LOOP.md)).
|
| 358 |
+
- [x] RE-299 Recover pass/related sentinel normalization and isolated lookup request boundaries ([LOOP-003](research/COLLISION_LOOP.md)).
|
| 359 |
+
- [ ] RE-300 Recover area-query count/order ownership and legal bounds, entity lookup arithmetic/validity, all candidate body/callee state effects, collision-produced status reachability and full geometry/original-runtime/all-platform scheduling conformance.
|
docs/research/COLLISION_LOOP.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Entity-clipping count, scheduling and sentinel normalization
|
| 2 |
+
|
| 3 |
+
Static/bounded pass, 2026-10-11. Reproduce with `python3 scripts/analyze_collision_loop.py`; check with `python3 scripts/test_collision_loop.py`. [Generated evidence](collision-loop.json) verifies original and disassembly identities for both Linux ELF64 engines and records **72 entry slices, 20 normalization slices, 360 continuation slices and 70 scheduling compositions**. This follows [result-copy composition](COLLISION_COMPOSITION.md) and [candidate exclusions](NATIVE_COLLISION.md).
|
| 4 |
+
|
| 5 |
+
No original process, area query, entity lookup or collision callee executes. Scheduling compositions skip each candidate body and explicitly supply its resulting current status0. The report retains actual bounded instruction traces for counts through 32; counts 1023/1024 retain all visits, a trace digest/instruction count and the final continuation trace. Those large counts are supplied boundaries, not observed runtime workloads.
|
| 6 |
+
|
| 7 |
+
## LOOP-001: entry count, status and parity gates
|
| 8 |
+
|
| 9 |
+
After SV_AreaEntities_r, the native code reads candidate count from stack +0x1088 and pass word from clip +0x74. The query setup statically initializes count zero, array pointer stack +0x30 and capacity 1024 at stack +0x108c. Query traversal/order/count production remains unexecuted.
|
| 10 |
+
|
| 11 |
+
The count gate uses a signed 32-bit `testl`/`jle`: counts <=0 return to cleanup. Positive counts next test current clip +0x3c (result status0); **any nonzero value** returns. These entry checks precede candidate processing, but follow pass/related normalization, so zero count does not prove that pass lookup is skipped.
|
| 12 |
+
|
| 13 |
+
With positive count and status0 zero, counter r13d becomes zero, array pointer r12 becomes its supplied base, and clip +0x30 is saved as an endpoint pointer. Testing bit zero of the low byte of count-1 selects:
|
| 14 |
+
|
| 15 |
+
| Count | First candidate lane | Subsequent lane order if status0 stays zero |
|
| 16 |
+
| --- | --- | --- |
|
| 17 |
+
| Positive odd | Ordinary lane 0 | 0, 1, 0, 1, …, 0 |
|
| 18 |
+
| Positive even | Peeled lane 2 | 2, 0, 1, 0, …, 0 |
|
| 19 |
+
|
| 20 |
+
The 36 entry cases per engine cover raw count bits -1, 0, 1–4, 1023, 1024 and 0x80000000 with status0 0/1/4/0xffffffff. Signed-negative count patterns terminate; noncanonical status values are explicitly synthetic probes.
|
| 21 |
+
|
| 22 |
+
## LOOP-002: three continuations and bounded scheduling invariant
|
| 23 |
+
|
| 24 |
+
Lane numbers match the preceding merge/copy report. The native continuation rules are:
|
| 25 |
+
|
| 26 |
+
| Completed lane | Counter and array advance | Next decision |
|
| 27 |
+
| --- | --- | --- |
|
| 28 |
+
| 0, ordinary first lane | Increment counter; if it is not equal to count, form second pointer = first pointer +4 | Equality returns before reading status0; otherwise nonzero status0 returns, zero enters lane 1 |
|
| 29 |
+
| 1, paired second lane | First pointer = second pointer +4; increment counter | Nonzero status0 returns; zero enters lane 0 |
|
| 30 |
+
| 2, initial peel | Increment counter; first pointer +=4 | Nonzero status0 returns; zero enters lane 0 |
|
| 31 |
+
|
| 32 |
+
Only lane 0 has a count comparison, and it checks **equality**, not >=. Lanes 1 and 2 depend on the parity-established invariant. For positive odd counts, lane 0 completes the final candidate; for positive even counts, the peel advances once, then lane 0 completes the final candidate. Supplied status0 stops are checked after every processed candidate. These continuation slices do not read status4 or the fraction; they do not independently stop on those values.
|
| 33 |
+
|
| 34 |
+
Each engine has 180 isolated continuation cases: three lanes, counts 1–4, incoming counters 0–4 and status0 0/1/4. Deliberately inconsistent counter/count combinations demonstrate the absence of a >= guard; they do not establish that such combinations are reachable in the game. Tests independently check next lane, counter and pointer movement.
|
| 35 |
+
|
| 36 |
+
The 35 scheduling compositions per engine cover counts 1–8, 31, 32, 1023 and 1024. Each uses no status stop, a first-candidate stop and a final-candidate stop (duplicates removed for count 1). Every observed visit uses array byte offset 4*i exactly once and follows the parity sequence above; final counter equals visits. The two engines agree for all tested outputs. This proves the bounded scheduling composition under supplied count, unchanged count/counter and injected status assumptions; it does not recover area-query traversal, filtering/intersection results, callee mutation or runtime order.
|
| 37 |
+
|
| 38 |
+
## LOOP-003: pass and related-word sentinel handling
|
| 39 |
+
|
| 40 |
+
Before the count gate, pass word **1023** skips SV_GentityNum and writes **0xffffffff** to stack +0x2c. Other pass words request SV_GentityNum(pass) without an observed range check in this slice. The collector stops before that call and records the requested argument; even invalid-looking supplied values do not execute the lookup.
|
| 41 |
+
|
| 42 |
+
In separate post-lookup slices, supplied returned-object field +0x200 is copied to stack +0x2c. If that word equals **1023**, an out-of-line branch overwrites it with **0xffffffff**. Other tested words (0, 7, 1022, 0xffffffff) retain their bits. Thus both sentinel paths converge on a -1-shaped related word, but only pass==1023 bypasses lookup. Negative/invalid pass interpretation, legal entity range and returned-object validity remain open.
|
| 43 |
+
|
| 44 |
+
This supplies the previously isolated related input in candidate exclusions. It does not prove field +0x200's ownership semantics or establish every producer of pass/related values.
|
| 45 |
+
|
| 46 |
+
## Address correspondence and evaluator scope
|
| 47 |
+
|
| 48 |
+
| Site | Linux dedicated | Linux client |
|
| 49 |
+
| --- | --- | --- |
|
| 50 |
+
| After area query | 0x4155bb | 0x47d53b |
|
| 51 |
+
| Pass lookup call / after call | 0x4155d2 / 0x4155d7 | 0x47d552 / 0x47d557 |
|
| 52 |
+
| Count gate | 0x4155ed | 0x47d56d |
|
| 53 |
+
| Lane 0 / 1 / 2 entries | 0x41561e / 0x4157a9 / 0x415957 | 0x47d59e / 0x47d729 / 0x47d8d7 |
|
| 54 |
+
| Lane 0 / 1 / 2 continuations | 0x41578c / 0x415930 / 0x415acc | 0x47d70c / 0x47d8b0 / 0x47da4c |
|
| 55 |
+
| Cleanup stop | 0x415945 | 0x47d8c5 |
|
| 56 |
+
|
| 57 |
+
Sites are selected by unique instruction patterns and adjacent call identity, with ambiguous/changed patterns rejected. The byte-addressed evaluator adds signed TEST/CMP-to-JLE, low-byte TEST, simple signed-displacement LEA, ADDQ and optional final register capture. LEA and MOV preserve flags. Signed branches after unmodeled arithmetic flags fail closed. Indexed addressing, original calls, frame unwinding and exceptional float behavior remain unsupported. Existing composition report shape stays unchanged unless register capture is requested.
|
| 58 |
+
|
| 59 |
+
For Rust collision specifications, preserve stable candidate-order and first-stop semantics in fixtures, separate sentinel normalization from candidate exclusion, and establish a checked count/array/identity contract before choosing an iterator implementation. RE-300 remains open for that full contract, all candidate bodies and collision-result reachability, lookup address arithmetic/bounds, actual geometry and original-runtime/macOS/Windows/Linux32 equivalence. Broad collision and port milestones remain unchecked.
|
docs/research/README.md
CHANGED
|
@@ -103,3 +103,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 103 |
[Native entity collision filters and decisions](NATIVE_COLLISION.md) adds RE-289–291: paired first-lane filter/result probes, result-field relationships and model/transform paths. Reproduce with `python3 scripts/analyze_native_collision.py`; check with `python3 scripts/test_native_collision.py`. Full collision geometry/lane composition/runtime/platform behavior remains open.
|
| 104 |
|
| 105 |
[Collision copy/status composition and world decisions](COLLISION_COMPOSITION.md) adds RE-293–295: all three bounded merge/copy lanes, byte-addressed payload/status preservation and world sentinel/zero-fraction routing. Reproduce with `python3 scripts/analyze_collision_composition.py`; check with `python3 scripts/test_collision_composition.py`. Whole-loop/geometry/exception/runtime/platform behavior remains open.
|
|
|
|
|
|
|
|
|
| 103 |
[Native entity collision filters and decisions](NATIVE_COLLISION.md) adds RE-289–291: paired first-lane filter/result probes, result-field relationships and model/transform paths. Reproduce with `python3 scripts/analyze_native_collision.py`; check with `python3 scripts/test_native_collision.py`. Full collision geometry/lane composition/runtime/platform behavior remains open.
|
| 104 |
|
| 105 |
[Collision copy/status composition and world decisions](COLLISION_COMPOSITION.md) adds RE-293–295: all three bounded merge/copy lanes, byte-addressed payload/status preservation and world sentinel/zero-fraction routing. Reproduce with `python3 scripts/analyze_collision_composition.py`; check with `python3 scripts/test_collision_composition.py`. Whole-loop/geometry/exception/runtime/platform behavior remains open.
|
| 106 |
+
|
| 107 |
+
[Entity-clipping loop and sentinel normalization](COLLISION_LOOP.md) adds RE-297–299: signed count/status entry gates, parity-selected lanes, bounded continuation/scheduling compositions and pass/related sentinel normalization. Reproduce with `python3 scripts/analyze_collision_loop.py`; check with `python3 scripts/test_collision_loop.py`. Count/order/lookup ownership, candidate/callee/geometry behavior and runtime/platform conformance remain open.
|
docs/research/collision-loop.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_collision_composition.py
CHANGED
|
@@ -31,15 +31,15 @@ def get_word(m,address,width=4):
|
|
| 31 |
return sum(m[address+i]<<(8*i) for i in range(width))
|
| 32 |
|
| 33 |
|
| 34 |
-
def evaluate(graph,start,stops,memory,registers,limit=128):
|
| 35 |
rows=graph['instructions'];indices={x['address']:i for i,x in enumerate(rows)}
|
| 36 |
-
regs={register(k)[0]:v for k,v in registers.items()};m=dict(memory);equal=ge=False;pc=start;trace=[];writes=[]
|
| 37 |
def read_reg(name):
|
| 38 |
key,size=register(name)
|
| 39 |
if key not in regs:raise ValueError('unknown register')
|
| 40 |
return regs[key]&((1<<size)-1)
|
| 41 |
def address(arg):
|
| 42 |
-
match=re.fullmatch(r'(0x[0-9a-f]+)?\((%\w+)\)',arg)
|
| 43 |
if not match:raise ValueError('unsupported memory operand')
|
| 44 |
return read_reg(match[2])+int(match[1] or '0',0)
|
| 45 |
def read(arg,width):
|
|
@@ -61,24 +61,37 @@ def evaluate(graph,start,stops,memory,registers,limit=128):
|
|
| 61 |
if op=='nop':pass
|
| 62 |
elif op in ['movl','movq','movss']:
|
| 63 |
a,b=args.split(', ');width=8 if op=='movq' else 4;write(b,read(a,width),width)
|
| 64 |
-
elif op in ['
|
| 65 |
-
a,b=args.split(', ');
|
| 66 |
-
elif op in ['
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
result=v^w if op=='xorl' else v|w if op=='orl' else v+w
|
| 69 |
-
write(b,result,
|
|
|
|
| 70 |
elif op=='xorps':
|
| 71 |
a,b=args.split(', ')
|
| 72 |
if a!=b:raise ValueError('unsupported SIMD xor')
|
| 73 |
write(b,0,4) # Does not change integer flags.
|
| 74 |
elif op=='comiss':
|
| 75 |
-
a,b=args.split(', ');v,w=number(read(a,4)),number(read(b,4));equal=v==w;ge=w>=v
|
| 76 |
elif op=='sete':write(args,int(equal),1)
|
| 77 |
-
elif op in ['je','jne','jae','jmp']:
|
| 78 |
-
|
|
|
|
| 79 |
if taken:pc=int(args.split()[0],16)
|
| 80 |
else:raise ValueError('unsupported composition opcode '+op)
|
| 81 |
-
|
|
|
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
def lane_sites(graph):
|
|
|
|
| 31 |
return sum(m[address+i]<<(8*i) for i in range(width))
|
| 32 |
|
| 33 |
|
| 34 |
+
def evaluate(graph,start,stops,memory,registers,limit=128,capture_registers=False):
|
| 35 |
rows=graph['instructions'];indices={x['address']:i for i,x in enumerate(rows)}
|
| 36 |
+
regs={register(k)[0]:v for k,v in registers.items()};m=dict(memory);equal=ge=False;signed_le=None;pc=start;trace=[];writes=[]
|
| 37 |
def read_reg(name):
|
| 38 |
key,size=register(name)
|
| 39 |
if key not in regs:raise ValueError('unknown register')
|
| 40 |
return regs[key]&((1<<size)-1)
|
| 41 |
def address(arg):
|
| 42 |
+
match=re.fullmatch(r'(-?0x[0-9a-f]+)?\((%\w+)\)',arg)
|
| 43 |
if not match:raise ValueError('unsupported memory operand')
|
| 44 |
return read_reg(match[2])+int(match[1] or '0',0)
|
| 45 |
def read(arg,width):
|
|
|
|
| 61 |
if op=='nop':pass
|
| 62 |
elif op in ['movl','movq','movss']:
|
| 63 |
a,b=args.split(', ');width=8 if op=='movq' else 4;write(b,read(a,width),width)
|
| 64 |
+
elif op in ['leal','leaq']:
|
| 65 |
+
a,b=args.split(', ');write(b,address(a),4 if op=='leal' else 8)
|
| 66 |
+
elif op in ['testl','testb','cmpl']:
|
| 67 |
+
width=1 if op=='testb' else 4
|
| 68 |
+
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 69 |
+
def signed(value):
|
| 70 |
+
sign=1<<(width*8-1)
|
| 71 |
+
return (value^sign)-sign
|
| 72 |
+
equal=v==w if op=='cmpl' else (v&w)==0
|
| 73 |
+
signed_le=signed(w)<=signed(v) if op=='cmpl' else signed(v&w)<=0
|
| 74 |
+
elif op in ['xorl','orl','addl','addq']:
|
| 75 |
+
width=8 if op=='addq' else 4
|
| 76 |
+
a,b=args.split(', ');v,w=read(a,width),read(b,width)
|
| 77 |
result=v^w if op=='xorl' else v|w if op=='orl' else v+w
|
| 78 |
+
write(b,result,width);equal=(result&((1<<(width*8))-1))==0
|
| 79 |
+
signed_le=None
|
| 80 |
elif op=='xorps':
|
| 81 |
a,b=args.split(', ')
|
| 82 |
if a!=b:raise ValueError('unsupported SIMD xor')
|
| 83 |
write(b,0,4) # Does not change integer flags.
|
| 84 |
elif op=='comiss':
|
| 85 |
+
a,b=args.split(', ');v,w=number(read(a,4)),number(read(b,4));equal=v==w;ge=w>=v;signed_le=None
|
| 86 |
elif op=='sete':write(args,int(equal),1)
|
| 87 |
+
elif op in ['je','jne','jae','jle','jmp']:
|
| 88 |
+
if op=='jle' and signed_le is None:raise ValueError('unmodeled signed flags')
|
| 89 |
+
taken=op=='jmp' or (equal if op=='je' else not equal if op=='jne' else signed_le if op=='jle' else ge)
|
| 90 |
if taken:pc=int(args.split()[0],16)
|
| 91 |
else:raise ValueError('unsupported composition opcode '+op)
|
| 92 |
+
result={'trace':trace,'stop':pc,'memory':m,'writes':writes}
|
| 93 |
+
if capture_registers:result['registers']=regs
|
| 94 |
+
return result
|
| 95 |
|
| 96 |
|
| 97 |
def lane_sites(graph):
|
scripts/analyze_collision_loop.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Recover bounded Linux entity-clipping scheduling, without executing callees."""
|
| 3 |
+
import gzip
|
| 4 |
+
import hashlib
|
| 5 |
+
import itertools
|
| 6 |
+
import json
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from analyze_collision_composition import evaluate, get_word, lane_sites, put_word
|
| 9 |
+
from analyze_engine_control import control_flow
|
| 10 |
+
from analyze_qvm import write_json
|
| 11 |
+
from analyze_vm_extensions import native_functions, verified
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def sites(graph):
|
| 15 |
+
rows=graph['instructions']
|
| 16 |
+
def index(op,args):
|
| 17 |
+
found=[i for i,x in enumerate(rows) if x['opcode']==op and x['operands'].split('#')[0].strip()==args]
|
| 18 |
+
if len(found)!=1:raise ValueError('ambiguous loop site '+args)
|
| 19 |
+
return found[0]
|
| 20 |
+
def addr(op,args):return rows[index(op,args)]['address']
|
| 21 |
+
gate=index('testl','%r15d, %r15d')
|
| 22 |
+
if rows[gate+1]['opcode']!='jle':raise ValueError('count branch changed')
|
| 23 |
+
exit_addr=int(rows[gate+1]['operands'].split()[0],16)
|
| 24 |
+
query=[i for i,x in enumerate(rows) if '<SV_AreaEntities_r>' in x['operands']]
|
| 25 |
+
if len(query)!=1:raise ValueError('query call changed')
|
| 26 |
+
def candidate_entry(args):
|
| 27 |
+
found=[x['address'] for i,x in enumerate(rows[:-1]) if x['opcode']=='movl'
|
| 28 |
+
and x['operands']==args and '<SV_GentityNum>' in rows[i+1]['operands']]
|
| 29 |
+
if len(found)!=1:raise ValueError('ambiguous candidate entry')
|
| 30 |
+
return found[0]
|
| 31 |
+
entry0=candidate_entry('(%r12), %edi')
|
| 32 |
+
entry1=candidate_entry('(%r14), %edi')
|
| 33 |
+
entry2=candidate_entry('(%rbp), %edi')
|
| 34 |
+
for entry in [entry0,entry1,entry2]:
|
| 35 |
+
i=next(i for i,x in enumerate(rows) if x['address']==entry)
|
| 36 |
+
if '<SV_GentityNum>' not in rows[i+1]['operands']:raise ValueError('candidate call changed')
|
| 37 |
+
pass_read=index('movl','0x200(%rax), %edx')
|
| 38 |
+
if '<SV_GentityNum>' not in rows[pass_read-1]['operands']:raise ValueError('pass lookup changed')
|
| 39 |
+
return {'after_query':rows[query[0]+1]['address'], 'count_gate':rows[gate]['address'],
|
| 40 |
+
'pass_lookup_call':rows[pass_read-1]['address'],
|
| 41 |
+
'after_pass_lookup':addr('movl','0x200(%rax), %edx'),
|
| 42 |
+
'exit':exit_addr,'entries':[entry0,entry1,entry2],
|
| 43 |
+
'continuations':[x['stop'] for x in lane_sites(graph)]}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def run(graph,start,stops,m,regs):
|
| 47 |
+
return evaluate(graph,start,stops,m,regs,capture_registers=True)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main():
|
| 51 |
+
out={'native':{},'limitations':'Synthetic bounded instruction slices only. Area query, entity lookup, '
|
| 52 |
+
'candidate filters and collision calls are not executed. Scheduling compositions inject status0 '
|
| 53 |
+
'after each skipped candidate body. Normal count ownership/bounds, collision-produced statuses, '
|
| 54 |
+
'geometry, original runtime and other platforms remain open.'}
|
| 55 |
+
clip,stack,array,obj=0x10000,0x40000,0x40030,0x20000
|
| 56 |
+
for label in ['linux--Quake3-UrT-Ded.x86_64','linux--Quake3-UrT.x86_64']:
|
| 57 |
+
r=json.loads(Path(f'docs/research/native/{label}.json').read_text())
|
| 58 |
+
verified((Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes(),r['source']['sha256'])
|
| 59 |
+
a=r['artifacts']['disassembly.txt']
|
| 60 |
+
fs=native_functions(verified(gzip.open(a['path'],'rb').read(),a['uncompressed_sha256']).decode())
|
| 61 |
+
g=control_flow(fs['SV_ClipMoveToEntities']);s=sites(g)
|
| 62 |
+
entries=s['entries'];stops=entries+[s['exit']]
|
| 63 |
+
entry=[]
|
| 64 |
+
for count,status in itertools.product([-1,0,1,2,3,4,1023,1024,0x80000000],[0,1,4,0xffffffff]):
|
| 65 |
+
m={};put_word(m,clip+0x3c,status)
|
| 66 |
+
t=run(g,s['count_gate'],stops,m,{'%rbx':clip,'%rsp':stack,'%rbp':array,'%r15':count,'%r13':99})
|
| 67 |
+
entry.append({'count_bits':count&0xffffffff,'status0':status,'lane':entries.index(t['stop']) if t['stop'] in entries else None,
|
| 68 |
+
'trace':t['trace'],'stop':t['stop'],'registers':t['registers'],'writes':t['writes']})
|
| 69 |
+
normalization=[]
|
| 70 |
+
for pass_id in [0,7,1022,1023,0xffffffff]:
|
| 71 |
+
m={};put_word(m,clip+0x74,pass_id);put_word(m,stack+0x1088,3)
|
| 72 |
+
t=run(g,s['after_query'],[s['count_gate'],s['pass_lookup_call']],m,{'%rbx':clip,'%rsp':stack})
|
| 73 |
+
normalization.append({'kind':'pass','input':pass_id,'lookup_requested':t['stop']==s['pass_lookup_call'],
|
| 74 |
+
'normalized_word':get_word(t['memory'],stack+0x2c) if t['stop']==s['count_gate'] else None,
|
| 75 |
+
'trace':t['trace'],'registers':t['registers']})
|
| 76 |
+
for related in [0,7,1022,1023,0xffffffff]:
|
| 77 |
+
m={};put_word(m,obj+0x200,related)
|
| 78 |
+
t=run(g,s['after_pass_lookup'],[s['count_gate']],m,{'%rax':obj,'%rsp':stack})
|
| 79 |
+
normalization.append({'kind':'related','input':related,'normalized_word':get_word(t['memory'],stack+0x2c),
|
| 80 |
+
'trace':t['trace'],'writes':t['writes']})
|
| 81 |
+
continuations=[]
|
| 82 |
+
for lane,count,counter,status in itertools.product(range(3),[1,2,3,4],range(5),[0,1,4]):
|
| 83 |
+
m={};put_word(m,clip+0x3c,status)
|
| 84 |
+
regs={'%rbx':clip,'%rsp':stack,'%r12':array,'%r14':array+4,'%r13':counter,'%r15':count}
|
| 85 |
+
t=run(g,s['continuations'][lane],stops,m,regs)
|
| 86 |
+
continuations.append({'lane':lane,'count':count,'counter_before':counter,'status0':status,
|
| 87 |
+
'next_lane':entries.index(t['stop']) if t['stop'] in entries else None,
|
| 88 |
+
'counter_after':t['registers']['r13'],'registers':t['registers'],'trace':t['trace']})
|
| 89 |
+
schedules=[]
|
| 90 |
+
for count in list(range(1,9))+[31,32,1023,1024]:
|
| 91 |
+
for stop_after in sorted({-1,0,count-1}): # -1 means no supplied status stop.
|
| 92 |
+
m={};put_word(m,clip+0x3c,0)
|
| 93 |
+
t=run(g,s['count_gate'],stops,m,{'%rbx':clip,'%rsp':stack,'%rbp':array,'%r15':count,'%r13':99})
|
| 94 |
+
regs=t['registers'];m=t['memory'];pc=t['stop'];visited=[];trace=list(t['trace']);segments=[]
|
| 95 |
+
while pc!=s['exit']:
|
| 96 |
+
if len(visited)>=count:raise ValueError('schedule exceeded supplied count')
|
| 97 |
+
lane=entries.index(pc);ptr=regs['r14' if lane==1 else 'r12']
|
| 98 |
+
visited.append({'lane':lane,'array_byte_offset':ptr-array})
|
| 99 |
+
# Explicitly skip candidate body/callees and supply its resulting current status0.
|
| 100 |
+
put_word(m,clip+0x3c,int(len(visited)-1==stop_after))
|
| 101 |
+
t=run(g,s['continuations'][lane],stops,m,regs)
|
| 102 |
+
regs=t['registers'];m=t['memory'];pc=t['stop'];trace.extend(t['trace']);segments.append(t['trace'])
|
| 103 |
+
schedules.append({'count':count,'status_stop_after_index':stop_after,'visited':visited,
|
| 104 |
+
'final_counter':regs['r13'],'trace_instruction_count':len(trace),
|
| 105 |
+
'trace_sha256':hashlib.sha256(json.dumps(trace,separators=(',',':')).encode()).hexdigest(),
|
| 106 |
+
'trace':trace if count<=32 else None,'last_continuation_trace':segments[-1]})
|
| 107 |
+
out['native'][label]={'source':r['source'],'disassembly_sha256':a['uncompressed_sha256'],
|
| 108 |
+
'graph':g,'sites':s,'entry_traces':entry,'normalization_traces':normalization,
|
| 109 |
+
'continuation_traces':continuations,'scheduling_compositions':schedules}
|
| 110 |
+
write_json(Path('docs/research/collision-loop.json'),out)
|
| 111 |
+
print('Recovered 72 entry, 20 normalization, 360 continuation slices and 70 scheduling compositions')
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
if __name__=='__main__':main()
|
scripts/test_collision_loop.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Independent scheduling oracles and restricted evaluator boundary checks."""
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import unittest
|
| 6 |
+
from analyze_collision_composition import evaluate, get_word
|
| 7 |
+
from analyze_collision_loop import sites
|
| 8 |
+
from test_collision_composition import graph
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class CollisionLoopTests(unittest.TestCase):
|
| 12 |
+
@classmethod
|
| 13 |
+
def setUpClass(cls):cls.r=json.loads(Path('docs/research/collision-loop.json').read_text())
|
| 14 |
+
|
| 15 |
+
def test_entry_signed_count_status_and_parity(self):
|
| 16 |
+
for n in self.r['native'].values():
|
| 17 |
+
self.assertEqual(len(n['entry_traces']),36)
|
| 18 |
+
for x in n['entry_traces']:
|
| 19 |
+
signed=x['count_bits'] if x['count_bits']<2**31 else x['count_bits']-2**32
|
| 20 |
+
expected=None if signed<=0 or x['status0'] else 2 if signed%2==0 else 0
|
| 21 |
+
self.assertEqual(x['lane'],expected)
|
| 22 |
+
if expected is not None:
|
| 23 |
+
self.assertEqual(x['registers']['r13'],0)
|
| 24 |
+
self.assertEqual(x['registers']['r12'],0x40030)
|
| 25 |
+
self.assertEqual(x['writes'],[{'address':0x40020,'bytes':8}])
|
| 26 |
+
else:self.assertEqual(x['writes'],[])
|
| 27 |
+
|
| 28 |
+
def test_sentinel_normalization_and_lookup_boundary(self):
|
| 29 |
+
for n in self.r['native'].values():
|
| 30 |
+
self.assertEqual(len(n['normalization_traces']),10)
|
| 31 |
+
for x in n['normalization_traces']:
|
| 32 |
+
if x['kind']=='pass':
|
| 33 |
+
self.assertEqual(x['lookup_requested'],x['input']!=1023)
|
| 34 |
+
self.assertEqual(x['normalized_word'],0xffffffff if x['input']==1023 else None)
|
| 35 |
+
if x['lookup_requested']:self.assertEqual(x['registers']['rdi'],x['input'])
|
| 36 |
+
else:self.assertEqual(x['normalized_word'],0xffffffff if x['input']==1023 else x['input'])
|
| 37 |
+
|
| 38 |
+
def test_continuation_equality_and_status_rules(self):
|
| 39 |
+
for n in self.r['native'].values():
|
| 40 |
+
self.assertEqual(len(n['continuation_traces']),180)
|
| 41 |
+
for x in n['continuation_traces']:
|
| 42 |
+
counter=x['counter_before']+1
|
| 43 |
+
expected=None if x['status0'] or (x['lane']==0 and counter==x['count']) else 1 if x['lane']==0 else 0
|
| 44 |
+
self.assertEqual(x['next_lane'],expected)
|
| 45 |
+
self.assertEqual(x['counter_after'],counter)
|
| 46 |
+
if x['lane']==1:self.assertEqual(x['registers']['r12'],0x40038)
|
| 47 |
+
if x['lane']==2:self.assertEqual(x['registers']['r12'],0x40034)
|
| 48 |
+
# A counter already at count does not trigger the equality guard on increment.
|
| 49 |
+
witness=next(x for x in n['continuation_traces'] if x['lane']==0 and x['count']==2
|
| 50 |
+
and x['counter_before']==2 and x['status0']==0)
|
| 51 |
+
self.assertEqual(witness['next_lane'],1)
|
| 52 |
+
|
| 53 |
+
def test_composed_visit_order_and_stop_boundaries(self):
|
| 54 |
+
for n in self.r['native'].values():
|
| 55 |
+
self.assertEqual(len(n['scheduling_compositions']),35)
|
| 56 |
+
for x in n['scheduling_compositions']:
|
| 57 |
+
length=x['count'] if x['status_stop_after_index']==-1 else x['status_stop_after_index']+1
|
| 58 |
+
lanes=([2]+[i%2 for i in range(x['count']-1)]) if x['count']%2==0 else [i%2 for i in range(x['count'])]
|
| 59 |
+
self.assertEqual(x['visited'],[{'lane':lane,'array_byte_offset':4*i} for i,lane in enumerate(lanes[:length])])
|
| 60 |
+
self.assertEqual(x['final_counter'],length)
|
| 61 |
+
self.assertEqual(len(x['trace_sha256']),64)
|
| 62 |
+
if x['trace'] is not None:self.assertEqual(len(x['trace']),x['trace_instruction_count'])
|
| 63 |
+
|
| 64 |
+
def test_paired_engines_have_same_observed_outputs(self):
|
| 65 |
+
a,b=self.r['native'].values()
|
| 66 |
+
for key in ['entry_traces','normalization_traces','continuation_traces','scheduling_compositions']:
|
| 67 |
+
def outcomes(n):return [{k:v for k,v in x.items() if k not in ['trace','stop','last_continuation_trace','trace_sha256']} for x in n[key]]
|
| 68 |
+
self.assertEqual(outcomes(a),outcomes(b))
|
| 69 |
+
|
| 70 |
+
def test_signed_branch_and_lea_preserves_flags(self):
|
| 71 |
+
g=graph(('testl','%edi, %edi'),('leal','-0x1(%rdi), %eax'),('jle','0x5'),('movl','$0x7, %eax'))
|
| 72 |
+
for value in [0,0xffffffff,0x80000000,1,0x7fffffff]:
|
| 73 |
+
t=evaluate(g,1,[5],{}, {'%rdi':value},capture_registers=True)
|
| 74 |
+
signed=value if value<2**31 else value-2**32
|
| 75 |
+
self.assertEqual(t['registers']['rax'],(value-1)&0xffffffff if signed<=0 else 7)
|
| 76 |
+
g=graph(('cmpl','$0xffffffff, %edi'),('jle','0x4'),('movl','$0x7, %eax'))
|
| 77 |
+
for value in [0xfffffffe,0xffffffff,0]:
|
| 78 |
+
t=evaluate(g,1,[4],{}, {'%rdi':value,'%rax':9},capture_registers=True)
|
| 79 |
+
self.assertEqual(t['registers']['rax'],9 if value>=0xfffffffe else 7)
|
| 80 |
+
|
| 81 |
+
def test_pointer_add_low_byte_test_and_unmodeled_flags(self):
|
| 82 |
+
g=graph(('addq','$0x4, %r12'),('testb','$0x1, %al'),('je','0x5'),('movq','%r12, (%rbx)'))
|
| 83 |
+
t=evaluate(g,1,[5],{}, {'%r12':2**32,'%rax':0x101,'%rbx':0x10000},capture_registers=True)
|
| 84 |
+
self.assertEqual(get_word(t['memory'],0x10000,8),2**32+4)
|
| 85 |
+
t=evaluate(g,1,[5],{}, {'%r12':2**32,'%rax':0x100,'%rbx':0x10000})
|
| 86 |
+
self.assertEqual(t['writes'],[])
|
| 87 |
+
with self.assertRaises(ValueError):evaluate(graph(('addl','$0x1, %eax'),('jle','0x3')),1,[3],{}, {'%rax':0})
|
| 88 |
+
with self.assertRaises(ValueError):evaluate(graph(('leaq','0x4(%r12,%rax), %rax')),1,[2],{}, {'%r12':0,'%rax':0})
|
| 89 |
+
|
| 90 |
+
def test_ambiguous_site_rejected(self):
|
| 91 |
+
for n in self.r['native'].values():
|
| 92 |
+
g=dict(n['graph']);g['instructions']=n['graph']['instructions']+[next(x for x in n['graph']['instructions'] if x['operands']=='%r15d, %r15d')]
|
| 93 |
+
with self.assertRaises(ValueError):sites(g)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
if __name__=='__main__':unittest.main()
|