|
| 1 | +//! Precise-store-traps pass. |
| 2 | +//! |
| 3 | +//! On some instruction-set architectures, a store that crosses a page |
| 4 | +//! boundary such that one of the pages would fault on a write can |
| 5 | +//! sometimes still perform part of its memory update on the other |
| 6 | +//! page. This becomes relevant, and problematic, when page |
| 7 | +//! protections are load-bearing for Wasm VM semantics: see [this |
| 8 | +//! issue] where a partially-out-of-bounds store in Wasm is currently |
| 9 | +//! defined to perform no side-effect, but with a common lowering on |
| 10 | +//! several ISAs and on some microarchitectures does actually perform |
| 11 | +//! a "torn write". |
| 12 | +//! |
| 13 | +//! [this issue]: https://github.com/WebAssembly/design/issues/1490 |
| 14 | +//! |
| 15 | +//! This pass performs a transform on CLIF that should avoid "torn |
| 16 | +//! partially-faulting stores" by performing a throwaway *load* before |
| 17 | +//! every store, of the same size and to the same address. This |
| 18 | +//! throwaway load will fault if the store would have faulted due to |
| 19 | +//! not-present pages (this still does nothing for |
| 20 | +//! readonly-page-faults). Because the load happens before the store |
| 21 | +//! in program order, if it faults, any ISA that guarantees precise |
| 22 | +//! exceptions (all ISAs that we support) will ensure that the store |
| 23 | +//! has no side-effects. (Microarchitecturally, once the faulting |
| 24 | +//! instruction retires, the later not-yet-retired entries in the |
| 25 | +//! store buffer will be flushed.) |
| 26 | +//! |
| 27 | +//! This is not on by default and remains an "experimental" option |
| 28 | +//! while the Wasm spec resolves this issue, and serves for now to |
| 29 | +//! allow collecting data on overheads and experimenting on affected |
| 30 | +//! machines. |
| 31 | +
|
| 32 | +use crate::cursor::{Cursor, FuncCursor}; |
| 33 | +use crate::ir::types::*; |
| 34 | +use crate::ir::*; |
| 35 | + |
| 36 | +fn covering_type_for_value(func: &Function, value: Value) -> Type { |
| 37 | + match func.dfg.value_type(value).bits() { |
| 38 | + 8 => I8, |
| 39 | + 16 => I16, |
| 40 | + 32 => I32, |
| 41 | + 64 => I64, |
| 42 | + 128 => I8X16, |
| 43 | + _ => unreachable!(), |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +/// Perform the precise-store-traps transform on a function body. |
| 48 | +pub fn do_precise_store_traps(func: &mut Function) { |
| 49 | + let mut pos = FuncCursor::new(func); |
| 50 | + while let Some(_block) = pos.next_block() { |
| 51 | + while let Some(inst) = pos.next_inst() { |
| 52 | + match &pos.func.dfg.insts[inst] { |
| 53 | + &InstructionData::StackStore { |
| 54 | + opcode: _, |
| 55 | + arg: data, |
| 56 | + stack_slot, |
| 57 | + offset, |
| 58 | + } => { |
| 59 | + let ty = covering_type_for_value(&pos.func, data); |
| 60 | + let _ = pos.ins().stack_load(ty, stack_slot, offset); |
| 61 | + } |
| 62 | + &InstructionData::DynamicStackStore { |
| 63 | + opcode: _, |
| 64 | + arg: data, |
| 65 | + dynamic_stack_slot, |
| 66 | + } => { |
| 67 | + let ty = covering_type_for_value(&pos.func, data); |
| 68 | + let _ = pos.ins().dynamic_stack_load(ty, dynamic_stack_slot); |
| 69 | + } |
| 70 | + &InstructionData::Store { |
| 71 | + opcode, |
| 72 | + args, |
| 73 | + flags, |
| 74 | + offset, |
| 75 | + } => { |
| 76 | + let (data, addr) = (args[0], args[1]); |
| 77 | + let ty = match opcode { |
| 78 | + Opcode::Store => covering_type_for_value(&pos.func, data), |
| 79 | + Opcode::Istore8 => I8, |
| 80 | + Opcode::Istore16 => I16, |
| 81 | + Opcode::Istore32 => I32, |
| 82 | + _ => unreachable!(), |
| 83 | + }; |
| 84 | + let _ = pos.ins().load(ty, flags, addr, offset); |
| 85 | + } |
| 86 | + &InstructionData::StoreNoOffset { |
| 87 | + opcode: Opcode::AtomicStore, |
| 88 | + args, |
| 89 | + flags, |
| 90 | + } => { |
| 91 | + let (data, addr) = (args[0], args[1]); |
| 92 | + let ty = covering_type_for_value(&pos.func, data); |
| 93 | + let _ = pos.ins().atomic_load(ty, flags, addr); |
| 94 | + } |
| 95 | + &InstructionData::AtomicCas { .. } | &InstructionData::AtomicRmw { .. } => { |
| 96 | + // Nothing: already does a read before the write. |
| 97 | + } |
| 98 | + &InstructionData::NullAry { |
| 99 | + opcode: Opcode::Debugtrap, |
| 100 | + } => { |
| 101 | + // Marked as `can_store`, but no concerns here. |
| 102 | + } |
| 103 | + inst => { |
| 104 | + assert!(!inst.opcode().can_store()); |
| 105 | + } |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | +} |
0 commit comments