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