لقد واجهت منطقة مشعرة حقًا من كود ASM.
اقتراحي الأول ليس محاولة الاتصال من المجمّع في Go. - إيان لانس تايلور
طالما أن رمز المجمع الخاص بك يفعل شيئًا بسيطًا ، فإن كل شيء يبدو جيدًا.
بمجرد أن تكون لديك مهمة لاستدعاء وظيفة من كود المجمّع لـ Go ، فإن إحدى النصائح الأولى التي ستحصل عليها: لا تفعل ذلك.
ولكن ماذا لو كنت حقا بحاجة إليها؟ في هذه الحالة ، من فضلك ، تحت القط.

اصطلاح الدعوة
يبدأ كل شيء بحقيقة أنك بحاجة إلى فهم كيفية تمرير الحجج إلى الوظائف وكيفية قبول نتائجها .
أوصي بأن تتعرف على وظائف Go بلغة التجميع ، حيث يتم وصف معظم المعلومات التي نحتاجها بوضوح.
عادة ، تختلف اصطلاح الاتصال من نظام إلى آخر ، حيث قد تختلف مجموعة السجلات المتاحة. سننظر فقط GOARCH=amd64
، ولكن في حالة Go ، تختلف الاتفاقيات بشكل كبير.
فيما يلي بعض ميزات اصطلاح استدعاء الوظيفة في Go:
- يتم تمرير كافة الوسائط من خلال المكدس ، باستثناء "السياق" في عمليات الإغلاق ، ويمكن الوصول إليها من خلال السجل
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)
}
// 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_ARGS
— funcdata.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
package main
import (
"log"
"reflect"
"syscall"
"unsafe"
)
func main() {
a := funcAddr(goFunc)
code := []byte{
0xb8, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
0xff, 0xd0,
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{
0x48, 0xc7, 0xc1, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
0x48, 0xc7, 0xc7, byte(j), byte(j >> 8), byte(j >> 16), byte(j >> 24),
0x48, 0x8d, 0x35, (4 + 2), 0, 0, 0,
0x48, 0x89, 0x34, 0x24,
0xff, 0xe7,
0x48, 0x83, 0xc4, (8 + 8),
0xc3,
}
CX
, callgo
DI
, SI
, [rsp]
. 4+2
— LEAQ
.
, , 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, , .
:
- .
- .
calling convention ABI0
, ABIInternal
.
Go -S
, , ABIInternal
, ABI0
:

ABIInternal
, ABI1
, . ABIInternal
calling convention .
, .
, Go . , .

Hub-
, . , , , . .