We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Originally I met the issue in browser environment. Below is a simplified example with wazero.
tinygo version 0.35.0 linux/amd64 (using go version go1.23.5 and LLVM version 18.1.2)
Test repo: https://github.com/falconandy/tinygo-wasm-leak
Wasm lib:
package main import ( _ "embed" "fmt" "runtime" ) //go:wasmexport processData func processData() { N := 1 * 1024 * 1024 data := make([]byte, N) fmt.Println(len(data)) } //go:wasmexport printMemUsage func printMemUsage() { runtime.GC() var m runtime.MemStats runtime.ReadMemStats(&m) fmt.Printf("runtime.MemStats: Alloc = %v MiB, TotalAlloc = %v MiB, Sys = %v MiB\n", bToMb(m.Alloc), bToMb(m.TotalAlloc), bToMb(m.Sys)) } func bToMb(b uint64) uint64 { return b / 1024 / 1024 }
Command to build: tinygo build -target=wasip1 -o ./leak/leak.wasm --no-debug -scheduler=none -buildmode=c-shared ./leak
tinygo build -target=wasip1 -o ./leak/leak.wasm --no-debug -scheduler=none -buildmode=c-shared ./leak
Test code:
package main import ( "context" _ "embed" "fmt" "log" "os" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" ) //go:embed leak/leak.wasm var leakWasm []byte func main() { ctx := context.Background() r := wazero.NewRuntime(ctx) defer r.Close(ctx) wasi_snapshot_preview1.MustInstantiate(ctx, r) mod, err := r.InstantiateWithConfig(ctx, leakWasm, wazero.NewModuleConfig().WithStartFunctions("_initialize").WithStdout(os.Stdout)) if err != nil { log.Fatal(err) } processData := mod.ExportedFunction("processData") printMemUsage := mod.ExportedFunction("printMemUsage") _, err = printMemUsage.Call(ctx) if err != nil { log.Fatal(err) } fmt.Println("mod.Memory().Size():", bToMb(mod.Memory().Size()), "MiB") for i := range 10 { fmt.Println("STEP:", i+1) step(ctx, mod, processData, printMemUsage) } } func step(ctx context.Context, mod api.Module, processData, printMemUsage api.Function) { _, err := processData.Call(ctx) if err != nil { log.Fatal(err) } _, err = printMemUsage.Call(ctx) if err != nil { log.Fatal(err) } fmt.Println("mod.Memory().Size():", bToMb(mod.Memory().Size()), "MiB") } func bToMb(b uint32) uint32 { return b / 1024 / 1024 }
Output - memory usage only grows. A leak (10 * 1 megabytes)?
runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB mod.Memory().Size(): 0 MiB STEP: 1 1048576 runtime.MemStats: Alloc = 1 MiB, TotalAlloc = 1 MiB, Sys = 1 MiB mod.Memory().Size(): 2 MiB STEP: 2 1048576 runtime.MemStats: Alloc = 2 MiB, TotalAlloc = 2 MiB, Sys = 3 MiB mod.Memory().Size(): 4 MiB STEP: 3 1048576 runtime.MemStats: Alloc = 3 MiB, TotalAlloc = 3 MiB, Sys = 3 MiB mod.Memory().Size(): 4 MiB STEP: 4 1048576 runtime.MemStats: Alloc = 4 MiB, TotalAlloc = 4 MiB, Sys = 7 MiB mod.Memory().Size(): 8 MiB STEP: 5 1048576 runtime.MemStats: Alloc = 5 MiB, TotalAlloc = 5 MiB, Sys = 7 MiB mod.Memory().Size(): 8 MiB STEP: 6 1048576 runtime.MemStats: Alloc = 6 MiB, TotalAlloc = 6 MiB, Sys = 7 MiB mod.Memory().Size(): 8 MiB STEP: 7 1048576 runtime.MemStats: Alloc = 7 MiB, TotalAlloc = 7 MiB, Sys = 7 MiB mod.Memory().Size(): 8 MiB STEP: 8 1048576 runtime.MemStats: Alloc = 8 MiB, TotalAlloc = 8 MiB, Sys = 15 MiB mod.Memory().Size(): 16 MiB STEP: 9 1048576 runtime.MemStats: Alloc = 9 MiB, TotalAlloc = 9 MiB, Sys = 15 MiB mod.Memory().Size(): 16 MiB STEP: 10 1048576 runtime.MemStats: Alloc = 10 MiB, TotalAlloc = 10 MiB, Sys = 15 MiB mod.Memory().Size(): 16 MiB
If I run the code locally (a unit test, with removed go:wasmexport directives) - no leaks (TotalAlloc is 0 for some reason).
go:wasmexport
func TestNativeLeak(t *testing.T) { printMemUsage() for i := range 10 { fmt.Println("STEP:", i+1) processData() printMemUsage() } }
tinygo test -v ./leak/
runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 1 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 2 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 3 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 4 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 5 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 6 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 7 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 8 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 9 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB STEP: 10 1048576 runtime.MemStats: Alloc = 0 MiB, TotalAlloc = 0 MiB, Sys = 0 MiB
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Originally I met the issue in browser environment. Below is a simplified example with wazero.
tinygo version 0.35.0 linux/amd64 (using go version go1.23.5 and LLVM version 18.1.2)
Test repo: https://github.com/falconandy/tinygo-wasm-leak
Wasm lib:
Command to build:
tinygo build -target=wasip1 -o ./leak/leak.wasm --no-debug -scheduler=none -buildmode=c-shared ./leak
Test code:
Output - memory usage only grows. A leak (10 * 1 megabytes)?
If I run the code locally (a unit test, with removed
go:wasmexport
directives) - no leaks (TotalAlloc is 0 for some reason).tinygo test -v ./leak/
The text was updated successfully, but these errors were encountered: