Skip to content

Commit b7a3fd8

Browse files
aykevldeadprogram
authored andcommitted
cgo: add support for #cgo noescape lines
Here is the proposal: golang/go#56378 They are documented here: https://pkg.go.dev/cmd/cgo@master#hdr-Optimizing_calls_of_C_code This would have been very useful to fix tinygo-org/bluetooth#176 in a nice way. That bug is now fixed in a different way using a wrapper function, but once this new noescape pragma gets included in TinyGo we could remove the workaround and use `#cgo noescape` instead.
1 parent fd625f7 commit b7a3fd8

File tree

6 files changed

+87
-8
lines changed

6 files changed

+87
-8
lines changed

cgo/cgo.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"go/scanner"
1919
"go/token"
2020
"path/filepath"
21+
"sort"
2122
"strconv"
2223
"strings"
2324

@@ -42,6 +43,7 @@ type cgoPackage struct {
4243
fset *token.FileSet
4344
tokenFiles map[string]*token.File
4445
definedGlobally map[string]ast.Node
46+
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
4547
anonDecls map[interface{}]string
4648
cflags []string // CFlags from #cgo lines
4749
ldflags []string // LDFlags from #cgo lines
@@ -80,6 +82,13 @@ type bitfieldInfo struct {
8082
endBit int64 // may be 0 meaning "until the end of the field"
8183
}
8284

85+
// Information about a #cgo noescape line in the source code.
86+
type noescapingFunc struct {
87+
name string
88+
pos token.Pos
89+
used bool // true if used somewhere in the source (for proper error reporting)
90+
}
91+
8392
// cgoAliases list type aliases between Go and C, for types that are equivalent
8493
// in both languages. See addTypeAliases.
8594
var cgoAliases = map[string]string{
@@ -255,6 +264,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
255264
fset: fset,
256265
tokenFiles: map[string]*token.File{},
257266
definedGlobally: map[string]ast.Node{},
267+
noescapingFuncs: map[string]*noescapingFunc{},
258268
anonDecls: map[interface{}]string{},
259269
visitedFiles: map[string][]byte{},
260270
}
@@ -418,6 +428,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
418428
})
419429
}
420430

431+
// Show an error when a #cgo noescape line isn't used in practice.
432+
// This matches upstream Go. I think the goal is to avoid issues with
433+
// misspelled function names, which seems very useful.
434+
var unusedNoescapeLines []*noescapingFunc
435+
for _, value := range p.noescapingFuncs {
436+
if !value.used {
437+
unusedNoescapeLines = append(unusedNoescapeLines, value)
438+
}
439+
}
440+
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
441+
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
442+
})
443+
for _, value := range unusedNoescapeLines {
444+
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
445+
}
446+
421447
// Print the newly generated in-memory AST, for debugging.
422448
//ast.Print(fset, p.generated)
423449

@@ -483,6 +509,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
483509
}
484510
text = text[:lineStart] + string(spaces) + text[lineEnd:]
485511

512+
allFields := strings.Fields(line[4:])
513+
switch allFields[0] {
514+
case "noescape":
515+
// The code indicates that pointer parameters will not be captured
516+
// by the called C function.
517+
if len(allFields) < 2 {
518+
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
519+
continue
520+
}
521+
if len(allFields) > 2 {
522+
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
523+
continue
524+
}
525+
name := allFields[1]
526+
p.noescapingFuncs[name] = &noescapingFunc{
527+
name: name,
528+
pos: pos,
529+
used: false,
530+
}
531+
continue
532+
case "nocallback":
533+
// We don't do anything special when calling a C function, so there
534+
// appears to be no optimization that we can do here.
535+
// Accept, but ignore the parameter for compatibility.
536+
continue
537+
}
538+
486539
// Get the text before the colon in the #cgo directive.
487540
colon := strings.IndexByte(line, ':')
488541
if colon < 0 {

cgo/libclang.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,18 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
256256
},
257257
},
258258
}
259+
var doc []string
259260
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
261+
doc = append(doc, "//go:variadic")
262+
}
263+
if _, ok := f.noescapingFuncs[name]; ok {
264+
doc = append(doc, "//go:noescape")
265+
f.noescapingFuncs[name].used = true
266+
}
267+
if len(doc) != 0 {
260268
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
261269
Slash: pos - 1,
262-
Text: "//go:variadic",
270+
Text: strings.Join(doc, "\n"),
263271
})
264272
}
265273
for i := 0; i < numArgs; i++ {

cgo/testdata/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ typedef struct {
1010
1111
typedef someType noType; // undefined type
1212
13+
// Some invalid noescape lines
14+
#cgo noescape
15+
#cgo noescape foo bar
16+
#cgo noescape unusedFunction
17+
1318
#define SOME_CONST_1 5) // invalid const syntax
1419
#define SOME_CONST_2 6) // const not used (so no error)
1520
#define SOME_CONST_3 1234 // const too large for byte

cgo/testdata/errors.out.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
// CGo errors:
2+
// testdata/errors.go:14:1: missing function name in #cgo noescape line
3+
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
24
// testdata/errors.go:4:2: warning: some warning
35
// testdata/errors.go:11:9: error: unknown type name 'someType'
4-
// testdata/errors.go:26:5: warning: another warning
5-
// testdata/errors.go:13:23: unexpected token ), expected end of expression
6-
// testdata/errors.go:21:26: unexpected token ), expected end of expression
7-
// testdata/errors.go:16:33: unexpected token ), expected end of expression
8-
// testdata/errors.go:17:34: unexpected token ), expected end of expression
6+
// testdata/errors.go:31:5: warning: another warning
7+
// testdata/errors.go:18:23: unexpected token ), expected end of expression
8+
// testdata/errors.go:26:26: unexpected token ), expected end of expression
9+
// testdata/errors.go:21:33: unexpected token ), expected end of expression
10+
// testdata/errors.go:22:34: unexpected token ), expected end of expression
911
// -: unexpected token INT, expected end of expression
10-
// testdata/errors.go:30:35: unexpected number of parameters: expected 2, got 3
11-
// testdata/errors.go:31:31: unexpected number of parameters: expected 2, got 1
12+
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
13+
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
14+
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
1215

1316
// Type checking errors after CGo processing:
1417
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)

cgo/testdata/symbols.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ static void staticfunc(int x);
99
1010
// Global variable signatures.
1111
extern int someValue;
12+
13+
void notEscapingFunction(int *a);
14+
15+
#cgo noescape notEscapingFunction
1216
*/
1317
import "C"
1418

@@ -18,6 +22,7 @@ func accessFunctions() {
1822
C.variadic0()
1923
C.variadic2(3, 5)
2024
C.staticfunc(3)
25+
C.notEscapingFunction(nil)
2126
}
2227

2328
func accessGlobals() {

cgo/testdata/symbols.out.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,10 @@ func C.staticfunc!symbols.go(x C.int)
7575

7676
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
7777

78+
//export notEscapingFunction
79+
//go:noescape
80+
func C.notEscapingFunction(a *C.int)
81+
82+
var C.notEscapingFunction$funcaddr unsafe.Pointer
7883
//go:extern someValue
7984
var C.someValue C.int

0 commit comments

Comments
 (0)