Skip to content

Commit

Permalink
content: add cgo-handle
Browse files Browse the repository at this point in the history
  • Loading branch information
changkun committed Jun 11, 2021
1 parent dcccec1 commit 86c6a99
Show file tree
Hide file tree
Showing 10 changed files with 1,192 additions and 0 deletions.
25 changes: 25 additions & 0 deletions content/assets/cgo-handle/cgo1/bench-2021-06-10-19:57:43.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
goos: darwin
goarch: arm64
pkg: cgo-handle/cgo1
BenchmarkHandle/non-concurrent-8 2822158 405.0 ns/op
BenchmarkHandle/non-concurrent-8 2935992 409.3 ns/op
BenchmarkHandle/non-concurrent-8 2932722 417.4 ns/op
BenchmarkHandle/non-concurrent-8 2917302 409.5 ns/op
BenchmarkHandle/non-concurrent-8 2921462 409.1 ns/op
BenchmarkHandle/non-concurrent-8 2953590 407.4 ns/op
BenchmarkHandle/non-concurrent-8 2948713 407.0 ns/op
BenchmarkHandle/non-concurrent-8 2955361 405.4 ns/op
BenchmarkHandle/non-concurrent-8 2833503 419.7 ns/op
BenchmarkHandle/non-concurrent-8 2925784 405.4 ns/op
BenchmarkHandle/concurrent-8 1562476 767.6 ns/op
BenchmarkHandle/concurrent-8 1561358 767.9 ns/op
BenchmarkHandle/concurrent-8 1560795 769.4 ns/op
BenchmarkHandle/concurrent-8 1563916 768.8 ns/op
BenchmarkHandle/concurrent-8 1518699 768.2 ns/op
BenchmarkHandle/concurrent-8 1559850 769.1 ns/op
BenchmarkHandle/concurrent-8 1557451 768.2 ns/op
BenchmarkHandle/concurrent-8 1560009 777.5 ns/op
BenchmarkHandle/concurrent-8 1510149 766.8 ns/op
BenchmarkHandle/concurrent-8 1500530 767.7 ns/op
PASS
ok cgo-handle/cgo1 36.071s
160 changes: 160 additions & 0 deletions content/assets/cgo-handle/cgo1/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>

// Package cgo is an implementation of golang.org/issue/37033.
//
// See golang.org/cl/294670 for code review discussion.
package cgo1

import (
"reflect"
"sync"
)

// Handle provides a way to pass values that contain Go pointers
// (pointers to memory allocated by Go) between Go and C without
// breaking the cgo pointer passing rules. A Handle is an integer
// value that can represent any Go value. A Handle can be passed
// through C and back to Go, and Go code can use the Handle to
// retrieve the original Go value.
//
// The underlying type of Handle is guaranteed to fit in an integer type
// that is large enough to hold the bit pattern of any pointer. The zero
// value of a Handle is not valid, and thus is safe to use as a sentinel
// in C APIs.
//
// For instance, on the Go side:
//
// package main
//
// /*
// #include <stdint.h> // for uintptr_t
//
// extern void MyGoPrint(uintptr_t handle);
// void myprint(uintptr_t handle);
// */
// import "C"
// import "runtime/cgo"
//
// //export MyGoPrint
// func MyGoPrint(handle C.uintptr_t) {
// h := cgo.Handle(handle)
// val := h.Value().(string)
// println(val)
// h.Delete()
// }
//
// func main() {
// val := "hello Go"
// C.myprint(C.uintptr_t(cgo.NewHandle(val)))
// // Output: hello Go
// }
//
// and on the C side:
//
// #include <stdint.h> // for uintptr_t
//
// // A Go function
// extern void MyGoPrint(uintptr_t handle);
//
// // A C function
// void myprint(uintptr_t handle) {
// MyGoPrint(handle);
// }
type Handle uintptr

// NewHandle returns a handle for a given value.
//
// The handle is valid until the program calls Delete on it. The handle
// uses resources, and this package assumes that C code may hold on to
// the handle, so a program must explicitly call Delete when the handle
// is no longer needed.
//
// The intended use is to pass the returned handle to C code, which
// passes it back to Go, which calls Value.
func NewHandle(v interface{}) Handle {
var k uintptr

rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
reflect.Map, reflect.Chan, reflect.Func:
if rv.IsNil() {
panic("cgo: cannot use Handle for nil value")
}

k = rv.Pointer()
default:
// Wrap and turn a value parameter into a pointer. This enables
// us to always store the passing object as a pointer, and helps
// to identify which of whose are initially pointers or values
// when Value is called.
v = &wrap{v}
k = reflect.ValueOf(v).Pointer()
}

// v was escaped to the heap because of reflection. As Go do not have
// a moving GC (and possibly lasts true for a long future), it is
// safe to use its pointer address as the key of the global map at
// this moment. The implementation must be reconsidered if moving GC
// is introduced internally in the runtime.
actual, loaded := m.LoadOrStore(k, v)
if !loaded {
return Handle(k)
}

arv := reflect.ValueOf(actual)
switch arv.Kind() {
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
reflect.Map, reflect.Chan, reflect.Func:
// The underlying object of the given Go value already have
// its existing handle.
if arv.Pointer() == k {
return Handle(k)
}

// If the loaded pointer is inconsistent with the new pointer,
// it means the address has been used for different objects
// because of GC and its address is reused for a new Go object,
// meaning that the Handle does not call Delete explicitly when
// the old Go value is not needed. Consider this as a misuse of
// a handle, do panic.
panic("cgo: misuse of a Handle")
default:
panic("cgo: Handle implementation has an internal bug")
}
}

