wasmtime_internal_cranelift/func_environ/gc/enabled/
drc.rs

1//! Compiler for the deferred reference-counting (DRC) collector and its
2//! barriers.
3
4use super::*;
5use crate::translate::TargetEnvironment;
6use crate::{TRAP_INTERNAL_ASSERT, func_environ::FuncEnvironment};
7use cranelift_codegen::ir::condcodes::IntCC;
8use cranelift_codegen::ir::{self, InstBuilder};
9use cranelift_frontend::FunctionBuilder;
10use smallvec::SmallVec;
11use wasmtime_environ::drc::{EXCEPTION_TAG_DEFINED_OFFSET, EXCEPTION_TAG_INSTANCE_OFFSET};
12use wasmtime_environ::{
13    GcTypeLayouts, ModuleInternedTypeIndex, PtrSize, TypeIndex, VMGcKind, WasmHeapTopType,
14    WasmHeapType, WasmRefType, WasmResult, WasmStorageType, WasmValType, drc::DrcTypeLayouts,
15};
16
17#[derive(Default)]
18pub struct DrcCompiler {
19    layouts: DrcTypeLayouts,
20}
21
22impl DrcCompiler {
23    /// Generate code to load the given GC reference's ref count.
24    ///
25    /// Assumes that the given `gc_ref` is a non-null, non-i31 GC reference.
26    fn load_ref_count(
27        &mut self,
28        func_env: &mut FuncEnvironment<'_>,
29        builder: &mut FunctionBuilder,
30        gc_ref: ir::Value,
31    ) -> ir::Value {
32        let offset = func_env.offsets.vm_drc_header_ref_count();
33        let pointer = func_env.prepare_gc_ref_access(
34            builder,
35            gc_ref,
36            BoundsCheck::StaticOffset {
37                offset,
38                access_size: u8::try_from(ir::types::I64.bytes()).unwrap(),
39            },
40        );
41        builder
42            .ins()
43            .load(ir::types::I64, ir::MemFlags::trusted(), pointer, 0)
44    }
45
46    /// Generate code to update the given GC reference's ref count to the new
47    /// value.
48    ///
49    /// Assumes that the given `gc_ref` is a non-null, non-i31 GC reference.
50    fn store_ref_count(
51        &mut self,
52        func_env: &mut FuncEnvironment<'_>,
53        builder: &mut FunctionBuilder,
54        gc_ref: ir::Value,
55        new_ref_count: ir::Value,
56    ) {
57        let offset = func_env.offsets.vm_drc_header_ref_count();
58        let pointer = func_env.prepare_gc_ref_access(
59            builder,
60            gc_ref,
61            BoundsCheck::StaticOffset {
62                offset,
63                access_size: u8::try_from(ir::types::I64.bytes()).unwrap(),
64            },
65        );
66        builder
67            .ins()
68            .store(ir::MemFlags::trusted(), new_ref_count, pointer, 0);
69    }
70
71    /// Generate code to increment or decrement the given GC reference's ref
72    /// count.
73    ///
74    /// The new ref count is returned.
75    ///
76    /// Assumes that the given `gc_ref` is a non-null, non-i31 GC reference.
77    fn mutate_ref_count(
78        &mut self,
79        func_env: &mut FuncEnvironment<'_>,
80        builder: &mut FunctionBuilder,
81        gc_ref: ir::Value,
82        delta: i64,
83    ) -> ir::Value {
84        debug_assert!(delta == -1 || delta == 1);
85        let old_ref_count = self.load_ref_count(func_env, builder, gc_ref);
86        let new_ref_count = builder.ins().iadd_imm(old_ref_count, delta);
87        self.store_ref_count(func_env, builder, gc_ref, new_ref_count);
88        new_ref_count
89    }
90
91    /// Push `gc_ref` onto the over-approximated-stack-roots list.
92    ///
93    /// `gc_ref` must not already be in the list.
94    ///
95    /// `reserved` must be the current reserved bits for this `gc_ref`.
96    fn push_onto_over_approximated_stack_roots(
97        &mut self,
98        func_env: &mut FuncEnvironment<'_>,
99        builder: &mut FunctionBuilder<'_>,
100        gc_ref: ir::Value,
101        reserved: ir::Value,
102    ) {
103        debug_assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);
104        debug_assert_eq!(builder.func.dfg.value_type(reserved), ir::types::I32);
105
106        let head = self.load_over_approximated_stack_roots_head(func_env, builder);
107
108        // Load the current first list element, which will be our new next list
109        // element.
110        let next = builder
111            .ins()
112            .load(ir::types::I32, ir::MemFlags::trusted(), head, 0);
113
114        // Update our object's header to point to `next` and consider itself part of the list.
115        self.set_next_over_approximated_stack_root(func_env, builder, gc_ref, next);
116        self.set_in_over_approximated_stack_roots_bit(func_env, builder, gc_ref, reserved);
117
118        // Increment our ref count because the list is logically holding a strong reference.
119        self.mutate_ref_count(func_env, builder, gc_ref, 1);
120
121        // Commit this object as the new head of the list.
122        builder
123            .ins()
124            .store(ir::MemFlags::trusted(), gc_ref, head, 0);
125    }
126
127    /// Load a pointer to the first element of the DRC heap's
128    /// over-approximated-stack-roots list.
129    fn load_over_approximated_stack_roots_head(
130        &mut self,
131        func_env: &mut FuncEnvironment<'_>,
132        builder: &mut FunctionBuilder,
133    ) -> ir::Value {
134        let ptr_ty = func_env.pointer_type();
135        let vmctx = func_env.vmctx(&mut builder.func);
136        let vmctx = builder.ins().global_value(ptr_ty, vmctx);
137        builder.ins().load(
138            ptr_ty,
139            ir::MemFlags::trusted().with_readonly(),
140            vmctx,
141            i32::from(func_env.offsets.ptr.vmctx_gc_heap_data()),
142        )
143    }
144
145    /// Set the `VMDrcHeader::next_over_approximated_stack_root` field.
146    fn set_next_over_approximated_stack_root(
147        &mut self,
148        func_env: &mut FuncEnvironment<'_>,
149        builder: &mut FunctionBuilder<'_>,
150        gc_ref: ir::Value,
151        next: ir::Value,
152    ) {
153        debug_assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);
154        debug_assert_eq!(builder.func.dfg.value_type(next), ir::types::I32);
155        let ptr = func_env.prepare_gc_ref_access(
156            builder,
157            gc_ref,
158            BoundsCheck::StaticOffset {
159                offset: func_env
160                    .offsets
161                    .vm_drc_header_next_over_approximated_stack_root(),
162                access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
163            },
164        );
165        builder.ins().store(ir::MemFlags::trusted(), next, ptr, 0);
166    }
167
168    /// Set the in-over-approximated-stack-roots list bit in a `VMDrcHeader`'s
169    /// reserved bits.
170    fn set_in_over_approximated_stack_roots_bit(
171        &mut self,
172        func_env: &mut FuncEnvironment<'_>,
173        builder: &mut FunctionBuilder<'_>,
174        gc_ref: ir::Value,
175        old_reserved_bits: ir::Value,
176    ) {
177        let in_set_bit = builder.ins().iconst(
178            ir::types::I32,
179            i64::from(wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT),
180        );
181        let new_reserved = builder.ins().bor(old_reserved_bits, in_set_bit);
182        self.set_reserved_bits(func_env, builder, gc_ref, new_reserved);
183    }
184
185    /// Update the reserved bits in a `VMDrcHeader`.
186    fn set_reserved_bits(
187        &mut self,
188        func_env: &mut FuncEnvironment<'_>,
189        builder: &mut FunctionBuilder<'_>,
190        gc_ref: ir::Value,
191        new_reserved: ir::Value,
192    ) {
193        let ptr = func_env.prepare_gc_ref_access(
194            builder,
195            gc_ref,
196            BoundsCheck::StaticOffset {
197                offset: func_env.offsets.vm_gc_header_reserved_bits(),
198                access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
199            },
200        );
201        builder
202            .ins()
203            .store(ir::MemFlags::trusted(), new_reserved, ptr, 0);
204    }
205
206    /// Write to an uninitialized field or element inside a GC object.
207    fn init_field(
208        &mut self,
209        func_env: &mut FuncEnvironment<'_>,
210        builder: &mut FunctionBuilder<'_>,
211        field_addr: ir::Value,
212        ty: WasmStorageType,
213        val: ir::Value,
214    ) -> WasmResult<()> {
215        // Data inside GC objects is always little endian.
216        let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);
217
218        match ty {
219            WasmStorageType::Val(WasmValType::Ref(r))
220                if r.heap_type.top() == WasmHeapTopType::Func =>
221            {
222                write_func_ref_at_addr(func_env, builder, r, flags, field_addr, val)?;
223            }
224            WasmStorageType::Val(WasmValType::Ref(r)) => {
225                self.translate_init_gc_reference(func_env, builder, r, field_addr, val, flags)?;
226            }
227            WasmStorageType::I8 => {
228                assert_eq!(builder.func.dfg.value_type(val), ir::types::I32);
229                builder.ins().istore8(flags, val, field_addr, 0);
230            }
231            WasmStorageType::I16 => {
232                assert_eq!(builder.func.dfg.value_type(val), ir::types::I32);
233                builder.ins().istore16(flags, val, field_addr, 0);
234            }
235            WasmStorageType::Val(_) => {
236                let size_of_access = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&ty);
237                assert_eq!(builder.func.dfg.value_type(val).bytes(), size_of_access);
238                builder.ins().store(flags, val, field_addr, 0);
239            }
240        }
241
242        Ok(())
243    }
244
245    /// Write to an uninitialized GC reference field, initializing it.
246    ///
247    /// ```text
248    /// *dst = new_val
249    /// ```
250    ///
251    /// Doesn't need to do a full write barrier: we don't have an old reference
252    /// that is being overwritten and needs its refcount decremented, just a new
253    /// reference whose count should be incremented.
254    fn translate_init_gc_reference(
255        &mut self,
256        func_env: &mut FuncEnvironment<'_>,
257        builder: &mut FunctionBuilder,
258        ty: WasmRefType,
259        dst: ir::Value,
260        new_val: ir::Value,
261        flags: ir::MemFlags,
262    ) -> WasmResult<()> {
263        let (ref_ty, needs_stack_map) = func_env.reference_type(ty.heap_type);
264        debug_assert!(needs_stack_map);
265
266        // Special case for references to uninhabited bottom types: see
267        // `translate_write_gc_reference` for details.
268        if let WasmHeapType::None = ty.heap_type {
269            if ty.nullable {
270                let null = builder.ins().iconst(ref_ty, 0);
271                builder.ins().store(flags, null, dst, 0);
272            } else {
273                let zero = builder.ins().iconst(ir::types::I32, 0);
274                builder.ins().trapz(zero, TRAP_INTERNAL_ASSERT);
275            }
276            return Ok(());
277        };
278
279        // Special case for `i31ref`s: no need for any barriers.
280        if let WasmHeapType::I31 = ty.heap_type {
281            return unbarriered_store_gc_ref(builder, ty.heap_type, dst, new_val, flags);
282        }
283
284        // Our initialization barrier for GC references being copied out of the
285        // stack and initializing a table/global/struct field/etc... is roughly
286        // equivalent to the following pseudo-CLIF:
287        //
288        // ```
289        // current_block:
290        //     ...
291        //     let new_val_is_null_or_i31 = ...
292        //     brif new_val_is_null_or_i31, continue_block, inc_ref_block
293        //
294        // inc_ref_block:
295        //     let ref_count = load new_val.ref_count
296        //     let new_ref_count = iadd_imm ref_count, 1
297        //     store new_val.ref_count, new_ref_count
298        //     jump check_old_val_block
299        //
300        // continue_block:
301        //     store dst, new_val
302        //     ...
303        // ```
304        //
305        // This write barrier is responsible for ensuring that the new value's
306        // ref count is incremented now that the table/global/struct/etc... is
307        // holding onto it.
308
309        let current_block = builder.current_block().unwrap();
310        let inc_ref_block = builder.create_block();
311        let continue_block = builder.create_block();
312
313        builder.ensure_inserted_block();
314        builder.insert_block_after(inc_ref_block, current_block);
315        builder.insert_block_after(continue_block, inc_ref_block);
316
317        // Current block: check whether the new value is non-null and
318        // non-i31. If so, branch to the `inc_ref_block`.
319        log::trace!("DRC initialization barrier: check if the value is null or i31");
320        let new_val_is_null_or_i31 = func_env.gc_ref_is_null_or_i31(builder, ty, new_val);
321        builder.ins().brif(
322            new_val_is_null_or_i31,
323            continue_block,
324            &[],
325            inc_ref_block,
326            &[],
327        );
328
329        // Block to increment the ref count of the new value when it is non-null
330        // and non-i31.
331        builder.switch_to_block(inc_ref_block);
332        builder.seal_block(inc_ref_block);
333        log::trace!("DRC initialization barrier: increment the ref count of the initial value");
334        self.mutate_ref_count(func_env, builder, new_val, 1);
335        builder.ins().jump(continue_block, &[]);
336
337        // Join point after we're done with the GC barrier: do the actual store
338        // to initialize the field.
339        builder.switch_to_block(continue_block);
340        builder.seal_block(continue_block);
341        log::trace!(
342            "DRC initialization barrier: finally, store into {dst:?} to initialize the field"
343        );
344        unbarriered_store_gc_ref(builder, ty.heap_type, dst, new_val, flags)?;
345
346        Ok(())
347    }
348}
349
350/// Emit CLIF to call the `gc_raw_alloc` libcall.
351fn emit_gc_raw_alloc(
352    func_env: &mut FuncEnvironment<'_>,
353    builder: &mut FunctionBuilder<'_>,
354    kind: VMGcKind,
355    ty: ModuleInternedTypeIndex,
356    size: ir::Value,
357    align: u32,
358) -> ir::Value {
359    let gc_alloc_raw_builtin = func_env.builtin_functions.gc_alloc_raw(builder.func);
360    let vmctx = func_env.vmctx_val(&mut builder.cursor());
361
362    let kind = builder
363        .ins()
364        .iconst(ir::types::I32, i64::from(kind.as_u32()));
365
366    let ty = builder.ins().iconst(ir::types::I32, i64::from(ty.as_u32()));
367
368    assert!(align.is_power_of_two());
369    let align = builder.ins().iconst(ir::types::I32, i64::from(align));
370
371    let call_inst = builder
372        .ins()
373        .call(gc_alloc_raw_builtin, &[vmctx, kind, ty, size, align]);
374
375    let gc_ref = builder.func.dfg.first_result(call_inst);
376    builder.declare_value_needs_stack_map(gc_ref);
377    gc_ref
378}
379
380impl GcCompiler for DrcCompiler {
381    fn layouts(&self) -> &dyn GcTypeLayouts {
382        &self.layouts
383    }
384
385    fn alloc_array(
386        &mut self,
387        func_env: &mut FuncEnvironment<'_>,
388        builder: &mut FunctionBuilder<'_>,
389        array_type_index: TypeIndex,
390        init: super::ArrayInit<'_>,
391    ) -> WasmResult<ir::Value> {
392        let interned_type_index =
393            func_env.module.types[array_type_index].unwrap_module_type_index();
394        let ptr_ty = func_env.pointer_type();
395
396        let len_offset = gc_compiler(func_env)?.layouts().array_length_field_offset();
397        let array_layout = func_env.array_layout(interned_type_index).clone();
398        let base_size = array_layout.base_size;
399        let align = array_layout.align;
400        let len_to_elems_delta = base_size.checked_sub(len_offset).unwrap();
401
402        // First, compute the array's total size from its base size, element
403        // size, and length.
404        let len = init.len(&mut builder.cursor());
405        let size = emit_array_size(func_env, builder, &array_layout, len);
406
407        // Second, now that we have the array object's total size, call the
408        // `gc_alloc_raw` builtin libcall to allocate the array.
409        let array_ref = emit_gc_raw_alloc(
410            func_env,
411            builder,
412            VMGcKind::ArrayRef,
413            interned_type_index,
414            size,
415            align,
416        );
417
418        // Write the array's length into the appropriate slot.
419        //
420        // Note: we don't need to bounds-check the GC ref access here, since we
421        // trust the results of the allocation libcall.
422        let base = func_env.get_gc_heap_base(builder);
423        let extended_array_ref =
424            uextend_i32_to_pointer_type(builder, func_env.pointer_type(), array_ref);
425        let object_addr = builder.ins().iadd(base, extended_array_ref);
426        let len_addr = builder.ins().iadd_imm(object_addr, i64::from(len_offset));
427        let len = init.len(&mut builder.cursor());
428        builder
429            .ins()
430            .store(ir::MemFlags::trusted(), len, len_addr, 0);
431
432        // Finally, initialize the elements.
433        let len_to_elems_delta = builder.ins().iconst(ptr_ty, i64::from(len_to_elems_delta));
434        let elems_addr = builder.ins().iadd(len_addr, len_to_elems_delta);
435        init.initialize(
436            func_env,
437            builder,
438            interned_type_index,
439            base_size,
440            size,
441            elems_addr,
442            |func_env, builder, elem_ty, elem_addr, val| {
443                self.init_field(func_env, builder, elem_addr, elem_ty, val)
444            },
445        )?;
446        Ok(array_ref)
447    }
448
449    fn alloc_struct(
450        &mut self,
451        func_env: &mut FuncEnvironment<'_>,
452        builder: &mut FunctionBuilder<'_>,
453        struct_type_index: TypeIndex,
454        field_vals: &[ir::Value],
455    ) -> WasmResult<ir::Value> {
456        let interned_type_index =
457            func_env.module.types[struct_type_index].unwrap_module_type_index();
458        let struct_layout = func_env.struct_or_exn_layout(interned_type_index);
459
460        // Copy some stuff out of the struct layout to avoid borrowing issues.
461        let struct_size = struct_layout.size;
462        let struct_align = struct_layout.align;
463        let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().copied().collect();
464        assert_eq!(field_vals.len(), field_offsets.len());
465
466        let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));
467
468        let struct_ref = emit_gc_raw_alloc(
469            func_env,
470            builder,
471            VMGcKind::StructRef,
472            interned_type_index,
473            struct_size_val,
474            struct_align,
475        );
476
477        // Second, initialize each of the newly-allocated struct's fields.
478        //
479        // Note: we don't need to bounds-check the GC ref access here, since we
480        // trust the results of the allocation libcall.
481        let base = func_env.get_gc_heap_base(builder);
482        let extended_struct_ref =
483            uextend_i32_to_pointer_type(builder, func_env.pointer_type(), struct_ref);
484        let raw_ptr_to_struct = builder.ins().iadd(base, extended_struct_ref);
485        initialize_struct_fields(
486            func_env,
487            builder,
488            interned_type_index,
489            raw_ptr_to_struct,
490            field_vals,
491            |func_env, builder, ty, field_addr, val| {
492                self.init_field(func_env, builder, field_addr, ty, val)
493            },
494        )?;
495
496        Ok(struct_ref)
497    }
498
499    fn alloc_exn(
500        &mut self,
501        func_env: &mut FuncEnvironment<'_>,
502        builder: &mut FunctionBuilder<'_>,
503        tag_index: TagIndex,
504        field_vals: &[ir::Value],
505        instance_id: ir::Value,
506        tag: ir::Value,
507    ) -> WasmResult<ir::Value> {
508        let interned_type_index = func_env.module.tags[tag_index]
509            .exception
510            .unwrap_module_type_index();
511        let exn_layout = func_env.struct_or_exn_layout(interned_type_index);
512
513        // Copy some stuff out of the exception layout to avoid borrowing issues.
514        let exn_size = exn_layout.size;
515        let exn_align = exn_layout.align;
516        let field_offsets: SmallVec<[_; 8]> = exn_layout.fields.iter().copied().collect();
517        assert_eq!(field_vals.len(), field_offsets.len());
518
519        let exn_size_val = builder.ins().iconst(ir::types::I32, i64::from(exn_size));
520
521        let exn_ref = emit_gc_raw_alloc(
522            func_env,
523            builder,
524            VMGcKind::ExnRef,
525            interned_type_index,
526            exn_size_val,
527            exn_align,
528        );
529
530        // Second, initialize each of the newly-allocated exception
531        // object's fields.
532        //
533        // Note: we don't need to bounds-check the GC ref access here, since we
534        // trust the results of the allocation libcall.
535        let base = func_env.get_gc_heap_base(builder);
536        let extended_exn_ref =
537            uextend_i32_to_pointer_type(builder, func_env.pointer_type(), exn_ref);
538        let raw_ptr_to_exn = builder.ins().iadd(base, extended_exn_ref);
539        initialize_struct_fields(
540            func_env,
541            builder,
542            interned_type_index,
543            raw_ptr_to_exn,
544            field_vals,
545            |func_env, builder, ty, field_addr, val| {
546                self.init_field(func_env, builder, field_addr, ty, val)
547            },
548        )?;
549
550        // Finally, initialize the tag fields.
551        let instance_id_addr = builder
552            .ins()
553            .iadd_imm(raw_ptr_to_exn, i64::from(EXCEPTION_TAG_INSTANCE_OFFSET));
554        self.init_field(
555            func_env,
556            builder,
557            instance_id_addr,
558            WasmStorageType::Val(WasmValType::I32),
559            instance_id,
560        )?;
561        let tag_addr = builder
562            .ins()
563            .iadd_imm(raw_ptr_to_exn, i64::from(EXCEPTION_TAG_DEFINED_OFFSET));
564        self.init_field(
565            func_env,
566            builder,
567            tag_addr,
568            WasmStorageType::Val(WasmValType::I32),
569            tag,
570        )?;
571
572        Ok(exn_ref)
573    }
574
575    fn translate_read_gc_reference(
576        &mut self,
577        func_env: &mut FuncEnvironment<'_>,
578        builder: &mut FunctionBuilder,
579        ty: WasmRefType,
580        src: ir::Value,
581        flags: ir::MemFlags,
582    ) -> WasmResult<ir::Value> {
583        log::trace!("translate_read_gc_reference({ty:?}, {src:?}, {flags:?})");
584
585        assert!(ty.is_vmgcref_type());
586
587        let (reference_type, needs_stack_map) = func_env.reference_type(ty.heap_type);
588        debug_assert!(needs_stack_map);
589
590        // Special case for references to uninhabited bottom types: the
591        // reference must either be nullable and we can just eagerly return
592        // null, or we are in dynamically unreachable code and should just trap.
593        if let WasmHeapType::None = ty.heap_type {
594            let null = builder.ins().iconst(reference_type, 0);
595
596            // If the `flags` can trap, then we need to do an actual load. We
597            // might be relying on, e.g., this load trapping to raise a
598            // out-of-bounds-table-index trap, rather than successfully loading
599            // a null `noneref`.
600            //
601            // That said, while we will do the load, we won't use the loaded
602            // value, and will still use our null constant below. This will
603            // avoid an unnecessary load dependency, slightly improving the code
604            // we ultimately emit. This probably doesn't matter, but it is easy
605            // to do and can only improve things, so we do it.
606            if flags.trap_code().is_some() {
607                let _ = builder.ins().load(reference_type, flags, src, 0);
608            }
609
610            if !ty.nullable {
611                // NB: Don't use an unconditional trap instruction, since that
612                // is a block terminator, and we still need to integrate with
613                // the rest of the surrounding code.
614                let zero = builder.ins().iconst(ir::types::I32, 0);
615                builder.ins().trapz(zero, TRAP_INTERNAL_ASSERT);
616            }
617
618            return Ok(null);
619        };
620
621        // Special case for `i31` references: they don't need barriers.
622        if let WasmHeapType::I31 = ty.heap_type {
623            return unbarriered_load_gc_ref(builder, ty.heap_type, src, flags);
624        }
625
626        // Our read barrier for GC references is roughly equivalent to the
627        // following pseudo-CLIF:
628        //
629        // ```
630        // current_block:
631        //     ...
632        //     let gc_ref = load src
633        //     let gc_ref_is_null = is_null gc_ref
634        //     let gc_ref_is_i31 = ...
635        //     let gc_ref_is_null_or_i31 = bor gc_ref_is_null, gc_ref_is_i31
636        //     brif gc_ref_is_null_or_i31, continue_block, non_null_gc_ref_block
637        //
638        // non_null_gc_ref_block:
639        //     let reserved = load reserved bits from gc_ref's header
640        //     let in_set_bit = iconst OVER_APPROX_SET_BIT
641        //     let in_set = band reserved, in_set_bit
642        //     br_if in_set, continue_block, insert_block
643        //
644        // insert_block:
645        //     let next = load over-approximated-stack-roots head from DRC heap
646        //     store gc_ref to over-approximated-stack-roots head in DRC heap
647        //     store next to gc_ref's header's next_over_approximated_stack_root field
648        //     let new_reserved = bor reserved, in_set_bit
649        //     store new_reserved to gc_ref's headers reserved bits
650        //     inc_ref(gc_ref)
651        //     jump continue_block
652        //
653        // continue_block:
654        //     ...
655        // ```
656        //
657        // This ensures that all GC references entering the Wasm stack are in
658        // the over-approximated-stack-roots list.
659
660        let current_block = builder.current_block().unwrap();
661        let non_null_gc_ref_block = builder.create_block();
662        let insert_block = builder.create_block();
663        let continue_block = builder.create_block();
664
665        builder.ensure_inserted_block();
666        builder.insert_block_after(non_null_gc_ref_block, current_block);
667        builder.insert_block_after(insert_block, non_null_gc_ref_block);
668        builder.insert_block_after(continue_block, insert_block);
669
670        log::trace!("DRC read barrier: load the gc reference and check for null or i31");
671        let gc_ref = unbarriered_load_gc_ref(builder, ty.heap_type, src, flags)?;
672        let gc_ref_is_null_or_i31 = func_env.gc_ref_is_null_or_i31(builder, ty, gc_ref);
673        builder.ins().brif(
674            gc_ref_is_null_or_i31,
675            continue_block,
676            &[],
677            non_null_gc_ref_block,
678            &[],
679        );
680
681        // Block for when the GC reference is not null and is not an `i31ref`.
682        //
683        // Tests whether the object is already in the
684        // over-approximated-stack-roots list or not.
685        builder.switch_to_block(non_null_gc_ref_block);
686        builder.seal_block(non_null_gc_ref_block);
687        log::trace!(
688            "DRC read barrier: check whether this object is already in the \
689             over-approximated-stack-roots list"
690        );
691        let ptr = func_env.prepare_gc_ref_access(
692            builder,
693            gc_ref,
694            BoundsCheck::StaticOffset {
695                offset: func_env.offsets.vm_gc_header_reserved_bits(),
696                access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),
697            },
698        );
699        let reserved = builder
700            .ins()
701            .load(ir::types::I32, ir::MemFlags::trusted(), ptr, 0);
702        let in_set_bit = builder.ins().iconst(
703            ir::types::I32,
704            i64::from(wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT),
705        );
706        let in_set = builder.ins().band(reserved, in_set_bit);
707        builder
708            .ins()
709            .brif(in_set, continue_block, &[], insert_block, &[]);
710
711        // Block for when the object needs to be inserted into the
712        // over-approximated-stack-roots list.
713        builder.switch_to_block(insert_block);
714        builder.seal_block(insert_block);
715        log::trace!(
716            "DRC read barrier: push the object onto the over-approximated-stack-roots list"
717        );
718        self.push_onto_over_approximated_stack_roots(func_env, builder, gc_ref, reserved);
719        builder.ins().jump(continue_block, &[]);
720
721        // Join point after we're done with the GC barrier.
722        builder.switch_to_block(continue_block);
723        builder.seal_block(continue_block);
724        log::trace!("translate_read_gc_reference(..) -> {gc_ref:?}");
725        Ok(gc_ref)
726    }
727
728    fn translate_write_gc_reference(
729        &mut self,
730        func_env: &mut FuncEnvironment<'_>,
731        builder: &mut FunctionBuilder,
732        ty: WasmRefType,
733        dst: ir::Value,
734        new_val: ir::Value,
735        flags: ir::MemFlags,
736    ) -> WasmResult<()> {
737        assert!(ty.is_vmgcref_type());
738
739        let (ref_ty, needs_stack_map) = func_env.reference_type(ty.heap_type);
740        debug_assert!(needs_stack_map);
741
742        // Special case for references to uninhabited bottom types: either the
743        // reference is nullable and we can just eagerly store null into `dst`
744        // or we are in unreachable code and should just trap.
745        if let WasmHeapType::None = ty.heap_type {
746            if ty.nullable {
747                let null = builder.ins().iconst(ref_ty, 0);
748                builder.ins().store(flags, null, dst, 0);
749            } else {
750                // NB: Don't use an unconditional trap instruction, since that
751                // is a block terminator, and we still need to integrate with
752                // the rest of the surrounding code.
753                let zero = builder.ins().iconst(ir::types::I32, 0);
754                builder.ins().trapz(zero, TRAP_INTERNAL_ASSERT);
755            }
756            return Ok(());
757        };
758
759        // Special case for `i31` references: they don't need barriers.
760        if let WasmHeapType::I31 = ty.heap_type {
761            return unbarriered_store_gc_ref(builder, ty.heap_type, dst, new_val, flags);
762        }
763
764        // Our write barrier for GC references being copied out of the stack and
765        // written into a table/global/etc... is roughly equivalent to the
766        // following pseudo-CLIF:
767        //
768        // ```
769        // current_block:
770        //     ...
771        //     let old_val = *dst
772        //     let new_val_is_null = ref.null new_val
773        //     let new_val_is_i31 = ...
774        //     let new_val_is_null_or_i31 = bor new_val_is_null, new_val_is_i31
775        //     brif new_val_is_null_or_i31, check_old_val_block, inc_ref_block
776        //
777        // inc_ref_block:
778        //     let ref_count = load new_val.ref_count
779        //     let new_ref_count = iadd_imm ref_count, 1
780        //     store new_val.ref_count, new_ref_count
781        //     jump check_old_val_block
782        //
783        // check_old_val_block:
784        //     store dst, new_val
785        //     let old_val_is_null = ref.null old_val
786        //     let old_val_is_i31 = ...
787        //     let old_val_is_null_or_i31 = bor old_val_is_null, old_val_is_i31
788        //     brif old_val_is_null_or_i31, continue_block, dec_ref_block
789        //
790        // dec_ref_block:
791        //     let ref_count = load old_val.ref_count
792        //     let new_ref_count = isub_imm ref_count, 1
793        //     let old_val_needs_drop = icmp_imm eq new_ref_count, 0
794        //     brif old_val_needs_drop, drop_old_val_block, store_dec_ref_block
795        //
796        // cold drop_old_val_block:
797        //     call drop_gc_ref(old_val)
798        //     jump continue_block
799        //
800        // store_dec_ref_block:
801        //     store old_val.ref_count, new_ref_count
802        //     jump continue_block
803        //
804        // continue_block:
805        //     ...
806        // ```
807        //
808        // This write barrier is responsible for ensuring that:
809        //
810        // 1. The new value's ref count is incremented now that the table is
811        //    holding onto it.
812        //
813        // 2. The old value's ref count is decremented, and that it is dropped
814        //    if the ref count reaches zero.
815        //
816        // We must do the increment before the decrement. If we did it in the
817        // other order, then when `*dst == new_val`, we could confuse ourselves
818        // by observing a zero ref count after the decrement but before it would
819        // become non-zero again with the subsequent increment.
820        //
821        // Additionally, we take care that we don't ever call out-out-of-line to
822        // drop the old value until all the new value has been written into
823        // `dst` and its reference count has been updated. This makes sure that
824        // host code has a consistent view of the world.
825
826        let current_block = builder.current_block().unwrap();
827        let inc_ref_block = builder.create_block();
828        let check_old_val_block = builder.create_block();
829        let dec_ref_block = builder.create_block();
830        let drop_old_val_block = builder.create_block();
831        let store_dec_ref_block = builder.create_block();
832        let continue_block = builder.create_block();
833
834        builder.ensure_inserted_block();
835        builder.set_cold_block(drop_old_val_block);
836
837        builder.insert_block_after(inc_ref_block, current_block);
838        builder.insert_block_after(check_old_val_block, inc_ref_block);
839        builder.insert_block_after(dec_ref_block, check_old_val_block);
840        builder.insert_block_after(drop_old_val_block, dec_ref_block);
841        builder.insert_block_after(store_dec_ref_block, drop_old_val_block);
842        builder.insert_block_after(continue_block, store_dec_ref_block);
843
844        // Load the old value and then check whether the new value is non-null
845        // and non-i31.
846        log::trace!("DRC write barrier: load old ref; check if new ref is null or i31");
847        let old_val = unbarriered_load_gc_ref(builder, ty.heap_type, dst, flags)?;
848        let new_val_is_null_or_i31 = func_env.gc_ref_is_null_or_i31(builder, ty, new_val);
849        builder.ins().brif(
850            new_val_is_null_or_i31,
851            check_old_val_block,
852            &[],
853            inc_ref_block,
854            &[],
855        );
856
857        // Block to increment the ref count of the new value when it is non-null
858        // and non-i31.
859        builder.switch_to_block(inc_ref_block);
860        log::trace!("DRC write barrier: increment new ref's ref count");
861        builder.seal_block(inc_ref_block);
862        self.mutate_ref_count(func_env, builder, new_val, 1);
863        builder.ins().jump(check_old_val_block, &[]);
864
865        // Block to store the new value into `dst` and then check whether the
866        // old value is non-null and non-i31 and therefore needs its ref count
867        // decremented.
868        builder.switch_to_block(check_old_val_block);
869        builder.seal_block(check_old_val_block);
870        log::trace!("DRC write barrier: store new ref into field; check if old ref is null or i31");
871        unbarriered_store_gc_ref(builder, ty.heap_type, dst, new_val, flags)?;
872        let old_val_is_null_or_i31 = func_env.gc_ref_is_null_or_i31(builder, ty, old_val);
873        builder.ins().brif(
874            old_val_is_null_or_i31,
875            continue_block,
876            &[],
877            dec_ref_block,
878            &[],
879        );
880
881        // Block to decrement the ref count of the old value when it is non-null
882        // and non-i31.
883        builder.switch_to_block(dec_ref_block);
884        builder.seal_block(dec_ref_block);
885        log::trace!(
886            "DRC write barrier: decrement old ref's ref count and check for zero ref count"
887        );
888        let ref_count = self.load_ref_count(func_env, builder, old_val);
889        let new_ref_count = builder.ins().iadd_imm(ref_count, -1);
890        let old_val_needs_drop = builder.ins().icmp_imm(IntCC::Equal, new_ref_count, 0);
891        builder.ins().brif(
892            old_val_needs_drop,
893            drop_old_val_block,
894            &[],
895            store_dec_ref_block,
896            &[],
897        );
898
899        // Block to call out-of-line to drop a GC reference when its ref count
900        // reaches zero.
901        //
902        // Note that this libcall does its own dec-ref operation, so we only
903        // actually store `new_ref_count` back to the `old_val` object when
904        // `new_ref_count != 0`.
905        builder.switch_to_block(drop_old_val_block);
906        builder.seal_block(drop_old_val_block);
907        log::trace!("DRC write barrier: drop old ref with a ref count of zero");
908        let drop_gc_ref_libcall = func_env.builtin_functions.drop_gc_ref(builder.func);
909        let vmctx = func_env.vmctx_val(&mut builder.cursor());
910        builder.ins().call(drop_gc_ref_libcall, &[vmctx, old_val]);
911        builder.ins().jump(continue_block, &[]);
912
913        // Block to store the new ref count back to `old_val` for when
914        // `new_ref_count != 0`, as explained above.
915        builder.switch_to_block(store_dec_ref_block);
916        builder.seal_block(store_dec_ref_block);
917        log::trace!("DRC write barrier: store decremented ref count into old ref");
918        self.store_ref_count(func_env, builder, old_val, new_ref_count);
919        builder.ins().jump(continue_block, &[]);
920
921        // Join point after we're done with the GC barrier.
922        builder.switch_to_block(continue_block);
923        builder.seal_block(continue_block);
924        log::trace!("DRC write barrier: finished");
925        Ok(())
926    }
927}