1use core::fmt;
2
3use alloc::{format, string::ToString, vec::Vec};
4
5use cubecl_macros_internal::cube_op;
6use derive_more::{Deref, From};
7use derive_new::new;
8use itertools::Itertools;
9use pliron::{
10 builtin::{
11 attributes::IdentifierAttr,
12 ops::FuncOp,
13 types::{FunctionType, IntegerType, Signedness},
14 },
15 combine::{Parser, parser::char::char},
16 derive::pliron_attr,
17 identifier::Identifier,
18 input_err,
19 irfmt::parsers::{delimited_list_parser, spaced, ssa_opd_parse, ssa_opd_parser},
20 location::Location,
21 op::OpObj,
22 parsable::{self, IntoParseResult, Parsable, ParseResult},
23 printable::{self, Printable},
24 symbol_table::SymbolTableCollection,
25 r#type::TypedHandle,
26 verify_err,
27};
28
29use crate::{
30 CanMaterialize, HasSideEffects, Pure,
31 attributes::{BoolAttr, IndexAttr},
32 dialect::{general::SymbolUserOpVerifyErr, synchronization::SyncScope},
33 interfaces::{
34 TypedExt,
35 side_effects::{MemoryEffect, MemoryEffectsOp},
36 synchronizes,
37 },
38 prelude::*,
39 types::{
40 ArrayType, MatrixShape, PointerType, VectorType,
41 matrix::{MatrixLayout, MatrixType},
42 },
43};
44
45#[pliron_attr(name = "matrix.layout", format = "$0", verifier = "succ")]
46#[derive(new, From, PartialEq, Eq, Clone, Debug, Hash, PartialOrd, Ord, Deref)]
47pub struct MatrixLayoutAttr(pub MatrixLayout);
48
49#[pliron_attr(name = "matrix.type", format = "$0", verifier = "succ")]
50#[derive(new, From, Debug, Clone, PartialEq, Eq, Hash, Deref)]
51pub struct MatrixTypeAttr(pub TypedHandle<MatrixType>);
52
53#[pliron_attr(name = "matrix.type", format = "$0", verifier = "succ")]
54#[derive(new, From, Debug, Clone, PartialEq, Eq, Hash, Deref)]
55pub struct MatrixShapeAttr(pub MatrixShape);
56
57#[cube_op(name = "matrix.fill")]
61#[result_ty(none)]
62#[op_traits(CanMaterialize, HasSideEffects)]
63pub struct FillOp {
64 #[operand(ptr_write)]
65 pub matrix: Value,
66 pub value: Value,
67}
68
69#[cube_op(name = "matrix.load")]
70#[result_ty(none)]
71#[op_traits(CanMaterialize, HasSideEffects)]
72pub struct LoadOp {
73 #[operand(ptr_write)]
74 pub matrix: Value,
75 #[operand(ptr_read)]
76 pub source: Value,
77 pub stride: Value,
78 pub layout: MatrixLayoutAttr,
79}
80synchronizes!(LoadOp, SyncScope::Plane);
81
82#[cube_op(name = "matrix.store")]
83#[result_ty(none)]
84#[op_traits(CanMaterialize, HasSideEffects)]
85pub struct StoreOp {
86 #[operand(ptr_read)]
87 pub matrix: Value,
88 #[operand(ptr_write)]
89 pub destination: Value,
90 pub stride: Value,
91 pub layout: MatrixLayoutAttr,
92}
93synchronizes!(StoreOp, SyncScope::Plane);
94
95#[cube_op(name = "matrix.multiply_accumulate")]
96#[result_ty(none)]
97#[op_traits(CanMaterialize, HasSideEffects)]
98pub struct MultiplyAccumulateOp {
99 #[operand(ptr_read)]
100 pub mat_a: Value,
101 #[operand(ptr_read)]
102 pub mat_b: Value,
103 #[operand(ptr_read)]
104 pub mat_c: Value,
105 #[operand(ptr_write)]
106 pub mat_d: Value,
107}
108synchronizes!(MultiplyAccumulateOp, SyncScope::Plane);
109
110#[cube_op(name = "matrix.cast")]
114#[result_ty(none)]
115#[op_traits(CanMaterialize, HasSideEffects)]
116pub struct CastOp {
117 #[operand(ptr_read)]
118 pub input: Value,
119 #[operand(ptr_write)]
120 pub output: Value,
121}
122
123#[cube_op(name = "matrix.row_index")]
124#[result_ty(fixed = IntegerType::get(ctx, 32, Signedness::Unsigned).into())]
125#[op_traits(CanMaterialize, Pure)]
126pub struct RowIndexOp {
127 pub lane_id: Value,
128 pub i: Value,
129 pub matrix_ty: MatrixTypeAttr,
130}
131
132#[cube_op(name = "matrix.col_index")]
133#[result_ty(fixed = IntegerType::get(ctx, 32, Signedness::Unsigned).into())]
134#[op_traits(CanMaterialize, Pure)]
135pub struct ColIndexOp {
136 pub lane_id: Value,
137 pub i: Value,
138 pub matrix_ty: MatrixTypeAttr,
139}
140
141#[cube_op(name = "matrix.ldmatrix")]
142#[result_ty(none)]
143#[op_traits(CanMaterialize, HasSideEffects)]
144pub struct LdMatrixOp {
145 #[operand(ptr_read)]
146 pub ptr: Value,
147 #[operand(ptr_write)]
148 pub out_arr: Value,
149 pub factor: IndexAttr,
150 pub transpose: BoolAttr,
151}
152synchronizes!(LdMatrixOp, SyncScope::Plane);
153
154#[cube_op(name = "matrix.stmatrix")]
155#[result_ty(none)]
156#[op_traits(CanMaterialize, HasSideEffects)]
157#[op_interfaces(OperandNOfType<0, ArrayType>, OperandNOfType<1, PointerType>)]
158pub struct StMatrixOp {
159 pub registers: Value,
160 #[operand(ptr_write)]
161 pub destination: Value,
162 pub factor: IndexAttr,
163 pub transpose: BoolAttr,
164}
165synchronizes!(StMatrixOp, SyncScope::Plane);
166
167#[cube_op(name = "matrix.mma_manual")]
168#[result_ty(none)]
169#[op_traits(CanMaterialize, HasSideEffects)]
170#[op_interfaces(
171 OperandNOfType<0, ArrayType>, OperandNOfType<1, ArrayType>, OperandNOfType<2, ArrayType>,
172 OperandNOfType<3, PointerType>,
173)]
174pub struct MmaManualOp {
175 pub registers_a: Value,
176 pub registers_b: Value,
177 pub registers_c: Value,
178 #[operand(ptr_write)]
179 pub registers_d: Value,
180 pub shape: MatrixShapeAttr,
181}
182synchronizes!(MmaManualOp, SyncScope::Plane);
183
184#[cube_op(name = "matrix.mma_manual_scaled")]
185#[result_ty(none)]
186#[op_traits(CanMaterialize, HasSideEffects)]
187#[op_interfaces(
188 OperandNOfType<0, ArrayType>, OperandNOfType<1, ArrayType>, OperandNOfType<2, ArrayType>,
189 OperandNOfType<3, PointerType>, OperandNOfType<4, VectorType>, OperandNOfType<5, VectorType>,
190)]
191pub struct MmaManualScaledOp {
192 pub registers_a: Value,
193 pub registers_b: Value,
194 pub registers_c: Value,
195 #[operand(ptr_write)]
196 pub registers_d: Value,
197 pub scales_a: Value,
198 pub scales_b: Value,
199 pub scales_factor: IndexAttr,
200 pub shape: MatrixShapeAttr,
201}
202synchronizes!(MmaManualScaledOp, SyncScope::Plane);
203
204#[pliron_op(
208 name = "matrix.elementwise",
209 attributes = (matrix_elementwise_closure: IdentifierAttr),
210 verifier = "succ"
211)]
212#[op_traits(CanMaterialize, HasSideEffects)]
213pub struct ElementwiseOp;
214
215impl ElementwiseOp {
216 pub fn new(
217 ctx: &mut Context,
218 matrix_in: Value,
219 matrix_out: Value,
220 closure: Identifier,
221 captures: Vec<Value>,
222 ) -> Self {
223 let mut opds = vec![matrix_in, matrix_out];
224 opds.extend(captures);
225 let op = Self {
226 op: Operation::new(ctx, Self::get_concrete_op_info(), vec![], opds, vec![], 0),
227 };
228 op.set_attr_matrix_elementwise_closure(ctx, IdentifierAttr::new(closure));
229 op
230 }
231
232 pub fn matrix_in(&self, ctx: &Context) -> Value {
233 self.get_operation().operand(ctx, 0)
234 }
235
236 pub fn matrix_out(&self, ctx: &Context) -> Value {
237 self.get_operation().operand(ctx, 1)
238 }
239
240 pub fn closure(&self, ctx: &Context) -> Identifier {
241 let attr = self.get_attr_matrix_elementwise_closure(ctx).unwrap();
242 attr.clone().into()
243 }
244
245 pub fn closure_captures(&self, ctx: &Context) -> Vec<Value> {
246 self.get_operation().deref(ctx).operands().skip(2).collect()
247 }
248}
249
250impl ElementwiseOp {
251 fn callee_type(&self, ctx: &Context) -> TypeHandle {
253 let u32 = IntegerType::get(ctx, 32, Signedness::Unsigned).to_handle();
254 let elem_ty = self
255 .matrix_in(ctx)
256 .unwrap_ptr(ctx)
257 .element_ty(ctx)
258 .scalar_ty(ctx);
259 let mut args = vec![u32, u32, elem_ty];
260 args.extend(self.closure_captures(ctx).iter().map(|it| it.get_type(ctx)));
261 FunctionType::get(ctx, args, vec![elem_ty]).to_handle()
262 }
263}
264
265impl Printable for ElementwiseOp {
266 fn fmt(
267 &self,
268 ctx: &Context,
269 _state: &printable::State,
270 f: &mut fmt::Formatter<'_>,
271 ) -> fmt::Result {
272 let op_id = self.get_opid().disp(ctx).to_string();
273 let mat_in = self.matrix_in(ctx).disp(ctx).to_string();
274 let mat_out = self.matrix_out(ctx).disp(ctx).to_string();
275 write!(f, "{op_id} ({mat_in}, {mat_out}) ")?;
276 print_closure(ctx, &self.closure(ctx), &self.closure_captures(ctx), f)
277 }
278}
279impl Parsable for ElementwiseOp {
280 type Arg = Vec<(Identifier, Location)>;
281 type Parsed = OpObj;
282 fn parse<'a>(
283 input: &mut parsable::StateStream<'a>,
284 arg: Self::Arg,
285 ) -> ParseResult<'a, Self::Parsed> {
286 if !arg.is_empty() {
287 return input_err!(input.loc(), "Expected no results").into_parse_result();
288 }
289
290 spaced(char('(')).parse_stream(input).into_result()?;
291 let mat_in = ssa_opd_parse(input, ())?.0;
292 spaced(char(',')).parse_stream(input).into_result()?;
293 let mat_out = ssa_opd_parse(input, ())?.0;
294 spaced(char(')')).parse_stream(input).into_result()?;
295
296 let (closure, captures) = parse_closure(input)?.0;
297 let ctx = &mut input.state.ctx;
298
299 let op = ElementwiseOp::new(ctx, mat_in, mat_out, closure, captures);
300 Ok(OpObj::new(op)).into_parse_result()
301 }
302}
303
304#[op_interface_impl]
305impl MemoryEffectsOp for ElementwiseOp {
306 fn memory_effects(&self, ctx: &Context) -> Vec<MemoryEffect> {
307 vec![
308 MemoryEffect::Read(self.matrix_in(ctx)),
309 MemoryEffect::Write(self.matrix_out(ctx)),
310 ]
311 }
312}
313
314#[op_interface_impl]
315impl SymbolUserOpInterface for ElementwiseOp {
316 fn verify_symbol_uses(
317 &self,
318 ctx: &Context,
319 symbol_tables: &mut SymbolTableCollection,
320 ) -> Result<()> {
321 let callee_sym = self.closure(ctx);
322 let Some(callee) =
323 symbol_tables.lookup_symbol_in_nearest_table(ctx, self.get_operation(), &callee_sym)
324 else {
325 return verify_err!(
326 self.loc(ctx),
327 SymbolUserOpVerifyErr::SymbolNotFound(callee_sym.to_string())
328 );
329 };
330 let Some(func_op) = (&*callee as &dyn Op).downcast_ref::<FuncOp>() else {
331 return verify_err!(
332 self.loc(ctx),
333 SymbolUserOpVerifyErr::NotFunc(callee_sym.to_string())
334 );
335 };
336 let func_op_ty = func_op.get_type(ctx);
337
338 if func_op_ty != self.callee_type(ctx) {
339 return verify_err!(
340 self.loc(ctx),
341 SymbolUserOpVerifyErr::FuncTypeErr(format!(
342 "expected {}, got {}",
343 func_op_ty.disp(ctx),
344 self.callee_type(ctx).disp(ctx)
345 ))
346 );
347 }
348 Ok(())
349 }
350
351 fn used_symbols(&self, ctx: &Context) -> Vec<Identifier> {
352 vec![self.closure(ctx)]
353 }
354}
355
356pub fn print_closure(
358 ctx: &Context,
359 closure: &Identifier,
360 captures: &[Value],
361 f: &mut fmt::Formatter<'_>,
362) -> fmt::Result {
363 let captures = captures
364 .iter()
365 .map(|it| it.disp(ctx).to_string())
366 .join(", ");
367 write!(f, "@{}({captures})", closure.disp(ctx))
368}
369
370pub fn parse_closure<'a>(
372 input: &mut parsable::StateStream<'a>,
373) -> ParseResult<'a, (Identifier, Vec<Value>)> {
374 let mut parse_closure = char('@').with(Identifier::parser(()));
375 let closure = parse_closure.parse_stream(input).into_result()?.0;
376 let mut captures = delimited_list_parser('(', ')', ',', ssa_opd_parser());
377 let captures = captures.parse_stream(input).into_result()?.0;
378 Ok((closure, captures)).into_parse_result()
379}