Skip to content

Commit fc78952

Browse files
alexcrichtonfitzgendicejjlb6740nightkr
authored
[48.0.0] Backport some changes from main (#14106)
* Observe all stores we replace in `LastStores`; keep track of who observed a store (#14080) Fixes #14053 * update `WasiCtxBuilder::allow_{tcp,udp}` docs (#14089) PR #13936 disabled these settings by default but did not update the docs to match. * mpk: restore protection keys after mmap'ing memory images (#14076) * mpk: restore protection keys after mmap'ing memory images A fresh `mmap` associates the pages it replaces with the default protection key 0, and key 0 is accessible from every stripe (host code needs it). `MemoryImageSlot` maps over pkey-colored pool slots in three places, so any module with a `(data ...)` segment silently lost its key. Because MPK striping deliberately shrinks the guard regions between slots, a neighboring instance could then read and write that memory for real. Note that `mprotect` preserves the key, so only `mmap` sites are affected. Fix this by re-applying the key with `pkey_mprotect` after each `mmap`: add `ProtectionKey::reprotect`, give `MemoryImageSlot` the key its stripe was colored with, and call the new `reapply_pkey` helper after `map_at`, `remap_as_zeros_at`, and `erase_existing_mapping`. Tables, stacks, and GC heaps are never pkey-colored, and decommit uses `madvise(MADV_DONTNEED)` which preserves VMA flags, so `MemoryImageSlot` was the only exposure. Cost: one extra syscall per `mmap`, and `instantiate` only `mmap`s when a slot is handed a different image than it already holds. Measured over 1000 instantiations, a module repeatedly instantiated into its affine slot adds 8 calls total (one per slot, at first use) and is in the noise end-to-end. A pool thrashing between more modules than it has slots takes 2 extra calls per instantiation, ~+43% on instantiation. With MPK disabled `ProtectionKey` is uninhabited and this all compiles away. Fixes #13982 Fixes #7942 * prtest:full * wasmtime: Clarify that component::Linker doesn't support intra-component linking yet (#14088) * wasmtime: Clarify that component::Linker doesn't support intra-component linking yet https://bytecodealliance.zulipchat.com/#narrow/channel/217126-wasmtime/topic/.E2.9C.94.20linking.20wasm.20components.20at.20runtime.3F/near/615012938 The current state tripped me up a bit, since the docs make it sound like it's already there and working, while the API itself seems nowhere to be found. * Remove the mention of intra-component linking entirely #14088 (review) * Reflow the paragraph * Remove preemption points in bulk operations (#14045) * Remove preemption points in bulk operations This commit updates the translation of bulk operations such as `memory.grow` which were recently refactored to not have preemption points within the operation itself. Preemption points within the operation, while useful for very large operations, expose internal and intermediate state to embedders and the rest of the runtime. For example tables that are grown are initially filled with null, which may not be valid for the table's type. These bulk operations didn't recompute pointers/indices after a possible preemption meaning if memories were grown/moved then it would cause faults. In general this is seen as too risky of an operation to perform. The fix in this commit is to move all preemption checks to the start of the operation itself. This means that bulk operations continue to be metered with a cost proportional to the size of the operation for fuel, and they all contain an initial epoch check for epochs. Once the operation is committed to, however, there's no cancelling it and it'll continue to run. In practice this means that extremely large copies, for example, can blow the epoch budget. To re-add preemption checks within the operation, however, will require very careful reintroduction to avoid these sorts of problems/faults. * Fix miri * Fix `named_imports` with a hyphen in interface names (#14105) Closes #14090 --------- Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com> Co-authored-by: Joel Dice <joel.dice@fermyon.com> Co-authored-by: Johnnie Birch <johnnie.l.birch.jr@intel.com> Co-authored-by: Natalie Klestrup Röijezon <nat@nullable.se>
1 parent 45af25f commit fc78952

24 files changed

Lines changed: 1357 additions & 849 deletions

File tree

cranelift/codegen/src/alias_analysis.rs

Lines changed: 110 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,9 @@
7474
//! up front, in `AliasAnalysis::observed_stores`, rather than tracking
7575
//! it as part of the per-block `LastStores` state.
7676
77-
use crate::cursor::CursorPosition;
7877
use crate::{FxHashMap, FxHashSet};
7978
use crate::{
80-
cursor::{Cursor, FuncCursor},
79+
cursor::{Cursor, CursorPosition, FuncCursor},
8180
dominator_tree::DominatorTree,
8281
flowgraph::ControlFlowGraph,
8382
inst_predicates::{inst_addr_offset_type, inst_store_data, visit_block_succs},
@@ -144,6 +143,48 @@ fn alias_regions_observed(func: &Function, inst: Inst, opcode: Opcode) -> AliasR
144143
}
145144
}
146145

146+
/// Who was the observer of some store instruction?
147+
///
148+
/// `Option<Observer>` -- where `None` is logically represented by the absense
149+
/// of an entry in `AliasAnalysis::observed_stores` -- forms the following
150+
/// lattice:
151+
///
152+
/// ```ignore
153+
/// None
154+
/// / | \ \ \
155+
/// / | \ \ \
156+
/// / | \ \ \
157+
/// / | \ \ \
158+
/// inst0 inst1 instN...
159+
/// \ | / / /
160+
/// \ | / / /
161+
/// \ | / / /
162+
/// \ | / / /
163+
/// Many
164+
/// ```
165+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166+
pub(crate) enum Observer {
167+
/// There was exactly one observer: this instruction.
168+
One(Inst),
169+
/// There were many observers.
170+
Many,
171+
}
172+
173+
impl Observer {
174+
fn meet(a: Self, b: Self) -> Self {
175+
match (a, b) {
176+
(Observer::Many, _) | (_, Observer::Many) => Observer::Many,
177+
(Observer::One(a), Observer::One(b)) => {
178+
if a == b {
179+
Observer::One(a)
180+
} else {
181+
Observer::Many
182+
}
183+
}
184+
}
185+
}
186+
}
187+
147188
/// For a given program point, the last-store instruction for each disjoint
148189
/// category of abstract state.
149190
///
@@ -176,14 +217,23 @@ pub struct LastStores {
176217
}
177218

178219
/// Mark the store, if any, in the given last-store slot as observed.
179-
fn observe(func: &Function, observed_stores: &mut FxHashSet<Inst>, last_store: PackedOption<Inst>) {
180-
if let Some(inst) = last_store.expand() {
220+
fn observe(
221+
func: &Function,
222+
observed_stores: &mut FxHashMap<Inst, Observer>,
223+
last_store: PackedOption<Inst>,
224+
observer: Inst,
225+
) {
226+
if let Some(last_store) = last_store.expand() {
181227
// NB: last-store slots do not always hold stores; they can also hold
182228
// calls, fences, and the markers that `LastStores::meet_from` inserts
183229
// where two control-flow paths disagree. Only actual stores can be DSE
184230
// candidates, so don't bother recording other instructions as observed.
185-
if func.dfg.insts[inst].opcode().can_store() {
186-
observed_stores.insert(inst);
231+
if func.dfg.insts[last_store].opcode().can_store() {
232+
let entry = observed_stores
233+
.entry(last_store)
234+
.or_insert(Observer::One(observer));
235+
*entry = Observer::meet(*entry, Observer::One(observer));
236+
trace!(" observed_stores[{last_store:?}] = {entry:?}");
187237
}
188238
}
189239
}
@@ -193,7 +243,7 @@ impl LastStores {
193243
&mut self,
194244
func: &Function,
195245
inst: Inst,
196-
observed_stores: &mut FxHashSet<Inst>,
246+
observed_stores: &mut FxHashMap<Inst, Observer>,
197247
) {
198248
let opcode = func.dfg.insts[inst].opcode();
199249

@@ -207,7 +257,7 @@ impl LastStores {
207257
// state of memory on trap. We do this by marking every last-store as
208258
// observed, but not clearing our last-store information.
209259
else if opcode.can_trap() {
210-
self.observe_others(func, observed_stores, None);
260+
self.observe_others(func, observed_stores, None, inst);
211261
}
212262
// Store instructions: update the last-store information for this
213263
// instruction's alias region, or, if it has no alias region, treat it
@@ -216,30 +266,7 @@ impl LastStores {
216266
if let Some(memflags) = func.dfg.insts[inst].memflags() {
217267
match func.dfg.mem_flags[memflags].alias_region() {
218268
Some(region) => {
219-
// NB: The old last-store instruction is *not* observed
220-
// here, even though this new store instruction may not
221-
// fully overwrite it. First, a new store in a block
222-
// does not itself observe an old store in the same
223-
// block. Second, the old store will never be an
224-
// optimization candidate again from here on out:
225-
//
226-
// * We won't consider it again as we process the rest
227-
// of this block, as it won't be in the last-store
228-
// slot anymore.
229-
//
230-
// * What if we re-process this block in our initial
231-
// fixed point loop? That implies this block is a
232-
// member of a cycle in the CFG, but `meet_from` only
233-
// propagates a store instruction when all
234-
// predecessors agree on the same last-store
235-
// instruction, but the predecessors already won't
236-
// agree it is the old store since this block (which
237-
// is on that path and therefore some kind of
238-
// transitive predecessor) has already overridden it.
239-
//
240-
// Therefore, marking the old last-store as observed
241-
// here is unnecessary (and, in fact, doing so would
242-
// only inhibit optimization).
269+
observe(func, observed_stores, self.regions[region], inst);
243270
self.regions[region] = inst.into();
244271

245272
// If this store can trap, then we need to observe
@@ -282,9 +309,9 @@ impl LastStores {
282309
// incorrectly store to `v4+16`, when we otherwise
283310
// wouldn't have.
284311
if func.dfg.mem_flags[memflags].trap_code().is_some() {
285-
self.observe_others(func, observed_stores, Some(region));
312+
self.observe_others(func, observed_stores, Some(region), inst);
286313
} else {
287-
self.observe_trapping_others(func, observed_stores, region);
314+
self.observe_trapping_others(func, observed_stores, region, inst);
288315
}
289316
}
290317
None => {
@@ -303,15 +330,22 @@ impl LastStores {
303330
// instruction observes.
304331
else {
305332
match alias_regions_observed(func, inst, opcode) {
306-
AliasRegionsObserved::All => self.observe_others(func, observed_stores, None),
333+
AliasRegionsObserved::All => self.observe_others(func, observed_stores, None, inst),
307334
AliasRegionsObserved::Just(region) => {
308-
observe(func, observed_stores, self.last_store_for_region(region));
335+
observe(
336+
func,
337+
observed_stores,
338+
self.last_store_for_region(region),
339+
inst,
340+
);
309341
// NB: Because stores without regions may alias any other
310342
// region, we have also observed the last such store, which
311343
// `self.last_fence` tracks.
312-
observe(func, observed_stores, self.last_fence);
344+
observe(func, observed_stores, self.last_fence, inst);
345+
}
346+
AliasRegionsObserved::Other => {
347+
observe(func, observed_stores, self.last_fence, inst)
313348
}
314-
AliasRegionsObserved::Other => observe(func, observed_stores, self.last_fence),
315349
AliasRegionsObserved::None => {}
316350
}
317351
}
@@ -322,24 +356,26 @@ impl LastStores {
322356
fn observe_others(
323357
&self,
324358
func: &Function,
325-
observed_stores: &mut FxHashSet<Inst>,
359+
observed_stores: &mut FxHashMap<Inst, Observer>,
326360
excluding: Option<AliasRegion>,
361+
observer: Inst,
327362
) {
328363
for (region, last_store) in self.regions.iter() {
329364
if excluding.is_none_or(|r| r != region) {
330-
observe(func, observed_stores, *last_store);
365+
observe(func, observed_stores, *last_store, observer);
331366
}
332367
}
333-
observe(func, observed_stores, self.last_fence);
368+
observe(func, observed_stores, self.last_fence, observer);
334369
}
335370

336371
/// Mark the last store to every region whose last store can trap, except for
337372
/// `excluding`, as observed.
338373
fn observe_trapping_others(
339374
&self,
340375
func: &Function,
341-
observed_stores: &mut FxHashSet<Inst>,
376+
observed_stores: &mut FxHashMap<Inst, Observer>,
342377
excluding: AliasRegion,
378+
observer: Inst,
343379
) {
344380
let can_trap = |last_store: PackedOption<Inst>| {
345381
last_store
@@ -349,21 +385,26 @@ impl LastStores {
349385

350386
for (region, last_store) in self.regions.iter() {
351387
if region != excluding && can_trap(*last_store) {
352-
observe(func, observed_stores, *last_store);
388+
observe(func, observed_stores, *last_store, observer);
353389
}
354390
}
355391

356392
if can_trap(self.last_fence) {
357-
observe(func, observed_stores, self.last_fence);
393+
observe(func, observed_stores, self.last_fence, observer);
358394
}
359395
}
360396

361397
/// Handle memory fence-like instructions by clearing all analysis data.
362-
fn fence(&mut self, func: &Function, inst: Inst, observed_stores: &mut FxHashSet<Inst>) {
398+
fn fence(
399+
&mut self,
400+
func: &Function,
401+
inst: Inst,
402+
observed_stores: &mut FxHashMap<Inst, Observer>,
403+
) {
363404
// A fence can observe every region, so every store we are currently
364405
// tracking for a region becomes observed.
365406
for (_region, last_store) in self.regions.iter() {
366-
observe(func, observed_stores, *last_store);
407+
observe(func, observed_stores, *last_store, inst);
367408
}
368409
self.regions.clear();
369410

@@ -409,7 +450,7 @@ impl LastStores {
409450
func: &Function,
410451
rhs: &LastStores,
411452
loc: Inst,
412-
observed_stores: &mut FxHashSet<Inst>,
453+
observed_stores: &mut FxHashMap<Inst, Observer>,
413454
) -> bool {
414455
// NB: Destructure to make sure we don't accidentally forget a
415456
// field.
@@ -418,7 +459,7 @@ impl LastStores {
418459
last_fence,
419460
} = self;
420461

421-
let meet = |observed_stores: &mut FxHashSet<Inst>,
462+
let meet = |observed_stores: &mut FxHashMap<Inst, Observer>,
422463
a: &mut PackedOption<Inst>,
423464
b: PackedOption<Inst>|
424465
-> bool {
@@ -433,8 +474,8 @@ impl LastStores {
433474
// mark them both observed here. This keeps the
434475
// observed-stores set sound in the presence of loops and
435476
// control-flow join points.
436-
observe(func, observed_stores, x.filter(|x| *x != loc).into());
437-
observe(func, observed_stores, y.filter(|y| *y != loc).into());
477+
observe(func, observed_stores, x.filter(|x| *x != loc).into(), loc);
478+
observe(func, observed_stores, y.filter(|y| *y != loc).into(), loc);
438479
Some(loc)
439480
}
440481
};
@@ -527,7 +568,7 @@ pub struct AliasAnalysis<'a> {
527568
/// Unlike the last-store state in `block_input`, this is *not* flow
528569
/// sensitive: a store is either observable somewhere in the function or it
529570
/// is not.
530-
observed_stores: FxHashSet<Inst>,
571+
observed_stores: FxHashMap<Inst, Observer>,
531572

532573
/// Input state to a basic block.
533574
block_input: FxHashMap<Block, LastStores>,
@@ -543,12 +584,12 @@ pub struct AliasAnalysis<'a> {
543584
impl<'a> AliasAnalysis<'a> {
544585
/// Perform an alias analysis pass.
545586
pub fn new(func: &Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> {
546-
trace!("alias analysis: input is:\n{:?}", func);
587+
trace!("alias analysis input is:\n{func:?}");
547588
assert!(domtree.is_valid());
548589
let mut analysis = AliasAnalysis {
549590
domtree,
550591
post_dom_tree: None,
551-
observed_stores: FxHashSet::default(),
592+
observed_stores: FxHashMap::default(),
552593
block_input: FxHashMap::default(),
553594
mem_values: FxHashMap::default(),
554595
};
@@ -604,15 +645,13 @@ impl<'a> AliasAnalysis<'a> {
604645
.or_insert_with(|| LastStores::default())
605646
.clone();
606647

607-
trace!(
608-
"alias analysis: input to block{} is {:?}",
609-
block.index(),
610-
state
611-
);
648+
trace!("analyzing {block:?}");
649+
trace!(" initial block state = {state:?}");
612650

613651
for inst in func.layout.block_insts(block) {
652+
trace!(" analyzing {inst:?}: {}", func.dfg.display_inst(inst));
614653
state.update(func, inst, &mut self.observed_stores);
615-
trace!("after inst{}: state is {:?}", inst.index(), state);
654+
trace!(" updated state = {state:?}");
616655
}
617656

618657
visit_block_succs(func, block, |_inst, succ, _from_table| {
@@ -635,6 +674,8 @@ impl<'a> AliasAnalysis<'a> {
635674
}
636675
});
637676
}
677+
678+
trace!("final observed_stores = {:#?}", self.observed_stores);
638679
}
639680

640681
/// Get the starting state for a block.
@@ -673,8 +714,9 @@ impl<'a> AliasAnalysis<'a> {
673714

674715
// Check whether this store makes the last store dead.
675716
if let Some(last_store) = last_store.expand() {
676-
// A store can only be dead when unobserved.
677-
if !self.observed_stores.contains(&last_store)
717+
// A store can only be dead when unobserved or only observed
718+
// by its overwriter.
719+
if self.observed_stores.get(&last_store).is_none_or(|o| *o == Observer::One(inst))
678720
// This instruction doesn't make the last
679721
// store dead if it itself is the last store.
680722
&& inst != last_store
@@ -742,8 +784,13 @@ impl<'a> AliasAnalysis<'a> {
742784
// We are removing this idempotent store in favor of the
743785
// original, so if this idempotent store was observed,
744786
// then the original must now be observed as well.
745-
if self.observed_stores.contains(&inst) {
746-
observe(func, &mut self.observed_stores, last_store);
787+
if let Some(last_store) = last_store.expand() {
788+
if let Some(observer) = self.observed_stores.get(&inst).copied() {
789+
let entry =
790+
self.observed_stores.entry(last_store).or_insert(observer);
791+
*entry = Observer::meet(*entry, observer);
792+
trace!(" observed_stores[{last_store:?}] = {entry:?}");
793+
}
747794
}
748795

749796
return OptResult::IdempotentStore;

0 commit comments

Comments
 (0)