Skip to content

Commit 4539476

Browse files
committed
Transitively dec-ref objects in the DRC collector
Previously, the DRC collector only performed shallow reference counting where if an object's ref count was decremented and reached zero, then we would deallocate that object, but we would not decrement the reference counts of any other object that was referenced by it. This commit changes that, so that now we will decrement the reference counts of other objects referenced by an object that is being deallocated. This requires some method of tracing an object's outgoing edges. To avoid needing to keep around and dispatch upon type-specific information about which struct fields are GC references, for example, we instead pack all GC references at the start of the object, just after its header. We additionally keep a count of how many GC references an object has in its header. This allows us to create a slice of any object's GC references, and we can uniformly trace any GC object's outgoing edges. We don't need to reflect on the GC object's actual type. A final detail: the DRC collector was previously storing the object size in the common GC header's reserved bits. It now stores the number of GC refs there instead, and the object size is stored in its own field in the DRC-specific header, next to the ref count. This is nice because, since we are making the DRC header larger anyways, it raises the DRC collector's implementation limit on object size from `u27::MAX` to `u32::MAX`. Fixes bytecodealliance#9701
1 parent ad21d95 commit 4539476

File tree

9 files changed

+399
-63
lines changed

9 files changed

+399
-63
lines changed

crates/cranelift/src/gc/enabled.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ pub fn translate_struct_get(
316316
let struct_size = struct_layout.size;
317317
let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));
318318

319-
let field_offset = struct_layout.fields[field_index];
319+
let field_offset = struct_layout.fields[field_index].offset;
320320
let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];
321321
let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);
322322
assert!(field_offset + field_size <= struct_size);
@@ -361,7 +361,7 @@ pub fn translate_struct_set(
361361
let struct_size = struct_layout.size;
362362
let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));
363363

