O que você precisa saber se deseja chamar as funções Go do assembler

Você encontrou uma área realmente complicada de código asm.
Minha primeira sugestão não é tentar chamar do assembler para o Go. - Ian Lance Taylor

Desde que o código do seu assembler faça algo simples, tudo ficará bem.


Assim que você tiver uma tarefa para chamar uma função do código assembler do Go, uma das primeiras dicas que você receberá: não faça isso.


Mas e se você realmente precisar? Nesse caso, por favor, sob gato.



Convenção de chamada


Tudo começa com o fato de que você precisa entender como passar argumentos para funções e como aceitar seus resultados .


Eu recomendo que você se familiarize com as funções Go na linguagem assembly , onde a maioria das informações necessárias é claramente descrita.


Normalmente, a convenção de chamada varia de plataforma para plataforma, pois o conjunto de registros disponíveis pode variar. Consideraremos apenas GOARCH=amd64, mas no caso de Go, as convenções diferem não tão significativamente.


Aqui estão alguns recursos da convenção de chamada de função no Go:


  • Todos os argumentos são passados ​​pela pilha, exceto pelo "contexto" nos fechamentos, que é acessível através do registro DX(% rdx).
  • ( arguments).
  • .
  • . , .

, . .



, register-based calling convention. Go.


, :


  • , .
  • 0(SP), 8(SP) ( 8 ), .
  • n(SP), n — . int64, 16(SP).

.


package main

func asmfunc(x int32) (int32, int32)

func gofunc(a1 int64, a2, a3 int32) (int32, int32) {
    return int32(a1) + a2, int32(a1) + a3
}

func main() {
    v1, v2 := asmfunc(10)
    println(v1, v2) // => 3, 11
}

// func asmfunc(x int32) (int32, int32)
TEXT ·asmfunc(SB), 0, $24-12
  MOVL x+0(FP), AX
  MOVQ $1, 0(SP)  //   (a1 int64)
  MOVL $2, 8(SP)  //   (a2 int32)
  MOVL AX, 12(SP) //   (a3 int32)
  CALL ·gofunc(SB)
  MOVL 16(SP), AX //   
  MOVL 20(SP), CX //   
  MOVL AX, ret+8(FP)  //   
  MOVL CX, ret+12(FP) //   
  RET

$24-16 (locals=24 bytes, args=16 bytes)

          0     8     12    16     20     SP
locals=24 [a1:8][a2:4][a3:4][ret:4][ret:4]
(ret    asmfunc,   gofunc)

        0    4          8      12     FP
args=16 [x:4][padding:4][ret:4][ret:4]
(ret    main,   asmfunc)

, 4 . , , (8 amd64).


, . — int32, — int64, offset 8, 4, reflect.TypeOf(int64(0)).Align() 8.


, FP go vet.


stackmap


-.


package foo

import (
    "fmt"
    "testing"
)

func foo(ptr *object)

type object struct {
    x, y, z int64
}

func printPtr(ptr *object) {
    fmt.Println(*ptr)
}

func TestFoo(t *testing.T) {
    foo(&object{x: 11, y: 22, z: 33})
}

TEXT ·foo(SB), 0, $8-8
        MOVQ ptr+0(FP), AX
        MOVQ AX, 0(SP)
        CALL ·printPtr(SB)
        RET

, - stackmap:


=== RUN   TestFoo
runtime: frame <censored> untyped locals 0xc00008ff38+0x8
fatal error: missing stackmap

, , GC stackmaps. Go , .


stub ( Go ). , stackmap , .


, stackmap (), NO_LOCAL_POINTERS ( ).


NO_LOCAL_POINTERS


TestFoo :


#include "funcdata.h"

TEXT ·foo(SB), 0, $8-8
        NO_LOCAL_POINTERS
        MOVQ ptr+0(FP), AX
        MOVQ AX, 0(SP)
        CALL ·printPtr(SB)
        RET

, .


, ? , , "" , , , , ?


, heap, , . , , . GC "" , , .


, , "" (escapes to heap) escape analysis, , .


NO_LOCAL_POINTERS: , , , GC . .


Go non-cooperative preemption, , .


Go. , go:nosplit, , , NO_LOCAL_POINTERS .


GO_ARGS


, Go prototype, GO_ARGS.


GO_ARGSfuncdata.h, NO_LOCAL_POINTERS. , stackmap Go .


, stackmap . args_stackmap . : , stackmap.


GO_RESULTS_INITIALIZED


Go , ( ) GO_RESULTS_INITIALIZED .


:


// func getg() interface{}
TEXT ·getg(SB), NOSPLIT, $32-16
  //     .
  //      .
  MOVQ $0, ret_type+0(FP)
  MOVQ $0, ret_data+8(FP)
  GO_RESULTS_INITIALIZED
  //    ...
  RET

