Files
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

111 lines
6.7 KiB
WebAssembly Text Format

;; cg_manyfuncs.wat - many small functions + globals + call_indirect.
;; Used with a tiny MultiPackageChunkBytes so the partitioner emits
;; several chunk packages, exercising translate_multi/linkname/multipkg.
(module
(global $g (mut i32) (i32.const 7))
(table 30 funcref)
(elem (i32.const 0) $leaf0 $leaf1 $leaf2 $leaf3 $leaf4 $leaf5 $leaf6 $leaf7 $leaf8 $leaf9 $leaf10 $leaf11 $leaf12 $leaf13 $leaf14 $leaf15 $leaf16 $leaf17 $leaf18 $leaf19 $leaf20 $leaf21 $leaf22 $leaf23 $leaf24 $leaf25 $leaf26 $leaf27 $leaf28 $leaf29)
(type $unary (func (param i32) (result i32)))
(func $leaf0 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 1)) (i32.const 0)))
(func $leaf1 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 2)) (i32.const 3)))
(func $leaf2 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 3)) (i32.const 6)))
(func $leaf3 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 4)) (i32.const 9)))
(func $leaf4 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 5)) (i32.const 12)))
(func $leaf5 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 6)) (i32.const 15)))
(func $leaf6 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 7)) (i32.const 18)))
(func $leaf7 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 8)) (i32.const 21)))
(func $leaf8 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 9)) (i32.const 24)))
(func $leaf9 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 10)) (i32.const 27)))
(func $leaf10 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 11)) (i32.const 30)))
(func $leaf11 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 12)) (i32.const 33)))
(func $leaf12 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 13)) (i32.const 36)))
(func $leaf13 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 14)) (i32.const 39)))
(func $leaf14 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 15)) (i32.const 42)))
(func $leaf15 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 16)) (i32.const 45)))
(func $leaf16 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 17)) (i32.const 48)))
(func $leaf17 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 18)) (i32.const 51)))
(func $leaf18 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 19)) (i32.const 54)))
(func $leaf19 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 20)) (i32.const 57)))
(func $leaf20 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 21)) (i32.const 60)))
(func $leaf21 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 22)) (i32.const 63)))
(func $leaf22 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 23)) (i32.const 66)))
(func $leaf23 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 24)) (i32.const 69)))
(func $leaf24 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 25)) (i32.const 72)))
(func $leaf25 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 26)) (i32.const 75)))
(func $leaf26 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 27)) (i32.const 78)))
(func $leaf27 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 28)) (i32.const 81)))
(func $leaf28 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 29)) (i32.const 84)))
(func $leaf29 (param $x i32) (result i32)
(i32.add (i32.mul (local.get $x) (i32.const 30)) (i32.const 87)))
(func (export "sum_all") (param $x i32) (result i32)
(local $acc i32)
(local.set $acc (i32.const 0))
(local.set $acc (i32.add (local.get $acc) (call $leaf0 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf1 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf2 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf3 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf4 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf5 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf6 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf7 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf8 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf9 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf10 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf11 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf12 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf13 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf14 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf15 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf16 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf17 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf18 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf19 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf20 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf21 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf22 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf23 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf24 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf25 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf26 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf27 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf28 (local.get $x))))
(local.set $acc (i32.add (local.get $acc) (call $leaf29 (local.get $x))))
(local.get $acc))
(func (export "indirect") (param $which i32) (param $x i32) (result i32)
(call_indirect (type $unary) (local.get $x) (local.get $which)))
(func (export "get_g") (result i32) (global.get $g))
(func (export "set_g") (param i32) (global.set $g (local.get 0)))
(func (export "inc_g") (result i32)
(global.set $g (i32.add (global.get $g) (i32.const 1)))
(global.get $g))
)