Skip to content

Commit c5e6bd1

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 b152712 commit c5e6bd1

File tree

787 files changed

+6885
-5869
lines changed

Some content is hidden

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

787 files changed

+6885
-5869
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/translate/code_translator/bounds_checks.rs renamed to crates/cranelift/src/bounds_checks.rs

Lines changed: 169 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,165 @@
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},
2830
ir::{Expr, Fact},
2931
};
3032
use cranelift_frontend::FunctionBuilder;
31-
use wasmtime_environ::{Unsigned, WasmResult};
33+
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,
49-
) -> WasmResult<Reachability<ir::Value>> {
176+
trap: ir::TrapCode,
177+
) -> 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 {
@@ -165,19 +294,19 @@ pub fn bounds_check_and_compute_addr(
165294
// Special case: trap immediately if `offset + access_size >
166295
// max_memory_size`, since we will end up being out-of-bounds regardless
167296
// of the given `index`.
168-
env.before_unconditionally_trapping_memory_access(builder)?;
169-
env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
170-
return Ok(Unreachable);
297+
env.before_unconditionally_trapping_memory_access(builder);
298+
env.trap(builder, trap);
299+
return Unreachable;
171300
}
172301

173302
// Special case: if this is a 32-bit platform and the `offset_and_size`
174303
// overflows the 32-bit address space then there's no hope of this ever
175304
// being in-bounds. We can't represent `offset_and_size` in CLIF as the
176305
// native pointer type anyway, so this is an unconditional trap.
177306
if pointer_bit_width < 64 && offset_and_size >= (1 << pointer_bit_width) {
178-
env.before_unconditionally_trapping_memory_access(builder)?;
179-
env.trap(builder, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
180-
return Ok(Unreachable);
307+
env.before_unconditionally_trapping_memory_access(builder);
308+
env.trap(builder, trap);
309+
return Unreachable;
181310
}
182311

183312
// Special case for when we can completely omit explicit
@@ -226,27 +355,27 @@ pub fn bounds_check_and_compute_addr(
226355
can_use_virtual_memory,
227356
"static memories require the ability to use virtual memory"
228357
);
229-
return Ok(Reachable(compute_addr(
358+
return Reachable(compute_addr(
230359
&mut builder.cursor(),
231360
heap,
232361
env.pointer_type(),
233362
index,
234363
offset,
235364
AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
236-
)));
365+
));
237366
}
238367

239368
// Special case when the `index` is a constant and statically known to be
240369
// in-bounds on this memory, no bounds checks necessary.
241370
if statically_in_bounds {
242-
return Ok(Reachable(compute_addr(
371+
return Reachable(compute_addr(
243372
&mut builder.cursor(),
244373
heap,
245374
env.pointer_type(),
246375
index,
247376
offset,
248377
AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
249-
)));
378+
));
250379
}
251380

252381
// Special case for when we can rely on virtual memory, the minimum
@@ -287,7 +416,7 @@ pub fn bounds_check_and_compute_addr(
287416
adjusted_bound_value,
288417
Some(0),
289418
);
290-
return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
419+
return Reachable(explicit_check_oob_condition_and_compute_addr(
291420
env,
292421
builder,
293422
heap,
@@ -297,7 +426,8 @@ pub fn bounds_check_and_compute_addr(
297426
oob_behavior,
298427
AddrPcc::static32(heap.pcc_memory_type, memory_reservation),
299428
oob,
300-
)));
429+
trap,
430+
));
301431
}
302432

303433
// Special case for when `offset + access_size == 1`:
@@ -320,7 +450,7 @@ pub fn bounds_check_and_compute_addr(
320450
bound,
321451
Some(0),
322452
);
323-
return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
453+
return Reachable(explicit_check_oob_condition_and_compute_addr(
324454
env,
325455
builder,
326456
heap,
@@ -330,7 +460,8 @@ pub fn bounds_check_and_compute_addr(
330460
oob_behavior,
331461
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
332462
oob,
333-
)));
463+
trap,
464+
));
334465
}
335466

336467
// Special case for when we know that there are enough guard
@@ -368,7 +499,7 @@ pub fn bounds_check_and_compute_addr(
368499
bound,
369500
Some(0),
370501
);
371-
return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
502+
return Reachable(explicit_check_oob_condition_and_compute_addr(
372503
env,
373504
builder,
374505
heap,
@@ -378,7 +509,8 @@ pub fn bounds_check_and_compute_addr(
378509
oob_behavior,
379510
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
380511
oob,
381-
)));
512+
trap,
513+
));
382514
}
383515

384516
// Special case for when `offset + access_size <= min_size`.
@@ -412,7 +544,7 @@ pub fn bounds_check_and_compute_addr(
412544
adjusted_bound,
413545
Some(adjustment),
414546
);
415-
return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
547+
return Reachable(explicit_check_oob_condition_and_compute_addr(
416548
env,
417549
builder,
418550
heap,
@@ -422,7 +554,8 @@ pub fn bounds_check_and_compute_addr(
422554
oob_behavior,
423555
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
424556
oob,
425-
)));
557+
trap,
558+
));
426559
}
427560

428561
// General case for dynamic bounds checks:
@@ -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,
@@ -461,7 +589,7 @@ pub fn bounds_check_and_compute_addr(
461589
bound,
462590
Some(0),
463591
);
464-
Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
592+
Reachable(explicit_check_oob_condition_and_compute_addr(
465593
env,
466594
builder,
467595
heap,
@@ -471,7 +599,8 @@ pub fn bounds_check_and_compute_addr(
471599
oob_behavior,
472600
AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
473601
oob,
474-
)))
602+
trap,
603+
))
475604
}
476605

477606
/// Get the bound of a dynamic heap as an `ir::Value`.
@@ -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

0 commit comments

Comments
 (0)