Stream: git-wasmtime

Topic: wasmtime / issue #14323 Regionless-loads can return stale...


view this post on Zulip Wasmtime GitHub notifications bot (Sep 11 2026 at 21:09):

alexcrichton opened issue #14323:

This test:

test interpret
test run
set opt_level=speed
target x86_64
target aarch64

;; A load with no alias region may alias any region, so it must observe a
;; preceding store that carries an alias region. Alias analysis instead keys
;; the regionless load on `last_fence` only and forwards the value of the
;; earlier region0 load (v1 = 1), dropping the intervening store of 42.
function %regionless_load_after_region_store(i64) -> i32 {
    ss0 = explicit_slot 4
    region0 = 0 "heap"
block0(v0: i64):
    v9 = stack_addr.i64 ss0
    v8 = iconst.i32 1
    store notrap v8, v9
    v1 = load.i32 notrap region0 v9
    v2 = iconst.i32 42
    store notrap region0 v2, v9
    v3 = load.i32 notrap v9
    v4 = iadd v3, v1
    return v4
}
; run: %regionless_load_after_region_store(0) == 43

fails with:

$ cargo run -p cranelift-tools test ./regionless-load.clif
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running `/home/alex/code/wasmtime2/target/debug/clif-util test ./regionless-load.clif`
[2026-09-11T21:06:41Z ERROR cranelift_filetests::concurrent] FAIL: run
FAIL ./regionless-load.clif: run

Caused by:
    Failed test: run: %regionless_load_after_region_store(0) == 43, actual: 2
1 tests
Error: 1 failure

and a similar test case:

test interpret
test run
set opt_level=speed
target x86_64

function %dse(i64) -> i32 {
    ss0 = explicit_slot 4
    region0 = 0 "heap"
block0(v0: i64):
    v9 = stack_addr.i64 ss0
    v2 = iconst.i32 42
    store notrap region0 v2, v9
    v3 = load.i32 notrap v9
    v5 = iconst.i32 7
    store notrap region0 v5, v9
    return v3
}
; run: %dse(0) == 42

fails with:

$ cargo run -p cranelift-tools test ./regionless-load-dse.clif
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
     Running `/home/alex/code/wasmtime2/target/debug/clif-util test ./regionless-load-dse.clif`
[2026-09-11T21:07:29Z ERROR cranelift_filetests::concurrent] FAIL: run
FAIL ./regionless-load-dse.clif: run

Caused by:
    Failed test: run: %dse(0) == 42, actual: -2147474110
1 tests
Error: 1 failure

a full llm-generated report is here if that's useful. I'm relatively certain that this is not UB and the tests here are correct, but I'm not 100% certain. If this is IR-level UB then this can just be closed.

cc @fitzgen

view this post on Zulip Wasmtime GitHub notifications bot (Sep 11 2026 at 21:09):

alexcrichton added the cranelift label to Issue #14323.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 02:32):

agourakis82 commented on issue #14323:

Opened a fix: https://github.com/bytecodealliance/wasmtime/pull/$(gh pr list --repo bytecodealliance/wasmtime --head agourakis82:users/agourakis/cranelift-regionless-load-alias-fence --json number -q '.[0].number')

Root cause: regionless loads were keyed/observed only via last_fence, while named-region stores update only regions[r]. That let forwarding/DSE ignore an intervening region0 store that a later regionless load can still see.

The PR makes regionless loads meet all region slots for versioning and observe all region stores.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 02:33):

agourakis82 commented on issue #14323:

Concrete PR link: https://github.com/bytecodealliance/wasmtime/pull/14373

Root cause: regionless loads were keyed/observed only via last_fence, while named-region stores update only regions[r]. That let forwarding/DSE ignore an intervening region0 store that a later regionless load can still see.

The PR makes regionless loads meet all region slots for versioning and observe all region stores.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 04:20):

cfallin commented on issue #14323:

(Sorry, just saw this because of the recent comments bumping it)

In my understanding at least, "no region" is a separate region than a concrete region. The definition here is ambiguous, so happy to discuss at the Cranelift meeting and we can pin it down one way or another.

(I don't care too strongly either way, but I believe it's a little clearer to think of it in the above way, i.e. it's a simpler mental model not to have special "globally aliasing" operations; it fits how we use regions in Wasmtime just fine; and it is simpler and less error-prone to implement.)

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 14:48):

agourakis82 commented on issue #14323:

Thanks @cfallin for the clarification.

Agreed the docs are ambiguous today. I opened #14374-adjacent work as #14373 under Alex’s reading; happy to park/close that PR if the meeting settles on “no region is a separate disjoint region” (which would make these tests invalid CLIF rather than an alias bug).

Will follow whatever invariant you all pin down.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 14:50):

agourakis82 commented on issue #14323:

Design pin-down attempt (store/load asymmetry)

@cfallin @fitzgen @alexcrichton — trying to make the meeting discussion cheap by writing down what the code already does.

