1. 42

  2. 16

    Other people are not happy about Go making direct system calls; for example, on Solaris and Illumos, the only officially supported method of making system calls is also through the C library (although Go does it itself on those operating systems).

    This is false. Go uses libc on those systems. I wrote the port.

    1. 8

      I’ve updated my article with a correction (and the error is my fault for relying on a faulty memory instead of trying to check). Thank you for noting this.

      (I’m the author of the original article.)

    2. 9

      Seems that a big part of the justification in the article is to support small stacks for goroutines. Go is not the only language with green threads but seems fairly atypical in its bypassing of libc, how do other languages with green threads handle this?

      1. 5

        I think Go is unusual in that it uses a custom ABI to support the green threads internally, somewhat like Haskell.

        In typical cases where you’re not doing something like “Cheney on the MTA” style stack manipulation, you can just use setjmp and longjmp for management of the stack, and let the system figure out restoring. Go does quite a bit of stack manipulation in order to handle this stuff.

        1. 5

          “Cheney on the MTA” style stack manipulation,

          Please, please elaborate on this.

          1. 18

            sigh I accidentally reloaded the page when I had a bunch of links queued up for you hahaha 😭

            However, sorry for not elucidating on that reference! “Cheney on the MTA” is a garbage collector style that uses the stack to allocate objects rather than the heap. To do this, Cheney uses a set of function calls and callbacks, so as to “clear” the stack under certain conditions. The original paper by Henry Baker is super interesting, and yes this is yet another Henry Baker creation (if you’re not familiar, Henry Baker is/was a big Lisp and ML-the-language person who designed a number of interesting things for those languages). You can see this in Baker’s paper:

            object foo(env,cont,a1,a2,a3) environment env; object cont,a1,a2,a3;
            {int xyzzy; void *sp = &xyzzy; /* Where are we on the stack? */
             /* May put other local allocations here. */
             if (stack_check(sp)) /* Check allocation limit. */
                {closure5_type foo_closure; /* Locally allocate closure with 5 slots. */
                 /* Initialize foo_closure with env,cont,a1,a2,a3 and ptr to foo code. */
                 return GC(&foo_closure);} /* Do GC and then execute foo_closure. */
             /* Rest of foo code follows. */

            That GC call with a closure and such is used to actually clean the stack of Garbage. Chicken Scheme use(s|d) it to great effect, but it meant that calling into Chicken code was slightly more complex, because it’s not exactly the same calling convention: there are extra parameters and such that need to passed in, and many things are actually linked around callbacks. (this is the most hand wavy explanation of this, without going in to details, and if Christian is still on lobste.rs he will likely correct me…)

            Now, as related to Go, Go also uses it’s own calling convention and ABI internally. This has at times clashed with certain optimization techniques, because it’s not exactly register based, and like Haskell or Chicken Scheme, it’s not exactly trivial to call all code from C sources because of this change in ABI. This document here is one of the better references for Golang’s calling conventions, at least for x86/64; it’s not terrible, and there are reasons for it, but it does make things non-trivial; for example Osiris Lab at NYU just added Golang support to Ghidra, because it is slightly different from what other languages and stacks use.

            That was a lot, but I hope that made the reference more clear? Basically, there are systems like Cheney and others that manipulate the C stack via various means to make certain things easier, like Garbage collection. Golang does the same, but for purposes of making goroutines lighter weight.

            1. 4

              Thank you so much for the explanation!

              I thought it was going to be some slang/metaphor for like GC as it would be handled by a certain former US vice president trying to use public transportation or something–like, shooting in the face any allocations that weren’t high enough value or something.

              1. 7

                ah, no no; Cheney therein is a reference to reference to C.J. Cheney, best known for Cheney’s Algorithm, a stop-and-copy algorithm from the 70’s. Baker was making a song reference (“Charlie on the MTA”) and alluding to Cheney’s algorithm.

                1. 7

                  I’m a firm believer in that knowing the history and culture of a subject is essential to understanding it, and I do not know of another website where I could get this kind of insight into computing. Thank you.

                  1. 3

                    I also think that, esp. in computer science and to a lesser degree in software engineering, we have a tendency to not “survey the literature” regarding what we’re doing. So we end up very often recreating the same modalities as other pieces of software or algorithms without realizing this.

                    I’m very big into the history of programming languages, since I’ve read so many papers and books on the topic, and it’s always interesting to see how often we recreate things we had in the 70’s for things. A similar comment applies to operating systems, to a lesser degree, since we generally create fewer of them.

                    1. 2

                      In the large, and the small.

                      One of the characteristics of the best programmers I’ve worked with is a reflex to check whether the problem they’re dealing with is a solved one.

                      Another is to have the chops to solve the problem if it isn’t :)

                      Yet another is to check whether their eventual solution - either their own, or a previously invented wheel - is idiomatic.

                      All but one are problems solved with a search engine and a few good books, for most of the problems most teams encounter. And yet I lose count of the number of times people haven’t checked, and it’s been a mess, and the inheritors of the mess have wondered why the original implementers reinvented that particular wheel. As an octagon.

      2. 5

        WRT returning multiple members: I’m pretty sure you can return a struct by value - maybe even in registers! However, most do the pointer based out convention.

        Also, AIX (to the point the runtime loader must resolve syscalls, which has the amusing effect of allowing syscalls to be dynamically added and removed while being referenced by name) and Windows (which goes to very aggressive degrees to prevent raw system calls, including randomization) disallow raw syscalls, while FreeBSD macOS doesn’t block it, but highly discourage it (there have and will be ABI breaks like threading changes in macOS and inode64 in FreeBSD; they expect libsystem/libc as the stable ABI boundary boundary for that).

        I kinda wished there would be a seperation between the C stdlib and POSIX stuff - a cleaner separation would make it feel less bad for stuff like Go?

        1. 3

          C has no easy way to return multiple values; the natural modern API is Go’s approach of returning the result and the errno

          In C, it is „ugly“ and „procedural“, but possible and quite easy – you can have an „output parameter“ (a pointer), that is filled by the function (actually a procedure). It is a common way to „return“ multiple values from a „function“. Often the return value is „misused“ for exceptions/errors and actuall return value (something useful, why you called the function) is returned through an output parameter. Just the syntactic sugar is missing. I do not like this C way much, but I must admit that it is something that works and can be used.

          1. 3

            I would agree that “return a success/failure code and write output values via pointers instead” is a better idiom for C. When I’ve tried using apr (it’s surprisingly nice except that I could not find any documentation bar the headers) which uses this style pervasively, it’s relatively okay. Every function had identical error checking style. Downsides are that every function call takes 2 or more lines of boilerplate: declare automatic variables for the return value(s), call the function, branch on failure to a handler.

            For historical reasons, though, none of the posix C API is written in that style and we get errno (which sucks) and in-band errors instead (which sucks more) from many (but not all) functions.

            1. 0

              Such inconvenience is mostly given by the manual memory management. This is one of main reasons why I prefer higher languages than C for almost everything (application code). But C still makes sense in some cases – as a low level language, or as a „gateway language“ for linking code written in various languages – the C API can be called from probably every other language + many languages allow exporting their code/functions as a C API, so it allows linking code written in different languages together.

              Often this is the only option. However it would be nice to have similar bridge for object-oriented APIs. The D language can do this with C++… Of course, we can use some protocol (e.g. D-Bus, SNMP, CORBA or WebServices) and communicate over sockets, but it is completely different story than direct function/method calls.

              1. 3

                Yeah, uh, I don’t know why you are talking about this here? The discussion here is about why the posix C API kind of sucks as an ABI from Golang’s perspective.

                1. 2

                  Such inconvenience is mostly given by the manual memory management.

                  Not really. Go has GC but its error handling is only a little less boiler-plate-y, while writing purely unsafe Rust has very little of it because its type system and the language do more of the work for you.

              2. 1

                In C, it is „ugly“ and „procedural“, but possible and quite easy – you can have an „output parameter“ (a pointer), that is filled by the function (actually a procedure).

                What exactly is so ugly about this? It is simple and clean, and it lets you write the outputs of a procedure exactly where they are needed. The only “downside” seems to be that you cannot chain operations like foo.do_this().do_that().and_then_that(). But manipulating imperative data structures in this fashion seems like a pain to debug, even if you are using automatic memory management.

              3. 3

                Actually, how does Go handle this on Windows? As far as I know the only kernel API that Windows presents is through C functions or COM.

                1. 7

                  On Windows and some other platforms, Go does call through the system libraries. Go can make calls into platform libraries, it just doesn’t like doing it.

                  (I’m the author of the linked-to entry.)

                  1. 1

                    Cool, thanks!

                2. 2

                  Requiring all languages to go through the C library’s normal Unix API for system calls means constraining all languages to live with C’s historical baggage and limits. You could invent a new C library API for all of the system calls that directly wrote the error number to a spot the caller provided, which would make life much simpler, but no major Unix or C library is so far proposing to do this.

                  IMHO cleaner and proper solution would be a reusable abstraction layer (Go developers probably already had to write such code) that provides nicer API for the syscalls and that could be used also by other languages, not only Go. But of course, nobody can force the Go developers to do such work for others.

                  1. 4

                    The core problem is that this new API needs to be supported by the platform in order to be efficient (and to deal with things like the stack size issue). Otherwise you’re adding a layer of indirection over all C library functions you export without fixing all of the problems and without being able to make strong guarantees, and for at least some functions this slowdown matters.

                    (I’m the author of the original article.)

                  2. 1

                    Interestingly, after the OpenBSD port migrates to making syscalls through libc, there will be more supported platforms calling syscalls through libc than through golang’s custom assembly stubs (AIX, Solaris, macOS and Windows already call through libc).

                    There’s also another cost not covered in the article. Golang has its own calling conventions that it uses internally, so to make calls into C, you have to do some argument juggling in their assembly before actually making the function call.

                    In addition, to create the machine threads, you also now have to call into the system’s threading library rather than calling the primitives directly. There are some interesting questions about stack size for the machine threads, which I haven’t found answers to, since each platform that implements this seems to pull a magic number for how large to make the stack for each machine thread.