I'm trying to make one of Luis Figuerido's WASM canvas demos work with TinyGo, and I'm running out of memory:
panic: runtime error: out of memory
RuntimeError: unreachable executed test.wasm:3182:1
runtimePanic http://localhost:8080/test.wasm:3182
alloc http://localhost:8080/test.wasm:3124
resume http://localhost:8080/test.wasm:7950
_resume http://localhost:8080/wasm_exec.js:359
_makeFuncWrapper http://localhost:8080/wasm_exec.js:370
When trying with -gc=none I get a compilation error:
panic: interp: expected typecode to be a ptrtoint
goroutine 1 [running]:
github.com/tinygo-org/tinygo/interp.(*frame).evalBasicBlock(0xc001a5f3e0, 0x7f1018fa23a0, 0x7f1018fa1eb0, 0x0, 0x0, 0x0, 0x0, 0xc002fd5e20, 0x1, 0x1, ...)
/home/circleci/project/interp/frame.go:333 +0x9002
github.com/tinygo-org/tinygo/interp.(*Eval).function(0xc001fcc990, 0x7f1018003148, 0xc001a5f520, 0x2, 0x2, 0xc002f0e6a0, 0x9, 0x0, 0x0, 0x0, ...)
/home/circleci/project/interp/interp.go:104 +0x208
github.com/tinygo-org/tinygo/interp.(*Eval).Function(...)
/home/circleci/project/interp/interp.go:87
github.com/tinygo-org/tinygo/interp.Run(0x54c24f0, 0x54c0a70, 0x0, 0x0, 0x0)
/home/circleci/project/interp/interp.go:74 +0x681
main.Compile(0x7fff001f6eb0, 0x10, 0x7fff001f6e7e, 0x10, 0xc000064600, 0xc001a5ff00, 0xc001a5fd88, 0x0, 0x0)
/home/circleci/project/main.go:132 +0xb04
main.Build(0x7fff001f6eb0, 0x10, 0x7fff001f6e7e, 0x10, 0x7fff001f6e97, 0x4, 0xc000091f00, 0xc0000f02c0, 0xc0000f4200)
/home/circleci/project/main.go:323 +0xe2
main.main()
/home/circleci/project/main.go:616 +0xc74
make: *** [Makefile:45: tinygo-canvas] Error 2
With -opt=0:
error: async function syscall/js.handleEvent used as function pointer
The source in question is available at https://github.com/johanbrandhorst/wasm-experiments/blob/master/canvas/main.go. It runs fine when compiled with gc.
I am still interested in trying to get this to work, what would be the best way to proceed? I can't tell if there's a bug here or if it's really just running out of memory.
It's likely that at least fmt.Sprintf will cause an allocation. The args parameter on line 36 may also be heap-allocated, I haven't checked. To fix this, we really need a proper GC which has not yet been implemented. You could see whether #350 works: it is still unsafe in general but may work in this particular case.
I'll give it a go and report back.
It seems to take marginally longer, but it's still running out of memory on this branch:
panic: runtime error: nil pointer dereference wasm_exec.js:199:18
RuntimeError: unreachable executed test.wasm:4445:1
runtimePanic http://localhost:8080/test.wasm:4445
nilPanic http://localhost:8080/test.wasm:4405
handleEvent http://localhost:8080/test.wasm:17492
resume http://localhost:8080/test.wasm:9186
_resume http://localhost:8080/wasm_exec.js:359
_makeFuncWrapper http://localhost:8080/wasm_exec.js:370
How can I get a better idea of where the allocations are happening? I thought gc=none would do it but it's causing a compilation error on line 132, which I wouldn't think would cause an allocation.
Sounds like this wasn't safe and a memory corruption happened.
There currently is not a good way but with https://reviews.llvm.org/D63056 I hope to add an easy-to-use method of listing all source locations that do an allocation.
Actually, there is a way but it's not easy to read. You can compile with -opt=1 -o=wasm.ll -target=wasm and look at the wasm.ll file to find calls to runtime.alloc.
I tried converting this to something that didn't need to run in a browser, blindly cutting out most of the canvas stuff. Unfortunately, it hits a segmentation fault when I do that. Are you sure the code is correct?
Here is the code I ran, this time as a regular (OS) executable:
https://gist.github.com/aykevl/c15ff6c2fadaf3f837dad34d10ad8756
This is the error I get:
Program received signal SIGSEGV, Segmentation fault.
0x00000000004091f2 in (*./tmp/canvas.go.DotThing).Update (dt=0x7ffff7ec9060, dtTime=0.00010000000000000009) at tmp/canvas.go:60
60 if dot.pos[0] < dot.size {
EDIT: nevermind, this must be a TinyGo bug because it works just fine in Go. Will have to look into it.
Well, actually the bug above was because I was using the new still-unsafe portable GC, no wonder memory corruption happened.
However, when I investigated further I found a real bug: #416. Can I use the code above as a test case and merge it in the repository? I've long struggled to come up with a good stress test for the GC and this code happens to trigger a bug so may surface other bugs in the future.
The code belongs to @stdiopt, I suppose we can probably include it if you include the LICENSE?
Source is here: https://github.com/stdiopt/gowasm-experiments/tree/master/repulsion. LICENSE: https://github.com/stdiopt/gowasm-experiments/blob/master/LICENSE (Apache 2)
Though, that code is slightly outdated since it was built for 1.11. The code in my repo is updated to work with 1.12, but still subject to the LICENSE, of course.
Ah, I guess I'll see whether @stdiopt is fine with merging it under our license (with proper attribution, of course). It probably won't happen immediately but when the GC also works on other platforms.
Semi-related: #421
@johanbrandhorst This is a fork of your fork, with the callbacks changed to be fired from JS:
The errors don't seem to happen with it. While interesting in itself, it probably also narrows down what could be going wrong a bit.
Also removed the use of fmt while at it, which drops the compressed .wasm file size to ~19kB. :man_dancing:
@justinclift I tried https://justinclift.github.io/wasm-experiments/, and you're right, it works much longer, but the dots eventually disappear altogether :confused:. There are no warnings in the console either. Either way, it's a big improvement, have you tried compiling it with the current release (without the GC)? Have you tried compiling it with the gc compiler?
No worries. :)
When the dots disappear, they can be "reset" by choosing a new number of dots on the slider.
My guess is something losing the dt variable contents. Might be the gc, might be something else not returning correctly from the frame rendering function, or it could be something else completely (haven't checked).
Didn't try with anything other than the tinygo compiler with the new WIP conservative gc. :wink:
This bug is still present in dev, and seems to be the last outstanding issue before we can run pure canvas demos confidently.
Interestingly, the modified tinygo-canvas demo in my wasm-experiments repo runs fine without the out-of-memory error. I hypothesize that the memory leak has something to do with the callback implementation.
Now that v0.8.0 is out has this issue been addressed?
@johanbrandhorst does this problem still occur with the latest dev branch? There have been quite a few changes there...
Sorry, I hope to have a chance to test this again tomorrow :).
I've tested the original repulsion demo for several minutes on 0.8.0 and I am very pleased with the performance and stability. I did a few memory profiles and even with a large amounts of dots it seems to work just fine. I think we can consider this specific issue fixed! Well done everyone!
Just as a closed it I hit an out of memory error - but I think it may well be because of a memory leak in the implementation as Ayke mentioned some time ago. In any case, it now takes minutes to hit the memory limit rather than seconds.
Sounds like things are improving. :smile: