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

cmd/compile: simplify converting a Go function to buildable Go assembly #29538

Open
mvdan opened this issue Jan 3, 2019 · 6 comments
Open

cmd/compile: simplify converting a Go function to buildable Go assembly #29538

mvdan opened this issue Jan 3, 2019 · 6 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FeatureRequest NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made.
Milestone

Comments

@mvdan
Copy link
Member

mvdan commented Jan 3, 2019

I'm slowly learning x86 assembly and how Go uses it, so it's very useful to see how existing functions in Go are translated to it.

The next logical step is to be able to make small changes to that assembly, to see if my understanding of it is correct, and to play with trying to make the code better manually. However, as I've found out, there's no easy way to move a function from a .go file to a .s file.

After reading some blog posts online and some trial and error, I've found out that most functions can be translated by hand. For example, take a single file with this very simple Go function:

package p

func Add(x, y int) int {
        return x + y
}

With go tool compile -S f.go, we can scroll through the output to find its assembly:

TEXT    "".Add(SB), NOSPLIT|ABIInternal, $0-24
FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
FUNCDATA        $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
PCDATA  $2, $0
PCDATA  $0, $0
MOVQ    "".y+16(SP), AX
MOVQ    "".x+8(SP), CX
ADDQ    CX, AX
MOVQ    AX, "".~r2+24(SP)
RET

However, we can't just add the assembly to the source code and make the Go func a stub, as that just fails:

$ go build
# foo.bar
./f.s:1: string constant must be an immediate
./f.s:7: string constant must be an immediate
./f.s:8: string constant must be an immediate
./f.s:10: string constant must be an immediate
asm: assembly of ./f.s failed

We can apply multiple changes to get it to work:

  • remove FUNCDATA and PCDATA lines
  • replace "".Func with ·Func
  • remove all addressing modes (?) like NOSPLIT|ABIInternal, to get rid of errors like illegal or missing addressing mode for symbol NOSPLIT
  • replace "".var with var
  • replace "".~r2 with r2 (I still don't understand that tilde)

End result, which works just as fine as the Go code:

TEXT    ·Add(SB),$0-24
MOVQ    y+16(SP), AX
MOVQ    x+8(SP), CX
ADDQ    CX, AX
MOVQ    AX, r2+24(SP)
RET

In this case, we're done. But if the function had jumps like JMP 90, we'd have to add a label like L90 after finding the right line, and then modifying the jump to be JMP L90. go vet also tends to complain about the result of the process above, even when the assembly works - for example, in our case:

$ go vet
# foo.bar
./f.s:6:1: [amd64] Add: RET without writing to 8-byte ret+16(FP)

I've succsesfully done this with multiple funcs on the small side, but it's tedious. Worse even, I still haven't been able to correctly translate a function calling another function to assembly - I keep getting panics like runtime: unexpected return pc for ..., which are very confusing.

I think the compiler should support translating Go functions to assembly which can just be dumped onto a .s file after replacing their Go implementation with a stub. It doesn't matter if it couldn't directly work on some weird edge cases; if at least it could give a starting point after doing grunt work as mentioned above, that would already be a huge improvement.

Ideas that come to mind to improve go tool compile -S (thanks for @josharian for his input):

  • Allow filtering for functions, e.g. via a regexp. Similar to GOSSAFUNC.
  • Add a mode where the output can theoretically/probably be built inside a .s file as-is.

For example, borrowing from flags like compile -d, one might imagine go tool compile -S=func=Add,clean to do what I had done manually above.

If this makes sense, I'm happy to do some of the work during the upcoming cycle. I'll just need a bit of help from the compiler folks, as my assembly (and especially Go assembly syntax) knowledge is limited.

/cc @randall77 @griesemer @josharian @mdempsky @martisch, as per https://dev.golang.org/owners/.

@mvdan mvdan added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Jan 3, 2019
@mvdan
Copy link
Member Author

mvdan commented Jan 3, 2019

Someone on Slack mentioned https://github.com/rsc/tmp/tree/master/go2asm, which seems to be a very similar tool. Looks a bit outdated and seems like it only supports amd64. I wonder if optimistically supporting all architectures would be a good idea. /cc @rsc

@josharian
Copy link
Contributor

Allow filtering for functions,

This is something I want frequently.

@mvdan
Copy link
Member Author

mvdan commented Jan 4, 2019

I forgot to address one point - why this should be part of go tool compile. I personally think this is something at the same level of usefulness as go tool compile -S, if not more. But I'd be fine with it living in a repo like x/tools instead, as long as it's still officially maintained and easy to find.

@qdm12
Copy link
Contributor

qdm12 commented Jul 20, 2022

Does anyone know how to handle for example CALL runtime.convT32(SB)? I get the error expected '(', found . 🤔 Is it possible at all to do calls to other functions from assembly code in that case?

@cherrymui
Copy link
Member

Change the . to middle dot ·, CALL runtime·convT32(SB)

@qdm12
Copy link
Contributor

qdm12 commented Aug 16, 2022

Thanks! Got 3 new questions now if that's ok to ask since it might be relevant:

  1. How do you create an inlined slice like x := []byte{1, 2} in assembly? I'm trying with type·[2]uint8(SB) but it doesn't like the square brackets at all.
  2. How do you deal with autotmp_7? For example MOVQ ""..autotmp_7+24(SP), AX; this seems to be used when trying to write to a *bytes.Buffer 🤔
  3. How do you call runtime functions such as make for a byte slice? For example CALL runtime·makeslice(SB) fails with relocation target runtime.makeslice not defined for ABI0 (but is defined for ABIInternal) but then I don't think we can use ABIInternal right? 🤔

@seankhliao seankhliao added this to the Unplanned milestone Aug 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FeatureRequest NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made.
Projects
Status: Triage Backlog
Development

No branches or pull requests

6 participants