CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
547 lines
22 KiB
Python
547 lines
22 KiB
Python
# Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Instruction scheduler for subtile-based mainloop.
|
|
|
|
Interleaves non-MFMA instructions between MFMAs using a slot-based placer
|
|
with pluggable scheduling rules.
|
|
"""
|
|
|
|
from typing import List, Tuple, Optional
|
|
from rocisa.code import Module
|
|
from rocisa.instruction import SWaitCnt, SBarrier, MFMAInstruction, MXMFMAInstruction, \
|
|
LocalReadInstruction, GlobalReadInstruction, CommonInstruction
|
|
|
|
|
|
class _SlotPlacer:
|
|
"""Generic slot placement engine for interleaving instructions between MFMAs.
|
|
|
|
Each interval (pair of adjacent MFMAs) has 2 placement slots.
|
|
Rules are injected via callbacks:
|
|
- validators: (placer, pos, inst) -> bool — reject invalid slots
|
|
- adjusters: (placer, limit, inst) -> limit — shift search start
|
|
- onPlace: (placer, pos, inst) -> None — update rule state after placement
|
|
"""
|
|
|
|
def __init__(self, intervals: int, numModules: int,
|
|
pathOrders: List[List[int]],
|
|
validators=None, adjusters=None, onPlace=None):
|
|
self.totalSlots = intervals * 2
|
|
self._n = numModules
|
|
self._prevInPath: List[int] = [-1] * numModules
|
|
self._nextInPath: List[int] = [-1] * numModules
|
|
for order in pathOrders:
|
|
for a, b in zip(order, order[1:]):
|
|
self._prevInPath[b] = a
|
|
self._nextInPath[a] = b
|
|
self._validators = validators or []
|
|
self._adjusters = adjusters or []
|
|
self._onPlace = onPlace
|
|
|
|
self._placed: List[List[Tuple[int, object]]] = [[] for _ in range(self.totalSlots)]
|
|
self._firstPos: List[Optional[int]] = [None] * numModules
|
|
self._lastPos: List[Optional[int]] = [None] * numModules
|
|
self.leftovers: List[Tuple[int, object]] = []
|
|
|
|
# ── Placement ──
|
|
|
|
def _canPlace(self, pos: int, inst) -> bool:
|
|
if pos < 0 or pos >= self.totalSlots or len(self._placed[pos]) >= 2:
|
|
return False
|
|
return all(v(self, pos, inst) for v in self._validators)
|
|
|
|
def adjustLimit(self, limit: int, inst) -> int:
|
|
for adj in self._adjusters:
|
|
limit = adj(self, limit, inst)
|
|
return limit
|
|
|
|
def bounds(self, mid: int) -> Tuple[int, int]:
|
|
lo = 0
|
|
pred = self._prevInPath[mid]
|
|
if 0 <= pred < self._n and self._lastPos[pred] is not None:
|
|
lo = self._lastPos[pred] + 1
|
|
hi = self.totalSlots - 1
|
|
succ = self._nextInPath[mid]
|
|
if 0 <= succ < self._n and self._firstPos[succ] is not None:
|
|
hi = self._firstPos[succ] - 1
|
|
return lo, hi
|
|
|
|
def findSlot(self, mid: int, inst, limit: int, reverse: bool = False) -> Optional[int]:
|
|
lo, hi = self.bounds(mid)
|
|
if reverse:
|
|
hi = min(hi, limit)
|
|
else:
|
|
lo = max(lo, limit)
|
|
if hi < lo:
|
|
return None
|
|
for pos in (range(hi, lo - 1, -1) if reverse else range(lo, hi + 1)):
|
|
if self._canPlace(pos, inst):
|
|
return pos
|
|
return None
|
|
|
|
def _forceSlot(self, mid: int, limit: int, reverse: bool) -> int:
|
|
"""Find the closest valid slot respecting dependencies, allowing >2 items per slot."""
|
|
lo, hi = self.bounds(mid)
|
|
if reverse:
|
|
hi = min(hi, limit)
|
|
lo = max(lo, 0)
|
|
if hi < lo:
|
|
hi = lo
|
|
return hi
|
|
else:
|
|
lo = max(lo, limit)
|
|
hi = min(hi, self.totalSlots - 1)
|
|
if lo > hi:
|
|
lo = hi
|
|
return lo
|
|
|
|
def place(self, pos: int, item: Tuple[int, object], reverse: bool = False):
|
|
mid = item[0]
|
|
if reverse:
|
|
self._placed[pos].insert(0, item)
|
|
else:
|
|
self._placed[pos].append(item)
|
|
if self._firstPos[mid] is None or pos < self._firstPos[mid]:
|
|
self._firstPos[mid] = pos
|
|
if self._lastPos[mid] is None or pos > self._lastPos[mid]:
|
|
self._lastPos[mid] = pos
|
|
if self._onPlace:
|
|
self._onPlace(self, pos, item[1])
|
|
|
|
def placePath(self, pathInsts: List[Tuple[int, object]], reverse: bool = False,
|
|
multiDU: bool = False):
|
|
"""Place a sequence of (moduleId, instruction) items into slots.
|
|
|
|
Walks pathInsts in order, applying adjusters (forward only) and
|
|
finding valid slots. When no empty slot is found, force-places at
|
|
the closest valid position respecting dependencies (allowing >2
|
|
items per slot).
|
|
|
|
When multiDU is True, wait_gr immediately followed by barrier is
|
|
placed as an atomic pair in the same slot so other paths cannot
|
|
insert m0 setup between them.
|
|
"""
|
|
limit = (self.totalSlots - 1) if reverse else 0
|
|
if not multiDU:
|
|
for item in pathInsts:
|
|
mid, inst = item
|
|
if not reverse:
|
|
limit = self.adjustLimit(limit, inst)
|
|
pos = self.findSlot(mid, inst, limit, reverse=reverse)
|
|
if pos is None:
|
|
pos = self._forceSlot(mid, limit, reverse)
|
|
self.place(pos, item, reverse=reverse)
|
|
limit = (pos - 1) if reverse else (pos + 1)
|
|
return
|
|
|
|
idx = 0
|
|
while idx < len(pathInsts):
|
|
item = pathInsts[idx]
|
|
mid, inst = item
|
|
if (not reverse and idx + 1 < len(pathInsts)
|
|
and _isWaitGr(inst) and _isBarrier(pathInsts[idx + 1][1])):
|
|
if not reverse:
|
|
limit = self.adjustLimit(limit, inst)
|
|
pos = self.findSlot(mid, inst, limit, reverse=reverse)
|
|
if pos is None:
|
|
pos = self._forceSlot(mid, limit, reverse)
|
|
self.place(pos, item, reverse=reverse)
|
|
sync_item = pathInsts[idx + 1]
|
|
if self._canPlace(pos, sync_item[1]):
|
|
self.place(pos, sync_item, reverse=reverse)
|
|
else:
|
|
sync_pos = self.findSlot(sync_item[0], sync_item[1],
|
|
pos + 1, reverse=reverse)
|
|
if sync_pos is None:
|
|
sync_pos = self._forceSlot(sync_item[0], pos + 1, reverse)
|
|
self.place(sync_pos, sync_item, reverse=reverse)
|
|
pos = sync_pos
|
|
limit = (pos - 1) if reverse else (pos + 1)
|
|
idx += 2
|
|
continue
|
|
if not reverse:
|
|
limit = self.adjustLimit(limit, inst)
|
|
pos = self.findSlot(mid, inst, limit, reverse=reverse)
|
|
if pos is None:
|
|
pos = self._forceSlot(mid, limit, reverse)
|
|
self.place(pos, item, reverse=reverse)
|
|
limit = (pos - 1) if reverse else (pos + 1)
|
|
idx += 1
|
|
|
|
# ── Assembly ──
|
|
|
|
def assemble(self, mfmas) -> Module:
|
|
intervals = len(mfmas) - 1
|
|
result = Module()
|
|
result.add(mfmas[0])
|
|
for i in range(intervals):
|
|
for slot in (2 * i, 2 * i + 1):
|
|
for item in self._placed[slot]:
|
|
result.add(item[1])
|
|
result.add(mfmas[i + 1])
|
|
for _, inst in self.leftovers:
|
|
result.add(inst)
|
|
return result
|
|
|
|
|
|
# ── Scheduling rules ──
|
|
|
|
# Hardcoded gap to hide ds_read latency. TODO: compute this more accurately.
|
|
# gfx1250 needs a larger gap (8); other archs use 4.
|
|
_MIN_MFMA_GAP_DS_READ_TO_WAIT_DEFAULT = 4
|
|
_MIN_MFMA_GAP_DS_READ_TO_WAIT_GFX1250 = 8
|
|
|
|
_isDsRead = lambda x: isinstance(x, LocalReadInstruction)
|
|
_isBufferLoad = lambda x: isinstance(x, GlobalReadInstruction)
|
|
_isWaitCnt = lambda x: isinstance(x, SWaitCnt)
|
|
_isM0Update = lambda x: isinstance(x, CommonInstruction) and hasattr(x, 'dst') and hasattr(x.dst, 'regType') and x.dst.regType == 'm'
|
|
# Typed op detection (see SWaitCntEx.isWaitGr / SBarrier) rather than matching
|
|
# substrings in the instruction comment, which is brittle to comment edits.
|
|
_isWaitGr = lambda x: _isWaitCnt(x) and getattr(x, "isWaitGr", False)
|
|
_isBarrier = lambda x: isinstance(x, SBarrier)
|
|
|
|
|
|
class _SchedulingRules:
|
|
"""Scheduling rules for slot placement: validators, adjusters, and placement hooks.
|
|
|
|
Owns all rule state (ds_read/waitcnt tracking, buffer-load spreading).
|
|
Bound methods are passed as callbacks to _SlotPlacer.
|
|
"""
|
|
|
|
def __init__(self, totalSlots: int, minGapDsReadToWait: int = _MIN_MFMA_GAP_DS_READ_TO_WAIT_DEFAULT):
|
|
# Cross-path state
|
|
self.lastDsReadPos = -1
|
|
self.earliestWaitCntPos = totalSlots
|
|
self.waitGrBarrierPos: Optional[int] = None
|
|
self.maxDsReadAfterWaitGr = -1
|
|
self.minGap = minGapDsReadToWait
|
|
# Per-path state
|
|
self._resetPath()
|
|
|
|
def _resetPath(self):
|
|
self.firstBufLoadPos: Optional[int] = None
|
|
self.bufLoadIdx = 0
|
|
self.bufLoadMaxSlot = 0
|
|
self.numBufLoads = 0
|
|
|
|
# ── Validators: (placer, pos, inst) -> bool ──
|
|
|
|
def oneDsReadPerInterval(self, placer, pos, inst):
|
|
"""At most one ds_read per interval (pair of slots) to avoid same SIMD pair stalls as we have a single codepath"""
|
|
if not _isDsRead(inst):
|
|
return True
|
|
peer = pos ^ 1
|
|
return not (0 <= peer < placer.totalSlots
|
|
and any(_isDsRead(item[1]) for item in placer._placed[peer]))
|
|
|
|
def minGapDsReadBeforeWait(self, placer, pos, inst):
|
|
"""Reject ds_read too close to an already-placed waitcnt ahead."""
|
|
if not _isDsRead(inst):
|
|
return True
|
|
gap = self.minGap * 2
|
|
return self.earliestWaitCntPos - pos >= gap
|
|
|
|
def minGapDsReadToWait(self, placer, pos, inst):
|
|
"""Reject waitcnt too close to the last placed ds_read."""
|
|
if not _isWaitCnt(inst) or self.lastDsReadPos < 0:
|
|
return True
|
|
gap = self.minGap * 2
|
|
return pos - self.lastDsReadPos >= gap
|
|
|
|
def noM0WithBufferLoad(self, placer, pos, inst):
|
|
"""Avoid placing M0 updates and buffer_loads in the same MFMA interval."""
|
|
if not _isM0Update(inst) and not _isBufferLoad(inst):
|
|
return True
|
|
peer = pos ^ 1
|
|
slots = [pos]
|
|
if 0 <= peer < placer.totalSlots:
|
|
slots.append(peer)
|
|
if _isM0Update(inst):
|
|
return not any(_isBufferLoad(item[1]) for s in slots for item in placer._placed[s])
|
|
return not any(_isM0Update(item[1]) for s in slots for item in placer._placed[s])
|
|
|
|
def grAfterWaitGrDsReads(self, placer, pos, inst):
|
|
"""Keep GR (m0 / buffer_load) out of the window after wait_gr+barrier until ds_reads finish."""
|
|
if not (_isBufferLoad(inst) or _isM0Update(inst)):
|
|
return True
|
|
if self.waitGrBarrierPos is None or self.maxDsReadAfterWaitGr < 0:
|
|
return True
|
|
if pos <= self.waitGrBarrierPos:
|
|
return True
|
|
return pos > self.maxDsReadAfterWaitGr
|
|
|
|
# ── Adjusters: (placer, limit, inst) -> limit ──
|
|
|
|
def deferGrAfterWaitGrDsReads(self, placer, limit, inst):
|
|
"""Once past wait_gr, start searching for GR only after post-barrier ds_reads."""
|
|
if not (_isBufferLoad(inst) or _isM0Update(inst)):
|
|
return limit
|
|
if self.waitGrBarrierPos is None or self.maxDsReadAfterWaitGr < 0:
|
|
return limit
|
|
if limit <= self.waitGrBarrierPos:
|
|
return limit
|
|
return max(limit, self.maxDsReadAfterWaitGr + 1)
|
|
|
|
def spreadBufferLoads(self, placer, limit, inst):
|
|
"""Spread buffer_load instructions evenly across available range."""
|
|
if not _isBufferLoad(inst) or self.bufLoadMaxSlot <= 0:
|
|
return limit
|
|
if self.firstBufLoadPos is not None:
|
|
stride = max(1, (self.bufLoadMaxSlot - self.firstBufLoadPos)
|
|
// self.numBufLoads)
|
|
limit = max(limit, self.firstBufLoadPos
|
|
+ self.bufLoadIdx * stride)
|
|
self.bufLoadIdx += 1
|
|
return limit
|
|
|
|
# ── Placement hook: (placer, pos, inst) -> None ──
|
|
|
|
def trackPlacement(self, placer, pos, inst):
|
|
"""Update rule state after a successful placement."""
|
|
if _isDsRead(inst):
|
|
self.lastDsReadPos = max(self.lastDsReadPos, pos)
|
|
if _isWaitCnt(inst):
|
|
self.earliestWaitCntPos = min(self.earliestWaitCntPos, pos)
|
|
if _isBufferLoad(inst) and self.firstBufLoadPos is None:
|
|
self.firstBufLoadPos = pos
|
|
|
|
# ── Per-path setup ──
|
|
|
|
def resetPath(self):
|
|
self._resetPath()
|
|
|
|
def setupBufLoadSpreading(self, placer, pathInsts, order):
|
|
"""Compute buffer-load spreading bounds for a forward path.
|
|
|
|
Reserves tail slots for non-buffer-load instructions in modules that
|
|
follow the last GR module (e.g. GR_INC SRD updates, LDS buffer swaps).
|
|
"""
|
|
self.numBufLoads = sum(1 for _, inst in pathInsts if _isBufferLoad(inst))
|
|
if self.numBufLoads > 1:
|
|
_, rawMax = placer.bounds(pathInsts[-1][0])
|
|
grModuleIds = {mid for mid, inst in pathInsts if _isBufferLoad(inst)}
|
|
lastGrIdx = max(order.index(m) for m in grModuleIds if m in order)
|
|
tailModuleIds = set(order[lastGrIdx + 1:])
|
|
numTailInsts = sum(1 for mid, _ in pathInsts if mid in tailModuleIds)
|
|
# this is an approximation as we don't know exactly how many slots will be use by modules after the GR yet (in this codepath)
|
|
self.bufLoadMaxSlot = max(0, rawMax - numTailInsts)
|
|
|
|
def setPostWaitGrDsReadFence(self, placer) -> None:
|
|
"""After the wait_gr path is placed, record ds_read coverage for GR deferral."""
|
|
wg_pos = None
|
|
for pos, slot in enumerate(placer._placed):
|
|
for _, inst in slot:
|
|
if _isWaitGr(inst):
|
|
wg_pos = pos if wg_pos is None else min(wg_pos, pos)
|
|
if wg_pos is None:
|
|
self.waitGrBarrierPos = None
|
|
self.maxDsReadAfterWaitGr = -1
|
|
return
|
|
max_ds = -1
|
|
for pos, slot in enumerate(placer._placed):
|
|
if pos <= wg_pos:
|
|
continue
|
|
for _, inst in slot:
|
|
if _isDsRead(inst):
|
|
max_ds = max(max_ds, pos)
|
|
self.waitGrBarrierPos = wg_pos
|
|
self.maxDsReadAfterWaitGr = max_ds
|
|
|
|
|
|
def _classifyPaths(pathOrders, emittedModules):
|
|
"""Classify paths by wait_gr presence, sorted: wait_gr first, then by index."""
|
|
paths = []
|
|
for order in pathOrders:
|
|
hasWaitGR = any(emittedModules[i].opType == "wait_gr" for i in order)
|
|
paths.append((order, hasWaitGR))
|
|
paths.sort(key=lambda p: (0 if p[1] else 1, p[0][0] if p[0] else 10**9))
|
|
return paths
|
|
|
|
|
|
def _flattenPath(order, emittedModules, reverse=False):
|
|
"""Flatten a path of module indices into (moduleId, instruction) pairs."""
|
|
pathInsts = [(mid, inst) for mid in order for inst in emittedModules[mid].instructions]
|
|
if reverse:
|
|
pathInsts.reverse()
|
|
return pathInsts
|
|
|
|
|
|
def extractPathsFromBeforeDeps(emittedModules) -> Tuple[int, List[List[int]], List[List[int]]]:
|
|
"""Extract non-MFMA dependency paths using only EmittedModule.before links.
|
|
|
|
Returns:
|
|
(mfmaIdx, paths, preMfmaPaths)
|
|
- mfmaIdx: index of the MFMA emitted module in emittedModules
|
|
- paths: list of non-MFMA module-index paths to interleave between MFMAs
|
|
- preMfmaPaths: paths that must be emitted before the first MFMA
|
|
(reachable from the MFMA's before link)
|
|
"""
|
|
idToIdx = {em.moduleId: i for i, em in enumerate(emittedModules)}
|
|
n = len(emittedModules)
|
|
|
|
mfmaModuleIds = [i for i, em in enumerate(emittedModules) if em.opType == "mfma"]
|
|
assert len(mfmaModuleIds) == 1, "extractPathsFromBeforeDeps expects exactly one MFMA emitted module"
|
|
mfmaIdx = mfmaModuleIds[0]
|
|
nonMfmaIds = [i for i in range(n) if i != mfmaIdx]
|
|
nonMfmaSet = set(nonMfmaIds)
|
|
|
|
# Identify the non-MFMA module the MFMA depends on (if any).
|
|
mfmaBefore = emittedModules[mfmaIdx].before
|
|
preMfmaTarget = None
|
|
if mfmaBefore is not None:
|
|
bi = idToIdx.get(mfmaBefore)
|
|
if bi is not None and bi in nonMfmaSet:
|
|
preMfmaTarget = bi
|
|
|
|
# Each non-MFMA module has at most one predecessor, and each predecessor
|
|
# has at most one child, so paths are simple chains.
|
|
pred: List[int] = [-1 for _ in range(n)]
|
|
child: List[int] = [-1 for _ in range(n)]
|
|
for i in nonMfmaIds:
|
|
parent = -1
|
|
b = emittedModules[i].before
|
|
if b is not None:
|
|
bi = idToIdx.get(b)
|
|
if bi is not None and bi != i and bi in nonMfmaSet:
|
|
parent = bi
|
|
pred[i] = parent
|
|
if parent != -1:
|
|
assert child[parent] == -1, \
|
|
f"extractPathsFromBeforeDeps expects unique child per predecessor, got {child[parent]} and {i} for {parent}"
|
|
child[parent] = i
|
|
|
|
def _findHead(mid: int) -> int:
|
|
cur = mid
|
|
seen = [False for _ in range(n)]
|
|
while pred[cur] != -1 and not seen[cur]:
|
|
seen[cur] = True
|
|
cur = pred[cur]
|
|
return cur
|
|
|
|
def _walkFromHead(head: int, used: List[bool]) -> List[int]:
|
|
order: List[int] = []
|
|
localSeen = [False for _ in range(n)]
|
|
cur = head
|
|
while cur != -1 and not used[cur] and not localSeen[cur]:
|
|
order.append(cur)
|
|
localSeen[cur] = True
|
|
cur = child[cur]
|
|
return order
|
|
|
|
used = [False for _ in range(n)]
|
|
paths: List[List[int]] = []
|
|
for mid in nonMfmaIds:
|
|
if used[mid]:
|
|
continue
|
|
head = _findHead(mid)
|
|
order = _walkFromHead(head, used)
|
|
assert order, f"extractPathsFromBeforeDeps produced empty path for module {mid}"
|
|
for i in order:
|
|
used[i] = True
|
|
paths.append(order)
|
|
|
|
# Separate paths that the MFMA depends on (must go before first MFMA).
|
|
preMfmaPaths: List[List[int]] = []
|
|
regularPaths: List[List[int]] = []
|
|
for path in paths:
|
|
if preMfmaTarget is not None and preMfmaTarget in path:
|
|
preMfmaPaths.append(path)
|
|
else:
|
|
regularPaths.append(path)
|
|
|
|
return mfmaIdx, regularPaths, preMfmaPaths
|
|
|
|
|
|
def instructionSchedule(emittedModules, multiDU: bool = False,
|
|
minGapDsReadToWait=_MIN_MFMA_GAP_DS_READ_TO_WAIT_DEFAULT):
|
|
"""Interleave non-MFMA instructions between MFMAs using 2 slots/interval.
|
|
|
|
Rules (shared):
|
|
- MFMA order is preserved.
|
|
- Between two adjacent MFMAs there are 2 placement slots.
|
|
- At most one ds_read (LocalReadInstruction) per interval.
|
|
- Before dependencies are respected at module order level.
|
|
- Minimm distance between ds_read and it waitcnt (hardcoded for now)
|
|
- Module-internal instruction order is preserved.
|
|
- GR path is spread as much as possible across remaining valid slots.
|
|
|
|
When multiDU is False (single-DU / legacy path):
|
|
- LR path containing a WAIT_GR is packed from the end backwards.
|
|
|
|
When multiDU is True (MX multi-DU path):
|
|
- LR path containing a WAIT_GR is placed forward (wait_gr → sync → LR insts).
|
|
- wait_gr+barrier stay contiguous; GR m0/buffer_load defer until that path's
|
|
post-barrier ds_reads complete.
|
|
|
|
TODO : To be tested on multi-partition setup.
|
|
"""
|
|
if not emittedModules:
|
|
return Module()
|
|
|
|
isMFMA = lambda x: isinstance(x, (MFMAInstruction, MXMFMAInstruction))
|
|
n = len(emittedModules)
|
|
|
|
mfmaIdx, pathOrders, preMfmaOrders = extractPathsFromBeforeDeps(emittedModules)
|
|
mfmas = [x for x in emittedModules[mfmaIdx].instructions if isMFMA(x)]
|
|
|
|
def _emitPreMfma(result):
|
|
for order in preMfmaOrders:
|
|
for mid in order:
|
|
for inst in emittedModules[mid].instructions:
|
|
result.add(inst)
|
|
|
|
# Single MFMA: no slots to interleave into — emit preMfma, MFMA, then paths.
|
|
if len(mfmas) < 2:
|
|
result = Module()
|
|
_emitPreMfma(result)
|
|
for m in mfmas:
|
|
result.add(m)
|
|
for order in pathOrders:
|
|
for mid in order:
|
|
for inst in emittedModules[mid].instructions:
|
|
result.add(inst)
|
|
return result
|
|
|
|
paths = _classifyPaths(pathOrders, emittedModules)
|
|
rules = _SchedulingRules(totalSlots=(len(mfmas) - 1) * 2,
|
|
minGapDsReadToWait=minGapDsReadToWait)
|
|
base_validators = [rules.oneDsReadPerInterval, rules.minGapDsReadBeforeWait,
|
|
rules.minGapDsReadToWait, rules.noM0WithBufferLoad]
|
|
base_adjusters = [rules.spreadBufferLoads]
|
|
if multiDU:
|
|
base_validators.append(rules.grAfterWaitGrDsReads)
|
|
base_adjusters.insert(0, rules.deferGrAfterWaitGrDsReads)
|
|
placer = _SlotPlacer(
|
|
len(mfmas) - 1, n, pathOrders,
|
|
validators=base_validators,
|
|
adjusters=base_adjusters,
|
|
onPlace=rules.trackPlacement)
|
|
|
|
for order, hasWaitGR in paths:
|
|
if not order:
|
|
continue
|
|
reverse = (not multiDU) and hasWaitGR
|
|
pathInsts = _flattenPath(order, emittedModules, reverse=reverse)
|
|
rules.resetPath()
|
|
if not hasWaitGR:
|
|
rules.setupBufLoadSpreading(placer, pathInsts, order)
|
|
placer.placePath(pathInsts, reverse=reverse, multiDU=multiDU)
|
|
if multiDU and hasWaitGR:
|
|
rules.setPostWaitGrDsReadFence(placer)
|
|
|
|
scheduled = Module()
|
|
_emitPreMfma(scheduled)
|
|
scheduled.add(placer.assemble(mfmas))
|
|
|
|
# Post-pass: adjust vmcnt of any SWaitCnt to account for buffer_loads
|
|
# that the scheduler placed before it within this subIterK.
|
|
bufLoadCount = 0
|
|
for inst in scheduled.flatitems():
|
|
if _isBufferLoad(inst):
|
|
bufLoadCount += 1
|
|
elif _isWaitCnt(inst) and inst.vlcnt >= 0:
|
|
if getattr(inst, 'adjustVmcnt', True):
|
|
inst.vlcnt += bufLoadCount
|
|
|
|
return scheduled
|