goblend

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2024 License: BSD-3-Clause Imports: 3 Imported by: 0

README

goblend

CGO-less FFI for Go.

goblend uses the .syso trick from GcToolchainTricks, to enable the embedding of object files using only the inbuilt Go linker. When combined with a small amount of trampoline assembly, this allows programs to use pre-compiled code from another language without requiring a cross-compiler.

The main motivation behind this library is to enable library authors to reuse native code, without forcing downstream users to modify their build steps. Provided a user is on a supported platform, a goblend library can be used like any other Go library, with a simple go get $pkg.

The main downside is that all non-Go code must be pre-compiled outside of go build, and for library authors, must be commited to the repository. The friction of using native code is shifted from the end user to the library maintainers.

Features

  • CGO-less FFI calls and callbacks (CGO_ENABLED=0)
  • Cross-compilation
    • e.g. Build for Windows from a Linux machine, without configuring a cross-compiler.
  • CGO compatible (CGO_ENABLED=1)
  • runtime.iscgo == true on all platforms
    • The runtime is built from the original CGO source (for .S and .c files) for all supported platforms.
    • Should be (virtually) indistinguishable from CGO.

Supported platforms

  • Linux: arm64
  • Windows: amd64
  • Darwin: arm64

Example

See examples/add for a complete example.

To demonstrate the library is compatible:

$ go mod init example
$ go get github.com/csnewman/goblend/examples/add
$ go run github.com/csnewman/goblend/examples/add
2024/12/09 11:23:39 Add example
Hello world from Zig!
2024/12/09 11:23:39 123
2024/12/09 11:23:39 End of example
Native code

The exported functions must have the following signature:

export fn add(ptr: *const anyopaque) callconv(.c) u32 {
    // code here
}

and should be compiled to a native object file (archives/.a and libraries/.dll are unsupported):

zig build-obj -O ReleaseFast -target aarch64-linux-gnu --library c -femit-bin=core_linux_arm64.syso add.zig add2.zig etc

The native object file should have the .syso extension, so the Go linker detects them. On Windows and Linux you can take advantage of partial linking to reduce the number of object files down to one per platform.

Native imports

When building, you will likely experience errors such as:

github.com/csnewman/goblend/runtime(.text): unknown symbol memset in callarm64
github.com/csnewman/goblend/runtime(.text): unknown symbol memset in callarm64
github.com/csnewman/goblend/runtime(.text): relocation target memset not defined

This is caused by your native code importing other code, such as memset from libc in the above example. As such, you will need to create a file per platform, such as core_linux.go that imports the necessary dependencies:

//go:build linux

package runtime

import "unsafe"

//go:cgo_import_dynamic memset memset "libc.so.6"

// More dependencies...

Note: On Windows, the situation is slightly more complex due to __imp_{some name} symbols being a pointer to a function, while the {some name} symbol expects to be the function itself. Please see WriteFile in the examples/add example.

Calling from Go

First you will need to create a trampoline for each function you intend to call: (core.s)

#include "go_asm.h"
#include "funcdata.h"
#include "textflag.h"

TEXT goblend_add(SB),NOSPLIT|NOFRAME,$0-0
  JMP add(SB)
  RET

Note, no parameter information is necessary here, as this is a simple jmp.

Then in a Go file, such as core.go, you will need to get a ptr to the trampoline:

package main

import "unsafe"

//go:linkname add goblend_add
var add uintptr
var addPtr = uintptr(unsafe.Pointer(&add))

Finally, the native function can be called:

ret := goblend.Call(addPtr, 0)

The 0 argument is the ptr to pass to the native function. You can pass and return any arbitrary data this way.

Callbacks

The pointer to a function can be resolved via goblend.FuncPtr:

func ExampleCallback(ptr uintptr) {
	// callback logic here
}

ptr := goblend.FuncPtr(ExampleCallback)

Callbacks must have the above signature and can be invoked via:

extern "C" fn _cgo_wait_runtime_init_done() callconv(.C) *const anyopaque;
extern "C" fn _cgo_release_context(v: *const anyopaque) callconv(.C) void;
extern "C" fn crosscall2(ptr: *const anyopaque, data: *const anyopaque, size: c_int, ctx: *const anyopaque) callconv(.C) void;

fn someExample(ptr: *const anyopaque) void {
    var value :u64 = 321;

    const ctx = _cgo_wait_runtime_init_done();
    crosscall2(ptr, &value, 0, ctx);
    _cgo_release_context(ctx);
}

The ctx setup and releasing is optional in most circumstances and instead 0/null can be passed, however runtime.SetCgoTraceback will not work. size appears to be unused in the cgo runtime and can be left as 0.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AlwaysFalse bool

AlwaysFalse is always false.

This value is hidden from the compiler, so code using it will not be eliminated.

Functions

func Call

func Call(fn uintptr, arg uintptr) int32

Call invokes the native function, passing a single argument.

It is important that any pointers referenced from arg are alive and are valid throughout the duration of their use.

KeepAliveSpill is the preferred choice for keeping pointers alive. It will cause the value to be stored on the meaning that the native code is allowed to perform Go callbacks or store the pointer (assuming the memory has been pinned with runtime.Pinner).

KeepAliveNoSpill can be used in limited circumstance, where the function does not call Go code or attempt to store the value.

func CheckPointer added in v0.3.0

func CheckPointer(ptr any, arg any)

CheckPointer checks if the argument contains a Go pointer that points to an unpinned Go pointer, and panics if it does. See [runtime.cgoCheckPointer] for more details.

NOTE: This may not yet be working.

func CheckResult added in v0.3.0

func CheckResult(ptr any)

CheckResult checks the result parameter of an exported Go function. It panics if the result is or contains any other pointer into unpinned Go memory. See [runtime.cgoCheckResult] for more details.

NOTE: This may not yet be working.

func FuncPtr added in v0.2.0

func FuncPtr(f any) uintptr

FuncPtr returns the address of the provided function, suitable for passing to native code.

func KeepAliveNoSpill added in v0.3.0

func KeepAliveNoSpill(v any)

KeepAliveNoSpill will keep the value alive until this point without causing the value to be stored on the heap.

func KeepAliveSpill added in v0.3.0

func KeepAliveSpill(v any)

KeepAliveSpill will keep the value alive until this point and will cause the value to be stored on the heap.

Types

This section is empty.

Directories

Path Synopsis
examples
add

Jump to

Keyboard shortcuts

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