Skip to main content

cubecl_ir/
scope.rs

1use alloc::collections::BTreeMap;
2use alloc::{borrow::Cow, rc::Rc, string::String, string::ToString, vec::Vec};
3use core::{
4    any::TypeId,
5    cell::{Ref, RefCell, RefMut},
6    fmt::Display,
7};
8use derive_more::{Eq, PartialEq};
9use enumset::EnumSet;
10use hashbrown::{HashMap, HashSet};
11use itertools::Itertools;
12
13use crate::{
14    AddressSpace, AggregateExtractOperands, CubeFnSource, DeviceProperties, FastMath, Function,
15    OpaqueType, Operation, OperationReflect, Processor, SourceLoc, StorageType, TargetProperties,
16    TypeHash, arena::DropBump,
17};
18
19use super::{Allocator, Id, Instruction, Type, Value, processing::ScopeProcessing};
20
21pub type TypeMap = HashMap<TypeId, StorageType>;
22pub type SizeMap = HashMap<TypeId, usize>;
23
24/// The scope is the main [`crate::Operation`] and [`crate::Value`] container that simplify
25/// the process of reading inputs, creating local variables and adding new operations.
26///
27/// Notes:
28///
29/// This type isn't responsible for creating shader bindings and figuring out which
30/// variable can be written to.
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[derive(Debug, Clone, PartialEq, Eq, TypeHash)]
33#[allow(missing_docs)]
34pub struct Scope {
35    validation_errors: ValidationErrors,
36    pub depth: u8,
37    pub instructions: RefCell<Vec<Instruction>>,
38    pub return_value: Option<Value>,
39    pub locals: RefCell<Vec<Value>>,
40    pub debug: DebugInfo,
41
42    #[cfg_attr(feature = "serde", serde(skip))]
43    pub global_state: GlobalState,
44}
45
46pub type GlobalState = Rc<RefCell<GlobalStateInner>>;
47
48#[derive(Debug, PartialEq, Eq, TypeHash, Default)]
49pub struct GlobalStateInner {
50    #[partial_eq(skip)]
51    #[eq(skip)]
52    pub reference_arena: DropBump,
53    pub allocator: Allocator,
54
55    pub global_args: Vec<Value>,
56    pub functions: BTreeMap<Id, Function>,
57    pub typemap: TypeMap,
58    pub sizemap: SizeMap,
59    pub modes: InstructionModes,
60    pub target_properties: TargetProperties,
61    pub device_properties: Option<Rc<DeviceProperties>>,
62}
63
64impl GlobalStateInner {
65    pub fn clone_deep(&self) -> Self {
66        Self {
67            reference_arena: DropBump::new(),
68            allocator: self.allocator.clone_deep(),
69            global_args: self.global_args.clone(),
70            functions: self.functions.clone(),
71            typemap: self.typemap.clone(),
72            sizemap: self.sizemap.clone(),
73            modes: self.modes,
74            target_properties: self.target_properties.clone(),
75            device_properties: self.device_properties.clone(),
76        }
77    }
78}
79
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81#[derive(Debug, Clone, PartialEq, Eq, TypeHash)]
82pub struct ValidationErrors {
83    errors: Rc<RefCell<Vec<String>>>,
84}
85
86/// Debug related fields, most of these are global
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[derive(Debug, Clone, PartialEq, Eq, TypeHash)]
89pub struct DebugInfo {
90    pub enabled: bool,
91    pub sources: Rc<RefCell<HashSet<CubeFnSource>>>,
92    pub value_names: Rc<RefCell<HashMap<Value, Cow<'static, str>>>>,
93    pub source_loc: RefCell<Option<SourceLoc>>,
94    pub entry_loc: RefCell<Option<SourceLoc>>,
95}
96
97/// Modes set and reset during expansion
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, TypeHash)]
100pub struct InstructionModes {
101    pub fp_math_mode: EnumSet<FastMath>,
102}
103
104impl core::hash::Hash for Scope {
105    fn hash<H: core::hash::Hasher>(&self, ra_expand_state: &mut H) {
106        self.depth.hash(ra_expand_state);
107        self.instructions.borrow().hash(ra_expand_state);
108        self.locals.borrow().hash(ra_expand_state);
109    }
110}
111
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TypeHash)]
114#[allow(missing_docs)]
115pub enum ReadingStrategy {
116    /// Each element will be read in a way to be compatible with the output layout.
117    OutputLayout,
118    /// Keep the current layout.
119    Plain,
120}
121
122impl Scope {
123    /// Set the device properties.
124    pub fn device_properties(&self, properties: &DeviceProperties) {
125        self.state_mut().device_properties = Some(Rc::new(properties.clone()));
126    }
127
128    pub fn state(&self) -> Ref<'_, GlobalStateInner> {
129        self.global_state.borrow()
130    }
131
132    pub fn state_mut(&self) -> RefMut<'_, GlobalStateInner> {
133        self.global_state.borrow_mut()
134    }
135
136    /// Create a scope that is at the root of a kernel definition.
137    ///
138    /// A local scope can be created with the [child](Self::child) method.
139    pub fn root(debug_enabled: bool) -> Self {
140        Self {
141            validation_errors: ValidationErrors {
142                errors: Rc::new(RefCell::new(Vec::new())),
143            },
144            depth: 0,
145            instructions: Default::default(),
146            return_value: None,
147            locals: Default::default(),
148            debug: DebugInfo {
149                enabled: debug_enabled,
150                sources: Default::default(),
151                value_names: Default::default(),
152                source_loc: Default::default(),
153                entry_loc: Default::default(),
154            },
155            global_state: Default::default(),
156        }
157    }
158
159    /// Use existing state.
160    pub fn with_global_state(mut self, global_state: GlobalState) -> Self {
161        self.global_state = global_state;
162        self
163    }
164
165    /// Create a new immutable value of type specified by `ty`.
166    pub fn create_value(&self, ty: Type) -> Value {
167        let id = self.new_local_index();
168        Value::new(id, ty)
169    }
170
171    /// Create a new mutable local variable of type specified by `value_ty`.
172    pub fn create_local_mut(&self, value_ty: impl Into<Type>) -> Value {
173        let value_ty = value_ty.into();
174        let ty = Type::Pointer(value_ty.intern(), AddressSpace::Local);
175        let out = self.create_value(ty);
176        self.register(Instruction::new(
177            Operation::DeclareVariable {
178                value_ty,
179                addr_space: AddressSpace::Local,
180                alignment: value_ty.align(),
181            },
182            out,
183        ));
184        out
185    }
186
187    /// Create a shared variable of the given item type.
188    pub fn create_shared(&self, value_ty: impl Into<Type>, alignment: Option<usize>) -> Value {
189        let value_ty = value_ty.into();
190        let ty = Type::Pointer(value_ty.intern(), AddressSpace::Shared);
191        let out = self.create_value(ty);
192        self.register(Instruction::new(
193            Operation::DeclareVariable {
194                value_ty,
195                addr_space: AddressSpace::Shared,
196                alignment: alignment.unwrap_or_else(|| value_ty.align()),
197            },
198            out,
199        ));
200        out
201    }
202
203    /// Create a new function.
204    pub fn create_function(&self, explicit_params: Vec<Value>, scope: Scope) -> Id {
205        let id = self.state().allocator.new_local_index();
206        self.state_mut().functions.insert(
207            id,
208            Function {
209                explicit_params,
210                scope,
211            },
212        );
213        id
214    }
215
216    /// Register an [`Instruction`] into the scope.
217    pub fn register<T: Into<Instruction>>(&self, instruction: T) {
218        let mut inst = instruction.into();
219        inst.operation.sanitize_args(self);
220        inst.source_loc = self.debug.source_loc.borrow().clone();
221        inst.modes = self.state().modes;
222        self.instructions.borrow_mut().push(inst)
223    }
224
225    /// Add a value to the global arena so we can create a kernel-wide reference to it.
226    /// The reference is the same as the type for simplicity, but is only valid for the duration of
227    /// the root scope. Ensure the reference lifetime is shortened to the lifetime of the underlying
228    /// variable being referenced.
229    pub fn create_kernel_ref<'a, T>(&self, value: T) -> &'a mut T
230    where
231        T: 'a,
232    {
233        let mut state = self.state_mut();
234        let reference = state.reference_arena.alloc(value);
235        unsafe { core::mem::transmute(reference) }
236    }
237
238    /// Resolve the element type of the given generic type.
239    pub fn resolve_type<T: 'static>(&self) -> Option<StorageType> {
240        let state = self.state();
241        let result = state.typemap.get(&TypeId::of::<T>());
242
243        result.cloned()
244    }
245
246    /// Resolve the comptime size of the given generic size.
247    pub fn resolve_size<T: 'static>(&self) -> Option<usize> {
248        let state = self.state();
249        let result = state.sizemap.get(&TypeId::of::<T>());
250
251        result.cloned()
252    }
253
254    /// Register the element type for the given generic type.
255    pub fn register_type<T: 'static>(&self, elem: StorageType) {
256        let mut state = self.state_mut();
257
258        state.typemap.insert(TypeId::of::<T>(), elem);
259    }
260
261    /// Register the comptime size for the given generic size.
262    pub fn register_size<T: 'static>(&self, size: usize) {
263        let mut state = self.state_mut();
264
265        state.sizemap.insert(TypeId::of::<T>(), size);
266    }
267
268    /// Create an empty child scope.
269    pub fn child(&self) -> Self {
270        Self {
271            validation_errors: self.validation_errors.clone(),
272            depth: self.depth + 1,
273            instructions: Default::default(),
274            return_value: None,
275            locals: Default::default(),
276            debug: self.debug.clone(),
277            global_state: self.global_state.clone(),
278        }
279    }
280
281    // Adds a validation error.
282    pub fn push_error(&self, msg: impl Into<String>) {
283        self.validation_errors.errors.borrow_mut().push(msg.into());
284    }
285
286    /// Returns all validation errors.
287    pub fn pop_errors(&self) -> Vec<String> {
288        self.validation_errors.errors.replace_with(|_| Vec::new())
289    }
290
291    /// Returns the operations to be declared and executed.
292    ///
293    /// Notes:
294    ///
295    /// New operations can be created within the same scope without having name
296    /// conflicts.
297    pub fn process<'a>(
298        &self,
299        processors: impl IntoIterator<Item = &'a dyn Processor>,
300    ) -> ScopeProcessing {
301        self.global_state.borrow_mut().reference_arena.reset();
302
303        let mut instructions = Vec::new();
304
305        for inst in self.instructions.borrow_mut().drain(..) {
306            instructions.push(inst);
307        }
308
309        let mut processing = ScopeProcessing {
310            instructions,
311            global_state: self.global_state.clone(),
312        };
313
314        for p in processors {
315            processing = p.transform(processing);
316        }
317
318        processing
319    }
320
321    pub fn new_local_index(&self) -> u32 {
322        self.state().allocator.new_local_index()
323    }
324
325    /// Obtain the index-th buffer
326    pub fn global(&self, id: Id, value_ty: Type) -> Value {
327        let ty_arr = Type::DynamicArray(value_ty.intern());
328        let ty = Type::Pointer(ty_arr.intern(), AddressSpace::Global(id));
329        let value = self.create_value(ty);
330        self.state_mut().global_args.insert(id as usize, value);
331        value
332    }
333
334    /// Obtain the index-th tensor map
335    pub fn tensor_map(&self, id: Id) -> Value {
336        let ty = Type::Opaque(OpaqueType::TensorMap);
337        let value = self.create_value(ty);
338        self.state_mut().global_args.insert(id as usize, value);
339        value
340    }
341
342    pub fn update_source(&self, source: CubeFnSource) {
343        if self.debug.enabled {
344            self.debug.sources.borrow_mut().insert(source.clone());
345            *self.debug.source_loc.borrow_mut() = Some(SourceLoc {
346                line: source.line,
347                column: source.column,
348                source,
349            });
350            if self.debug.entry_loc.borrow().is_none() {
351                *self.debug.entry_loc.borrow_mut() = self.debug.source_loc.borrow().clone();
352            }
353        }
354    }
355
356    pub fn register_all(&self, instructions: impl IntoIterator<Item = Instruction>) {
357        self.instructions.borrow_mut().extend(instructions);
358    }
359
360    pub fn take_instructions(&self) -> Vec<Instruction> {
361        core::mem::take(&mut *self.instructions.borrow_mut())
362    }
363
364    pub fn update_span(&self, line: u32, col: u32) {
365        if let Some(loc) = self.debug.source_loc.borrow_mut().as_mut() {
366            loc.line = line;
367            loc.column = col;
368        }
369    }
370
371    pub fn update_value_name(&self, value: Value, name: impl Into<Cow<'static, str>>) {
372        if self.debug.enabled {
373            self.debug
374                .value_names
375                .borrow_mut()
376                .insert(value, name.into());
377        }
378    }
379
380    pub fn extract_field(&self, aggregate: Value, ty: Type, field: usize) -> Value {
381        if !matches!(aggregate.ty, Type::Aggregate(..)) {
382            panic!(
383                "Tried extracting field from non-aggregate {aggregate}.\nCurrent state:\n{}",
384                self.instructions.borrow().iter().join("\n")
385            )
386        }
387        let out = self.create_value(ty);
388        self.register(Instruction::new(
389            Operation::ExtractAggregateField(AggregateExtractOperands { aggregate, field }),
390            out,
391        ));
392        out
393    }
394}
395
396impl Display for Scope {
397    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398        writeln!(f, "{{")?;
399        for instruction in self.instructions.borrow().iter() {
400            let instruction_str = instruction.to_string();
401            if !instruction_str.is_empty() {
402                writeln!(
403                    f,
404                    "{}{}",
405                    "    ".repeat(self.depth as usize + 1),
406                    instruction_str,
407                )?;
408            }
409        }
410        write!(f, "{}}}", "    ".repeat(self.depth as usize))?;
411        Ok(())
412    }
413}