tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

Add support for reading Go assembly files

Open aykevl opened this issue 1 year ago • 52 comments

This is a pretty big change that adds support for Go assembly files. Go assembly files are different from standard assembly and can't be read by tools like Clang. They also use a different calling convention. On top of that, the tools that can read them (the gc toolchain) only output custom (unstable) object files. In a previous attempt I tried to parse these files but with this attempt I simply read the output of go tool asm -S, which is a more-or-less stable text format (that, importantly, includes the raw machine code and all relocations).

As a result:

  • This implements syscall.seek on 386 and arm. The following works with this commit: GOARCH=arm tinygo test os. See #2578.
  • This changes the math package, probably speeding up some algorithms (although I didn't benchmark them).
  • The crypto/aes package now works (issue #2779).
  • Undefined references to crypto/internal/boring/sig.StandardCrypto in Go 1.19 are fixed (see #3057, #3080).

So far, this is only for Linux (on all supported architectures: 386, amd64, arm, arm64). Adding support for macOS and Windows should be feasible by implementing the appropriate file formats (MachO and COFF). It should be relatively easy to also add support for js/wasm. I'm not so sure about other systems where the GOARCH doesn't match the actual architecture, like WASI and most/all baremetal systems. It might be possible to get them to work by lifting ARM machine code to LLVM IR, but that will be quite some work in itself and certainly something for the longer term.


This replaces #2688. It is still a draft because it depends on #3102 (math intrinsics) and #2904 (drop Go 1.16/1.17 support). It needs to be rebased on top of the dev branch once those PRs are in.

aykevl avatar Aug 28 '22 21:08 aykevl

Looks like macOS and Linux not passing CI on this @aykevl

deadprogram avatar Aug 30 '22 08:08 deadprogram

Looks like needs merge conflict resolved @aykevl

deadprogram avatar Aug 30 '22 15:08 deadprogram

Rebased to fix the merge conflict.

aykevl avatar Aug 30 '22 17:08 aykevl

Can someone take a look at this? I have a number of other changes waiting for this PR to be merged.

aykevl avatar Sep 15 '22 21:09 aykevl

I tried reviewing this, but there's too much compiler stuff I don't yet understand. Can we put this behind a flag temporarily so that it can be merged and allow us to do more tests before making it the default?

Also, is there anything inherently preventing this from working on Darwin, or just "haven't gotten around to it yet"?

dgryski avatar Sep 16 '22 19:09 dgryski

I tried reviewing this, but there's too much compiler stuff I don't yet understand.

If you need help with anything, feel free to ask.

Can we put this behind a flag temporarily so that it can be merged and allow us to do more tests before making it the default?

I guess, but we'd have to review it some time anyway. Not sure what the advantage is. Avoiding breakage?

Also, is there anything inherently preventing this from working on Darwin, or just "haven't gotten around to it yet"?

I just need to write a port to MachO, shouldn't be too difficult. I already made one for Windows. (Those are things I will make PRs for once this PR is in).

aykevl avatar Sep 16 '22 19:09 aykevl

Yes. We can review it and test it more thoroughly without holding up other PRs and without creating large merge conflicts.

Glad to hear Darwin support is possible.

dgryski avatar Sep 16 '22 20:09 dgryski

I think this change also means we can get rid of this 'hack' in the test cases: https://github.com/tinygo-org/tinygo/blob/dev/main_test.go#L173-L180

kenbell avatar Sep 17 '22 09:09 kenbell

@dgryski I still don't understand why this feature should be disabled in the beginning? If it's disabled, that won't help with testing. What is it exactly that you're proposing and what's the advantage of it?

I think this change also means we can get rid of this 'hack' in the test cases: https://github.com/tinygo-org/tinygo/blob/dev/main_test.go#L173-L180

Thanks, yes it does! Happy to see it go away. I've updated this PR.

aykevl avatar Sep 19 '22 12:09 aykevl

@aykevl My goal with the merge but with flag to enable was to allow this to unblock other patches (as you said above) and give people the opportunity to test it more easily until we made it the default.

I'll try to understand this patch tonight and you can merge it (since it seems to be working.)

dgryski avatar Sep 19 '22 14:09 dgryski

My suggestion is to run the test corpus, removing all the purego and noasm tags. If that passes, we can merge this and debug any issues as they come up.

dgryski avatar Sep 22 '22 18:09 dgryski

Just catching up on this PR. Did we ever perform the test suggested by @dgryski and if so do we consider it ready to merge?

deadprogram avatar Sep 26 '22 18:09 deadprogram

@dgryski and @aykevl have we performed the recommended tests and/or are we ready to merge this?

deadprogram avatar Oct 17 '22 09:10 deadprogram

@dkegel-fastly You have a linux box. You want to take a look at running these tests?

dgryski avatar Oct 17 '22 16:10 dgryski

will do

dkegel-fastly avatar Oct 17 '22 19:10 dkegel-fastly

Hrm. "make test" fails on my linux box with

tinygo:ld.lld: error: undefined symbol: log
>>> referenced by log.go:80 (/usr/lib/go-1.18/src/math/log.go:80)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced by log.go:80 (/usr/lib/go-1.18/src/math/log.go:80)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced by log.go:80 (/usr/lib/go-1.18/src/math/log.go:80)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced 4 more times

tinygo:ld.lld: error: undefined symbol: ceil
>>> referenced by floor.go:41 (/usr/lib/go-1.18/src/math/floor.go:41)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)

tinygo:ld.lld: error: undefined symbol: exp
>>> referenced by sinh.go:0 (/usr/lib/go-1.18/src/math/sinh.go:0)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced by exp.go:14 (/usr/lib/go-1.18/src/math/exp.go:14)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced by exp.go:14 (/usr/lib/go-1.18/src/math/exp.go:14)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
>>> referenced 7 more times

tinygo:ld.lld: error: undefined symbol: exp2
>>> referenced by exp.go:140 (/usr/lib/go-1.18/src/math/exp.go:140)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)

tinygo:ld.lld: error: undefined symbol: floor
>>> referenced by floor.go:13 (/usr/lib/go-1.18/src/math/floor.go:13)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)

