fastcall

package module
v0.0.0-...-7835fc4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2021 License: MIT Imports: 1 Imported by: 0

README

Fastcall

Benchmark Result

goos: darwin
goarch: amd64
pkg: github.com/barmco/fastcall/test
cpu: Intel(R) Xeon(R) CPU E5-1620 v2 @ 3.70GHz
BenchmarkFastNoop-8         	220709697	         5.409 ns/op	       0 B/op	       0 allocs/op
BenchmarkFastCallbackGo-8   	129235213	         9.115 ns/op	       0 B/op	       0 allocs/op
BenchmarkFastRoundtrip-8    	81416146	        14.79 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoNoop-8          	 6661453	       176.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoCallback-8      	 6579610	       183.3 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoRoundtrip-8     	 2789162	       403.9 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/barmco/fastcall/test	9.402s

--
#GOMAXPROCS=1

goos: darwin
goarch: amd64
pkg: github.com/barmco/fastcall/test
cpu: Intel(R) Xeon(R) CPU E5-1620 v2 @ 3.70GHz
BenchmarkFastNoop       	193424708	         5.415 ns/op	       0 B/op	       0 allocs/op
BenchmarkFastCallbackGo 	134540083	         8.904 ns/op	       0 B/op	       0 allocs/op
BenchmarkFastRoundtrip  	83491647	        18.21 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoNoop        	 6786681	       179.7 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoCallback    	 6437816	       182.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkCGoRoundtrip   	 2998263	       423.0 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/barmco/fastcall/test	9.755s

설탕, 양념 그리고 흑마법 같은 것들을 섞어 만든 CGo 를 위한 도핑제의 효과 테스트냥.

그리고 효과는 좋았다냥.
벤치마크 참고냥.

이건 어셈블리 레벨에서 구현된 트램폴린 함수를 타고서 CGo 의 빡빡하고 느려터진 실행 과정을 전부 무시한 채로 노출된 C 함수를 호출하는 흑마법이다냥.
경고냥: 속도만 보고 혹해서 이걸 이용하다가는 불의의 사태가 일어날 수 있으니깐 조심해서 사용하는게 권장된다냥.


아래 레퍼런스들에 기재된 어셈블리 트램폴린 테크닉과 실제 구현들을 참고했다냥.
다만, 이 리포지토리의 구현은 C 함수에서 다시 Go 함수를 호출 (콜백) 할 때, 정상적인 스택 포인터로 회귀하지 못했던 문제점을 올바르게 고쳤다냥.

C 함수쪽에서 올바르게 Go 함수를 호출하기 위해 필요한 fast_crosscall2 함수는 정적 라이브러리로 빌드된 공유 변수에 런타임에 대입된다냥.

cgo 패키지를 통해서 fastcall.Crosscall2 를 내보내면 cgo 가 래핑해버리기 때문에 어쩔 수 없이 해키한 방법을 사용했다냥. 이건 go1.10 이상부터 go:cgo_export_static 디렉티브를 cgo 나 runtime 패키지에서만 사용가능하도록 Golang 팀이 제약을 걸어놓은 것에 대한 영향이다냥.

정적 라이브러리 빌드를 위해서는 CMake 가 필요하다냥.
Vscode 편집기에서 빠른 컴파일을 위해서 도움이 되는 스크립트도 내장했다냥.
스크립트는 전부 bin 폴더에 있으니깐 관심 있으면 열어서 보면 된다냥.

본론으로 돌아와서 빌드를 하려면 터미널을 키고 아래의 명령을 프로젝트 폴더 안에 있는 상태에서 입력하면 된다냥.

> ./bin/build.sh

fastcall 을 어떻게 사용할 수 있는지 궁금하다면 test 폴더를 참고하면 된다냥.
기본적인 활용 예제는 만들어뒀다냥.

'^'b

주의: fast_crosscall2 함수를 호출할 때 주의할 점

fastcall_amd64.s 파일을 보면 알겠지만냥,
CallXX 로 시작하는 모든 어셈블리 트램폴린은 기본적으로 스택 사이즈가 2056 이다냥. 여기서 8바이트는 이전 BP 를 저장하기 위해서 사용하기 때문에, 실제로 할당되고 사용 가능한 공간은 2048 이다냥.

여기서 주의할 점은 fast_crosscall2 을 경유해서 다시 Go 함수를 실행시켰을 때 그때까지 늘어난 스택 메모리가 2048 바이트를 절대 넘어서서는 안된다라는거다냥.
모든 Go 함수에는 특별한 지시어로 처리가 되어있지 않는 이상 함수의 시작 부분(프롤로그) 와 끝 부분(에필로그) 부분에 스택이 부족할 때를 대비해서 시작 부분에서 실행하려는 함수의 필요 스택 바이트 수와 지금까지 늘어났던 스택의 차를 계산해서 더 큰 스택 메모리로 지금까지의 모든 함수 프레임을 이전 시켜야하는지 판단한다냥.