Today, regionless stores and regionless loads are not symmetric:

  1. Regionless store → fence / globally aliasing (in tree):
    rust // A store with no memflags, and therefore no alias region, may alias // any region, so treat it like a fence.
    And named-region loads already observe last_fence because regionless stores may alias them.

  2. Regionless load → only last_fence / "other" (in tree before #14373):
    so a later store region0 does not invalidate / get observed by a regionless load to the same address — which is exactly Alex's repro.

So we currently have two half-models at once:

Two coherent end-states

Model Regionless store Regionless load Diff size Fits Wasmtime usage?
G – globally-aliasing absence (Alex / #14373) fence (already) observe+version over all regions small, load-side only conservative; safe if a frontend forgets a tag
D – disjoint other (Chris's preference) only touch "other" (must stop being a fence) only observe "other" (today's load behavior) larger: demote regionless stores from fence simpler mental model if frontends never omit tags on real aliases

I don't want to force G. But I also don't think D is "what the code means today" — today's store path already chose G for writes.

Proposal

  1. Explicitly pick G or D in the meeting / here.
  2. If G: keep #14373 (maybe with doc wording tightened to "absence of region means may-alias-all", matching the store comment).
  3. If D: close #14373, change regionless stores to non-fence "other", update no-region.clif / docs, and treat Alex's tests as invalid CLIF under the frontend invariant.

Happy to implement whichever side you pin. My only strong preference is ending the split-brain between load and store.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:22):

cfallin commented on issue #14323:

// A store with no memflags, and therefore no alias region, may alias // any region, so treat it like a fence.

Interesting -- for whatever it's worth, this appears to be a behavior change with the new alias analysis work. Looking at the code as of here (Jan of this year), which is the original fixed-four-region design, no-region is explicitly a separate region when handling stores (and loads are consistent with this).

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:22):

agourakis82 commented on issue #14323:

Update: Model D is falsified by an in-tree runtest

I tried implementing Chris’s Model D locally (regionless = disjoint other, demote regionless stores from fence). It fails alias/issue-13508.clif:

store notrap v3, v0          ;; regionless — comment says "clobbers cache"
...
v8 = load.i32 region0 v0     ;; must NOT reuse the pre-store cached value
; run: %g() == [-1, -1, 42]  ;; the 42 comes from the regionless store

That test’s run expectation requires a regionless store to be visible to a later named-region load of the same address. Under Model D that result is impossible without calling the IR invalid — but the test is intentionally valid CLIF with a concrete semantic check.

So the code already committed to Model G for stores. The remaining bug in #14323 is just the load-side asymmetry.

Conclusion: keep Model G (this PR): regionless means may-alias-all on both loads and stores. Model D would require rewriting issue-13508 and changing long-standing store-as-fence behavior.

@cfallin @fitzgen — given issue-13508, are you OK locking Model G as the invariant and taking #14373?

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:29):

cfallin commented on issue #14323:

Well, the test encodes the new semantics. I wouldn't say that that "falsifies" the choice. If we make the other choice, we write a test that encodes those semantics. The test appears to have been added as part of the new alias analysis work (and I even approved it and missed this change, oops!). So I think I'd still like to have the discussion, especially since this diverges from what we originally had.

(As an aside, please (i) don't use AI to write comments, and (ii) let's not attach design points to specific people; it distracts from the technical points and the real reasons we might prefer one or the other. Let's say "explicit no-region" and "no-region as fence", with the original semantics being the former and current semantics being the latter.)

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:33):

agourakis82 commented on issue #14323:

Fair point on the test — it encodes the current semantics, it doesn’t prove them. And thanks for the pointer to the January code; I hadn’t checked that the original fixed-four-region path treated no-region as its own store bucket.

Happy to wait for the meeting. I’ll use “explicit no-region” vs “no-region as fence” from here.

If the outcome is explicit no-region (restore the January behavior for stores, keep loads matching), I can rework #14373 that way instead of extending fence-to-loads. If it’s no-region as fence, the current patch direction stays.

Sorry about the earlier tone/shape of the comments.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

Opened #14373 for this.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

PR: https://github.com/bytecodealliance/wasmtime/pull/14373

Before the patch, a regionless load only looked at last_fence, so it could miss a later region0 store to the same address.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

Thanks. Docs are ambiguous either way.

#14373 follows the reading in the issue report. If the meeting lands on explicit no-region, I can close/rework it.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

One concrete asymmetry in the current code:

So loads and stores disagree today. The two clean options seem to be:

  1. no-region as fence — make loads match the current store behavior (#14373)
  2. explicit no-region — restore the older store behavior (own bucket), keep loads as they are

I’m fine implementing either once that’s pinned down.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

I did try the explicit-no-region store change locally. It breaks alias/issue-13508.clif under the current expectations ([-1,-1,42]), because that runtest wants a regionless store to be visible to a later named-region load.

That doesn’t settle the design — as noted below, the test encodes the newer semantics. Just recording what I hit when I tried the other direction.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 15:36):

agourakis82 edited a comment on issue #14323:

Fair point on the test — it encodes the current semantics, it doesn’t prove them. Thanks for the January pointer; I hadn’t checked that the original path treated no-region as its own store bucket.

I’ll wait for the meeting and stick to “explicit no-region” vs “no-region as fence”.

If it’s explicit no-region, I can rework #14373 that way. If it’s no-region as fence, the current direction stays.

Sorry about the earlier comments.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 16:02):

alexcrichton commented on issue #14323:

I've personally got no horse in this race. This was an LLM-found issue which smelled fishy to me so I opened an issue, but if the conclusion is that this is working as intended then I have no objection to just closing this.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 17:07):

fitzgen commented on issue #14323:

I thought that we _always_ treated regionless stores as fences that alias everything, and regionless loads as limited to the implicit "anonymous" region, and I've been somewhat bending over backwards to preserve those semantics. But given this comment it seems like I misunderstood the existing semantics and made all this up. If that's the case, then it would be much easier for us long term to simplify the semanticsand stop treating regionless stores as fences and instead just as stores to the "anonymous" region.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 22 2026 at 17:12):

agourakis82 commented on issue #14323:

Reworked #14373 to restore explicit no-region (loads and stores both use the disjoint “other” bucket; only real fences touch everything). That matches the older alias-analysis behavior.

Under that rule, the original same-address mixed-tag example is outside the frontend invariant. If that is the meeting outcome, we can close this when the PR lands.


Last updated: Oct 11 2026 at 02:20 UTC