Skip to content
New issue

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

internal/task: use asyncify on WASM #2011

Merged
merged 2 commits into from Nov 14, 2021
Merged

Conversation

niaow
Copy link
Member

@niaow niaow commented Jul 21, 2021

This currently requires manually running the compiled wasm through wasm-opt.
Very little works so far.
Issue: #1101

@dgryski
Copy link
Member

dgryski commented Sep 24, 2021

What work is remaining to get this PR finished? I'd love to help out any way I can.

@niaow
Copy link
Member Author

niaow commented Sep 24, 2021

This is mainly blocked by #2035 I think

@dgryski
Copy link
Member

dgryski commented Sep 24, 2021

While that bug is getting fixed, is there work that could be done on this issue even with having to run wasm-opt manually?

@aykevl
Copy link
Member

aykevl commented Sep 24, 2021

I'm almost inclined to say that we should perhaps just get this finished (even without #2035). That means compiling for WebAssembly requires an extra binary - but I think that's only required when using the new Asincify based scheduler?

Thoughts?
I don't like adding an external dependency (it makes distribution a whole lot harder) but right now concurrency on WebAssembly is already somewhat broken and this PR might fix it.

@niaow
Copy link
Member Author

niaow commented Sep 25, 2021

Alright, I can modify this to just shell out to wasm-opt for now when using asyncify.

@niaow
Copy link
Member Author

niaow commented Sep 25, 2021

I made that change and attempted to debug some issues. It appears that after the initial launch suspend, resumed goroutines will not suspend again. I don't quite understand it yet.

@dgryski
Copy link
Member

dgryski commented Sep 25, 2021

I'll take a look at this on Monday and see what I can come up with.

@niaow
Copy link
Member Author

niaow commented Oct 12, 2021

I have fixed what was probably the biggest bug. However, there are still some things left to be fixed apparently, such as weird WASI crashes and some sort of unicode error in the reflect test (. . . and i still havent added binaryen to CI).

@dgryski
Copy link
Member

dgryski commented Oct 12, 2021

\o/. This works great on my little test ping pong program (included below)

package main

import (
	"time"
)

func pinger(pinger <-chan int, ponger chan<- int, who string) {
	for {
		<-pinger
		println(who)
		time.Sleep(time.Second)
		ponger <- 1
	}
}

func main() {
	ping := make(chan int)
	pong := make(chan int)

	go pinger(ping, pong, "ping")
	go pinger(pong, ping, "pong")

	ping <- 1

	select {}
}

However, I'm getting an unaligned pointer error with gc=leaking on wasmtime when trying to read the clock.

I managed to fix it with this patch.

 func ticks() timeUnit {
-       var nano uint64
-       clock_time_get(0, timePrecisionNanoseconds, &nano)
+       var b [16]byte
+       ptr := uintptr(unsafe.Pointer(&b[0]))
+       ptr += 8 + (^ptr & 0x7) + 1
+
+       clock_time_get(0, timePrecisionNanoseconds, (*uint64)(unsafe.Pointer(ptr)))
+       nano := *(*uint64)(unsafe.Pointer(ptr))
        return timeUnit(nano)
 }

It seems like a proper fix lies elsewhere, although I'm not sure where..

Trying to run the above ping-pong code with gc=conservative instead produces:

Caused by:
    0: failed to invoke command default
    1: wasm trap: uninitialized element
       wasm backtrace:
           0: 0x6c376 - <unknown>!<wasm function 266>
           1: 0x1489c - <unknown>!<wasm function 68>
           2: 0x2e336 - <unknown>!<wasm function 158>
           3: 0x2d77d - <unknown>!<wasm function 156>
           4: 0x2d636 - <unknown>!<wasm function 155>

I'm still looking into this..

@niaow
Copy link
Member Author

niaow commented Oct 12, 2021

However, I'm getting an unaligned pointer error with gc=leaking on wasmtime when trying to read the clock.

I suspect this may be fixed by #2009. This PR is kinda old so I can rebase tommorow.

@dgryski
Copy link
Member

dgryski commented Oct 12, 2021

Adding -g to the options provided to wasm-opt with the following patch:

diff --git a/builder/build.go b/builder/build.go
index df329bd7..7568e8ae 100644
--- a/builder/build.go
+++ b/builder/build.go
@@ -641,7 +641,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
                                default:
                                        return fmt.Errorf("unknown opt level: %q", config.Options.Opt)
                                }