tinygo:ld.lld: error: undefined symbol: trunc
>>> referenced by floor.go:58 (/usr/lib/go-1.18/src/math/floor.go:58)
>>>               /home/dank/.cache/tinygo/thinlto/llvmcache-CC40CEA9C88595CE4A2271BD52FFCD6A1FF971D3:(runtime.run$1$gowrapper)
failed to run tool: ld.lld
--- FAIL: TestBuild (0.00s)
    --- FAIL: TestBuild/Host (0.00s)
        --- FAIL: TestBuild/Host/math.go (1.25s)
            main.go:1192: error: failed to link /tmp/tinygo1886996487/main: exit status 1
FAIL
FAIL	github.com/tinygo-org/tinygo	14.778s

It's like it needs -lm or something.

dankegel avatar Oct 18 '22 03:10 dankegel

@dankegel most likely you need to run tinygo clean: you seem to be using and old cached build of musl. See #3099 for details.

aykevl avatar Oct 18 '22 04:10 aykevl

Thanks. I'm afraid I'm hitting panics on the real test, though.

e.g.

=== CONT  TestCorpus/github.com/dgryski/go-postings
    main_test.go:523: go: creating new go.mod: module github.com/tinygo/tinygo-corpus-test
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0413fe4a0 stack=[0xc0413fe000, 0xc0613fe000]
fatal error: stack overflow

runtime stack:
runtime.throw({0x57c250d?, 0x77bcda0?})
	/usr/lib/go-1.18/src/runtime/panic.go:992 +0x71
runtime.newstack()
	/usr/lib/go-1.18/src/runtime/stack.go:1101 +0x5cc
runtime.morestack()
	/usr/lib/go-1.18/src/runtime/asm_amd64.s:547 +0x8b
