Skip to content

Commit 2dd03bc

Browse files
authored
Implement the semi-space copying garbage collector (#13107)
* Implement the semi-space copying garbage collector This is a classic semi-space copying collector that uses bump allocation and Cheney-style worklists to avoid an explicit stack for grey (relocated but not yet scanned) objects outside the GC heap. Forwarding "pointers" (really `VMGcRef` indices) are stored inline in objects in the old semi-space, which again avoids explicit data structures outside of the GC heap. Furthermore, an intrusive linked-list of all `externref`s is maintained to allow for efficiently sweeping their associated host data after collection (and, once again, avoiding additional data structures outside the GC heap). Allocation in compiled Wasm code always happens by calling out to the `gc_alloc_raw` libcall currently. This is expected to become inline bump allocation, very similar to the null collector, in a follow-up commit shortly after this one. It is delayed to make review easier. Collection is not incremental (in the Wasmtime sense, not the GC literature sense) yet and the `CopyingCollection::collect_increment` implementation only has a single increment. This is delayed to make review easier and is also expected to come shortly in follow-up commits. I've also added new disas tests for the copying collector and also enabled running wast tests which need a GC heap with the copying collector. Finally, fuzz config generation can now generate configurations that enable the copying collector. * fix compilation error, add a couple more asserts * Fix bug where semi-spaces' usable sizes were not equal * Ensure that we always poison new gc heap capacity in `replace_memory` * remove dead method * remove unused import * review feedback and little clean ups * fix poisoning for new growth * Add additional copying collector and GC object tests * fix compilation after rebase * actually fix compilation after rebase * Update disas test output * Fix grow-vs-collect logic under GC zeal * remove warning in no-gc build
1 parent 24938c4 commit 2dd03bc

73 files changed

Lines changed: 4457 additions & 263 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ rustix = { workspace = true, features = ["mm", "process"] }
9797

9898
[dev-dependencies]
9999
# depend again on wasmtime to activate its default features for tests
100-
wasmtime = { workspace = true, features = ['default', 'anyhow', 'winch', 'pulley', 'all-arch', 'call-hook', 'memory-protection-keys', 'component-model-async', 'component-model-async-bytes'] }
100+
wasmtime = { workspace = true, features = ['default', 'anyhow', 'winch', 'pulley', 'all-arch', 'call-hook', 'memory-protection-keys', 'component-model-async', 'component-model-async-bytes', 'gc-copying'] }
101101
env_logger = { workspace = true }
102102
log = { workspace = true }
103103
filecheck = { workspace = true }

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub fn gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcC
7272
}
7373

7474
#[cfg_attr(
75-
not(feature = "gc-drc"),
75+
not(any(feature = "gc-drc", feature = "gc-copying")),
7676
expect(dead_code, reason = "easier to define")
7777
)]
7878
fn unbarriered_load_gc_ref(
@@ -90,7 +90,7 @@ fn unbarriered_load_gc_ref(
9090
}
9191

9292
#[cfg_attr(
93-
not(any(feature = "gc-drc", feature = "gc-null")),
93+
not(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying")),
9494
expect(dead_code, reason = "easier to define")
9595
)]
9696
fn unbarriered_store_gc_ref(
@@ -105,6 +105,37 @@ fn unbarriered_store_gc_ref(
105105
Ok(())
106106
}
107107

108+
/// Emit CLIF to call the `gc_raw_alloc` libcall.
109+
#[cfg(any(feature = "gc-drc", feature = "gc-copying"))]
110+
fn emit_gc_raw_alloc(
111+
func_env: &mut FuncEnvironment<'_>,
112+
builder: &mut FunctionBuilder<'_>,
113+
kind: VMGcKind,
114+
ty: ModuleInternedTypeIndex,
115+
size: ir::Value,
116+
align: u32,
117+
) -> ir::Value {
118+
let gc_alloc_raw_builtin = func_env.builtin_functions.gc_alloc_raw(builder.func);
119+
let vmctx = func_env.vmctx_val(&mut builder.cursor());
120+
121+
let kind = builder
122+
.ins()
123+
.iconst(ir::types::I32, i64::from(kind.as_u32()));
124+
125+
let ty = func_env.module_interned_to_shared_ty(&mut builder.cursor(), ty);
126+
127+
assert!(align.is_power_of_two());
128+
let align = builder.ins().iconst(ir::types::I32, i64::from(align));
129+
130+
let call_inst = builder
131+
.ins()
132+
.call(gc_alloc_raw_builtin, &[vmctx, kind, ty, size, align]);
133+
134+
let gc_ref = builder.func.dfg.first_result(call_inst);
135+
builder.declare_value_needs_stack_map(gc_ref);
136+
gc_ref
137+
}
138+
108139
/// Emit inline CLIF code that asserts an object's `VMGcKind` matches the
109140
/// expected kind. Only emits code when `cfg(gc_zeal)` is enabled.
110141
///
@@ -645,7 +676,7 @@ pub fn translate_array_new_fixed(
645676
impl ArrayInit<'_> {
646677
/// Get the length (as an `i32`-typed `ir::Value`) of these array elements.
647678
#[cfg_attr(
648-
not(any(feature = "gc-drc", feature = "gc-null")),
679+
not(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying")),
649680
expect(dead_code, reason = "easier to define")
650681
)]
651682
fn len(self, pos: &mut FuncCursor) -> ir::Value {
@@ -660,7 +691,7 @@ impl ArrayInit<'_> {
660691

661692
/// Initialize a newly-allocated array's elements.
662693
#[cfg_attr(
663-
not(any(feature = "gc-drc", feature = "gc-null")),
694+
not(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying")),
664695
expect(dead_code, reason = "easier to define")
665696
)]
666697
fn initialize(
@@ -1333,7 +1364,7 @@ fn uextend_i32_to_pointer_type(
13331364
///
13341365
/// Traps if the size overflows.
13351366
#[cfg_attr(
1336-
not(any(feature = "gc-drc", feature = "gc-null")),
1367+
not(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying")),
13371368
expect(dead_code, reason = "easier to define")
13381369
)]
13391370
fn emit_array_size(
@@ -1381,7 +1412,7 @@ fn emit_array_size(
13811412
/// Common helper for struct-field initialization that can be reused across
13821413
/// collectors.
13831414
#[cfg_attr(
1384-
not(any(feature = "gc-drc", feature = "gc-null")),
1415+
not(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying")),
13851416
expect(dead_code, reason = "easier to define")
13861417
)]
13871418
fn initialize_struct_fields(
@@ -1483,7 +1514,7 @@ impl FuncEnvironment<'_> {
14831514
}
14841515

14851516
/// Get the GC heap's base.
1486-
#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
1517+
#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
14871518
fn get_gc_heap_base(&mut self, builder: &mut FunctionBuilder) -> ir::Value {
14881519
let global = self.get_gc_heap_base_global(&mut builder.func);
14891520
builder.ins().global_value(self.pointer_type(), global)

0 commit comments

Comments
 (0)