From 3a0d72457bfd56dbe3de4fef6e52376d3f442368 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 11 May 2026 13:42:53 -0700 Subject: [PATCH] interp: fix partial store aliasing with previously loaded values The optimization to skip cloning the destination object on partial stores when the buffer is already owned by the current memory view mutated obj.buffer.buf in place. The previous v.buf clone only handled the case where the store value itself aliased that buffer; it did not cover the more common case where an earlier partial load had returned a slice into the same buffer. The in-place store would then corrupt the previously loaded value, breaking code such as cloudflare/bn256 where the gfP arithmetic loads, computes, and stores within the same global during package initialization. Fix this in load instead: when the source object is owned by the current memory view, copy the loaded slice so the returned value is independent of the live buffer. The v.buf clone in store is no longer needed and is removed. Updates the regression test added in the previous commit to reflect the corrected behavior. --- interp/memory.go | 14 +++++++------- interp/testdata/store.out.ll | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interp/memory.go b/interp/memory.go index 8b7834787..3c777d08e 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -316,10 +316,14 @@ func (mv *memoryView) load(p pointerValue, size uint32) value { panic("interp: load out of bounds") } v := obj.buffer.asRawValue(mv.r) - loadedValue := rawValue{ - buf: v.buf[p.offset() : p.offset()+size], + loadedBuf := v.buf[p.offset() : p.offset()+size] + if _, writable := mv.objects[p.index()]; writable { + // This object's buffer is owned by this view, which means a later + // store may mutate it in place (see store below). Copy the loaded + // slice so the returned value is not aliased with the live buffer. + loadedBuf = append([]uint64(nil), loadedBuf...) } - return loadedValue + return rawValue{buf: loadedBuf} } // Store to the value behind the given pointer. This overwrites the value in the @@ -356,10 +360,6 @@ func (mv *memoryView) store(v value, p pointerValue) bool { buffer := obj.buffer.asRawValue(mv.r) obj.buffer = buffer v := v.asRawValue(mv.r) - if writable { - // A partial load from this object may share the destination buffer. - v.buf = append([]uint64(nil), v.buf...) - } for i := uint32(0); i < valueLen; i++ { buffer.buf[p.offset()+i] = v.buf[i] } diff --git a/interp/testdata/store.out.ll b/interp/testdata/store.out.ll index 6c0f95052..d86e5b0cc 100644 --- a/interp/testdata/store.out.ll +++ b/interp/testdata/store.out.ll @@ -5,7 +5,7 @@ target triple = "x86_64--linux" @alias.src = local_unnamed_addr global [4 x i8] c"\05\06\07\08" @alias.dst = local_unnamed_addr global [2 x i8] c"\09\07" @reload.buf = local_unnamed_addr global [4 x i8] c"c\02\03\09" -@reload.out = local_unnamed_addr global [2 x i8] c"c\02" +@reload.out = local_unnamed_addr global [2 x i8] c"\01\02" define void @runtime.initAll() unnamed_addr { entry: