Files
9e1233c9df threads: atomics, wasi-threads, and a copy-on-write shared-memory image (#30)
Adds the wasm threads proposal to the codegen and, on top of it, a
copy-on-write memory-sharing facility for embedders that stand up many
instances of the same module.

Threads
  - Lower the 0xFE atomic family (load/store/rmw/cmpxchg across widths and
    sub-word lanes, plus wait/notify) to sync/atomic-based module helpers.
  - wasi_thread_spawn runs each guest thread on a goroutine. A shared memory
    is allocated at its declared ceiling once and never relocates, so every
    agent's cached base pointer stays valid; growth is a lone atomic store.
  - Passive data segments, per-agent globals, coherent plain access for
    shared memories, and multi-package output for threaded modules.
  - A shared memory's ceiling is a runtime value the embedder chooses, not
    the binary's declared maximum.

Copy-on-write shared memory
  - NewWithMemory lets the caller supply the linear memory, and memory.init
    compares before it writes — so a copy-on-write map of a pre-initialized
    image survives the start section instead of being faulted private.
  - NewSharedImage builds an initialized image once per process and maps it
    copy-on-write into every instance: the data segments (identical in every
    instance, read-mostly) then cost almost nothing per instance.
  - NewSharedSnapshot / NewFromSnapshot share a WHOLE initialized instance —
    memory and wasm globals both — so instances re-run nothing at all.
    SaveGlobals/RestoreGlobals carry the initialized state that lives outside
    linear memory (the stack pointer and other mutable globals).

Also: coverage for the threads/atomics helpers, a WASM2GO_SSA_PASSES_OFF
diagnostic knob, an AtomicWait timeout margin, and thread-trap visibility.

Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-07-16 10:37:21 +09:00

67 lines
2.1 KiB
Go

// Package sharedimage carries the copy-on-write shared-memory-image runtime
// that wasm2go emits alongside a translated module.
//
// It lives here as source text rather than as a string literal in the
// generator because it is ordinary Go that happens to be output: keeping it in
// .go.txt files means it stays readable, reviewable, and greppable. The only
// generated parts are the identifiers the surrounding package decides — the
// package name and the Module field names, which single-package output keeps
// unexported and multi-package output must export.
package sharedimage
import (
"bytes"
_ "embed"
"fmt"
"go/format"
"text/template"
)
//go:embed sharedimage.go.txt
var coreTmpl string
//go:embed sharedimage_mmap.go.txt
var mmapTmpl string
//go:embed sharedimage_nommap.go.txt
var nommapTmpl string
// Names are the identifiers the emitting package supplies.
type Names struct {
Pkg string // package clause of the generated files
Module string // the Module type's name (always "Module" today)
Memory string // Module's linear-memory field
MemSize string // Module's *atomic.Uint64 current-size field
DataEnd string // Module's data-segment-extent field
// SaveGlobals is the generated globals-snapshot function (see
// emitGlobalsSnapshot); multi-package output exports it.
SaveGlobals string
}
// Files renders the runtime: output filename → formatted Go source. The three
// files split by platform (mmap / no mmap) and must be emitted together.
func Files(n Names) (map[string][]byte, error) {
out := map[string][]byte{}
for name, src := range map[string]string{
"sharedimage.go": coreTmpl,
"sharedimage_mmap.go": mmapTmpl,
"sharedimage_nommap.go": nommapTmpl,
} {
t, err := template.New(name).Parse(src)
if err != nil {
return nil, fmt.Errorf("sharedimage: parsing %s: %w", name, err)
}
var buf bytes.Buffer
if err := t.Execute(&buf, n); err != nil {
return nil, fmt.Errorf("sharedimage: rendering %s: %w", name, err)
}
formatted, err := format.Source(buf.Bytes())
if err != nil {
return nil, fmt.Errorf("sharedimage: formatting %s: %w", name, err)
}
out[name] = formatted
}
return out, nil
}