Skip to content

Commit cecc784

Browse files
committed
Do not reorder/pack struct fields
We were not considering that when a type T is a subtype of type U, that all of the fields that T and U share must be at the same offsets. Our reordering/packing algorithm did not consider this. So we stop reordering struct fields for now. This also means that we cannot guarantee that all GC refs in an object are contiguously packed anymore. Instead, we maintain a map keyed by `VMSharedTypeIndex`, whose value is all the offsets of outgoing GC references for instances of that type. Tracing looks up the current object's type in this map, and then extracts each of the GC edges. This new tracing scheme means that we no longer need to store the number of outgoing edges in the GC object header, and can put the object size in there again, reducing the DRC header from 24 to 16 bytes. Fixes bytecodealliance#10459 Fixes bytecodealliance#10464
1 parent 3f53021 commit cecc784

36 files changed

+650
-554
lines changed

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,10 @@ impl DrcCompiler {
260260
}
261261

262262
/// Emit CLIF to call the `gc_raw_alloc` libcall.
263-
///
264-
/// It is the caller's responsibility to ensure that `num_gc_refs` fits within
265-
/// the `VMGcKind`'s unused bits.
266263
fn emit_gc_raw_alloc(
267264
func_env: &mut FuncEnvironment<'_>,
268265
builder: &mut FunctionBuilder<'_>,
269266
kind: VMGcKind,
270-
num_gc_refs: ir::Value,
271267
ty: ModuleInternedTypeIndex,
272268
size: ir::Value,
273269
align: u32,
@@ -278,17 +274,15 @@ fn emit_gc_raw_alloc(
278274
let kind = builder
279275
.ins()
280276
.iconst(ir::types::I32, i64::from(kind.as_u32()));
281-
let kind_and_reserved = builder.ins().bor(kind, num_gc_refs);
282277

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

285280
assert!(align.is_power_of_two());
286281
let align = builder.ins().iconst(ir::types::I32, i64::from(align));
287282

288-
let call_inst = builder.ins().call(
289-
gc_alloc_raw_builtin,
290-
&[vmctx, kind_and_reserved, ty, size, align],
291-
);
283+
let call_inst = builder
284+
.ins()
285+
.call(gc_alloc_raw_builtin, &[vmctx, kind, ty, size, align]);
292286

293287
let gc_ref = builder.func.dfg.first_result(call_inst);
294288
builder.declare_value_needs_stack_map(gc_ref);
@@ -321,19 +315,13 @@ impl GcCompiler for DrcCompiler {
321315
// size, and length.
322316
let len = init.len(&mut builder.cursor());
323317
let size = emit_array_size(func_env, builder, &array_layout, len);
324-
let num_gc_refs = if array_layout.elems_are_gc_refs {
325-
len
326-
} else {
327-
builder.ins().iconst(ir::types::I32, 0)
328-
};
329318

330319
// Second, now that we have the array object's total size, call the
331320
// `gc_alloc_raw` builtin libcall to allocate the array.
332321
let array_ref = emit_gc_raw_alloc(
333322
func_env,
334323
builder,
335324
VMGcKind::ArrayRef,
336-
num_gc_refs,
337325
interned_type_index,
338326
size,
339327
align,
@@ -394,15 +382,10 @@ impl GcCompiler for DrcCompiler {
394382
assert_eq!(VMGcKind::UNUSED_MASK & struct_size, struct_size);
395383
let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));
396384

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-
401385
let struct_ref = emit_gc_raw_alloc(
402386
func_env,
403387
builder,
404388
VMGcKind::StructRef,
405-
num_gc_refs,
406389
interned_type_index,
407390
struct_size_val,
408391
struct_align,

crates/environ/src/gc.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ fn common_array_layout(
116116

117117
/// Common code to define a GC struct's layout, given the size and alignment of
118118
/// the collector's GC header and its expected offset of the array length field.
119-
#[cfg(feature = "gc-null")]
119+
#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
120120
fn common_struct_layout(
121121
ty: &WasmStructType,
122122
header_size: u32,
@@ -127,11 +127,12 @@ fn common_struct_layout(
127127

128128
// Process each field, aligning it to its natural alignment.
129129
//
130-
// We don't try and do any fancy field reordering to minimize padding
131-
// (yet?) because (a) the toolchain probably already did that and (b)
132-
// we're just doing the simple thing first. We can come back and improve
133-
// things here if we find that (a) isn't actually holding true in
134-
// practice.
130+
// We don't try and do any fancy field reordering to minimize padding (yet?)
131+
// because (a) the toolchain probably already did that and (b) we're just
132+
// doing the simple thing first, and (c) this is tricky in the presence of
133+
// subtyping where we need a subtype's fields to be assigned the same
134+
// offsets as its supertype's fields. We can come back and improve things
135+
// here if we find that (a) isn't actually holding true in practice.
135136
let mut size = header_size;
136137
let mut align = header_align;
137138

crates/environ/src/gc/drc.rs

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

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

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

2827
fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout {
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-
}
28+
common_struct_layout(ty, HEADER_SIZE, HEADER_ALIGN)
7829
}
7930
}

crates/wasmtime/src/runtime/store.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,7 @@ impl StoreOpaque {
14231423
);
14241424
let (index, heap) = engine
14251425
.allocator()
1426-
.allocate_gc_heap(&**engine.gc_runtime()?)?;
1426+
.allocate_gc_heap(engine, &**engine.gc_runtime()?)?;
14271427
Ok(GcStore::new(index, heap))
14281428
}
14291429

crates/wasmtime/src/runtime/trampoline/memory.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ unsafe impl InstanceAllocatorImpl for SingleMemoryInstance<'_> {
244244
#[cfg(feature = "gc")]
245245
fn allocate_gc_heap(
246246
&self,
247+
_engine: &crate::Engine,
247248
_gc_runtime: &dyn crate::runtime::vm::GcRuntime,
248249
) -> Result<(
249250
crate::runtime::vm::GcHeapAllocationIndex,

0 commit comments

Comments
 (0)