Skip to content

Commit 6593cf2

Browse files
committed
cgo: support errno value as second return parameter
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).
1 parent 548fba8 commit 6593cf2

23 files changed

+263
-16
lines changed

builder/picolibc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var libPicolibc = Library{
3434
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
3535
"-D__OBSOLETE_MATH_DOUBLE=0",
3636
"-D_WANT_IO_C99_FORMATS",
37+
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
3738
"-nostdlibinc",
3839
"-isystem", newlibDir + "/libc/include",
3940
"-I" + newlibDir + "/libc/tinystdio",

cgo/cgo.go

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ typedef unsigned long long _Cgo_ulonglong;
138138
// first.
139139
// These functions will be modified to get a "C." prefix, so the source below
140140
// doesn't reflect the final AST.
141-
const generatedGoFilePrefix = `
141+
const generatedGoFilePrefixBase = `
142+
import "syscall"
142143
import "unsafe"
143144
144145
var _ unsafe.Pointer
@@ -169,6 +170,74 @@ func __CBytes([]byte) unsafe.Pointer
169170
func CBytes(b []byte) unsafe.Pointer {
170171
return C.__CBytes(b)
171172
}
173+
174+
//go:linkname C.__get_errno_num runtime.cgo_errno
175+
func __get_errno_num() uintptr
176+
`
177+
178+
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
179+
func __get_errno() error {
180+
return syscall.Errno(C.__get_errno_num())
181+
}
182+
`
183+
184+
// Windows uses fake errno values in the syscall package.
185+
// See for example: https://github.com/golang/go/issues/23468
186+
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
187+
// syscall package is the standard library one we can't change it, but we can
188+
// map the errno values to match the values in the syscall package.
189+
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
190+
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
191+
var __errno_mapping = [...]syscall.Errno{
192+
1: syscall.EPERM,
193+
2: syscall.ENOENT,
194+
3: syscall.ESRCH,
195+
4: syscall.EINTR,
196+
5: syscall.EIO,
197+
6: syscall.ENXIO,
198+
7: syscall.E2BIG,
199+
8: syscall.ENOEXEC,
200+
9: syscall.EBADF,
201+
10: syscall.ECHILD,
202+
11: syscall.EAGAIN,
203+
12: syscall.ENOMEM,
204+
13: syscall.EACCES,
205+
14: syscall.EFAULT,
206+
16: syscall.EBUSY,
207+
17: syscall.EEXIST,
208+
18: syscall.EXDEV,
209+
19: syscall.ENODEV,
210+
20: syscall.ENOTDIR,
211+
21: syscall.EISDIR,
212+
22: syscall.EINVAL,
213+
23: syscall.ENFILE,
214+
24: syscall.EMFILE,
215+
25: syscall.ENOTTY,
216+
27: syscall.EFBIG,
217+
28: syscall.ENOSPC,
218+
29: syscall.ESPIPE,
219+
30: syscall.EROFS,
220+
31: syscall.EMLINK,
221+
32: syscall.EPIPE,
222+
33: syscall.EDOM,
223+
34: syscall.ERANGE,
224+
36: syscall.EDEADLK,
225+
38: syscall.ENAMETOOLONG,
226+
39: syscall.ENOLCK,
227+
40: syscall.ENOSYS,
228+
41: syscall.ENOTEMPTY,
229+
42: syscall.EILSEQ,
230+
}
231+
232+
func __get_errno() error {
233+
num := C.__get_errno_num()
234+
if num < uintptr(len(__errno_mapping)) {
235+
if mapped := __errno_mapping[num]; mapped != 0 {
236+
return mapped
237+
}
238+
}
239+
return syscall.Errno(num)
240+
}
172241
`
173242

174243
// Process extracts `import "C"` statements from the AST, parses the comment
@@ -178,7 +247,7 @@ func CBytes(b []byte) unsafe.Pointer {
178247
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
179248
// hashes of the accessed C header files. If there is one or more error, it
180249
// returns these in the []error slice but still modifies the AST.
181-
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
250+
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
182251
p := &cgoPackage{
183252
packageName: files[0].Name.Name,
184253
currentDir: dir,
@@ -210,7 +279,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
210279
// Construct a new in-memory AST for CGo declarations of this package.
211280
// The first part is written as Go code that is then parsed, but more code
212281
// is added later to the AST to declare functions, globals, etc.
213-
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
282+
goCode := "package " + files[0].Name.Name + "\n\n"
283+
if goos == "windows" {
284+
goCode += generatedGoFilePrefixWindows
285+
} else {
286+
goCode += generatedGoFilePrefixOther
287+
}
214288
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
215289
if err != nil {
216290
// 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
225299
switch decl := decl.(type) {
226300
case *ast.FuncDecl:
227301
switch decl.Name.Name {
228-
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes":
302+
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping":
229303
// Adjust the name to have a "C." prefix so it is correctly
230304
// resolved.
231305
decl.Name.Name = "C." + decl.Name.Name
@@ -1279,6 +1353,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
12791353
// separate namespace (no _Cgo_ hacks like in gc).
12801354
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
12811355
switch node := cursor.Node().(type) {
1356+
case *ast.AssignStmt:
1357+
// An assign statement could be something like this:
1358+
//
1359+
// val, errno := C.some_func()
1360+
//
1361+
// Check whether it looks like that, and if so, read the errno value and
1362+
// return it as the second return value. The call will be transformed
1363+
// into something like this:
1364+
//
1365+
// val, errno := C.some_func(), C.__get_errno()
1366+
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
1367+
return true
1368+
}
1369+
rhs, ok := node.Rhs[0].(*ast.CallExpr)
1370+
if !ok {
1371+
return true
1372+
}
1373+
fun, ok := rhs.Fun.(*ast.SelectorExpr)
1374+
if !ok {
1375+
return true
1376+
}
1377+
x, ok := fun.X.(*ast.Ident)
1378+
if !ok {
1379+
return true
1380+
}
1381+
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
1382+
// Replace "C"."some_func" into "C.somefunc".
1383+
rhs.Fun = &ast.Ident{
1384+
NamePos: x.NamePos,
1385+
Name: f.getASTDeclName(fun.Sel.Name, found, true),
1386+
}
1387+
// Add the errno value as the second value in the statement.
1388+
node.Rhs = append(node.Rhs, &ast.CallExpr{
1389+
Fun: &ast.Ident{
1390+
NamePos: node.Lhs[1].End(),
1391+
Name: "C.__get_errno",
1392+
},
1393+
})
1394+
}
12821395
case *ast.CallExpr:
12831396
fun, ok := node.Fun.(*ast.SelectorExpr)
12841397
if !ok {

cgo/cgo_test.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ func TestCGo(t *testing.T) {
5656
}
5757

5858
// Process the AST with CGo.
59-
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
59+
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
6060

6161
// Check the AST for type errors.
6262
var typecheckErrors []error
6363
config := types.Config{
6464
Error: func(err error) {
6565
typecheckErrors = append(typecheckErrors, err)
6666
},
67-
Importer: simpleImporter{},
67+
Importer: newSimpleImporter(),
6868
Sizes: types.SizesFor("gccgo", "arm"),
6969
}
7070
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
@@ -202,14 +202,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
202202
}
203203

204204
// simpleImporter implements the types.Importer interface, but only allows
205-
// importing the unsafe package.
205+
// importing the syscall and unsafe packages.
206206
type simpleImporter struct {
207+
syscallPkg *types.Package
208+
}
209+
210+
func newSimpleImporter() *simpleImporter {
211+
i := &simpleImporter{}
212+
213+
// Implement a dummy syscall package with the Errno type.
214+
i.syscallPkg = types.NewPackage("syscall", "syscall")
215+
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
216+
named := types.NewNamed(obj, nil, nil)
217+
i.syscallPkg.Scope().Insert(obj)
218+
named.SetUnderlying(types.Typ[types.Uintptr])
219+
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
220+
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
221+
i.syscallPkg.MarkComplete()
222+
223+
return i
207224
}
208225

209226
// Import implements the Importer interface. For testing usage only: it only
210227
// supports importing the unsafe package.
211-
func (i simpleImporter) Import(path string) (*types.Package, error) {
228+
func (i *simpleImporter) Import(path string) (*types.Package, error) {
212229
switch path {
230+
case "syscall":
231+
return i.syscallPkg, nil
213232
case "unsafe":
214233
return types.Unsafe, nil
215234
default:

cgo/testdata/basic.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package main
22

3+
import "syscall"
34
import "unsafe"
45

56
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
3132
return C.__CBytes(b)
3233
}
3334

35+
//go:linkname C.__get_errno_num runtime.cgo_errno
36+
func C.__get_errno_num() uintptr
37+
38+
func C.__get_errno() error {
39+
return syscall.Errno(C.__get_errno_num())
40+
}
41+
3442
type (
3543
C.char uint8
3644
C.schar int8

cgo/testdata/const.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package main
22

3+
import "syscall"
34
import "unsafe"
45

56
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
3132
return C.__CBytes(b)
3233
}
3334

35+
//go:linkname C.__get_errno_num runtime.cgo_errno
36+
func C.__get_errno_num() uintptr
37+
38+
func C.__get_errno() error {
39+
return syscall.Errno(C.__get_errno_num())
40+
}
41+
3442
type (
3543
C.char uint8
3644
C.schar int8

cgo/testdata/errors.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package main
2626

27+
import "syscall"
2728
import "unsafe"
2829

2930
var _ unsafe.Pointer
@@ -55,6 +56,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
5556
return C.__CBytes(b)
5657
}
5758

59+
//go:linkname C.__get_errno_num runtime.cgo_errno
60+
func C.__get_errno_num() uintptr
61+
62+
func C.__get_errno() error {
63+
return syscall.Errno(C.__get_errno_num())
64+
}
65+
5866
type (
5967
C.char uint8
6068
C.schar int8

cgo/testdata/flags.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package main
77

8+
import "syscall"
89
import "unsafe"
910

1011
var _ unsafe.Pointer
@@ -36,6 +37,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
3637
return C.__CBytes(b)
3738
}
3839

40+
//go:linkname C.__get_errno_num runtime.cgo_errno
41+
func C.__get_errno_num() uintptr
42+
43+
func C.__get_errno() error {
44+
return syscall.Errno(C.__get_errno_num())
45+
}
46+
3947
type (
4048
C.char uint8
4149
C.schar int8

cgo/testdata/symbols.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package main
22

3+
import "syscall"
34
import "unsafe"
45

56
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
3132
return C.__CBytes(b)
3233
}
3334

35+
//go:linkname C.__get_errno_num runtime.cgo_errno
36+
func C.__get_errno_num() uintptr
37+
38+
func C.__get_errno() error {
39+
return syscall.Errno(C.__get_errno_num())
40+
}
41+
3442
type (
3543
C.char uint8
3644
C.schar int8

cgo/testdata/types.out.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package main
22

3+
import "syscall"
34
import "unsafe"
45

56
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
3132
return C.__CBytes(b)
3233
}
3334

35+
//go:linkname C.__get_errno_num runtime.cgo_errno
36+
func C.__get_errno_num() uintptr
37+
38+
func C.__get_errno() error {
39+
return syscall.Errno(C.__get_errno_num())
40+
}
41+
3442
type (
3543
C.char uint8
3644
C.schar int8

compileopts/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ func (c *Config) CFlags(libclang bool) []string {
331331
"-isystem", filepath.Join(path, "include"),
332332
"-isystem", filepath.Join(picolibcDir, "include"),
333333
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
334+
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
334335
)
335336
case "musl":
336337
root := goenv.Get("TINYGOROOT")
@@ -340,6 +341,7 @@ func (c *Config) CFlags(libclang bool) []string {
340341
"-nostdlibinc",
341342
"-isystem", filepath.Join(path, "include"),
342343
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
344+
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
343345
"-isystem", filepath.Join(root, "lib", "musl", "include"),
344346
)
345347
case "wasi-libc":

0 commit comments

Comments
 (0)