-                               cmd := exec.Command("wasm-opt", "--asyncify",
+                               cmd := exec.Command("wasm-opt", "-g", "--asyncify",
                                        "--optimize-level", strconv.Itoa(optLevel),
                                        "--shrink-level", strconv.Itoa(shrinkLevel),
                                        executable, "--output", executable)

gives me a much better error:

Caused by:
    0: failed to invoke command default
    1: In func wasi_snapshot_preview1::args_sizes_get at write size: Pointer not aligned to 4: Region { start: 1066183, len: 4 }
       wasm backtrace:
           0: 0x320f - <unknown>!runtime.initAll
           1: 0x307f - runtime.run$1
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/runtime/scheduler_any.go:19:10
           2: 0x301d - <goroutine wrapper>
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/runtime/scheduler_any.go:18:2
           3: 0x3cb6 - <unknown>!tinygo_launch
           4:  0x538 - (*internal/task.Task).Resume
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/internal/task/task_asyncify.go:94:17
           5: 0x2edb - runtime.scheduler
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/runtime/scheduler.go:171:11
                     - runtime.run
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/runtime/scheduler_any.go:24:11
                     - _start
                           at /Users/dgryski/go/src/github.com/tinygo-org/tinygo/src/runtime/runtime_wasm_wasi.go:21:5

So indeed, something about asyncify is futzing with the alignment of the pointers and making things unhappy.

@dgryski
Copy link
Member

dgryski commented Oct 12, 2021

I rebased my local copy against dev with no success. Same pointer alignment issue. I'll keep looking at this..

@dgryski
Copy link
Member

dgryski commented Oct 13, 2021

Patching gc_leaking.go with

 func initHeap() {
-       // preinit() may have moved heapStart; reset heapptr
-       heapptr = heapStart
+       // preinit() may have moved heapStart; reset heapptr and make sure it's aligned
+       heapptr = align(heapStart)
 }

allows both gc=conservative and gc=leaking to run the pingpong example.

@niaow
Copy link
Member Author

niaow commented Oct 13, 2021

For some reason I thought chocolatey had binaryen. I guess I will need to switch over the install then.

@dgryski
Copy link
Member

dgryski commented Oct 13, 2021

Something about the asyncify code is causing #2172 to fail

Copy link
Member

@aykevl aykevl left a comment

This looks pretty clean. I would like to take a more closer look at some parts, but so far I only have some relatively minor comments.

azure-pipelines.yml Outdated Show resolved Hide resolved
compiler/compiler_test.go Outdated Show resolved Hide resolved
s.args = args

// Create a stack.
stackSize = align(stackSize)
Copy link
Member

@aykevl aykevl Oct 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, I would expect an aligned stack to be passed in. For example, a stack size of 2048 is very much aligned.
I never really thought about aligning it, but yeah, technically it might need to be aligned.

I don't think this needs to be done at runtime (although there is a good chance it's optimized away in this case): it's only ever specified in code (in compileopts/target.go for example) or in target JSON files. The only way to easily change it is by modifying the JSON file directly, for example. If we're going to align it, I think it would be better to do it at compile time instead of at runtime, for example here:

stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)

This can be done by aligning to 16 bytes, I believe that's nowadays a pretty standard alignment (even on riscv32 and i386, for example).

src/internal/task/task_asyncify.go Show resolved Hide resolved
targets/wasi.json Outdated Show resolved Hide resolved
testdata/channel.go Outdated Show resolved Hide resolved
@aykevl
Copy link
Member

aykevl commented Oct 14, 2021

For some reason I thought chocolatey had binaryen. I guess I will need to switch over the install then.

scoop has it, not choco.

@niaow
Copy link
Member Author

niaow commented Oct 14, 2021

I have created a fix for the WASM scheduler recursion failure: #2178

@dgryski
Copy link
Member

dgryski commented Oct 14, 2021

I think this branch still has an odd failure. Trying to build the following program with
tinygo build -scheduler=asyncify -gc=leaking -target=wasi -o m.wasm main.go

package main

import (
	"context"
)

func main() {
	ctx := context.Background()

	select {
	case <-ctx.Done():
	}
}

produces the following build failure:

Call parameter type does not match function signature!
%runtime.channel.56* null
 %runtime.channel.80*  %1 = call i1 bitcast (i1 (%runtime.channel*, i8*, %runtime.channelBlockedList*, i8*, i8*)* @runtime.chanRecv to i1 (%runtime.channel.80*, i8*, %runtime.channelBlockedList.79*, i8*, i8*)*)(%runtime.channel.56* null, i8* null, %runtime.channelBlockedList.79* %chan.blockedList, i8* undef, i8* null), !dbg !1627

@niaow
Copy link
Member Author

niaow commented Oct 21, 2021

I have been poking around, and it currently appears that the interface lowering pass is the first pass introducing type errors when building that code.

@niaow
Copy link
Member Author

niaow commented Oct 21, 2021

I identified the issue and seperated it into #2197

@niaow
Copy link
Member Author

niaow commented Oct 30, 2021

Hmm, it looks like LLVM 10 may have issues with the not-yet-resolved symbols for asyncify. I am not really sure how we want to handle this. I guess we could disable this on LLVM 10? I am not sure of a better option.

@dgryski
Copy link
Member

dgryski commented Oct 30, 2021

Given that llvm 13 is out now, how many versions behind is tinygo committed to supporting?

@aykevl
Copy link
Member

aykevl commented Oct 30, 2021

Hmm, it looks like LLVM 10 may have issues with the not-yet-resolved symbols for asyncify. I am not really sure how we want to handle this. I guess we could disable this on LLVM 10? I am not sure of a better option.

