wasmtime_cranelift/translate/code_translator/
bounds_checks.rs

1//! Implementation of Wasm to CLIF memory access translation.
2//!
3//! Given
4//!
5//! * a dynamic Wasm memory index operand,
6//! * a static offset immediate, and
7//! * a static access size,
8//!
9//! bounds check the memory access and translate it into a native memory access.
10//!
11//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12//! !!!                                                                      !!!
13//! !!!    THIS CODE IS VERY SUBTLE, HAS MANY SPECIAL CASES, AND IS ALSO     !!!
14//! !!!   ABSOLUTELY CRITICAL FOR MAINTAINING THE SAFETY OF THE WASM HEAP    !!!
15//! !!!                             SANDBOX.                                 !!!
16//! !!!                                                                      !!!
17//! !!!    A good rule of thumb is to get two reviews on any substantive     !!!
18//! !!!                         changes in here.                             !!!
19//! !!!                                                                      !!!
20//! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
21
22use super::Reachability;
23use crate::func_environ::FuncEnvironment;
24use crate::translate::{HeapData, TargetEnvironment};
25use cranelift_codegen::{
26    cursor::{Cursor, FuncCursor},
27    ir::{self, condcodes::IntCC, InstBuilder, RelSourceLoc},
28    ir::{Expr, Fact},
29};
30use cranelift_frontend::FunctionBuilder;
31use wasmtime_environ::{Unsigned, WasmResult};
32use Reachability::*;
33
34/// Helper used to emit bounds checks (as necessary) and compute the native
35/// address of a heap access.
36///
37/// Returns the `ir::Value` holding the native address of the heap access, or
38/// `None` if the heap access will unconditionally trap.
39pub fn bounds_check_and_compute_addr(
40    builder: &mut FunctionBuilder,
41    env: &mut FuncEnvironment<'_>,
42    heap: &HeapData,
43    // Dynamic operand indexing into the heap.
44    index: ir::Value,
45    // Static immediate added to the index.
46    offset: u32,
47    // Static size of the heap access.
48    access_size: u8,
49) -> WasmResult<Reachability<ir::Value>> {
50    let pointer_bit_width = u16::try_from(env.pointer_type().bits()).unwrap();
51    let bound_gv = heap.bound;
52    let orig_index = index;
53    let offset_and_size = offset_plus_size(offset, access_size);
54    let clif_memory_traps_enabled = env.clif_memory_traps_enabled();
55    let spectre_mitigations_enabled =
56        env.heap_access_spectre_mitigation() && clif_memory_traps_enabled;
57    let pcc = env.proof_carrying_code();
58
59    let host_page_size_log2 = env.target_config().page_size_align_log2;
60    let can_use_virtual_memory = heap
61        .memory
62        .can_use_virtual_memory(env.tunables(), host_page_size_log2)
63        && clif_memory_traps_enabled;
64    let can_elide_bounds_check = heap
65        .memory
66        .can_elide_bounds_check(env.tunables(), host_page_size_log2)
67        && clif_memory_traps_enabled;
68    let memory_guard_size = env.tunables().memory_guard_size;
69    let memory_reservation = env.tunables().memory_reservation;
70
71    let statically_in_bounds = statically_in_bounds(&builder.func, heap, index, offset_and_size);
72
73    let index = cast_index_to_pointer_ty(
74        index,
75        heap.index_type(),
76        env.pointer_type(),
77        heap.pcc_memory_type.is_some(),
78        &mut builder.cursor(),
79    );
80
81    let oob_behavior = if spectre_mitigations_enabled {
82        OobBehavior::ConditionallyLoadFromZero {
83            select_spectre_guard: true,
84        }
85    } else if env.load_from_zero_allowed() {
86        OobBehavior::ConditionallyLoadFromZero {
87            select_spectre_guard: false,
88        }
89    } else {
90        OobBehavior::ExplicitTrap
91    };
92
93    let make_compare = |builder: &mut FunctionBuilder,
94                        compare_kind: IntCC,
95                        lhs: ir::Value,
96                        lhs_off: Option<i64>,
97                        rhs: ir::Value,
98                        rhs_off: Option<i64>| {
99        let result = builder.ins().icmp(compare_kind, lhs, rhs);
100        if pcc {
101            // Name the original value as a def of the SSA value;
102            // if the value was extended, name that as well with a
103            // dynamic range, overwriting the basic full-range
104            // fact that we previously put on the uextend.
105            builder.func.dfg.facts[orig_index] = Some(Fact::Def { value: orig_index });
106            if index != orig_index {
107                builder.func.dfg.facts[index] = Some(Fact::value(pointer_bit_width, orig_index));
108            }
109
110            // Create a fact on the LHS that is a "trivial symbolic
111            // fact": v1 has range v1+LHS_off..=v1+LHS_off
112            builder.func.dfg.facts[lhs] = Some(Fact::value_offset(
113                pointer_bit_width,
114                orig_index,
115                lhs_off.unwrap(),
116            ));
117            // If the RHS is a symbolic value (v1 or gv1), we can
118            // emit a Compare fact.
119            if let Some(rhs) = builder.func.dfg.facts[rhs]
120                .as_ref()
121                .and_then(|f| f.as_symbol())
122            {
123                builder.func.dfg.facts[result] = Some(Fact::Compare {
124                    kind: compare_kind,
125                    lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),
126                    rhs: Expr::offset(rhs, rhs_off.unwrap()).unwrap(),
127                });
128            }
129            // Likewise, if the RHS is a constant, we can emit a
130            // Compare fact.
131            if let Some(k) = builder.func.dfg.facts[rhs]
132                .as_ref()
133                .and_then(|f| f.as_const(pointer_bit_width))
134            {
135                builder.func.dfg.facts[result] = Some(Fact::Compare {
136                    kind: compare_kind,
137                    lhs: Expr::offset(&Expr::value(orig_index), lhs_off.unwrap()).unwrap(),
138                    rhs: Expr::constant((k as i64).checked_add(rhs_off.unwrap()).unwrap()),
139                });
140            }
141        }
142        result
143    };
144
145    // We need to emit code that will trap (or compute an address that will trap
146    // when accessed) if
147    //
148    //     index + offset + access_size > bound
149    //
150    // or if the `index + offset + access_size` addition overflows.
151    //
152    // Note that we ultimately want a 64-bit integer (we only target 64-bit
153    // architectures at the moment) and that `offset` is a `u32` and
154    // `access_size` is a `u8`. This means that we can add the latter together
155    // as `u64`s without fear of overflow, and we only have to be concerned with
156    // whether adding in `index` will overflow.
157    //
158    // Finally, the following if/else chains do have a little
159    // bit of duplicated code across them, but I think writing it this way is
160    // worth it for readability and seeing very clearly each of our cases for
161    // different bounds checks and optimizations of those bounds checks. It is
162    // intentionally written in a straightforward case-matching style that will
163    // hopefully make it easy to port to ISLE one day.
164    if offset_and_size > heap.memory.maximum_byte_size().unwrap_or(u64::MAX) {
165        // Special case: trap immediately if `offset + access_size >
166        // max_memory_size`, since we will end up being out-of-bounds regardless
167        // 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);
171    }
172
173    // Special case: if this is a 32-bit platform and the `offset_and_size`
174    // overflows the 32-bit address space then there's no hope of this ever
175    // being in-bounds. We can't represent `offset_and_size` in CLIF as the
176    // native pointer type anyway, so this is an unconditional trap.
177    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);
181    }
182
183    // Special case for when we can completely omit explicit
184    // bounds checks for 32-bit memories.
185    //
186    // First, let's rewrite our comparison to move all of the constants
187    // to one side:
188    //
189    //         index + offset + access_size > bound
190    //     ==> index > bound - (offset + access_size)
191    //
192    // We know the subtraction on the right-hand side won't wrap because
193    // we didn't hit the unconditional trap case above.
194    //
195    // Additionally, we add our guard pages (if any) to the right-hand
196    // side, since we can rely on the virtual memory subsystem at runtime
197    // to catch out-of-bound accesses within the range `bound .. bound +
198    // guard_size`. So now we are dealing with
199    //
200    //     index > bound + guard_size - (offset + access_size)
201    //
202    // Note that `bound + guard_size` cannot overflow for
203    // correctly-configured heaps, as otherwise the heap wouldn't fit in
204    // a 64-bit memory space.
205    //
206    // The complement of our should-this-trap comparison expression is
207    // the should-this-not-trap comparison expression:
208    //
209    //     index <= bound + guard_size - (offset + access_size)
210    //
211    // If we know the right-hand side is greater than or equal to
212    // `u32::MAX`, then
213    //
214    //     index <= u32::MAX <= bound + guard_size - (offset + access_size)
215    //
216    // This expression is always true when the heap is indexed with
217    // 32-bit integers because `index` cannot be larger than
218    // `u32::MAX`. This means that `index` is always either in bounds or
219    // within the guard page region, neither of which require emitting an
220    // explicit bounds check.
221    if can_elide_bounds_check
222        && u64::from(u32::MAX) <= memory_reservation + memory_guard_size - offset_and_size
223    {
224        assert!(heap.index_type() == ir::types::I32);
225        assert!(
226            can_use_virtual_memory,
227            "static memories require the ability to use virtual memory"
228        );
229        return Ok(Reachable(compute_addr(
230            &mut builder.cursor(),
231            heap,
232            env.pointer_type(),
233            index,
234            offset,
235            AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
236        )));
237    }
238
239    // Special case when the `index` is a constant and statically known to be
240    // in-bounds on this memory, no bounds checks necessary.
241    if statically_in_bounds {
242        return Ok(Reachable(compute_addr(
243            &mut builder.cursor(),
244            heap,
245            env.pointer_type(),
246            index,
247            offset,
248            AddrPcc::static32(heap.pcc_memory_type, memory_reservation + memory_guard_size),
249        )));
250    }
251
252    // Special case for when we can rely on virtual memory, the minimum
253    // byte size of this memory fits within the memory reservation, and
254    // memory isn't allowed to move. In this situation we know that
255    // memory will statically not grow beyond `memory_reservation` so we
256    // and we know that memory from 0 to that limit is guaranteed to be
257    // valid or trap. Here we effectively assume that the dynamic size
258    // of linear memory is its maximal value, `memory_reservation`, and
259    // we can avoid loading the actual length of memory.
260    //
261    // We have to explicitly test whether
262    //
263    //     index > bound - (offset + access_size)
264    //
265    // and trap if so.
266    //
267    // Since we have to emit explicit bounds checks, we might as well be
268    // precise, not rely on the virtual memory subsystem at all, and not
269    // factor in the guard pages here.
270    if can_use_virtual_memory
271        && heap.memory.minimum_byte_size().unwrap_or(u64::MAX) <= memory_reservation
272        && !heap.memory.memory_may_move(env.tunables())
273    {
274        let adjusted_bound = memory_reservation.checked_sub(offset_and_size).unwrap();
275        let adjusted_bound_value = builder
276            .ins()
277            .iconst(env.pointer_type(), adjusted_bound as i64);
278        if pcc {
279            builder.func.dfg.facts[adjusted_bound_value] =
280                Some(Fact::constant(pointer_bit_width, adjusted_bound));
281        }
282        let oob = make_compare(
283            builder,
284            IntCC::UnsignedGreaterThan,
285            index,
286            Some(0),
287            adjusted_bound_value,
288            Some(0),
289        );
290        return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
291            env,
292            builder,
293            heap,
294            index,
295            offset,
296            access_size,
297            oob_behavior,
298            AddrPcc::static32(heap.pcc_memory_type, memory_reservation),
299            oob,
300        )));
301    }
302
303    // Special case for when `offset + access_size == 1`:
304    //
305    //         index + 1 > bound
306    //     ==> index >= bound
307    //
308    // Note that this special case is skipped for Pulley targets to assist with
309    // pattern-matching bounds checks into single instructions. Otherwise more
310    // patterns/instructions would have to be added to match this. In the end
311    // the goal is to emit one instruction anyway, so this optimization is
312    // largely only applicable for native platforms.
313    if offset_and_size == 1 && !env.is_pulley() {
314        let bound = get_dynamic_heap_bound(builder, env, heap);
315        let oob = make_compare(
316            builder,
317            IntCC::UnsignedGreaterThanOrEqual,
318            index,
319            Some(0),
320            bound,
321            Some(0),
322        );
323        return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
324            env,
325            builder,
326            heap,
327            index,
328            offset,
329            access_size,
330            oob_behavior,
331            AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
332            oob,
333        )));
334    }
335
336    // Special case for when we know that there are enough guard
337    // pages to cover the offset and access size.
338    //
339    // The precise should-we-trap condition is
340    //
341    //     index + offset + access_size > bound
342    //
343    // However, if we instead check only the partial condition
344    //
345    //     index > bound
346    //
347    // then the most out of bounds that the access can be, while that
348    // partial check still succeeds, is `offset + access_size`.
349    //
350    // However, when we have a guard region that is at least as large as
351    // `offset + access_size`, we can rely on the virtual memory
352    // subsystem handling these out-of-bounds errors at
353    // runtime. Therefore, the partial `index > bound` check is
354    // sufficient for this heap configuration.
355    //
356    // Additionally, this has the advantage that a series of Wasm loads
357    // that use the same dynamic index operand but different static
358    // offset immediates -- which is a common code pattern when accessing
359    // multiple fields in the same struct that is in linear memory --
360    // will all emit the same `index > bound` check, which we can GVN.
361    if can_use_virtual_memory && offset_and_size <= memory_guard_size {
362        let bound = get_dynamic_heap_bound(builder, env, heap);
363        let oob = make_compare(
364            builder,
365            IntCC::UnsignedGreaterThan,
366            index,
367            Some(0),
368            bound,
369            Some(0),
370        );
371        return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
372            env,
373            builder,
374            heap,
375            index,
376            offset,
377            access_size,
378            oob_behavior,
379            AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
380            oob,
381        )));
382    }
383
384    // Special case for when `offset + access_size <= min_size`.
385    //
386    // We know that `bound >= min_size`, so we can do the following
387    // comparison, without fear of the right-hand side wrapping around:
388    //
389    //         index + offset + access_size > bound
390    //     ==> index > bound - (offset + access_size)
391    if offset_and_size <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX) {
392        let bound = get_dynamic_heap_bound(builder, env, heap);
393        let adjustment = offset_and_size as i64;
394        let adjustment_value = builder.ins().iconst(env.pointer_type(), adjustment);
395        if pcc {
396            builder.func.dfg.facts[adjustment_value] =
397                Some(Fact::constant(pointer_bit_width, offset_and_size));
398        }
399        let adjusted_bound = builder.ins().isub(bound, adjustment_value);
400        if pcc {
401            builder.func.dfg.facts[adjusted_bound] = Some(Fact::global_value_offset(
402                pointer_bit_width,
403                bound_gv,
404                -adjustment,
405            ));
406        }
407        let oob = make_compare(
408            builder,
409            IntCC::UnsignedGreaterThan,
410            index,
411            Some(0),
412            adjusted_bound,
413            Some(adjustment),
414        );
415        return Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
416            env,
417            builder,
418            heap,
419            index,
420            offset,
421            access_size,
422            oob_behavior,
423            AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
424            oob,
425        )));
426    }
427
428    // General case for dynamic bounds checks:
429    //
430    //     index + offset + access_size > bound
431    //
432    // And we have to handle the overflow case in the left-hand side.
433    let access_size_val = builder
434        .ins()
435        // Explicit cast from u64 to i64: we just want the raw
436        // bits, and iconst takes an `Imm64`.
437        .iconst(env.pointer_type(), offset_and_size as i64);
438    if pcc {
439        builder.func.dfg.facts[access_size_val] =
440            Some(Fact::constant(pointer_bit_width, offset_and_size));
441    }
442    let adjusted_index = env.uadd_overflow_trap(
443        builder,
444        index,
445        access_size_val,
446        ir::TrapCode::HEAP_OUT_OF_BOUNDS,
447    );
448    if pcc {
449        builder.func.dfg.facts[adjusted_index] = Some(Fact::value_offset(
450            pointer_bit_width,
451            index,
452            i64::try_from(offset_and_size).unwrap(),
453        ));
454    }
455    let bound = get_dynamic_heap_bound(builder, env, heap);
456    let oob = make_compare(
457        builder,
458        IntCC::UnsignedGreaterThan,
459        adjusted_index,
460        i64::try_from(offset_and_size).ok(),
461        bound,
462        Some(0),
463    );
464    Ok(Reachable(explicit_check_oob_condition_and_compute_addr(
465        env,
466        builder,
467        heap,
468        index,
469        offset,
470        access_size,
471        oob_behavior,
472        AddrPcc::dynamic(heap.pcc_memory_type, bound_gv),
473        oob,
474    )))
475}
476
477/// Get the bound of a dynamic heap as an `ir::Value`.
478fn get_dynamic_heap_bound(
479    builder: &mut FunctionBuilder,
480    env: &mut FuncEnvironment<'_>,
481    heap: &HeapData,
482) -> ir::Value {
483    let enable_pcc = heap.pcc_memory_type.is_some();
484
485    let (value, gv) = match heap.memory.static_heap_size() {
486        // The heap has a constant size, no need to actually load the
487        // bound.  TODO: this is currently disabled for PCC because we
488        // can't easily prove that the GV load indeed results in a
489        // constant (that information is lost in the CLIF). We'll want
490        // to create an `iconst` GV expression kind to reify this fact
491        // in the GV, then re-enable this opt. (Or, alternately,
492        // compile such memories with a static-bound memtype and
493        // facts.)
494        Some(max_size) if !enable_pcc => (
495            builder.ins().iconst(env.pointer_type(), max_size as i64),
496            heap.bound,
497        ),
498
499        // Load the heap bound from its global variable.
500        _ => (
501            builder.ins().global_value(env.pointer_type(), heap.bound),
502            heap.bound,
503        ),
504    };
505
506    // If proof-carrying code is enabled, apply a fact to the range to
507    // tie it to the GV.
508    if enable_pcc {
509        builder.func.dfg.facts[value] = Some(Fact::global_value(
510            u16::try_from(env.pointer_type().bits()).unwrap(),
511            gv,
512        ));
513    }
514
515    value
516}
517
518fn cast_index_to_pointer_ty(
519    index: ir::Value,
520    index_ty: ir::Type,
521    pointer_ty: ir::Type,
522    pcc: bool,
523    pos: &mut FuncCursor,
524) -> ir::Value {
525    if index_ty == pointer_ty {
526        return index;
527    }
528
529    // If the index size is larger than the pointer, that means that this is a
530    // 32-bit host platform with a 64-bit wasm linear memory. If the index is
531    // larger than 2**32 then that's guaranteed to be out-of-bounds, otherwise we
532    // `ireduce` the index.
533    //
534    // Also note that at this time this branch doesn't support pcc nor the
535    // value-label-ranges of the below path.
536    //
537    // Finally, note that the returned `low_bits` here are still subject to an
538    // explicit bounds check in wasm so in terms of Spectre speculation on
539    // either side of the `trapnz` should be ok.
540    if index_ty.bits() > pointer_ty.bits() {
541        assert_eq!(index_ty, ir::types::I64);
542        assert_eq!(pointer_ty, ir::types::I32);
543        let low_bits = pos.ins().ireduce(pointer_ty, index);
544        let c32 = pos.ins().iconst(pointer_ty, 32);
545        let high_bits = pos.ins().ushr(index, c32);
546        let high_bits = pos.ins().ireduce(pointer_ty, high_bits);
547        pos.ins()
548            .trapnz(high_bits, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
549        return low_bits;
550    }
551
552    // Convert `index` to `addr_ty`.
553    let extended_index = pos.ins().uextend(pointer_ty, index);
554
555    // Add a range fact on the extended value.
556    if pcc {
557        pos.func.dfg.facts[extended_index] = Some(Fact::max_range_for_width_extended(
558            u16::try_from(index_ty.bits()).unwrap(),
559            u16::try_from(pointer_ty.bits()).unwrap(),
560        ));
561    }
562
563    // Add debug value-label alias so that debuginfo can name the extended
564    // value as the address
565    let loc = pos.srcloc();
566    let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc);
567    pos.func
568        .stencil
569        .dfg
570        .add_value_label_alias(extended_index, loc, index);
571
572    extended_index
573}
574
575/// Which facts do we want to emit for proof-carrying code, if any, on
576/// address computations?
577#[derive(Clone, Copy, Debug)]
578enum AddrPcc {
579    /// A 32-bit static memory with the given size.
580    Static32(ir::MemoryType, u64),
581    /// Dynamic bounds-check, with actual memory size (the `GlobalValue`)
582    /// expressed symbolically.
583    Dynamic(ir::MemoryType, ir::GlobalValue),
584}
585impl AddrPcc {
586    fn static32(memory_type: Option<ir::MemoryType>, size: u64) -> Option<Self> {
587        memory_type.map(|ty| AddrPcc::Static32(ty, size))
588    }
589    fn dynamic(memory_type: Option<ir::MemoryType>, bound: ir::GlobalValue) -> Option<Self> {
590        memory_type.map(|ty| AddrPcc::Dynamic(ty, bound))
591    }
592}
593
594/// What to do on out-of-bounds for the
595/// `explicit_check_oob_condition_and_compute_addr` function below.
596enum OobBehavior {
597    /// An explicit `trapnz` instruction should be used.
598    ExplicitTrap,
599    /// A load from NULL should be issued if the address is out-of-bounds.
600    ConditionallyLoadFromZero {
601        /// Whether or not to use `select_spectre_guard` to choose the address
602        /// to load from. If `false` then a normal `select` is used.
603        select_spectre_guard: bool,
604    },
605}
606
607/// Emit explicit checks on the given out-of-bounds condition for the Wasm
608/// address and return the native address.
609///
610/// This function deduplicates explicit bounds checks and Spectre mitigations
611/// that inherently also implement bounds checking.
612fn explicit_check_oob_condition_and_compute_addr(
613    env: &mut FuncEnvironment<'_>,
614    builder: &mut FunctionBuilder,
615    heap: &HeapData,
616    index: ir::Value,
617    offset: u32,
618    access_size: u8,
619    oob_behavior: OobBehavior,
620    // Whether we're emitting PCC facts.
621    pcc: Option<AddrPcc>,
622    // The `i8` boolean value that is non-zero when the heap access is out of
623    // bounds (and therefore we should trap) and is zero when the heap access is
624    // in bounds (and therefore we can proceed).
625    oob_condition: ir::Value,
626) -> ir::Value {
627    if let OobBehavior::ExplicitTrap = oob_behavior {
628        env.trapnz(builder, oob_condition, ir::TrapCode::HEAP_OUT_OF_BOUNDS);
629    }
630    let addr_ty = env.pointer_type();
631
632    let mut addr = compute_addr(&mut builder.cursor(), heap, addr_ty, index, offset, pcc);
633
634    if let OobBehavior::ConditionallyLoadFromZero {
635        select_spectre_guard,
636    } = oob_behavior
637    {
638        // These mitigations rely on trapping when loading from NULL so
639        // CLIF memory instruction traps must be allowed for this to be
640        // generated.
641        assert!(env.load_from_zero_allowed());
642        let null = builder.ins().iconst(addr_ty, 0);
643        addr = if select_spectre_guard {
644            builder
645                .ins()
646                .select_spectre_guard(oob_condition, null, addr)
647        } else {
648            builder.ins().select(oob_condition, null, addr)
649        };
650
651        match pcc {
652            None => {}
653            Some(AddrPcc::Static32(ty, size)) => {
654                builder.func.dfg.facts[null] =
655                    Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));
656                builder.func.dfg.facts[addr] = Some(Fact::Mem {
657                    ty,
658                    min_offset: 0,
659                    max_offset: size.checked_sub(u64::from(access_size)).unwrap(),
660                    nullable: true,
661                });
662            }
663            Some(AddrPcc::Dynamic(ty, gv)) => {
664                builder.func.dfg.facts[null] =
665                    Some(Fact::constant(u16::try_from(addr_ty.bits()).unwrap(), 0));
666                builder.func.dfg.facts[addr] = Some(Fact::DynamicMem {
667                    ty,
668                    min: Expr::constant(0),
669                    max: Expr::offset(
670                        &Expr::global_value(gv),
671                        i64::try_from(env.tunables().memory_guard_size)
672                            .unwrap()
673                            .checked_sub(i64::from(access_size))
674                            .unwrap(),
675                    )
676                    .unwrap(),
677                    nullable: true,
678                });
679            }
680        }
681    }
682
683    addr
684}
685
686/// Emit code for the native address computation of a Wasm address,
687/// without any bounds checks or overflow checks.
688///
689/// It is the caller's responsibility to ensure that any necessary bounds and
690/// overflow checks are emitted, and that the resulting address is never used
691/// unless they succeed.
692fn compute_addr(
693    pos: &mut FuncCursor,
694    heap: &HeapData,
695    addr_ty: ir::Type,
696    index: ir::Value,
697    offset: u32,
698    pcc: Option<AddrPcc>,
699) -> ir::Value {
700    debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty);
701
702    let heap_base = pos.ins().global_value(addr_ty, heap.base);
703
704    match pcc {
705        None => {}
706        Some(AddrPcc::Static32(ty, _size)) => {
707            pos.func.dfg.facts[heap_base] = Some(Fact::Mem {
708                ty,
709                min_offset: 0,
710                max_offset: 0,
711                nullable: false,
712            });
713        }
714        Some(AddrPcc::Dynamic(ty, _limit)) => {
715            pos.func.dfg.facts[heap_base] = Some(Fact::dynamic_base_ptr(ty));
716        }
717    }
718
719    let base_and_index = pos.ins().iadd(heap_base, index);
720
721    match pcc {
722        None => {}
723        Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {
724            if let Some(idx) = pos.func.dfg.facts[index]
725                .as_ref()
726                .and_then(|f| f.as_symbol())
727                .cloned()
728            {
729                pos.func.dfg.facts[base_and_index] = Some(Fact::DynamicMem {
730                    ty,
731                    min: idx.clone(),
732                    max: idx,
733                    nullable: false,
734                });
735            } else {
736                pos.func.dfg.facts[base_and_index] = Some(Fact::Mem {
737                    ty,
738                    min_offset: 0,
739                    max_offset: u64::from(u32::MAX),
740                    nullable: false,
741                });
742            }
743        }
744    }
745
746    if offset == 0 {
747        base_and_index
748    } else {
749        // NB: The addition of the offset immediate must happen *before* the
750        // `select_spectre_guard`, if any. If it happens after, then we
751        // potentially are letting speculative execution read the whole first
752        // 4GiB of memory.
753        let offset_val = pos.ins().iconst(addr_ty, i64::from(offset));
754
755        if pcc.is_some() {
756            pos.func.dfg.facts[offset_val] = Some(Fact::constant(
757                u16::try_from(addr_ty.bits()).unwrap(),
758                u64::from(offset),
759            ));
760        }
761
762        let result = pos.ins().iadd(base_and_index, offset_val);
763
764        match pcc {
765            None => {}
766            Some(AddrPcc::Static32(ty, _) | AddrPcc::Dynamic(ty, _)) => {
767                if let Some(idx) = pos.func.dfg.facts[index]
768                    .as_ref()
769                    .and_then(|f| f.as_symbol())
770                {
771                    pos.func.dfg.facts[result] = Some(Fact::DynamicMem {
772                        ty,
773                        min: idx.clone(),
774                        // Safety: adding an offset to an expression with
775                        // zero offset -- add cannot wrap, so `unwrap()`
776                        // cannot fail.
777                        max: Expr::offset(idx, i64::from(offset)).unwrap(),
778                        nullable: false,
779                    });
780                } else {
781                    pos.func.dfg.facts[result] = Some(Fact::Mem {
782                        ty,
783                        min_offset: u64::from(offset),
784                        // Safety: can't overflow -- two u32s summed in a
785                        // 64-bit add. TODO: when memory64 is supported here,
786                        // `u32::MAX` is no longer true, and we'll need to
787                        // handle overflow here.
788                        max_offset: u64::from(u32::MAX) + u64::from(offset),
789                        nullable: false,
790                    });
791                }
792            }
793        }
794        result
795    }
796}
797
798#[inline]
799fn offset_plus_size(offset: u32, size: u8) -> u64 {
800    // Cannot overflow because we are widening to `u64`.
801    offset as u64 + size as u64
802}
803
804/// Returns whether `index` is statically in-bounds with respect to this
805/// `heap`'s configuration.
806///
807/// This is `true` when `index` is a constant and when the offset/size are added
808/// in it's all still less than the minimum byte size of the heap.
809///
810/// The `offset_and_size` here are the static offset that was listed on the wasm
811/// instruction plus the size of the access being made.
812fn statically_in_bounds(
813    func: &ir::Function,
814    heap: &HeapData,
815    index: ir::Value,
816    offset_and_size: u64,
817) -> bool {
818    func.dfg
819        .value_def(index)
820        .inst()
821        .and_then(|i| {
822            let imm = match func.dfg.insts[i] {
823                ir::InstructionData::UnaryImm {
824                    opcode: ir::Opcode::Iconst,
825                    imm,
826                } => imm,
827                _ => return None,
828            };
829            let ty = func.dfg.value_type(index);
830            let index = imm.zero_extend_from_width(ty.bits()).bits().unsigned();
831            let final_addr = index.checked_add(offset_and_size)?;
832            Some(final_addr <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX))
833        })
834        .unwrap_or(false)
835}