use std::collections::{BTreeMap, BTreeSet};
use crate::DType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Space {
Global,
Shared,
Register,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Swizzle {
None,
Xor(u32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Layout {
pub offset_bytes: usize,
pub strides: Vec<usize>,
pub swizzle: Swizzle,
}
impl Layout {
pub fn row_major(dims: &[usize]) -> Self {
let mut strides = vec![1usize; dims.len()];
for i in (0..dims.len().saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * dims[i + 1];
}
Self {
offset_bytes: 0,
strides,
swizzle: Swizzle::None,
}
}
pub fn col_major(dims: &[usize]) -> Self {
let mut strides = vec![1usize; dims.len()];
for i in 1..dims.len() {
strides[i] = strides[i - 1] * dims[i - 1];
}
Self {
offset_bytes: 0,
strides,
swizzle: Swizzle::None,
}
}
pub fn extent_elems(&self, dims: &[usize]) -> usize {
dims.iter()
.zip(&self.strides)
.map(|(d, s)| d.saturating_sub(1) * s)
.sum::<usize>()
+ 1
}
pub fn agrees_with(&self, other: &Layout) -> bool {
self.offset_bytes == other.offset_bytes
&& self.strides == other.strides
&& self.swizzle == other.swizzle
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Region {
pub name: String,
pub space: Space,
pub dims: Vec<usize>,
pub dtype: DType,
pub stages: usize,
pub layout: Layout,
}
impl Region {
pub fn bytes(&self) -> usize {
self.dims.iter().product::<usize>() * self.dtype.size_bytes() * self.stages.max(1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Role {
pub name: String,
pub warps: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Barrier {
pub name: String,
pub producers: Vec<String>,
pub consumers: Vec<String>,
pub count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Instruction {
CoopMatrix { m: u32, n: u32, k: u32 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct Access {
pub region: String,
pub layout: Option<Layout>,
}
impl Access {
pub fn plain(region: impl Into<String>) -> Self {
Self {
region: region.into(),
layout: None,
}
}
pub fn with(region: impl Into<String>, layout: Layout) -> Self {
Self {
region: region.into(),
layout: Some(layout),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
Load { access: Access, stage: usize },
Compute {
reads: Vec<Access>,
writes: Vec<Access>,
stage: usize,
via: Option<Instruction>,
},
Store { access: Access, stage: usize },
Arrive { barrier: String, stage: usize },
Wait { barrier: String, stage: usize },
}
impl Action {
pub fn stage(&self) -> usize {
match self {
Self::Load { stage, .. }
| Self::Compute { stage, .. }
| Self::Store { stage, .. }
| Self::Arrive { stage, .. }
| Self::Wait { stage, .. } => *stage,
}
}
pub fn accesses(&self) -> Vec<&Access> {
match self {
Self::Load { access, .. } | Self::Store { access, .. } => vec![access],
Self::Compute { reads, writes, .. } => reads.iter().chain(writes).collect(),
_ => vec![],
}
}
fn barrier(&self) -> Option<&str> {
match self {
Self::Arrive { barrier, .. } | Self::Wait { barrier, .. } => Some(barrier),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct KernelSchedule {
pub name: String,
pub regions: Vec<Region>,
pub roles: Vec<Role>,
pub barriers: Vec<Barrier>,
pub body: BTreeMap<String, Vec<Action>>,
pub stages: usize,
pub requires: Vec<Feature>,
}
impl KernelSchedule {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
stages: 1,
..Default::default()
}
}
pub fn bytes_in(&self, space: Space) -> usize {
self.regions
.iter()
.filter(|r| r.space == space)
.map(Region::bytes)
.sum()
}
pub fn warps(&self) -> BTreeSet<u32> {
self.roles
.iter()
.flat_map(|r| r.warps.iter().copied())
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Feature {
CoopMatrix { m: u32, n: u32, k: u32 },
AsyncCopy,
AsyncBarrier,
Swizzle,
}
pub const MAX_QUERIED_FEATURES: usize = 16;
#[derive(Debug, Clone, Copy)]
pub struct FeatureSet {
items: [Option<Feature>; MAX_QUERIED_FEATURES],
len: usize,
pub truncated: bool,
}
impl Default for FeatureSet {
fn default() -> Self {
Self::new()
}
}
impl FeatureSet {
pub const fn new() -> Self {
Self {
items: [None; MAX_QUERIED_FEATURES],
len: 0,
truncated: false,
}
}
pub fn push(&mut self, f: Feature) -> bool {
if self.len >= MAX_QUERIED_FEATURES {
self.truncated = true;
return false;
}
self.items[self.len] = Some(f);
self.len += 1;
true
}
pub fn contains(&self, f: Feature) -> bool {
self.items[..self.len].contains(&Some(f))
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Debug, Clone, Copy)]
pub enum Features {
Static(&'static [Feature]),
Queried(FeatureSet),
}
#[derive(Debug, Clone, Copy)]
pub struct Target {
pub name: &'static str,
pub max_shared_bytes: usize,
pub max_warps: usize,
pub features: Features,
}
impl Target {
pub fn has(&self, f: Feature) -> bool {
match &self.features {
Features::Static(list) => list.contains(&f),
Features::Queried(set) => set.contains(f),
}
}
pub fn capabilities_may_be_incomplete(&self) -> bool {
matches!(&self.features, Features::Queried(s) if s.truncated)
}
}
impl Target {
pub const PORTABLE: Self = Self {
name: "portable-floor",
max_shared_bytes: 32 * 1024,
max_warps: 32,
features: Features::Static(&[]),
};
pub const CUDA_SM86: Self = Self {
name: "sm_86",
max_shared_bytes: 48 * 1024,
max_warps: 32,
features: Features::Static(&[
Feature::CoopMatrix { m: 16, n: 8, k: 16 },
Feature::AsyncCopy,
Feature::Swizzle,
]),
};
pub const CUDA_SM70: Self = Self {
name: "sm_70",
max_shared_bytes: 48 * 1024,
max_warps: 32,
features: Features::Static(&[Feature::CoopMatrix { m: 16, n: 8, k: 16 }]),
};
pub const ROCM_GFX908: Self = Self {
name: "gfx908",
max_shared_bytes: 64 * 1024,
max_warps: 32,
features: Features::Static(&[Feature::CoopMatrix { m: 16, n: 16, k: 4 }]),
};
pub const ROCM_GFX1103: Self = Self {
name: "gfx1103",
max_shared_bytes: 64 * 1024,
max_warps: 32,
features: Features::Static(&[]),
};
pub const METAL_APPLE: Self = Self {
name: "apple-gpu",
max_shared_bytes: 32 * 1024,
max_warps: 32,
features: Features::Static(&[Feature::CoopMatrix { m: 8, n: 8, k: 8 }, Feature::Swizzle]),
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KernelScheduleError {
UnsynchronizedHandoff {
region: String,
producer: String,
consumer: String,
},
DeadlockWait { role: String, barrier: String },
UnreachableCount {
barrier: String,
count: u32,
producers: usize,
},
SharedOverBudget {
bytes: usize,
limit: usize,
target: &'static str,
},
WarpConflict { warp: u32, roles: Vec<String> },
UndeclaredName { kind: &'static str, name: String },
EmptyRole { role: String },
StageOutOfRange {
role: String,
stage: usize,
stages: usize,
},
LayoutDisagreement {
region: String,
a: String,
b: String,
detail: String,
},
UnsupportedFeature {
feature: Feature,
target: &'static str,
},
MissingReadBarrier {
role: String,
region: String,
wrote_at: usize,
read_at: usize,
},
MissingOverwriteBarrier {
role: String,
region: String,
read_at: usize,
overwrote_at: usize,
},
OperandStrideNotTiled {
region: String,
stride: usize,
tile: u32,
instruction: Instruction,
},
OperandNotTileable {
region: String,
dims: Vec<usize>,
m: u32,
k: u32,
},
IllegalSwizzle {
region: String,
period: u32,
innermost: usize,
},
LayoutOutOfRegion {
region: String,
role: String,
extent_elems: usize,
region_elems: usize,
},
}
impl std::fmt::Display for KernelScheduleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsynchronizedHandoff {
region,
producer,
consumer,
} => write!(
f,
"role '{consumer}' reads '{region}' written by role '{producer}' with no \
barrier between them — a race, not a slow path"
),
Self::DeadlockWait { role, barrier } => {
write!(
f,
"role '{role}' waits on barrier '{barrier}' that no role arrives at"
)
}
Self::UnreachableCount {
barrier,
count,
producers,
} => write!(
f,
"barrier '{barrier}' needs {count} arrival(s) but only {producers} role(s) \
arrive at it"
),
Self::SharedOverBudget {
bytes,
limit,
target,
} => write!(
f,
"declared shared memory {bytes} B exceeds {target}'s {limit} B budget"
),
Self::WarpConflict { warp, roles } => {
write!(f, "warp {warp} claimed by roles {roles:?}")
}
Self::UndeclaredName { kind, name } => write!(f, "undeclared {kind}: '{name}'"),
Self::EmptyRole { role } => write!(f, "role '{role}' has no actions"),
Self::StageOutOfRange {
role,
stage,
stages,
} => write!(
f,
"role '{role}' names stage {stage} but the pipeline declares {stages}"
),
Self::UnsupportedFeature { feature, target } => write!(
f,
"schedule requires {feature:?} which target '{target}' does not provide — \
no step-down is attempted"
),
Self::MissingReadBarrier {
role,
region,
wrote_at,
read_at,
} => write!(
f,
"role '{role}' writes '{region}' at action {wrote_at} and reads it at \
{read_at} with no barrier between — the write may not be visible"
),
Self::MissingOverwriteBarrier {
role,
region,
read_at,
overwrote_at,
} => write!(
f,
"role '{role}' reads '{region}' at action {read_at} and overwrites it at \
{overwrote_at} with no barrier between — the overwrite may land mid-read"
),
Self::OperandStrideNotTiled {
region,
stride,
tile,
instruction,
} => write!(
f,
"operand '{region}' has row stride {stride}, not a multiple of the \
{tile}-wide tile {instruction:?} requires"
),
Self::OperandNotTileable { region, dims, m, k } => write!(
f,
"operand '{region}' with dims {dims:?} does not tile evenly under {m}x{k}"
),
Self::IllegalSwizzle {
region,
period,
innermost,
} => write!(
f,
"region '{region}': XOR-swizzle period {period} must be a power of two that \
divides the innermost extent {innermost}, or it aliases rows instead of \
spreading them"
),
Self::LayoutDisagreement {
region,
a,
b,
detail,
} => write!(
f,
"region '{region}': '{a}' and '{b}' commit to different layouts ({detail}) — \
the shapes match and the addressing does not"
),
Self::LayoutOutOfRegion {
region,
role,
extent_elems,
region_elems,
} => write!(
f,
"role '{role}' commits to a layout over '{region}' spanning {extent_elems} \
element(s), past the region's {region_elems}"
),
}
}
}
pub fn verify_kernel_schedule(sched: &KernelSchedule, target: Target) -> Vec<KernelScheduleError> {
let mut errors = Vec::new();
check_declarations_resolve(sched, &mut errors);
check_roles(sched, target, &mut errors);
check_resources(sched, target, &mut errors);
check_synchronization(sched, &mut errors);
check_intra_role_ordering(sched, &mut errors);
check_target_capabilities(sched, target, &mut errors);
check_operand_contracts(sched, target, &mut errors);
check_layout_commitments(sched, &mut errors);
errors
}
fn check_declarations_resolve(sched: &KernelSchedule, errors: &mut Vec<KernelScheduleError>) {
let role_names: BTreeSet<&str> = sched.roles.iter().map(|r| r.name.as_str()).collect();
let region_names: BTreeSet<&str> = sched.regions.iter().map(|r| r.name.as_str()).collect();
let barrier_names: BTreeSet<&str> = sched.barriers.iter().map(|b| b.name.as_str()).collect();
for b in &sched.barriers {
for r in b.producers.iter().chain(&b.consumers) {
if !role_names.contains(r.as_str()) {
errors.push(KernelScheduleError::UndeclaredName {
kind: "role",
name: r.clone(),
});
}
}
}
for (role, actions) in &sched.body {
if !role_names.contains(role.as_str()) {
errors.push(KernelScheduleError::UndeclaredName {
kind: "role",
name: role.clone(),
});
}
for a in actions {
if a.stage() >= sched.stages.max(1) {
errors.push(KernelScheduleError::StageOutOfRange {
role: role.clone(),
stage: a.stage(),
stages: sched.stages.max(1),
});
}
for acc in a.accesses() {
if !region_names.contains(acc.region.as_str()) {
errors.push(KernelScheduleError::UndeclaredName {
kind: "region",
name: acc.region.clone(),
});
}
}
if let Some(b) = a.barrier()
&& !barrier_names.contains(b)
{
errors.push(KernelScheduleError::UndeclaredName {
kind: "barrier",
name: b.to_string(),
});
}
}
}
}
fn check_roles(sched: &KernelSchedule, target: Target, errors: &mut Vec<KernelScheduleError>) {
let mut warp_owner: BTreeMap<u32, Vec<String>> = BTreeMap::new();
for r in &sched.roles {
if sched.body.get(&r.name).is_none_or(|a| a.is_empty()) {
errors.push(KernelScheduleError::EmptyRole {
role: r.name.clone(),
});
}
for w in &r.warps {
warp_owner.entry(*w).or_default().push(r.name.clone());
}
}
for (warp, roles) in warp_owner {
if roles.len() > 1 {
errors.push(KernelScheduleError::WarpConflict { warp, roles });
}
}
if sched.warps().len() > target.max_warps {
errors.push(KernelScheduleError::SharedOverBudget {
bytes: sched.warps().len(),
limit: target.max_warps,
target: target.name,
});
}
}
fn check_resources(sched: &KernelSchedule, target: Target, errors: &mut Vec<KernelScheduleError>) {
let shared = sched.bytes_in(Space::Shared);
if shared > target.max_shared_bytes {
errors.push(KernelScheduleError::SharedOverBudget {
bytes: shared,
limit: target.max_shared_bytes,
target: target.name,
});
}
}
fn check_synchronization(sched: &KernelSchedule, errors: &mut Vec<KernelScheduleError>) {
for b in &sched.barriers {
let arrivers: usize = b
.producers
.iter()
.filter(|p| {
sched.body.get(*p).is_some_and(|acts| {
acts.iter()
.any(|a| matches!(a, Action::Arrive { barrier, .. } if barrier == &b.name))
})
})
.count();
if (b.count as usize) > arrivers {
errors.push(KernelScheduleError::UnreachableCount {
barrier: b.name.clone(),
count: b.count,
producers: arrivers,
});
}
}
for (role, actions) in &sched.body {
for a in actions {
if let Action::Wait { barrier, .. } = a {
let anyone_arrives = sched.body.values().any(|acts| {
acts.iter()
.any(|x| matches!(x, Action::Arrive { barrier: b2, .. } if b2 == barrier))
});
if !anyone_arrives {
errors.push(KernelScheduleError::DeadlockWait {
role: role.clone(),
barrier: barrier.clone(),
});
}
}
}
}
let mut writers: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
let mut readers: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
for (role, actions) in &sched.body {
for a in actions {
match a {
Action::Load { access, .. } => {
writers.entry(&access.region).or_default().insert(role);
}
Action::Compute { reads, writes, .. } => {
for r in reads {
readers.entry(&r.region).or_default().insert(role);
}
for w in writes {
writers.entry(&w.region).or_default().insert(role);
}
}
Action::Store { access, .. } => {
readers.entry(&access.region).or_default().insert(role);
}
_ => {}
}
}
}
for (region, ws) in &writers {
let Some(rs) = readers.get(region) else {
continue;
};
for w in ws {
for r in rs {
if w == r {
continue;
}
let synced = sched.barriers.iter().any(|b| {
b.producers.iter().any(|p| p == w)
&& b.consumers.iter().any(|c| c == r)
&& sched.body.get(*w).is_some_and(|acts| {
acts.iter().any(|a| matches!(a, Action::Arrive { barrier, .. } if barrier == &b.name))
})
&& sched.body.get(*r).is_some_and(|acts| {
acts.iter().any(|a| matches!(a, Action::Wait { barrier, .. } if barrier == &b.name))
})
});
if !synced {
errors.push(KernelScheduleError::UnsynchronizedHandoff {
region: (*region).to_string(),
producer: (*w).to_string(),
consumer: (*r).to_string(),
});
}
}
}
}
}
fn check_intra_role_ordering(sched: &KernelSchedule, errors: &mut Vec<KernelScheduleError>) {
let shared: BTreeSet<&str> = sched
.regions
.iter()
.filter(|r| r.space == Space::Shared)
.map(|r| r.name.as_str())
.collect();
let depth: BTreeMap<&str, usize> = sched
.regions
.iter()
.map(|r| (r.name.as_str(), r.stages.max(1)))
.collect();
let slot = |region: &str, stage: usize| -> usize {
let d = depth.get(region).copied().unwrap_or(1);
if d <= 1 { 0 } else { stage % d }
};
for (role, actions) in &sched.body {
let mut wrote: BTreeMap<(&str, usize), usize> = BTreeMap::new();
let mut read: BTreeMap<(&str, usize), usize> = BTreeMap::new();
let mut last_sync: Option<usize> = None;
for (i, a) in actions.iter().enumerate() {
if matches!(a, Action::Wait { .. } | Action::Arrive { .. }) {
last_sync = Some(i);
continue;
}
let (reads, writes): (Vec<&str>, Vec<&str>) = match a {
Action::Load { access, .. } => (vec![], vec![access.region.as_str()]),
Action::Store { access, .. } => (vec![access.region.as_str()], vec![]),
Action::Compute { reads, writes, .. } => (
reads.iter().map(|x| x.region.as_str()).collect(),
writes.iter().map(|x| x.region.as_str()).collect(),
),
_ => (vec![], vec![]),
};
let stage = a.stage();
for r in &reads {
if !shared.contains(r) {
continue;
}
if let Some(w) = wrote.get(&(*r, slot(r, stage)))
&& last_sync.is_none_or(|s| s < *w)
{
errors.push(KernelScheduleError::MissingReadBarrier {
role: role.clone(),
region: (*r).to_string(),
wrote_at: *w,
read_at: i,
});
}
}
for w in &writes {
if !shared.contains(w) {
continue;
}
if let Some(rd) = read.get(&(*w, slot(w, stage)))
&& last_sync.is_none_or(|s| s < *rd)
{
errors.push(KernelScheduleError::MissingOverwriteBarrier {
role: role.clone(),
region: (*w).to_string(),
read_at: *rd,
overwrote_at: i,
});
}
}
for r in reads {
read.insert((r, slot(r, stage)), i);
}
for w in writes {
wrote.insert((w, slot(w, stage)), i);
}
}
}
}
fn check_target_capabilities(
sched: &KernelSchedule,
target: Target,
errors: &mut Vec<KernelScheduleError>,
) {
for f in &sched.requires {
if !target.has(*f) {
errors.push(KernelScheduleError::UnsupportedFeature {
feature: *f,
target: target.name,
});
}
}
let uses_swizzle = sched
.regions
.iter()
.any(|r| r.layout.swizzle != Swizzle::None)
|| sched.body.values().flatten().any(|a| {
a.accesses().iter().any(|acc| {
acc.layout
.as_ref()
.is_some_and(|l| l.swizzle != Swizzle::None)
})
});
if uses_swizzle && !target.has(Feature::Swizzle) {
errors.push(KernelScheduleError::UnsupportedFeature {
feature: Feature::Swizzle,
target: target.name,
});
}
}
fn check_operand_contracts(
sched: &KernelSchedule,
target: Target,
errors: &mut Vec<KernelScheduleError>,
) {
let region_lookup: BTreeMap<&str, &Region> =
sched.regions.iter().map(|r| (r.name.as_str(), r)).collect();
for actions in sched.body.values() {
for a in actions {
let Action::Compute {
reads,
writes,
via: Some(instr),
..
} = a
else {
continue;
};
let Instruction::CoopMatrix { m, n, k } = *instr;
if !target.has(Feature::CoopMatrix { m, n, k }) {
errors.push(KernelScheduleError::UnsupportedFeature {
feature: Feature::CoopMatrix { m, n, k },
target: target.name,
});
}
for acc in reads.iter().chain(writes) {
let Some(region) = region_lookup.get(acc.region.as_str()) else {
continue;
};
let layout = acc.layout.as_ref().unwrap_or(®ion.layout);
let stride = *layout.strides.first().unwrap_or(&1);
if !stride.is_multiple_of(n as usize) {
errors.push(KernelScheduleError::OperandStrideNotTiled {
region: acc.region.clone(),
stride,
tile: n,
instruction: *instr,
});
}
let rows = *region.dims.first().unwrap_or(&1);
let cols = *region.dims.last().unwrap_or(&1);
if !rows.is_multiple_of(m as usize) || !cols.is_multiple_of(k as usize) {
errors.push(KernelScheduleError::OperandNotTileable {
region: acc.region.clone(),
dims: region.dims.clone(),
m,
k,
});
}
}
}
}
}
fn check_layout_commitments(sched: &KernelSchedule, errors: &mut Vec<KernelScheduleError>) {
let region_by_name: BTreeMap<&str, &Region> =
sched.regions.iter().map(|r| (r.name.as_str(), r)).collect();
let mut committed: BTreeMap<&str, (String, &Layout)> = BTreeMap::new();
for (role, actions) in &sched.body {
for a in actions {
for acc in a.accesses() {
let Some(region) = region_by_name.get(acc.region.as_str()) else {
continue;
};
let layout = acc.layout.as_ref().unwrap_or(®ion.layout);
if let Swizzle::Xor(period) = layout.swizzle {
let innermost = *region.dims.last().unwrap_or(&1);
let pow2 = period.is_power_of_two();
if !pow2 || period == 0 || !innermost.is_multiple_of(period as usize) {
errors.push(KernelScheduleError::IllegalSwizzle {
region: region.name.clone(),
period,
innermost,
});
}
}
let region_elems: usize = region.dims.iter().product();
let extent = layout.extent_elems(®ion.dims);
let off_elems = layout.offset_bytes / region.dtype.size_bytes().max(1);
if off_elems + extent > region_elems {
errors.push(KernelScheduleError::LayoutOutOfRegion {
region: region.name.clone(),
role: role.clone(),
extent_elems: off_elems + extent,
region_elems,
});
}
match committed.get(acc.region.as_str()) {
Some((other_role, other)) if !layout.agrees_with(other) => {
errors.push(KernelScheduleError::LayoutDisagreement {
region: acc.region.clone(),
a: other_role.clone(),
b: role.clone(),
detail: format!(
"strides {:?}/swizzle {:?} vs strides {:?}/swizzle {:?}",
other.strides, other.swizzle, layout.strides, layout.swizzle
),
});
}
Some(_) => {}
None => {
committed.insert(acc.region.as_str(), (role.clone(), layout));
}
}
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Lowered {
pub region_offsets: BTreeMap<String, usize>,
pub barrier_slots: BTreeMap<String, u32>,
pub role_base_warp: BTreeMap<String, u32>,
pub shared_bytes: usize,
pub threads: usize,
}
pub fn lower(sched: &KernelSchedule, target: Target) -> Result<Lowered, Vec<KernelScheduleError>> {
let errors = verify_kernel_schedule(sched, target);
if !errors.is_empty() {
return Err(errors);
}
const WARP_LANES: usize = 32;
let mut region_offsets = BTreeMap::new();
let mut cursor = 0usize;
for r in sched.regions.iter().filter(|r| r.space == Space::Shared) {
let align = r.dtype.size_bytes().max(1);
cursor = cursor.div_ceil(align) * align;
region_offsets.insert(r.name.clone(), cursor);
cursor += r.bytes();
}
let shared_bytes = cursor;
let barrier_slots = sched
.barriers
.iter()
.enumerate()
.map(|(i, b)| (b.name.clone(), i as u32))
.collect();
let role_base_warp = sched
.roles
.iter()
.filter_map(|r| r.warps.iter().min().map(|w| (r.name.clone(), *w)))
.collect();
Ok(Lowered {
region_offsets,
barrier_slots,
role_base_warp,
shared_bytes,
threads: sched.warps().len() * WARP_LANES,
})
}