Skip to content

Commit ac4d11d

Browse files
committed
Reuse code for Wasm linear memories for GC heaps
Instead of bespoke code paths and structures for Wasm GC, this commit makes it so that we now reuse VM structures like `VMMemoryDefinition` and bounds-checking logic. Notably, we also reuse all the associated bounds-checking optimizations and, when possible, virtual-memory techniques to completely elide them. Furthermore, this commit adds support for growing GC heaps, reusing the machinery for growing memories, and makes it so that GC heaps always start out empty. This allows us to properly delay allocating the GC heap's storage until a GC object is actually allocated. Fixes bytecodealliance#9350
1 parent aa7ca6c commit ac4d11d

File tree

777 files changed

+6401
-5502
lines changed

Some content is hidden

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

777 files changed

+6401
-5502
lines changed

cranelift/codegen/src/isa/x64/inst/external.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ fn enc_gpr(gpr: &Gpr) -> u8 {
131131
if let Some(real) = gpr.to_reg().to_real_reg() {
132132
real.hw_enc()
133133
} else {
134-
unreachable!()
134+
0
135+
// unreachable!() TODO FITZGEN
135136
}
136137
}
137138

crates/cranelift/src/bounds_checks.rs

Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
//! !!! !!!
2020
//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2121
22-
use super::Reachability;
23-
use crate::func_environ::FuncEnvironment;
24-
use crate::translate::{HeapData, TargetEnvironment};
22+
use crate::{
23+
func_environ::FuncEnvironment,
24+
translate::{HeapData, TargetEnvironment},
25+
Reachability,
26+
};
2527
use cranelift_codegen::{
2628
cursor::{Cursor, FuncCursor},
2729
ir::{self, condcodes::IntCC, InstBuilder, RelSourceLoc},
@@ -31,26 +33,151 @@ use cranelift_frontend::FunctionBuilder;
3133
use wasmtime_environ::Unsigned;
3234
use Reachability::*;
3335

36+
/// The kind of bounds check to perform when accessing a Wasm linear memory or
37+
/// GC heap.
38+
///
39+
/// Prefer `BoundsCheck::*WholeObject` over `BoundsCheck::Field` when possible,
40+
/// as that approach allows the mid-end to deduplicate bounds checks across
41+
/// multiple accesses to the same GC object.
42+
#[derive(Debug)]
43+
pub enum BoundsCheck {
44+
/// Check that this one access in particular is in bounds:
45+
///
46+
/// ```ignore
47+
/// index + offset + access_size <= gc_heap_bound
48+
/// ```
49+
Field { offset: u32, access_size: u8 },
50+
51+
/// Assuming the precondition `offset + access_size <= object_size`, check
52+
/// that this whole object is in bounds:
53+
///
54+
/// ```ignore
55+
/// index + object_size <= bound
56+
/// ```
57+
StaticWholeObject {
58+
offset: u32,
59+
access_size: u8,
60+
object_size: u32,
61+
},
62+
63+
/// Like `StaticWholeObject` but with dynamic offset and object size.
64+
///
65+
/// It is *your* responsibility to ensure that the `offset + access_size <=
66+
/// object_size` precondition holds.
67+
DynamicWholeObject {
68+
offset: ir::Value,
69+
object_size: ir::Value,
70+
},
71+
}
72+
3473
/// Helper used to emit bounds checks (as necessary) and compute the native
3574
/// address of a heap access.
3675
///
3776
/// Returns the `ir::Value` holding the native address of the heap access, or
38-
/// `None` if the heap access will unconditionally trap.
77+
/// `Reachability::Unreachable` if the heap access will unconditionally trap and
78+
/// any subsequent code in this basic block is unreachable.
3979
pub fn bounds_check_and_compute_addr(
4080
builder: &mut FunctionBuilder,
4181
env: &mut FuncEnvironment<'_>,
4282
heap: &HeapData,
43-
// Dynamic operand indexing into the heap.
4483
index: ir::Value,
45-
// Static immediate added to the index.
84+
bounds_check: BoundsCheck,
85+
trap: ir::TrapCode,
86+
) -> Reachability<ir::Value> {
87+
match bounds_check {
88+
BoundsCheck::Field {
89+
offset,
90+
access_size,
91+
} => bounds_check_field_access(builder, env, heap, index, offset, access_size, trap),
92+
93+
BoundsCheck::StaticWholeObject {
94+
offset,
95+
access_size,
96+
object_size,
97+
} => {
98+
// Assert that the precondition holds.
99+
let offset_and_access_size = offset.checked_add(access_size.into()).unwrap();
100+
assert!(offset_and_access_size <= object_size);
101+
102+
// When we can, pretend that we are doing one big access of the
103+
// whole object all at once. This enables better GVN for repeated
104+
// accesses of the same object.
105+
if let Ok(object_size) = u8::try_from(object_size) {
106+
let obj_ptr = match bounds_check_field_access(
107+
builder,
108+
env,
109+
heap,
110+
index,
111+
0,
112+
object_size,
113+
trap,
114+
) {
115+
Reachable(v) => v,
116+
u @ Unreachable => return u,
117+
};
118+
let offset = builder.ins().iconst(env.pointer_type(), i64::from(offset));
119+
let field_ptr = builder.ins().iadd(obj_ptr, offset);
120+
return Reachable(field_ptr);
121+
}
122+
123+
// Otherwise, bounds check just this one field's access.
124+
bounds_check_field_access(builder, env, heap, index, offset, access_size, trap)
125+
}
126+
127+
// Compute the index of the end of the object, bounds check that and get
128+
// a pointer to just after the object, and then reverse offset from that
129+
// to get the pointer to the field being accessed.
130+
BoundsCheck::DynamicWholeObject {
131+
offset,
132+
object_size,
133+
} => {
134+
assert_eq!(heap.index_type(), ir::types::I32);
135+
assert_eq!(builder.func.dfg.value_type(index), ir::types::I32);
136+
assert_eq!(builder.func.dfg.value_type(offset), ir::types::I32);
137+
assert_eq!(builder.func.dfg.value_type(object_size), ir::types::I32);
138+
139+
let index_and_object_size = builder.ins().uadd_overflow_trap(index, object_size, trap);
140+
let ptr_just_after_obj = match bounds_check_field_access(
141+
builder,
142+
env,
143+
heap,
144+
index_and_object_size,
145+
0,
146+
0,
147+
trap,
148+
) {
149+
Reachable(v) => v,
150+
u @ Unreachable => return u,
151+
};
152+
153+
let backwards_offset = builder.ins().isub(object_size, offset);
154+
let backwards_offset = cast_index_to_pointer_ty(
155+
backwards_offset,
156+
ir::types::I32,
157+
env.pointer_type(),
158+
false,
159+
&mut builder.cursor(),
160+
trap,
161+
);
162+
163+
let field_ptr = builder.ins().isub(ptr_just_after_obj, backwards_offset);
164+
Reachable(field_ptr)
165+
}
166+
}
167+
}
168+
169+
fn bounds_check_field_access(
170+
builder: &mut FunctionBuilder,
171+
env: &mut FuncEnvironment<'_>,
172+
heap: &HeapData,
173+
index: ir::Value,
46174
offset: u32,
47-
// Static size of the heap access.
48175
access_size: u8,
176+
trap: ir::TrapCode,
49177
) -> Reachability<ir::Value> {
50178
let pointer_bit_width = u16::try_from(env.pointer_type().bits()).unwrap();
51179
let bound_gv = heap.bound;
52180
let orig_index = index;
53-
let offset_and_size = offset_plus_size(offset, access_size);
54181
let clif_memory_traps_enabled = env.clif_memory_traps_enabled();
55182
let spectre_mitigations_enabled =
56183
env.heap_access_spectre_mitigation() && clif_memory_traps_enabled;
@@ -68,6 +195,7 @@ pub fn bounds_check_and_compute_addr(
68195
let memory_guard_size = env.tunables().memory_guard_size;
69196
let memory_reservation = env.tunables().memory_reservation;
70197

198+
let offset_and_size = offset_plus_size(offset, access_size);
71199
let statically_in_bounds = statically_in_bounds(&builder.func, heap, index, offset_and_size);
72200

73201
let index = cast_index_to_pointer_ty(
@@ -76,6 +204,7 @@ pub fn bounds_check_and_compute_addr(
76204
env.pointer_type(),
77205
heap.pcc_memory_type.is_some(),
78206
&mut builder.cursor(),
207+
trap,
79208
);
80209

81210
let oob_behavior = if spectre_mitigations_enabled {
@@ -166,7 +295,7 @@ pub fn bounds_check_and_compute_addr(
166295
// max_memory_size`, since we will end up being out-of-bounds regardless
167296
// of the given `index`.
168297
env.before_unconditionally_trapping_memory_access(builder);
169-
env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
298+
env.trap(builder, trap);
170299
return Unreachable;
171300
}
172301

@@ -176,7 +305,7 @@ pub fn bounds_check_and_compute_addr(
176305
// native pointer type anyway, so this is an unconditional trap.
177306
if pointer_bit_width < 64 && offset_and_size >= (1 << pointer_bit_width) {
178307
env.before_unconditionally_trapping_memory_access(builder);
179-
env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
308+
env.trap(builder, trap);
180309
return Unreachable;
181310
}
182311

@@ -297,6 +426,7 @@ pub fn bounds_check_and_compute_addr(
297426
oob_behavior,
298427
AddrPcc::static32(heap.pcc_memory_type, memory_reservation),
299428
oob,
429+
trap,
300430
));
301431
}
302432

@@ -330,6 +460,7 @@ pub fn bounds_check_and_compute_addr(
330460
oob_behavior,
331461
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
332462
oob,
463+
trap,
333464
));
334465
}
335466

@@ -378,6 +509,7 @@ pub fn bounds_check_and_compute_addr(
378509
oob_behavior,
379510
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
380511
oob,
512+
trap,
381513
));
382514
}
383515

