Skip to content

Commit 5e678b6

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 c0d873b commit 5e678b6

File tree

20 files changed

+179
-11
lines changed

20 files changed

+179
-11
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: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ typedef unsigned long long _Cgo_ulonglong;
139139
// These functions will be modified to get a "C." prefix, so the source below
140140
// doesn't reflect the final AST.
141141
const generatedGoFilePrefix = `
142+
import "syscall"
142143
import "unsafe"
143144
144145
var _ unsafe.Pointer
@@ -169,6 +170,13 @@ 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+
func __get_errno() error {
178+
return syscall.Errno(C.__get_errno_num())
179+
}
172180
`
173181

174182
// Process extracts `import "C"` statements from the AST, parses the comment
@@ -225,7 +233,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
225233
switch decl := decl.(type) {
226234
case *ast.FuncDecl:
227235
switch decl.Name.Name {
228-
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes":
236+
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno":
229237
// Adjust the name to have a "C." prefix so it is correctly
230238
// resolved.
231239
decl.Name.Name = "C." + decl.Name.Name
@@ -1279,6 +1287,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
12791287
// separate namespace (no _Cgo_ hacks like in gc).
12801288
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
12811289
switch node := cursor.Node().(type) {
1290+
case *ast.AssignStmt:
1291+
// An assign statement could be something like this:
1292+
//
1293+
// val, errno := C.some_func()
1294+
//
1295+
// Check whether it looks like that, and if so, read the errno value and
1296+
// return it as the second return value. The call will be transformed
1297+
// into something like this:
1298+
//
1299+
// val, errno := C.some_func(), C.__get_errno()
1300+
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
1301+
return true
1302+
}
1303+
rhs, ok := node.Rhs[0].(*ast.CallExpr)
1304+
if !ok {
1305+
return true
1306+
}
1307+
fun, ok := rhs.Fun.(*ast.SelectorExpr)
1308+
if !ok {
1309+
return true
1310+
}
1311+
x, ok := fun.X.(*ast.Ident)
1312+
if !ok {
1313+
return true
1314+
}
1315+
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
1316+
// Replace "C"."some_func" into "C.somefunc".
1317+
rhs.Fun = &ast.Ident{
1318+
NamePos: x.NamePos,
1319+
Name: f.getASTDeclName(fun.Sel.Name, found, true),
1320+
}
1321+
// Add the errno value as the second value in the statement.
1322+
node.Rhs = append(node.Rhs, &ast.CallExpr{
1323+
Fun: &ast.Ident{
1324+
NamePos: node.Lhs[1].End(),
1325+
Name: "C.__get_errno",
1326+
},
1327+
})
1328+
}
12821329
case *ast.CallExpr:
12831330
fun, ok := node.Fun.(*ast.SelectorExpr)
12841331
if !ok {

cgo/cgo_test.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestCGo(t *testing.T) {
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)