// Value returns the associated Go value for a valid handle.
//
// The method panics if the handle is invalid.
func (h Handle) Value() interface{} {
v, ok := m.Load(uintptr(h))
if !ok {
panic("cgo: misuse of an invalid Handle")
}
if wv, ok := v.(*wrap); ok {
return wv.v
}
return v
}

// Delete invalidates a handle. This method should only be called once
// the program no longer needs to pass the handle to C and the C code
// no longer has a copy of the handle value.
//
// The method panics if the handle is invalid.
func (h Handle) Delete() {
_, ok := m.LoadAndDelete(uintptr(h))
if !ok {
panic("cgo: misuse of an invalid Handle")
}
}

var m = &sync.Map{} // map[uintptr]interface{}

// wrap wraps a Go value.
type wrap struct{ v interface{} }
149 changes: 149 additions & 0 deletions content/assets/cgo-handle/cgo1/cgo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>

package cgo1

import (
"testing"
)

func TestValueHandle(t *testing.T) {
v := 42

h1 := NewHandle(v)
h2 := NewHandle(v)

if uintptr(h1) == uintptr(h2) {
t.Fatalf("duplicated Go values should have different handles")
}

h1v := h1.Value().(int)
h2v := h2.Value().(int)
if h1v != h2v {
t.Fatalf("the Value of duplicated Go values are different: want %d, got %d", h1v, h2v)
}
if h1v != v {
t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v)
}

h1.Delete()
h2.Delete()

siz := 0
m.Range(func(k, v interface{}) bool {
siz++
return true
})
if siz != 0 {
t.Fatalf("handles are not deleted, want: %d, got %d", 0, siz)
}
}

func TestPointerHandle(t *testing.T) {
v := 42

p1 := &v
p2 := &v

h1 := NewHandle(p1)
h2 := NewHandle(p2)

if uintptr(h1) != uintptr(h2) {
t.Fatalf("pointers to the same value should have same handle")
}

h1v := h1.Value().(*int)
h2v := h2.Value().(*int)
if h1v != h2v {
t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v)
}

h1.Delete()

siz := 0
m.Range(func(k, v interface{}) bool {
siz++
return true
})
if siz != 0 {
t.Fatalf("handles are not deleted: want %d, got %d", 0, siz)
}

defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("double Delete on a same handle did not trigger a panic")
}()

h2.Delete()
}

func TestNilHandle(t *testing.T) {
var v *int

defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("nil should not be created as a handle successfully")
}()

_ = NewHandle(v)
}

func f1() {}
func f2() {}

type foo struct{}

func (f *foo) bar() {}
func (f *foo) wow() {}

func TestFuncHandle(t *testing.T) {
h1 := NewHandle(f1)
h2 := NewHandle(f2)
h3 := NewHandle(f2)

if h1 == h2 {
t.Fatalf("different functions should have different handles")
}
if h2 != h3 {
t.Fatalf("same functions should have same handles")
}

f := foo{}
h4 := NewHandle(f.bar)
h5 := NewHandle(f.bar)
h6 := NewHandle(f.wow)

if h4 != h5 {
t.Fatalf("same methods should have same handles")
}

if h5 == h6 {
t.Fatalf("different methods should have different handles")
}
}
func BenchmarkHandle(b *testing.B) {
b.Run("non-concurrent", func(b *testing.B) {
for i := 0; i < b.N; i++ {
h := NewHandle(i)
_ = h.Value()
h.Delete()
}
})
b.Run("concurrent", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var v int
for pb.Next() {
h := NewHandle(v)
_ = h.Value()
h.Delete()
}
})
})
}
25 changes: 25 additions & 0 deletions content/assets/cgo-handle/cgo2/bench-2021-06-10-19:56:57.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
goos: darwin
goarch: arm64
pkg: cgo-handle/cgo2
BenchmarkHandle/non-concurrent-8 2972786 400.8 ns/op
BenchmarkHandle/non-concurrent-8 3040192 390.4 ns/op
BenchmarkHandle/non-concurrent-8 3058515 391.1 ns/op
BenchmarkHandle/non-concurrent-8 3036595 391.0 ns/op
BenchmarkHandle/non-concurrent-8 3050779 392.0 ns/op
BenchmarkHandle/non-concurrent-8 3042757 389.9 ns/op
BenchmarkHandle/non-concurrent-8 3020358 404.6 ns/op
BenchmarkHandle/non-concurrent-8 3008872 396.0 ns/op
BenchmarkHandle/non-concurrent-8 3024003 392.7 ns/op
BenchmarkHandle/non-concurrent-8 3051001 392.9 ns/op
BenchmarkHandle/concurrent-8 1586451 755.9 ns/op
BenchmarkHandle/concurrent-8 1588989 754.7 ns/op
BenchmarkHandle/concurrent-8 1543646 757.2 ns/op
BenchmarkHandle/concurrent-8 1586863 755.3 ns/op
BenchmarkHandle/concurrent-8 1584315 758.5 ns/op
BenchmarkHandle/concurrent-8 1584865 759.3 ns/op
BenchmarkHandle/concurrent-8 1575646 769.2 ns/op
BenchmarkHandle/concurrent-8 1496425 760.6 ns/op
BenchmarkHandle/concurrent-8 1572453 759.0 ns/op
BenchmarkHandle/concurrent-8 1585522 779.9 ns/op
PASS
ok cgo-handle/cgo2 35.840s
Loading

0 comments on commit 86c6a99

Please sign in to comment.