Skip to content

Commit 3d92f43

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 3d92f43

File tree

13 files changed

+112
-9
lines changed

13 files changed

+112
-9
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: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ 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 "unsafe"
142+
import (
143+
"syscall"
144+
"unsafe"
145+
)
143146
144147
var _ unsafe.Pointer
145148
@@ -169,6 +172,13 @@ func __CBytes([]byte) unsafe.Pointer
169172
func CBytes(b []byte) unsafe.Pointer {
170173
return C.__CBytes(b)
171174
}
175+
176+
//go:linkname C.__get_errno_num runtime.cgo_errno
177+
func __get_errno_num() uintptr
178+
179+
func __get_errno() error {
180+
return syscall.Errno(C.__get_errno_num())
181+
}
172182
`
173183

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

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":

src/runtime/baremetal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,16 @@ func AdjustTimeOffset(offset int64) {
8686
// TODO: do this atomically?
8787
timeOffset += offset
8888
}
89+
90+
// Picolibc is not configured to define its own errno value, instead it calls
91+
// __errno_location.
92+
// TODO: a global works well enough for now (same as errno on Linux with
93+
// -scheduler=tasks), but this should ideally be a thread-local variable stored
94+
// in task.Task.
95+
// Especially when we add multicore support for microcontrollers.
96+
var errno int32
97+
98+
//export __errno_location
99+
func libc_errno_location() *int32 {
100+
return &errno
101+
}

src/runtime/os_darwin.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
151151
r1 = uintptr(result)
152152
if result == -1 {
153153
// Syscall returns -1 on failure.
154-
err = uintptr(*libc___error())
154+
err = uintptr(*libc_errno_location())
155155
}
156156
return
157157
}
@@ -161,7 +161,7 @@ func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
161161
r1 = call_syscallX(fn, a1, a2, a3)
162162
if int64(r1) == -1 {
163163
// Syscall returns -1 on failure.
164-
err = uintptr(*libc___error())
164+
err = uintptr(*libc_errno_location())
165165
}
166166
return
167167
}
@@ -171,7 +171,7 @@ func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
171171
r1 = call_syscallX(fn, a1, a2, a3)
172172
if r1 == 0 {
173173
// Syscall returns a pointer on success, or NULL on failure.
174-
err = uintptr(*libc___error())
174+
err = uintptr(*libc_errno_location())
175175
}
176176
return
177177
}
@@ -182,7 +182,7 @@ func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
182182
r1 = uintptr(result)
183183
if result == -1 {
184184
// Syscall returns -1 on failure.
185-
err = uintptr(*libc___error())
185+
err = uintptr(*libc_errno_location())
186186
}
187187
return
188188
}
@@ -192,7 +192,7 @@ func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
192192
r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6)
193193
if int64(r1) == -1 {
194194
// Syscall returns -1 on failure.
195-
err = uintptr(*libc___error())
195+
err = uintptr(*libc_errno_location())
196196
}
197197
return
198198
}
@@ -216,7 +216,7 @@ func libc_getpagesize() int32
216216
// }
217217
//
218218
//export __error
219-
func libc___error() *int32
219+
func libc_errno_location() *int32
220220

221221
//export tinygo_syscall
222222
func call_syscall(fn, a1, a2, a3 uintptr) int32

src/runtime/os_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,8 @@ func hardwareRand() (n uint64, ok bool) {
139139
//
140140
//export getrandom
141141
func libc_getrandom(buf unsafe.Pointer, buflen uintptr, flags uint32) uint32
142+
143+
// int *__errno_location(void);
144+
//
145+
//export __errno_location
146+
func libc_errno_location() *int32

src/runtime/os_windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,6 @@ func syscall_Getpagesize() int {
113113
_GetSystemInfo(unsafe.Pointer(&info))
114114
return int(info.dwpagesize)
115115
}
116+
117+
//export _errno
118+
func libc_errno_location() *int32

src/runtime/runtime.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ func write(fd uintptr, p unsafe.Pointer, n int32) int32 {
127127
func getAuxv() []uintptr {
128128
return nil
129129
}
130+
131+
// Called from cgo to obtain the errno value.
132+
func cgo_errno() uintptr {
133+
return uintptr(*libc_errno_location())
134+
}

src/runtime/runtime_wasm_js.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ func ticks() timeUnit
3636
func beforeExit() {
3737
__stdio_exit()
3838
}
39+
40+
// int *__errno_location(void);
41+
//
42+
//export __errno_location
43+
func libc_errno_location() *int32

testdata/cgo/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ double doSqrt(double x) {
7777
void printf_single_int(char *format, int arg) {
7878
printf(format, arg);
7979
}
80+
81+
int set_errno(int err) {
82+
errno = err;
83+
return -1;
84+
}

0 commit comments

Comments
 (0)