Browse Source

cgo: add support for C.CString and related functions

pull/2312/head
Ayke van Laethem 3 years ago
committed by Ron Evans
parent
commit
c31aef06ba
  1. 42
      cgo/cgo.go
  2. 20
      cgo/testdata/basic.out.go
  3. 20
      cgo/testdata/const.out.go
  4. 20
      cgo/testdata/errors.out.go
  5. 20
      cgo/testdata/flags.out.go
  6. 20
      cgo/testdata/symbols.out.go
  7. 20
      cgo/testdata/types.out.go
  8. 3
      src/runtime/runtime.go
  9. 3
      src/runtime/runtime_unix.go
  10. 47
      src/runtime/string.go
  11. 2
      testdata/cgo/main.c
  12. 15
      testdata/cgo/main.go
  13. 2
      testdata/cgo/main.h
  14. 7
      testdata/cgo/out.txt

42
cgo/cgo.go

@ -165,10 +165,35 @@ typedef unsigned long long _Cgo_ulonglong;
// First part of the generated Go file. Written here as Go because that's much // First part of the generated Go file. Written here as Go because that's much
// easier than constructing the entire AST in memory. // easier than constructing the entire AST in memory.
// The string/bytes functions below implement C.CString etc. To make sure the
// runtime doesn't need to know the C int type, lengths are converted to uintptr
// first.
// These functions will be modified to get a "C." prefix, so the source below
// doesn't reflect the final AST.
const generatedGoFilePrefix = ` const generatedGoFilePrefix = `
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func __GoStringN(*C.char, uintptr) string
func GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func __GoBytes(unsafe.Pointer, uintptr) []byte
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
` `
// Process extracts `import "C"` statements from the AST, parses the comment // Process extracts `import "C"` statements from the AST, parses the comment
@ -219,6 +244,23 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// This is always a bug in the cgo package. // This is always a bug in the cgo package.
panic("unexpected error: " + err.Error()) panic("unexpected error: " + err.Error())
} }
// If the Comments field is not set to nil, the fmt package will get
// confused about where comments should go.
p.generated.Comments = nil
// Adjust some of the functions in there.
for _, decl := range p.generated.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
}
}
}
// Patch some types, for example *C.char in C.CString.
astutil.Apply(p.generated, p.walker, nil)
// Find all C.* symbols. // Find all C.* symbols.
for _, f := range files { for _, f := range files {

20
cgo/testdata/basic.out.go

@ -4,6 +4,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type C.int16_t = int16 type C.int16_t = int16
type C.int32_t = int32 type C.int32_t = int32
type C.int64_t = int64 type C.int64_t = int64

20
cgo/testdata/const.out.go

@ -4,6 +4,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
const C.bar = C.foo const C.bar = C.foo
const C.foo = 3 const C.foo = 3

20
cgo/testdata/errors.out.go

@ -18,6 +18,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
const C.SOME_CONST_3 = 1234 const C.SOME_CONST_3 = 1234
type C.int16_t = int16 type C.int16_t = int16

20
cgo/testdata/flags.out.go

@ -9,6 +9,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
const C.BAR = 3 const C.BAR = 3
const C.FOO_H = 1 const C.FOO_H = 1

20
cgo/testdata/symbols.out.go

@ -4,6 +4,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
//export foo //export foo
func C.foo(a C.int, b C.int) C.int func C.foo(a C.int, b C.int) C.int

20
cgo/testdata/types.out.go

@ -4,6 +4,26 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
const C.option2A = 20 const C.option2A = 20
const C.optionA = 0 const C.optionA = 0
const C.optionB = 1 const C.optionB = 1

3
src/runtime/runtime.go

@ -42,6 +42,9 @@ func memzero(ptr unsafe.Pointer, size uintptr)
//export strlen //export strlen
func strlen(ptr unsafe.Pointer) uintptr func strlen(ptr unsafe.Pointer) uintptr
//export malloc
func malloc(size uintptr) unsafe.Pointer
// Compare two same-size buffers for equality. // Compare two same-size buffers for equality.
func memequal(x, y unsafe.Pointer, n uintptr) bool { func memequal(x, y unsafe.Pointer, n uintptr) bool {
for i := uintptr(0); i < n; i++ { for i := uintptr(0); i < n; i++ {

3
src/runtime/runtime_unix.go

@ -13,9 +13,6 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int
//export usleep //export usleep
func usleep(usec uint) int func usleep(usec uint) int
//export malloc
func malloc(size uintptr) unsafe.Pointer
// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
// Note: off_t is defined as int64 because: // Note: off_t is defined as int64 because:
// - musl (used on Linux) always defines it as int64 // - musl (used on Linux) always defines it as int64

47
src/runtime/string.go

@ -233,3 +233,50 @@ func isContinuation(b byte) bool {
// Continuation bytes have their topmost bits set to 0b10. // Continuation bytes have their topmost bits set to 0b10.
return b&0xc0 == 0x80 return b&0xc0 == 0x80
} }
// Functions used in CGo.
// Convert a Go string to a C string.
func cgo_CString(s _string) unsafe.Pointer {
buf := malloc(s.length + 1)
memcpy(buf, unsafe.Pointer(s.ptr), s.length)
*(*byte)(unsafe.Pointer(uintptr(buf) + s.length)) = 0 // trailing 0 byte
return buf
}
// Convert a C string to a Go string.
func cgo_GoString(cstr unsafe.Pointer) _string {
if cstr == nil {
return _string{}
}
return makeGoString(cstr, strlen(cstr))
}
// Convert a C data buffer to a Go string (that possibly contains 0 bytes).
func cgo_GoStringN(cstr unsafe.Pointer, length uintptr) _string {
return makeGoString(cstr, length)
}
// Make a Go string given a source buffer and a length.
func makeGoString(cstr unsafe.Pointer, length uintptr) _string {
s := _string{
length: length,
}
if s.length != 0 {
buf := make([]byte, s.length)
s.ptr = &buf[0]
memcpy(unsafe.Pointer(s.ptr), cstr, s.length)
}
return s
}
// Convert a C data buffer to a Go byte slice.
func cgo_GoBytes(ptr unsafe.Pointer, length uintptr) []byte {
// Note: don't return nil if length is 0, to match the behavior of C.GoBytes
// of upstream Go.
buf := make([]byte, length)
if length != 0 {
memcpy(unsafe.Pointer(&buf[0]), ptr, uintptr(length))
}
return buf
}

2
testdata/cgo/main.c

@ -24,6 +24,8 @@ int cflagsConstant = SOME_CONSTANT;
int smallEnumWidth = sizeof(option2_t); int smallEnumWidth = sizeof(option2_t);
char globalChars[] = {2, 0, 4, 8};
int fortytwo() { int fortytwo() {
return 42; return 42;
} }

15
testdata/cgo/main.go

@ -135,6 +135,21 @@ func main() {
// void arraydecay(int *buf1, int *buf2[8], int *buf3[7][2]); // void arraydecay(int *buf1, int *buf2[8], int *buf3[7][2]);
C.arraydecay((*C.int)(nil), (*[8]C.int)(nil), (*[7][2]C.int)(nil)) C.arraydecay((*C.int)(nil), (*[8]C.int)(nil), (*[7][2]C.int)(nil))
// Test CGo builtins like C.CString.
cstr := C.CString("string passed to C")
println("cstr length:", C.strlen(cstr))
gostr := C.GoString(cstr)
println("C.CString:", gostr)
charBuf := C.GoBytes(unsafe.Pointer(&C.globalChars[0]), 4)
println("C.charBuf:", charBuf[0], charBuf[1], charBuf[2], charBuf[3])
binaryString := C.GoStringN(&C.globalChars[0], 4)
println("C.CStringN:", len(binaryString), binaryString[0], binaryString[1], binaryString[2], binaryString[3])
// Test whether those builtins also work with zero length data.
println("C.GoString(nil):", C.GoString(nil))
println("len(C.GoStringN(nil, 0)):", len(C.GoStringN(nil, 0)))
println("len(C.GoBytes(nil, 0)):", len(C.GoBytes(nil, 0)))
// libc: test whether C functions work at all. // libc: test whether C functions work at all.
buf1 := []byte("foobar\x00") buf1 := []byte("foobar\x00")
buf2 := make([]byte, len(buf1)) buf2 := make([]byte, len(buf1))

2
testdata/cgo/main.h

@ -141,6 +141,8 @@ extern int smallEnumWidth;
extern int cflagsConstant; extern int cflagsConstant;
extern char globalChars[4];
// test duplicate definitions // test duplicate definitions
int add(int a, int b); int add(int a, int b);
extern int global; extern int global;

7
testdata/cgo/out.txt

@ -61,5 +61,12 @@ option 2A: 20
option 3A: 21 option 3A: 21
enum width matches: true enum width matches: true
CFLAGS value: 17 CFLAGS value: 17
cstr length: 18
C.CString: string passed to C
C.charBuf: 2 0 4 8
C.CStringN: 4 2 0 4 8
C.GoString(nil):
len(C.GoStringN(nil, 0)): 0
len(C.GoBytes(nil, 0)): 0
copied string: foobar copied string: foobar
line written using C puts line written using C puts

Loading…
Cancel
Save