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+ } ;
2527use cranelift_codegen:: {
2628 cursor:: { Cursor , FuncCursor } ,
2729 ir:: { self , condcodes:: IntCC , InstBuilder , RelSourceLoc } ,
2830 ir:: { Expr , Fact } ,
2931} ;
3032use cranelift_frontend:: FunctionBuilder ;
31- use wasmtime_environ:: { Unsigned , WasmResult } ;
33+ use wasmtime_environ:: Unsigned ;
3234use 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.
3979pub 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