Skip to main content

cubecl_ir/
scope.rs

1use crate::{
2    EnumSet,
3    dialect::memory::StoreOp,
4    interfaces::control_flow::{SymbolOpInterface, SymbolVisibility},
5    rewrite::op_insertion_point_in_block,
6};
7use alloc::{boxed::Box, format, rc::Rc, string::String, sync::Arc, vec, vec::Vec};
8use core::{
9    any::{TypeId, type_name},
10    cell::{Ref, RefCell, RefMut, UnsafeCell},
11    fmt::{Debug, Display},
12    sync::atomic::Ordering,
13};
14use cubecl_common::format::type_name_sanitized;
15use cubecl_environment::{collections::HashMap, sync::Mutex};
16use derive_more::{Eq, PartialEq};
17use pliron::{
18    attribute::AttrObj,
19    basic_block::BasicBlock,
20    builtin::{
21        attributes::{TypeAttr, VecAttr},
22        given_names::set_operation_result_name,
23        op_interfaces::{OneResultInterface, SingleBlockRegionInterface},
24        ops::{ConstantOp, FuncOp, ModuleOp},
25        type_interfaces::FunctionTypeInterface,
26        types::{FunctionType, UnitType},
27    },
28    context::{AuxDataIndex, Context},
29    dict_key,
30    identifier::Identifier,
31    irbuild::inserter::{IRInserter, Inserter, OpInsertionPoint},
32    op::Op,
33    operation::Operation,
34    printable::Printable,
35    r#type::{TypeHandle, Typed, type_cast},
36    value::Value,
37};
38use portable_atomic::AtomicUsize;
39use spin::LazyLock;
40
41use crate::{
42    AddressSpace, AddressType, DeviceProperties, ElemType, FastMath, TargetProperties, TypeHash,
43    arena::DropBump,
44    attributes::{
45        ATTR_BUFFER_BINDING, ATTR_KEY_ARG_ATTRS, ATTR_TENSOR_MAP_BINDING, BoolAttr,
46        BufferBindingAttr, EntrypointAbiAttr, EntrypointInterface, FuncInterface, IndexAttr,
47    },
48    debug::{DebugState, LocationListener},
49    dialect::{
50        OperationPtrExt,
51        branch::{IfOp, ReturnOp, YieldOp},
52        memory::DeclareVariableOp,
53        vector::CompositeExtractOp,
54    },
55    interfaces::{ScalarType, TypedExt},
56    read_value,
57    settings::{DebugInfo, KernelSettings},
58    types::{PointerType, RuntimeArrayType, cuda::TensorMapType, scalar::BoolType},
59};
60
61pub type Types = HashMap<TypeId, ElemType>;
62pub type Sizes = HashMap<TypeId, usize>;
63
64pub type OpInserter = IRInserter<LocationListener>;
65
66/// SAFETY: This should be fine for parsing the AST, hopefully. There's just no good way to
67/// have both owned and borrowed contexts in scopes.
68#[derive(Clone)]
69enum CtxHandle {
70    Rc(Rc<UnsafeCell<Context>>),
71    Ref(*mut Context),
72}
73
74impl CtxHandle {
75    pub fn borrow(&self) -> &Context {
76        match self {
77            CtxHandle::Rc(cell) => unsafe { &*cell.get() },
78            CtxHandle::Ref(ptr) => unsafe { &**ptr },
79        }
80    }
81
82    #[allow(clippy::mut_from_ref)]
83    pub fn borrow_mut(&self) -> &mut Context {
84        match self {
85            CtxHandle::Rc(cell) => unsafe { &mut *cell.get() },
86            CtxHandle::Ref(ptr) => unsafe { &mut **ptr },
87        }
88    }
89}
90
91/// SAFETY: This should be fine for parsing the AST, hopefully. There's just no good way to
92/// have both owned and borrowed inserters in scopes.
93enum InserterHandle {
94    Owned(Box<UnsafeCell<dyn Inserter>>),
95    Ref(*mut dyn Inserter),
96}
97
98impl InserterHandle {
99    pub fn owned(inserter: impl Inserter + 'static) -> Self {
100        Self::Owned(Box::new(UnsafeCell::new(inserter)))
101    }
102
103    #[allow(clippy::mut_from_ref)]
104    pub fn borrow_mut(&self) -> &mut dyn Inserter {
105        match self {
106            InserterHandle::Owned(cell) => unsafe { &mut *cell.get() },
107            InserterHandle::Ref(ptr) => unsafe { &mut **ptr },
108        }
109    }
110}
111
112/// The scope represents the region currently being parsed, as well as the current insertion point.
113/// It is created from scratch for the initial codegen phase, or rebuilt from an existing scope and
114/// a rewriter for passes/conversions.
115///
116/// All state apart from the insertion point is shared. Global state like the type map and errors
117/// are stored in the context's auxiliary storage, so they can be reconstructed from the rewriter
118/// state.
119#[allow(missing_docs)]
120pub struct Scope {
121    ctx: CtxHandle,
122    inserter: InserterHandle,
123    expand_state: RefCell<ExpandState>,
124}
125
126#[derive(Clone, Copy, Default)]
127pub struct ExpandState {
128    pub may_terminate: bool,
129    pub may_return: bool,
130    pub may_break: bool,
131    pub may_continue: bool,
132    // Whether the kernel has *not* terminated. Inverted to save a not on the loop condition.
133    pub inv_terminate_flag: Option<Value>,
134    // Whether the current function has *not* returned. Inverted to save a not on the loop condition.
135    pub inv_return_flag: Option<Value>,
136    /// Whether the loop is *not* broken. Inverted to save a not on the loop condition.
137    pub inv_break_flag: Option<Value>,
138    /// Whether the loop is *not* continued. Inverted to save a not on the loop condition.
139    pub inv_continue_flag: Option<Value>,
140    /// The return value, if early return is used.
141    pub return_value: Option<Value>,
142}
143
144impl Debug for Scope {
145    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
146        f.debug_struct("Scope").finish()
147    }
148}
149
150#[track_caller]
151pub fn ident(name: impl Into<String>) -> Identifier {
152    Identifier::try_new(name.into()).unwrap()
153}
154
155pub struct GlobalState {
156    pub reference_arena: DropBump,
157    pub errors: Vec<String>,
158    pub warnings: Vec<String>,
159
160    pub module: ModuleOp,
161    pub module_inserter: OpInserter,
162    pub entry_func: FuncOp,
163    pub ident_unique_id: AtomicUsize,
164    pub typemap: Types,
165    pub sizemap: Sizes,
166    pub modes: InstructionModes,
167    pub target_properties: TargetProperties,
168    pub device_properties: Option<Arc<DeviceProperties>>,
169}
170
171unsafe impl Send for GlobalState {}
172
173impl GlobalState {
174    /// Register the element type for the given generic type.
175    pub fn register_type<T: 'static>(&mut self, elem: ElemType) {
176        self.typemap.insert(TypeId::of::<T>(), elem);
177    }
178}
179
180dict_key!(ADDRESS_TYPE_KEY, "kernel_address_type");
181
182static TY_IDENTS: LazyLock<Mutex<HashMap<TypeId, Identifier>>> = LazyLock::new(Default::default);
183
184fn ty_ident<T: 'static>() -> Identifier {
185    let mut idents = TY_IDENTS.lock();
186    let ident = idents
187        .entry(TypeId::of::<T>())
188        .or_insert_with(|| Identifier::try_from(type_name_sanitized::<T>()).unwrap());
189    ident.clone()
190}
191
192fn ty_key<T: 'static>(ctx: &Context) -> Option<AuxDataIndex> {
193    let mut idents = TY_IDENTS.lock();
194    let ident = idents
195        .entry(TypeId::of::<T>())
196        .or_insert_with(|| Identifier::try_from(type_name_sanitized::<T>()).unwrap());
197    ctx.aux_data_map.get(ident).copied()
198}
199
200pub trait ContextExt {
201    fn aux_ty<T: Send + 'static>(&self) -> &T;
202    fn try_aux_ty<T: Send + 'static>(&self) -> Option<&T>;
203    fn aux_ty_mut<T: Send + 'static>(&mut self) -> &mut T;
204    fn try_aux_ty_mut<T: Send + 'static>(&mut self) -> Option<&mut T>;
205    fn set_aux_ty<T: Send + 'static>(&mut self, value: T);
206    fn set_address_type(&mut self, addr: AddressType);
207    fn address_type(&self) -> AddressType;
208}
209
210impl ContextExt for Context {
211    #[track_caller]
212    fn aux_ty<T: Send + 'static>(&self) -> &T {
213        self.try_aux_ty()
214            .ok_or_else(|| format!("Key for {} should exist", type_name::<T>()))
215            .unwrap()
216    }
217
218    fn try_aux_ty<T: Send + 'static>(&self) -> Option<&T> {
219        self.aux_data[ty_key::<T>(self)?].downcast_ref()
220    }
221
222    #[track_caller]
223    fn aux_ty_mut<T: Send + 'static>(&mut self) -> &mut T {
224        self.try_aux_ty_mut()
225            .ok_or_else(|| format!("Key for {} should exist", type_name::<T>()))
226            .unwrap()
227    }
228
229    fn try_aux_ty_mut<T: Send + 'static>(&mut self) -> Option<&mut T> {
230        let key = ty_key::<T>(self)?;
231        self.aux_data[key].downcast_mut()
232    }
233
234    fn set_aux_ty<T: Send + 'static>(&mut self, value: T) {
235        if let Some(key) = ty_key::<T>(self) {
236            *self.aux_data.get_mut(key).unwrap() = Box::new(value);
237        } else {
238            let ident = ty_ident::<T>();
239            let key = self.aux_data.insert(Box::new(value));
240            self.aux_data_map.insert(ident, key);
241        }
242    }
243
244    fn set_address_type(&mut self, addr: AddressType) {
245        if let Some(key) = self.aux_data_map.get(&ADDRESS_TYPE_KEY).copied() {
246            *self.aux_data.get_mut(key).unwrap() = Box::new(addr);
247        } else {
248            let key = self.aux_data.insert(Box::new(addr));
249            self.aux_data_map.insert(ADDRESS_TYPE_KEY.clone(), key);
250        }
251    }
252
253    fn address_type(&self) -> AddressType {
254        let key = self.aux_data_map[&ADDRESS_TYPE_KEY];
255        *self.aux_data[key].downcast_ref::<AddressType>().unwrap()
256    }
257}
258
259pub trait FuncOpExt {
260    fn push_argument(&self, ctx: &Context, ty: TypeHandle) -> usize;
261    fn pop_argument(&self, ctx: &Context);
262    fn remove_argument(&self, ctx: &Context, arg_idx: usize);
263    fn return_type(&self, ctx: &Context) -> TypeHandle;
264}
265
266impl FuncOpExt for FuncOp {
267    fn push_argument(&self, ctx: &Context, ty: TypeHandle) -> usize {
268        let id = BasicBlock::push_argument(self.get_entry_block(ctx), ctx, ty);
269
270        let (mut arg_types, res_types) = {
271            let current_func_ty = self.get_type(ctx).deref(ctx);
272            let current_func_ty = current_func_ty.downcast_ref::<FunctionType>().unwrap();
273            (current_func_ty.arg_types(), current_func_ty.res_types())
274        };
275
276        arg_types.insert(id, ty);
277        let new_func_ty = FunctionType::get(ctx, arg_types, res_types).to_handle();
278        self.set_attr_builtin_func_type(ctx, new_func_ty.into());
279        id
280    }
281
282    fn pop_argument(&self, ctx: &Context) {
283        let last_idx = self.get_entry_block(ctx).deref(ctx).get_num_arguments() - 1;
284        BasicBlock::pop_argument(self.get_entry_block(ctx), ctx);
285
286        let (mut arg_types, res_types) = {
287            let current_func_ty = self.get_type(ctx).deref(ctx);
288            let current_func_ty = current_func_ty.downcast_ref::<FunctionType>().unwrap();
289            (current_func_ty.arg_types(), current_func_ty.res_types())
290        };
291
292        arg_types.pop();
293        let new_func_ty = FunctionType::get(ctx, arg_types, res_types).to_handle();
294        self.set_attr_builtin_func_type(ctx, new_func_ty.into());
295        let mut op = self.get_operation().deref_mut(ctx);
296        let arg_attrs = op.attributes.0.get_mut(&ATTR_KEY_ARG_ATTRS);
297        if let Some(arg_attrs) = arg_attrs.and_then(|attr| attr.downcast_mut::<VecAttr>()) {
298            arg_attrs.0.truncate(last_idx);
299        }
300    }
301
302    fn remove_argument(&self, ctx: &Context, arg_idx: usize) {
303        BasicBlock::remove_argument(self.get_entry_block(ctx), ctx, arg_idx);
304
305        let (mut arg_types, res_types) = {
306            let current_func_ty = self.get_type(ctx).deref(ctx);
307            let current_func_ty = current_func_ty.downcast_ref::<FunctionType>().unwrap();
308            (current_func_ty.arg_types(), current_func_ty.res_types())
309        };
310
311        arg_types.remove(arg_idx);
312        let new_func_ty = FunctionType::get(ctx, arg_types, res_types).to_handle();
313        self.set_attr_builtin_func_type(ctx, new_func_ty.into());
314
315        let mut op = self.get_operation().deref_mut(ctx);
316        let arg_attrs = op.attributes.0.get_mut(&ATTR_KEY_ARG_ATTRS);
317        if let Some(arg_attrs) = arg_attrs.and_then(|attr| attr.downcast_mut::<VecAttr>()) {
318            arg_attrs.0.remove(arg_idx);
319        }
320    }
321
322    fn return_type(&self, ctx: &Context) -> TypeHandle {
323        let ty = self.get_type(ctx).deref(ctx);
324        ty.downcast_ref::<FunctionType>().unwrap().res_types()[0]
325    }
326}
327
328fn new_context(settings: KernelSettings) -> Rc<UnsafeCell<Context>> {
329    let mut ctx = Context::default();
330    ctx.set_address_type(settings.address_type);
331
332    let module = ModuleOp::new(&mut ctx, ident("kernel"));
333    let module_block = module.get_body(&ctx, 0);
334    let mut module_inserter = OpInserter::new_at_block_end(module_block);
335
336    // Start out empty and fill in once args register themselves
337    let entry_func_ty = FunctionType::get(&ctx, vec![], vec![UnitType::get(&ctx).into()]);
338    let entry_name = Identifier::try_new(settings.kernel_name).unwrap_or(ident("kernel_entry"));
339    let abi = EntrypointAbiAttr::new(settings.cube_dim, settings.cluster_dim);
340    let entry_func = FuncOp::new(&mut ctx, entry_name, entry_func_ty);
341    entry_func.set_visibility(&mut ctx, SymbolVisibility::Public);
342    entry_func.set_entrypoint_abi(&mut ctx, abi);
343    module_inserter.append_op(&ctx, &entry_func);
344
345    let mut state = GlobalState {
346        reference_arena: Default::default(),
347        module,
348        module_inserter,
349        entry_func,
350        ident_unique_id: Default::default(),
351        typemap: Default::default(),
352        sizemap: Default::default(),
353        modes: Default::default(),
354        target_properties: Default::default(),
355        device_properties: Default::default(),
356        errors: Default::default(),
357        warnings: Default::default(),
358    };
359    settings.address_type.register(&mut state);
360
361    ctx.set_aux_ty(state);
362    ctx.set_aux_ty(DebugState::new(settings.debug_info != DebugInfo::None));
363    Rc::new(UnsafeCell::new(ctx))
364}
365
366/// Create a dummy context that can't be used for actual codegen. Useful for registering and
367/// resolving types without making the interface for that overly complex.
368/// Maybe we can replace the `&Scope` with a type registration trait on that function only at some point.
369fn dummy_context() -> Rc<UnsafeCell<Context>> {
370    let mut ctx = Context::default();
371
372    let module = ModuleOp::new(&mut ctx, ident("dummy_module"));
373    let module_block = module.get_body(&ctx, 0);
374    let mut module_inserter = OpInserter::new_at_block_end(module_block);
375
376    let entry_func_ty = FunctionType::get(&ctx, vec![], vec![UnitType::get(&ctx).into()]);
377    let entry_name = ident("dummy_entry");
378    let entry_func = FuncOp::new(&mut ctx, entry_name, entry_func_ty);
379    module_inserter.append_op(&ctx, &entry_func);
380
381    let state = GlobalState {
382        reference_arena: Default::default(),
383        module,
384        module_inserter,
385        entry_func,
386        ident_unique_id: Default::default(),
387        typemap: Default::default(),
388        sizemap: Default::default(),
389        modes: Default::default(),
390        target_properties: Default::default(),
391        device_properties: Default::default(),
392        errors: Default::default(),
393        warnings: Default::default(),
394    };
395
396    ctx.set_aux_ty(state);
397    Rc::new(UnsafeCell::new(ctx))
398}
399
400impl Debug for GlobalState {
401    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402        f.debug_struct("GlobalStateInner")
403            .field("reference_arena", &self.reference_arena)
404            .field("typemap", &self.typemap)
405            .field("sizemap", &self.sizemap)
406            .field("modes", &self.modes)
407            .field("target_properties", &self.target_properties)
408            .field("device_properties", &self.device_properties)
409            .finish()
410    }
411}
412
413/// Modes set and reset during expansion
414#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
415#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, TypeHash)]
416pub struct InstructionModes {
417    pub fp_math_mode: EnumSet<FastMath>,
418}
419
420impl Scope {
421    /// Set the device properties, shared with the client that holds them: a launch sets them
422    /// every time, so they are never copied.
423    pub fn device_properties(&self, properties: Arc<DeviceProperties>) {
424        self.state_mut().device_properties = Some(properties);
425    }
426
427    #[track_caller]
428    pub fn state(&self) -> &GlobalState {
429        self.ctx().aux_ty()
430    }
431
432    #[track_caller]
433    pub fn state_mut(&self) -> &mut GlobalState {
434        self.ctx_mut().aux_ty_mut()
435    }
436
437    /// The source location state, or `None` when this kernel records no locations.
438    pub fn debug_state(&self) -> Option<&mut DebugState> {
439        self.ctx_mut()
440            .try_aux_ty_mut::<DebugState>()
441            .filter(|debug| debug.is_enabled())
442    }
443
444    fn ident_id(&self) -> usize {
445        self.state().ident_unique_id.fetch_add(1, Ordering::SeqCst)
446    }
447
448    #[track_caller]
449    pub fn expand_state(&self) -> Ref<'_, ExpandState> {
450        self.expand_state.borrow()
451    }
452
453    #[track_caller]
454    pub fn expand_state_mut(&self) -> RefMut<'_, ExpandState> {
455        self.expand_state.borrow_mut()
456    }
457
458    pub fn ctx(&self) -> &Context {
459        self.ctx.borrow()
460    }
461
462    // #[allow(clippy::mut_from_ref)]
463    pub fn ctx_mut(&self) -> &mut Context {
464        self.ctx.borrow_mut()
465    }
466
467    pub fn inserter(&self) -> &mut dyn Inserter {
468        self.inserter.borrow_mut()
469    }
470
471    /// Create a parse scope that is at the root of a kernel definition.
472    ///
473    /// A local scope can be created with the [child](Self::child) method.
474    pub fn root(settings: KernelSettings) -> Self {
475        let ctx = new_context(settings);
476        let mut inserter = {
477            let ctx = unsafe { &*ctx.get() };
478            let state = ctx.aux_ty::<GlobalState>();
479            let entry_block = state.entry_func.get_entry_block(ctx);
480            OpInserter::new_at_block_end(entry_block)
481        };
482        let terminate_flag = init_bool_flag(
483            unsafe { &mut *ctx.get() },
484            &mut inserter,
485            "inv_terminate_flag",
486        );
487        Self {
488            ctx: CtxHandle::Rc(ctx),
489            inserter: InserterHandle::owned(inserter),
490            expand_state: RefCell::new(ExpandState {
491                inv_terminate_flag: Some(terminate_flag),
492                ..ExpandState::default()
493            }),
494        }
495    }
496
497    /// Create a parse scope that only exists for type registration in the kernel builder.
498    /// The state will be incomplete and should never be used for actual codegen.
499    pub fn dummy() -> Self {
500        let ctx = dummy_context();
501        let inserter = {
502            let ctx = unsafe { &*ctx.get() };
503            let state = ctx.aux_ty::<GlobalState>();
504            let entry_block = state.entry_func.get_entry_block(ctx);
505            OpInserter::new_at_block_end(entry_block)
506        };
507        Self {
508            ctx: CtxHandle::Rc(ctx),
509            inserter: InserterHandle::owned(inserter),
510            expand_state: Default::default(),
511        }
512    }
513
514    /// Create a rewrite scope from an existing context and inserter/rewriter
515    pub fn from_context_and_inserter(
516        ctx: &mut Context,
517        inserter: &mut (impl Inserter + 'static),
518    ) -> Self {
519        let inserter: *mut dyn Inserter = inserter;
520        Self {
521            ctx: CtxHandle::Ref(ctx),
522            inserter: InserterHandle::Ref(inserter),
523            expand_state: RefCell::new(ExpandState::default()),
524        }
525    }
526
527    /// Create a new mutable local variable of type specified by `value_ty`.
528    /// `initializer` is a constant attribute and has the same rules as `OpConstant`. This is because
529    /// SPIR-V does not allow non-constant (technically non-global, but constants are the only
530    /// non-pointer globals) initializers.
531    pub fn create_local_mut(
532        &self,
533        value_ty: impl Into<TypeHandle>,
534        init: Option<AttrObj>,
535    ) -> Value {
536        let value_ty = value_ty.into();
537        let ctx = self.ctx_mut();
538        let align = value_ty.align(ctx);
539        let op = DeclareVariableOp::new(ctx, value_ty, AddressSpace::Local, align, init);
540        let out = op.get_result(ctx);
541        self.inserter().append_op(ctx, &op);
542        out
543    }
544
545    /// Create a shared variable of the given item type.
546    pub fn create_shared(
547        &self,
548        value_ty: impl Into<TypeHandle>,
549        alignment: Option<usize>,
550    ) -> Value {
551        let value_ty = value_ty.into();
552        let ctx = self.ctx_mut();
553        let align = alignment.unwrap_or_else(|| value_ty.align(ctx));
554        let op = DeclareVariableOp::new(ctx, value_ty, AddressSpace::Shared, align, None);
555        let out = op.get_result(ctx);
556        self.inserter().append_op(ctx, &op);
557        out
558    }
559
560    pub fn func_ident(&self, label: Option<&str>) -> Identifier {
561        let unique_id = self.ident_id();
562        match label {
563            Some(label) => ident(format!("{label}_{unique_id}")),
564            None => ident(format!("func_{unique_id}")),
565        }
566    }
567
568    /// Create a new function.
569    pub fn register_func(&self, func: FuncOp) {
570        let ctx = self.ctx_mut();
571        let state = self.state_mut();
572        func.set_visibility(ctx, SymbolVisibility::Private);
573        state.module_inserter.append_op(ctx, &func);
574    }
575
576    /// Register an [`Instruction`] into the scope.
577    pub fn register(&self, op: &dyn Op) {
578        let ctx = self.ctx();
579        self.inserter().append_op(ctx, op);
580    }
581
582    /// Register an [`Instruction`] into the scope and return its result.
583    pub fn register_with_result(&self, op: &dyn OneResultInterface) -> Value {
584        self.register(op);
585        op.get_result(self.ctx())
586    }
587
588    /// Terminate block with a `cube.yield` if not already terminated
589    pub fn terminate_yield(&self) {
590        let block = self.inserter().get_insertion_block(self.ctx());
591        let block = block.expect("Should have insertion block");
592        if block.deref(self.ctx()).get_terminator(self.ctx()).is_none() {
593            self.register(&YieldOp::new(self.ctx_mut()));
594        }
595    }
596
597    pub fn update_flags_after_branch(&self, children: &[Scope]) {
598        self.set_may_continue(children);
599        self.set_may_break(children);
600        self.set_may_return(children);
601        self.set_may_terminate(children);
602    }
603
604    pub fn update_flags_after_loop(&self, children: &[Scope]) {
605        self.set_may_return(children);
606        self.set_may_terminate(children);
607    }
608
609    pub fn update_flags_after_early_return(&self, children: &[Scope]) {
610        self.set_may_terminate(children);
611    }
612
613    pub fn update_flags_before_unrolled_iteration(&self, body: &Scope) {
614        body.update_flags_before_unrolled_iteration_at(self.unrolled_insertion_point(body));
615    }
616
617    fn update_flags_before_unrolled_iteration_at(&self, insertion_point: OpInsertionPoint) {
618        self.inserter().set_insertion_point(insertion_point);
619
620        // Reset continue to initial state
621        if self.expand_state().may_continue {
622            let flag = self.expand_state().inv_continue_flag.unwrap();
623            self.register(&StoreOp::new(self.ctx_mut(), flag, self.const_bool(true)));
624            self.expand_state_mut().may_continue = false;
625        }
626
627        self.set_may_break(core::slice::from_ref(self));
628        self.set_may_return(core::slice::from_ref(self));
629        self.set_may_terminate(core::slice::from_ref(self));
630    }
631
632    pub fn finalize_unrolled_loop(&self, body: &Scope) {
633        self.inserter()
634            .set_insertion_point(self.unrolled_insertion_point(body));
635
636        self.set_may_return(core::slice::from_ref(body));
637        self.set_may_terminate(core::slice::from_ref(body));
638    }
639
640    fn unrolled_insertion_point(&self, body: &Scope) -> OpInsertionPoint {
641        let current_block = self.inserter().get_insertion_block(self.ctx()).unwrap();
642        op_insertion_point_in_block(
643            self.ctx(),
644            body.inserter().get_insertion_point(),
645            current_block,
646        )
647    }
648
649    pub fn set_may_terminate(&self, children: &[Scope]) {
650        let child_may_terminate = children
651            .iter()
652            .any(|scope| scope.expand_state().may_terminate);
653        if child_may_terminate {
654            self.expand_state_mut().may_terminate = true;
655            let flag = self.expand_state().inv_terminate_flag;
656            self.predicate_on_flag(flag.expect("Can't terminate in rewrite context"));
657        }
658    }
659
660    pub fn set_may_return(&self, children: &[Scope]) {
661        let child_may_return = children.iter().any(|scope| scope.expand_state().may_return);
662        if child_may_return {
663            self.expand_state_mut().may_return = true;
664            let flag = self.expand_state().inv_return_flag;
665            self.predicate_on_flag(flag.expect("Can't return in rewrite context"));
666        }
667    }
668
669    pub fn set_may_break(&self, children: &[Scope]) {
670        let child_may_break = children.iter().any(|scope| scope.expand_state().may_break);
671        if child_may_break {
672            self.expand_state_mut().may_break = true;
673            let flag = self.expand_state().inv_break_flag;
674            self.predicate_on_flag(flag.expect("Should have break flag"));
675        }
676    }
677
678    pub fn set_may_continue(&self, children: &[Scope]) {
679        let child_may_continue = children
680            .iter()
681            .any(|scope| scope.expand_state().may_continue);
682        if child_may_continue {
683            self.expand_state_mut().may_continue = true;
684            let flag = self.expand_state().inv_continue_flag;
685            self.predicate_on_flag(flag.expect("Should have continue flag"));
686        }
687    }
688
689    fn predicate_on_flag(&self, flag: Value) {
690        let ctx = self.ctx_mut();
691        let cond = read_value(self, flag);
692        let predication = IfOp::new(ctx, cond);
693        let then_block = predication.then_block(ctx);
694        let else_block = predication.else_block(ctx);
695        let yield_ = YieldOp::new(ctx).get_operation();
696        yield_.insert_at_back(then_block, ctx);
697        let yield_ = YieldOp::new(ctx).get_operation();
698        yield_.insert_at_back(else_block, ctx);
699        self.register(&predication);
700        // The insertion cursor can sit inside a block that already ends with a
701        // terminator — e.g. the then-block of an earlier predication, whose
702        // trailing yield was placed when that predication was built. Appending
703        // another yield there would leave the block with two terminators, which
704        // fails module verification. Only terminate the block if it isn't yet.
705        self.terminate_yield();
706        self.inserter()
707            .set_insertion_point_to_block_start(then_block);
708    }
709
710    /// Add a value to the global arena so we can create a kernel-wide reference to it.
711    /// The reference is the same as the type for simplicity, but is only valid for the duration of
712    /// the root scope. Ensure the reference lifetime is shortened to the lifetime of the underlying
713    /// variable being referenced.
714    pub fn create_kernel_ref<'a, T>(&self, value: T) -> &'a mut T
715    where
716        T: 'a,
717    {
718        let state = self.state_mut();
719        let reference = state.reference_arena.alloc(value);
720        unsafe { core::mem::transmute(reference) }
721    }
722
723    /// Resolve the element type of the given generic type.
724    pub fn resolve_type<T: 'static>(&self) -> Option<ElemType> {
725        let state = self.state();
726        let result = state.typemap.get(&TypeId::of::<T>());
727
728        result.cloned()
729    }
730
731    /// Resolve the comptime size of the given generic size.
732    pub fn resolve_size<T: 'static>(&self) -> Option<usize> {
733        let state = self.state();
734        let result = state.sizemap.get(&TypeId::of::<T>());
735
736        result.cloned()
737    }
738
739    /// Register the element type for the given generic type.
740    pub fn register_type<T: 'static>(&self, elem: ElemType) {
741        self.state_mut().register_type::<T>(elem);
742    }
743
744    /// `T` names `elem` until the returned definition is dropped, which gives `T` back the
745    /// element it named before, if any: what a `define!` in a function re-entered on another
746    /// element leaves its outer call.
747    pub fn define_type<T: 'static>(&self, elem: ElemType) -> TypeDefinition<'_> {
748        let before = self.resolve_type::<T>();
749        self.register_type::<T>(elem);
750        TypeDefinition {
751            scope: self,
752            ty: TypeId::of::<T>(),
753            before,
754        }
755    }
756
757    /// `T` names `size` until the returned definition is dropped, which gives `T` back the size
758    /// it named before, if any: what a `size!` in a function re-entered on another size leaves
759    /// its outer call.
760    pub fn define_size<T: 'static>(&self, size: usize) -> SizeDefinition<'_> {
761        let before = self.resolve_size::<T>();
762        self.register_size::<T>(size);
763        SizeDefinition {
764            scope: self,
765            size: TypeId::of::<T>(),
766            before,
767        }
768    }
769
770    /// Register the comptime size for the given generic size.
771    pub fn register_size<T: 'static>(&self, size: usize) {
772        let state = self.state_mut();
773
774        state.sizemap.insert(TypeId::of::<T>(), size);
775    }
776
777    /// Register the type and size of a scalarizable type
778    pub fn register_value_type<T: 'static, N: 'static>(&self, value: impl Typed) {
779        let ty = value.get_type(self.ctx());
780        let scalar_ty = ty.scalar_ty(self.ctx());
781        let vector_size = ty.vector_size(self.ctx());
782        let storage_ty = {
783            let ctx = self.ctx();
784            let scalar_ty = scalar_ty.deref(ctx);
785            let scalar = type_cast::<dyn ScalarType>(&*scalar_ty).unwrap();
786            scalar.elem_type(ctx)
787        };
788        self.register_type::<T>(storage_ty);
789        self.register_size::<N>(vector_size);
790    }
791
792    /// Create an empty child scope for branching ops that don't need new flags, i.e. if/else.
793    pub fn branch_child(&self, inserter: impl Inserter + 'static) -> Self {
794        Self {
795            ctx: self.ctx.clone(),
796            inserter: InserterHandle::owned(inserter),
797            expand_state: RefCell::new(ExpandState {
798                may_continue: false,
799                may_break: false,
800                may_return: false,
801                may_terminate: false,
802                inv_terminate_flag: self.expand_state().inv_terminate_flag,
803                inv_return_flag: self.expand_state().inv_return_flag,
804                inv_break_flag: self.expand_state().inv_break_flag,
805                inv_continue_flag: self.expand_state().inv_continue_flag,
806                return_value: self.expand_state().return_value,
807            }),
808        }
809    }
810
811    /// Create a child scope with a new break condition.
812    pub fn loop_child(&self, mut inserter: impl Inserter + 'static) -> Self {
813        let break_flag = init_bool_flag(self.ctx_mut(), self.inserter(), "inv_break_flag");
814        let continue_flag = init_bool_flag(self.ctx_mut(), &mut inserter, "inv_continue_flag");
815        Self {
816            ctx: self.ctx.clone(),
817            inserter: InserterHandle::owned(inserter),
818            expand_state: RefCell::new(ExpandState {
819                may_terminate: false,
820                may_return: false,
821                may_break: false,
822                may_continue: false,
823                inv_terminate_flag: self.expand_state().inv_terminate_flag,
824                inv_return_flag: self.expand_state().inv_return_flag,
825                inv_break_flag: Some(break_flag),
826                inv_continue_flag: Some(continue_flag),
827                return_value: self.expand_state().return_value,
828            }),
829        }
830    }
831
832    /// Create a child scope with a new break condition.
833    pub fn unrolled_loop_child(&self) -> Self {
834        let break_flag = init_bool_flag(self.ctx_mut(), self.inserter(), "inv_break_flag");
835        let continue_flag = init_bool_flag(self.ctx_mut(), self.inserter(), "inv_continue_flag");
836        let inserter = OpInserter::new(self.inserter().get_insertion_point());
837        Self {
838            ctx: self.ctx.clone(),
839            inserter: InserterHandle::owned(inserter),
840            expand_state: RefCell::new(ExpandState {
841                may_terminate: false,
842                may_return: false,
843                may_break: false,
844                may_continue: false,
845                inv_terminate_flag: self.expand_state().inv_terminate_flag,
846                inv_return_flag: self.expand_state().inv_return_flag,
847                inv_break_flag: Some(break_flag),
848                inv_continue_flag: Some(continue_flag),
849                return_value: self.expand_state().return_value,
850            }),
851        }
852    }
853
854    /// Create a child that's at the root of a new inlined function and sets up a new early return flag.
855    pub fn inlined_func_child(
856        &self,
857        mut inserter: impl Inserter + 'static,
858        return_value: Option<Value>,
859    ) -> Self {
860        let return_flag = init_bool_flag(self.ctx_mut(), &mut inserter, "inv_return_flag");
861        Self {
862            ctx: self.ctx.clone(),
863            inserter: InserterHandle::owned(inserter),
864            expand_state: RefCell::new(ExpandState {
865                may_continue: false,
866                may_break: false,
867                may_return: false,
868                may_terminate: false,
869                inv_terminate_flag: self.expand_state().inv_terminate_flag,
870                inv_return_flag: Some(return_flag),
871                inv_break_flag: None,
872                inv_continue_flag: None,
873                return_value: return_value,
874            }),
875        }
876    }
877
878    /// Create a child that's at the root of a new non-inlined function.
879    /// `terminate!()` won't work for now so if non-inlined functions are ever made user accessible
880    /// we need to deal with that.
881    pub fn func_child(&self, mut inserter: impl Inserter + 'static) -> Self {
882        let terminate_flag = init_bool_flag(self.ctx_mut(), &mut inserter, "inv_terminate_flag");
883        Self {
884            ctx: self.ctx.clone(),
885            inserter: InserterHandle::owned(inserter),
886            expand_state: RefCell::new(ExpandState {
887                may_continue: false,
888                may_break: false,
889                may_return: false,
890                may_terminate: false,
891                inv_terminate_flag: Some(terminate_flag),
892                inv_return_flag: None,
893                inv_break_flag: None,
894                inv_continue_flag: None,
895                return_value: None,
896            }),
897        }
898    }
899
900    // Adds a validation error.
901    pub fn push_error(&self, msg: impl Into<String>) {
902        self.state_mut().errors.push(msg.into());
903    }
904
905    /// Returns all validation errors.
906    pub fn pop_errors(&self) -> Vec<String> {
907        core::mem::take(&mut self.state_mut().errors)
908    }
909
910    /// Adds a non-fatal validation warning. Logged when the kernel is built.
911    pub fn push_warning(&self, msg: impl Into<String>) {
912        self.state_mut().warnings.push(msg.into());
913    }
914
915    /// Returns all validation warnings.
916    pub fn pop_warnings(&self) -> Vec<String> {
917        core::mem::take(&mut self.state_mut().warnings)
918    }
919
920    /// Obtain the index-th buffer
921    pub fn global(
922        &self,
923        buffer_pos: usize,
924        ext_meta_pos: Option<usize>,
925        value_ty: TypeHandle,
926    ) -> Value {
927        let entry_func = self.state().entry_func;
928        let ctx = self.ctx_mut();
929
930        let ty_arr = RuntimeArrayType::get(ctx, value_ty);
931        let ty = PointerType::get(ctx, ty_arr.into(), AddressSpace::Global(buffer_pos));
932
933        let id = entry_func.push_argument(ctx, ty.to_handle());
934        entry_func.set_arg_attr(
935            ctx,
936            id,
937            &ATTR_BUFFER_BINDING,
938            Box::new(BufferBindingAttr::new(buffer_pos, ext_meta_pos)),
939        );
940
941        entry_func.get_entry_block(ctx).deref(ctx).get_argument(id)
942    }
943
944    /// Obtain the index-th tensor map
945    pub fn tensor_map(&self, buffer_pos: usize, ext_meta_pos: usize) -> Value {
946        let entry_func = self.state().entry_func;
947        let ctx = self.ctx();
948        let ty = TensorMapType::get(ctx);
949
950        let id = entry_func.push_argument(ctx, ty.to_handle());
951        entry_func.set_arg_attr_unit(ctx, id, &ATTR_TENSOR_MAP_BINDING);
952        entry_func.set_arg_attr(
953            ctx,
954            id,
955            &ATTR_BUFFER_BINDING,
956            Box::new(BufferBindingAttr::new(buffer_pos, Some(ext_meta_pos))),
957        );
958        entry_func.get_entry_block(ctx).deref(ctx).get_argument(id)
959    }
960
961    pub fn kernel_arg(&self, idx: usize) -> Value {
962        let entry_block = self.state().entry_func.get_entry_block(self.ctx());
963        entry_block.deref(self.ctx()).get_argument(idx)
964    }
965
966    pub fn extract_field(&self, aggregate: Value, field: usize) -> Value {
967        let ctx = self.ctx_mut();
968        let op = CompositeExtractOp::new(ctx, aggregate, field);
969        self.register_with_result(&op)
970    }
971
972    pub fn const_usize(&self, value: usize) -> Value {
973        let op = ConstantOp::new(self.ctx_mut(), Box::new(IndexAttr::new(value)));
974        self.register_with_result(&op)
975    }
976
977    pub fn const_bool(&self, value: bool) -> Value {
978        let op = ConstantOp::new(self.ctx_mut(), Box::new(BoolAttr::new(value)));
979        self.register_with_result(&op)
980    }
981
982    pub fn into_context(self) -> Option<Context> {
983        let entry = self.state().entry_func.get_entry_block(self.ctx());
984        let term = entry.deref(self.ctx()).get_terminator(self.ctx());
985        let is_yield = term.is_some_and(|term| term.is_op::<YieldOp>(self.ctx()));
986        if let Some(term) = term
987            && is_yield
988        {
989            self.inserter().set_insertion_point_to_block_end(entry);
990            self.register(&ReturnOp::new(self.ctx_mut()));
991            Operation::erase(term, self.ctx_mut());
992        } else if term.is_none() {
993            self.inserter().set_insertion_point_to_block_end(entry);
994            self.register(&ReturnOp::new(self.ctx_mut()));
995        }
996        match self.ctx {
997            CtxHandle::Rc(ctx) => Some(Rc::into_inner(ctx)?.into_inner()),
998            CtxHandle::Ref(_) => None,
999        }
1000    }
1001}
1002
1003impl Display for Scope {
1004    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1005        let ctx = self.ctx();
1006        let state = self.state();
1007        write!(f, "{}", state.module.disp(ctx))
1008    }
1009}
1010
1011fn init_bool_flag(ctx: &mut Context, inserter: &mut dyn Inserter, name: &str) -> Value {
1012    let bool = TypeAttr::new(BoolType::get(ctx).to_handle());
1013    let r#true = BoolAttr::new(true).into();
1014    let flag = DeclareVariableOp::new(ctx, bool, AddressSpace::Local, 1, Some(r#true));
1015    inserter.append_op(ctx, &flag);
1016    set_operation_result_name(ctx, flag.get_operation(), 0, Some(ident(name)));
1017    flag.get_result(ctx)
1018}
1019
1020/// A type's definition for a block, made by [`Scope::define_type`].
1021pub struct TypeDefinition<'a> {
1022    scope: &'a Scope,
1023    ty: TypeId,
1024    before: Option<ElemType>,
1025}
1026
1027impl Drop for TypeDefinition<'_> {
1028    fn drop(&mut self) {
1029        if let Some(before) = self.before {
1030            self.scope.state_mut().typemap.insert(self.ty, before);
1031        }
1032    }
1033}
1034
1035/// A size's definition for a block, made by [`Scope::define_size`].
1036pub struct SizeDefinition<'a> {
1037    scope: &'a Scope,
1038    size: TypeId,
1039    before: Option<usize>,
1040}
1041
1042impl Drop for SizeDefinition<'_> {
1043    fn drop(&mut self) {
1044        if let Some(before) = self.before {
1045            self.scope.state_mut().sizemap.insert(self.size, before);
1046        }
1047    }
1048}