use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::ExprCall;
use super::super::_function::CUDATileFunctionCompiler;
use super::super::_value::{DimOrigin, TileRustValue};
use crate::error::JITError;
pub(super) struct AxisGoals<'v> {
pub(super) axis: usize,
pub(super) extent_axis: usize,
pub(super) tile: i32,
pub(super) static_extent: Option<i32>,
pub(super) index: &'v TileRustValue,
}
pub(super) struct StaticViolation {
pub(super) min: i64,
pub(super) max: i64,
pub(super) num_tiles: i64,
}
pub(super) enum Brand {
Matched,
Foreign,
}
pub(super) fn brand_of(goals: &AxisGoals<'_>, bound_origin: &DimOrigin) -> Option<Brand> {
match goals.index.index_origin.as_ref() {
Some(origin) if origin == bound_origin => Some(Brand::Matched),
Some(_) => Some(Brand::Foreign),
None => None,
}
}
pub(super) fn fold_static(goals: &AxisGoals<'_>) -> Option<Result<(), StaticViolation>> {
let bounds = goals.index.bounds?;
let extent = goals.static_extent?;
let num_tiles = (extent as i64 + goals.tile as i64 - 1) / goals.tile as i64;
Some(if 0 <= bounds.start && bounds.end < num_tiles {
Ok(())
} else {
Err(StaticViolation {
min: bounds.start,
max: bounds.end,
num_tiles,
})
})
}
impl<'m> CUDATileFunctionCompiler<'m> {
pub(super) fn form_axis_goals<'v>(
&self,
partition: &TileRustValue,
static_tile: &[i32],
static_shape: &[i32],
dim_map: &[i32],
axis: usize,
index: &'v TileRustValue,
) -> AxisGoals<'v> {
let extent_axis = dim_map[axis] as usize;
let static_extent = match static_shape[extent_axis] {
-1 => self.declared_view_extent(partition, dim_map, axis),
extent => Some(extent),
};
AxisGoals {
axis,
extent_axis,
tile: static_tile[axis],
static_extent,
index,
}
}
pub(super) fn discharge_by_axis_provenance(
&self,
goals: &AxisGoals<'_>,
partition: &TileRustValue,
) -> bool {
if let (Some(origin), Some(target)) = (
goals.index.partition_axis_origin.as_ref(),
partition.tensor_origin.as_ref(),
) {
if origin.tile_dim == goals.tile
&& self.root_framed_param(target).is_some()
&& self.resolve_dim_le(
&origin.tensor,
origin.axis,
target,
goals.extent_axis,
goals.tile,
)
{
return true;
}
}
if let (
Some(DimOrigin::PartitionAxis {
view,
axis,
tile_dim,
}),
Some(partition_view),
) = (goals.index.index_origin.as_ref(), partition.value)
{
if *view == partition_view && *axis == goals.axis && *tile_dim == goals.tile {
return true;
}
}
false
}
pub(super) fn discharge_by_block_id_axiom(
&self,
goals: &AxisGoals<'_>,
partition: &TileRustValue,
) -> bool {
use cuda_async::predicate::{Atom, Predicate, Term};
let Some(term) = goals.index.term.as_ref() else {
return false;
};
if term.constant_part() != 0 || term.coeffs().len() != 1 {
return false;
}
let Some((atom, &coeff)) = term.coeffs().iter().next() else {
return false;
};
let k = match (atom, coeff) {
(Atom::TileBlockId(k), 1) => *k,
_ => return false,
};
let Some(tensor) = partition.tensor_origin.as_ref() else {
return false;
};
let Some(¶m) = self.param_index.get(tensor) else {
return false;
};
let tile = goals.tile as i64;
let Some(lhs) = Term::atom(Atom::NumTileBlocks(k)).mul_const(tile) else {
return false;
};
let Some(rhs) =
Term::atom(self.extent_atom(param, goals.extent_axis)).add(&Term::constant(tile - 1))
else {
return false;
};
let Some(le) = Predicate::le(&lhs, &rhs) else {
return false;
};
let cause = format!(
"num_tile_blocks({k}) <= ceil(extent({tensor}, {})/{})",
goals.extent_axis, goals.tile
);
self.lower_obligation(le, cause)
}
pub(super) fn hoist_zero_coordinate_nonempty_extent(
&self,
goals: &AxisGoals<'_>,
partition: &TileRustValue,
) -> bool {
use cuda_async::predicate::{Predicate, Term};
if goals.static_extent.is_some() {
return false;
}
let Some(bounds) = goals.index.bounds else {
return false;
};
if !(bounds.start == 0 && bounds.end == 0) {
return false;
}
let Some(tensor) = partition.tensor_origin.as_ref() else {
return false;
};
let Some(¶m) = self.param_index.get(tensor) else {
return false;
};
let predicate = Predicate::nonzero(Term::atom(self.extent_atom(param, goals.extent_axis)));
let cause = format!(
"partition access on axis {} of `{tensor}` requires a non-empty extent",
goals.extent_axis
);
self.lower_obligation(predicate, cause)
}
pub(super) fn resolve_dynamic_extent(
&self,
goals: &AxisGoals<'_>,
static_shape: &[i32],
shape_dims: &[TileRustValue],
call_expr: &ExprCall,
) -> Result<TileRustValue, JITError> {
let span: Span = call_expr.span();
let dynamic_shape_index = static_shape
.iter()
.take(goals.extent_axis + 1)
.filter(|&&dim| dim == -1)
.count()
.checked_sub(1)
.ok_or_else(|| {
self.jit_error(
&span,
"internal: dynamic partition dimension was not found in tensor shape metadata",
)
})?;
shape_dims
.get(dynamic_shape_index)
.cloned()
.ok_or_else(|| {
self.jit_error(
&span,
&format!(
"internal: tensor shape metadata is missing dynamic dimension {dynamic_shape_index}"
),
)
})
}
}