Skip to content

wasm: does not return memory to the OS #59061

Closed
@achille-roussel

Description

@achille-roussel

What version of Go are you using (go version)?

$ go version
go version go1.20 darwin/arm64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE="on"
GOARCH="arm64"
GOBIN=""
GOCACHE="..."
GOENV="..."
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="..."
GONOPROXY="github.com/..."
GONOSUMDB="github.com/..."
GOOS="darwin"
GOPATH="..."
GOPRIVATE="github.com/..."
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.20"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/qc/zv1mt0j11sscgjygfx47nxlm0000gn/T/go-build1776626115=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

As part of the work on #58141, I came across the runtime code that takes care of interfacing between the Go and system memory allocators, and I believe that Go programs built to WebAssembly are leaking all memory they attempt to release to the OS.

The relevant code is in https://github.com/golang/go/blob/master/src/runtime/mem_js.go

The code comment suggest that the initial plan was either to leverage virtual memory capabilities of the host application if the design discussions recorded at https://github.com/WebAssembly/design/blob/master/FutureFeatures.md#finer-grained-control-over-memory had materialized, or use the same allocation strategy used on Plan9 which also uses a linear memory space (via brk/sbrk) https://github.com/golang/go/blob/master/src/runtime/mem_plan9.go

Since neither of these alternatives were implemented, the current implementation leaks all memory blocks that the GC attempts to release to the OS (via calls to runtime.sysFreeOS).

I have verified this hypothesis by adding print statements in runtime.sysReserveOS and runtime.sysFreeOS which show that the runtime.reserveEnd pointer tracking the current size of the WASM memory, is always increasing (verified when running tests for the standard bytes package). It also shows that the size of the WASM memory is ~7x the size of the Go heap in those tests, which further supports the hypothesis of a leak.

sysReserveOS: 0x0 262144
init reserve end to 2293760
update reserve end to 2555904
sysReserveOS: 0x0 65536
update reserve end to 2621440
sysReserveOS: 0x0 8388608
update reserve end to 11010048
sysFreeOS: 0x280000 1572864
sysFreeOS: 0x800000 2621440
sysReserveOS: 0x0 8192
update reserve end to 11075584
sysReserveOS: 0x0 70864
update reserve end to 11206656
sysReserveOS: 0x0 65536
update reserve end to 11272192
sysReserveOS: 0x0 131072
update reserve end to 11403264
sysReserveOS: 0x0 65536
update reserve end to 11468800
sysReserveOS: 0x0 65536
update reserve end to 11534336
sysReserveOS: 0x0 262144
update reserve end to 11796480
sysReserveOS: 0x800000 4194304
sysReserveOS: 0x0 4194304
update reserve end to 15990784
sysFreeOS: 0xb40000 4194304
sysReserveOS: 0x0 8388608
update reserve end to 24379392
sysFreeOS: 0xf40000 786432
sysFreeOS: 0x1400000 3407872
sysReserveOS: 0x0 70864
update reserve end to 24510464
===================================================================
gcController.heapInUse:    3866624
gcController.heapFree:     32768
===================================================================

I looked for open issues related to WebAssembly and memory usage and it is possible that these might be tied back to this memory leak (if confirmed):

What did you expect to see?

I was expecting the size of the WASM memory to be closer to the size of the Go heap.

I was also expecting runtime.sysFreeOS to retain the freed memory blocks in a freelist in order to reuse them when memory is needed again.

What did you see instead?

The size of the WASM memory is ~7x larger than the size of the Go heap.

When the program needs memory, it always increases the size of the WebAssembly linear memory and never reuses memory blocks that it freed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatureRequestIssues asking for a new feature that does not need a proposal.FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.OS-Plan9arch-wasmWebAssembly issueshelp wanted

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions