1use std::collections::{HashMap, HashSet};
2
3use cubecl_core::{
4 frontend::HasValue,
5 ir::{
6 Builtin, Scope,
7 attributes::{FuncInterface, IndexAttr},
8 dialect::general::ReadBuiltinOp,
9 ident,
10 prelude::*,
11 },
12};
13use pliron::{
14 builtin::{
15 given_names::set_block_arg_name,
16 ops::FuncOp,
17 types::{IntegerType, Signedness},
18 },
19 irbuild::{listener::DummyListener, match_rewrite::MatchRewrite},
20 value::Value,
21};
22
23use crate::{
24 metal::{BuiltInAttr, metal_op_with_out, signature::ATTR_BUILTIN_ATTRIBUTE},
25 shared::{
26 CompilationState,
27 builtin::{LowerBuiltins, absolute_pos, constant, cube_count, cube_pos},
28 },
29 target::Metal,
30};
31
32#[cube_op(name = "msl.read_dim3_builtin")]
33#[result_ty(argument)]
34pub struct ReadDim3BuiltinOp {
35 pub builtin: Value,
36 pub dim: IndexAttr,
37}
38
39metal_op_with_out!(ReadDim3BuiltinOp, |op, ctx| {
40 format!("{}[{}]", op.builtin(ctx).name(ctx), op.dim(ctx).0)
41});
42
43impl MatchRewrite for LowerBuiltins<Metal> {
44 fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool {
45 op.is_op::<ReadBuiltinOp>(ctx)
46 }
47
48 fn rewrite(
49 &mut self,
50 ctx: &mut Context,
51 rewriter: &mut MatchRewriter,
52 op: Ptr<Operation>,
53 ) -> Result<()> {
54 let builtin = op.as_op::<ReadBuiltinOp>(ctx).unwrap().builtin(ctx).0;
55 let scope = Scope::from_context_and_inserter(ctx, rewriter);
56 if let Some(new_value) = builtin.maybe_lower_metal(&scope) {
57 rewriter.replace_operation_with_values(ctx, op, vec![new_value]);
58 }
59 Ok(())
60 }
61}
62
63trait MetalBuiltin {
64 fn maybe_lower_metal(&self, scope: &Scope) -> Option<Value>;
65}
66
67impl MetalBuiltin for Builtin {
68 fn maybe_lower_metal(&self, scope: &Scope) -> Option<Value> {
69 let cube_dim = scope.ctx().aux_ty::<CompilationState>().cube_dim;
70 match self {
71 Builtin::UnitPos => None,
72 Builtin::UnitPosX if cube_dim.x == 1 => Some(constant::expand(scope, 0).value(scope)),
75 Builtin::UnitPosY if cube_dim.y == 1 => Some(constant::expand(scope, 0).value(scope)),
76 Builtin::UnitPosZ if cube_dim.z == 1 => Some(constant::expand(scope, 0).value(scope)),
77 Builtin::UnitPosX | Builtin::UnitPosY | Builtin::UnitPosZ => None,
78 Builtin::CubePosCluster => Some(constant::expand(scope, 0).value(scope)),
79 Builtin::CubePosClusterX => Some(constant::expand(scope, 0).value(scope)),
80 Builtin::CubePosClusterY => Some(constant::expand(scope, 0).value(scope)),
81 Builtin::CubePosClusterZ => Some(constant::expand(scope, 0).value(scope)),
82 Builtin::CubePos => Some(cube_pos::expand(scope).value(scope)),
83 Builtin::CubePosX | Builtin::CubePosY | Builtin::CubePosZ => None,
84 Builtin::CubeDim => Some(constant::expand(scope, cube_dim.num_elems()).value(scope)),
85 Builtin::CubeDimX => Some(constant::expand(scope, cube_dim.x).value(scope)),
86 Builtin::CubeDimY => Some(constant::expand(scope, cube_dim.y).value(scope)),
87 Builtin::CubeDimZ => Some(constant::expand(scope, cube_dim.z).value(scope)),
88 Builtin::CubeClusterDim => Some(constant::expand(scope, 1).value(scope)),
89 Builtin::CubeClusterDimX => Some(constant::expand(scope, 1).value(scope)),
90 Builtin::CubeClusterDimY => Some(constant::expand(scope, 1).value(scope)),
91 Builtin::CubeClusterDimZ => Some(constant::expand(scope, 1).value(scope)),
92 Builtin::CubeCount => Some(cube_count::expand(scope).value(scope)),
93 Builtin::CubeCountX | Builtin::CubeCountY | Builtin::CubeCountZ => None,
94 Builtin::PlaneDim => None,
95 Builtin::PlanePos => None,
96 Builtin::UnitPosPlane => None,
97 Builtin::AbsolutePos => Some(absolute_pos::expand(scope).value(scope)),
98 Builtin::AbsolutePosX | Builtin::AbsolutePosY | Builtin::AbsolutePosZ => None,
99 }
100 }
101}
102
103pub fn append_msl_builtins(ctx: &mut Context, entry_func: FuncOp) {
104 let op = entry_func.get_operation();
105 let mut used = HashSet::new();
106 let mut read_ops = HashSet::new();
107 let state = &mut (&mut used, &mut read_ops);
108 visit_all_ops_of_type::<ReadBuiltinOp, _>(ctx, state, op, |ctx, (used, ops), op| {
109 ops.insert(op);
110 used.insert(built_in_attr(op.builtin(ctx).0));
111 });
112
113 let values = used
114 .into_iter()
115 .map(|attr| {
116 let entry = entry_func.get_entry_block(ctx);
117 let i = entry_func.push_argument(ctx, attr.ty(ctx));
118 entry_func.set_arg_attr(ctx, i, &ATTR_BUILTIN_ATTRIBUTE, Box::new(attr));
119 set_block_arg_name(ctx, entry, i, Some(ident(attr.to_string())));
120 let value = entry.deref(ctx).get_argument(i);
121 (attr, value)
122 })
123 .collect::<HashMap<_, _>>();
124
125 let mut rewriter = IRRewriter::<DummyListener>::default();
126
127 for op in read_ops {
128 rewriter.set_insertion_point_before_operation(op.get_operation());
129
130 let builtin = op.builtin(ctx).0;
131 let attr = built_in_attr(builtin);
132 let value = values[&attr];
133 match builtin {
134 Builtin::UnitPos | Builtin::PlaneDim | Builtin::PlanePos | Builtin::UnitPosPlane => {
135 rewriter.replace_operation_with_values(ctx, op.get_operation(), vec![value]);
136 }
137 Builtin::UnitPosX | Builtin::CubePosX | Builtin::CubeCountX | Builtin::AbsolutePosX => {
138 read_dim3(ctx, &mut rewriter, op, value, 0);
139 }
140 Builtin::UnitPosY | Builtin::CubePosY | Builtin::CubeCountY | Builtin::AbsolutePosY => {
141 read_dim3(ctx, &mut rewriter, op, value, 1);
142 }
143 Builtin::UnitPosZ | Builtin::CubePosZ | Builtin::CubeCountZ | Builtin::AbsolutePosZ => {
144 read_dim3(ctx, &mut rewriter, op, value, 2);
145 }
146 other => unreachable!("{other:?} should be lowered"),
147 }
148 }
149}
150
151fn read_dim3(
152 ctx: &mut Context,
153 rewriter: &mut impl Rewriter,
154 op: ReadBuiltinOp,
155 value: Value,
156 dim: usize,
157) {
158 let u32 = IntegerType::get(ctx, 32, Signedness::Unsigned).to_handle();
159 let new_op = ReadDim3BuiltinOp::new(ctx, u32, value, dim);
160 rewriter.append_op(ctx, &new_op);
161 rewriter.replace_operation(ctx, op.get_operation(), new_op.get_operation());
162}
163
164fn built_in_attr(builtin: Builtin) -> BuiltInAttr {
165 match builtin {
166 Builtin::UnitPos => BuiltInAttr::ThreadIndexInThreadgroup,
167 Builtin::UnitPosX | Builtin::UnitPosY | Builtin::UnitPosZ => {
168 BuiltInAttr::ThreadPositionInThreadgroup
169 }
170 Builtin::CubePosX | Builtin::CubePosY | Builtin::CubePosZ => {
171 BuiltInAttr::ThreadgroupPositionInGrid
172 }
173 Builtin::CubeCountX | Builtin::CubeCountY | Builtin::CubeCountZ => {
174 BuiltInAttr::ThreadgroupsPerGrid
175 }
176 Builtin::PlaneDim => BuiltInAttr::ThreadsPerSIMDgroup,
177 Builtin::PlanePos => BuiltInAttr::SIMDgroupIndexInThreadgroup,
178 Builtin::UnitPosPlane => BuiltInAttr::ThreadIndexInSIMDgroup,
179 Builtin::AbsolutePosX | Builtin::AbsolutePosY | Builtin::AbsolutePosZ => {
180 BuiltInAttr::ThreadPositionInGrid
181 }
182 other => unreachable!("{other:?} should be lowered"),
183 }
184}