Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builder/picolibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
121 changes: 117 additions & 4 deletions cgo/cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 23 additions & 4 deletions cgo/cgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ 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
config := types.Config{
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)
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/basic.out.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/const.out.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/errors.out.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/flags.out.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/symbols.out.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cgo/testdata/types.out.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package main

import "syscall"
import "unsafe"

var _ unsafe.Pointer
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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":
Expand Down
2 changes: 1 addition & 1 deletion loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions src/runtime/baremetal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading
Loading