@@ -422,6 +554,7 @@ pub fn bounds_check_and_compute_addr(
422554
oob_behavior,
423555
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
424556
oob,
557+
trap,
425558
));
426559
}
427560

@@ -439,12 +572,7 @@ pub fn bounds_check_and_compute_addr(
439572
builder.func.dfg.facts[access_size_val] =
440573
Some(Fact::constant(pointer_bit_width, offset_and_size));
441574
}
442-
let adjusted_index = env.uadd_overflow_trap(
443-
builder,
444-
index,
445-
access_size_val,
446-
ir::TrapCode::HEAP_OUT_OF_BOUNDS,
447-
);
575+
let adjusted_index = env.uadd_overflow_trap(builder, index, access_size_val, trap);
448576
if pcc {
449577
builder.func.dfg.facts[adjusted_index] = Some(Fact::value_offset(
450578
pointer_bit_width,
@@ -471,6 +599,7 @@ pub fn bounds_check_and_compute_addr(
471599
oob_behavior,
472600
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
473601
oob,
602+
trap,
474603
))
475604
}
476605

@@ -521,6 +650,7 @@ fn cast_index_to_pointer_ty(
521650
pointer_ty: ir::Type,
522651
pcc: bool,
523652
pos: &mut FuncCursor,
653+
trap: ir::TrapCode,
524654
) -> ir::Value {
525655
if index_ty == pointer_ty {
526656
return index;
@@ -544,8 +674,7 @@ fn cast_index_to_pointer_ty(
544674
let c32 = pos.ins().iconst(pointer_ty, 32);
545675
let high_bits = pos.ins().ushr(index, c32);
546676
let high_bits = pos.ins().ireduce(pointer_ty, high_bits);
547-
pos.ins()
548-
.trapnz(high_bits, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
677+
pos.ins().trapnz(high_bits, trap);
549678
return low_bits;
550679
}
551680

@@ -623,9 +752,10 @@ fn explicit_check_oob_condition_and_compute_addr(
623752
// bounds (and therefore we should trap) and is zero when the heap access is
624753
// in bounds (and therefore we can proceed).
625754
oob_condition: ir::Value,
755+
trap: ir::TrapCode,
626756
) -> ir::Value {
627757
if let OobBehavior::ExplicitTrap = oob_behavior {
628-
env.trapnz(builder, oob_condition, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
758+
env.trapnz(builder, oob_condition, trap);
629759
}
630760
let addr_ty = env.pointer_type();
631761

crates/cranelift/src/func_environ.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ pub struct FuncEnvironment<'module_environment> {
105105
wasmtime_environ::GcLayout,
106106
>,
107107

108+
#[cfg(feature = "gc")]
109+
gc_heap: Option<Heap>,
110+
111+
/// The Cranelift global holding the GC heap's base address.
112+
#[cfg(feature = "gc")]
113+
gc_heap_base: Option<ir::GlobalValue>,
114+
115+
/// The Cranelift global holding the GC heap's base address.
116+
#[cfg(feature = "gc")]
117+
gc_heap_bound: Option<ir::GlobalValue>,
118+
108119
#[cfg(feature = "wmemcheck")]
109120
translation: &'module_environment ModuleTranslation<'module_environment>,
110121

@@ -188,6 +199,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
188199

189200
#[cfg(feature = "gc")]
190201
ty_to_gc_layout: std::collections::HashMap::new(),
202+
#[cfg(feature = "gc")]
203+
gc_heap: None,
204+
#[cfg(feature = "gc")]
205+
gc_heap_base: None,
206+
#[cfg(feature = "gc")]
207+
gc_heap_bound: None,
191208

192209
heaps: PrimaryMap::default(),
193210
tables: SecondaryMap::default(),

0 commit comments

Comments
 (0)