...

What's the easiest way to tell the corpus test to use a bigger stack?

dankegel avatar Oct 18 '22 15:10 dankegel

Pretty sure that test shouldn't be hitting a stack overflow. The assembly version of the code is a few instructions and a table lookup.

Edit: oh, saw the stack overflow was in the compiler not the test.

dgryski avatar Oct 18 '22 15:10 dgryski

What's the easiest way to tell the corpus test to use a bigger stack?

It's using a 1GB stack, more stack is not going to help you here.

(Now running the corpus test to reproduce the issue)

aykevl avatar Oct 18 '22 15:10 aykevl

I rebased this PR and I can't reproduce the stack overflow. I think it has been fixed in the dev branch. (That said, I'm running on a slightly unusual setup with a linux/arm64 host and testing on linux/amd64 via qemu-x86_64).

aykevl avatar Oct 20 '22 20:10 aykevl

I'll retest tonight.

dkegel-fastly avatar Oct 20 '22 21:10 dkegel-fastly

With some testing (now with the build tags removed), I don't think this is quite ready yet:

  • github.com/dgryski/go-bloomindex crashed QEMU with a SIGILL. I suspect this is actually a QEMU bug (apparently the ptest instruction isn't commonly used and isn't implemented in QEMU)
  • github.com/dgryski/go-chaskey results in a SIGSEGV, apparently because it jumps somewhere it really shouldn't.
  • github.com/dgryski/go-cuckoof also resulted in a SIGSEGV, this time it appears to happen somewhere in the Go assembly wrapper code

So while the first seems to be a QEMU bug, the other two are most likely real bugs that I need to fix. I did not yet test other packages than these three.

aykevl avatar Oct 20 '22 21:10 aykevl

test corpus FTW!

dkegel-fastly avatar Oct 20 '22 21:10 dkegel-fastly

github.com/dgryski/go-bloomindex crashed QEMU with a SIGILL. I suspect this is actually a QEMU bug (apparently the ptest instruction isn't commonly used and isn't implemented in QEMU)

Not anymore with a recent QEMU (apparently they implemented ptest) but it now triggers a segmentation fault... so there is still a bug in TinyGo.

aykevl avatar Oct 21 '22 13:10 aykevl

I'm always so pleased when testing reveals bugs :D

dgryski avatar Oct 21 '22 13:10 dgryski

Took me a while to figure out, but I think I found the bug: LLVM doesn't save/restore the rbp register through the call even though the assembly marks it as clobbered.

aykevl avatar Oct 21 '22 14:10 aykevl

I know a solution to this, but it's going to need a bit of a rewrite. It's a cleanup I've wanted to do after this PR landed but it appears I now need to clean up my code for it to work :smiling_face_with_tear:

aykevl avatar Oct 21 '22 15:10 aykevl

The rewrite works. Those 3 tests that failed before now pass with my new implementation (I didn't test any others yet). The resulting assembly is a lot cleaner so it's probably also a lot faster (if that matters).

Now I need to clean it up, implement all the loose ends, and make sure it also works on all other architectures. And of course test the rest of the corpus.

aykevl avatar Oct 21 '22 22:10 aykevl

Currently fails with

    main_test.go:523: FAIL	github.com/dgryski/go-bloomindex	0.174s
    main_test.go:523: FAIL	github.com/dgryski/go-metro	0.179s
    main_test.go:523: FAIL	github.com/dgryski/go-marvin32	0.176s
    main_test.go:523: FAIL	github.com/dgryski/go-farm	0.177s
    main_test.go:523: FAIL	github.com/dgryski/go-cuckoof	0.176s

and then a stack overflow (which probably hides a bunch of failures).

I narrowed the go-farm failure down slightly; the hash tests pass, but 'tinygo test -run TestFingerprint32' fails.

Also, go-postings on its own still fails here :-(

dankegel avatar Oct 22 '22 21:10 dankegel