Purego – A library for calling C functions from Go without Cgo
github.com
github.com
It looks like this would make cross-compiling CGO easier (no target C toolchain needed?)
Does this do anything to reduce the overhead of CGO calls / the stack size problem? IIRC the reason CGO overhead exists is at least partly because goroutines only have an ~8k stack to start with, and the C code doesn't know how to expand it-so CGO calls "must" first have the goroutine switched to an OS thread which has an ~8MB stack.
One reason I think Go <-> Zig could be a fantastic pairing is that Zig plans to add a builtin which tells you the maximum stack size of a given function[1], so you could grow the goroutine stack to that size and then call Zig (or, since Zig an compile C code, you could also call C with a tiny shim to report the stack required?) and then eliminate the goroutine -> OS thread switching overhead.
I’ve actually been quite interested in Zig. If that built-in was added than it would likely be possible to grow the goroutine stack to the proper size and than call the Zig code. Very interesting stuff!
I wonder if a more tailored CGO implementation could pin a goroutine to a thread which is guaranteed to have a system stack available, so that each CGO call need not worry about that switching at all. Maybe that'd require runtime changes though?
If cgo's only overhead was the "ordinary" overhead, most people wouldn't have an issue with it. It's downright zippy, in fact... as long as your syscall/C call takes less than 1us. If you stay under the 1us threshold, go will put the OS thread used for the syscall back where it found it and everything moves on.
The issue is that the OS thread was previously serving N goroutines that other parts of the program may be waiting on to move forward, and the OS thread is in a state where go can't pre-empt it and allow those other goroutines to move forward, and it has no idea how long it will be until it can move forward.
As a result, if a syscall/c call takes longer than 1us, go has no choice at this time but to resume a new thread, context switch all the old work onto that thread, and then suspend the syscall thread when it comes back. If you do this a lot, your performance will crater.
There's also separately a few issues around how go chooses to resume/suspend OS threads (for instance, if an os-locked goroutine does coooperative park for any reason to wait on another thread to do something, go will suspend the thread it was on, context switch to a different thread, then when the goroutine wakes up, it will realize its mistake, resume the thread it was on and context switch again).
This is all fixable stuff, but all the use cases that google cares about are working fine so it doesn't really get any attention.
Maybe I should play with removing the stack switching from purego so that under condition of a locked OS thread you can avoid that overhead :) I might give that a shot sometime
https://github.com/ebitengine/purego/blob/v0.2.0-alpha/sys_d...
I noticed another 0xB7 character in a comment, and sure enough it seems to be part of the identifiers: https://github.com/ebitengine/purego/search?q=runtime%C2%B7c...
[0] - https://go.dev/doc/asm
This is pretty cool because you can already do this sort of thing on Windows (using the syscall package, since the Windows Loader is always available from kernel32 anyways) and I use it all the time. Probably the most consequential thing I've done with it is my WebView 2 bindings. But with this, you could probably do the same thing on Linux and Mac with GtkWebkit and ... WebKit, and get a native HTML window without CGo on Windows, Mac, and Linux. Perhaps this has already been done (haven't paid attention) but it would make a pretty nice way to get a UI going in Go. (It's not like I'm a fan of using HTML UIs for native apps, but it works pretty well if you don't overdo it, and using native widgets on a given platform does mess up predictability a bit, but it saves disk space at least.)
It made sense after reading the comment about not needing a C toolchain for cross-compiling CGO but I didn't realize it immediately.
Neat stuff
I do want to write an article about how purego works under the hood.
(I have no idea how or why that came about, I've merely observed people having all sorts of trouble getting posts on there visible in aggregators and etc.)
extern (C) size_t strlen(const char*);
...
size_t length = strlen(p);
You can call any C code like that. You can even simply import C code!This would be such a game-changer for server-side rendering Javascript in Go with V8.
I'd love to integrate this into Bud[1].
No documentation, and example has no content makes the learning curve a bit steep.
Does anyone have any pointers on how to use this?
Just call purego.Dlopen(“libname.so”, purego.RTLD_GLOBAL)
Take the returned library (make sure to check for errors with purego.Dlerror() first) and call purego.Dlsym(lib, “cfuncName”). If it exists than u can call it with either purego.SyscallN or purego.RegisterFunc
It's easier with dlopen because it's still C and therefore you have the normal headers…
Particularly an example that takes a c struct pointer would be awesome.
What happens to const char* return values that are null ? I think it is empty string, but either test case or doc confirming it would be awesome
Thinking about this as I'd like to call native android libraries.
I've used SWIG extensively with Python to call C code and import C headers for testing/tooling purposes.
It's the Go equivalent of Python's ctypes, I think?
There might be a way, too.
`-buildmode=c-shared` is what produces a shared library with a C ABI exposed; https://golang.org/s/execmodes was the design document for it, which explains:
> It follows that all Go code shares a single runtime. All Go code uses the same memory allocator, the same goroutine scheduler, and in general acts as though it were linked into a single Go program. This is true even when multiple shared libraries are involved.
https://medium.com/analytics-vidhya/running-go-code-from-pyt...
Using Zig for Go cross-compilation, although quite great, isn't bulletproof. Finding the right CC/CXX incantation can be fairly tricky, the articles on this are not super up-to-date, and you need a copy of the macOS SDK[0] if you intend to cross-compile for macOS. You may also run into a few scary linker warnings and need to figure out the right Go build flags.
I definitely think Zig and Go can be best-friends, after all they share so many similar qualities and seem quite complementary to each-other. It's definitely possible to get Go<->Zig CGO cross compilation working (I did so for Sourcegraph); But Zig needs a little more love before it'll 'just work' as claimed for the Go use case, so best not to claim otherwise until then.
Pure Go? don't need anything else beside the Go compiler.
If you can avoid CGO, Avoid it.
When cross compiling with cgo you need a C cross compiling toolchain for the target platform installed with the right libc, etc. It is not impossible and that is how C/C++ is always cross compiled, but it is much more hassle than compiling pure Go.
The issue in Go is that goroutines run on small stack and C code has no way to know of that or increase the stack size - so Go's C calling facility (cgo) has to go through a thread and a proper stack.
There are some wild assembly hacks to go around it ^^
purego solves this by using dlopen and friends.
My understanding is Rust solves this through libloading (same approach effectively) and more heavy-handed approaches like cross-rs which distribute full C/C++ build toolchains for each target (in Docker images or something?)
It’s unavoidable on most platforms. Linux is pretty much the only mainstream platform where the syscall interface is considered the stable interface between user and kernel. Other platforms (like macOS, *BSD, and Windows) consider libc (or equivalent in Win32) to be the stable interface.
In the end, it's a very different paradigm and it is a pain for me to switch from the C-family-syntax to Go and back on a daily basis, and I gave up. Using C, JS, Java, C++, Dart, maybe even Rust(only tried it shortly) on the other hand is much more comfortable and natural for me. Go is just such a different style.
Been vastly different does carry some cost shall I say, Go could be a great language for new programmers however.
If everyone has their own repos, and there are too many of them, it becomes difficult for central teams to do more than write migration tools and dash boards and nag people.