cubecl_wgpu/compiler/wgsl/
compiler.rs1use super::shader::ComputeShader;
2use crate::compiler::wgsl::{
3 self, EnableFeaturesPass,
4 builtin::{LowerBuiltinsPass, WorkgroupLayout},
5 lower::LowerOpsWgslPass,
6 metadata::declare_info,
7 rewrite_args, shared_memory_size, types,
8};
9use cubecl_server::kernel::BufferIOAttr;
10
11use cubecl_core::{
12 WgpuCompilationOptions,
13 codegen::KernelDump,
14 post_processing::{
15 checked_io::{CheckedIo, CheckedIoPass},
16 minifloat::{
17 Fp8Container, LowerMinifloatCast, LowerMinifloatCastPass, LowerMinifloatCompare,
18 LowerMinifloatComparePass,
19 },
20 saturating::LowerSaturatingArithmeticPass,
21 unroll::UnrollPass,
22 },
23};
24use cubecl_environment::backtrace::BackTrace;
25use cubecl_ir::{
26 ContextExt,
27 features::EnumSet,
28 pliron::{
29 builtin::ops::{FuncOp, ModuleOp},
30 operation::verify_operation,
31 opts::{dce::DCEPass, mem2reg::Mem2RegPass},
32 printable::Printable,
33 },
34 prelude::{AnalysisManager, NestedOpsPass, Op, OpPass, Pass, Passes},
35 rewrite::{CanonicalizePass, LowerOpsPass, SimplifyOpsPass},
36 settings::Dim3,
37};
38use cubecl_opt::passes::{
39 annotate_buffer_visibility::AnnotateGlobalVisibilityPass, inst_combine::InstCombinePass,
40 sccp::SCCPPass, simple_cse::SimpleCSEPass, sroa::SROAPass,
41};
42use cubecl_server::compiler::CompilationError;
43use cubecl_server::kernel;
44
45const MAX_VECTOR_SIZE: usize = 4;
46
47#[derive(Clone, Copy)]
48pub struct KernelInfo {
49 pub cube_dim: Dim3,
50 pub layout: WorkgroupLayout,
52}
53
54#[derive(Clone, Default)]
56pub struct WgslCompiler;
57
58impl core::fmt::Debug for WgslCompiler {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.write_str("WgslCompiler")
61 }
62}
63
64impl cubecl_core::Compiler for WgslCompiler {
65 type Representation = ComputeShader;
66 type CompilationOptions = WgpuCompilationOptions;
67
68 fn buffer_io(repr: &Self::Representation) -> Option<Vec<BufferIOAttr>> {
69 Some(repr.io.clone())
70 }
71
72 fn compile(
73 &mut self,
74 shader: kernel::KernelDefinition,
75 compilation_options: &Self::CompilationOptions,
76 ) -> Result<Self::Representation, CompilationError> {
77 self.compile_shader(shader, compilation_options)
78 }
79
80 fn extension(&self) -> &'static str {
81 "wgsl"
82 }
83
84 fn lang_tag(&self) -> &'static str {
85 "wgsl"
86 }
87}
88
89impl WgslCompiler {
90 fn compile_shader(
91 &mut self,
92 value: kernel::KernelDefinition,
93 compilation_options: &WgpuCompilationOptions,
94 ) -> Result<wgsl::ComputeShader, CompilationError> {
95 #[cfg(target_family = "wasm")]
96 crate::compiler::wgsl::ops::wasm_inventory_root();
97
98 let errors = value.body.pop_errors();
99 if !errors.is_empty() {
100 let mut reason = "Can't compile wgsl kernel".to_string();
101 for error in errors {
102 reason += error.as_str();
103 reason += "\n";
104 }
105
106 return Err(CompilationError::Validation {
107 reason,
108 backtrace: BackTrace::capture(),
109 });
110 }
111
112 let dump = KernelDump::new(&value.settings.kernel_name);
113
114 let module = value.body.state().module;
115 let entry_func = value.body.state().entry_func;
116 let module_op = module.get_operation();
117 let mut ctx = value.body.into_context().expect("Should be unique");
118 ctx.set_aux_ty(value.info);
119 let cube_dim = value.settings.cube_dim;
120 let layout = WorkgroupLayout::new(&ctx, module_op, &cube_dim);
121 ctx.set_aux_ty(KernelInfo { cube_dim, layout });
122 ctx.set_aux_ty(*compilation_options);
123
124 dump.write("initial.plir", || module_op.disp(&ctx).to_string());
125
126 verify_operation(module_op, &ctx)?;
127 types::check_fp8_lanes(&ctx, module_op)?;
128
129 let mut analyses = AnalysisManager::default();
130 analyses.set_config(dump.pass_config());
131
132 let mut passes = OpPass::<ModuleOp, Passes>::default();
133 let mut func_passes = OpPass::<FuncOp, Passes>::default();
134
135 func_passes.add_pass(LowerOpsPass::default());
136 func_passes.add_pass(SROAPass);
137 func_passes.add_pass(CheckedIoPass::new(CheckedIo::new(
138 value.settings.execution_mode,
139 value.settings.kernel_name.clone(),
140 )));
141 func_passes.add_pass(UnrollPass::new(MAX_VECTOR_SIZE));
142 func_passes.add_pass(LowerMinifloatCastPass::new(LowerMinifloatCast::new(
144 EnumSet::empty(),
145 Fp8Container::Words,
146 )));
147 func_passes.add_pass(LowerMinifloatComparePass::new(LowerMinifloatCompare::new(
148 Fp8Container::Words,
149 )));
150
151 func_passes.add_pass(LowerOpsWgslPass::default());
152 func_passes.add_pass(LowerSaturatingArithmeticPass::default());
153 func_passes.add_pass(LowerBuiltinsPass);
154
155 func_passes.add_pass(SCCPPass);
156 func_passes.add_pass(InstCombinePass::default());
157 func_passes.add_pass(SimpleCSEPass::without_memory());
158 func_passes.add_pass(SimplifyOpsPass::default());
159 func_passes.add_pass(DCEPass);
160 func_passes.add_pass(SROAPass);
161
162 func_passes.add_pass(Mem2RegPass);
164
165 func_passes.add_pass(SROAPass);
166 func_passes.add_pass(SCCPPass);
167 func_passes.add_pass(SimpleCSEPass::with_memory());
168 func_passes.add_pass(SimplifyOpsPass::default());
169 func_passes.add_pass(CanonicalizePass::default());
170 func_passes.add_pass(DCEPass);
171
172 passes.add_pass(NestedOpsPass::new(func_passes));
173 passes.add_pass(AnnotateGlobalVisibilityPass);
174 passes.add_pass(EnableFeaturesPass);
175
176 passes.run(module_op, &mut ctx, &mut analyses)?;
177
178 let io = cubecl_core::ir::attributes::buffer_io_by_position(&ctx, entry_func)
181 .into_iter()
182 .collect();
183 let buffers = rewrite_args(&mut ctx, entry_func);
184 declare_info(&mut ctx, entry_func, buffers.len());
185 let shared_memory_size = shared_memory_size(&ctx, module_op);
186
187 verify_operation(module.get_operation(), &ctx)?;
188
189 Ok(ComputeShader {
190 buffers,
191 io,
192 shared_memory_size,
193 ctx,
194 })
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use cubecl_core::{self as cubecl, Compiler, prelude::*};
201 use cubecl_ir::{metadata::Info, settings::Dim3};
202
203 use cubecl_server::kernel::KernelDefinition;
204
205 use super::WgslCompiler;
206
207 #[cube]
209 fn axes() {
210 let _ = UNIT_POS_X + UNIT_POS_Y + UNIT_POS_Z;
211 }
212
213 #[cube]
215 fn axes_and_plane() {
216 let _ = UNIT_POS_X + UNIT_POS_Y + UNIT_POS_Z + PLANE_POS;
217 }
218
219 #[cube]
221 fn axes_and_plane_sum() {
222 let _ = plane_sum(UNIT_POS_X + UNIT_POS_Y + UNIT_POS_Z);
223 }
224
225 fn workgroup_size(cube_dim: Dim3, body: fn(&Scope)) -> String {
228 let settings = KernelSettings::new(cube_dim, ExecutionMode::Checked, AddressType::U32);
229 let scope = Scope::root(settings.clone());
230 body(&scope);
231 let definition = KernelDefinition {
232 body: scope,
233 settings,
234 info: Info::default(),
235 };
236 let shader = WgslCompiler
237 .compile(definition, &Default::default())
238 .expect("the kernel compiles");
239 shader
240 .to_string()
241 .lines()
242 .find(|line| line.starts_with("@compute"))
243 .expect("an entry point")
244 .to_string()
245 }
246
247 #[test]
248 fn a_cube_spanning_y_without_planes_keeps_its_shape() {
249 assert_eq!(
250 workgroup_size(Dim3::new_3d(8, 4, 2), axes::expand),
251 "@compute @workgroup_size(8, 4, 2)"
252 );
253 }
254
255 #[test]
256 fn a_cube_spanning_y_with_planes_launches_flat() {
257 assert_eq!(
258 workgroup_size(Dim3::new_3d(8, 4, 2), axes_and_plane::expand),
259 "@compute @workgroup_size(64, 1, 1)"
260 );
261 }
262
263 #[test]
264 fn a_cube_spanning_y_with_plane_ops_but_no_plane_ids_keeps_its_shape() {
265 assert_eq!(
266 workgroup_size(Dim3::new_3d(8, 4, 2), axes_and_plane_sum::expand),
267 "@compute @workgroup_size(8, 4, 2)"
268 );
269 }
270
271 #[test]
272 fn a_cube_along_x_with_planes_keeps_its_shape() {
273 assert_eq!(
274 workgroup_size(Dim3::new_1d(64), axes_and_plane::expand),
275 "@compute @workgroup_size(64, 1, 1)"
276 );
277 }
278}