1use crate::{
2 SpirvKernel,
3 debug::DebugInfo,
4 item::Item,
5 lookups::CompilerState,
6 target::{GLCompute, SpirvTarget},
7 transformers::{BitwiseTransform, ErfTransform, HypotTransform, RhypotTransform},
8};
9use cubecl_common::backtrace::BackTrace;
10use cubecl_core::{
11 Compiler, CubeDim, Info, Metadata, WgpuCompilationOptions,
12 ir::{self as core, ElemType, Id, InstructionModes, StorageType, UIntKind, features::EnumSet},
13 post_processing::{
14 checked_io::CheckedIoVisitor, disaggregate::DisaggregateVisitor,
15 saturating::SaturatingArithmeticProcessor, unroll::UnrollVisitor,
16 },
17 prelude::{FastMath, KernelDefinition, Visibility},
18 server::ExecutionMode,
19};
20use cubecl_opt::{
21 BasicBlock, Function, NodeIndex, Optimizer, OptimizerBuilder, SharedLiveness, Uniformity,
22};
23use cubecl_runtime::{
24 compiler::CompilationError,
25 config::{CubeClRuntimeConfig, RuntimeConfig, compilation::CompilationLogLevel},
26};
27use rspirv::{
28 binary::Assemble,
29 dr::{Builder, InsertPoint, Instruction, Module, Operand},
30 spirv::{BuiltIn, Capability, Decoration, FPFastMathMode, Op, StorageClass, Word},
31};
32use std::{
33 collections::HashSet,
34 fmt::Debug,
35 mem::take,
36 ops::{Deref, DerefMut},
37 rc::Rc,
38 sync::Arc,
39};
40
41pub struct SpirvCompiler<Target: SpirvTarget = GLCompute> {
42 pub target: Target,
43 pub(crate) builder: Builder,
44
45 pub cube_dim: CubeDim,
46 pub mode: ExecutionMode,
47 pub addr_type: StorageType,
48 pub debug_symbols: bool,
49 global_invocation_id: Word,
50 num_workgroups: Word,
51 pub setup_block: usize,
52 pub opt: Rc<Optimizer>,
53 pub uniformity: Rc<Uniformity>,
54 pub shared_liveness: Rc<SharedLiveness>,
55 pub current_func: Option<Id>,
56 pub current_block: Option<NodeIndex>,
57
58 pub capabilities: HashSet<Capability>,
59 pub state: CompilerState,
60 pub ext_meta_pos: Vec<u32>,
61 pub info: Info,
62 pub debug_info: Option<DebugInfo>,
63 pub compilation_options: WgpuCompilationOptions,
64}
65
66unsafe impl<T: SpirvTarget> Send for SpirvCompiler<T> {}
67unsafe impl<T: SpirvTarget> Sync for SpirvCompiler<T> {}
68
69impl<T: SpirvTarget> Clone for SpirvCompiler<T> {
70 fn clone(&self) -> Self {
71 Self {
72 target: self.target.clone(),
73 builder: Builder::new_from_module(self.module_ref().clone()),
74 cube_dim: self.cube_dim,
75 mode: self.mode,
76 addr_type: self.addr_type,
77 global_invocation_id: self.global_invocation_id,
78 num_workgroups: self.num_workgroups,
79 setup_block: self.setup_block,
80 opt: self.opt.clone(),
81 uniformity: self.uniformity.clone(),
82 shared_liveness: self.shared_liveness.clone(),
83 current_func: self.current_func,
84 current_block: self.current_block,
85 capabilities: self.capabilities.clone(),
86 state: self.state.clone(),
87 debug_symbols: self.debug_symbols,
88 info: self.info.clone(),
89 debug_info: self.debug_info.clone(),
90 ext_meta_pos: self.ext_meta_pos.clone(),
91 compilation_options: self.compilation_options,
92 }
93 }
94}
95
96fn debug_symbols_activated() -> bool {
97 matches!(
98 CubeClRuntimeConfig::get().compilation.logger.level,
99 CompilationLogLevel::Full
100 )
101}
102
103impl<T: SpirvTarget> Default for SpirvCompiler<T> {
104 fn default() -> Self {
105 Self {
106 target: Default::default(),
107 builder: Builder::new(),
108 cube_dim: CubeDim::new_single(),
109 mode: Default::default(),
110 addr_type: ElemType::UInt(UIntKind::U32).into(),
111 global_invocation_id: Default::default(),
112 num_workgroups: Default::default(),
113 capabilities: Default::default(),
114 state: Default::default(),
115 setup_block: Default::default(),
116 opt: Default::default(),
117 uniformity: Default::default(),
118 shared_liveness: Default::default(),
119 current_func: Default::default(),
120 current_block: Default::default(),
121 debug_symbols: debug_symbols_activated(),
122 info: Default::default(),
123 debug_info: Default::default(),
124 ext_meta_pos: Default::default(),
125 compilation_options: Default::default(),
126 }
127 }
128}
129
130impl<T: SpirvTarget> Deref for SpirvCompiler<T> {
131 type Target = Builder;
132
133 fn deref(&self) -> &Self::Target {
134 &self.builder
135 }
136}
137
138impl<T: SpirvTarget> DerefMut for SpirvCompiler<T> {
139 fn deref_mut(&mut self) -> &mut Self::Target {
140 &mut self.builder
141 }
142}
143
144impl<T: SpirvTarget> Compiler for SpirvCompiler<T> {
145 type Representation = SpirvKernel;
146 type CompilationOptions = WgpuCompilationOptions;
147
148 fn compile(
149 &mut self,
150 value: KernelDefinition,
151 compilation_options: &Self::CompilationOptions,
152 mode: ExecutionMode,
153 addr_type: StorageType,
154 ) -> Result<Self::Representation, CompilationError> {
155 let errors = value.body.pop_errors();
156 if !errors.is_empty() {
157 let mut reason = "Can't compile spirv kernel".to_string();
158 for error in errors {
159 reason += error.as_str();
160 reason += "\n";
161 }
162
163 return Err(CompilationError::Validation {
164 reason,
165 backtrace: BackTrace::capture(),
166 });
167 }
168
169 let bindings = value.buffers.clone();
170 let mut ext_meta_pos = Vec::new();
171 let mut num_ext = 0;
172
173 let mut all_meta: Vec<_> = value
174 .buffers
175 .iter()
176 .chain(value.tensor_maps.iter())
177 .map(|buf| (buf.id, buf.has_extended_meta))
178 .collect();
179 all_meta.sort_by_key(|(id, _)| *id);
180
181 let num_meta = all_meta.len();
182
183 for (_, has_extended_meta) in all_meta.iter() {
184 ext_meta_pos.push(num_ext);
185 if *has_extended_meta {
186 num_ext += 1;
187 }
188 }
189
190 let metadata = Metadata::new(num_meta as u32, num_ext);
191
192 self.cube_dim = value.cube_dim;
193 self.mode = mode;
194 self.addr_type = addr_type;
195 self.info = Info::new(&value.scalars, metadata, addr_type);
196 self.compilation_options = *compilation_options;
197 self.ext_meta_pos = ext_meta_pos;
198
199 let (module, optimizer, shared_size) = self.compile_kernel(value);
200 let info_visibility = match T::info_storage_class(self) {
201 StorageClass::Uniform => Visibility::Uniform,
202 _ => Visibility::Read,
203 };
204 let immediate_size = match T::params_storage_class(self, bindings.len()) {
205 StorageClass::PushConstant => Some((bindings.len() + 1) * size_of::<u64>()),
206 _ => None,
207 };
208
209 let visibility = self.opt.global_state.buffer_visibility.borrow();
210 let bindings = visibility
211 .iter()
212 .map(|vis| match vis.writable {
213 true => Visibility::ReadWrite,
214 false => Visibility::Read,
215 })
216 .collect();
217
218 Ok(SpirvKernel {
219 assembled_module: module.assemble(),
220 module: Some(Arc::new(module)),
221 optimizer: Some(Arc::new(optimizer)),
222 bindings,
223 shared_size,
224 immediate_size,
225 info_visibility,
226 })
227 }
228
229 fn elem_size(&self, elem: core::ElemType) -> usize {
230 elem.size()
231 }
232
233 fn extension(&self) -> &'static str {
234 "spv"
235 }
236}
237
238impl<Target: SpirvTarget> Debug for SpirvCompiler<Target> {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(f, "spirv<{:?}>", self.target)
241 }
242}
243
244impl<Target: SpirvTarget> SpirvCompiler<Target> {
245 pub fn compile_kernel(&mut self, mut kernel: KernelDefinition) -> (Module, Optimizer, usize) {
246 let options = kernel.options.clone();
247
248 self.debug_symbols = debug_symbols_activated() || options.debug_symbols;
249
250 let version = self.compilation_options.vulkan.max_spirv_version;
251 self.set_version(version.0, version.1);
252
253 let mut target = self.target.clone();
254
255 let mut opt = OptimizerBuilder::default()
256 .with_transformer(ErfTransform)
257 .with_transformer(BitwiseTransform::new(
258 self.compilation_options.vulkan.supports_arbitrary_bitwise,
259 ))
260 .with_transformer(HypotTransform)
261 .with_transformer(RhypotTransform)
262 .with_visitor(CheckedIoVisitor::new(
263 self.mode,
264 kernel.options.kernel_name.clone(),
265 ))
266 .with_visitor(DisaggregateVisitor::default())
267 .with_visitor(UnrollVisitor::new(
268 self.compilation_options.vulkan.max_vector_size,
269 ))
270 .with_processor(SaturatingArithmeticProcessor::new(true))
271 .optimize(kernel.body.clone(), kernel.cube_dim);
272
273 self.uniformity = opt.main.analysis::<Uniformity>(&opt.global_state);
274 self.shared_liveness = opt.main.analysis::<SharedLiveness>(&opt.global_state);
275 self.opt = Rc::new(opt);
276
277 self.init_debug();
278 self.init_base_state(&mut kernel);
279
280 let cube_dims = vec![kernel.cube_dim.x, kernel.cube_dim.y, kernel.cube_dim.z];
281
282 target.set_kernel_name(options.kernel_name.clone());
283
284 let opt = self.opt.clone();
285 for (id, func) in opt.global_state.extra_functions.iter() {
286 let def = self.declare_function(func);
287 self.current_func = Some(*id);
288
289 let blocks = func.breadth_first_dominators();
290 for block in blocks {
291 self.compile_block(block);
292 }
293 self.end_function_and_reset_lookups();
294
295 self.state.extra_funcs.insert(*id, def);
296 self.current_func = None;
297 }
298
299 self.init_kernel_state(kernel);
300
301 let (main, debug_setup) = self.declare_main(&options.kernel_name);
302
303 let setup = self.id();
304 self.debug_name(setup, "setup");
305
306 let entry = self.opt.entry();
307 let body = self.label(entry);
308
309 let setup_block = self.setup(setup, debug_setup);
310 self.setup_block = setup_block;
311
312 let shared_size = self.declare_shared_memories();
313
314 let blocks = opt.main.breadth_first_dominators();
315 for block in blocks {
316 self.compile_block(block);
317 }
318
319 self.select_block(Some(setup_block)).unwrap();
320 self.branch(body).unwrap();
321
322 self.end_function().unwrap();
324
325 let builtins = self
326 .state
327 .used_builtins
328 .clone()
329 .into_iter()
330 .map(|(builtin, (id, item))| {
331 let ty = Item::Pointer(StorageClass::Input, Box::new(item)).id(self);
332 self.variable(ty, Some(id), StorageClass::Input, None);
333 self.decorate(id, Decoration::BuiltIn, vec![builtin.into()]);
334 id
335 })
336 .collect::<Vec<_>>();
337
338 target.set_modes(self, main, builtins, cube_dims);
339
340 let module = take(&mut self.builder).module();
341 (module, self.opt.as_ref().clone(), shared_size)
342 }
343
344 fn setup(&mut self, label: Word, debug_setup: impl Fn(&mut Self)) -> usize {
345 self.begin_block(Some(label)).unwrap();
346
347 Target::load_params(self);
348
349 debug_setup(self);
350
351 let setup_block = self.selected_block().unwrap();
352 self.select_block(None).unwrap();
353 setup_block
354 }
355
356 #[track_caller]
357 pub fn current_block(&self) -> BasicBlock {
358 self.current_func()
359 .block(self.current_block.unwrap())
360 .clone()
361 }
362
363 pub fn current_func(&self) -> &Function {
364 self.current_func
365 .map(|func| &self.opt.global_state.extra_functions[&func])
366 .unwrap_or(&self.opt.main)
367 }
368
369 pub fn builtin(&mut self, builtin: BuiltIn, item: Item) -> Word {
370 if let Some(existing) = self.state.used_builtins.get(&builtin) {
371 existing.0
372 } else {
373 let id = self.id();
374 self.state.used_builtins.insert(builtin, (id, item));
375 id
376 }
377 }
378
379 pub fn compile_block(&mut self, block: NodeIndex) {
380 self.current_block = Some(block);
381
382 let label = self.label(block);
383 self.begin_block(Some(label)).unwrap();
384 let block_id = self.selected_block().unwrap();
385
386 self.debug_start_block();
387
388 let operations = self.current_block().ops.borrow().clone();
389 for (_, operation) in operations {
390 self.compile_operation(operation);
391 }
392
393 let control_flow = self.current_block().control_flow.borrow().clone();
394 self.compile_control_flow(control_flow);
395
396 let current = self.selected_block();
397 self.select_block(Some(block_id)).unwrap();
398 let phi = { self.current_func().block(block).phi_nodes.borrow().clone() };
399 for phi in phi {
400 let out = self.compile_value(phi.out);
401 let ty = out.item().id(self);
402 let out_id = self.write_id(&out);
403 let entries: Vec<_> = phi
404 .entries
405 .into_iter()
406 .map(|it| {
407 let label = self.end_label(it.block);
408 let value = self.compile_value(it.value);
409 let value = self.read(&value);
410 (value, label)
411 })
412 .collect();
413 self.insert_phi(InsertPoint::Begin, ty, Some(out_id), entries)
414 .unwrap();
415 }
416 self.select_block(current).unwrap();
417 }
418
419 pub fn declare_function_variable(&mut self, ty: Word, init: Option<Word>) -> Word {
421 let setup = self.setup_block;
422 let id = self.id();
423 let mut val = Instruction::new(
424 Op::Variable,
425 Some(ty),
426 Some(id),
427 vec![Operand::StorageClass(StorageClass::Function)],
428 );
429 if let Some(init) = init {
430 val.operands.push(Operand::IdRef(init));
431 }
432 let current_block = self.selected_block();
433 self.select_block(Some(setup)).unwrap();
434 self.insert_into_block(InsertPoint::Begin, val).unwrap();
435 self.select_block(current_block).unwrap();
436 id
437 }
438
439 fn declare_shared_memories(&mut self) -> usize {
440 if self.compilation_options.vulkan.supports_explicit_smem {
441 self.declare_shared_memories_explicit() as usize
442 } else {
443 self.declare_shared_memories_implicit() as usize
444 }
445 }
446
447 fn declare_shared_memories_explicit(&mut self) -> u32 {
452 let mut shared_size = 0;
453
454 let shared = self.state.shared.clone();
455 if shared.is_empty() {
456 return shared_size;
457 }
458
459 self.capabilities
460 .insert(Capability::WorkgroupMemoryExplicitLayoutKHR);
461
462 for (index, memory) in shared {
463 let memory_size = memory.item.size();
464 let value_size = memory.item.value_type().size();
465 shared_size = shared_size.max(memory.offset + memory_size);
466
467 match value_size {
470 1 => {
471 self.capabilities
472 .insert(Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR);
473 }
474 2 => {
475 self.capabilities
476 .insert(Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
477 }
478 _ => {}
479 }
480
481 let item_id = memory.item.id(self);
482 let block_id = self.id();
483
484 if let Item::Array(_, _) = memory.item {
485 self.decorate(item_id, Decoration::ArrayStride, [value_size.into()]);
486 }
487
488 self.type_struct_id(Some(block_id), [item_id]);
489
490 self.decorate(block_id, Decoration::Block, []);
491 self.member_decorate(block_id, 0, Decoration::Offset, [memory.offset.into()]);
492
493 let block_ptr_ty = self.type_pointer(None, StorageClass::Workgroup, block_id);
494 let ptr_ty =
495 self.type_pointer(Some(memory.ptr_ty_id), StorageClass::Workgroup, item_id);
496
497 self.debug_shared(memory.id, index);
498 self.variable(
499 block_ptr_ty,
500 Some(memory.val_id),
501 StorageClass::Workgroup,
502 None,
503 );
504 self.decorate(memory.val_id, Decoration::Aliased, []);
505
506 self.insert_in_setup(|b| {
507 let zero = b.const_u32(0);
508 b.in_bounds_access_chain(ptr_ty, Some(memory.id), memory.val_id, [zero])
509 .unwrap()
510 });
511 }
512
513 shared_size
514 }
515
516 fn declare_shared_memories_implicit(&mut self) -> u32 {
517 let mut shared_size = 0;
518
519 let shared = self.state.shared.clone();
520 for (index, memory) in shared {
521 shared_size += memory.item.size();
522
523 let ty_id = memory.item.id(self);
524 let ptr_ty = self.type_pointer(Some(memory.ptr_ty_id), StorageClass::Workgroup, ty_id);
525
526 self.debug_shared(memory.id, index);
527 self.variable(ptr_ty, Some(memory.id), StorageClass::Workgroup, None);
528 }
529 shared_size
530 }
531
532 pub fn declare_math_mode(&mut self, modes: InstructionModes, out_id: Word) {
533 if !self.compilation_options.vulkan.supports_fp_fast_math || modes.fp_math_mode.is_empty() {
534 return;
535 }
536 let mode = convert_math_mode(modes.fp_math_mode);
537 self.capabilities.insert(Capability::FloatControls2);
538 self.decorate(
539 out_id,
540 Decoration::FPFastMathMode,
541 [Operand::FPFastMathMode(mode)],
542 );
543 }
544
545 pub fn is_uniform_block(&self) -> bool {
546 self.uniformity
547 .is_block_uniform(self.current_block.unwrap())
548 }
549}
550
551pub(crate) fn convert_math_mode(math_mode: EnumSet<FastMath>) -> FPFastMathMode {
552 let mut flags = FPFastMathMode::NONE;
553
554 for mode in math_mode.iter() {
555 match mode {
556 FastMath::NotNaN => flags |= FPFastMathMode::NOT_NAN,
557 FastMath::NotInf => flags |= FPFastMathMode::NOT_INF,
558 FastMath::UnsignedZero => flags |= FPFastMathMode::NSZ,
559 FastMath::AllowReciprocal => flags |= FPFastMathMode::ALLOW_RECIP,
560 FastMath::AllowContraction => flags |= FPFastMathMode::ALLOW_CONTRACT,
561 FastMath::AllowReassociation => flags |= FPFastMathMode::ALLOW_REASSOC,
562 FastMath::AllowTransform => {
563 flags |= FPFastMathMode::ALLOW_CONTRACT
564 | FPFastMathMode::ALLOW_REASSOC
565 | FPFastMathMode::ALLOW_TRANSFORM
566 }
567 _ => {}
568 }
569 }
570
571 flags
572}