C callbacks and non-Go threads

264 views
Skip to first unread message

Matt Joiner

unread,
Feb 22, 2011, 9:43:48 PM2/22/11
to golang-nuts
I have another SO link:
http://stackoverflow.com/questions/4312894/c-callbacks-and-non-go-threads
I'll quote repeat question below, as I'm very keen to get an answer or
hear the development direction on this:

How does one call Go code in C from threads that weren't created by Go?
What do I assign to a C function pointer such that threads not created
by Go can call that pointer and enter into Go code?
I don't want to use SWIG.
The callbacks will be coming from threads Go hasn't seen before.
Neither cgo/life nor anything in pkg/runtime demonstrates this
behaviour AFAICT.

Ian Lance Taylor

unread,
Feb 22, 2011, 10:17:55 PM2/22/11
to Matt Joiner, golang-nuts
Matt Joiner <anac...@gmail.com> writes:

> How does one call Go code in C from threads that weren't created by Go?
> What do I assign to a C function pointer such that threads not created
> by Go can call that pointer and enter into Go code?
> I don't want to use SWIG.
> The callbacks will be coming from threads Go hasn't seen before.
> Neither cgo/life nor anything in pkg/runtime demonstrates this
> behaviour AFAICT.

I think that right now you can't. Conceptually it's not hard: the
callback from C to Go has to start a new goroutine running the Go
function, and then wait for that goroutine to finish. It shouldn't take
too much additional code, but nobody has pulled it together.

Ian

Gustavo Niemeyer

unread,
Feb 23, 2011, 9:16:29 AM2/23/11
to Matt Joiner, golang-nuts
> How does one call Go code in C from threads that weren't created by Go?
> What do I assign to a C function pointer such that threads not created
> by Go can call that pointer and enter into Go code?
> I don't want to use SWIG.

This blog post covers a very similar problem, and the solution used
(scroll down to "Watch callbacks and channels"):

http://j.mp/hoIUBU

--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter

unread,
Feb 23, 2011, 3:01:57 PM2/23/11
to golang-nuts
On Feb 23, 2:43 am, Matt Joiner <anacro...@gmail.com> wrote:
> How does one call Go code in C from threads that weren't created by Go?
> What do I assign to a C function pointer such that threads not created
> by Go can call that pointer and enter into Go code?

I don't think it is possible without resorting to run-time machine
code generation.

unread,
Feb 23, 2011, 3:06:09 PM2/23/11
to golang-nuts


On Feb 23, 3:17 am, Ian Lance Taylor <i...@google.com> wrote:
Wouldn't you mind to describe how exactly you plan to be able to
assign a Go function pointer to a C function pointer? Because I don't
think it is possible without resorting to run-time machine code
generation. I am talking about the case when the Go function pointer
is carrying values of some variables from the context in which the Go
function pointer was created.

Russ Cox

unread,
Feb 23, 2011, 3:08:17 PM2/23/11
to ⚛, golang-nuts
> Wouldn't you mind to describe how exactly you plan to be able to
> assign a Go function pointer to a C function pointer? Because I don't
> think it is possible without resorting to run-time machine code
> generation. I am talking about the case when the Go function pointer
> is carrying values of some variables from the context in which the Go
> function pointer was created.

See misc/cgo/life, which already lets C code call into Go code.

The wrinkle in this email is not letting C call Go,
it is letting that happen in a C-created thread.
It is the thread part that is not possible right now.

Russ

unread,
Feb 23, 2011, 5:12:38 PM2/23/11
to golang-nuts
I see. The thread part is problematic.

But I was thinking about something like "src/pkg/runtime/386/
closure.c". It is interesting that Go is using 4 bytes (the width of a
single pointer on i386) to represent Go function pointers, in
particular to represent closures. It would seem more logical to me to
use 2*4 bytes to represent a universal function pointer in Go (one
half would be the pointer to static code, the other half would be
optionally pointing to the dynamic data from the closure's context).

One problem of the current Go's approach to function pointers might be
that if someone passes a closure to the C code, then it will probably
confuse the garbage collector in case the function pointer is stored
only in C memory and has no representation in memory managed by Go.
Usually, C people don't expect function pointers to be garbage-
collectible objects, but expect them to point to static code.

Shouldn't the fact that [a variable of type "function pointer" could
in fact in Go point to a dynamically allocated code-object] be
mentioned somewhere in the documentation?