, , -.


GitHub.


Go JIT-


Go .


Garbage collector Go , , , , , Go JIT'.


, . , , .


calljit-v1


// file jit.go
package main

import (
    "log"
    "reflect"
    "syscall"
    "unsafe"
)

func main() {
    a := funcAddr(goFunc)

    code := []byte{
        // MOVQ addr(goFunc), AX
        0xb8, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
        // CALL AX
        0xff, 0xd0,
        // RET
        0xc3,
    }

    executable, err := mmapExecutable(len(code))
    if err != nil {
        log.Panicf("mmap: %v", err)
    }
    copy(executable, code)
    calljit(&executable[0])
}

func calljit(code *byte)

func goFunc() {
    println("called from JIT")
}

func mmapExecutable(length int) ([]byte, error) {
    const prot = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC
    const flags = syscall.MAP_PRIVATE | syscall.MAP_ANON
    return mmapLinux(0, uintptr(length), prot, flags, 0, 0)
}

func mmapLinux(addr, length, prot, flags, fd, off uintptr) ([]byte, error) {
    ptr, _, err := syscall.Syscall6(
        syscall.SYS_MMAP,
        addr, length, prot, flags, fd, offset)
    if err != 0 {
        return nil, err
    }
    slice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
        Data: ptr,
        Len:  int(length),
        Cap:  int(length),
    }))
    return slice, nil
}

func funcAddr(fn interface{}) uintptr {
    type emptyInterface struct {
        typ   uintptr
        value *uintptr
    }
    e := (*emptyInterface)(unsafe.Pointer(&fn))
    return *e.value
}

// file jit_amd64.s
TEXT ·calljit(SB), 0, $0-8
        MOVQ code+0(FP), AX
        JMP AX



, ( ):


$ go build -o jit . && ./jit
called from JIT

goFunc, JIT-:


 func goFunc() {
    println("called from JIT")
+   runtime.GC()
 }

:


$ go build -o jit . && ./jit
called from JIT
runtime: unexpected return pc for main.goFunc called from 0x7f9465f7c007
stack: frame={sp:0xc00008ced0, fp:0xc00008cef0} stack=[0xc00008c000,0xc00008d000)
000000c00008cdd0:  0000000000000000  00007f94681f7558 
000000c00008cde0:  000000c000029270  000000000000000b 
... (+ more)

: unexpected return pc for main.goFunc called from 0x7f9465f7c007, 0x7f9465f7c007 — JIT-. , runtime.


, , FP BP return address , .


Go runtime , JIT-, , .


calljit , , Go . , Go , , Go (calljit).


#include "funcdata.h"

TEXT ·calljit(SB), 0, $8-8
        NO_LOCAL_POINTERS
        MOVQ code+0(FP), AX
        JMP AX
callgo:
        CALL CX
        JMP (SP)

:


  • 8 , return address JIT .
  • NO_LOCAL_POINTERS - , CALL.

calljit , , , Go . , CX , [rsp] — , .


, callgo. , . main():


a := funcAddr(goFunc)
j := funcAddr(calljit) + 36

code := []byte{
    // MOVQ funcAddr(goFunc), CX
    0x48, 0xc7, 0xc1, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
    // MOVQ funcAddr(calljit), DI
    0x48, 0xc7, 0xc7, byte(j), byte(j >> 8), byte(j >> 16), byte(j >> 24),
    // LEAQ 6(PC), SI
    0x48, 0x8d, 0x35, (4 + 2), 0, 0, 0,
    // MOVQ SI, (SP)
    0x48, 0x89, 0x34, 0x24,
    // JMP DI
    0xff, 0xe7,

    // ADDQ $framesize, SP
    0x48, 0x83, 0xc4, (8 + 8),
    // RET
    0xc3,
}

CX, callgo DI, SI , [rsp]. 4+2LEAQ .


, , ADDQ. 8 8 BP.


calljit:


   return address  callgo
|
|       Go    BP   
|       |
0(SP)   8(SP)    16(SP)    24(SP)
[empty] [prevBP] [retaddr] [arg1:code]
|             /  |         |
|            /   |           calljit (caller frame)
|           /    |      
|          /       CALL   calljit
|         /
calljit frame, 16 bytes     

runtime.GC():


$ go build -o jit . && ./jit
called from JIT

Go . , — , .


, , . Go.


Go Internal ABI


Go Internal ABI — .


Go , , . ABI, , .


:


  1. .
  2. .

calling convention ABI0, ABIInternal.


Go -S, , ABIInternal , ABI0:



ABIInternal , ABI1, . ABIInternal calling convention .


, .


, Go . , .





Hub-


, . , , , . .


All Articles