diff --git a/builder/picolibc.go b/builder/picolibc.go index d33f31c094..9026b99ee4 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 a90b307538..0ed26e3fb8 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 dc79b21d53..f852c7f5f9 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 6c2623980e..7348ee3791 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 e7ee15380a..21705afc4f 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 baadba68d2..cbac4fceb3 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 83ca604200..8662412296 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 569cb65f6a..b49150e601 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 e5382ec803..b3fe414b07 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 76215b1817..ee5c34537c 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 f3ffa86144..e935a9de3a 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 173d0db25e..aecb189720 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 8338d0f18b..e7f7b368fb 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_linux.go b/src/runtime/os_linux.go index df5870a2df..1cc5f720f0 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -139,3 +139,8 @@ func hardwareRand() (n uint64, ok bool) { // //export getrandom func libc_getrandom(buf unsafe.Pointer, buflen uintptr, flags uint32) uint32 + +// int *__errno_location(void); +// +//export __errno_location +func libc_errno_location() *int32 diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 3750d51940..a124e7ab14 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 bb937f0447..99ca34f2c8 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 7d67a86264..d2567b1ccf 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 f791ffacdf..03ed3e11b7 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -107,3 +107,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 39caa245a2..4b0bb5c1e9 100644 --- a/src/runtime/runtime_tinygowasm_unknown.go +++ b/src/runtime/runtime_tinygowasm_unknown.go @@ -47,3 +47,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 eb3c507fd2..d889610002 100644 --- a/src/runtime/runtime_tinygowasmp2.go +++ b/src/runtime/runtime_tinygowasmp2.go @@ -74,3 +74,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 4a5bd6b9c6..94b338dda2 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 00e0ba01da..38d11386a9 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 e7c64ffc3f..3942497f23 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -1,5 +1,6 @@ #include #include +#include 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 3088d4cb4d..1d63f5e82f 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