mirror of
https://github.com/hanzoai/wasm2go.git
synced 2026-11-03 04:28:53 +00:00
* codegen: compact memops via m.M cache + _consts table
Two related changes that together reduce per-access source size and
fix Linux ARM64 cross-compilation of very large transpiled modules,
without changing the runtime model or the public API.
1) m.M field cache.
Each memory load/store previously expanded to
*(*T)(unsafe.Add(unsafe.Pointer(unsafe.SliceData(m.memory)),
uintptr(uint32(<base>)+<offset>)))
re-fetching the slice header on every access. Pull the pointer
into a `M unsafe.Pointer` field on Module that New() initialises
and memoryGrow refreshes on the reallocate path. Reslice grows
leave the underlying data pointer alone, so the cache stays
valid across the common-case grow without any extra refresh.
Accesses now read
*(*T)(unsafe.Add(m.M, <off>))
The bare `<off>` form leans on unsafe.Add's IntegerType acceptance
(the spec performs the implicit uintptr conversion), elides the
uintptr() wrap, and unwraps the redundant int32() cast around
integer-constant bases. Negative-int32 constants keep the uint32
wrap so the bit pattern survives Go's sign-extension to uintptr.
2) Package-level _consts table.
Large constant offsets (>= 4 KiB) are routed through a per-file
var _consts = [N]uintptr{ ... }
slice and emitted as `_consts[<idx>]` at access sites. The
indirection prevents the Go ARM64 backend from folding the
constant into LDR/LDPSW immediates and falling back to a
PC-relative literal pool that the assembler then cannot reach
inside very large transpiled functions ("constant is not in
pool"). `_consts[N]` is a runtime memory load — the compiler
must use register-offset addressing, which has no immediate-
magnitude limit. Functions whose offsets stay below the
threshold continue to emit bare literals so the table stays
compact.
The table lives at file scope, one per chunk in the linkname-
split layout and one per output file in the single-package
layout (currentChunk == 0 for the single-package case).
Combined effect, measured against the googlesql @ 13.8 MB
wasm-opt'd wasm:
- Linux ARM64 `go build ./...` now succeeds where it previously
aborted with "constant is not in pool".
- Per-access generated source drops ~55 % (about 93 chars -> 40
chars in the constant-base / small-offset case).
- Peak compile memory matches the wazero baseline at ~5.4 GB
inside `golang:1.26 --memory=6g`.
- googlesqlite bench numbers stay within noise of the earlier
inline form on amd64; pprof shows the bridge layer is no
longer visible in either CPU or allocation top samples.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
* codegen: tighten error-handling lint and audit existing sites
Strengthens the golangci-lint config so the codebase can no longer
silently swallow errors, and walks the existing production code to
fix every site the stricter rules flag.
Lint changes:
- errcheck.check-blank: true catches `_, _ = f()` style explicit
suppressions in addition to fully-unchecked returns.
- errorlint enables errorf / asserts / comparison so `fmt.Errorf`
must wrap with %w and `err == io.EOF` becomes `errors.Is(err,
io.EOF)`.
- nilerr + nilnesserr cover the `return nil` when err is non-nil
family.
- No exclude-functions list — every Close/Flush/Fprintf is held
to the same standard as anything else.
Code changes follow the user's stated principle: if an error is a
precondition violation, panic; if it can legitimately occur, return
it to the caller (using errors.Join + named/local error on the
defer-close paths so the close failure cannot be lost).
Notable production-side fixes:
- `WasiStubs.Fd_close` and `Fd_renumber` now collect Close errors
from the f / conn / listener handles via a new closeWasiOpen
helper and surface them via mapOSError instead of dropping them.
- `Path_open`'s close-on-error paths join the Close error into
the primary statErr / not-dir error so neither failure is lost.
- `Sock_shutdown` joins CloseRead / CloseWrite errors into a
single mapped errno; the guest now sees a real failure if the
half-close fails.
- `resolveFiletimes` previously fell silently to epoch when
f.Stat failed; it now returns the Stat error so
Fd_filestat_set_times can map it to a wasi errno. The Stat
call is also skipped entirely when no preserve bits are set,
which is the only case where the current on-disk time matters.
- `translate_multi.go` chunk-name parsing replaced the Atoi
`err == nil` guard with a panic on inconsistency, matching the
`isChunkPkgName` precondition (the chunker's invariant
guarantees a numeric suffix; an Atoi failure here is an
emitter bug, not a runtime condition).
- `WASM2GO_MULTIPACKAGE_THRESHOLD` env-var parsing logs the
malformed value to stderr instead of silently using the default.
- `cmd/wasm2go/main.go` deferred Close/Flush calls now route a
failure through `fail()`; printSummary returns a single
accumulated write error via a small errWriter helper.
- `internal/wasm/leb128.go` and `parser.go` swap `err == io.EOF`
for `errors.Is(err, io.EOF)` so wrapped EOFs are detected.
Test-side changes are the same shape: `defer something.Close()` is
swapped for `t.Cleanup` with `t.Errorf` on failure, Read/Stat/
ReadFile in tests check err, and the captureStdout / captureStderr
helpers distinguish io.EOF from real read failures.
constInt32 itself is unchanged from the previous commit: it returns
(int32, bool) for the pattern-match classification, the caller
(`memOffsetExpr`) does an explicit early-return on `!ok`, and the
unreachable parse-failure inside a matched `int32(N)` panics as an
emitter-invariant assertion.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
43 lines
984 B
YAML
43 lines
984 B
YAML
version: "2"
|
|
|
|
run:
|
|
timeout: 30m
|
|
|
|
linters:
|
|
default: standard
|
|
enable:
|
|
# errcheck + check-blank catches `_, _ = f()` style full ignores; the
|
|
# errorlint / nilerr / nilnesserr trio catches the more subtle anti-
|
|
# patterns of comparing wrapped errors with == , wrapping with %v
|
|
# instead of %w, and returning nil while err is non-nil.
|
|
- errcheck
|
|
- errorlint
|
|
- govet
|
|
- ineffassign
|
|
- nilerr
|
|
- nilnesserr
|
|
- staticcheck
|
|
- unused
|
|
settings:
|
|
errcheck:
|
|
# Catch `x, _ := f()` style suppressions. Type assertions are a
|
|
# different axis (invariant-style "must be T") and intentionally
|
|
# not toggled on here — this config is scoped to error-handling.
|
|
check-blank: true
|
|
errorlint:
|
|
errorf: true
|
|
asserts: true
|
|
comparison: true
|
|
exclusions:
|
|
presets:
|
|
- comments
|
|
- common-false-positives
|
|
- legacy
|
|
paths:
|
|
- testdata
|
|
|
|
formatters:
|
|
enable:
|
|
- gofmt
|
|
- goimports
|