Russ Cox

unread,
Feb 24, 2011, 8:22:15 AM2/24/11
to ⚛, golang-nuts
> But I was thinking about something like "src/pkg/runtime/386/
> closure.c". It is interesting that Go is using 4 bytes (the width of a
> single pointer on i386) to represent Go function pointers, in
> particular to represent closures. It would seem more logical to me to
> use 2*4 bytes to represent a universal function pointer in Go (one
> half would be the pointer to static code, the other half would be
> optionally pointing to the dynamic data from the closure's context).

It seemed more logical to us to use one word,
so that at an instruction level you can always
use the CALL instruction to call a function value.

> One problem of the current Go's approach to function pointers might be
> that if someone passes a closure to the C code, then it will probably
> confuse the garbage collector in case the function pointer is stored
> only in C memory and has no representation in memory managed by Go.

Any references passed to C need to be retained by Go too.
Also, having a double-width function pointer would not solve
anything. The second word would point to allocated memory
and have the same management requirements.

Russ

roger peppe

unread,
Feb 24, 2011, 8:32:27 AM2/24/11
to r...@golang.org, ⚛, golang-nuts
On 24 February 2011 13:22, Russ Cox <r...@golang.org> wrote:
>> But I was thinking about something like "src/pkg/runtime/386/
>> closure.c". It is interesting that Go is using 4 bytes (the width of a
>> single pointer on i386) to represent Go function pointers, in
>> particular to represent closures. It would seem more logical to me to
>> use 2*4 bytes to represent a universal function pointer in Go (one
>> half would be the pointer to static code, the other half would be
>> optionally pointing to the dynamic data from the closure's context).
>
> It seemed more logical to us to use one word,
> so that at an instruction level you can always
> use the CALL instruction to call a function value.

it might be interesting to do some performance comparisons.
function pointers are often used for ephemeral functions where
the allocation and initialisation overhead might be significantly
greater than the overhead of passing around a double word.

Russ Cox

unread,
Feb 24, 2011, 8:57:14 AM2/24/11
to roger peppe, ⚛, golang-nuts
> it might be interesting to do some performance comparisons.
> function pointers are often used for ephemeral functions where
> the allocation and initialisation overhead might be significantly
> greater than the overhead of passing around a double word.

what do you mean by ephemeral?
func expressions that appear only as the function
in a call expression can be handled without any
allocation. it's been on my to-do list for a while.

beyond that i think you'd have a hard time running
the experiment. changing the representation of
func values is a sweeping change.

roger peppe

unread,
Feb 24, 2011, 9:13:44 AM2/24/11
to r...@golang.org, ⚛, golang-nuts
On 24 February 2011 13:57, Russ Cox <r...@golang.org> wrote:
>> it might be interesting to do some performance comparisons.
>> function pointers are often used for ephemeral functions where
>> the allocation and initialisation overhead might be significantly
>> greater than the overhead of passing around a double word.
>
> what do you mean by ephemeral?

i'm thinking of cases like the function passed to sort.Search
or strings.FieldsFunc (although of course the latter can often
be static).

> beyond that i think you'd have a hard time running
> the experiment.  changing the representation of
> func values is a sweeping change.

i'm sure that's true, but out of interest, would anything outside the
compiler and cgo need to change?

Russ Cox

unread,
Feb 24, 2011, 9:28:22 AM2/24/11
to roger peppe, ⚛, golang-nuts
>> what do you mean by ephemeral?
>
> i'm thinking of cases like the function passed to sort.Search
> or strings.FieldsFunc (although of course the latter can often
> be static).

note that the change would make passing an ordinary
function value like main.main more expensive. the only
benefit would be not depending on being able to store
executable code in allocated memory. that bothers some
people but it doesn't seem a compelling reason.

changing the representation won't help with allocations.
right now the representation is one block that begins
with a code stub and then has the captured addresses
(it is created by runtime.closure). making the representation
two words will eliminate the code stub but not the need to
keep the list of captured addresses somewhere. it won't
change the allocation profile.

the plan is to do escape analysis at some point and
pick off those allocations too. escape analysis applies
equally well to both representations.

>> beyond that i think you'd have a hard time running
>> the experiment.  changing the representation of
>> func values is a sweeping change.
>
> i'm sure that's true, but out of interest, would anything outside the
> compiler and cgo need to change?

reflect always has to change for things like this.
the runtime probably doesn't.


russ

Ian Lance Taylor

unread,
Feb 24, 2011, 9:48:34 AM2/24/11
to r...@golang.org, roger peppe, ⚛, golang-nuts
Russ Cox <r...@golang.org> writes:

> note that the change would make passing an ordinary
> function value like main.main more expensive. the only
> benefit would be not depending on being able to store
> executable code in allocated memory. that bothers some
> people but it doesn't seem a compelling reason.

I'm not sure, but I think this may have to be addressed one way or
another when running Go code on SELinux. SELinux works pretty hard to
prevent a program from generating any data which then becomes
executable. There are ways to handle it other than using double word
function pointers, though.

Ian

roger peppe

unread,
Feb 24, 2011, 9:56:56 AM2/24/11
to r...@golang.org, ⚛, golang-nuts
On 24 February 2011 14:28, Russ Cox <r...@golang.org> wrote:
>>> what do you mean by ephemeral?
>>
>> i'm thinking of cases like the function passed to sort.Search
>> or strings.FieldsFunc (although of course the latter can often
>> be static).
>
> note that the change would make passing an ordinary
> function value like main.main more expensive.  the only
> benefit would be not depending on being able to store
> executable code in allocated memory.  that bothers some
> people but it doesn't seem a compelling reason.
>
> changing the representation won't help with allocations.
> right now the representation is one block that begins
> with a code stub and then has the captured addresses
> (it is created by runtime.closure).  making the representation
> two words will eliminate the code stub but not the need to
> keep the list of captured addresses somewhere.  it won't
> change the allocation profile.

that's interesting. without thinking too hard about it, i'd
assumed that the captured variables were all allocated
in one contiguous block, so for a single closed-over int you'd
have the code stub followed by that int directly.

in that case the 50(?) byte overhead of the stub seems
quite harsh.

with the two word approach, you could in some
cases eliminate allocation entirely (although it would
require more compiler analysis), e.g.

func (f *Foo) Doer() func() {
return func(){
f.do()
}
}

given that f is never assigned to, there's no need
to take its address, so f could be passed directly in
the second word. this is probably quite a common
scenario (and enables efficient method expressions
for pointer receivers, should they be added to the language).

a similar thing could apply to more involved closures too.
given better escape analysis, both the closures below
could be passed a pointer to foo's frame directly
(although in such a case i'm sure you could generate
the stub on the stack too).

func foo(s string) (int, int) {
var i, j int
m := map[string]func(){
func() {i = 10},
func() {j = 20},
}
m[s]()
return i, j
}

unread,
Feb 24, 2011, 10:26:42 AM2/24/11
to golang-nuts
On Feb 24, 1:22 pm, Russ Cox <r...@golang.org> wrote:
> > But I was thinking about something like "src/pkg/runtime/386/
> > closure.c". It is interesting that Go is using 4 bytes (the width of a
> > single pointer on i386) to represent Go function pointers, in
> > particular to represent closures. It would seem more logical to me to
> > use 2*4 bytes to represent a universal function pointer in Go (one
> > half would be the pointer to static code, the other half would be
> > optionally pointing to the dynamic data from the closure's context).
>
> It seemed more logical to us to use one word,
> so that at an instruction level you can always
> use the CALL instruction to call a function value.

I think it would be only marginally more complex at the instruction
level. For example, one approach would be to load the 2nd half of the
two-word value to a predefined register, so instead of just

CALL *functionPtr

the caller would do

EAX := functionPtr[1]
CALL *functionPtr[0]

> > One problem of the current Go's approach to function pointers might be
> > that if someone passes a closure to the C code, then it will probably
> > confuse the garbage collector in case the function pointer is stored
> > only in C memory and has no representation in memory managed by Go.
>
> Any references passed to C need to be retained by Go too.
> Also, having a double-width function pointer would not solve
> anything.  The second word would point to allocated memory
> and have the same management requirements.

Well, but the only place where the representation matters is when Go
machine code interacts with the "outside world" (e.g: with C code). It
seems to me that in order to enable C code to *transparently* call Go
functions via *pointers*, there is no other choice than to run-time
generate another piece of code in *addition* to the optionally run-
time generated code used within the "Go world".

"Transparently" means that the C caller does not need to care about
switching stacks (the ESP register on i386), and that it can call the
function from any OS thread, including an OS thread unknown to Go's
runtime.

Let me explain it in more detail: A closure in Go is currently
implemented via run-time code generation. What you have written so far
suggests that you might be thinking that in order to call Go closure
from C (via a 4-byte function pointer stored in the "C world"), you
need to do run-time code generation only *once*. In my opinion, this
is false, because you need to do run-time code generation *twice*. The
1st time when the closure is being created in the "Go world", and the
2nd time when that closure is being passed as a 4-byte value from "Go
world" to "C world".

In case of a plain function pointer (not a closure), there is no need
to generate any run-time code while it is being used with the "Go
world". But as soon you want to pass it to C, you need to do run-time
code generation.

In summary:

- passing a plain function pointer from Go to C: needs 1 code-objects

- passing a closure from Go to C: needs 1+1=2 code-objects

The reason why a code-object is needed also in the case of plain-
function *pointer* is that the pointer is a *variable*. In other
words: the compiler has no idea what the actual value of the pointer
will be. Since the pointer's value is not known at compile-time, it is
impossible to construct the required code-object at compile-time. So,
we are left with only one option: to construct the code-object at run-
time. Note that the code-object needs to be able to switch stacks and
to somehow determine (presumably by calling some functions in Go's
runtime or by directly examining some global variables) whether it is
called from an OS thread known to the "Go world" or from an OS thread
created in the "C world".

unread,
Feb 24, 2011, 10:57:24 AM2/24/11
to golang-nuts
On Feb 24, 1:57 pm, Russ Cox <r...@golang.org> wrote:
> > it might be interesting to do some performance comparisons.
> > function pointers are often used for ephemeral functions where
> > the allocation and initialisation overhead might be significantly
> > greater than the overhead of passing around a double word.
>
> [cut]
> beyond that i think you'd have a hard time running
> the experiment.  changing the representation of
> func values is a sweeping change.

It isn't that hard. The results (code is listed below) are as follows:

- the top half of func main(): 250 milliseconds
- the bottom half of func main(): 669 milliseconds

Both parts are using dynamic memory allocation. The main difference
between the loops is that the 2nd loop is constructing a code-object.

So the speedup/slowdown factor for this toy benchmark is: 2.676

Although a toy benchmark, I think it clearly shows which approach is
faster.

-----

package main

type context_t struct {
a, b int
}

func add(c *context_t) int {
return c.a + c.b
}

func main() {
var j int = 0
for i := 0; i < 1e6; i++ {
ctx := &context_t{i, j}
f := add
j = f(ctx)
}
println(j)

j = 0
for i := 0; i < 1e6; i++ {
f := func() int { return i + j }
j = f()
}
println(j)
}

David Brown

unread,
Feb 24, 2011, 12:20:20 PM2/24/11
to ⚛, golang-nuts
On Thu, Feb 24 2011, ⚛ wrote:

> On Feb 24, 1:57 pm, Russ Cox <r...@golang.org> wrote:

> It isn't that hard. The results (code is listed below) are as follows:
>
> - the top half of func main(): 250 milliseconds
> - the bottom half of func main(): 669 milliseconds
>
> Both parts are using dynamic memory allocation. The main difference
> between the loops is that the 2nd loop is constructing a code-object.
>
> So the speedup/slowdown factor for this toy benchmark is: 2.676
>
> Although a toy benchmark, I think it clearly shows which approach is
> faster.

This is also going to depend a lot on the architecture, and what kind of
coherency is between the I and D caches. On ARM, generating a closure
requires a syscall to flush the cache, so the discrepancy is probably
much worse. If I get a chance, I'll try it later today.

David

Russ Cox

unread,
Feb 24, 2011, 12:24:11 PM2/24/11
to ⚛, golang-nuts
> Well, but the only place where the representation matters is when Go
> machine code interacts with the "outside world" (e.g: with C code).

You're right. So it doesn't matter, because you can't pass
an arbitrary Go function to C code, only functions that have
been tagged as //export in cgo code.

Russ

roger peppe

unread,
Feb 24, 2011, 12:08:54 PM2/24/11
to ⚛, golang-nuts
On 24 February 2011 15:57, ⚛ <0xe2.0x...@gmail.com> wrote:
> On Feb 24, 1:57 pm, Russ Cox <r...@golang.org> wrote:
>> > it might be interesting to do some performance comparisons.
>> > function pointers are often used for ephemeral functions where
>> > the allocation and initialisation overhead might be significantly
>> > greater than the overhead of passing around a double word.
>>
>> [cut]
>> beyond that i think you'd have a hard time running
>> the experiment.  changing the representation of
>> func values is a sweeping change.
>
> It isn't that hard. The results (code is listed below) are as follows:
>
> - the top half of func main(): 250 milliseconds
> - the bottom half of func main(): 669 milliseconds
>
> Both parts are using dynamic memory allocation. The main difference
> between the loops is that the 2nd loop is constructing a code-object.

i'm not sure that your code represents what the runtime is doing very well.

here's an alternative that i think is a little more accurate, with
some loop unrolling
to try to lessen the loop overhead.

obviously this is just a toy benchmark, but it's interesting to see the
difference between function allocation and frame allocation.
i *think* that's probably mostly to do with the stub generation overhead.

it's also interesting that the overhead of passing around a double word
seems to be overtaken by the overhead of calling the stub.

newdouble 5000000 593 ns/op
newfunc 1000000 2296 ns/op
calldouble 50000000 42 ns/op
callfunc 50000000 45 ns/op


package main

import (
"fmt"
"testing"
"runtime"
)

func main() {
fmt.Printf("newdouble %v\n", testing.Benchmark(benchmarkNewDouble))
fmt.Printf("newfunc %v\n", testing.Benchmark(benchmarkNewFunc))
fmt.Printf("calldouble %v\n", testing.Benchmark(benchmarkCallDouble))
fmt.Printf("callfunc %v\n", testing.Benchmark(benchmarkCallFunc))
}

type frame struct {
i *int
j *int
}

type funcptr struct {
code func(*frame) int
frame *frame
}

func add(f *frame) int {
return *f.i + *f.j
}

func benchmarkNewDouble(b *testing.B) {
var i, j int
runtime.GC()
b.ResetTimer()
b.StartTimer()
f := frame{&i, &j}
for n := b.N - 1; n >= 0; n-- {
fp := funcptr{add, &frame{&i, &j}}
fp.code(&f)
fp = funcptr{add, &frame{&i, &j}}
fp.code(&f)
fp = funcptr{add, &frame{&i, &j}}
fp.code(&f)
fp = funcptr{add, &frame{&i, &j}}
fp.code(&f)
fp = funcptr{add, &frame{&i, &j}}
fp.code(&f)
}
}

func benchmarkNewFunc(b *testing.B) {
var i, j int
runtime.GC()
b.ResetTimer()
b.StartTimer()
for n := b.N - 1; n >= 0; n-- {
fp := func() int { return i + j }
fp()
fp = func() int { return i + j }
fp()
fp = func() int { return i + j }
fp()
fp = func() int { return i + j }
fp()
fp = func() int { return i + j }
fp()
}
}

func execDouble(fp funcptr) int {
return fp.code(fp.frame)
}

func execFunc(f func() int) int {
return f()
}

func benchmarkCallDouble(b *testing.B) {
var i, j int
f := frame{&i, &j}
fp := funcptr{add, &f}
runtime.GC()
b.ResetTimer()
b.StartTimer()
for n := b.N - 1; n >= 0; n-- {
execDouble(fp)
execDouble(fp)
execDouble(fp)
execDouble(fp)
execDouble(fp)
}
}

func benchmarkCallFunc(b *testing.B) {
var i, j int
fp := func() int { return i + j }
runtime.GC()
b.ResetTimer()
b.StartTimer()
for n := b.N - 1; n >= 0; n-- {
execFunc(fp)
execFunc(fp)
execFunc(fp)
execFunc(fp)
execFunc(fp)
}
}

roger peppe

unread,
Feb 24, 2011, 1:57:08 PM2/24/11
to David Brown, ⚛, golang-nuts
On 24 February 2011 17:20, David Brown <gol...@davidb.org> wrote:
> This is also going to depend a lot on the architecture, and what kind of
> coherency is between the I and D caches.

i guess the I cache is another reason why it might not be a good idea
to be generating stub code all the time - it'll blow the cache
very quickly.

dynamically generating stub code is certainly cute, but is it actually worth it?

unread,
Feb 24, 2011, 2:49:08 PM2/24/11
to golang-nuts
I have no idea what ways do you mean. I am assuming that we are
talking about the case when the program is allowed to create as many
closures (billions) as it desires.

If it is impossible to execute an instruction belonging to a non-
executable region of memory, and it is impossible to turn a data
region into executable region, the only option that is left is to
write an interpreter for the purpose of turning the data snippets into
general purpose code snippets (we know it will work thanks to Turing,
Goedel, etc). But of course, you still cannot execute the CALL machine
instruction on the data. So, although a code-object pointer is a 4-
byte value (32-bit address space), it still cannot be executed
natively on the CPU.

Is this what you meant?

Russ Cox

unread,
Feb 24, 2011, 2:23:07 PM2/24/11
to roger peppe, David Brown, ⚛, golang-nuts
This discussion is stuck in hypotheticals.
If someone wants to do the work to convert
the representation, show that it's better, and
then contribute the code back under a CLA,
then please go ahead.

Russ

Ian Lance Taylor

unread,
Feb 24, 2011, 3:22:13 PM2/24/11
to ⚛, golang-nuts
⚛ <0xe2.0x...@gmail.com> writes:

One approach is that you write a special page in your executable which
holds a small piece of executable code which uses PC relative addressing
to load some sort of closure pointer, and probably also a code address,
from the subsequent page. Then when you need a closure you grab two
pages from virtual address space. You mmap your special page from the
executable onto the first one, with executable and non-writable
protection. Then you write the closure pointer and code address onto
the second page, with non-executable and writable protection. Of course
you can actually pack a bunch of executable code sequences onto the
first page, so you do that to use less address space and get better
cache behaviour, and you teach the memory allocator and garbage
collector how to handle these special pages.

Then a function pointer is a pointer into the allocated page with the
special executable page mapped onto it. When you jump to that address,
it loads the closure pointer and the real code address, and branches to
the real code address. If an attacker can take control of the second
mapped page they can control where the program branches to, but they can
only make it execute code which the program already contains. This is
not fundamentally different from taking control of a C++ virtual table
pointer.

Ian

David Brown

unread,
Feb 24, 2011, 7:44:08 PM2/24/11
to roger peppe, ⚛, golang-nuts
On Thu, Feb 24 2011, roger peppe wrote:

> On 24 February 2011 15:57, ⚛ <0xe2.0x...@gmail.com> wrote:

> it's also interesting that the overhead of passing around a double word
> seems to be overtaken by the overhead of calling the stub.
>
> newdouble 5000000 593 ns/op
> newfunc 1000000 2296 ns/op
> calldouble 50000000 42 ns/op
> callfunc 50000000 45 ns/op

For curiosity, this benchmark run on Arm (MSM8250, similar to a
NexusOne):

newdouble 100000 18984 ns/op
newfunc 50000 61560 ns/op
calldouble 5000000 329 ns/op
callfunc 5000000 584 ns/op

Not sure how much this would compare to actually changing the code
generator. The function pointer calls are more expensive relative (and
everything is slower overall, which isn't surprising).

David

Reply all
Reply to author
Forward
0 new messages