aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2024-11-06 14:57:56 +0100
committerAyke van Laethem <[email protected]>2024-11-19 07:54:06 +0100
commitc6c9e0a2a16d6d5c4b781161696986848bb46144 (patch)
treee993b43b87bc87c2434e24f9fd52568a500f3723
parent0087d4c6bf13a1bac648b0e61459d717bae87b02 (diff)
downloadtinygo-cgo-errno-return.tar.gz
tinygo-cgo-errno-return.zip
cgo: support errno value as second return parametercgo-errno-return
Making this work on all targets was interesting but there's now a test in place to make sure this works on all targets that have the CGo test enabled (which is almost all targets).
-rw-r--r--builder/picolibc.go1
-rw-r--r--cgo/cgo.go121
-rw-r--r--cgo/cgo_test.go27
-rw-r--r--cgo/testdata/basic.out.go8
-rw-r--r--cgo/testdata/const.out.go8
-rw-r--r--cgo/testdata/errors.out.go8
-rw-r--r--cgo/testdata/flags.out.go8
-rw-r--r--cgo/testdata/symbols.out.go8
-rw-r--r--cgo/testdata/types.out.go8
-rw-r--r--compileopts/config.go2
-rw-r--r--loader/loader.go2
-rw-r--r--src/runtime/baremetal.go13
-rw-r--r--src/runtime/os_darwin.go12
-rw-r--r--src/runtime/os_windows.go3
-rw-r--r--src/runtime/runtime.go5
-rw-r--r--src/runtime/runtime_nintendoswitch.go6
-rw-r--r--src/runtime/runtime_tinygowasm.go5
-rw-r--r--src/runtime/runtime_tinygowasm_unknown.go6
-rw-r--r--src/runtime/runtime_tinygowasmp2.go6
-rw-r--r--testdata/cgo/main.c5
-rw-r--r--testdata/cgo/main.go12
-rw-r--r--testdata/cgo/main.h3
-rw-r--r--testdata/cgo/out.txt2
23 files changed, 263 insertions, 16 deletions
diff --git a/builder/picolibc.go b/builder/picolibc.go
index d33f31c09..9026b99ee 100644
--- a/builder/picolibc.go
+++ b/builder/picolibc.go
@@ -34,6 +34,7 @@ var libPicolibc = Library{
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
+ "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
"-nostdlibinc",
"-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio",
diff --git a/cgo/cgo.go b/cgo/cgo.go
index a90b30753..0ed26e3fb 100644
--- a/cgo/cgo.go
+++ b/cgo/cgo.go
@@ -138,7 +138,8 @@ typedef unsigned long long _Cgo_ulonglong;
// first.
// These functions will be modified to get a "C." prefix, so the source below
// doesn't reflect the final AST.
-const generatedGoFilePrefix = `
+const generatedGoFilePrefixBase = `
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -169,6 +170,74 @@ func __CBytes([]byte) unsafe.Pointer
func CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func __get_errno_num() uintptr
+`
+
+const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
+func __get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+`
+
+// Windows uses fake errno values in the syscall package.
+// See for example: https://github.com/golang/go/issues/23468
+// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
+// syscall package is the standard library one we can't change it, but we can
+// map the errno values to match the values in the syscall package.
+// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
+const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
+var __errno_mapping = [...]syscall.Errno{
+ 1: syscall.EPERM,
+ 2: syscall.ENOENT,
+ 3: syscall.ESRCH,
+ 4: syscall.EINTR,
+ 5: syscall.EIO,
+ 6: syscall.ENXIO,
+ 7: syscall.E2BIG,
+ 8: syscall.ENOEXEC,
+ 9: syscall.EBADF,
+ 10: syscall.ECHILD,
+ 11: syscall.EAGAIN,
+ 12: syscall.ENOMEM,
+ 13: syscall.EACCES,
+ 14: syscall.EFAULT,
+ 16: syscall.EBUSY,
+ 17: syscall.EEXIST,
+ 18: syscall.EXDEV,
+ 19: syscall.ENODEV,
+ 20: syscall.ENOTDIR,
+ 21: syscall.EISDIR,
+ 22: syscall.EINVAL,
+ 23: syscall.ENFILE,
+ 24: syscall.EMFILE,
+ 25: syscall.ENOTTY,
+ 27: syscall.EFBIG,
+ 28: syscall.ENOSPC,
+ 29: syscall.ESPIPE,
+ 30: syscall.EROFS,
+ 31: syscall.EMLINK,
+ 32: syscall.EPIPE,
+ 33: syscall.EDOM,
+ 34: syscall.ERANGE,
+ 36: syscall.EDEADLK,
+ 38: syscall.ENAMETOOLONG,
+ 39: syscall.ENOLCK,
+ 40: syscall.ENOSYS,
+ 41: syscall.ENOTEMPTY,
+ 42: syscall.EILSEQ,
+}
+
+func __get_errno() error {
+ num := C.__get_errno_num()
+ if num < uintptr(len(__errno_mapping)) {
+ if mapped := __errno_mapping[num]; mapped != 0 {
+ return mapped
+ }
+ }
+ return syscall.Errno(num)
+}
`
// Process extracts `import "C"` statements from the AST, parses the comment
@@ -178,7 +247,7 @@ func CBytes(b []byte) unsafe.Pointer {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
-func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
+func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir,
@@ -210,7 +279,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Construct a new in-memory AST for CGo declarations of this package.
// The first part is written as Go code that is then parsed, but more code
// is added later to the AST to declare functions, globals, etc.
- goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
+ goCode := "package " + files[0].Name.Name + "\n\n"
+ if goos == "windows" {
+ goCode += generatedGoFilePrefixWindows
+ } else {
+ goCode += generatedGoFilePrefixOther
+ }
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
if err != nil {
// This is always a bug in the cgo package.
@@ -225,7 +299,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
- case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes":
+ case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
@@ -1279,6 +1353,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// separate namespace (no _Cgo_ hacks like in gc).
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
switch node := cursor.Node().(type) {
+ case *ast.AssignStmt:
+ // An assign statement could be something like this:
+ //
+ // val, errno := C.some_func()
+ //
+ // Check whether it looks like that, and if so, read the errno value and
+ // return it as the second return value. The call will be transformed
+ // into something like this:
+ //
+ // val, errno := C.some_func(), C.__get_errno()
+ if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
+ return true
+ }
+ rhs, ok := node.Rhs[0].(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ fun, ok := rhs.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ x, ok := fun.X.(*ast.Ident)
+ if !ok {
+ return true
+ }
+ if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
+ // Replace "C"."some_func" into "C.somefunc".
+ rhs.Fun = &ast.Ident{
+ NamePos: x.NamePos,
+ Name: f.getASTDeclName(fun.Sel.Name, found, true),
+ }
+ // Add the errno value as the second value in the statement.
+ node.Rhs = append(node.Rhs, &ast.CallExpr{
+ Fun: &ast.Ident{
+ NamePos: node.Lhs[1].End(),
+ Name: "C.__get_errno",
+ },
+ })
+ }
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go
index dc79b21d5..f852c7f5f 100644
--- a/cgo/cgo_test.go
+++ b/cgo/cgo_test.go
@@ -56,7 +56,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
- cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
+ cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
// Check the AST for type errors.
var typecheckErrors []error
@@ -64,7 +64,7 @@ func TestCGo(t *testing.T) {
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
- Importer: simpleImporter{},
+ Importer: newSimpleImporter(),
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
@@ -202,14 +202,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
}
// simpleImporter implements the types.Importer interface, but only allows
-// importing the unsafe package.
+// importing the syscall and unsafe packages.
type simpleImporter struct {
+ syscallPkg *types.Package
+}
+
+func newSimpleImporter() *simpleImporter {
+ i := &simpleImporter{}
+
+ // Implement a dummy syscall package with the Errno type.
+ i.syscallPkg = types.NewPackage("syscall", "syscall")
+ obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
+ named := types.NewNamed(obj, nil, nil)
+ i.syscallPkg.Scope().Insert(obj)
+ named.SetUnderlying(types.Typ[types.Uintptr])
+ sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
+ named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
+ i.syscallPkg.MarkComplete()
+
+ return i
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
-func (i simpleImporter) Import(path string) (*types.Package, error) {
+func (i *simpleImporter) Import(path string) (*types.Package, error) {
switch path {
+ case "syscall":
+ return i.syscallPkg, nil
case "unsafe":
return types.Unsafe, nil
default:
diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go
index 6c2623980..7348ee379 100644
--- a/cgo/testdata/basic.out.go
+++ b/cgo/testdata/basic.out.go
@@ -1,5 +1,6 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go
index e7ee15380..21705afc4 100644
--- a/cgo/testdata/const.out.go
+++ b/cgo/testdata/const.out.go
@@ -1,5 +1,6 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go
index baadba68d..cbac4fceb 100644
--- a/cgo/testdata/errors.out.go
+++ b/cgo/testdata/errors.out.go
@@ -24,6 +24,7 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -55,6 +56,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go
index 83ca60420..866241229 100644
--- a/cgo/testdata/flags.out.go
+++ b/cgo/testdata/flags.out.go
@@ -5,6 +5,7 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -36,6 +37,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go
index 569cb65f6..b49150e60 100644
--- a/cgo/testdata/symbols.out.go
+++ b/cgo/testdata/symbols.out.go
@@ -1,5 +1,6 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go
index e5382ec80..b3fe414b0 100644
--- a/cgo/testdata/types.out.go
+++ b/cgo/testdata/types.out.go
@@ -1,5 +1,6 @@
package main
+import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
+//go:linkname C.__get_errno_num runtime.cgo_errno
+func C.__get_errno_num() uintptr
+
+func C.__get_errno() error {
+ return syscall.Errno(C.__get_errno_num())
+}
+
type (
C.char uint8
C.schar int8
diff --git a/compileopts/config.go b/compileopts/config.go
index 76215b181..ee5c34537 100644
--- a/compileopts/config.go
+++ b/compileopts/config.go
@@ -331,6 +331,7 @@ func (c *Config) CFlags(libclang bool) []string {
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
+ "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
)
case "musl":
root := goenv.Get("TINYGOROOT")
@@ -340,6 +341,7 @@ func (c *Config) CFlags(libclang bool) []string {
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
+ "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
diff --git a/loader/loader.go b/loader/loader.go
index f3ffa8614..e935a9de3 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -485,7 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
initialCFlags = append(initialCFlags, "-I"+p.Dir)
- generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags)
+ generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
p.CFlags = append(initialCFlags, cflags...)
p.CGoHeaders = headerCode
for path, hash := range accessedFiles {
diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go
index 173d0db25..aecb18972 100644
--- a/src/runtime/baremetal.go
+++ b/src/runtime/baremetal.go
@@ -86,3 +86,16 @@ func AdjustTimeOffset(offset int64) {
// TODO: do this atomically?
timeOffset += offset
}
+
+// Picolibc is not configured to define its own errno value, instead it calls
+// __errno_location.
+// TODO: a global works well enough for now (same as errno on Linux with
+// -scheduler=tasks), but this should ideally be a thread-local variable stored
+// in task.Task.
+// Especially when we add multicore support for microcontrollers.
+var errno int32
+
+//export __errno_location
+func libc_errno_location() *int32 {
+ return &errno
+}
diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go
index 8338d0f18..e7f7b368f 100644
--- a/src/runtime/os_darwin.go
+++ b/src/runtime/os_darwin.go
@@ -151,7 +151,7 @@ func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
r1 = uintptr(result)
if result == -1 {
// Syscall returns -1 on failure.
- err = uintptr(*libc___error())
+ err = uintptr(*libc_errno_location())
}
return
}
@@ -161,7 +161,7 @@ func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
r1 = call_syscallX(fn, a1, a2, a3)
if int64(r1) == -1 {
// Syscall returns -1 on failure.
- err = uintptr(*libc___error())
+ err = uintptr(*libc_errno_location())
}
return
}
@@ -171,7 +171,7 @@ func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
r1 = call_syscallX(fn, a1, a2, a3)
if r1 == 0 {
// Syscall returns a pointer on success, or NULL on failure.
- err = uintptr(*libc___error())
+ err = uintptr(*libc_errno_location())
}
return
}
@@ -182,7 +182,7 @@ func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
r1 = uintptr(result)
if result == -1 {
// Syscall returns -1 on failure.
- err = uintptr(*libc___error())
+ err = uintptr(*libc_errno_location())
}
return
}
@@ -192,7 +192,7 @@ func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6)
if int64(r1) == -1 {
// Syscall returns -1 on failure.
- err = uintptr(*libc___error())
+ err = uintptr(*libc_errno_location())
}
return
}
@@ -216,7 +216,7 @@ func libc_getpagesize() int32
// }
//
//export __error
-func libc___error() *int32
+func libc_errno_location() *int32
//export tinygo_syscall
func call_syscall(fn, a1, a2, a3 uintptr) int32
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
index 3750d5194..a124e7ab1 100644
--- a/src/runtime/os_windows.go
+++ b/src/runtime/os_windows.go
@@ -113,3 +113,6 @@ func syscall_Getpagesize() int {
_GetSystemInfo(unsafe.Pointer(&info))
return int(info.dwpagesize)
}
+
+//export _errno
+func libc_errno_location() *int32
diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go
index bb937f044..99ca34f2c 100644
--- a/src/runtime/runtime.go
+++ b/src/runtime/runtime.go
@@ -127,3 +127,8 @@ func write(fd uintptr, p unsafe.Pointer, n int32) int32 {
func getAuxv() []uintptr {
return nil
}
+
+// Called from cgo to obtain the errno value.
+func cgo_errno() uintptr {
+ return uintptr(*libc_errno_location())
+}
diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go
index 7d67a8626..d2567b1cc 100644
--- a/src/runtime/runtime_nintendoswitch.go
+++ b/src/runtime/runtime_nintendoswitch.go
@@ -313,3 +313,9 @@ func hardwareRand() (n uint64, ok bool) {
// TODO: see whether there is a RNG and use it.
return 0, false
}
+
+func libc_errno_location() *int32 {
+ // CGo is unavailable, so this function should be unreachable.
+ runtimePanic("runtime: no cgo errno")
+ return nil
+}
diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go
index 7bc65e9c4..67367b479 100644
--- a/src/runtime/runtime_tinygowasm.go
+++ b/src/runtime/runtime_tinygowasm.go
@@ -112,3 +112,8 @@ func hardwareRand() (n uint64, ok bool) {
//
//export arc4random
func libc_arc4random() uint32
+
+// int *__errno_location(void);
+//
+//export __errno_location
+func libc_errno_location() *int32
diff --git a/src/runtime/runtime_tinygowasm_unknown.go b/src/runtime/runtime_tinygowasm_unknown.go
index e426f36ff..b67d70aea 100644
--- a/src/runtime/runtime_tinygowasm_unknown.go
+++ b/src/runtime/runtime_tinygowasm_unknown.go
@@ -51,3 +51,9 @@ func procUnpin() {
func hardwareRand() (n uint64, ok bool) {
return 0, false
}
+
+func libc_errno_location() *int32 {
+ // CGo is unavailable, so this function should be unreachable.
+ runtimePanic("runtime: no cgo errno")
+ return nil
+}
diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go
index 70b5a6d11..5565cb4ee 100644
--- a/src/runtime/runtime_tinygowasmp2.go
+++ b/src/runtime/runtime_tinygowasmp2.go
@@ -81,3 +81,9 @@ func procUnpin() {
func hardwareRand() (n uint64, ok bool) {
return random.GetRandomU64(), true
}
+
+func libc_errno_location() *int32 {
+ // CGo is unavailable, so this function should be unreachable.
+ runtimePanic("runtime: no cgo errno")
+ return nil
+}
diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c
index 4a5bd6b9c..94b338dda 100644
--- a/testdata/cgo/main.c
+++ b/testdata/cgo/main.c
@@ -77,3 +77,8 @@ double doSqrt(double x) {
void printf_single_int(char *format, int arg) {
printf(format, arg);
}
+
+int set_errno(int err) {
+ errno = err;
+ return -1;
+}
diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go
index 00e0ba01d..38d11386a 100644
--- a/testdata/cgo/main.go
+++ b/testdata/cgo/main.go
@@ -18,7 +18,10 @@ import "C"
// static int headerfunc_static(int a) { return a - 1; }
import "C"
-import "unsafe"
+import (
+ "syscall"
+ "unsafe"
+)
func main() {
println("fortytwo:", C.fortytwo())
@@ -168,6 +171,13 @@ func main() {
println("len(C.GoBytes(C.CBytes(nil),0)):", len(C.GoBytes(C.CBytes(nil), 0)))
println(`rountrip CBytes:`, C.GoString((*C.char)(C.CBytes([]byte("hello\000")))))
+ // Check that errno is returned from the second return value, and that it
+ // matches the errno value that was just set.
+ _, errno := C.set_errno(C.EINVAL)
+ println("EINVAL:", errno == syscall.EINVAL)
+ _, errno = C.set_errno(C.EAGAIN)
+ println("EAGAIN:", errno == syscall.EAGAIN)
+
// 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 e7c64ffc3..3942497f2 100644
--- a/testdata/cgo/main.h
+++ b/testdata/cgo/main.h
@@ -1,5 +1,6 @@
#include <stdbool.h>
#include <stdint.h>
+#include <errno.h>
typedef short myint;
typedef short unusedTypedef;
@@ -154,3 +155,5 @@ void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3);
double doSqrt(double);
void printf_single_int(char *format, int arg);
+
+int set_errno(int err);
diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt
index 3088d4cb4..1d63f5e82 100644
--- a/testdata/cgo/out.txt
+++ b/testdata/cgo/out.txt
@@ -75,6 +75,8 @@ len(C.GoStringN(nil, 0)): 0
len(C.GoBytes(nil, 0)): 0
len(C.GoBytes(C.CBytes(nil),0)): 0
rountrip CBytes: hello
+EINVAL: true
+EAGAIN: true
copied string: foobar
CGo sqrt(3): +1.732051e+000
C sqrt(3): +1.732051e+000