19
19
//! !!! !!!
20
20
//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
21
21
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
+ } ;
25
27
use cranelift_codegen:: {
26
28
cursor:: { Cursor , FuncCursor } ,
27
29
ir:: { self , condcodes:: IntCC , InstBuilder , RelSourceLoc } ,
@@ -31,26 +33,155 @@ use cranelift_frontend::FunctionBuilder;
31
33
use wasmtime_environ:: Unsigned ;
32
34
use Reachability :: * ;
33
35
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 <= bound
48
+ /// ```
49
+ StaticOffset { 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
+ #[ cfg( feature = "gc" ) ]
58
+ StaticObjectField {
59
+ offset : u32 ,
60
+ access_size : u8 ,
61
+ object_size : u32 ,
62
+ } ,
63
+
64
+ /// Like `StaticWholeObject` but with dynamic offset and object size.
65
+ ///
66
+ /// It is *your* responsibility to ensure that the `offset + access_size <=
67
+ /// object_size` precondition holds.
68
+ #[ cfg( feature = "gc" ) ]
69
+ DynamicObjectField {
70
+ offset : ir:: Value ,
71
+ object_size : ir:: Value ,
72
+ } ,
73
+ }
74
+
34
75
/// Helper used to emit bounds checks (as necessary) and compute the native
35
76
/// address of a heap access.
36
77
///
37
78
/// Returns the `ir::Value` holding the native address of the heap access, or
38
- /// `None` if the heap access will unconditionally trap.
79
+ /// `Reachability::Unreachable` if the heap access will unconditionally trap and
80
+ /// any subsequent code in this basic block is unreachable.
39
81
pub fn bounds_check_and_compute_addr (
40
82
builder : & mut FunctionBuilder ,
41
83
env : & mut FuncEnvironment < ' _ > ,
42
84
heap : & HeapData ,
43
- // Dynamic operand indexing into the heap.
44
85
index : ir:: Value ,
45
- // Static immediate added to the index.
86
+ bounds_check : BoundsCheck ,
87
+ trap : ir:: TrapCode ,
88
+ ) -> Reachability < ir:: Value > {
89
+ match bounds_check {
90
+ BoundsCheck :: StaticOffset {
91
+ offset,
92
+ access_size,
93
+ } => bounds_check_field_access ( builder, env, heap, index, offset, access_size, trap) ,
94
+
95
+ #[ cfg( feature = "gc" ) ]
96
+ BoundsCheck :: StaticObjectField {
97
+ offset,
98
+ access_size,
99
+ object_size,
100
+ } => {
101
+ // Assert that the precondition holds.
102
+ let offset_and_access_size = offset. checked_add ( access_size. into ( ) ) . unwrap ( ) ;
103
+ assert ! ( offset_and_access_size <= object_size) ;
104
+
105
+ // When we can, pretend that we are doing one big access of the
106
+ // whole object all at once. This enables better GVN for repeated
107
+ // accesses of the same object.
108
+ if let Ok ( object_size) = u8:: try_from ( object_size) {
109
+ let obj_ptr = match bounds_check_field_access (
110
+ builder,
111
+ env,
112
+ heap,
113
+ index,
114
+ 0 ,
115
+ object_size,
116
+ trap,
117
+ ) {
118
+ Reachable ( v) => v,
119
+ u @ Unreachable => return u,
120
+ } ;
121
+ let offset = builder. ins ( ) . iconst ( env. pointer_type ( ) , i64:: from ( offset) ) ;
122
+ let field_ptr = builder. ins ( ) . iadd ( obj_ptr, offset) ;
123
+ return Reachable ( field_ptr) ;
124
+ }
125
+
126
+ // Otherwise, bounds check just this one field's access.
127
+ bounds_check_field_access ( builder, env, heap, index, offset, access_size, trap)
128
+ }
129
+
130
+ // Compute the index of the end of the object, bounds check that and get
131
+ // a pointer to just after the object, and then reverse offset from that
132
+ // to get the pointer to the field being accessed.
133
+ #[ cfg( feature = "gc" ) ]
134
+ BoundsCheck :: DynamicObjectField {
135
+ offset,
136
+ object_size,
137
+ } => {
138
+ assert_eq ! ( heap. index_type( ) , ir:: types:: I32 ) ;
139
+ assert_eq ! ( builder. func. dfg. value_type( index) , ir:: types:: I32 ) ;
140
+ assert_eq ! ( builder. func. dfg. value_type( offset) , ir:: types:: I32 ) ;
141
+ assert_eq ! ( builder. func. dfg. value_type( object_size) , ir:: types:: I32 ) ;
142
+
143
+ let index_and_object_size = builder. ins ( ) . uadd_overflow_trap ( index, object_size, trap) ;
144
+ let ptr_just_after_obj = match bounds_check_field_access (
145
+ builder,
146
+ env,
147
+ heap,
148
+ index_and_object_size,
149
+ 0 ,
150
+ 0 ,
151
+ trap,
152
+ ) {
153
+ Reachable ( v) => v,
154
+ u @ Unreachable => return u,
155
+ } ;
156
+
157
+ let backwards_offset = builder. ins ( ) . isub ( object_size, offset) ;
158
+ let backwards_offset = cast_index_to_pointer_ty (
159
+ backwards_offset,
160
+ ir:: types:: I32 ,
161
+ env. pointer_type ( ) ,
162
+ false ,
163
+ & mut builder. cursor ( ) ,
164
+ trap,
165
+ ) ;
166
+
167
+ let field_ptr = builder. ins ( ) . isub ( ptr_just_after_obj, backwards_offset) ;
168
+ Reachable ( field_ptr)
169
+ }
170
+ }
171
+ }
172
+
173
+ fn bounds_check_field_access (
174
+ builder : & mut FunctionBuilder ,
175
+ env : & mut FuncEnvironment < ' _ > ,
176
+ heap : & HeapData ,
177
+ index : ir:: Value ,
46
178
offset : u32 ,
47
- // Static size of the heap access.
48
179
access_size : u8 ,
180
+ trap : ir:: TrapCode ,
49
181
) -> Reachability < ir:: Value > {
50
182
let pointer_bit_width = u16:: try_from ( env. pointer_type ( ) . bits ( ) ) . unwrap ( ) ;
51
183
let bound_gv = heap. bound ;
52
184
let orig_index = index;
53
- let offset_and_size = offset_plus_size ( offset, access_size) ;
54
185
let clif_memory_traps_enabled = env. clif_memory_traps_enabled ( ) ;
55
186
let spectre_mitigations_enabled =
56
187
env. heap_access_spectre_mitigation ( ) && clif_memory_traps_enabled;
@@ -68,6 +199,7 @@ pub fn bounds_check_and_compute_addr(
68
199
let memory_guard_size = env. tunables ( ) . memory_guard_size ;
69
200
let memory_reservation = env. tunables ( ) . memory_reservation ;
70
201
202
+ let offset_and_size = offset_plus_size ( offset, access_size) ;
71
203
let statically_in_bounds = statically_in_bounds ( & builder. func , heap, index, offset_and_size) ;
72
204
73
205
let index = cast_index_to_pointer_ty (
@@ -76,6 +208,7 @@ pub fn bounds_check_and_compute_addr(
76
208
env. pointer_type ( ) ,
77
209
heap. pcc_memory_type . is_some ( ) ,
78
210
& mut builder. cursor ( ) ,
211
+ trap,
79
212
) ;
80
213
81
214
let oob_behavior = if spectre_mitigations_enabled {
@@ -166,7 +299,7 @@ pub fn bounds_check_and_compute_addr(
166
299
// max_memory_size`, since we will end up being out-of-bounds regardless
167
300
// of the given `index`.
168
301
env. before_unconditionally_trapping_memory_access ( builder) ;
169
- env. trap ( builder, ir :: TrapCode :: HEAP_OUT_OF_BOUNDS ) ;
302
+ env. trap ( builder, trap ) ;
170
303
return Unreachable ;
171
304
}
172
305
@@ -176,7 +309,7 @@ pub fn bounds_check_and_compute_addr(
176
309
// native pointer type anyway, so this is an unconditional trap.
177
310
if pointer_bit_width < 64 && offset_and_size >= ( 1 << pointer_bit_width) {
178
311
env. before_unconditionally_trapping_memory_access ( builder) ;
179
- env. trap ( builder, ir :: TrapCode :: HEAP_OUT_OF_BOUNDS ) ;
312
+ env. trap ( builder, trap ) ;
180
313
return Unreachable ;
181
314
}
182
315
@@ -297,6 +430,7 @@ pub fn bounds_check_and_compute_addr(
297
430
oob_behavior,
298
431
AddrPcc :: static32 ( heap. pcc_memory_type , memory_reservation) ,
299
432
oob,
433
+ trap,
300
434
) ) ;
301
435
}
302
436
@@ -330,6 +464,7 @@ pub fn bounds_check_and_compute_addr(
330
464
oob_behavior,
331
465
AddrPcc :: dynamic ( heap. pcc_memory_type , bound_gv) ,
332
466
oob,
467
+ trap,
333
468
) ) ;
334
469
}
335
470
@@ -378,6 +513,7 @@ pub fn bounds_check_and_compute_addr(
378
513
oob_behavior,
379
514
AddrPcc :: dynamic ( heap. pcc_memory_type , bound_gv) ,
380
515
oob,
516
+ trap,
381
517
) ) ;
382
518
}
383
519
@@ -422,6 +558,7 @@ pub fn bounds_check_and_compute_addr(
422
558
oob_behavior,
423
559
AddrPcc :: dynamic ( heap. pcc_memory_type , bound_gv) ,
424
560
oob,
561
+ trap,
425
562
) ) ;
426
563
}
427
564
@@ -439,12 +576,7 @@ pub fn bounds_check_and_compute_addr(
439
576
builder. func . dfg . facts [ access_size_val] =
440
577
Some ( Fact :: constant ( pointer_bit_width, offset_and_size) ) ;
441
578
}
442
- let adjusted_index = env. uadd_overflow_trap (
443
- builder,
444
- index,
445
- access_size_val,
446
- ir:: TrapCode :: HEAP_OUT_OF_BOUNDS ,
447
- ) ;
579
+ let adjusted_index = env. uadd_overflow_trap ( builder, index, access_size_val, trap) ;
448
580
if pcc {
449
581
builder. func . dfg . facts [ adjusted_index] = Some ( Fact :: value_offset (
450
582
pointer_bit_width,
@@ -471,6 +603,7 @@ pub fn bounds_check_and_compute_addr(
471
603
oob_behavior,
472
604
AddrPcc :: dynamic ( heap. pcc_memory_type , bound_gv) ,
473
605
oob,
606
+ trap,
474
607
) )
475
608
}
476
609
@@ -521,6 +654,7 @@ fn cast_index_to_pointer_ty(
521
654
pointer_ty : ir:: Type ,
522
655
pcc : bool ,
523
656
pos : & mut FuncCursor ,
657
+ trap : ir:: TrapCode ,
524
658
) -> ir:: Value {
525
659
if index_ty == pointer_ty {
526
660
return index;
@@ -544,8 +678,7 @@ fn cast_index_to_pointer_ty(
544
678
let c32 = pos. ins ( ) . iconst ( pointer_ty, 32 ) ;
545
679
let high_bits = pos. ins ( ) . ushr ( index, c32) ;
546
680
let high_bits = pos. ins ( ) . ireduce ( pointer_ty, high_bits) ;
547
- pos. ins ( )
548
- . trapnz ( high_bits, ir:: TrapCode :: HEAP_OUT_OF_BOUNDS ) ;
681
+ pos. ins ( ) . trapnz ( high_bits, trap) ;
549
682
return low_bits;
550
683
}
551
684
@@ -623,9 +756,10 @@ fn explicit_check_oob_condition_and_compute_addr(
623
756
// bounds (and therefore we should trap) and is zero when the heap access is
624
757
// in bounds (and therefore we can proceed).
625
758
oob_condition : ir:: Value ,
759
+ trap : ir:: TrapCode ,
626
760
) -> ir:: Value {
627
761
if let OobBehavior :: ExplicitTrap = oob_behavior {
628
- env. trapnz ( builder, oob_condition, ir :: TrapCode :: HEAP_OUT_OF_BOUNDS ) ;
762
+ env. trapnz ( builder, oob_condition, trap ) ;
629
763
}
630
764
let addr_ty = env. pointer_type ( ) ;
631
765
0 commit comments