Don't let that be a blocker. If LLVM 10 is an issue, we'll drop LLVM 10 support. That's a really old version by now. (Usually we support the last two LLVM versions or so, to make things easier for distributions). Even more so because this PR is a blocker to add support for newer LLVM versions.

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

It looks like the version of GCC used by Debian Stretch is too old to build binaryen correctly, and that the cache was hiding this fact.

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

I managed to work around that by using the copy of clang that we build.
Now the only remaining issue is Windows.

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

If my debugging attempts are correct, it looks like binaryen is still trying to dynamically link against libstdc++ and libgcc from mingw, and failing (error code 0xc0000139).

[niaow@finch bin]$ objdump -p wasm-opt.exe | grep "DLL Name:"
	DLL Name: libgcc_s_seh-1.dll
	DLL Name: KERNEL32.dll
	DLL Name: msvcrt.dll
	DLL Name: libwinpthread-1.dll
	DLL Name: USER32.dll
	DLL Name: libstdc++-6.dll

@aykevl
Copy link
Member

aykevl commented Nov 11, 2021

For the TinyGo build I use -static -static-libgcc -static-libstdc++, I think you might need those for binaryen too.

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

Yeah I just realized that. Adding now.

@aykevl
Copy link
Member

aykevl commented Nov 11, 2021

I can try rebooting to Windows to test some build options, would that help?

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

I suspect that I just fixed it. If not, I finally have a Windows VM setup so I can test there.

@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

All tests are now passing.

This change implements a new "scheduler" for WebAssembly using binaryen's asyncify transform.
This is more reliable than the current "coroutines" transform, and works with non-Go code in the call stack.

runtime (js/wasm): handle scheduler nesting

If WASM calls into JS which calls back into WASM, it is possible for the scheduler to nest.
The event from the callback must be handled immediately, so the task cannot simply be deferred to the outer scheduler.
This creates a minimal scheduler loop which is used to handle such nesting.
@niaow
Copy link
Member Author

niaow commented Nov 11, 2021

I did a quick squash/cleanup, so I think this is ready for a final review.

@niaow niaow requested a review from aykevl Nov 11, 2021
@deadprogram
Copy link
Member

deadprogram commented Nov 11, 2021

Do we need to modify the dev Dockerfile if you want to use this scheduler? https://github.com/tinygo-org/tinygo/blob/dev/Dockerfile#L23-L28

Dockerfile Show resolved Hide resolved
Makefile Outdated Show resolved Hide resolved
Oops I forgot to install cmake and ninja.
Copy link
Member

@deadprogram deadprogram left a comment

All tests are now passing, and seems good to me.

@deadprogram
Copy link
Member

deadprogram commented Nov 12, 2021

@aykevl any last comments before merge?

@gedw99
Copy link

gedw99 commented Nov 12, 2021

Is this the fix to get WASM compilation working again ?

@deadprogram
Copy link
Member

deadprogram commented Nov 12, 2021

Is this the fix to get WASM compilation working again ?

WASM compilation is working afaik. This PR is to add asyncify as an option for task scheduling for WASM programs.

@gedw99
Copy link

gedw99 commented Nov 13, 2021

Is this the fix to get WASM compilation working again ?

WASM compilation is working afaik. This PR is to add asyncify as an option for task scheduling for WASM programs.

@deadprogram

ok thank for the response.

#1972 is blocking one . I believe is for gio when building to Web ( wasm ). I need to try again...

Copy link
Member

@aykevl aykevl left a comment

This looks good to me.
I can't really comment on the Dockerfile changes, is the issue there fixed now? I don't know a whole lot about Docker.

default:
return ""
}
}

// Find wasm-opt, or exit with an error.
func findWasmOpt() string {
Copy link
Member

@aykevl aykevl Nov 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[this comment was intended to be part of the review but I forgot to click the "start review" button]

I think this function can be simplified. In short: the bundled wasm-opt binaries don't need to be checked for a version.

I would suggest the following logic:

if $TINYGOROOT/bin/wasm-opt[.exe] exists:
    return this path
if $TINYGOROOT/build/wasm-opt[.exe] exists:
    return this path
look for wasm-opt using LookPath
if not found:
    print or return error
check for version
if version too old:
    return error
return this path

This is not a blocker to me.

Copy link
Member Author

@niaow niaow Nov 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still be doing a version check regardless for now, until we can actually link it in properly.

Copy link
Member Author

@niaow niaow Nov 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has already caught one issue, so it is still helpful.

@niaow
Copy link
Member Author

niaow commented Nov 14, 2021

The Dockerfile issues have been fixed.

@deadprogram
Copy link
Member

deadprogram commented Nov 14, 2021

OK merge time! Great work on this @niaow thank you. Also thanks to everyone who helped review/test.

@deadprogram deadprogram merged commit 539e132 into tinygo-org:dev Nov 14, 2021
6 checks passed
@aykevl aykevl mentioned this pull request Nov 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants