"""Phase 1 spec tests for ADR-0065 P4 — strict-FIFO RW hazard tracker. PE_SCHEDULER serializes composites that share read/write handles (ADR-0065 D6.3 / DDD §8): a new composite whose read or write handles intersect an in-flight composite's ``rw_handles`` waits until all earlier overlapping composites complete. Non-overlapping composites proceed. These unit-test the ``_RwHazardTracker`` directly with a SimPy env. Legacy composites (``rw_handles == ()``) never register and never block, so the existing benches' Stage sequences are untouched. Phase 1 (this commit): tests only. FAIL until P4: - ``_RwHazardTracker`` does not exist in ``pe_scheduler``. """ from __future__ import annotations import simpy from kernbench.common.pe_commands import ( CompletionHandle, CompositeCmd, OpSpec, Scope, TensorHandle, ) def _h(addr: int) -> TensorHandle: return TensorHandle(id=f"h{addr}", addr=addr, shape=(8,), dtype="f16", nbytes=16, space="tcm") def _cmd(cid: str, rw=(), reads=()) -> CompositeCmd: """A composite that writes ``rw`` (rw_handles) and reads ``reads`` (as MATH-op operands).""" if reads: ops = tuple( OpSpec(kind="math", scope=Scope.KERNEL, operands={"src": r}, out=r) for r in reads ) else: ops = (OpSpec(kind="math", scope=Scope.KERNEL, operands={}, out=None),) return CompositeCmd(completion=CompletionHandle(id=cid), ops=ops, rw_handles=tuple(rw)) def _tracker(): from kernbench.components.builtin.pe_scheduler import _RwHazardTracker return _RwHazardTracker() def test_admit_immediate_when_no_inflight(): env = simpy.Environment() tr = _tracker() state: dict = {} def driver(): yield from tr.admit(env, _cmd("a", rw=(_h(1),)), env.event()) state["t"] = env.now env.process(driver()) env.run() assert state["t"] == 0 def test_admit_blocks_until_overlapping_inflight_retires(): env = simpy.Environment() tr = _tracker() O = _h(1) eA = env.event() state: dict = {} def b_proc(): # B reads O, which in-flight A writes (RAW) → must block. yield from tr.admit(env, _cmd("b", reads=(O,)), env.event()) state["B"] = env.now def driver(): yield from tr.admit(env, _cmd("a", rw=(O,)), eA) state["A"] = env.now env.process(b_proc()) yield env.timeout(10) tr.retire("a") eA.succeed() # A completes at t=10 yield env.timeout(1) env.process(driver()) env.run() assert state["A"] == 0 assert state["B"] >= 10, f"B admitted at {state.get('B')}, must wait for A" def test_admit_no_block_when_disjoint(): env = simpy.Environment() tr = _tracker() O, X = _h(1), _h(2) state: dict = {} def driver(): yield from tr.admit(env, _cmd("a", rw=(O,)), env.event()) yield from tr.admit(env, _cmd("b", reads=(X,)), env.event()) state["B"] = env.now env.process(driver()) env.run() assert state["B"] == 0, "disjoint composite must not block" def test_write_after_write_overlap_blocks(): """Two composites both writing the same handle (WAW) serialize.""" env = simpy.Environment() tr = _tracker() O = _h(1) eA = env.event() state: dict = {} def b_proc(): yield from tr.admit(env, _cmd("b", rw=(O,)), env.event()) state["B"] = env.now def driver(): yield from tr.admit(env, _cmd("a", rw=(O,)), eA) env.process(b_proc()) yield env.timeout(5) tr.retire("a") eA.succeed() yield env.timeout(1) env.process(driver()) env.run() assert state["B"] >= 5 def test_legacy_composite_never_registers(): """A composite with empty rw_handles must not block a later composite that touches the same input handles (legacy composites carry no RW metadata → no serialization, byte-equal behavior).""" env = simpy.Environment() tr = _tracker() O = _h(1) state: dict = {} def driver(): # A is "legacy": reads O but declares no rw_handles → not registered. yield from tr.admit(env, _cmd("a", reads=(O,)), env.event()) # B reads O too; since A registered nothing, B must not block. yield from tr.admit(env, _cmd("b", reads=(O,)), env.event()) state["B"] = env.now env.process(driver()) env.run() assert state["B"] == 0