364-
let field_offset = struct_layout.fields[field_index];
364+
let field_offset = struct_layout.fields[field_index].offset;
365365
let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];
366366
let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);
367367
assert!(field_offset + field_size <= struct_size);
@@ -1194,7 +1194,7 @@ fn initialize_struct_fields(
11941194
) -> WasmResult<()> {
11951195
let struct_layout = func_env.struct_layout(struct_ty);
11961196
let struct_size = struct_layout.size;
1197-
let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().copied().collect();
1197+
let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().map(|f| f.offset).collect();
11981198
assert_eq!(field_offsets.len(), field_values.len());
11991199

12001200
assert!(!func_env.types[struct_ty].composite_type.shared);

crates/cranelift/src/gc/enabled/drc.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,13 @@ impl DrcCompiler {
261261

262262
/// Emit CLIF to call the `gc_raw_alloc` libcall.
263263
///
264-
/// It is the caller's responsibility to ensure that `size` fits within the
265-
/// `VMGcKind`'s unused bits.
264+
/// It is the caller's responsibility to ensure that `num_gc_refs` fits within
265+
/// the `VMGcKind`'s unused bits.
266266
fn emit_gc_raw_alloc(
267267
func_env: &mut FuncEnvironment<'_>,
268268
builder: &mut FunctionBuilder<'_>,
269269
kind: VMGcKind,
270+
num_gc_refs: ir::Value,
270271
ty: ModuleInternedTypeIndex,
271272
size: ir::Value,
272273
align: u32,
@@ -277,15 +278,17 @@ fn emit_gc_raw_alloc(
277278
let kind = builder
278279
.ins()
279280
.iconst(ir::types::I32, i64::from(kind.as_u32()));
281+
let kind_and_reserved = builder.ins().bor(kind, num_gc_refs);
280282

281283
let ty = builder.ins().iconst(ir::types::I32, i64::from(ty.as_u32()));
282284

283285
assert!(align.is_power_of_two());
284286
let align = builder.ins().iconst(ir::types::I32, i64::from(align));
285287

286-
let call_inst = builder
287-
.ins()
288-
.call(gc_alloc_raw_builtin, &[vmctx, kind, ty, size, align]);
288+
let call_inst = builder.ins().call(
289+
gc_alloc_raw_builtin,
290+
&[vmctx, kind_and_reserved, ty, size, align],
291+
);
289292

290293
let gc_ref = builder.func.dfg.first_result(call_inst);
291294
let gc_ref = builder.ins().ireduce(ir::types::I32, gc_ref);
@@ -318,13 +321,19 @@ impl GcCompiler for DrcCompiler {
318321
// First, compute the array's total size from its base size, element
319322
// size, and length.
320323
let size = emit_array_size(func_env, builder, &array_layout, init);
324+
let num_gc_refs = if array_layout.elems_are_gc_refs {
325+
size
326+
} else {
327+
builder.ins().iconst(ir::types::I32, 0)
328+
};
321329

322330
// Second, now that we have the array object's total size, call the
323331
// `gc_alloc_raw` builtin libcall to allocate the array.
324332
let array_ref = emit_gc_raw_alloc(
325333
func_env,
326334
builder,
327335
VMGcKind::ArrayRef,
336+
num_gc_refs,
328337
interned_type_index,
329338
size,
330339
align,
@@ -385,10 +394,15 @@ impl GcCompiler for DrcCompiler {
385394
assert_eq!(VMGcKind::UNUSED_MASK & struct_size, struct_size);
386395
let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));
387396

397+
let num_gc_refs = struct_layout.fields.iter().filter(|f| f.is_gc_ref).count();
398+
let num_gc_refs = u32::try_from(num_gc_refs).unwrap();
399+
let num_gc_refs = builder.ins().iconst(ir::types::I32, i64::from(num_gc_refs));
400+
388401
let struct_ref = emit_gc_raw_alloc(
389402
func_env,
390403
builder,
391404
VMGcKind::StructRef,
405+
num_gc_refs,
392406
interned_type_index,
393407
struct_size_val,
394408
struct_align,

crates/environ/src/gc.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,29 +81,42 @@ fn common_array_layout(
8181
header_align: u32,
8282
expected_array_length_offset: u32,
8383
) -> GcArrayLayout {
84+
use core::mem;
85+
8486
assert!(header_size >= crate::VM_GC_HEADER_SIZE);
8587
assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
8688

8789
let mut size = header_size;
8890
let mut align = header_align;
8991

90-
let length_field_offset = field(&mut size, &mut align, 4);
92+
let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();
93+
let length_field_offset = field(&mut size, &mut align, length_field_size);
9194
assert_eq!(length_field_offset, expected_array_length_offset);
9295

9396
let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
9497
let elems_offset = align_up(&mut size, &mut align, elem_size);
9598
assert_eq!(elems_offset, size);
9699

100+
let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();
101+
if elems_are_gc_refs {
102+
debug_assert_eq!(
103+
length_field_offset + length_field_size,
104+
elems_offset,
105+
"DRC collector relies on GC ref elements appearing directly after the length field, without any padding",
106+
);
107+
}
108+
97109
GcArrayLayout {
98110
base_size: size,
99111
align,
100112
elem_size,
113+
elems_are_gc_refs,
101114
}
102115
}
103116

104117
/// Common code to define a GC struct's layout, given the size and alignment of
105118
/// the collector's GC header and its expected offset of the array length field.
106-
#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
119+
#[cfg(feature = "gc-null")]
107120
fn common_struct_layout(
108121
ty: &WasmStructType,
109122
header_size: u32,
@@ -127,7 +140,9 @@ fn common_struct_layout(
127140
.iter()
128141
.map(|f| {
129142
let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
130-
field(&mut size, &mut align, field_size)
143+
let offset = field(&mut size, &mut align, field_size);
144+
let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
145+
GcStructLayoutField { offset, is_gc_ref }
131146
})
132147
.collect();
133148

@@ -241,6 +256,9 @@ pub struct GcArrayLayout {
241256

242257
/// The size and natural alignment of each element in this array.
243258
pub elem_size: u32,
259+
260+
/// Whether or not the elements of this array are GC references or not.
261+
pub elems_are_gc_refs: bool,
244262
}
245263

246264
impl GcArrayLayout {
@@ -283,9 +301,9 @@ pub struct GcStructLayout {
283301
/// The alignment (in bytes) of this struct.
284302
pub align: u32,
285303

286-
/// The fields of this struct. The `i`th entry is the `i`th struct field's
287-
/// offset (in bytes) in the struct.
288-
pub fields: Vec<u32>,
304+
/// The fields of this struct. The `i`th entry contains information about
305+
/// the `i`th struct field's layout.
306+
pub fields: Vec<GcStructLayoutField>,
289307
}
290308

291309
impl GcStructLayout {
@@ -297,6 +315,20 @@ impl GcStructLayout {
297315
}
298316
}
299317

318+
/// A field in a `GcStructLayout`.
319+
#[derive(Clone, Copy, Debug)]
320+
pub struct GcStructLayoutField {
321+
/// The offset (in bytes) of this field inside instances of this type.
322+
pub offset: u32,
323+
324+
/// Whether or not this field might contain a reference to another GC
325+
/// object.
326+
///
327+
/// Note: it is okay for this to be `false` for `i31ref`s, since they never
328+
/// actually reference another GC object.
329+
pub is_gc_ref: bool,
330+
}
331+
300332
/// The kind of an object in a GC heap.
301333
///
302334
/// Note that this type is accessed from Wasm JIT code.

crates/environ/src/gc/drc.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Layout of Wasm GC objects in the deferred reference-counting collector.
22
33
use super::*;
4+
use core::cmp;
45

56
/// The size of the `VMDrcHeader` header for GC objects.
6-
pub const HEADER_SIZE: u32 = 16;
7+
pub const HEADER_SIZE: u32 = 24;
78

89
/// The align of the `VMDrcHeader` header for GC objects.
910
pub const HEADER_ALIGN: u32 = 8;
@@ -25,6 +26,54 @@ impl GcTypeLayouts for DrcTypeLayouts {
2526
}
2627

2728
fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout {
28-
common_struct_layout(ty, HEADER_SIZE, HEADER_ALIGN)
29+
// Sort the struct fields into the order in which we will lay them out.
30+
//
31+
// NB: we put all GC refs first so that we can simply store the number
32+
// of GC refs in any object in the `VMDrcHeader` and then uniformly
33+
// trace all structs types.
34+
let mut fields: Vec<_> = ty.fields.iter().enumerate().collect();
35+
fields.sort_by_key(|(i, f)| {
36+
let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
37+
let size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
38+
(cmp::Reverse(is_gc_ref), cmp::Reverse(size), *i)
39+
});
40+
41+
// Compute the offset of each field as well as the size and alignment of
42+
// the whole struct.
43+
let mut size = HEADER_SIZE;
44+
let mut align = HEADER_ALIGN;
45+
let mut fields: Vec<_> = fields
46+
.into_iter()
47+
.map(|(i, f)| {
48+
let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
49+
let offset = field(&mut size, &mut align, field_size);
50+
let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
51+
(i, GcStructLayoutField { offset, is_gc_ref })
52+
})
53+
.collect();
54+
if let Some((_i, f)) = fields.get(0) {
55+
if f.is_gc_ref {
56+
debug_assert_eq!(
57+
f.offset, HEADER_SIZE,
58+
"GC refs should come directly after the header, without any padding",
59+
);
60+
}
61+
}
62+
63+
// Re-sort the fields into their definition (rather than layout) order
64+
// and throw away the definition index.
65+
fields.sort_by_key(|(i, _f)| *i);
66+
let fields: Vec<_> = fields.into_iter().map(|(_i, f)| f).collect();
67+
68+
// Ensure that the final size is a multiple of the alignment, for
69+
// simplicity.
70+
let align_size_to = align;
71+
align_up(&mut size, &mut align, align_size_to);
72+
73+
GcStructLayout {
74+
size,
75+
align,
76+
fields,
77+
}
2978
}
3079
}

crates/environ/src/types.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ impl WasmValType {
198198
/// Is this a type that is represented as a `VMGcRef` and is additionally
199199
/// not an `i31`?
200200
///
201-
/// That is, is this a a type that actually refers to an object allocated in
202-
/// a GC heap?
201+
/// That is, is this a type that actually refers to an object allocated in a
202+
/// GC heap?
203203
#[inline]
204204
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
205205
match self {
@@ -280,8 +280,8 @@ impl WasmRefType {
280280
/// Is this a type that is represented as a `VMGcRef` and is additionally
281281
/// not an `i31`?
282282
///
283-
/// That is, is this a a type that actually refers to an object allocated in
284-
/// a GC heap?
283+
/// That is, is this a type that actually refers to an object allocated in a
284+
/// GC heap?
285285
#[inline]
286286
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
287287
self.heap_type.is_vmgcref_type_and_not_i31()
@@ -545,8 +545,8 @@ impl WasmHeapType {
545545
/// Is this a type that is represented as a `VMGcRef` and is additionally
546546
/// not an `i31`?
547547
///
548-
/// That is, is this a a type that actually refers to an object allocated in
549-
/// a GC heap?
548+
/// That is, is this a type that actually refers to an object allocated in a
549+
/// GC heap?
550550
#[inline]
551551
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
552552
self.is_vmgcref_type() && *self != Self::I31
@@ -862,6 +862,20 @@ impl TypeTrace for WasmStorageType {
862862
}
863863
}
864864

865+
impl WasmStorageType {
866+
/// Is this a type that is represented as a `VMGcRef` and is additionally
867+
/// not an `i31`?
868+
///
869+
/// That is, is this a type that actually refers to an object allocated in a
870+
/// GC heap?
871+
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
872+
match self {
873+
WasmStorageType::I8 | WasmStorageType::I16 => false,
874+
WasmStorageType::Val(v) => v.is_vmgcref_type_and_not_i31(),
875+
}
876+
}
877+
}
878+
865879
/// The type of a struct field or array element.
866880
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
867881
pub struct WasmFieldType {

0 commit comments

Comments
 (0)