From de9a321f0221a8ea0f16222d133a60fa6b0084c3 Mon Sep 17 00:00:00 2001 From: William Gilmore Date: Sun, 11 Oct 2026 12:20:50 -0500 Subject: [PATCH 1/2] Fix code generation for exported shared memory Owned shared memory modules exported a Memory view by casting the module's []byte to *wasmMemory, but Waiters() was generated on that slice alias and referenced m.waiters, which does not exist on []byte. Generated packages then failed to compile (e.g. m.waiters undefined). Model shared owned memory as a wasmMemory struct holding *[]byte and *sync.Map, and return a composite literal from memory exports so Waiters() is wired to the module's waiter map. Add a minimal shared_export regression fixture and test; refresh provided_helper golden output from translate. --- module.go | 100 ++++++--- regression_test.go | 12 ++ .../regression/shared_export/shared_export.go | 201 ++++++++++++++++++ .../shared_export/shared_export.wasm | Bin 0 -> 61 bytes .../shared_export/shared_export.wat | 6 + translate_test.go | 4 + 6 files changed, 292 insertions(+), 31 deletions(-) create mode 100644 testdata/regression/shared_export/shared_export.go create mode 100644 testdata/regression/shared_export/shared_export.wasm create mode 100644 testdata/regression/shared_export/shared_export.wat diff --git a/module.go b/module.go index 63b06fba..be6233de 100644 --- a/module.go +++ b/module.go @@ -402,43 +402,70 @@ func (t *translator) createMemoryTypes() []ast.Decl { name = "atomic_memory_grow" needsUnsafe("shared memory") } - - decls = append(decls, &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{&ast.TypeSpec{ - Name: newID("wasmMemory"), - Type: &ast.ArrayType{Elt: newID("byte")}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, - Name: newID("Slice"), - Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, - Args: []ast.Expr{newID("m")}}}}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, - Name: newID("Grow"), - Type: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: newID(name), - Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, - Args: []ast.Expr{newID("m")}}, - newID("delta"), - newID("max")}}}}}}}) t.helpers.add(name) if t.memory.shared { - decls = append(decls, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, + decls = append(decls, &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{&ast.TypeSpec{ + Name: newID("wasmMemory"), + Type: &ast.StructType{Fields: &ast.FieldList{List: []*ast.Field{ + {Names: []*ast.Ident{newID("memory")}, Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}, + {Names: []*ast.Ident{newID("waiters")}, Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}, + }}}}}, + }, &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, + Name: newID("Slice"), + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{ + &ast.SelectorExpr{X: newID("m"), Sel: newID("memory")}}}}}, + }, &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, + Name: newID("Grow"), + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, + Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: newID(name), + Args: []ast.Expr{ + &ast.SelectorExpr{X: newID("m"), Sel: newID("memory")}, + newID("delta"), + newID("max")}}}}}}, + }, &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, Name: newID("Waiters"), Type: &ast.FuncType{ Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}}}}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{ - modSel(newID("waiters"))}}}}}) + &ast.SelectorExpr{X: newID("m"), Sel: newID("waiters")}}}}}, + }) + } else { + decls = append(decls, &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{&ast.TypeSpec{ + Name: newID("wasmMemory"), + Type: &ast.ArrayType{Elt: newID("byte")}}}, + }, &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, + Name: newID("Slice"), + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, + Args: []ast.Expr{newID("m")}}}}}}, + }, &ast.FuncDecl{ + Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, + Name: newID("Grow"), + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, + Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: newID(name), + Args: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, + Args: []ast.Expr{newID("m")}}, + newID("delta"), + newID("max")}}}}}}}) } } return decls @@ -499,7 +526,18 @@ func (t *translator) createExportMethods() []ast.Decl { case externMemory: decl.Type.Results = &ast.FieldList{List: []*ast.Field{{Type: newID("Memory")}}} if t.memory.imported { - decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{modSel(newID("memImp"))}}} + decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.SelectorExpr{X: newID("m"), Sel: newID("memImp")}}}} + } else if t.memory.shared { + decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CompositeLit{ + Type: newID("wasmMemory"), + Elts: []ast.Expr{ + &ast.KeyValueExpr{ + Key: newID("memory"), + Value: &ast.UnaryExpr{Op: token.AND, X: &ast.SelectorExpr{X: newID("m"), Sel: t.memory.id}}}, + &ast.KeyValueExpr{ + Key: newID("waiters"), + Value: &ast.SelectorExpr{X: newID("m"), Sel: newID("waiters")}}, + }}}}} } else { decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ Fun: &ast.ParenExpr{X: &ast.StarExpr{X: newID("wasmMemory")}}, diff --git a/regression_test.go b/regression_test.go index df326544..c3177a2d 100644 --- a/regression_test.go +++ b/regression_test.go @@ -10,6 +10,7 @@ import ( oob_trap_test "github.com/ncruces/wasm2go/testdata/regression/oob_trap" provided_helper_test "github.com/ncruces/wasm2go/testdata/regression/provided_helper" select_test "github.com/ncruces/wasm2go/testdata/regression/select_effect" + shared_export_test "github.com/ncruces/wasm2go/testdata/regression/shared_export" store_grow_test "github.com/ncruces/wasm2go/testdata/regression/store_grow" ) @@ -132,6 +133,17 @@ func Test_regression_bulk_grow(t *testing.T) { mustTrap("table.fill past grown end", func() { m.Xtfill(3, 1) }) } +func Test_regression_shared_export(t *testing.T) { + m := shared_export_test.New() + mem := m.Xmemory() + if mem.Waiters() == nil { + t.Fatal("Waiters() is nil") + } + if got := m.Xwait0(); got != 2 { + t.Errorf("wait0() = %d, want 2 (timed out)", got) + } +} + func Test_regression_provided_helper(t *testing.T) { m := provided_helper_test.New() diff --git a/testdata/regression/shared_export/shared_export.go b/testdata/regression/shared_export/shared_export.go new file mode 100644 index 00000000..9df150d7 --- /dev/null +++ b/testdata/regression/shared_export/shared_export.go @@ -0,0 +1,201 @@ +// Code generated by wasm2go (devel). DO NOT EDIT. + +package wasm2go + +import ( + "math" + "math/bits" + "runtime" + "sync" + "sync/atomic" + "time" + "unsafe" +) + +type Module struct { + memory []byte + maxMem int64 + waiters *sync.Map +} + +func New() *Module { + m := new(Module) + m.maxMem = 1 + m.memory = make([]byte, 65536, 65536) + m.waiters = new(sync.Map) + return m +} + +type Memory = interface { + Slice() *[]byte + Grow(delta, max int64) int64 + Waiters() *sync.Map +} +type wasmMemory struct { + memory *[]byte + waiters *sync.Map +} + +func (m wasmMemory) Slice() *[]byte { + return m.memory +} +func (m wasmMemory) Grow(delta, max int64) int64 { + return atomic_memory_grow(m.memory, delta, max) +} +func (m wasmMemory) Waiters() *sync.Map { + return m.waiters +} +func (m *Module) Xwait0() int32 { + t0 := atomic_wait32(m.memory, uint32(i32(0)), uint32(i32(0)), i64(0), m.waiters) + return t0 +} +func (m *Module) Xmemory() Memory { + return wasmMemory{memory: &m.memory, waiters: m.waiters} +} + +// Compiler error if endianess is unknown. +var _ = map[bool]struct{}{big: {}, little: {}} + +// go.dev/src/cmd/compile/internal/ssa/config.go +const ( + big = false || + runtime.GOARCH == "ppc64" || runtime.GOARCH == "s390x" || + runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" + + little = false || + runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || + runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" || + runtime.GOARCH == "riscv64" || runtime.GOARCH == "wasm" || + runtime.GOARCH == "ppc64le" || runtime.GOARCH == "loong64" || + runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64le" + + unalignedOK = false || + runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || + runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64" || + runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || + runtime.GOARCH == "s390x" || runtime.GOARCH == "wasm" +) + +//go:nosplit +func atomic_wait32[T uint32 | uint64](mem []byte, addr T, exp uint32, timeout int64, waiters *sync.Map) int32 { + if big { + exp = bits.ReverseBytes32(exp) + } + ptr := atomic_ptr32(mem, addr) + return atomic_wait(int64(addr), timeout, waiters, func() bool { + return exp == atomic.LoadUint32(ptr) + }) +} + +func atomic_wait(addr, timeout int64, waiters *sync.Map, equal func() bool) int32 { + const ( + ok = 0 + not_equal = 1 + timed_out = 2 + ) + + if waiters == nil { + panic("expected shared memory") + } + if timeout == 0 { + if !equal() { + return not_equal + } + return timed_out + } + + wa, loaded := waiters.Load(addr) + if !loaded { + wa, _ = waiters.LoadOrStore(addr, &atomic_waiters{}) + } + w := wa.(*atomic_waiters) + w.Lock() + + switch { + case !equal(): + w.Unlock() + return not_equal + case w.C == nil: + w.C = make(chan struct{}, min(math.MaxInt, math.MaxUint32)) + case w.N >= cap(w.C): + w.Unlock() + panic("too many waiters") + } + + wait := w.C + w.N++ + w.Unlock() + + if timeout < 0 { + <-wait + return ok + } + + timer := time.NewTimer(time.Duration(timeout)) + select { + case <-wait: + timer.Stop() + return ok + case <-timer.C: + } + + w.Lock() + if w.N > 0 { + w.N-- + w.Unlock() + return timed_out + } + <-wait + w.Unlock() + return ok +} + +//go:nosplit +func atomic_ptr32[T uint32 | uint64](mem []byte, addr T) *uint32 { + if uint32(addr)&3 != 0 { + panic("unaligned atomic") + } + return (*uint32)(unsafe.Pointer(&mem[addr])) +} + +type atomic_waiters = struct { + C chan struct{} // +checklocks:Mutex + N int // +checklocks:Mutex + sync.Mutex +} + +func atomic_memory_grow(mem *[]byte, delta, max int64) int64 { + // Compiler error if sizeof(int) != sizeof(uintptr). + var _ [unsafe.Sizeof(int(0))]byte = [unsafe.Sizeof(uintptr(0))]byte{} + + type slice struct { + _ uintptr + len atomic.Uintptr + cap uintptr + } + + hdr := (*slice)(unsafe.Pointer(mem)) + max = min(max, int64(hdr.cap)>>16) + for { + len := int64(hdr.len.Load()) + old := len >> 16 + if delta == 0 { + return old + } + + new := old + delta + if new > max || new < old { + return -1 + } + + if hdr.len.CompareAndSwap(uintptr(len), uintptr(new<<16)) { + return old + } + } +} + +//go:nosplit +func i32(x int32) int32 { return x } + +//go:nosplit +func i64(x int64) int64 { return x } diff --git a/testdata/regression/shared_export/shared_export.wasm b/testdata/regression/shared_export/shared_export.wasm new file mode 100644 index 0000000000000000000000000000000000000000..605edb6044a5d26fb881d27ad8da55151e6e4d96 GIT binary patch literal 61 zcmZQbEY4+QU|?WmWlUgTtY>CoWME}sWM*V!7h+<|P0h_Os$^ndEl Date: Thu, 15 Oct 2026 18:26:09 +0100 Subject: [PATCH 2/2] Embed wasmMemory in Module Rather than special casing shared memory, make wasmMemory a struct that holds the owned memory (and waiters), and embed it in Module. Field promotion keeps m.memory/m.waiters working unchanged, Slice/Grow operate on the embedded fields, and the memory export is &m.wasmMemory. This gives shared and non-shared owned memory the same shape, and removes the (*[]byte)(m) casts. --- fncompiler.go | 3 - libc-gen/test_alloc/bump/bump.go | 12 +- libc-gen/test_alloc/sbrk/sbrk.go | 12 +- libc-gen/test_alloc/tlsf/tlsf.go | 12 +- libc-gen/test_sjlj/sjlj.go | 16 +- module.go | 137 +++++++----------- regression_test.go | 8 + testdata/memory/memory.go | 12 +- testdata/regression/bulk_grow/bulk_grow.go | 14 +- testdata/regression/oob_trap/oob_trap.go | 12 +- .../provided_helper/provided_helper.go | 12 +- .../regression/shared_export/shared_export.go | 21 ++- testdata/regression/store_grow/store_grow.go | 6 +- testdata/trig/trig.go | 12 +- translate.go | 10 +- translate_test.go | 8 +- 16 files changed, 149 insertions(+), 158 deletions(-) diff --git a/fncompiler.go b/fncompiler.go index bf0b5033..37e12ed7 100644 --- a/fncompiler.go +++ b/fncompiler.go @@ -597,9 +597,6 @@ func (fn *funcCompiler) newLabel() *ast.Ident { } func (fn *funcCompiler) cleanup() { - // Resolve imports. - ast.Inspect(fn.decl, fn.resolveImports) - // Sanity checks. passes.CheckMaterialized(fn.decl) diff --git a/libc-gen/test_alloc/bump/bump.go b/libc-gen/test_alloc/bump/bump.go index f18a18c0..750be2b4 100644 --- a/libc-gen/test_alloc/bump/bump.go +++ b/libc-gen/test_alloc/bump/bump.go @@ -11,7 +11,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -27,13 +27,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xfree(v0 int32) { if v0 == 0 { @@ -166,7 +168,7 @@ l0: return v2 } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } // Compiler error if endianess is unknown. diff --git a/libc-gen/test_alloc/sbrk/sbrk.go b/libc-gen/test_alloc/sbrk/sbrk.go index 49f6d650..9c38ef71 100644 --- a/libc-gen/test_alloc/sbrk/sbrk.go +++ b/libc-gen/test_alloc/sbrk/sbrk.go @@ -11,7 +11,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -26,13 +26,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xmalloc(v0 int32) int32 { var v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 int32 @@ -3055,7 +3057,7 @@ func (m *Module) _sbrk(v0 int32) int32 { panic("unreachable") } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } // Compiler error if endianess is unknown. diff --git a/libc-gen/test_alloc/tlsf/tlsf.go b/libc-gen/test_alloc/tlsf/tlsf.go index 64651f9c..931e3ba2 100644 --- a/libc-gen/test_alloc/tlsf/tlsf.go +++ b/libc-gen/test_alloc/tlsf/tlsf.go @@ -11,7 +11,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory ___stack_pointer int32 } @@ -28,13 +28,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xfree(v0 int32) { if v0 != 0 { @@ -475,7 +477,7 @@ l1: panic("unreachable") } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } // Compiler error if endianess is unknown. diff --git a/libc-gen/test_sjlj/sjlj.go b/libc-gen/test_sjlj/sjlj.go index 1dc8eb4e..d710dd5c 100644 --- a/libc-gen/test_sjlj/sjlj.go +++ b/libc-gen/test_sjlj/sjlj.go @@ -11,9 +11,9 @@ import ( ) type Module struct { - t0 []any - elements [][]any - memory []byte + t0 []any + elements [][]any + wasmMemory ___stack_pointer int32 } @@ -34,13 +34,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xtest() int32 { var v0, v1, v2 int32 @@ -103,7 +105,7 @@ func (m *Module) X__stack_pointer() *int32 { return &m.___stack_pointer } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } // Compiler error if endianess is unknown. diff --git a/module.go b/module.go index be6233de..3c2b8bb2 100644 --- a/module.go +++ b/module.go @@ -32,23 +32,21 @@ func (t *translator) createModuleStruct() ast.Decl { Names: []*ast.Ident{newID("elements")}, Type: &ast.ArrayType{Elt: &ast.ArrayType{Elt: newID("any")}}}) } - // Memory: owned a []byte; imported a *[]byte and a Memory field. + // Memory: owned an embedded wasmMemory; imported a *[]byte and a Memory field. if t.memory != nil { if t.memory.imported { fields = append(fields, &ast.Field{ Names: []*ast.Ident{t.memory.id}, Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, }, &ast.Field{Names: []*ast.Ident{newID("memImp")}, Type: newID("Memory")}) + if t.memory.shared || t.helpers.has("atomic_waiters") { + fields = append(fields, &ast.Field{ + Names: []*ast.Ident{newID("waiters")}, + Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}) + } } else { - fields = append(fields, &ast.Field{ - Names: []*ast.Ident{t.memory.id}, - Type: &ast.ArrayType{Elt: newID("byte")}}) - } - if t.memory.shared || t.helpers.has("atomic_waiters") { - fields = append(fields, &ast.Field{ - Names: []*ast.Ident{newID("waiters")}, - Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}) - t.packages.add("sync") + // The []byte (and waiters) are promoted from wasmMemory. + fields = append(fields, &ast.Field{Type: newID("wasmMemory")}) } } // Globals: owned/immutable are type; imported *type. @@ -359,10 +357,10 @@ func (t *translator) createHostInterfaces() []ast.Decl { return decls } -func (t *translator) createMemoryTypes() []ast.Decl { +func (t *translator) createMemoryTypes(exported bool) []ast.Decl { var decls []ast.Decl // Memory interface as a type alias. - if !*nohost { + if !*nohost && (exported || t.memory.imported) { iface := &ast.InterfaceType{Methods: &ast.FieldList{ List: []*ast.Field{{ Names: []*ast.Ident{newID("Slice")}, @@ -395,8 +393,25 @@ func (t *translator) createMemoryTypes() []ast.Decl { Specs: []ast.Spec{&ast.TypeSpec{ Assign: 1, Name: newID("Memory"), Type: iface}}}) } - // Memory structure implementing the interface for owned memory. + // Memory structure for owned memory. + // It is embedded in Module, so its fields are promoted. if !t.memory.imported { + fields := []*ast.Field{{ + Names: []*ast.Ident{t.memory.id}, + Type: &ast.ArrayType{Elt: newID("byte")}}} + if t.memory.shared || t.helpers.has("atomic_waiters") { + fields = append(fields, &ast.Field{ + Names: []*ast.Ident{newID("waiters")}, + Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}) + } + decls = append(decls, &ast.GenDecl{ + Tok: token.TYPE, + Specs: []ast.Spec{&ast.TypeSpec{ + Name: newID("wasmMemory"), + Type: &ast.StructType{Fields: &ast.FieldList{List: fields}}}}}) + } + // Methods implementing the Memory interface for exported owned memory. + if !t.memory.imported && exported { name := "memory_grow" if t.memory.shared { name = "atomic_memory_grow" @@ -404,68 +419,32 @@ func (t *translator) createMemoryTypes() []ast.Decl { } t.helpers.add(name) + recv := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}} + mem := &ast.UnaryExpr{Op: token.AND, X: modSel(t.memory.id)} + + decls = append(decls, &ast.FuncDecl{ + Recv: recv, + Name: newID("Slice"), + Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{mem}}}}, + }, &ast.FuncDecl{ + Recv: recv, + Name: newID("Grow"), + Type: &ast.FuncType{ + Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, + Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, + Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ + Fun: newID(name), + Args: []ast.Expr{mem, newID("delta"), newID("max")}}}}}}}) + if t.memory.shared { - decls = append(decls, &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{&ast.TypeSpec{ - Name: newID("wasmMemory"), - Type: &ast.StructType{Fields: &ast.FieldList{List: []*ast.Field{ - {Names: []*ast.Ident{newID("memory")}, Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}, - {Names: []*ast.Ident{newID("waiters")}, Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}, - }}}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, - Name: newID("Slice"), - Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{ - &ast.SelectorExpr{X: newID("m"), Sel: newID("memory")}}}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, - Name: newID("Grow"), - Type: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: newID(name), - Args: []ast.Expr{ - &ast.SelectorExpr{X: newID("m"), Sel: newID("memory")}, - newID("delta"), - newID("max")}}}}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: newID("wasmMemory")}}}, + decls = append(decls, &ast.FuncDecl{ + Recv: recv, Name: newID("Waiters"), Type: &ast.FuncType{ Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.SelectorExpr{X: newID("sync"), Sel: newID("Map")}}}}}}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{ - &ast.SelectorExpr{X: newID("m"), Sel: newID("waiters")}}}}}, - }) - } else { - decls = append(decls, &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{&ast.TypeSpec{ - Name: newID("wasmMemory"), - Type: &ast.ArrayType{Elt: newID("byte")}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, - Name: newID("Slice"), - Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, - Args: []ast.Expr{newID("m")}}}}}}, - }, &ast.FuncDecl{ - Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("m")}, Type: &ast.StarExpr{X: newID("wasmMemory")}}}}, - Name: newID("Grow"), - Type: &ast.FuncType{ - Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{newID("delta"), newID("max")}, Type: newID("int64")}}}, - Results: &ast.FieldList{List: []*ast.Field{{Type: newID("int64")}}}}, - Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: newID(name), - Args: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.StarExpr{X: &ast.ArrayType{Elt: newID("byte")}}, - Args: []ast.Expr{newID("m")}}, - newID("delta"), - newID("max")}}}}}}}) + modSel(newID("waiters"))}}}}}) } } return decls @@ -526,22 +505,10 @@ func (t *translator) createExportMethods() []ast.Decl { case externMemory: decl.Type.Results = &ast.FieldList{List: []*ast.Field{{Type: newID("Memory")}}} if t.memory.imported { - decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.SelectorExpr{X: newID("m"), Sel: newID("memImp")}}}} - } else if t.memory.shared { - decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CompositeLit{ - Type: newID("wasmMemory"), - Elts: []ast.Expr{ - &ast.KeyValueExpr{ - Key: newID("memory"), - Value: &ast.UnaryExpr{Op: token.AND, X: &ast.SelectorExpr{X: newID("m"), Sel: t.memory.id}}}, - &ast.KeyValueExpr{ - Key: newID("waiters"), - Value: &ast.SelectorExpr{X: newID("m"), Sel: newID("waiters")}}, - }}}}} + decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{modSel(newID("memImp"))}}} } else { - decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ - Fun: &ast.ParenExpr{X: &ast.StarExpr{X: newID("wasmMemory")}}, - Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: &ast.SelectorExpr{X: newID("m"), Sel: t.memory.id}}}}}}} + decl.Body.List = []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{ + &ast.UnaryExpr{Op: token.AND, X: modSel(newID("wasmMemory"))}}}} } } diff --git a/regression_test.go b/regression_test.go index c3177a2d..72e10d06 100644 --- a/regression_test.go +++ b/regression_test.go @@ -136,6 +136,14 @@ func Test_regression_bulk_grow(t *testing.T) { func Test_regression_shared_export(t *testing.T) { m := shared_export_test.New() mem := m.Xmemory() + + // The export must view the module's own memory, not a copy. + if mem.Slice() != m.Xmemory().Slice() { + t.Error("Slice() is not stable") + } + if got := mem.Grow(0, 1); got != 1 { + t.Errorf("Grow(0) = %d, want 1", got) + } if mem.Waiters() == nil { t.Fatal("Waiters() is nil") } diff --git a/testdata/memory/memory.go b/testdata/memory/memory.go index cc15d72a..bd1245a0 100644 --- a/testdata/memory/memory.go +++ b/testdata/memory/memory.go @@ -9,7 +9,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -25,13 +25,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xwasm_grow(v0 int32) int32 { t0 := int32(memory_grow(&m.memory, int64(v0), maxMem)) @@ -53,7 +55,7 @@ func (m *Module) Xread_as_i8u(v0 int32) int32 { return t0 } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } //go:nosplit diff --git a/testdata/regression/bulk_grow/bulk_grow.go b/testdata/regression/bulk_grow/bulk_grow.go index e106d004..749f8170 100644 --- a/testdata/regression/bulk_grow/bulk_grow.go +++ b/testdata/regression/bulk_grow/bulk_grow.go @@ -8,8 +8,8 @@ import ( ) type Module struct { - t0 []any - memory []byte + t0 []any + wasmMemory } func New() *Module { @@ -25,13 +25,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xgrow(v0 int32) int32 { t0 := int32(memory_grow(&m.memory, int64(v0), maxMem)) @@ -58,7 +60,7 @@ func (m *Module) Xtfill(v0, v1 int32) { table_fill(m.t0, v0, any(nil), v1) } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } func memory_grow(mem *[]byte, delta, max int64) int64 { diff --git a/testdata/regression/oob_trap/oob_trap.go b/testdata/regression/oob_trap/oob_trap.go index e0a34ea4..9302d8fd 100644 --- a/testdata/regression/oob_trap/oob_trap.go +++ b/testdata/regression/oob_trap/oob_trap.go @@ -9,7 +9,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -24,13 +24,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xld16(v0 int32) int32 { t0 := int32(load16(m.memory, uint32(v0))) @@ -59,7 +61,7 @@ func (m *Module) Xgrow(v0 int32) int32 { return t0 } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } //go:nosplit diff --git a/testdata/regression/provided_helper/provided_helper.go b/testdata/regression/provided_helper/provided_helper.go index b4c06b0e..505ef39a 100644 --- a/testdata/regression/provided_helper/provided_helper.go +++ b/testdata/regression/provided_helper/provided_helper.go @@ -9,7 +9,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -25,20 +25,22 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xtest() int64 { t0 := m._peek(i32(8)) return t0 } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } //go:nosplit diff --git a/testdata/regression/shared_export/shared_export.go b/testdata/regression/shared_export/shared_export.go index 9df150d7..4154fc47 100644 --- a/testdata/regression/shared_export/shared_export.go +++ b/testdata/regression/shared_export/shared_export.go @@ -13,36 +13,35 @@ import ( ) type Module struct { - memory []byte - maxMem int64 - waiters *sync.Map + wasmMemory } func New() *Module { m := new(Module) - m.maxMem = 1 m.memory = make([]byte, 65536, 65536) m.waiters = new(sync.Map) return m } +const maxMem = 1 + type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 Waiters() *sync.Map } type wasmMemory struct { - memory *[]byte + memory []byte waiters *sync.Map } -func (m wasmMemory) Slice() *[]byte { - return m.memory +func (m *wasmMemory) Slice() *[]byte { + return &m.memory } -func (m wasmMemory) Grow(delta, max int64) int64 { - return atomic_memory_grow(m.memory, delta, max) +func (m *wasmMemory) Grow(delta, max int64) int64 { + return atomic_memory_grow(&m.memory, delta, max) } -func (m wasmMemory) Waiters() *sync.Map { +func (m *wasmMemory) Waiters() *sync.Map { return m.waiters } func (m *Module) Xwait0() int32 { @@ -50,7 +49,7 @@ func (m *Module) Xwait0() int32 { return t0 } func (m *Module) Xmemory() Memory { - return wasmMemory{memory: &m.memory, waiters: m.waiters} + return &m.wasmMemory } // Compiler error if endianess is unknown. diff --git a/testdata/regression/store_grow/store_grow.go b/testdata/regression/store_grow/store_grow.go index 4051892c..af060001 100644 --- a/testdata/regression/store_grow/store_grow.go +++ b/testdata/regression/store_grow/store_grow.go @@ -9,7 +9,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -20,6 +20,10 @@ func New() *Module { const maxMem = 65536 +type wasmMemory struct { + memory []byte +} + func (m *Module) fn0() int32 { _ = int32(memory_grow(&m.memory, int64(i32(1)), maxMem)) return i32(12345) diff --git a/testdata/trig/trig.go b/testdata/trig/trig.go index 31122a97..7aa68281 100644 --- a/testdata/trig/trig.go +++ b/testdata/trig/trig.go @@ -8,7 +8,7 @@ import ( ) type Module struct { - memory []byte + wasmMemory } func New() *Module { @@ -23,13 +23,15 @@ type Memory = interface { Slice() *[]byte Grow(delta, max int64) int64 } -type wasmMemory []byte +type wasmMemory struct { + memory []byte +} func (m *wasmMemory) Slice() *[]byte { - return (*[]byte)(m) + return &m.memory } func (m *wasmMemory) Grow(delta, max int64) int64 { - return memory_grow((*[]byte)(m), delta, max) + return memory_grow(&m.memory, delta, max) } func (m *Module) Xsin(v0 float64) float64 { var v1, v2 float64 @@ -87,7 +89,7 @@ l0: return v1 } func (m *Module) Xmemory() Memory { - return (*wasmMemory)(&m.memory) + return &m.wasmMemory } //go:nosplit diff --git a/translate.go b/translate.go index af3bec3b..f309d7c4 100644 --- a/translate.go +++ b/translate.go @@ -145,8 +145,8 @@ func translate(r io.Reader, w io.Writer) error { break } } - if t.memory != nil && (exported || t.memory.imported) { - t.out.Decls = append(t.createMemoryTypes(), t.out.Decls...) + if t.memory != nil { + t.out.Decls = append(t.createMemoryTypes(exported), t.out.Decls...) } if !*nohost && len(t.imports) > 0 { t.out.Decls = append(t.createHostInterfaces(), t.out.Decls...) @@ -211,7 +211,8 @@ func translate(r io.Reader, w io.Writer) error { } } - // Set imports. + // Resolve and add imports. + ast.Inspect(&t.out, t.resolveImports) if len(t.packages) > 0 { specs := make([]ast.Spec, 0, len(t.data)) for _, pkg := range slices.Sorted(maps.Keys(t.packages)) { @@ -1271,7 +1272,6 @@ func (t *translator) addHelpers(fset *token.FileSet, filename, src string) error continue } t.out.Decls = append(t.out.Decls, decl) - ast.Inspect(decl, t.resolveImports) } return nil } @@ -1286,14 +1286,12 @@ func (t *translator) resolveHelpers(fset *token.FileSet, filename, src string) e case *ast.FuncDecl: if t.helpers.has(d.Name.Name) { t.out.Decls = append(t.out.Decls, d) - ast.Inspect(d, t.resolveImports) delete(t.helpers, d.Name.Name) } case *ast.GenDecl: for _, spec := range d.Specs { if s, ok := spec.(*ast.TypeSpec); ok && t.helpers.has(s.Name.Name) { t.out.Decls = append(t.out.Decls, d) - ast.Inspect(d, t.resolveImports) delete(t.helpers, s.Name.Name) break } diff --git a/translate_test.go b/translate_test.go index 8c528a54..403a3419 100644 --- a/translate_test.go +++ b/translate_test.go @@ -19,13 +19,13 @@ func Test_translate(t *testing.T) { tests := []string{ "fib", "loops", "memory", "primes", "recursion", "stack", "table", "trig", "regression/bulk_grow", "regression/oob_trap", "regression/select_effect", "regression/store_grow", "regression/tee_self_loop", - "regression/shared_export", + } + if *unsafe { + // Shared memory requires unsafe. + tests = append(tests, "regression/shared_export") } for _, name := range tests { t.Run(name, func(t *testing.T) { - if name == "regression/shared_export" && !*unsafe { - t.Skip("needs -unsafe") - } path := "testdata/" + name + "/" + filepath.Base(name) in, err := os.Open(path + ".wasm")