diff --git a/cgo/cgo.go b/cgo/cgo.go index 81c4a17d..cd0c29d8 100644 --- a/cgo/cgo.go +++ b/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 // 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 = ` import "unsafe" 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 @@ -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. 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. for _, f := range files { diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go index 1fb2c4d1..125605de 100644 --- a/cgo/testdata/basic.out.go +++ b/cgo/testdata/basic.out.go @@ -4,6 +4,26 @@ import "unsafe" 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.int32_t = int32 type C.int64_t = int64 diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index d213a672..e12cded4 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -4,6 +4,26 @@ import "unsafe" 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.foo = 3 diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index 212b37ed..8200a459 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -18,6 +18,26 @@ import "unsafe" 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 type C.int16_t = int16 diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 4eb70112..92077865 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -9,6 +9,26 @@ import "unsafe" 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.FOO_H = 1 diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index 97a9522b..53ec4f0b 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -4,6 +4,26 @@ import "unsafe" 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 func C.foo(a C.int, b C.int) C.int diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index 1864e33f..639d1f4f 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -4,6 +4,26 @@ import "unsafe" 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.optionA = 0 const C.optionB = 1 diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 5243d34e..d0617fde 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -42,6 +42,9 @@ func memzero(ptr unsafe.Pointer, size uintptr) //export strlen func strlen(ptr unsafe.Pointer) uintptr +//export malloc +func malloc(size uintptr) unsafe.Pointer + // Compare two same-size buffers for equality. func memequal(x, y unsafe.Pointer, n uintptr) bool { for i := uintptr(0); i < n; i++ { diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 23a41714..ae924026 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -13,9 +13,6 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int //export usleep 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); // Note: off_t is defined as int64 because: // - musl (used on Linux) always defines it as int64 diff --git a/src/runtime/string.go b/src/runtime/string.go index 72037d3d..a2b57fe1 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -233,3 +233,50 @@ func isContinuation(b byte) bool { // Continuation bytes have their topmost bits set to 0b10. 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 +} diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index d4083324..31b60704 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -24,6 +24,8 @@ int cflagsConstant = SOME_CONSTANT; int smallEnumWidth = sizeof(option2_t); +char globalChars[] = {2, 0, 4, 8}; + int fortytwo() { return 42; } diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index cc25152e..e8438919 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -135,6 +135,21 @@ func main() { // 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)) + // 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. buf1 := []byte("foobar\x00") buf2 := make([]byte, len(buf1)) diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 09e1b4f0..702dab0c 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -141,6 +141,8 @@ extern int smallEnumWidth; extern int cflagsConstant; +extern char globalChars[4]; + // test duplicate definitions int add(int a, int b); extern int global; diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index ca77594f..375ad1f6 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -61,5 +61,12 @@ option 2A: 20 option 3A: 21 enum width matches: true 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 line written using C puts