Files
wasm2go/.golangci.yml
6eb002c79b codegen: compact memops via m.M cache + _consts table (#6)
* 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]>
2026-05-26 11:51:28 +09:00

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