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.
> 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
This blog post covers a very similar problem, and the solution used
(scroll down to "Watch callbacks and channels"):
--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter
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
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
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.
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?
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
> 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
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
}
> 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
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
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)
}
}
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?
Russ
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
> 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