Files
wasm2go/testdata/cg_sharedexit.wat
bd85549918 wasm2go: a WebAssembly-to-Go ahead-of-time transpiler (#1)
* wasm2go: a WebAssembly-to-Go ahead-of-time transpiler

wasm2go translates a WebAssembly binary into standalone Go source. The
generated package runs the module natively — no interpreter and no
embedded wasm runtime — so it builds and links like ordinary Go code.
This makes it practical to ship a wasm-compiled library (for example a
large C/C++ codebase built with the WASI SDK) inside a Go program
without the startup and memory cost of a wasm engine.

Components:

- internal/wasm — the WebAssembly binary parser.
- internal/codegen — translates a parsed module into Go source through
  the SSA lowering + optimization + emission pipeline.
- internal/ssa — a lightweight SSA IR plus optimization passes
  (const-propagation, DCE, CSE, branch folding, memory promotion).
- transpile — the public library API: Parse, Translate, and the
  Transpile one-shot convenience wrapper.
- cmd/wasm2go — the command-line interface.
- internal/testfixture — shared helper that compiles testdata/*.wat
  to wasm on demand via wat2wasm.

Library API (Options):

- Required: Package, OutputImportPath.
- Optional: BulkExportPrefix (per-export Inv_<svc>_<mt> dispatch for
  modules with uniformly-named exports), EntryExports (narrow DCE
  root set), KeepDeadFuncs (disable DCE for diff/debug),
  PromotionReportPath (write SSA memory-promotion JSON).

Everything else is auto-derived from the input: the SSA pipeline runs
unconditionally; data segments emit as //go:embed sidecars; wasi
imports translate to Go-native syscalls; modules whose total
function-body bytes exceed an internal threshold are emitted as a
chain of //go:linkname-split sub-packages so the Go compiler stays
inside a bounded per-package memory budget.

WASI runtime: full wasi_snapshot_preview1 coverage. Args/environ,
clocks, fd_*, path_* (open/create_directory/unlink_file/
remove_directory/rename/filestat_get/filestat_set_times/link/
symlink/readlink), readdir, pread/pwrite, sync/datasync, poll_oneoff
(clock + fd-event branches), sched_yield, random_get, proc_exit,
proc_raise, sock_accept/recv/send/shutdown — every call routes to the
Go stdlib or golang.org/x/sys/unix equivalent. Per-platform companion
files (unix/linux/darwin/windows) carry the bits that differ across
OSes; the translated module is a normal Go program with the host's
ordinary filesystem and network reach.

Release engineering for the public repository:

- GitHub Actions CI: build, vet, race-enabled tests behind an 85%
  coverage gate, golangci-lint — all driven through make targets.
- Release workflow runs GoReleaser on version tags, including
  deb/rpm/apk packages, with build-provenance attestations recorded
  for every release asset.
- A release-test workflow runs `make release/check` (GoReleaser
  config validation + a publish-free dry-run build) on every PR and
  main push so a broken release is caught before tag time.
- golangci-lint and goreleaser are pinned in tools/go.mod and run
  via `go tool`, so local and CI invocations use the same versions.
- `make install/wat2wasm` provisions the wat2wasm CLI the test suite
  needs, dispatching to the host's package manager (Homebrew on
  macOS, apt/dnf/pacman/apk on Linux).
- Makefile targets for local development.

Test coverage is 85.2% of statements overall.

* README: document the WASI runtime, performance, and library entry points

- Add a "WASI support" subsection explaining the single-file,
  stdlib-only `wasi_snapshot_preview1` implementation and the
  resulting cross-platform reach.
- Add a "Performance" section with the wazero-vs-wasm2go corpus
  numbers (cold start 8.4×, short queries up to 81×, TPC-H scans
  within ±10%).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* ci: align release-test with release.yml — use stable Go

The release-test workflow exercises the same `make release/check`
dry-run that the publish job runs at tag time, so it should pull
the same toolchain. Switch it from `go-version-file: tools/go.mod`
to `go-version: stable` to match release.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-24 13:34:54 +09:00

175 lines
11 KiB
WebAssembly Text Format

;; cg_sharedexit.wat - big br_table dispatcher whose case bodies converge
;; on one shared epilogue section. The epilogue ($epi block end) is NOT a
;; br_table target, so it stays a shared-exit label that dispatch_split
;; inlines into each case sub-function. The default routes to a real case.
(module
(global $out (mut i32) (i32.const 0))
(func (export "dispatch") (param $sel i32)
(local $acc i32)
(block $epi
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(block
(br_table 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 39 (local.get $sel))
)
(local.set $acc (i32.add (local.get $acc) (i32.const 10)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 20)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 30)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 40)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 50)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 60)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 70)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 80)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 90)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 100)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 110)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 120)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 130)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 140)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 150)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 160)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 170)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 180)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 190)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 200)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 210)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 220)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 230)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 240)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 250)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 260)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 270)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 280)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 290)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 300)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 310)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 320)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 330)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 340)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 350)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 360)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 370)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 380)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 390)))
(br $epi)
)
(local.set $acc (i32.add (local.get $acc) (i32.const 400)))
(br $epi)
)
(global.set $out (i32.const 777)))
(func (export "get_out") (result i32) (global.get $out))
)