Go 의 스택이 어떻게 생겼는지 아마 아는 사람도 있을거다냥. Go 의 스택은 필요에 따라서 런타임에 늘어나는 구조인데냥, 이 매커니즘이 리포지토리에서 구현한 빠른 C 함수 호출에 대해서 조건적으로 복병이 되는 경우가 있다냥.
통상의 Go 함수라면 에필로그쪽으로 가서 runtime.morestack 이라는 어셈블리 함수를 호출하고 용량이 더 늘어난 스택 메모리로 데이터를 이전하고 스택 포인터의 위치를 새로운 스택 메모리의 주소로 재정렬 한다냥.

문제가 되는 부분은 이 재정렬의 부분인데냥, 스택 프레임 사이사이에 Go 함수 프레임하고 C 함수 프레임이 섞여있으면 재정렬하는 도중에 오류가 발생한다라는거다냥.
runtime.morestack 은 오로지 Go 함수나 Cgo 로 알려진 정석적인 C 호출 릴레이 패키지를 통해서만 스택이 관리될 것을 가정하고 있다냥.

아래는 이것이 문제될 수 있는 Use case 다냥. 참고해라냥.

  1. Go 함수에서 C 함수 호출 (fastcall.CallN 을 통해서 호출)
  2. 1의 과정으로 2048 바이트 만큼의 사용 가능한 스택 메모리가 있다는 것이 Go 의 런타임에 알려짐
  3. 1에서 Callee 였던 C 함수가 다시 fast_crosscall2 함수를 이용해서 Go 함수를 호출
  4. 2의 과정으로 알려진 남은 공간과 지금 실행하려는 Go 함수의 필요 스택 공간을 계산
    1. 만약 필요 스택 공간이 2의 잉여 공간을 넘어서지 않는 경우에는, 그대로 함수 호출 속행 (정상적)
    2. 만약 필요 스택 공간이 2의 잉여 공간을 넘어선다면 runtime.morestack 을 호출하게 되고
      runtime.gentrackback 에서 스택 포인터의 위치를 새로운 스택 메모리 공간으로 재정렬 하던 도중 끼어있는 C 함수 프레임의 정보를 찾지 못해 fatal 상태로 들어감.

결론부터만 말하자면 fast_crosscall2 를 통한 Go 함수의 재호출 (어떤 함수던 간에) 을 하기 위해서는 직접 그 함수를 기점으로 (이후 연쇄적인 모든 Go 함수 호출) 할당될 것으로 여겨지는 스택 메모리 공간의 총합을 계산한 다음에 그 총합이 2048 바이트를 넘어서지 않을거라는 확인 이후에 사용하는 것이 안전하다냥.
C 함수로부터의 Round-trip 을 하고 싶다면 꼭 명심해야할 부분이다냥.
참고냥.

Reference

[1]: https://blog.filippo.io/rustgo
[2]: https://github.com/petermattis/fastcgo
[3]: https://github.com/choleraehyq/fastercgo

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssignFunc

func AssignFunc(ptr interface{}, fn interface{})

func Call0

func Call0(fn unsafe.Pointer)

func Call0R

func Call0R(fn unsafe.Pointer) uintptr

func Call1

func Call1(fn unsafe.Pointer, arg0 uintptr)

func Call10

func Call10(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 uintptr)

func Call10R

func Call10R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 uintptr) uintptr

func Call1R

func Call1R(fn unsafe.Pointer, arg0 uintptr) uintptr

func Call2

func Call2(fn unsafe.Pointer, arg0, arg1 uintptr)

func Call2R

func Call2R(fn unsafe.Pointer, arg0, arg1 uintptr) uintptr

func Call3

func Call3(fn unsafe.Pointer, arg0, arg1, arg2 uintptr)

func Call3R

func Call3R(fn unsafe.Pointer, arg0, arg1, arg2 uintptr) uintptr

func Call4

func Call4(fn unsafe.Pointer, arg0, arg1, arg2, arg3 uintptr)

func Call4R

func Call4R(fn unsafe.Pointer, arg0, arg1, arg2, arg3 uintptr) uintptr

func Call5

func Call5(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4 uintptr)

not prepared yet

func Call5R

func Call5R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4 uintptr) uintptr

func Call6

func Call6(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5 uintptr)

func Call6R

func Call6R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5 uintptr) uintptr

func Call7

func Call7(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6 uintptr)

func Call7R

func Call7R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) uintptr

func Call8

func Call8(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 uintptr)

func Call8R

func Call8R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 uintptr) uintptr

func Call9

func Call9(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 uintptr)

func Call9R

func Call9R(fn unsafe.Pointer, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 uintptr) uintptr

func Crosscall2

func Crosscall2()

func FuncAddr

func FuncAddr(fn interface{}) uintptr

Types

This section is empty.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL