mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-10-30 15:06:48 +00:00
* 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]>
118 lines
3.2 KiB
Go
118 lines
3.2 KiB
Go
package codegen
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// goKeywords lists Go reserved words that must not be used as identifiers.
|
|
var goKeywords = map[string]bool{
|
|
"break": true, "case": true, "chan": true, "const": true, "continue": true,
|
|
"default": true, "defer": true, "else": true, "fallthrough": true, "for": true,
|
|
"func": true, "go": true, "goto": true, "if": true, "import": true,
|
|
"interface": true, "map": true, "package": true, "range": true, "return": true,
|
|
"select": true, "struct": true, "switch": true, "type": true, "var": true,
|
|
// also a few predeclared names we never want to shadow.
|
|
"any": true, "true": true, "false": true, "nil": true, "iota": true,
|
|
}
|
|
|
|
// MangleID returns a valid Go identifier derived from the given wasm name.
|
|
// Empty input becomes "_". A leading digit is prefixed with "_". Any rune
|
|
// outside [A-Za-z0-9_] is encoded as "_x{hex}". Go keywords get a trailing
|
|
// underscore.
|
|
func MangleID(name string) string {
|
|
if name == "" {
|
|
return "_"
|
|
}
|
|
var b strings.Builder
|
|
b.Grow(len(name))
|
|
first := true
|
|
for _, r := range name {
|
|
switch {
|
|
case r == '_':
|
|
b.WriteRune(r)
|
|
case unicode.IsLetter(r):
|
|
b.WriteRune(r)
|
|
case unicode.IsDigit(r):
|
|
if first {
|
|
b.WriteByte('_')
|
|
}
|
|
b.WriteRune(r)
|
|
default:
|
|
fmt.Fprintf(&b, "_x%x", r)
|
|
}
|
|
first = false
|
|
}
|
|
out := b.String()
|
|
if goKeywords[out] {
|
|
out += "_"
|
|
}
|
|
return out
|
|
}
|
|
|
|
// MangleModuleType returns the Go type name used for the interface that
|
|
// represents an imported module (e.g. "env" -> "envImports").
|
|
func MangleModuleType(name string) string {
|
|
return MangleID(name) + "Imports"
|
|
}
|
|
|
|
// MangleModuleField returns the Go struct-field name for an imported module
|
|
// (e.g. "wasi_snapshot_preview1" -> "wasi_snapshot_preview1").
|
|
func MangleModuleField(name string) string {
|
|
return MangleID(name)
|
|
}
|
|
|
|
// ExportMethodName returns the Go method name for a wasm export. The
|
|
// algorithm preserves underscores between numeric segments so names like
|
|
// `w_56_10` and `w_561_0` (which collide if underscores are stripped) stay
|
|
// distinct as `W_56_10` and `W_561_0`. Letter-bordered underscores are
|
|
// dropped and CamelCased — `wasm_alloc` → `WasmAlloc`. Always starts with
|
|
// an uppercase letter so the method is exported.
|
|
func ExportMethodName(name string) string {
|
|
if name == "" {
|
|
return "Export_"
|
|
}
|
|
parts := strings.Split(name, "_")
|
|
var b strings.Builder
|
|
for i, p := range parts {
|
|
if p == "" {
|
|
continue
|
|
}
|
|
// Keep an underscore separator if either side of the boundary is
|
|
// purely numeric — preserves identity in `_<digit>_` chains. The
|
|
// first part never gets a leading underscore.
|
|
if i > 0 && b.Len() > 0 && (allDigits(p) || allDigits(parts[i-1])) {
|
|
b.WriteByte('_')
|
|
b.WriteString(p)
|
|
continue
|
|
}
|
|
runes := []rune(p)
|
|
runes[0] = unicode.ToUpper(runes[0])
|
|
b.WriteString(string(runes))
|
|
}
|
|
out := MangleID(b.String())
|
|
if out == "" {
|
|
return "Export_"
|
|
}
|
|
r0 := []rune(out)[0]
|
|
if !unicode.IsUpper(r0) {
|
|
// Numeric or other non-letter start: prefix with `X` so the name is
|
|
// a valid exported method.
|
|
out = "X" + out
|
|
}
|
|
return out
|
|
}
|
|
|
|
func allDigits(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
for _, r := range s {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|