├── LICENSE.txt ├── README.md ├── bench ├── bench_test.go ├── binding │ ├── binding.go │ ├── directcgo_amd64.s │ └── directcgo_arm64.s ├── code.c ├── code.go ├── code.h └── pureasm │ ├── add.go │ ├── add_amd64.s │ └── add_arm64.s ├── call_amd64.s ├── call_arm64.s ├── cmd └── directcgo │ └── main.go ├── codegen ├── amd64.go ├── arm64.go ├── builder.go ├── codegen.go ├── codegen_test.go ├── header.go └── ir.go ├── directcgo.go ├── directcgo.h ├── example ├── code.c ├── code.h └── main.go ├── go.mod ├── go.sum └── testsuite ├── binding ├── binding.go ├── directcgo_amd64.s └── directcgo_arm64.s ├── output.c ├── output.go ├── overflow ├── overflow.go └── overflow_test.go ├── primitive.c ├── primitive.go ├── primitive_test.go ├── struct.c ├── struct.go ├── struct_test.go └── testsuite.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright notice: 2 | 3 | (C) 2024 Max Poletaev 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # directcgo - Minimal Cost C Interop 2 | 3 | tl;dr: This is an experimental way of executing C code from Go, which targets one specific case: calling fast short-lived C functions in a loop (e.g., immediate mode graphic apps, game engines, SIMD code) with minimal possible overhead. Basically, what the original cgo design is infamous for. The suggested solution does not provide the same safety guarantees as cgo, and is not suitable for long-running or blocking C functions. 4 | 5 | There are two main reasons why cgo is considered slow in certain scenarios (of course, slow here [is not slow per se][4], we're talking about dozens of nanoseconds of overhead per call, it's just that this overhead gets accumulated quickly when a function is called hundreds of times per frame, and it is slower than most [FFI implementations][5] out there). The technical aspects have probably been described and discussed in great detail across many posts and articles on the internet, but in a nutshell: 6 | 7 | 1. Goroutines use dynamic stacks managed by the runtime. The stacks may grow, shrink and moved around at any time, which makes them not very suitable for C calls, nor is it possible to control the stack size from the C code being called. Due to this limitation, every cgo call is implemented by jumping to the system stack (the stack where the runtime is running), executing the code there, and jumping back. 8 | 9 | 2. Go wants C calls to natively fit into Go's concurrency model, making an assumption that any cgo call may block indefinitely. Therefore, any C call requires additional cooperation with the scheduler, such as removing threads occupied with C code from the thread pool and spawning new threads to compensate for the loss. This does have certain benefits if the program mainly uses C functions to offload heavy work or I/O, but greatly reduces throughput for small and quick C functions which need to be called at higher frequency. 10 | 11 | ## Stage 1: Bypassing Runtime 12 | 13 | The idea initially came from [fastcgo][1] (which, in turn, was inspired by [rustgo][2]) that avoids interacting with the scheduler and jumps to the system stack directly. A [thread][3] on Google Groups made me think if we could also get away with running C code right on the goroutine stack to avoid rapid context switches. And it turns out, we could! 14 | 15 | What the code in this repo tries to do is basically run C code on the same goroutine stack, ensuring that there is a reasonable stack space available to safely run this code. This brings it very close to the efficiency of native function calls (because it is essentially calling native functions, using the standard ABI calling convention for the target architecture). 16 | 17 | Performance-wise, this has only 2.5-3x overhead (introduced by an extra layer of indirection through the assembly code) compared to native Go calls, while traditional Cgo slaps you with a 30-40x penalty (go 1.23): 18 | 19 | ``` 20 | goos: darwin 21 | goarch: arm64 22 | pkg: github.com/maxpoletaev/directcgo/bench 23 | cpu: Apple M3 Pro 24 | BenchmarkAddTwoNumbers/cgo-4 35699083 33.57 ns/op 25 | BenchmarkAddTwoNumbers/directcgo-4 404929509 2.960 ns/op 26 | BenchmarkAddTwoNumbers/pureasm-4 411780055 2.913 ns/op 27 | BenchmarkAddTwoNumbers/native-4 1000000000 1.070 ns/op 28 | BenchmarkAddTwoNumbersLoop100/cgo-4 349206 3367 ns/op 29 | BenchmarkAddTwoNumbersLoop100/directcgo-4 3934260 304.3 ns/op 30 | BenchmarkAddTwoNumbersLoop100/pureasm-4 4177323 286.9 ns/op 31 | BenchmarkAddTwoNumbersLoop100/native-4 10627827 113.0 ns/op 32 | ``` 33 | 34 | From the table above we can see that calling C through directcgo is as fast as calling assembly, maybe a tiny bit slower. But note an interesting observation: assembly itself is slower to call than **non-inlined** Go functions. This is because the calling convention between Go and assembly relies exclusively on passing arguments through the stack, while Go-to-Go calls can go (pun intended) through registers. So 2.5ns is our theoretical minimum, and we can't go any lower than that without an [ABI update][9] (may happen) and/or proper [assembly inlining][8] (not going to happen). 35 | 36 | The implementation itself is so simple it's almost embarrassing, and pretty future-proof, as it does not rely that much on runtime internals. All we need is a so-called assembly trampoline that will set up a stack frame large enough to fit the entire C stack and arrange the arguments according to the ABI standard on the target platform: 37 | 38 | ``` 39 | //go:build arm64 40 | 41 | // func Call(fn unsafe.Pointer, arg unsafe.Pointer, ret unsafe.Pointer) 42 | TEXT ·Call(SB), $1048576-24 // 1MB stack frame, 24 bytes for parameters 43 | MOVD fn+0(FP), R2 44 | MOVD arg+8(FP), R0 45 | MOVD ret+16(FP), R1 46 | MOVD RSP, R20 47 | MOVD $1048576, R10 48 | ADD R10, RSP 49 | BL (R2) 50 | MOVD R20, RSP 51 | RET 52 | ``` 53 | 54 | *(the real code is a little longer as it includes checks for the stack overflow and does proper stack alignment, but you get the idea)* 55 | 56 | And some wrapping code for passing function arguments and return values: 57 | 58 | ```go 59 | /* 60 | #include "code.h" 61 | #include 62 | 63 | void ffi_addTwoNumbers(void *arg, void *ret) { 64 | struct { 65 | uint32_t a; 66 | uint32_t b; 67 | } *a = arg; 68 | 69 | uint32_t sum = addTwoNumbers(a->a, a->b); 70 | *(uint32_t *)ret = sum; 71 | } 72 | */ 73 | import "C" 74 | import "unsafe" 75 | 76 | func AddTwoNumbers(a, b uint32) (ret uint32) { 77 | type args struct { 78 | a uint32 79 | b uint32 80 | } 81 | directcgo.Call( 82 | C.ffi_addTwoNumbers, 83 | unsafe.Pointer(&args{a, b}), 84 | unsafe.Pointer(&ret), 85 | ) 86 | return ret 87 | } 88 | ``` 89 | 90 | ## Stage 2: Assembly Codegen 91 | 92 | One way of making this a little bit more interesting is getting rid of C wrappers and moving all the wrapping into assembly. That would mean we need to make an assembly code generator, capable of reading C function declarations and producing code that maps arguments and return values to the C function ABI. 93 | ``` 94 | $ directcgo -arch=amd64,arm64 ./testsuite/binding 95 | ``` 96 | 97 | What it does is reads Go function declarations from the provided package: 98 | 99 | ```go 100 | package binding 101 | 102 | //go:noescape 103 | func AddTwoNumbers(cfun unsafe.Pointer, a, b uint32) uint32 104 | 105 | //go:noescape 106 | func AddTwoFloats(cfun unsafe.Pointer, a, b float32) float32 107 | ``` 108 | 109 | and generates a bunch of `*.s` files containing assembly implementations of those declarations that do all the magic of calling and forwarding arguments to the C function. The first argument is a pointer to the function we want to call, and the rest are the arguments we want to pass to it. 110 | 111 | For now, only primitive types are reliably supported (ints, floats, pointers). Passing structs is work-in-progress. Small structs (under 16 bytes) kind of work already, but might be buggy (the ABI for passing structs is pure hell). Passing arguments through stack is not supported too, so you are limited to something like 6-8 integer and floating point arguments. Both AMD64 and ARM64 are supported, but only on Linux and macOS, since Windows ABI is different enough to require a separate implementation. 112 | 113 | ## Caveats 114 | 115 | Trying to bypass cgo in order to squeeze out every last bit of performance needs a good justification, as it comes with a lot of potential caveats: 116 | 117 | 1. Unusual stack sizes. This method will create goroutines with stacks that are probably too large for spawning thousands of them, but not large enough to match the size of the system stack. This repo experiments with ~~1MB~~ 64KB stack size, so prior to the C call, Go will allocate a 64KB stack frame for each goroutine calling to C code. That will likely lead to OOM when spawned uncontrollably, so you may want to create a dedicated pool of "fat" goroutines with bigger stacks for calling C code this way. 118 | 119 | 2. C calls do not inform the scheduler about their presence. The thread calling to C code will block until the C function returns, effectively blocking other goroutines waiting in the run queue on that thread. There is no way for the scheduler to preempt the running C function, which, in turn, may lead to other negative side effects like delaying garbage collector phases due to the goroutine not being able to reach a safepoint. That's why I said the function should be short-lived. 120 | 121 | 3. There aren't any security measures in place. Like, no cgocheck or anything like that for checking that you are passing pointers to pinned Go memory. Also, stack overflow of a goroutine stack is not guaranteed to cause the program crash, as it may overflow into valid memory reserved by the go allocator. So, no safety nets, no helmet, just good old undefined behavior. 122 | 123 | ## If you want to use it 124 | 125 | Mind that this is super experimental and by no means stable. The code is not well-tested, and there are probably a lot of bugs and edge cases that I haven't thought of, especially in the code produced by the codegen, as things like struct alignment are pretty tricky to get right. 126 | 127 | But if you want to give it a try, do extensive stress testing under various conditions and input sizes before seriously considering using directcgo instead of cgo. Ideally, you should know what the code is doing, how much stack space it actually needs, and if there are any VLAs, alloca(), or recursion involved that may blow up the stack. 128 | 129 | Again, all of this only make sense if the bottleneck is the internal mechanics of cgo itself, and not the actual C code being called. It is pretty easy to draw a [wrong conclusion][7] here, as the profiler only shows that `runtime.cgocall()` takes a lot of time, but it does not show what the C code is doing inside. 130 | 131 | [1]: https://github.com/petermattis/fastcgo 132 | [2]: https://words.filippo.io/rustgo/ 133 | [3]: https://groups.google.com/g/golang-nuts/c/_YrvM8OO6QY 134 | [4]: https://shane.ai/posts/cgo-performance-in-go1.21/ 135 | [5]: https://github.com/dyu/ffi-overhead 136 | [7]: https://github.com/golang/go/issues/19574#issuecomment-560060546 137 | [8]: https://github.com/golang/go/issues/26891 138 | [9]: https://groups.google.com/g/golang-nuts/c/08QdCx7UKrc -------------------------------------------------------------------------------- /bench/bench_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "math/rand/v2" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/maxpoletaev/directcgo/bench/pureasm" 9 | ) 10 | 11 | func TestAddTwoNumbers(t *testing.T) { 12 | for i := 0; i < 1000; i++ { 13 | var ( 14 | a = rand.Uint32() 15 | b = rand.Uint32() 16 | ) 17 | 18 | directCallResult := AddTwoNumbersDirectCall(a, b) 19 | pureasmResult := pureasm.AddTwoNumbers(a, b) 20 | want := AddTwoNumbersNative(a, b) 21 | 22 | if directCallResult != want || pureasmResult != want { 23 | t.Fatalf("want %d, got [%d, %d]", want, directCallResult, pureasmResult) 24 | } 25 | } 26 | } 27 | 28 | func BenchmarkAddTwoNumbers(b *testing.B) { 29 | b.Run("cgo", func(b *testing.B) { 30 | var res uint32 31 | x := rand.Uint32() 32 | for i := 0; i < b.N; i++ { 33 | res = AddTwoNumbersCgo(res, x) 34 | } 35 | runtime.KeepAlive(res) 36 | }) 37 | 38 | b.Run("directcgo", func(b *testing.B) { 39 | var res uint32 40 | x := rand.Uint32() 41 | for i := 0; i < b.N; i++ { 42 | res = AddTwoNumbersDirectCall(res, x) 43 | } 44 | runtime.KeepAlive(res) 45 | }) 46 | 47 | b.Run("codegen", func(b *testing.B) { 48 | var res uint32 49 | x := rand.Uint32() 50 | for i := 0; i < b.N; i++ { 51 | res = AddTwoNumbersCodegen(res, x) 52 | } 53 | runtime.KeepAlive(res) 54 | }) 55 | 56 | b.Run("pureasm", func(b *testing.B) { 57 | var res uint32 58 | x := rand.Uint32() 59 | for i := 0; i < b.N; i++ { 60 | res = pureasm.AddTwoNumbers(res, x) 61 | } 62 | runtime.KeepAlive(res) 63 | }) 64 | 65 | b.Run("native", func(b *testing.B) { 66 | var res uint32 67 | x := rand.Uint32() 68 | for i := 0; i < b.N; i++ { 69 | res = AddTwoNumbersNative(res, x) 70 | } 71 | runtime.KeepAlive(res) 72 | }) 73 | } 74 | 75 | func BenchmarkAddTwoNumbersLoop100(b *testing.B) { 76 | const iterations = 100 77 | 78 | b.Run("cgo", func(b *testing.B) { 79 | var res uint32 80 | x := rand.Uint32() 81 | for i := 0; i < b.N; i++ { 82 | for j := 0; j < iterations; j++ { 83 | res = AddTwoNumbersCgo(res, x) 84 | } 85 | } 86 | runtime.KeepAlive(res) 87 | }) 88 | 89 | b.Run("directcgo", func(b *testing.B) { 90 | var res uint32 91 | x := rand.Uint32() 92 | for i := 0; i < b.N; i++ { 93 | for j := 0; j < iterations; j++ { 94 | res = AddTwoNumbersDirectCall(res, x) 95 | } 96 | } 97 | runtime.KeepAlive(res) 98 | }) 99 | 100 | b.Run("codegen", func(b *testing.B) { 101 | var res uint32 102 | x := rand.Uint32() 103 | for i := 0; i < b.N; i++ { 104 | for j := 0; j < iterations; j++ { 105 | res = AddTwoNumbersCodegen(res, x) 106 | } 107 | } 108 | runtime.KeepAlive(res) 109 | }) 110 | 111 | b.Run("pureasm", func(b *testing.B) { 112 | var res uint32 113 | x := rand.Uint32() 114 | for i := 0; i < b.N; i++ { 115 | for j := 0; j < iterations; j++ { 116 | res = pureasm.AddTwoNumbers(res, x) 117 | } 118 | } 119 | runtime.KeepAlive(res) 120 | }) 121 | 122 | b.Run("native", func(b *testing.B) { 123 | var res uint32 124 | x := rand.Uint32() 125 | for i := 0; i < b.N; i++ { 126 | for j := 0; j < iterations; j++ { 127 | res = AddTwoNumbersNative(res, x) 128 | } 129 | } 130 | runtime.KeepAlive(res) 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /bench/binding/binding.go: -------------------------------------------------------------------------------- 1 | package binding 2 | 3 | import "unsafe" 4 | 5 | //go:noescape 6 | func AddTwoNumbers(fn unsafe.Pointer, a, b uint32) uint32 7 | -------------------------------------------------------------------------------- /bench/binding/directcgo_amd64.s: -------------------------------------------------------------------------------- 1 | //go:build amd64 && !windows 2 | 3 | // Code generated by directcgo. DO NOT EDIT. 4 | // directcgo -arch=amd64,arm64 ./bench/asm 5 | 6 | #include "go_asm.h" 7 | #include "textflag.h" 8 | #include "funcdata.h" 9 | 10 | // AddTwoNumbers func(fn unsafe.Pointer, a, b uint32) uint32 11 | TEXT ·AddTwoNumbers(SB), $65536-20 12 | MOVQ fn+0(FP), AX 13 | MOVLQZX a+8(FP), DI 14 | MOVLQZX b+12(FP), SI 15 | MOVL $0x841709CB, R10 16 | MOVL R10, 8(SP) 17 | MOVQ SP, R12 18 | LEAQ 65536(SP), SP 19 | ANDQ $~15, SP 20 | CALL AX 21 | MOVQ R12, SP 22 | MOVL 8(SP), R10 23 | CMPL R10, $0x841709CB 24 | JNE overflow 25 | MOVL AX, ret+16(FP) 26 | RET 27 | overflow: 28 | CALL runtime·abort(SB) 29 | RET 30 | -------------------------------------------------------------------------------- /bench/binding/directcgo_arm64.s: -------------------------------------------------------------------------------- 1 | //go:build arm64 && !windows 2 | 3 | // Code generated by directcgo. DO NOT EDIT. 4 | // directcgo -arch=amd64,arm64 ./bench/asm 5 | 6 | #include "go_asm.h" 7 | #include "textflag.h" 8 | #include "funcdata.h" 9 | 10 | // AddTwoNumbers func(fn unsafe.Pointer, a, b uint32) uint32 11 | TEXT ·AddTwoNumbers(SB), $65536-20 12 | MOVD fn+0(FP), R9 13 | MOVWU a+8(FP), R0 14 | MOVWU b+12(FP), R1 15 | MOVD $0x5AEBCA37, R10 16 | MOVD R10, 8(RSP) 17 | MOVD RSP, R20 18 | MOVD $65536, R10 19 | ADD R10, RSP 20 | MOVD RSP, R10 21 | AND $~15, R10, RSP 22 | BL (R9) 23 | MOVD R20, RSP 24 | MOVD 8(RSP), R10 25 | MOVD $0x5AEBCA37, R11 26 | CMP R10, R11 27 | BNE overflow 28 | MOVW R0, ret+16(FP) 29 | RET 30 | overflow: 31 | CALL runtime·abort(SB) 32 | RET 33 | -------------------------------------------------------------------------------- /bench/code.c: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | #include 3 | #include 4 | 5 | uint32_t AddTwoNumbers(uint32_t a, uint32_t b) 6 | { 7 | return a + b; 8 | } 9 | -------------------------------------------------------------------------------- /bench/code.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | /* 4 | #include "code.h" 5 | #include 6 | 7 | void ffi_AddTwoNumbers(void *arg, void *ret) { 8 | struct { uint32_t a; uint32_t b; } *a = arg; 9 | uint32_t sum = AddTwoNumbers(a->a, a->b); 10 | *(uint32_t *)ret = sum; 11 | } 12 | */ 13 | import "C" 14 | import ( 15 | "unsafe" 16 | 17 | "github.com/maxpoletaev/directcgo" 18 | "github.com/maxpoletaev/directcgo/bench/binding" 19 | ) 20 | 21 | func AddTwoNumbersDirectCall(a, b uint32) (ret uint32) { 22 | type args struct { 23 | a uint32 24 | b uint32 25 | } 26 | directcgo.Call( 27 | C.ffi_AddTwoNumbers, 28 | unsafe.Pointer(&args{a, b}), 29 | unsafe.Pointer(&ret), 30 | ) 31 | return ret 32 | } 33 | 34 | func AddTwoNumbersCodegen(a, b uint32) uint32 { 35 | return binding.AddTwoNumbers(C.AddTwoNumbers, a, b) 36 | } 37 | 38 | func AddTwoNumbersCgo(a, b uint32) uint32 { 39 | return uint32(C.AddTwoNumbers(C.uint32_t(a), C.uint32_t(b))) 40 | } 41 | 42 | //go:noinline // we're measuring function call, not the addition instruction 43 | func AddTwoNumbersNative(a, b uint32) uint32 { 44 | return a + b 45 | } 46 | -------------------------------------------------------------------------------- /bench/code.h: -------------------------------------------------------------------------------- 1 | #ifndef CODE_H 2 | #define CODE_H 3 | 4 | #include 5 | #include 6 | 7 | uint32_t AddTwoNumbers(uint32_t a, uint32_t b); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /bench/pureasm/add.go: -------------------------------------------------------------------------------- 1 | package pureasm 2 | 3 | //go:noescape 4 | func AddTwoNumbers(a, b uint32) uint32 5 | -------------------------------------------------------------------------------- /bench/pureasm/add_amd64.s: -------------------------------------------------------------------------------- 1 | //go:build amd64 2 | 3 | #include "textflag.h" 4 | 5 | //func AddTwoNumbers(a, b uint32) uint32 6 | TEXT ·AddTwoNumbers(SB), NOSPLIT, $0-12 7 | MOVL a+0(FP), AX 8 | MOVL b+4(FP), CX 9 | ADDL CX, AX 10 | MOVL AX, ret+8(FP) 11 | RET 12 | -------------------------------------------------------------------------------- /bench/pureasm/add_arm64.s: -------------------------------------------------------------------------------- 1 | //go:build arm64 2 | 3 | #include "textflag.h" 4 | 5 | //func AddTwoNumbers(a, b uint32) uint32 6 | TEXT ·AddTwoNumbers(SB), NOSPLIT, $0-12 7 | MOVWU a+0(FP), R0 8 | MOVWU b+4(FP), R1 9 | ADDW R0, R1 10 | MOVWU R1, ret+8(FP) 11 | RET 12 | -------------------------------------------------------------------------------- /call_amd64.s: -------------------------------------------------------------------------------- 1 | //go:build amd64 2 | 3 | #include "go_asm.h" 4 | #include "textflag.h" 5 | #include "funcdata.h" 6 | #include "directcgo.h" 7 | 8 | #ifdef GOOS_windows 9 | #define ARG0 CX 10 | #define ARG1 DX 11 | #else 12 | #define ARG0 DI 13 | #define ARG1 SI 14 | #endif 15 | 16 | // func call(fn unsafe.Pointer, arg unsafe.Pointer, ret unsafe.Pointer) 17 | TEXT ·Call(SB), $FRAME_SIZE-24 // 1MB stack frame, 24 bytes for parameters 18 | NO_LOCAL_POINTERS 19 | MOVQ fn+0(FP), AX 20 | MOVQ arg+8(FP), ARG0 21 | MOVQ ret+16(FP), ARG1 22 | #ifdef FRAME_GUARD 23 | MOVL $0xDEADBEEF, 8(SP) 24 | #endif 25 | MOVQ SP, R12 // preserve original SP (callee-saved) 26 | LEAQ FRAME_SIZE(SP), SP 27 | ANDQ $~15, SP // align to 16 bytes (ABI requirement) 28 | CALL AX // call C function 29 | MOVQ R12, SP // restore original SP 30 | #ifdef FRAME_GUARD 31 | MOVL $0xDEADBEEF, AX 32 | CMPL AX, 8(SP) 33 | JNE overflow 34 | #endif 35 | RET 36 | 37 | overflow: 38 | CALL runtime·abort(SB) 39 | RET 40 | -------------------------------------------------------------------------------- /call_arm64.s: -------------------------------------------------------------------------------- 1 | //go:build arm64 2 | 3 | #include "go_asm.h" 4 | #include "textflag.h" 5 | #include "funcdata.h" 6 | #include "directcgo.h" 7 | 8 | // func call(fn unsafe.Pointer, arg unsafe.Pointer, ret unsafe.Pointer) 9 | TEXT ·Call(SB), $FRAME_SIZE-24 // reserve 1MB stack frame, 24 bytes for parameters 10 | NO_LOCAL_POINTERS 11 | MOVD fn+0(FP), R9 12 | MOVD arg+8(FP), R0 13 | MOVD ret+16(FP), R1 14 | #ifdef FRAME_GUARD 15 | MOVD $0xDEADBEEF, R10 16 | MOVD R10, 8(RSP) 17 | #endif 18 | MOVD RSP, R20 // preserve original SP (callee-saved) 19 | MOVD $FRAME_SIZE, R10 20 | ADD R10, RSP // move SP to the end of the frame 21 | MOVD RSP, R10 22 | AND $~15, R10, RSP // align to 16 bytes (ABI requirement) 23 | BL (R9) // call function 24 | MOVD R20, RSP // restore original SP 25 | #ifdef FRAME_GUARD 26 | MOVD 8(RSP), R10 27 | MOVD $0xDEADBEEF, R11 28 | CMP R10, R11 29 | BNE overflow 30 | #endif 31 | RET 32 | 33 | overflow: 34 | CALL runtime·abort(SB) 35 | RET 36 | -------------------------------------------------------------------------------- /cmd/directcgo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/maxpoletaev/directcgo/codegen" 10 | ) 11 | 12 | type Options struct { 13 | pkg string 14 | arch string 15 | } 16 | 17 | func parseOptions() Options { 18 | var opts Options 19 | 20 | flag.StringVar(&opts.arch, "arch", "", "architecture") 21 | flag.Parse() 22 | 23 | if flag.NArg() < 1 { 24 | log.Fatalf("usage: %s -arch= ", os.Args[0]) 25 | } 26 | 27 | opts.pkg = flag.Arg(0) 28 | 29 | return opts 30 | } 31 | 32 | func parseList(s string) []string { 33 | j := 0 34 | parts := strings.Split(s, ",") 35 | for i := 0; i < len(parts); i++ { 36 | part := strings.TrimSpace(parts[i]) 37 | if part != "" { 38 | parts[j] = part 39 | j++ 40 | } 41 | } 42 | return parts[:j] 43 | } 44 | 45 | func main() { 46 | log.Default().SetFlags(0) 47 | opts := parseOptions() 48 | 49 | var archList []string 50 | if opts.arch == "" { 51 | archList = []string{ 52 | codegen.ArchARM64, 53 | codegen.ArchAMD64, 54 | } 55 | } else { 56 | archList = parseList(opts.arch) 57 | for _, arch := range archList { 58 | if _, ok := codegen.ValidArchitectures[arch]; !ok { 59 | log.Fatalf("unsupported architecture: %s", arch) 60 | } 61 | } 62 | } 63 | 64 | if err := codegen.Run(opts.pkg, archList); err != nil { 65 | log.Fatalf("error: %v", err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /codegen/amd64.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | "math/rand/v2" 7 | ) 8 | 9 | var ( 10 | amd64IntegerRegs = []string{"DI", "SI", "DX", "CX", "R8", "R9"} 11 | amd64FloatRegs = []string{"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7"} 12 | ) 13 | 14 | type amd64ArgClass uint8 15 | 16 | const ( 17 | amd64ArgClassNone amd64ArgClass = iota 18 | amd64ArgClassInteger 19 | amd64ArgClassSSE 20 | amd64ArgClassMemory 21 | ) 22 | 23 | // AMD64 is an AMD64 code generator. 24 | // Go assembly uses non-standard names for instructions and registers compared to Intel/AT&T syntax. 25 | // See: https://golang.org/doc/asm and https://www.quasilyte.dev/blog/post/go-asm-complementary-reference/ 26 | type amd64 struct { 27 | ngpr int // next general purpose register number 28 | nsrn int // next simd and fp register number 29 | nsaa int // next stack argument address 30 | } 31 | 32 | func newAMD64() *amd64 { 33 | return &amd64{ 34 | nsaa: 8, 35 | } 36 | } 37 | 38 | func (arch *amd64) resetState() { 39 | arch.ngpr = 0 40 | arch.nsrn = 0 41 | arch.nsaa = 8 42 | } 43 | 44 | func (arch *amd64) Name() string { 45 | return ArchAMD64 46 | } 47 | 48 | func (arch *amd64) totalArgsSize(fn *Function) (total int) { 49 | for _, arg := range fn.Args { 50 | size := typeSize(arg.Type) 51 | total = align(total, size) 52 | total += size 53 | } 54 | 55 | if fn.ReturnType != nil { 56 | size := typeSize(fn.ReturnType) 57 | total = align(total, size) 58 | total += size 59 | } 60 | 61 | return total 62 | } 63 | 64 | func (arch *amd64) loadInteger(buf *builder, arg *Argument, offset int, reg string) { 65 | size := typeSize(arg.Type) 66 | if isUnsigned(arg.Type) { 67 | switch size { 68 | case 8: 69 | buf.I("MOVQ", "%s+%d(FP), %s", arg.Name, offset, reg) 70 | case 4: 71 | buf.I("MOVLQZX", "%s+%d(FP), %s", arg.Name, offset, reg) 72 | case 2: 73 | buf.I("MOVWQZX", "%s+%d(FP), %s", arg.Name, offset, reg) 74 | case 1: 75 | buf.I("MOVBQZX", "%s+%d(FP), %s", arg.Name, offset, reg) 76 | default: 77 | panic(fmt.Sprintf("unknown int size: %d", size)) 78 | } 79 | } else { 80 | switch size { 81 | case 8: 82 | buf.I("MOVQ", "%s+%d(FP), %s", arg.Name, offset, reg) 83 | case 4: 84 | buf.I("MOVLQSX", "%s+%d(FP), %s", arg.Name, offset, reg) 85 | case 2: 86 | buf.I("MOVWQSX", "%s+%d(FP), %s", arg.Name, offset, reg) 87 | case 1: 88 | buf.I("MOVBQSX", "%s+%d(FP), %s", arg.Name, offset, reg) 89 | default: 90 | panic(fmt.Sprintf("unknown int size: %d", size)) 91 | } 92 | } 93 | } 94 | 95 | func (arch *amd64) loadFloat(buf *builder, arg *Argument, offset int, reg string) { 96 | size := typeSize(arg.Type) 97 | 98 | switch size { 99 | case 4: 100 | buf.I("MOVSS", "%s+%d(FP), %s", arg.Name, offset, reg) 101 | case 8: 102 | buf.I("MOVSD", "%s+%d(FP), %s", arg.Name, offset, reg) 103 | default: 104 | panic(fmt.Sprintf("unknown float size: %d", size)) 105 | } 106 | } 107 | 108 | func (arch *amd64) mergeClasses(c1, c2 amd64ArgClass) amd64ArgClass { 109 | switch { 110 | case c1 == c2: 111 | return c1 112 | case c1 == amd64ArgClassInteger, 113 | c2 == amd64ArgClassInteger: 114 | return amd64ArgClassInteger 115 | case c1 == amd64ArgClassMemory, 116 | c2 == amd64ArgClassMemory: 117 | return amd64ArgClassMemory 118 | default: 119 | return amd64ArgClassSSE 120 | } 121 | } 122 | 123 | func (arch *amd64) classifyType(ty types.Type) amd64ArgClass { 124 | switch { 125 | case isFloatingPoint(ty): 126 | return amd64ArgClassSSE 127 | case isInteger(ty) && typeSize(ty) <= 8: 128 | return amd64ArgClassInteger 129 | case isComposite(ty): 130 | fields := getFields(ty) 131 | class := amd64ArgClassNone 132 | 133 | for _, field := range fields { 134 | fieldClass := arch.classifyType(field) 135 | class = arch.mergeClasses(class, fieldClass) 136 | } 137 | 138 | return class 139 | } 140 | 141 | panic(fmt.Sprintf("unhandled type: %s", ty)) 142 | } 143 | 144 | func (arch *amd64) loadSmallStruct(buf *builder, arg *Argument, offset int) int { 145 | size := typeSize(arg.Type) 146 | fields := getFields(arg.Type) 147 | nEightbytes := (size + 7) / 8 148 | 149 | classes := make([]amd64ArgClass, nEightbytes) 150 | fieldOffset := 0 151 | 152 | for _, field := range fields { 153 | fieldSize := typeSize(field) 154 | fieldOffset = align(fieldOffset, fieldSize) 155 | eightbyte := fieldOffset / 8 156 | 157 | class := arch.classifyType(field) 158 | classes[eightbyte] = arch.mergeClasses(classes[eightbyte], class) 159 | 160 | fieldOffset += fieldSize 161 | } 162 | 163 | for i := 0; i < nEightbytes; i++ { 164 | switch classes[i] { 165 | case amd64ArgClassSSE: 166 | reg := amd64FloatRegs[arch.nsrn] 167 | buf.I("MOVQ", "%s+%d(FP), %s", arg.Name, offset, reg) 168 | arch.nsrn++ 169 | offset += 8 170 | case amd64ArgClassInteger: 171 | reg := amd64IntegerRegs[arch.ngpr] 172 | buf.I("MOVQ", "%s+%d(FP), %s", arg.Name, offset, reg) 173 | arch.ngpr++ 174 | offset += 8 175 | default: 176 | panic(fmt.Sprintf("unhandled eightbyte class: %d", classes[i])) 177 | } 178 | } 179 | 180 | return offset 181 | } 182 | 183 | func (arch *amd64) loadArg(buf *builder, arg *Argument, offset int) int { 184 | size := typeSize(arg.Type) 185 | 186 | if isInteger(arg.Type) && arch.ngpr < len(amd64IntegerRegs) { 187 | reg := amd64IntegerRegs[arch.ngpr] 188 | offset = align(offset, size) 189 | arch.loadInteger(buf, arg, offset, reg) 190 | arch.ngpr++ 191 | return offset + size 192 | } 193 | 194 | if isFloatingPoint(arg.Type) && arch.nsrn < len(amd64FloatRegs) { 195 | reg := amd64FloatRegs[arch.nsrn] 196 | offset = align(offset, size) 197 | arch.loadFloat(buf, arg, offset, reg) 198 | arch.nsrn++ 199 | return offset + size 200 | } 201 | 202 | // Small composite types are divided into eightbytes (8-byte chunks). 203 | // Each eightbyte can be classified as integer, sse, or memory. 204 | if isComposite(arg.Type) && size <= 16 { 205 | return arch.loadSmallStruct(buf, arg, offset) 206 | } 207 | 208 | panic(fmt.Sprintf("unhandled argument: %s", arg.Name)) 209 | } 210 | 211 | func (arch *amd64) storeReturn(buf *builder, arg *Argument, offset int) int { 212 | size := typeSize(arg.Type) 213 | offset = align(offset, size) 214 | 215 | switch { 216 | case isInteger(arg.Type): 217 | reg := "AX" 218 | switch size { 219 | case 1: 220 | buf.I("MOVB", "%s, %s+%d(FP)", reg, arg.Name, offset) 221 | case 2: 222 | buf.I("MOVW", "%s, %s+%d(FP)", reg, arg.Name, offset) 223 | case 4: 224 | buf.I("MOVL", "%s, %s+%d(FP)", reg, arg.Name, offset) 225 | case 8: 226 | buf.I("MOVQ", "%s, %s+%d(FP)", reg, arg.Name, offset) 227 | default: 228 | panic(fmt.Sprintf("unknown int size: %d", size)) 229 | } 230 | case isFloatingPoint(arg.Type): 231 | reg := "X0" 232 | switch size { 233 | case 4: 234 | buf.I("MOVSS", "%s, %s+%d(FP)", reg, arg.Name, offset) 235 | case 8: 236 | buf.I("MOVSD", "%s, %s+%d(FP)", reg, arg.Name, offset) 237 | default: 238 | panic(fmt.Sprintf("unknown float size: %d", size)) 239 | } 240 | default: 241 | panic(fmt.Sprintf("unsupported return type: %T", arg.Type)) 242 | } 243 | 244 | offset += size 245 | return offset 246 | } 247 | 248 | func (arch *amd64) GenerateFunc(buf *builder, f *Function) { 249 | arch.resetState() 250 | 251 | buf.S("// %s", f.Signature) 252 | buf.S("TEXT ·%s(SB), $%d-%d", f.Name, defaultFrameSize, arch.totalArgsSize(f)) 253 | buf.I("MOVQ", "%s+0(FP), AX", f.Args[0].Name) 254 | 255 | // Load arguments 256 | offset := 8 257 | for i := 1; i < len(f.Args); i++ { 258 | offset = arch.loadArg(buf, &f.Args[i], offset) 259 | } 260 | 261 | seed := [32]byte{} 262 | copy(seed[:], f.Name) 263 | rnd := rand.New(rand.NewChaCha8(seed)) 264 | 265 | // Set frame guard 266 | guardValue := rnd.Uint32() 267 | buf.I("MOVL", "$0x%X, R10", guardValue) 268 | buf.I("MOVL", "R10, 8(SP)") 269 | 270 | // Stack adjustment 271 | buf.I("MOVQ", "SP, R12") 272 | buf.I("LEAQ", "%d(SP), SP", defaultFrameSize) 273 | buf.I("ANDQ", "$~15, SP") 274 | 275 | // Call function 276 | buf.I("CALL", "AX") 277 | buf.I("MOVQ", "R12, SP") 278 | 279 | // Check frame guard 280 | buf.I("MOVL", "8(SP), R10") 281 | buf.I("CMPL", "R10, $0x%X", guardValue) 282 | buf.I("JNE", "overflow") 283 | 284 | // Store return value 285 | if f.ReturnType != nil { 286 | arch.storeReturn(buf, &Argument{ 287 | Type: f.ReturnType, 288 | Name: "ret", 289 | }, offset) 290 | } 291 | 292 | buf.I("RET", "") 293 | 294 | // Overflow handler 295 | buf.S("overflow:") 296 | buf.I("CALL", "runtime·abort(SB)") 297 | buf.I("RET", "") 298 | } 299 | -------------------------------------------------------------------------------- /codegen/arm64.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | "math/rand/v2" 7 | ) 8 | 9 | var ( 10 | arm64IntRegs = []string{"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7"} 11 | arm64FloatRegs = []string{"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7"} 12 | ) 13 | 14 | // ARM64 is an ARM64 code generator. 15 | // Go assembly uses non-standard names for instructions and registers, 16 | // e.g., ldr is called MOVD, etc. See: https://pkg.go.dev/cmd/internal/obj/arm64 17 | // Procedure call: https://developer.arm.com/documentation/102374/0102/Procedure-Call-Standard 18 | type arm64 struct { 19 | ngrn int // next general purpose register number 20 | nsrn int // next simd and fp register number 21 | nsaa int // next stack argument address 22 | } 23 | 24 | func newARM64() *arm64 { 25 | return &arm64{ 26 | nsaa: 8, 27 | } 28 | } 29 | 30 | func (arch *arm64) resetState() { 31 | arch.ngrn = 0 32 | arch.nsrn = 0 33 | arch.nsaa = 8 34 | } 35 | 36 | func (arch *arm64) Name() string { 37 | return ArchARM64 38 | } 39 | 40 | func (arch *arm64) totalArgsSize(fn *Function) (total int) { 41 | for _, arg := range fn.Args { 42 | size := typeSize(arg.Type) 43 | total = align(total, size) 44 | total += size 45 | } 46 | 47 | if fn.ReturnType != nil { 48 | size := typeSize(fn.ReturnType) 49 | total = align(total, size) 50 | total += size 51 | } 52 | 53 | return total 54 | } 55 | 56 | func (arch *arm64) isHFA(t types.Type) bool { 57 | if !isComposite(t) { 58 | return false 59 | } 60 | 61 | fields := getFields(t) 62 | if len(fields) == 0 || len(fields) > 4 { 63 | return false 64 | } 65 | 66 | firstField := fields[0] 67 | if !isFloatingPoint(firstField) { 68 | return false 69 | } 70 | 71 | for i := 1; i < len(fields); i++ { 72 | if fields[i] != firstField { 73 | return false 74 | } 75 | } 76 | 77 | return true 78 | } 79 | 80 | func (arch *arm64) isHVA(t types.Type) bool { 81 | return false 82 | } 83 | 84 | func (arch *arm64) loadInteger(buf *builder, arg *Argument, offset int, reg string) { 85 | size := typeSize(arg.Type) 86 | if isUnsigned(arg.Type) { 87 | switch size { 88 | case 1: 89 | buf.I("MOVBU", "%s+%d(FP), %s", arg.Name, offset, reg) 90 | case 2: 91 | buf.I("MOVHU", "%s+%d(FP), %s", arg.Name, offset, reg) 92 | case 4: 93 | buf.I("MOVWU", "%s+%d(FP), %s", arg.Name, offset, reg) 94 | case 8: 95 | buf.I("MOVD", "%s+%d(FP), %s", arg.Name, offset, reg) 96 | default: 97 | panic(fmt.Sprintf("unknown int size: %d", size)) 98 | } 99 | } else { 100 | switch size { 101 | case 1: 102 | buf.I("MOVB", "%s+%d(FP), %s", arg.Name, offset, reg) 103 | case 2: 104 | buf.I("MOVH", "%s+%d(FP), %s", arg.Name, offset, reg) 105 | case 4: 106 | buf.I("MOVW", "%s+%d(FP), %s", arg.Name, offset, reg) 107 | case 8: 108 | buf.I("MOVD", "%s+%d(FP), %s", arg.Name, offset, reg) 109 | default: 110 | panic(fmt.Sprintf("unknown int size: %d", size)) 111 | } 112 | } 113 | } 114 | 115 | func (arch *arm64) loadFloat(buf *builder, arg *Argument, offset int, reg string) { 116 | size := typeSize(arg.Type) 117 | switch size { 118 | case 4: 119 | buf.I("FMOVS", "%s+%d(FP), %s", arg.Name, offset, reg) 120 | case 8: 121 | buf.I("FMOVD", "%s+%d(FP), %s", arg.Name, offset, reg) 122 | default: 123 | panic(fmt.Sprintf("unknown float size: %d", size)) 124 | } 125 | } 126 | 127 | func (arch *arm64) loadHFA(buf *builder, arg *Argument, offset int, regs []string) { 128 | fields := getFields(arg.Type) 129 | 130 | for i, field := range fields { 131 | fieldSize := typeSize(field) 132 | offset = align(offset, fieldSize) 133 | 134 | arch.loadFloat(buf, &Argument{ 135 | Name: fmt.Sprintf("%s_%d", arg.Name, i), 136 | Type: field, 137 | }, offset, regs[i]) 138 | 139 | offset += fieldSize 140 | } 141 | } 142 | 143 | func (arch *arm64) loadArg(buf *builder, arg *Argument, offset int) int { 144 | ty := arg.Type 145 | size := typeSize(ty) 146 | 147 | if isInteger(ty) && arch.ngrn < len(arm64IntRegs) { 148 | reg := arm64IntRegs[arch.ngrn] 149 | arch.ngrn++ 150 | 151 | offset = align(offset, size) 152 | arch.loadInteger(buf, arg, offset, reg) 153 | 154 | return offset + size 155 | } 156 | 157 | if isFloatingPoint(ty) && arch.nsrn < len(arm64FloatRegs) { 158 | reg := arm64FloatRegs[arch.nsrn] 159 | arch.nsrn++ 160 | 161 | offset = align(offset, size) 162 | arch.loadFloat(buf, arg, offset, reg) 163 | 164 | return offset + size 165 | } 166 | 167 | if arch.isHFA(ty) || arch.isHVA(ty) { 168 | numFields := getFieldCount(ty) 169 | 170 | if arch.nsrn+numFields <= len(arm64FloatRegs) { 171 | regs := make([]string, numFields) 172 | for i := 0; i < len(regs); i++ { 173 | regs[i] = arm64FloatRegs[arch.nsrn] 174 | arch.nsrn++ 175 | } 176 | 177 | arch.loadHFA(buf, arg, offset, regs) 178 | return offset + size 179 | } 180 | } 181 | 182 | if isComposite(ty) { 183 | nChunks := (size + 7) / 8 184 | if nChunks <= len(arm64IntRegs)-arch.ngrn { 185 | regs := make([]string, nChunks) 186 | for i := 0; i < len(regs); i++ { 187 | regs[i] = arm64IntRegs[arch.ngrn] 188 | arch.ngrn++ 189 | } 190 | for _, reg := range regs { 191 | buf.I("MOVD", "%s+%d(FP), %s", arg.Name, offset, reg) 192 | offset += 8 193 | } 194 | return offset 195 | } 196 | } 197 | 198 | panic(fmt.Sprintf("unhandled argument: %s", arg.Name)) 199 | } 200 | 201 | func (arch *arm64) storeReturn(buf *builder, arg *Argument, offset int) int { 202 | size := typeSize(arg.Type) 203 | offset = align(offset, size) 204 | 205 | switch { 206 | case isFloatingPoint(arg.Type): 207 | reg := "F0" 208 | switch size { 209 | case 4: 210 | buf.I("FMOVS", "%s, %s+%d(FP)", reg, arg.Name, offset) 211 | case 8: 212 | buf.I("FMOVD", "%s, %s+%d(FP)", reg, arg.Name, offset) 213 | default: 214 | panic(fmt.Sprintf("unknown float size: %d", size)) 215 | } 216 | case isInteger(arg.Type): 217 | reg := "R0" 218 | if isUnsigned(arg.Type) { 219 | switch size { 220 | case 1: 221 | buf.I("MOVBU", "%s, %s+%d(FP)", reg, arg.Name, offset) 222 | case 2: 223 | buf.I("MOVHU", "%s, %s+%d(FP)", reg, arg.Name, offset) 224 | case 4: 225 | buf.I("MOVWU", "%s, %s+%d(FP)", reg, arg.Name, offset) 226 | case 8: 227 | buf.I("MOVD", "%s, %s+%d(FP)", reg, arg.Name, offset) 228 | default: 229 | panic(fmt.Sprintf("unknown int size: %d", size)) 230 | } 231 | } else { 232 | switch size { 233 | case 1: 234 | buf.I("MOVB", "%s, %s+%d(FP)", reg, arg.Name, offset) 235 | case 2: 236 | buf.I("MOVH", "%s, %s+%d(FP)", reg, arg.Name, offset) 237 | case 4: 238 | buf.I("MOVW", "%s, %s+%d(FP)", reg, arg.Name, offset) 239 | case 8: 240 | buf.I("MOVD", "%s, %s+%d(FP)", reg, arg.Name, offset) 241 | default: 242 | panic(fmt.Sprintf("unknown int size: %d", size)) 243 | } 244 | } 245 | default: 246 | panic(fmt.Sprintf("unsupported return type: %T", arg.Type)) 247 | } 248 | 249 | offset += size 250 | return offset 251 | } 252 | 253 | func (arch *arm64) GenerateFunc(buf *builder, f *Function) { 254 | // Stage A – Initialization 255 | arch.resetState() 256 | 257 | buf.S("// %s", f.Signature) 258 | buf.S("TEXT ·%s(SB), $%d-%d", f.Name, defaultFrameSize, arch.totalArgsSize(f)) 259 | buf.I("MOVD", "%s+0(FP), R9", f.Args[0].Name) 260 | 261 | // Load arguments 262 | offset := 8 263 | for i := 1; i < len(f.Args); i++ { 264 | offset = arch.loadArg(buf, &f.Args[i], offset) 265 | } 266 | 267 | seed := [32]byte{} 268 | copy(seed[:], f.Name) 269 | rnd := rand.New(rand.NewChaCha8(seed)) 270 | 271 | // Set frame guard 272 | guardValue := rnd.Uint32() 273 | buf.I("MOVD", "$0x%X, R10", guardValue) 274 | buf.I("MOVD", "R10, 8(RSP)") 275 | 276 | // Stack adjustment 277 | buf.I("MOVD", "RSP, R20") 278 | buf.I("MOVD", "$%d, R10", defaultFrameSize) 279 | buf.I("ADD", "R10, RSP") 280 | buf.I("MOVD", "RSP, R10") 281 | buf.I("AND", "$~15, R10, RSP") 282 | 283 | // Call function 284 | buf.I("BL", "(R9)") 285 | buf.I("MOVD", "R20, RSP") 286 | 287 | // Check frame guard 288 | buf.I("MOVD", "8(RSP), R10") 289 | buf.I("MOVD", "$0x%X, R11", guardValue) 290 | buf.I("CMP", "R10, R11") 291 | buf.I("BNE", "overflow") 292 | 293 | // Store return value 294 | if f.ReturnType != nil { 295 | arch.storeReturn(buf, &Argument{ 296 | Type: f.ReturnType, 297 | Name: "ret", 298 | }, offset) 299 | } 300 | 301 | buf.I("RET", "") 302 | 303 | // Overflow handler 304 | buf.S("overflow:") 305 | buf.I("CALL", "runtime·abort(SB)") 306 | buf.I("RET", "") 307 | } 308 | -------------------------------------------------------------------------------- /codegen/builder.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | type builder struct { 11 | buf bytes.Buffer 12 | } 13 | 14 | func (b *builder) Reset() { 15 | b.buf.Reset() 16 | } 17 | 18 | // NL writes a newline. 19 | func (b *builder) NL() { 20 | b.buf.WriteByte('\n') 21 | } 22 | 23 | // S writes a single line. 24 | func (b *builder) S(format string, args ...any) { 25 | _, err := fmt.Fprintf(&b.buf, format, args...) 26 | if err != nil { 27 | panic(err) 28 | } 29 | b.buf.WriteByte('\n') 30 | } 31 | 32 | // I writes an assembly instruction with padding. 33 | func (b *builder) I(instr, format string, args ...any) { 34 | b.buf.WriteByte('\t') 35 | 36 | _, err := fmt.Fprint(&b.buf, instr) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | if format != "" { 42 | _, err1 := fmt.Fprint(&b.buf, strings.Repeat(" ", 8-len(instr))) 43 | _, err2 := fmt.Fprintf(&b.buf, format, args...) 44 | 45 | if err = errors.Join(err1, err2); err != nil { 46 | panic(err) 47 | } 48 | } 49 | 50 | b.buf.WriteByte('\n') 51 | } 52 | 53 | func (b *builder) Bytes() []byte { 54 | return b.buf.Bytes() 55 | } 56 | -------------------------------------------------------------------------------- /codegen/codegen.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | "strings" 9 | 10 | "golang.org/x/tools/go/packages" 11 | ) 12 | 13 | const ( 14 | defaultFrameSize = 65536 15 | ) 16 | 17 | const ( 18 | ArchARM64 = "arm64" 19 | ArchAMD64 = "amd64" 20 | ) 21 | 22 | var ValidArchitectures = map[string]struct{}{ 23 | ArchARM64: {}, 24 | ArchAMD64: {}, 25 | } 26 | 27 | type platform interface { 28 | GenerateFunc(buf *builder, f *Function) 29 | Name() string 30 | } 31 | 32 | func composeAssemblyFile(buf *builder, outFile string, header *headerVars) error { 33 | tmpFilePath := outFile + ".tmp" 34 | 35 | f, err := os.OpenFile(tmpFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 36 | if err != nil { 37 | return fmt.Errorf("failed to open file: %w", err) 38 | } 39 | 40 | // Write file header 41 | if err := header.Format(f); err != nil { 42 | return fmt.Errorf("failed to write header: %w", err) 43 | } 44 | 45 | // Write generated code 46 | if _, err = f.Write(buf.Bytes()); err != nil { 47 | return fmt.Errorf("failed to write file: %w", err) 48 | } 49 | 50 | if err = f.Close(); err != nil { 51 | return fmt.Errorf("failed to close file: %w", err) 52 | } 53 | 54 | if err = os.Rename(tmpFilePath, outFile); err != nil { 55 | return fmt.Errorf("failed to rename file: %w", err) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func generatePackage(pkg *packages.Package, buf *builder, funcs []*Function, platform platform, fullCommand string) error { 62 | pkgDir := path.Dir(pkg.GoFiles[0]) 63 | asmFilePath := path.Join(pkgDir, fmt.Sprintf("directcgo_%s.s", platform.Name())) 64 | 65 | log.Printf("generating %s", asmFilePath) 66 | 67 | for i, fn := range funcs { 68 | platform.GenerateFunc(buf, fn) 69 | if i != len(funcs)-1 { 70 | buf.NL() 71 | } 72 | } 73 | 74 | err := composeAssemblyFile( 75 | buf, 76 | asmFilePath, 77 | &headerVars{ 78 | arch: platform.Name(), 79 | fullcmd: fullCommand, 80 | }, 81 | ) 82 | 83 | if err != nil { 84 | return fmt.Errorf("failed to write file: %w", err) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func align(offset, alignment int) int { 91 | return (offset + alignment - 1) & ^(alignment - 1) 92 | } 93 | 94 | func Run(pkgPath string, archList []string) error { 95 | cfg := &packages.Config{ 96 | Mode: packages.NeedName | 97 | packages.NeedFiles | 98 | packages.NeedSyntax | 99 | packages.NeedTypes | 100 | packages.NeedTypesInfo, 101 | } 102 | 103 | pkgs, err := packages.Load(cfg, pkgPath) 104 | if err != nil { 105 | return fmt.Errorf("failed to load packages: %v", err) 106 | } 107 | 108 | if len(pkgs) != 1 { 109 | return fmt.Errorf("expected exactly one package, got %d", len(pkgs)) 110 | } 111 | 112 | fullCmd := "directcgo " + strings.Join(os.Args[1:], " ") 113 | buf := new(builder) 114 | pkg := pkgs[0] 115 | 116 | funcs, err := parsePackage(pkg) 117 | if err != nil { 118 | return fmt.Errorf("ast parsing failed: %w", err) 119 | } 120 | 121 | for _, archName := range archList { 122 | buf.Reset() 123 | 124 | var arch platform 125 | 126 | switch archName { 127 | case ArchARM64: 128 | arch = newARM64() 129 | case ArchAMD64: 130 | arch = newAMD64() 131 | default: 132 | return fmt.Errorf("unknown architecture: %s", archName) 133 | } 134 | 135 | if err = generatePackage(pkg, buf, funcs, arch, fullCmd); err != nil { 136 | return fmt.Errorf("failed to process package: %w", err) 137 | } 138 | } 139 | 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /codegen/codegen_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import "testing" 4 | 5 | func TestAlign(t *testing.T) { 6 | tests := []struct { 7 | offset int 8 | alignment int 9 | want int 10 | }{ 11 | // 4-byte alignment 12 | {9, 4, 12}, 13 | {10, 4, 12}, 14 | {11, 4, 12}, 15 | 16 | // 8-byte alignment 17 | {9, 8, 16}, 18 | {10, 8, 16}, 19 | {11, 8, 16}, 20 | {12, 8, 16}, 21 | 22 | // 16-byte alignment 23 | {9, 16, 16}, 24 | {10, 16, 16}, 25 | {11, 16, 16}, 26 | {20, 16, 32}, 27 | {25, 16, 32}, 28 | } 29 | 30 | for _, tt := range tests { 31 | if got := align(tt.offset, tt.alignment); got != tt.want { 32 | t.Errorf("align(%d, %d) = %d; want %d", tt.offset, tt.alignment, got, tt.want) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /codegen/header.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | ) 8 | 9 | var headerTemplate = strings.TrimSpace(` 10 | //go:build %s && !windows 11 | 12 | // Code generated by directcgo. DO NOT EDIT. 13 | // %s 14 | 15 | #include "go_asm.h" 16 | #include "textflag.h" 17 | #include "funcdata.h" 18 | `) + "\n\n" 19 | 20 | type headerVars struct { 21 | arch string 22 | fullcmd string 23 | } 24 | 25 | func (v *headerVars) Format(w io.Writer) error { 26 | _, err := fmt.Fprintf(w, headerTemplate, v.arch, v.fullcmd) 27 | return err 28 | } 29 | -------------------------------------------------------------------------------- /codegen/ir.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/printer" 8 | "go/token" 9 | "go/types" 10 | "log" 11 | 12 | "golang.org/x/tools/go/packages" 13 | ) 14 | 15 | type Argument struct { 16 | Name string 17 | Type types.Type 18 | } 19 | 20 | type Function struct { 21 | Name string 22 | Args []Argument 23 | ReturnType types.Type 24 | Signature string 25 | } 26 | 27 | func parsePackage(pkg *packages.Package) ([]*Function, error) { 28 | var funcs []*Function 29 | var parseErr error 30 | 31 | for _, file := range pkg.Syntax { 32 | ast.Inspect(file, func(n ast.Node) bool { 33 | if parseErr != nil { 34 | return false 35 | } 36 | 37 | if decl, ok := n.(*ast.FuncDecl); ok { 38 | if decl.Body != nil || decl.Name.Name[0] == '_' || !hasNoEscapeComment(decl) { 39 | return false 40 | } 41 | 42 | fn, err := parseFunction(decl, pkg.TypesInfo) 43 | if err != nil { 44 | parseErr = fmt.Errorf("failed to process function %s: %v", decl.Name.Name, err) 45 | return false 46 | } 47 | 48 | log.Printf("found %s", fn.Signature) 49 | funcs = append(funcs, fn) 50 | } 51 | 52 | return true 53 | }) 54 | } 55 | 56 | if parseErr != nil { 57 | return nil, parseErr 58 | } 59 | 60 | return funcs, nil 61 | } 62 | 63 | func hasNoEscapeComment(decl *ast.FuncDecl) bool { 64 | if decl.Doc == nil { 65 | return false 66 | } 67 | for _, comment := range decl.Doc.List { 68 | if comment.Text == "//go:noescape" { 69 | return true 70 | } 71 | } 72 | return false 73 | } 74 | 75 | func parseFunction(decl *ast.FuncDecl, info *types.Info) (*Function, error) { 76 | f := &Function{ 77 | Signature: getFuncSignature(decl), 78 | Name: decl.Name.Name, 79 | } 80 | 81 | // Collect arguments 82 | if decl.Type.Params != nil { 83 | for _, field := range decl.Type.Params.List { 84 | typ := info.TypeOf(field.Type) 85 | 86 | for _, name := range field.Names { 87 | f.Args = append(f.Args, Argument{ 88 | Name: name.Name, 89 | Type: typ, 90 | }) 91 | } 92 | } 93 | } 94 | 95 | // Collect return type 96 | if decl.Type.Results != nil { 97 | if len(decl.Type.Results.List) > 1 { 98 | return nil, fmt.Errorf("multiple return values") 99 | } 100 | 101 | for _, field := range decl.Type.Results.List { 102 | typ := info.TypeOf(field.Type) 103 | f.ReturnType = typ 104 | } 105 | } 106 | 107 | return f, nil 108 | } 109 | 110 | func getFuncSignature(f *ast.FuncDecl) string { 111 | var buf bytes.Buffer 112 | 113 | err := printer.Fprint(&buf, token.NewFileSet(), f.Type) 114 | if err != nil { 115 | panic(err) 116 | } 117 | 118 | return f.Name.Name + " " + buf.String() 119 | } 120 | 121 | func typeSize(t types.Type) int { 122 | switch t := t.Underlying().(type) { 123 | case *types.Basic: 124 | switch t.Kind() { 125 | case types.Bool, types.Int8, types.Uint8: 126 | return 1 127 | case types.Int16, types.Uint16: 128 | return 2 129 | case types.Int32, types.Uint32, types.Float32: 130 | return 4 131 | case types.Int64, types.Uint64, types.Float64: 132 | return 8 133 | case types.UnsafePointer: 134 | return 8 135 | default: 136 | panic(fmt.Sprintf("unknown basic type: %s", t)) 137 | } 138 | case *types.Pointer: 139 | return 8 140 | case *types.Struct: 141 | var size int 142 | for i := 0; i < t.NumFields(); i++ { 143 | s := typeSize(t.Field(i).Type()) 144 | size = align(size, s) 145 | size += s 146 | } 147 | return size 148 | case *types.Array: 149 | return typeSize(t.Elem()) * int(t.Len()) 150 | } 151 | panic(fmt.Sprintf("unsupported type: %T", t)) 152 | } 153 | 154 | func isComposite(t types.Type) bool { 155 | switch t.Underlying().(type) { 156 | case *types.Struct, *types.Array: 157 | return true 158 | default: 159 | return false 160 | } 161 | } 162 | 163 | func isInteger(t types.Type) bool { 164 | if basic, ok := t.Underlying().(*types.Basic); ok { 165 | return basic.Info()&types.IsInteger != 0 166 | } 167 | return false 168 | } 169 | 170 | func isUnsigned(t types.Type) bool { 171 | if basic, ok := t.Underlying().(*types.Basic); ok { 172 | return basic.Info()&types.IsUnsigned != 0 173 | } 174 | return false 175 | } 176 | 177 | func isFloatingPoint(t types.Type) bool { 178 | if basic, ok := t.Underlying().(*types.Basic); ok { 179 | return basic.Info()&types.IsFloat != 0 180 | } 181 | return false 182 | } 183 | 184 | func getFieldCount(t types.Type) int { 185 | switch t := t.Underlying().(type) { 186 | case *types.Struct: 187 | return t.NumFields() 188 | case *types.Array: 189 | return int(t.Len()) 190 | default: 191 | panic(fmt.Sprintf("not a composite type: %T", t)) 192 | } 193 | } 194 | 195 | func getFields(t types.Type) []types.Type { 196 | switch typ := t.Underlying().(type) { 197 | case *types.Struct: 198 | fields := make([]types.Type, typ.NumFields()) 199 | for i := 0; i < typ.NumFields(); i++ { 200 | fields[i] = typ.Field(i).Type() 201 | } 202 | return fields 203 | case *types.Array: 204 | // For arrays, return a slice with the element type repeated length times 205 | fields := make([]types.Type, typ.Len()) 206 | for i := range fields { 207 | fields[i] = typ.Elem() 208 | } 209 | return fields 210 | default: 211 | panic(fmt.Sprintf("not a composite type: %T", t)) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /directcgo.go: -------------------------------------------------------------------------------- 1 | package directcgo 2 | 3 | import "unsafe" 4 | 5 | //go:noescape 6 | func Call(f unsafe.Pointer, arg unsafe.Pointer, ret unsafe.Pointer) 7 | -------------------------------------------------------------------------------- /directcgo.h: -------------------------------------------------------------------------------- 1 | #define FRAME_SIZE 65536 // 64KB 2 | #define FRAME_GUARD 3 | -------------------------------------------------------------------------------- /example/code.c: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | #include 3 | #include 4 | 5 | void PrintHello(char *name) 6 | { 7 | printf("Hello, %s!\n", name); 8 | } 9 | 10 | uint32_t AddTwoNumbers(uint32_t a, uint32_t b) 11 | { 12 | return a + b; 13 | } 14 | -------------------------------------------------------------------------------- /example/code.h: -------------------------------------------------------------------------------- 1 | #ifndef CODE_H 2 | #define CODE_H 3 | 4 | #include 5 | 6 | void PrintHello(char *name); 7 | 8 | uint32_t AddTwoNumbers(uint32_t a, uint32_t b); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #include "code.h" 5 | #include 6 | #include 7 | 8 | void ffi_PrintHello(void *arg, void *ret) 9 | { 10 | struct { 11 | char *name; 12 | } *a = arg; 13 | 14 | PrintHello(a->name); 15 | } 16 | 17 | void ffi_AddTwoNumbers(void *arg, void *ret) 18 | { 19 | struct { 20 | uint32_t a; 21 | uint32_t b; 22 | } *a = arg; 23 | 24 | uint32_t sum = AddTwoNumbers(a->a, a->b); 25 | *(uint32_t *)ret = sum; 26 | } 27 | */ 28 | import "C" 29 | import ( 30 | "fmt" 31 | "math/rand" 32 | "unsafe" 33 | 34 | "github.com/maxpoletaev/directcgo" 35 | ) 36 | 37 | func addTwoNumbers(a, b uint32) (ret uint32) { 38 | type args struct { 39 | a uint32 40 | b uint32 41 | } 42 | directcgo.Call( 43 | C.ffi_AddTwoNumbers, 44 | unsafe.Pointer(&args{a, b}), 45 | unsafe.Pointer(&ret), 46 | ) 47 | return ret 48 | } 49 | 50 | func printHello(name string) { 51 | type args struct { 52 | name unsafe.Pointer 53 | } 54 | 55 | nameBuf := make([]byte, len(name)+1) // +1 for null-terminator 56 | copy(nameBuf, name) 57 | 58 | directcgo.Call( 59 | C.ffi_PrintHello, 60 | unsafe.Pointer(&args{ 61 | unsafe.Pointer(&nameBuf[0]), 62 | }), 63 | nil, 64 | ) 65 | } 66 | 67 | func main() { 68 | var ( 69 | a = uint32(rand.Intn(100)) 70 | b = uint32(rand.Intn(100)) 71 | ) 72 | printHello("directcgo") 73 | sum := addTwoNumbers(a, b) 74 | fmt.Printf("%d + %d = %d\n", a, b, sum) 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/maxpoletaev/directcgo 2 | 3 | go 1.23.3 4 | 5 | require golang.org/x/tools v0.27.0 6 | 7 | require ( 8 | golang.org/x/mod v0.22.0 // indirect 9 | golang.org/x/sync v0.9.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 2 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 3 | golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= 4 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 5 | golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= 6 | golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 7 | -------------------------------------------------------------------------------- /testsuite/binding/binding.go: -------------------------------------------------------------------------------- 1 | package binding 2 | 3 | import "unsafe" 4 | 5 | // ----------------------- 6 | // Passing primitive types 7 | // ----------------------- 8 | 9 | /* 10 | void PassIntegers(int32_t i32, int64_t i64, int16_t i16, int8_t i8); 11 | void PassUnsignedIntegers(uint32_t u32, uint64_t u64, uint8_t u8, uint16_t u16); 12 | void PassFloats(float f32_0, double f64_0, double f64_1, float f32_1); 13 | void PassMixedNumbers(int8_t i8, float f32, uint32_t u32, double f64, int64_t i64); 14 | */ 15 | 16 | //go:noescape 17 | func PassIntegers(fn unsafe.Pointer, i32 int32, i64 int64, i16 int16, i8 int8) 18 | 19 | //go:noescape 20 | func PassUnsignedIntegers(fn unsafe.Pointer, u32 uint32, u64 uint64, u8 uint8, u16 uint16) 21 | 22 | //go:noescape 23 | func PassFloats(fn unsafe.Pointer, f32_0 float32, f64_0 float64, f64_1 float64, f32_1 float32) 24 | 25 | //go:noescape 26 | func PassMixedNumbers(fn unsafe.Pointer, i8 int8, f32 float32, u32 uint32, f64 float64, i64 int64) 27 | 28 | // ------------------------- 29 | // Returning primitive types 30 | // ------------------------- 31 | 32 | /* 33 | uint8_t ReturnUInt8(void); 34 | int8_t ReturnInt8(void); 35 | uint32_t ReturnUInt32(void); 36 | int32_t ReturnInt32(void); 37 | uint64_t ReturnUInt64(void); 38 | int64_t ReturnInt64(void); 39 | float ReturnFloat(void); 40 | double ReturnDouble(void); 41 | */ 42 | 43 | //go:noescape 44 | func ReturnUInt8(fn unsafe.Pointer) uint8 45 | 46 | //go:noescape 47 | func ReturnInt8(fn unsafe.Pointer) int8 48 | 49 | //go:noescape 50 | func ReturnUInt32(fn unsafe.Pointer) uint32 51 | 52 | //go:noescape 53 | func ReturnInt32(fn unsafe.Pointer) int32 54 | 55 | //go:noescape 56 | func ReturnUInt64(fn unsafe.Pointer) uint64 57 | 58 | //go:noescape 59 | func ReturnInt64(fn unsafe.Pointer) int64 60 | 61 | //go:noescape 62 | func ReturnFloat(fn unsafe.Pointer) float32 63 | 64 | //go:noescape 65 | func ReturnDouble(fn unsafe.Pointer) float64 66 | 67 | // --------------- 68 | // Passing structs 69 | // --------------- 70 | 71 | // typedef struct { 72 | // uint32_t u32_0; 73 | // uint32_t u32_1; 74 | // } SmallStructSameIntegers; 75 | type SmallStructSameIntegers struct { 76 | U32_0 uint32 77 | U32_1 uint32 78 | } 79 | 80 | // typedef struct { 81 | // uint8_t u8; 82 | // int32_t i32; 83 | // uint16_t u16; 84 | // } SmallStructMixedIntegers; 85 | type SmallStructMixedIntegers struct { 86 | U8 uint8 87 | I32 int32 88 | U16 uint16 89 | } 90 | 91 | // typedef struct { 92 | // float f32_0; 93 | // float f32_1; 94 | // float f32_2; 95 | // } SmallStructSameFloats; 96 | type SmallStructSameFloats struct { 97 | F32_0 float32 98 | F32_1 float32 99 | F32_2 float32 100 | } 101 | 102 | // typedef struct { 103 | // float f32_0; 104 | // double f64_0; 105 | // } SmallStructMixedFloats; 106 | type SmallStructMixedFloats struct { 107 | F32_0 float32 108 | F64_0 float64 109 | } 110 | 111 | // typedef struct { 112 | // int32_t i32; 113 | // uint8_t u8; 114 | // float f32; 115 | // uint16_t u16; 116 | // } SmallStructMixedNumbers; 117 | type SmallStructMixedNumbers struct { 118 | I32 int32 119 | U8 uint8 120 | F32 float32 121 | U16 uint16 122 | } 123 | 124 | // typedef struct { 125 | // uint32_t u32; 126 | // } SmallStructInner; 127 | type SmallStructInner struct { 128 | U32 uint32 129 | } 130 | 131 | // typedef struct { 132 | // SmallStructInner inner_0; 133 | // SmallStructInner inner_1; 134 | // } SmallStructOuter; 135 | type SmallStructOuter struct { 136 | Inner_0 SmallStructInner 137 | Inner_1 SmallStructInner 138 | } 139 | 140 | // typedef struct { 141 | // uint8_t u8; 142 | // uint8_t arr[3]; 143 | // double f64; 144 | // } SmallStructWithArray; 145 | type SmallStructWithArray struct { 146 | U8 uint8 147 | Arr [3]uint8 148 | F64 float64 149 | } 150 | 151 | /* 152 | void PassSmallStructSameIntegers(SmallStructSameIntegers s); 153 | void PassSmallStructMixedIntegers(SmallStructMixedIntegers s); 154 | void PassSmallStructSameFloats(SmallStructSameFloats s); 155 | void PassSmallStructMixedFloats(SmallStructMixedFloats s); 156 | void PassSmallStructMixedNumbers(SmallStructMixedNumbers s); 157 | void PassSmallStructNested(SmallStructOuter s); 158 | void PassSmallStructWithArray(SmallStructWithArray s); 159 | */ 160 | 161 | //go:noescape 162 | func PassSmallStructSameIntegers(fn unsafe.Pointer, s SmallStructSameIntegers) 163 | 164 | //go:noescape 165 | func PassSmallStructMixedIntegers(fn unsafe.Pointer, s SmallStructMixedIntegers) 166 | 167 | //go:noescape 168 | func PassSmallStructSameFloats(fn unsafe.Pointer, s SmallStructSameFloats) 169 | 170 | //go:noescape 171 | func PassSmallStructMixedFloats(fn unsafe.Pointer, s SmallStructMixedFloats) 172 | 173 | //go:noescape 174 | func PassSmallStructMixedNumbers(fn unsafe.Pointer, s SmallStructMixedNumbers) 175 | 176 | //go:noescape 177 | func PassSmallStructNested(fn unsafe.Pointer, s SmallStructOuter) 178 | 179 | //go:noescape 180 | func PassSmallStructWithArray(fn unsafe.Pointer, s SmallStructWithArray) 181 | -------------------------------------------------------------------------------- /testsuite/binding/directcgo_amd64.s: -------------------------------------------------------------------------------- 1 | //go:build amd64 && !windows 2 | 3 | // Code generated by directcgo. DO NOT EDIT. 4 | // directcgo -arch=amd64,arm64 ./testsuite/binding 5 | 6 | #include "go_asm.h" 7 | #include "textflag.h" 8 | #include "funcdata.h" 9 | 10 | // PassIntegers func(fn unsafe.Pointer, i32 int32, i64 int64, i16 int16, i8 int8) 11 | TEXT ·PassIntegers(SB), $65536-27 12 | MOVQ fn+0(FP), AX 13 | MOVLQSX i32+8(FP), DI 14 | MOVQ i64+16(FP), SI 15 | MOVWQSX i16+24(FP), DX 16 | MOVBQSX i8+26(FP), CX 17 | MOVL $0xF51A4219, R10 18 | MOVL R10, 8(SP) 19 | MOVQ SP, R12 20 | LEAQ 65536(SP), SP 21 | ANDQ $~15, SP 22 | CALL AX 23 | MOVQ R12, SP 24 | MOVL 8(SP), R10 25 | CMPL R10, $0xF51A4219 26 | JNE overflow 27 | RET 28 | overflow: 29 | CALL runtime·abort(SB) 30 | RET 31 | 32 | // PassUnsignedIntegers func(fn unsafe.Pointer, u32 uint32, u64 uint64, u8 uint8, u16 uint16) 33 | TEXT ·PassUnsignedIntegers(SB), $65536-28 34 | MOVQ fn+0(FP), AX 35 | MOVLQZX u32+8(FP), DI 36 | MOVQ u64+16(FP), SI 37 | MOVBQZX u8+24(FP), DX 38 | MOVWQZX u16+26(FP), CX 39 | MOVL $0x4A36BCE, R10 40 | MOVL R10, 8(SP) 41 | MOVQ SP, R12 42 | LEAQ 65536(SP), SP 43 | ANDQ $~15, SP 44 | CALL AX 45 | MOVQ R12, SP 46 | MOVL 8(SP), R10 47 | CMPL R10, $0x4A36BCE 48 | JNE overflow 49 | RET 50 | overflow: 51 | CALL runtime·abort(SB) 52 | RET 53 | 54 | // PassFloats func(fn unsafe.Pointer, f32_0 float32, f64_0 float64, f64_1 float64, f32_1 float32) 55 | TEXT ·PassFloats(SB), $65536-36 56 | MOVQ fn+0(FP), AX 57 | MOVSS f32_0+8(FP), X0 58 | MOVSD f64_0+16(FP), X1 59 | MOVSD f64_1+24(FP), X2 60 | MOVSS f32_1+32(FP), X3 61 | MOVL $0xFB966F8A, R10 62 | MOVL R10, 8(SP) 63 | MOVQ SP, R12 64 | LEAQ 65536(SP), SP 65 | ANDQ $~15, SP 66 | CALL AX 67 | MOVQ R12, SP 68 | MOVL 8(SP), R10 69 | CMPL R10, $0xFB966F8A 70 | JNE overflow 71 | RET 72 | overflow: 73 | CALL runtime·abort(SB) 74 | RET 75 | 76 | // PassMixedNumbers func(fn unsafe.Pointer, i8 int8, f32 float32, u32 uint32, f64 float64, i64 int64) 77 | TEXT ·PassMixedNumbers(SB), $65536-40 78 | MOVQ fn+0(FP), AX 79 | MOVBQSX i8+8(FP), DI 80 | MOVSS f32+12(FP), X0 81 | MOVLQZX u32+16(FP), SI 82 | MOVSD f64+24(FP), X1 83 | MOVQ i64+32(FP), DX 84 | MOVL $0x61A92597, R10 85 | MOVL R10, 8(SP) 86 | MOVQ SP, R12 87 | LEAQ 65536(SP), SP 88 | ANDQ $~15, SP 89 | CALL AX 90 | MOVQ R12, SP 91 | MOVL 8(SP), R10 92 | CMPL R10, $0x61A92597 93 | JNE overflow 94 | RET 95 | overflow: 96 | CALL runtime·abort(SB) 97 | RET 98 | 99 | // ReturnUInt8 func(fn unsafe.Pointer) uint8 100 | TEXT ·ReturnUInt8(SB), $65536-9 101 | MOVQ fn+0(FP), AX 102 | MOVL $0x8A5FE07A, R10 103 | MOVL R10, 8(SP) 104 | MOVQ SP, R12 105 | LEAQ 65536(SP), SP 106 | ANDQ $~15, SP 107 | CALL AX 108 | MOVQ R12, SP 109 | MOVL 8(SP), R10 110 | CMPL R10, $0x8A5FE07A 111 | JNE overflow 112 | MOVB AX, ret+8(FP) 113 | RET 114 | overflow: 115 | CALL runtime·abort(SB) 116 | RET 117 | 118 | // ReturnInt8 func(fn unsafe.Pointer) int8 119 | TEXT ·ReturnInt8(SB), $65536-9 120 | MOVQ fn+0(FP), AX 121 | MOVL $0xC188B80B, R10 122 | MOVL R10, 8(SP) 123 | MOVQ SP, R12 124 | LEAQ 65536(SP), SP 125 | ANDQ $~15, SP 126 | CALL AX 127 | MOVQ R12, SP 128 | MOVL 8(SP), R10 129 | CMPL R10, $0xC188B80B 130 | JNE overflow 131 | MOVB AX, ret+8(FP) 132 | RET 133 | overflow: 134 | CALL runtime·abort(SB) 135 | RET 136 | 137 | // ReturnUInt32 func(fn unsafe.Pointer) uint32 138 | TEXT ·ReturnUInt32(SB), $65536-12 139 | MOVQ fn+0(FP), AX 140 | MOVL $0x2414C9BE, R10 141 | MOVL R10, 8(SP) 142 | MOVQ SP, R12 143 | LEAQ 65536(SP), SP 144 | ANDQ $~15, SP 145 | CALL AX 146 | MOVQ R12, SP 147 | MOVL 8(SP), R10 148 | CMPL R10, $0x2414C9BE 149 | JNE overflow 150 | MOVL AX, ret+8(FP) 151 | RET 152 | overflow: 153 | CALL runtime·abort(SB) 154 | RET 155 | 156 | // ReturnInt32 func(fn unsafe.Pointer) int32 157 | TEXT ·ReturnInt32(SB), $65536-12 158 | MOVQ fn+0(FP), AX 159 | MOVL $0x7EE47F, R10 160 | MOVL R10, 8(SP) 161 | MOVQ SP, R12 162 | LEAQ 65536(SP), SP 163 | ANDQ $~15, SP 164 | CALL AX 165 | MOVQ R12, SP 166 | MOVL 8(SP), R10 167 | CMPL R10, $0x7EE47F 168 | JNE overflow 169 | MOVL AX, ret+8(FP) 170 | RET 171 | overflow: 172 | CALL runtime·abort(SB) 173 | RET 174 | 175 | // ReturnUInt64 func(fn unsafe.Pointer) uint64 176 | TEXT ·ReturnUInt64(SB), $65536-16 177 | MOVQ fn+0(FP), AX 178 | MOVL $0x87B7B8F8, R10 179 | MOVL R10, 8(SP) 180 | MOVQ SP, R12 181 | LEAQ 65536(SP), SP 182 | ANDQ $~15, SP 183 | CALL AX 184 | MOVQ R12, SP 185 | MOVL 8(SP), R10 186 | CMPL R10, $0x87B7B8F8 187 | JNE overflow 188 | MOVQ AX, ret+8(FP) 189 | RET 190 | overflow: 191 | CALL runtime·abort(SB) 192 | RET 193 | 194 | // ReturnInt64 func(fn unsafe.Pointer) int64 195 | TEXT ·ReturnInt64(SB), $65536-16 196 | MOVQ fn+0(FP), AX 197 | MOVL $0xA760BDE5, R10 198 | MOVL R10, 8(SP) 199 | MOVQ SP, R12 200 | LEAQ 65536(SP), SP 201 | ANDQ $~15, SP 202 | CALL AX 203 | MOVQ R12, SP 204 | MOVL 8(SP), R10 205 | CMPL R10, $0xA760BDE5 206 | JNE overflow 207 | MOVQ AX, ret+8(FP) 208 | RET 209 | overflow: 210 | CALL runtime·abort(SB) 211 | RET 212 | 213 | // ReturnFloat func(fn unsafe.Pointer) float32 214 | TEXT ·ReturnFloat(SB), $65536-12 215 | MOVQ fn+0(FP), AX 216 | MOVL $0x89131C30, R10 217 | MOVL R10, 8(SP) 218 | MOVQ SP, R12 219 | LEAQ 65536(SP), SP 220 | ANDQ $~15, SP 221 | CALL AX 222 | MOVQ R12, SP 223 | MOVL 8(SP), R10 224 | CMPL R10, $0x89131C30 225 | JNE overflow 226 | MOVSS X0, ret+8(FP) 227 | RET 228 | overflow: 229 | CALL runtime·abort(SB) 230 | RET 231 | 232 | // ReturnDouble func(fn unsafe.Pointer) float64 233 | TEXT ·ReturnDouble(SB), $65536-16 234 | MOVQ fn+0(FP), AX 235 | MOVL $0x47EA2CF, R10 236 | MOVL R10, 8(SP) 237 | MOVQ SP, R12 238 | LEAQ 65536(SP), SP 239 | ANDQ $~15, SP 240 | CALL AX 241 | MOVQ R12, SP 242 | MOVL 8(SP), R10 243 | CMPL R10, $0x47EA2CF 244 | JNE overflow 245 | MOVSD X0, ret+8(FP) 246 | RET 247 | overflow: 248 | CALL runtime·abort(SB) 249 | RET 250 | 251 | // PassSmallStructSameIntegers func(fn unsafe.Pointer, s SmallStructSameIntegers) 252 | TEXT ·PassSmallStructSameIntegers(SB), $65536-16 253 | MOVQ fn+0(FP), AX 254 | MOVQ s+8(FP), DI 255 | MOVL $0x8B8B10C2, R10 256 | MOVL R10, 8(SP) 257 | MOVQ SP, R12 258 | LEAQ 65536(SP), SP 259 | ANDQ $~15, SP 260 | CALL AX 261 | MOVQ R12, SP 262 | MOVL 8(SP), R10 263 | CMPL R10, $0x8B8B10C2 264 | JNE overflow 265 | RET 266 | overflow: 267 | CALL runtime·abort(SB) 268 | RET 269 | 270 | // PassSmallStructMixedIntegers func(fn unsafe.Pointer, s SmallStructMixedIntegers) 271 | TEXT ·PassSmallStructMixedIntegers(SB), $65536-26 272 | MOVQ fn+0(FP), AX 273 | MOVQ s+8(FP), DI 274 | MOVQ s+16(FP), SI 275 | MOVL $0x879EB4D2, R10 276 | MOVL R10, 8(SP) 277 | MOVQ SP, R12 278 | LEAQ 65536(SP), SP 279 | ANDQ $~15, SP 280 | CALL AX 281 | MOVQ R12, SP 282 | MOVL 8(SP), R10 283 | CMPL R10, $0x879EB4D2 284 | JNE overflow 285 | RET 286 | overflow: 287 | CALL runtime·abort(SB) 288 | RET 289 | 290 | // PassSmallStructSameFloats func(fn unsafe.Pointer, s SmallStructSameFloats) 291 | TEXT ·PassSmallStructSameFloats(SB), $65536-28 292 | MOVQ fn+0(FP), AX 293 | MOVQ s+8(FP), X0 294 | MOVQ s+16(FP), X1 295 | MOVL $0xA8292C92, R10 296 | MOVL R10, 8(SP) 297 | MOVQ SP, R12 298 | LEAQ 65536(SP), SP 299 | ANDQ $~15, SP 300 | CALL AX 301 | MOVQ R12, SP 302 | MOVL 8(SP), R10 303 | CMPL R10, $0xA8292C92 304 | JNE overflow 305 | RET 306 | overflow: 307 | CALL runtime·abort(SB) 308 | RET 309 | 310 | // PassSmallStructMixedFloats func(fn unsafe.Pointer, s SmallStructMixedFloats) 311 | TEXT ·PassSmallStructMixedFloats(SB), $65536-32 312 | MOVQ fn+0(FP), AX 313 | MOVQ s+8(FP), X0 314 | MOVQ s+16(FP), X1 315 | MOVL $0x80DD353C, R10 316 | MOVL R10, 8(SP) 317 | MOVQ SP, R12 318 | LEAQ 65536(SP), SP 319 | ANDQ $~15, SP 320 | CALL AX 321 | MOVQ R12, SP 322 | MOVL 8(SP), R10 323 | CMPL R10, $0x80DD353C 324 | JNE overflow 325 | RET 326 | overflow: 327 | CALL runtime·abort(SB) 328 | RET 329 | 330 | // PassSmallStructMixedNumbers func(fn unsafe.Pointer, s SmallStructMixedNumbers) 331 | TEXT ·PassSmallStructMixedNumbers(SB), $65536-30 332 | MOVQ fn+0(FP), AX 333 | MOVQ s+8(FP), DI 334 | MOVQ s+16(FP), SI 335 | MOVL $0xF126DC84, R10 336 | MOVL R10, 8(SP) 337 | MOVQ SP, R12 338 | LEAQ 65536(SP), SP 339 | ANDQ $~15, SP 340 | CALL AX 341 | MOVQ R12, SP 342 | MOVL 8(SP), R10 343 | CMPL R10, $0xF126DC84 344 | JNE overflow 345 | RET 346 | overflow: 347 | CALL runtime·abort(SB) 348 | RET 349 | 350 | // PassSmallStructNested func(fn unsafe.Pointer, s SmallStructOuter) 351 | TEXT ·PassSmallStructNested(SB), $65536-16 352 | MOVQ fn+0(FP), AX 353 | MOVQ s+8(FP), DI 354 | MOVL $0xDA593CF1, R10 355 | MOVL R10, 8(SP) 356 | MOVQ SP, R12 357 | LEAQ 65536(SP), SP 358 | ANDQ $~15, SP 359 | CALL AX 360 | MOVQ R12, SP 361 | MOVL 8(SP), R10 362 | CMPL R10, $0xDA593CF1 363 | JNE overflow 364 | RET 365 | overflow: 366 | CALL runtime·abort(SB) 367 | RET 368 | 369 | // PassSmallStructWithArray func(fn unsafe.Pointer, s SmallStructWithArray) 370 | TEXT ·PassSmallStructWithArray(SB), $65536-32 371 | MOVQ fn+0(FP), AX 372 | MOVQ s+8(FP), DI 373 | MOVQ s+16(FP), X0 374 | MOVL $0xEAB8C376, R10 375 | MOVL R10, 8(SP) 376 | MOVQ SP, R12 377 | LEAQ 65536(SP), SP 378 | ANDQ $~15, SP 379 | CALL AX 380 | MOVQ R12, SP 381 | MOVL 8(SP), R10 382 | CMPL R10, $0xEAB8C376 383 | JNE overflow 384 | RET 385 | overflow: 386 | CALL runtime·abort(SB) 387 | RET 388 | -------------------------------------------------------------------------------- /testsuite/binding/directcgo_arm64.s: -------------------------------------------------------------------------------- 1 | //go:build arm64 && !windows 2 | 3 | // Code generated by directcgo. DO NOT EDIT. 4 | // directcgo -arch=amd64,arm64 ./testsuite/binding 5 | 6 | #include "go_asm.h" 7 | #include "textflag.h" 8 | #include "funcdata.h" 9 | 10 | // PassIntegers func(fn unsafe.Pointer, i32 int32, i64 int64, i16 int16, i8 int8) 11 | TEXT ·PassIntegers(SB), $65536-27 12 | MOVD fn+0(FP), R9 13 | MOVW i32+8(FP), R0 14 | MOVD i64+16(FP), R1 15 | MOVH i16+24(FP), R2 16 | MOVB i8+26(FP), R3 17 | MOVD $0xF51A4219, R10 18 | MOVD R10, 8(RSP) 19 | MOVD RSP, R20 20 | MOVD $65536, R10 21 | ADD R10, RSP 22 | MOVD RSP, R10 23 | AND $~15, R10, RSP 24 | BL (R9) 25 | MOVD R20, RSP 26 | MOVD 8(RSP), R10 27 | MOVD $0xF51A4219, R11 28 | CMP R10, R11 29 | BNE overflow 30 | RET 31 | overflow: 32 | CALL runtime·abort(SB) 33 | RET 34 | 35 | // PassUnsignedIntegers func(fn unsafe.Pointer, u32 uint32, u64 uint64, u8 uint8, u16 uint16) 36 | TEXT ·PassUnsignedIntegers(SB), $65536-28 37 | MOVD fn+0(FP), R9 38 | MOVWU u32+8(FP), R0 39 | MOVD u64+16(FP), R1 40 | MOVBU u8+24(FP), R2 41 | MOVHU u16+26(FP), R3 42 | MOVD $0x4A36BCE, R10 43 | MOVD R10, 8(RSP) 44 | MOVD RSP, R20 45 | MOVD $65536, R10 46 | ADD R10, RSP 47 | MOVD RSP, R10 48 | AND $~15, R10, RSP 49 | BL (R9) 50 | MOVD R20, RSP 51 | MOVD 8(RSP), R10 52 | MOVD $0x4A36BCE, R11 53 | CMP R10, R11 54 | BNE overflow 55 | RET 56 | overflow: 57 | CALL runtime·abort(SB) 58 | RET 59 | 60 | // PassFloats func(fn unsafe.Pointer, f32_0 float32, f64_0 float64, f64_1 float64, f32_1 float32) 61 | TEXT ·PassFloats(SB), $65536-36 62 | MOVD fn+0(FP), R9 63 | FMOVS f32_0+8(FP), F0 64 | FMOVD f64_0+16(FP), F1 65 | FMOVD f64_1+24(FP), F2 66 | FMOVS f32_1+32(FP), F3 67 | MOVD $0xFB966F8A, R10 68 | MOVD R10, 8(RSP) 69 | MOVD RSP, R20 70 | MOVD $65536, R10 71 | ADD R10, RSP 72 | MOVD RSP, R10 73 | AND $~15, R10, RSP 74 | BL (R9) 75 | MOVD R20, RSP 76 | MOVD 8(RSP), R10 77 | MOVD $0xFB966F8A, R11 78 | CMP R10, R11 79 | BNE overflow 80 | RET 81 | overflow: 82 | CALL runtime·abort(SB) 83 | RET 84 | 85 | // PassMixedNumbers func(fn unsafe.Pointer, i8 int8, f32 float32, u32 uint32, f64 float64, i64 int64) 86 | TEXT ·PassMixedNumbers(SB), $65536-40 87 | MOVD fn+0(FP), R9 88 | MOVB i8+8(FP), R0 89 | FMOVS f32+12(FP), F0 90 | MOVWU u32+16(FP), R1 91 | FMOVD f64+24(FP), F1 92 | MOVD i64+32(FP), R2 93 | MOVD $0x61A92597, R10 94 | MOVD R10, 8(RSP) 95 | MOVD RSP, R20 96 | MOVD $65536, R10 97 | ADD R10, RSP 98 | MOVD RSP, R10 99 | AND $~15, R10, RSP 100 | BL (R9) 101 | MOVD R20, RSP 102 | MOVD 8(RSP), R10 103 | MOVD $0x61A92597, R11 104 | CMP R10, R11 105 | BNE overflow 106 | RET 107 | overflow: 108 | CALL runtime·abort(SB) 109 | RET 110 | 111 | // ReturnUInt8 func(fn unsafe.Pointer) uint8 112 | TEXT ·ReturnUInt8(SB), $65536-9 113 | MOVD fn+0(FP), R9 114 | MOVD $0x8A5FE07A, R10 115 | MOVD R10, 8(RSP) 116 | MOVD RSP, R20 117 | MOVD $65536, R10 118 | ADD R10, RSP 119 | MOVD RSP, R10 120 | AND $~15, R10, RSP 121 | BL (R9) 122 | MOVD R20, RSP 123 | MOVD 8(RSP), R10 124 | MOVD $0x8A5FE07A, R11 125 | CMP R10, R11 126 | BNE overflow 127 | MOVBU R0, ret+8(FP) 128 | RET 129 | overflow: 130 | CALL runtime·abort(SB) 131 | RET 132 | 133 | // ReturnInt8 func(fn unsafe.Pointer) int8 134 | TEXT ·ReturnInt8(SB), $65536-9 135 | MOVD fn+0(FP), R9 136 | MOVD $0xC188B80B, R10 137 | MOVD R10, 8(RSP) 138 | MOVD RSP, R20 139 | MOVD $65536, R10 140 | ADD R10, RSP 141 | MOVD RSP, R10 142 | AND $~15, R10, RSP 143 | BL (R9) 144 | MOVD R20, RSP 145 | MOVD 8(RSP), R10 146 | MOVD $0xC188B80B, R11 147 | CMP R10, R11 148 | BNE overflow 149 | MOVB R0, ret+8(FP) 150 | RET 151 | overflow: 152 | CALL runtime·abort(SB) 153 | RET 154 | 155 | // ReturnUInt32 func(fn unsafe.Pointer) uint32 156 | TEXT ·ReturnUInt32(SB), $65536-12 157 | MOVD fn+0(FP), R9 158 | MOVD $0x2414C9BE, R10 159 | MOVD R10, 8(RSP) 160 | MOVD RSP, R20 161 | MOVD $65536, R10 162 | ADD R10, RSP 163 | MOVD RSP, R10 164 | AND $~15, R10, RSP 165 | BL (R9) 166 | MOVD R20, RSP 167 | MOVD 8(RSP), R10 168 | MOVD $0x2414C9BE, R11 169 | CMP R10, R11 170 | BNE overflow 171 | MOVWU R0, ret+8(FP) 172 | RET 173 | overflow: 174 | CALL runtime·abort(SB) 175 | RET 176 | 177 | // ReturnInt32 func(fn unsafe.Pointer) int32 178 | TEXT ·ReturnInt32(SB), $65536-12 179 | MOVD fn+0(FP), R9 180 | MOVD $0x7EE47F, R10 181 | MOVD R10, 8(RSP) 182 | MOVD RSP, R20 183 | MOVD $65536, R10 184 | ADD R10, RSP 185 | MOVD RSP, R10 186 | AND $~15, R10, RSP 187 | BL (R9) 188 | MOVD R20, RSP 189 | MOVD 8(RSP), R10 190 | MOVD $0x7EE47F, R11 191 | CMP R10, R11 192 | BNE overflow 193 | MOVW R0, ret+8(FP) 194 | RET 195 | overflow: 196 | CALL runtime·abort(SB) 197 | RET 198 | 199 | // ReturnUInt64 func(fn unsafe.Pointer) uint64 200 | TEXT ·ReturnUInt64(SB), $65536-16 201 | MOVD fn+0(FP), R9 202 | MOVD $0x87B7B8F8, R10 203 | MOVD R10, 8(RSP) 204 | MOVD RSP, R20 205 | MOVD $65536, R10 206 | ADD R10, RSP 207 | MOVD RSP, R10 208 | AND $~15, R10, RSP 209 | BL (R9) 210 | MOVD R20, RSP 211 | MOVD 8(RSP), R10 212 | MOVD $0x87B7B8F8, R11 213 | CMP R10, R11 214 | BNE overflow 215 | MOVD R0, ret+8(FP) 216 | RET 217 | overflow: 218 | CALL runtime·abort(SB) 219 | RET 220 | 221 | // ReturnInt64 func(fn unsafe.Pointer) int64 222 | TEXT ·ReturnInt64(SB), $65536-16 223 | MOVD fn+0(FP), R9 224 | MOVD $0xA760BDE5, R10 225 | MOVD R10, 8(RSP) 226 | MOVD RSP, R20 227 | MOVD $65536, R10 228 | ADD R10, RSP 229 | MOVD RSP, R10 230 | AND $~15, R10, RSP 231 | BL (R9) 232 | MOVD R20, RSP 233 | MOVD 8(RSP), R10 234 | MOVD $0xA760BDE5, R11 235 | CMP R10, R11 236 | BNE overflow 237 | MOVD R0, ret+8(FP) 238 | RET 239 | overflow: 240 | CALL runtime·abort(SB) 241 | RET 242 | 243 | // ReturnFloat func(fn unsafe.Pointer) float32 244 | TEXT ·ReturnFloat(SB), $65536-12 245 | MOVD fn+0(FP), R9 246 | MOVD $0x89131C30, R10 247 | MOVD R10, 8(RSP) 248 | MOVD RSP, R20 249 | MOVD $65536, R10 250 | ADD R10, RSP 251 | MOVD RSP, R10 252 | AND $~15, R10, RSP 253 | BL (R9) 254 | MOVD R20, RSP 255 | MOVD 8(RSP), R10 256 | MOVD $0x89131C30, R11 257 | CMP R10, R11 258 | BNE overflow 259 | FMOVS F0, ret+8(FP) 260 | RET 261 | overflow: 262 | CALL runtime·abort(SB) 263 | RET 264 | 265 | // ReturnDouble func(fn unsafe.Pointer) float64 266 | TEXT ·ReturnDouble(SB), $65536-16 267 | MOVD fn+0(FP), R9 268 | MOVD $0x47EA2CF, R10 269 | MOVD R10, 8(RSP) 270 | MOVD RSP, R20 271 | MOVD $65536, R10 272 | ADD R10, RSP 273 | MOVD RSP, R10 274 | AND $~15, R10, RSP 275 | BL (R9) 276 | MOVD R20, RSP 277 | MOVD 8(RSP), R10 278 | MOVD $0x47EA2CF, R11 279 | CMP R10, R11 280 | BNE overflow 281 | FMOVD F0, ret+8(FP) 282 | RET 283 | overflow: 284 | CALL runtime·abort(SB) 285 | RET 286 | 287 | // PassSmallStructSameIntegers func(fn unsafe.Pointer, s SmallStructSameIntegers) 288 | TEXT ·PassSmallStructSameIntegers(SB), $65536-16 289 | MOVD fn+0(FP), R9 290 | MOVD s+8(FP), R0 291 | MOVD $0x8B8B10C2, R10 292 | MOVD R10, 8(RSP) 293 | MOVD RSP, R20 294 | MOVD $65536, R10 295 | ADD R10, RSP 296 | MOVD RSP, R10 297 | AND $~15, R10, RSP 298 | BL (R9) 299 | MOVD R20, RSP 300 | MOVD 8(RSP), R10 301 | MOVD $0x8B8B10C2, R11 302 | CMP R10, R11 303 | BNE overflow 304 | RET 305 | overflow: 306 | CALL runtime·abort(SB) 307 | RET 308 | 309 | // PassSmallStructMixedIntegers func(fn unsafe.Pointer, s SmallStructMixedIntegers) 310 | TEXT ·PassSmallStructMixedIntegers(SB), $65536-26 311 | MOVD fn+0(FP), R9 312 | MOVD s+8(FP), R0 313 | MOVD s+16(FP), R1 314 | MOVD $0x879EB4D2, R10 315 | MOVD R10, 8(RSP) 316 | MOVD RSP, R20 317 | MOVD $65536, R10 318 | ADD R10, RSP 319 | MOVD RSP, R10 320 | AND $~15, R10, RSP 321 | BL (R9) 322 | MOVD R20, RSP 323 | MOVD 8(RSP), R10 324 | MOVD $0x879EB4D2, R11 325 | CMP R10, R11 326 | BNE overflow 327 | RET 328 | overflow: 329 | CALL runtime·abort(SB) 330 | RET 331 | 332 | // PassSmallStructSameFloats func(fn unsafe.Pointer, s SmallStructSameFloats) 333 | TEXT ·PassSmallStructSameFloats(SB), $65536-28 334 | MOVD fn+0(FP), R9 335 | FMOVS s_0+8(FP), F0 336 | FMOVS s_1+12(FP), F1 337 | FMOVS s_2+16(FP), F2 338 | MOVD $0xA8292C92, R10 339 | MOVD R10, 8(RSP) 340 | MOVD RSP, R20 341 | MOVD $65536, R10 342 | ADD R10, RSP 343 | MOVD RSP, R10 344 | AND $~15, R10, RSP 345 | BL (R9) 346 | MOVD R20, RSP 347 | MOVD 8(RSP), R10 348 | MOVD $0xA8292C92, R11 349 | CMP R10, R11 350 | BNE overflow 351 | RET 352 | overflow: 353 | CALL runtime·abort(SB) 354 | RET 355 | 356 | // PassSmallStructMixedFloats func(fn unsafe.Pointer, s SmallStructMixedFloats) 357 | TEXT ·PassSmallStructMixedFloats(SB), $65536-32 358 | MOVD fn+0(FP), R9 359 | MOVD s+8(FP), R0 360 | MOVD s+16(FP), R1 361 | MOVD $0x80DD353C, R10 362 | MOVD R10, 8(RSP) 363 | MOVD RSP, R20 364 | MOVD $65536, R10 365 | ADD R10, RSP 366 | MOVD RSP, R10 367 | AND $~15, R10, RSP 368 | BL (R9) 369 | MOVD R20, RSP 370 | MOVD 8(RSP), R10 371 | MOVD $0x80DD353C, R11 372 | CMP R10, R11 373 | BNE overflow 374 | RET 375 | overflow: 376 | CALL runtime·abort(SB) 377 | RET 378 | 379 | // PassSmallStructMixedNumbers func(fn unsafe.Pointer, s SmallStructMixedNumbers) 380 | TEXT ·PassSmallStructMixedNumbers(SB), $65536-30 381 | MOVD fn+0(FP), R9 382 | MOVD s+8(FP), R0 383 | MOVD s+16(FP), R1 384 | MOVD $0xF126DC84, R10 385 | MOVD R10, 8(RSP) 386 | MOVD RSP, R20 387 | MOVD $65536, R10 388 | ADD R10, RSP 389 | MOVD RSP, R10 390 | AND $~15, R10, RSP 391 | BL (R9) 392 | MOVD R20, RSP 393 | MOVD 8(RSP), R10 394 | MOVD $0xF126DC84, R11 395 | CMP R10, R11 396 | BNE overflow 397 | RET 398 | overflow: 399 | CALL runtime·abort(SB) 400 | RET 401 | 402 | // PassSmallStructNested func(fn unsafe.Pointer, s SmallStructOuter) 403 | TEXT ·PassSmallStructNested(SB), $65536-16 404 | MOVD fn+0(FP), R9 405 | MOVD s+8(FP), R0 406 | MOVD $0xDA593CF1, R10 407 | MOVD R10, 8(RSP) 408 | MOVD RSP, R20 409 | MOVD $65536, R10 410 | ADD R10, RSP 411 | MOVD RSP, R10 412 | AND $~15, R10, RSP 413 | BL (R9) 414 | MOVD R20, RSP 415 | MOVD 8(RSP), R10 416 | MOVD $0xDA593CF1, R11 417 | CMP R10, R11 418 | BNE overflow 419 | RET 420 | overflow: 421 | CALL runtime·abort(SB) 422 | RET 423 | 424 | // PassSmallStructWithArray func(fn unsafe.Pointer, s SmallStructWithArray) 425 | TEXT ·PassSmallStructWithArray(SB), $65536-32 426 | MOVD fn+0(FP), R9 427 | MOVD s+8(FP), R0 428 | MOVD s+16(FP), R1 429 | MOVD $0xEAB8C376, R10 430 | MOVD R10, 8(RSP) 431 | MOVD RSP, R20 432 | MOVD $65536, R10 433 | ADD R10, RSP 434 | MOVD RSP, R10 435 | AND $~15, R10, RSP 436 | BL (R9) 437 | MOVD R20, RSP 438 | MOVD 8(RSP), R10 439 | MOVD $0xEAB8C376, R11 440 | CMP R10, R11 441 | BNE overflow 442 | RET 443 | overflow: 444 | CALL runtime·abort(SB) 445 | RET 446 | -------------------------------------------------------------------------------- /testsuite/output.c: -------------------------------------------------------------------------------- 1 | #include "testsuite.h" 2 | 3 | char out_buf[MAX_OUTPUT_SIZE]; 4 | 5 | const char* GetOutputBuffer() 6 | { 7 | return out_buf; 8 | } 9 | 10 | void ResetOutputBuffer() 11 | { 12 | out_buf[0] = '\0'; 13 | } 14 | -------------------------------------------------------------------------------- /testsuite/output.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | /* 4 | #include "testsuite.h" 5 | */ 6 | import "C" 7 | import ( 8 | "bytes" 9 | "unsafe" 10 | ) 11 | 12 | const ( 13 | outputBufferSize = 65536 14 | ) 15 | 16 | type Pair struct { 17 | Key string 18 | Val string 19 | } 20 | 21 | type Pairs []Pair 22 | 23 | func (p Pairs) Map() map[string]string { 24 | m := make(map[string]string) 25 | for _, pair := range p { 26 | m[pair.Key] = pair.Val 27 | } 28 | return m 29 | } 30 | 31 | func getOutput() (pairs Pairs) { 32 | outBuf := unsafe.Slice(C.GetOutputBuffer(), outputBufferSize) 33 | var key bytes.Buffer 34 | var val bytes.Buffer 35 | var inValue bool 36 | 37 | for _, b := range outBuf { 38 | if b == ' ' { 39 | if key.Len() != 0 && val.Len() != 0 { 40 | pairs = append(pairs, Pair{ 41 | Key: key.String(), 42 | Val: val.String(), 43 | }) 44 | } 45 | inValue = false 46 | key.Reset() 47 | val.Reset() 48 | } else if b == '=' { 49 | inValue = true 50 | continue 51 | } else if b == 0 { 52 | break 53 | } else { 54 | if inValue { 55 | val.WriteByte(byte(b)) 56 | } else { 57 | key.WriteByte(byte(b)) 58 | } 59 | } 60 | } 61 | 62 | if key.Len() != 0 && val.Len() != 0 { 63 | pairs = append(pairs, Pair{ 64 | Key: key.String(), 65 | Val: val.String(), 66 | }) 67 | } 68 | 69 | return pairs 70 | } 71 | -------------------------------------------------------------------------------- /testsuite/overflow/overflow.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #cgo CFLAGS: -O0 5 | 6 | #include 7 | #include 8 | 9 | static void printStackPointer() 10 | { 11 | void* sp; 12 | #if defined(__x86_64__) 13 | __asm__("movq %%rsp, %0" : "=r"(sp)); 14 | #elif defined(__aarch64__) 15 | __asm__("mov %0, sp" : "=r"(sp)); 16 | #endif 17 | printf("SP: %p\n", sp); 18 | } 19 | 20 | void recursiveFunc(void *arg) 21 | { 22 | struct { 23 | unsigned int n; 24 | } *a = arg; 25 | 26 | char buf[1024]; 27 | memset(buf, 0, 1024); 28 | 29 | if (a->n == 0) { 30 | return; 31 | } 32 | 33 | a->n--; 34 | recursiveFunc(a); 35 | } 36 | */ 37 | import "C" 38 | import ( 39 | "flag" 40 | "fmt" 41 | "log" 42 | "math/rand/v2" 43 | "runtime" 44 | "sync" 45 | "unsafe" 46 | 47 | "github.com/maxpoletaev/directcgo" 48 | ) 49 | 50 | func main() { 51 | var ( 52 | mode int 53 | depth int 54 | concurrency int 55 | iterations int 56 | silent bool 57 | ) 58 | 59 | flag.IntVar(&mode, "mode", 0, "0: directcgo (default), 2: cgo") 60 | flag.IntVar(&concurrency, "concurrency", 1, "concurrency") 61 | flag.IntVar(&iterations, "iterations", 1, "number of iterations (0 = infinite)") 62 | flag.BoolVar(&silent, "silent", false, "disable printing") 63 | flag.IntVar(&depth, "depth", 10, "recursion depth") 64 | flag.Parse() 65 | 66 | type funcArgs struct { 67 | n uint32 68 | } 69 | 70 | wg := sync.WaitGroup{} 71 | wg.Add(concurrency) 72 | 73 | for i := 0; i < concurrency; i++ { 74 | go func() { 75 | defer wg.Done() 76 | 77 | for j := 0; j < iterations; j++ { 78 | for k := 0; k < depth; k++ { 79 | n := uint32(k) 80 | 81 | if !silent { 82 | fmt.Println("depth=", n) 83 | } 84 | 85 | // Trigger GC occasionally 86 | if rand.Float32() < 0.3 { 87 | runtime.GC() 88 | } 89 | 90 | switch mode { 91 | case 0: 92 | directcgo.Call(C.recursiveFunc, unsafe.Pointer(&funcArgs{n}), nil) 93 | case 1: 94 | C.recursiveFunc(unsafe.Pointer(&funcArgs{n})) 95 | default: 96 | log.Fatalf("invalid mode: %d", mode) 97 | } 98 | } 99 | } 100 | }() 101 | } 102 | 103 | wg.Wait() 104 | } 105 | -------------------------------------------------------------------------------- /testsuite/overflow/overflow_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os/exec" 7 | "testing" 8 | ) 9 | 10 | // TestOverflow verifies correctness of directcgo calls under different conditions, 11 | // by running overflow.go, in a separate process. Basically it checks that: 12 | // 13 | // 1. Concurrent calls do not interfere with each other. 14 | // 2. GC triggered at random intervals does not cause issues. 15 | // 3. Stack smashing protection detects overflow at around 64 16 | // recursions (each recursion call allocates 1KB stack space). 17 | func TestOverflow(t *testing.T) { 18 | var p struct { 19 | concurrency []int 20 | depth []int 21 | } 22 | 23 | p.concurrency = []int{1, 2, 4} 24 | p.depth = []int{5, 10, 30, 50, 60} 25 | 26 | // Permute over all combinations of parameters. 27 | for _, c := range p.concurrency { 28 | for _, d := range p.depth { 29 | t.Run(fmt.Sprintf("concurrency=%d,depth=%d", c, d), func(t *testing.T) { 30 | cmd := exec.Command( 31 | "go", "run", "overflow.go", 32 | "-concurrency", fmt.Sprintf("%d", c), 33 | "-depth", fmt.Sprintf("%d", d), 34 | "-silent", 35 | ) 36 | 37 | out, err := cmd.CombinedOutput() 38 | 39 | if d >= 64 { 40 | if err == nil || !bytes.HasPrefix(out, []byte("SIGSEGV")) { 41 | t.Fatalf("expected stack smashing protection to trigger segfault\n%s", cmd.String()) 42 | } 43 | } else { 44 | if err != nil { 45 | t.Fatalf("failed to run testprog: %v\n%s", err, out) 46 | } 47 | } 48 | }) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /testsuite/primitive.c: -------------------------------------------------------------------------------- 1 | #include "testsuite.h" 2 | 3 | #include 4 | #include 5 | 6 | // ----------------------- 7 | // Passing primitive types 8 | // ----------------------- 9 | 10 | void PassIntegers(int32_t i32, int64_t i64, int16_t i16, int8_t i8) 11 | { 12 | ResetOutputBuffer(); 13 | sprintf(out_buf, "i32=%d i64=%lld i16=%d i8=%d", i32, i64, i16, i8); 14 | } 15 | 16 | void PassUnsignedIntegers(uint32_t u32, uint64_t u64, uint8_t u8, uint16_t u16) 17 | { 18 | ResetOutputBuffer(); 19 | sprintf(out_buf, "u32=%u u64=%llu u8=%u u16=%u", u32, u64, u8, u16); 20 | } 21 | 22 | void PassFloats(float f32_0, double f64_0, double f64_1, float f32_1) 23 | { 24 | ResetOutputBuffer(); 25 | sprintf(out_buf, "f32_0=%f f64_0=%f f64_1=%f f32_1=%f", f32_0, f64_0, f64_1, f32_1); 26 | } 27 | 28 | void PassMixedNumbers(int8_t i8, float f32, uint32_t u32, double f64, int64_t i64) 29 | { 30 | ResetOutputBuffer(); 31 | sprintf(out_buf, "i8=%d f32=%f u32=%u f64=%f i64=%lld", i8, f32, u32, f64, i64); 32 | } 33 | 34 | // ------------------------- 35 | // Returning primitive types 36 | // ------------------------- 37 | 38 | uint8_t ReturnUInt8(void) 39 | { 40 | return 0x12; 41 | } 42 | 43 | int8_t ReturnInt8(void) 44 | { 45 | return -0x12; 46 | } 47 | 48 | uint32_t ReturnUInt32(void) 49 | { 50 | return 0x12345678; 51 | } 52 | 53 | int32_t ReturnInt32(void) 54 | { 55 | return -0x12345678; 56 | } 57 | 58 | uint64_t ReturnUInt64(void) 59 | { 60 | return 0x123456789abcdef0; 61 | } 62 | 63 | int64_t ReturnInt64(void) 64 | { 65 | return -0x123456789abcdef0; 66 | } 67 | 68 | float ReturnFloat(void) 69 | { 70 | return 3.14159f; 71 | } 72 | 73 | double ReturnDouble(void) 74 | { 75 | return 3.14159265358979323846; 76 | } -------------------------------------------------------------------------------- /testsuite/primitive.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | /* 4 | #include "testsuite.h" 5 | */ 6 | import "C" 7 | import ( 8 | "github.com/maxpoletaev/directcgo/testsuite/binding" 9 | ) 10 | 11 | /* 12 | void PassIntegers(int32_t i32, int64_t i64, int16_t i16, int8_t i8); 13 | void PassUnsignedIntegers(uint32_t u32, uint64_t u64, uint8_t u8, uint16_t u16); 14 | void PassFloats(float f32_0, double f64_0, double f64_1, float f32_1); 15 | void PassMixedNumbers(int8_t i8, float f32, uint32_t u32, double f64, int64_t i64); 16 | */ 17 | 18 | func PassIntegers(i32 int32, i64 int64, i16 int16, i8 int8) { 19 | binding.PassIntegers(C.PassIntegers, i32, i64, i16, i8) 20 | } 21 | 22 | func PassIntegersCgo(i32 int32, i64 int64, i16 int16, i8 int8) { 23 | C.PassIntegers(C.int32_t(i32), C.int64_t(i64), C.int16_t(i16), C.int8_t(i8)) 24 | } 25 | 26 | func PassUnsignedIntegers(u32 uint32, u64 uint64, u8 uint8, u16 uint16) { 27 | binding.PassUnsignedIntegers(C.PassUnsignedIntegers, u32, u64, u8, u16) 28 | } 29 | 30 | func PassUnsignedIntegersCgo(u32 uint32, u64 uint64, u8 uint8, u16 uint16) { 31 | C.PassUnsignedIntegers(C.uint32_t(u32), C.uint64_t(u64), C.uint8_t(u8), C.uint16_t(u16)) 32 | } 33 | 34 | func PassFloats(f32_0 float32, f64_0 float64, f64_1 float64, f32_1 float32) { 35 | binding.PassFloats(C.PassFloats, f32_0, f64_0, f64_1, f32_1) 36 | } 37 | 38 | func PassFloatsCgo(f32_0 float32, f64_0 float64, f64_1 float64, f32_1 float32) { 39 | C.PassFloats(C.float(f32_0), C.double(f64_0), C.double(f64_1), C.float(f32_1)) 40 | } 41 | 42 | func PassMixedNumbers(i8 int8, f32 float32, u32 uint32, f64 float64, i64 int64) { 43 | binding.PassMixedNumbers(C.PassMixedNumbers, i8, f32, u32, f64, i64) 44 | } 45 | 46 | func PassMixedNumbersCgo(i8 int8, f32 float32, u32 uint32, f64 float64, i64 int64) { 47 | C.PassMixedNumbers(C.int8_t(i8), C.float(f32), C.uint32_t(u32), C.double(f64), C.int64_t(i64)) 48 | } 49 | 50 | /* 51 | uint8_t ReturnUInt8(void); 52 | int8_t ReturnInt8(void); 53 | uint32_t ReturnUInt32(void); 54 | int32_t ReturnInt32(void); 55 | uint64_t ReturnUInt64(void); 56 | int64_t ReturnInt64(void); 57 | float ReturnFloat(void); 58 | double ReturnDouble(void); 59 | */ 60 | 61 | func ReturnUInt8() uint8 { 62 | return binding.ReturnUInt8(C.ReturnUInt8) 63 | } 64 | 65 | func ReturnUInt8Cgo() uint8 { 66 | return uint8(C.ReturnUInt8()) 67 | } 68 | 69 | func ReturnInt8() int8 { 70 | return binding.ReturnInt8(C.ReturnInt8) 71 | } 72 | 73 | func ReturnInt8Cgo() int8 { 74 | return int8(C.ReturnInt8()) 75 | } 76 | 77 | func ReturnUInt32() uint32 { 78 | return binding.ReturnUInt32(C.ReturnUInt32) 79 | } 80 | 81 | func ReturnUInt32Cgo() uint32 { 82 | return uint32(C.ReturnUInt32()) 83 | } 84 | 85 | func ReturnInt32() int32 { 86 | return binding.ReturnInt32(C.ReturnInt32) 87 | } 88 | 89 | func ReturnInt32Cgo() int32 { 90 | return int32(C.ReturnInt32()) 91 | } 92 | 93 | func ReturnUInt64() uint64 { 94 | return binding.ReturnUInt64(C.ReturnUInt64) 95 | } 96 | 97 | func ReturnUInt64Cgo() uint64 { 98 | return uint64(C.ReturnUInt64()) 99 | } 100 | 101 | func ReturnInt64() int64 { 102 | return binding.ReturnInt64(C.ReturnInt64) 103 | } 104 | 105 | func ReturnInt64Cgo() int64 { 106 | return int64(C.ReturnInt64()) 107 | } 108 | 109 | func ReturnFloat() float32 { 110 | return binding.ReturnFloat(C.ReturnFloat) 111 | } 112 | 113 | func ReturnFloatCgo() float32 { 114 | return float32(C.ReturnFloat()) 115 | } 116 | 117 | func ReturnDouble() float64 { 118 | return binding.ReturnDouble(C.ReturnDouble) 119 | } 120 | 121 | func ReturnDoubleCgo() float64 { 122 | return float64(C.ReturnDouble()) 123 | } 124 | -------------------------------------------------------------------------------- /testsuite/primitive_test.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "math/rand/v2" 5 | "testing" 6 | ) 7 | 8 | func TestPassIntegers(t *testing.T) { 9 | for i := 0; i < 100; i++ { 10 | i32 := rand.Int32() 11 | i64 := rand.Int64() 12 | i16 := int16(rand.Int()) 13 | i8 := int8(rand.Int()) 14 | 15 | PassIntegers(i32, i64, i16, i8) 16 | result := getOutput() 17 | PassIntegersCgo(i32, i64, i16, i8) 18 | expected := getOutput() 19 | 20 | compareResults(t, result, expected) 21 | } 22 | } 23 | 24 | func TestPassUnsignedIntegers(t *testing.T) { 25 | for i := 0; i < 100; i++ { 26 | u32 := rand.Uint32() 27 | u64 := rand.Uint64() 28 | u8 := uint8(rand.Int()) 29 | u16 := uint16(rand.Int()) 30 | 31 | PassUnsignedIntegers(u32, u64, u8, u16) 32 | result := getOutput() 33 | PassUnsignedIntegersCgo(u32, u64, u8, u16) 34 | expected := getOutput() 35 | 36 | compareResults(t, result, expected) 37 | } 38 | } 39 | 40 | func TestPassFloats(t *testing.T) { 41 | for i := 0; i < 100; i++ { 42 | f32_0 := rand.Float32() 43 | f64_0 := rand.Float64() 44 | f64_1 := rand.Float64() 45 | f32_1 := rand.Float32() 46 | 47 | PassFloats(f32_0, f64_0, f64_1, f32_1) 48 | result := getOutput() 49 | PassFloatsCgo(f32_0, f64_0, f64_1, f32_1) 50 | expected := getOutput() 51 | 52 | compareResults(t, result, expected) 53 | } 54 | } 55 | 56 | func TestPassMixedNumbers(t *testing.T) { 57 | for i := 0; i < 100; i++ { 58 | i8 := int8(rand.Int()) 59 | f32 := rand.Float32() 60 | u32 := rand.Uint32() 61 | f64 := rand.Float64() 62 | i64 := rand.Int64() 63 | 64 | PassMixedNumbers(i8, f32, u32, f64, i64) 65 | result := getOutput() 66 | PassMixedNumbersCgo(i8, f32, u32, f64, i64) 67 | expected := getOutput() 68 | 69 | compareResults(t, result, expected) 70 | } 71 | } 72 | 73 | func TestReturnPrimitives(t *testing.T) { 74 | if ReturnUInt8() != ReturnUInt8Cgo() { 75 | t.Fatalf("ReturnUInt8 results do not match") 76 | } 77 | if ReturnInt8() != ReturnInt8Cgo() { 78 | t.Fatalf("ReturnInt8 results do not match") 79 | } 80 | if ReturnUInt32() != ReturnUInt32Cgo() { 81 | t.Fatalf("ReturnUInt32 results do not match") 82 | } 83 | if ReturnInt32() != ReturnInt32Cgo() { 84 | t.Fatalf("ReturnInt32 results do not match") 85 | } 86 | if ReturnUInt64() != ReturnUInt64Cgo() { 87 | t.Fatalf("ReturnUInt64 results do not match") 88 | } 89 | if ReturnInt64() != ReturnInt64Cgo() { 90 | t.Fatalf("ReturnInt64 results do not match") 91 | } 92 | if ReturnFloat() != ReturnFloatCgo() { 93 | t.Fatalf("ReturnFloat results do not match") 94 | } 95 | if ReturnDouble() != ReturnDoubleCgo() { 96 | t.Fatalf("ReturnDouble results do not match") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /testsuite/struct.c: -------------------------------------------------------------------------------- 1 | #include "testsuite.h" 2 | #include 3 | #include 4 | 5 | // --------------- 6 | // Passing structs 7 | // --------------- 8 | 9 | void PassSmallStructSameIntegers(SmallStructSameIntegers s) 10 | { 11 | ResetOutputBuffer(); 12 | sprintf(out_buf, "u32_0=%u u32_1=%u", s.u32_0, s.u32_1); 13 | } 14 | 15 | void PassSmallStructMixedIntegers(SmallStructMixedIntegers s) 16 | { 17 | ResetOutputBuffer(); 18 | sprintf(out_buf, "u8=%u i32=%d u16=%u", s.u8, s.i32, s.u16); 19 | } 20 | 21 | void PassSmallStructSameFloats(SmallStructSameFloats s) 22 | { 23 | ResetOutputBuffer(); 24 | sprintf(out_buf, "f32_0=%f f32_1=%f f32_2=%f", s.f32_0, s.f32_1, s.f32_2); 25 | } 26 | 27 | void PassSmallStructMixedFloats(SmallStructMixedFloats s) 28 | { 29 | ResetOutputBuffer(); 30 | sprintf(out_buf, "f32_0=%f f64_0=%f", s.f32_0, s.f64_0); 31 | } 32 | 33 | void PassSmallStructMixedNumbers(SmallStructMixedNumbers s) 34 | { 35 | ResetOutputBuffer(); 36 | sprintf(out_buf, "i32=%d u8=%u f32=%f u16=%u", s.i32, s.u8, s.f32, s.u16); 37 | } 38 | 39 | void PassSmallStructNested(SmallStructOuter s) 40 | { 41 | ResetOutputBuffer(); 42 | sprintf(out_buf, "inner_0.u32=%u inner_1.u32=%u", s.inner_0.u32, s.inner_1.u32); 43 | } 44 | 45 | void PassSmallStructWithArray(SmallStructWithArray s) 46 | { 47 | ResetOutputBuffer(); 48 | sprintf(out_buf, "u8=%u arr=[%u,%u,%u] f64=%f", s.u8, s.arr[0], s.arr[1], s.arr[2], s.f64); 49 | } 50 | -------------------------------------------------------------------------------- /testsuite/struct.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | /* 4 | #include "testsuite.h" 5 | */ 6 | import "C" 7 | import "github.com/maxpoletaev/directcgo/testsuite/binding" 8 | 9 | /* 10 | void PassSmallStructSameIntegers(SmallStructSameIntegers s); 11 | void PassSmallStructMixedIntegers(SmallStructMixedIntegers s); 12 | void PassSmallStructSameFloats(SmallStructSameFloats s); 13 | void PassSmallStructMixedFloats(SmallStructMixedFloats s); 14 | void PassSmallStructMixedNumbers(SmallStructMixedNumbers s); 15 | void PassSmallStructNested(SmallStructOuter s); 16 | */ 17 | 18 | func PassSmallStructSameIntegers(s binding.SmallStructSameIntegers) { 19 | binding.PassSmallStructSameIntegers(C.PassSmallStructSameIntegers, s) 20 | } 21 | 22 | func PassSmallStructSameIntegersCgo(s binding.SmallStructSameIntegers) { 23 | C.PassSmallStructSameIntegers(C.SmallStructSameIntegers{ 24 | u32_0: C.uint32_t(s.U32_0), 25 | u32_1: C.uint32_t(s.U32_1), 26 | }) 27 | } 28 | 29 | func PassSmallStructMixedIntegers(s binding.SmallStructMixedIntegers) { 30 | binding.PassSmallStructMixedIntegers(C.PassSmallStructMixedIntegers, s) 31 | } 32 | 33 | func PassSmallStructMixedIntegersCgo(s binding.SmallStructMixedIntegers) { 34 | C.PassSmallStructMixedIntegers(C.SmallStructMixedIntegers{ 35 | u8: C.uint8_t(s.U8), 36 | i32: C.int32_t(s.I32), 37 | u16: C.uint16_t(s.U16), 38 | }) 39 | } 40 | 41 | func PassSmallStructSameFloats(s binding.SmallStructSameFloats) { 42 | binding.PassSmallStructSameFloats(C.PassSmallStructSameFloats, s) 43 | } 44 | 45 | func PassSmallStructSameFloatsCgo(s binding.SmallStructSameFloats) { 46 | C.PassSmallStructSameFloats(C.SmallStructSameFloats{ 47 | f32_0: C.float(s.F32_0), 48 | f32_1: C.float(s.F32_1), 49 | f32_2: C.float(s.F32_2), 50 | }) 51 | } 52 | 53 | func PassSmallStructMixedFloats(s binding.SmallStructMixedFloats) { 54 | binding.PassSmallStructMixedFloats(C.PassSmallStructMixedFloats, s) 55 | } 56 | 57 | func PassSmallStructMixedFloatsCgo(s binding.SmallStructMixedFloats) { 58 | C.PassSmallStructMixedFloats(C.SmallStructMixedFloats{ 59 | f32_0: C.float(s.F32_0), 60 | f64_0: C.double(s.F64_0), 61 | }) 62 | } 63 | 64 | func PassSmallStructMixedNumbers(s binding.SmallStructMixedNumbers) { 65 | binding.PassSmallStructMixedNumbers(C.PassSmallStructMixedNumbers, s) 66 | } 67 | 68 | func PassSmallStructMixedNumbersCgo(s binding.SmallStructMixedNumbers) { 69 | C.PassSmallStructMixedNumbers(C.SmallStructMixedNumbers{ 70 | i32: C.int32_t(s.I32), 71 | u8: C.uint8_t(s.U8), 72 | f32: C.float(s.F32), 73 | u16: C.uint16_t(s.U16), 74 | }) 75 | } 76 | 77 | func PassSmallStructNested(s binding.SmallStructOuter) { 78 | binding.PassSmallStructNested(C.PassSmallStructNested, s) 79 | } 80 | 81 | func PassSmallStructNestedCgo(s binding.SmallStructOuter) { 82 | C.PassSmallStructNested(C.SmallStructOuter{ 83 | inner_0: C.SmallStructInner{ 84 | u32: C.uint32_t(s.Inner_0.U32), 85 | }, 86 | inner_1: C.SmallStructInner{ 87 | u32: C.uint32_t(s.Inner_1.U32), 88 | }, 89 | }) 90 | } 91 | 92 | func PassSmallStructWithArray(s binding.SmallStructWithArray) { 93 | binding.PassSmallStructWithArray(C.PassSmallStructWithArray, s) 94 | } 95 | 96 | func PassSmallStructWithArrayCgo(s binding.SmallStructWithArray) { 97 | C.PassSmallStructWithArray(C.SmallStructWithArray{ 98 | u8: C.uint8_t(s.U8), 99 | f64: C.double(s.F64), 100 | arr: [3]C.uint8_t{ 101 | C.uint8_t(s.Arr[0]), 102 | C.uint8_t(s.Arr[1]), 103 | C.uint8_t(s.Arr[2]), 104 | }, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /testsuite/struct_test.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "math/rand/v2" 5 | "testing" 6 | 7 | "github.com/maxpoletaev/directcgo/testsuite/binding" 8 | ) 9 | 10 | func compareResults(t *testing.T, result, expected []Pair) { 11 | t.Helper() 12 | 13 | if len(result) != len(expected) { 14 | t.Fatalf("expected %d pairs, got %d", len(expected), len(result)) 15 | } 16 | 17 | for i, pair := range expected { 18 | if result[i] != pair { 19 | t.Fatalf("results do not match:\nwant: %v\ngot: %v", expected, result) 20 | } 21 | } 22 | } 23 | 24 | func TestPassSmallStructSameIntegers(t *testing.T) { 25 | s := binding.SmallStructSameIntegers{ 26 | U32_0: rand.Uint32(), 27 | U32_1: rand.Uint32(), 28 | } 29 | 30 | PassSmallStructSameIntegers(s) 31 | result := getOutput() 32 | 33 | PassSmallStructSameIntegersCgo(s) 34 | expected := getOutput() 35 | 36 | compareResults(t, result, expected) 37 | } 38 | 39 | func TestPassSmallStructMixedIntegers(t *testing.T) { 40 | s := binding.SmallStructMixedIntegers{ 41 | U8: uint8(rand.Uint32()), 42 | I32: int32(rand.Uint32()), 43 | U16: uint16(rand.Uint32()), 44 | } 45 | 46 | PassSmallStructMixedIntegers(s) 47 | result := getOutput() 48 | 49 | PassSmallStructMixedIntegersCgo(s) 50 | expected := getOutput() 51 | 52 | compareResults(t, result, expected) 53 | } 54 | 55 | func TestPassSmallStructSameFloats(t *testing.T) { 56 | s := binding.SmallStructSameFloats{ 57 | F32_0: rand.Float32(), 58 | F32_1: rand.Float32(), 59 | F32_2: rand.Float32(), 60 | } 61 | 62 | PassSmallStructSameFloats(s) 63 | result := getOutput() 64 | 65 | PassSmallStructSameFloatsCgo(s) 66 | expected := getOutput() 67 | 68 | compareResults(t, result, expected) 69 | } 70 | 71 | func TestPassSmallStructMixedFloats(t *testing.T) { 72 | s := binding.SmallStructMixedFloats{ 73 | F32_0: rand.Float32(), 74 | F64_0: rand.Float64(), 75 | } 76 | 77 | PassSmallStructMixedFloats(s) 78 | result := getOutput() 79 | 80 | PassSmallStructMixedFloatsCgo(s) 81 | expected := getOutput() 82 | 83 | compareResults(t, result, expected) 84 | } 85 | 86 | func TestPassSmallStructMixedNumbers(t *testing.T) { 87 | s := binding.SmallStructMixedNumbers{ 88 | I32: int32(rand.Uint32()), 89 | U8: uint8(rand.Uint32()), 90 | F32: rand.Float32(), 91 | U16: uint16(rand.Uint32()), 92 | } 93 | 94 | PassSmallStructMixedNumbers(s) 95 | result := getOutput() 96 | 97 | PassSmallStructMixedNumbersCgo(s) 98 | expected := getOutput() 99 | 100 | compareResults(t, result, expected) 101 | } 102 | 103 | func TestPassSmallStructNested(t *testing.T) { 104 | s := binding.SmallStructOuter{ 105 | Inner_0: binding.SmallStructInner{U32: rand.Uint32()}, 106 | Inner_1: binding.SmallStructInner{U32: rand.Uint32()}, 107 | } 108 | 109 | PassSmallStructNested(s) 110 | result := getOutput() 111 | 112 | PassSmallStructNestedCgo(s) 113 | expected := getOutput() 114 | 115 | compareResults(t, result, expected) 116 | } 117 | 118 | func TestPassSmallStructWithArray(t *testing.T) { 119 | s := binding.SmallStructWithArray{ 120 | Arr: [3]uint8{ 121 | uint8(rand.Uint()), 122 | uint8(rand.Uint()), 123 | uint8(rand.Uint()), 124 | }, 125 | U8: uint8(rand.Uint()), 126 | F64: rand.Float64(), 127 | } 128 | 129 | PassSmallStructWithArray(s) 130 | result := getOutput() 131 | 132 | PassSmallStructWithArrayCgo(s) 133 | expected := getOutput() 134 | 135 | compareResults(t, result, expected) 136 | } 137 | -------------------------------------------------------------------------------- /testsuite/testsuite.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTSUITE_H_ 2 | #define TESTSUITE_H_ 3 | 4 | #include 5 | 6 | #define MAX_OUTPUT_SIZE 65536 7 | 8 | extern char out_buf[MAX_OUTPUT_SIZE]; 9 | 10 | const char *GetOutputBuffer(); 11 | 12 | void ResetOutputBuffer(); 13 | 14 | // ----------------------- 15 | // Passing primitive types 16 | // ----------------------- 17 | 18 | void PassIntegers(int32_t i32, int64_t i64, int16_t i16, int8_t i8); 19 | 20 | void PassUnsignedIntegers(uint32_t u32, uint64_t u64, uint8_t u8, uint16_t u16); 21 | 22 | void PassFloats(float f32_0, double f64_0, double f64_1, float f32_1); 23 | 24 | void PassMixedNumbers(int8_t i8, float f32, uint32_t u32, double f64, int64_t i64); 25 | 26 | // ------------------------- 27 | // Returning primitive types 28 | // ------------------------- 29 | 30 | uint8_t ReturnUInt8(void); 31 | 32 | int8_t ReturnInt8(void); 33 | 34 | uint32_t ReturnUInt32(void); 35 | 36 | int32_t ReturnInt32(void); 37 | 38 | uint64_t ReturnUInt64(void); 39 | 40 | int64_t ReturnInt64(void); 41 | 42 | float ReturnFloat(void); 43 | 44 | double ReturnDouble(void); 45 | 46 | // --------------- 47 | // Passing structs 48 | // --------------- 49 | 50 | typedef struct { 51 | uint32_t u32_0; 52 | uint32_t u32_1; 53 | } SmallStructSameIntegers; 54 | 55 | typedef struct { 56 | uint8_t u8; 57 | int32_t i32; 58 | uint16_t u16; 59 | } SmallStructMixedIntegers; 60 | 61 | typedef struct { 62 | float f32_0; 63 | float f32_1; 64 | float f32_2; 65 | } SmallStructSameFloats; 66 | 67 | typedef struct { 68 | float f32_0; 69 | double f64_0; 70 | } SmallStructMixedFloats; 71 | 72 | typedef struct { 73 | int32_t i32; 74 | uint8_t u8; 75 | float f32; 76 | uint16_t u16; 77 | } SmallStructMixedNumbers; 78 | 79 | typedef struct { 80 | uint32_t u32; 81 | } SmallStructInner; 82 | 83 | typedef struct { 84 | SmallStructInner inner_0; 85 | SmallStructInner inner_1; 86 | } SmallStructOuter; 87 | 88 | typedef struct { 89 | uint8_t u8; 90 | uint8_t arr[3]; 91 | double f64; 92 | } SmallStructWithArray; 93 | 94 | void PassSmallStructSameIntegers(SmallStructSameIntegers s); 95 | 96 | void PassSmallStructMixedIntegers(SmallStructMixedIntegers s); 97 | 98 | void PassSmallStructSameFloats(SmallStructSameFloats s); 99 | 100 | void PassSmallStructMixedFloats(SmallStructMixedFloats s); 101 | 102 | void PassSmallStructMixedNumbers(SmallStructMixedNumbers s); 103 | 104 | void PassSmallStructNested(SmallStructOuter s); 105 | 106 | void PassSmallStructWithArray(SmallStructWithArray s); 107 | 108 | #endif // TESTSUITE_H_ 109 | --------------------------------------------------------------------------------