store pointer values in it. Zero out the memory in C before passing it
to Go.
+# Optimizing calls of C code
+
+When passing a Go pointer to a C function the compiler normally ensures
+that the Go object lives on the heap. If the C function does not keep
+a copy of the Go pointer, and never passes the Go pointer back to Go code,
+then this is unnecessary. The #cgo noescape directive may be used to tell
+the compiler that no Go pointers escape via the named C function.
+If the noescape directive is used and the C function does not handle the
+pointer safely, the program may crash or see memory corruption.
+
+For example:
+
+ // #cgo noescape cFunctionName
+
+When a Go function calls a C function, it prepares for the C function to
+call back to a Go function. the #cgo nocallback directive may be used to
+tell the compiler that these preparations are not necessary.
+If the nocallback directive is used and the C function does call back into
+Go code, the program will panic.
+
+For example:
+
+ // #cgo nocallback cFunctionName
+
# Special cases
A few special C types which would normally be represented by a pointer
return s
}
-// DiscardCgoDirectives processes the import C preamble, and discards
-// all #cgo CFLAGS and LDFLAGS directives, so they don't make their
-// way into _cgo_export.h.
-func (f *File) DiscardCgoDirectives() {
+// ProcessCgoDirectives processes the import C preamble:
+// 1. discards all #cgo CFLAGS, LDFLAGS, nocallback and noescape directives,
+// so they don't make their way into _cgo_export.h.
+// 2. parse the nocallback and noescape directives.
+func (f *File) ProcessCgoDirectives() {
linesIn := strings.Split(f.Preamble, "\n")
linesOut := make([]string, 0, len(linesIn))
+ f.NoCallbacks = make(map[string]bool)
+ f.NoEscapes = make(map[string]bool)
for _, line := range linesIn {
l := strings.TrimSpace(line)
if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4])) {
linesOut = append(linesOut, line)
} else {
linesOut = append(linesOut, "")
+
+ // #cgo (nocallback|noescape) <function name>
+ if fields := strings.Fields(l); len(fields) == 3 {
+ directive := fields[1]
+ funcName := fields[2]
+ if directive == "nocallback" {
+ f.NoCallbacks[funcName] = true
+ } else if directive == "noescape" {
+ f.NoEscapes[funcName] = true
+ }
+ }
}
}
f.Preamble = strings.Join(linesOut, "\n")
continue
}
- _, frag, ok := bytes.Cut(line, []byte("ERROR HERE: "))
- if !ok {
- continue
+ if _, frag, ok := bytes.Cut(line, []byte("ERROR HERE: ")); ok {
+ re, err := regexp.Compile(fmt.Sprintf(":%d:.*%s", i+1, frag))
+ if err != nil {
+ t.Errorf("Invalid regexp after `ERROR HERE: `: %#q", frag)
+ continue
+ }
+ errors = append(errors, re)
}
- re, err := regexp.Compile(fmt.Sprintf(":%d:.*%s", i+1, frag))
- if err != nil {
- t.Errorf("Invalid regexp after `ERROR HERE: `: %#q", frag)
- continue
+
+ if _, frag, ok := bytes.Cut(line, []byte("ERROR MESSAGE: ")); ok {
+ re, err := regexp.Compile(string(frag))
+ if err != nil {
+ t.Errorf("Invalid regexp after `ERROR MESSAGE: `: %#q", frag)
+ continue
+ }
+ errors = append(errors, re)
}
- errors = append(errors, re)
}
if len(errors) == 0 {
t.Fatalf("cannot find ERROR HERE")
t.Fatalf("succeeded unexpectedly")
}
}
+
+func TestNotMatchedCFunction(t *testing.T) {
+ file := "notmatchedcfunction.go"
+ check(t, file)
+}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+/*
+// ERROR MESSAGE: #cgo noescape noMatchedCFunction: no matched C function
+#cgo noescape noMatchedCFunction
+*/
+import "C"
+
+func main() {
+}
Preamble string // collected preamble for _cgo_export.h
typedefs map[string]bool // type names that appear in the types of the objects we're interested in
typedefList []typedefInfo
+ noCallbacks map[string]bool // C function names with #cgo nocallback directive
+ noEscapes map[string]bool // C function names with #cgo noescape directive
}
// A typedefInfo is an element on Package.typedefList: a typedef name
// A File collects information about a single Go input file.
type File struct {
- AST *ast.File // parsed AST
- Comments []*ast.CommentGroup // comments from file
- Package string // Package name
- Preamble string // C preamble (doc comment on import "C")
- Ref []*Ref // all references to C.xxx in AST
- Calls []*Call // all calls to C.xxx in AST
- ExpFunc []*ExpFunc // exported functions for this file
- Name map[string]*Name // map from Go name to Name
- NamePos map[*Name]token.Pos // map from Name to position of the first reference
- Edit *edit.Buffer
+ AST *ast.File // parsed AST
+ Comments []*ast.CommentGroup // comments from file
+ Package string // Package name
+ Preamble string // C preamble (doc comment on import "C")
+ Ref []*Ref // all references to C.xxx in AST
+ Calls []*Call // all calls to C.xxx in AST
+ ExpFunc []*ExpFunc // exported functions for this file
+ Name map[string]*Name // map from Go name to Name
+ NamePos map[*Name]token.Pos // map from Name to position of the first reference
+ NoCallbacks map[string]bool // C function names that with #cgo nocallback directive
+ NoEscapes map[string]bool // C function names that with #cgo noescape directive
+ Edit *edit.Buffer
}
func (f *File) offset(p token.Pos) int {
f := new(File)
f.Edit = edit.NewBuffer(b)
f.ParseGo(input, b)
- f.DiscardCgoDirectives()
+ f.ProcessCgoDirectives()
fs[i] = f
}
p.writeOutput(f, input)
}
}
+ cFunctions := make(map[string]bool)
+ for _, key := range nameKeys(p.Name) {
+ n := p.Name[key]
+ if n.FuncType != nil {
+ cFunctions[n.C] = true
+ }
+ }
+
+ for funcName := range p.noEscapes {
+ if _, found := cFunctions[funcName]; !found {
+ error_(token.NoPos, "#cgo noescape %s: no matched C function", funcName)
+ }
+ }
+
+ for funcName := range p.noCallbacks {
+ if _, found := cFunctions[funcName]; !found {
+ error_(token.NoPos, "#cgo nocallback %s: no matched C function", funcName)
+ }
+ }
if !*godefs {
p.writeDefs()
os.Setenv("LC_ALL", "C")
p := &Package{
- PtrSize: ptrSize,
- IntSize: intSize,
- CgoFlags: make(map[string][]string),
- Written: make(map[string]bool),
+ PtrSize: ptrSize,
+ IntSize: intSize,
+ CgoFlags: make(map[string][]string),
+ Written: make(map[string]bool),
+ noCallbacks: make(map[string]bool),
+ noEscapes: make(map[string]bool),
}
p.addToFlag("CFLAGS", args)
return p
}
}
+ // merge nocallback & noescape
+ for k, v := range f.NoCallbacks {
+ p.noCallbacks[k] = v
+ }
+ for k, v := range f.NoEscapes {
+ p.noEscapes[k] = v
+ }
+
if f.ExpFunc != nil {
p.ExpFunc = append(p.ExpFunc, f.ExpFunc...)
p.Preamble += "\n" + f.Preamble
fmt.Fprintf(fgo2, "//go:linkname _Cgo_use runtime.cgoUse\n")
fmt.Fprintf(fgo2, "func _Cgo_use(interface{})\n")
}
+ fmt.Fprintf(fgo2, "//go:linkname _Cgo_no_callback runtime.cgoNoCallback\n")
+ fmt.Fprintf(fgo2, "func _Cgo_no_callback(bool)\n")
typedefNames := make([]string, 0, len(typedef))
for name := range typedef {
arg = "uintptr(unsafe.Pointer(&r1))"
}
+ noCallback := p.noCallbacks[n.C]
+ if noCallback {
+ // disable cgocallback, will check it in runtime.
+ fmt.Fprintf(fgo2, "\t_Cgo_no_callback(true)\n")
+ }
+
prefix := ""
if n.AddError {
prefix = "errno := "
if n.AddError {
fmt.Fprintf(fgo2, "\tif errno != 0 { r2 = syscall.Errno(errno) }\n")
}
- fmt.Fprintf(fgo2, "\tif _Cgo_always_false {\n")
- if d.Type.Params != nil {
- for i := range d.Type.Params.List {
- fmt.Fprintf(fgo2, "\t\t_Cgo_use(p%d)\n", i)
+ if noCallback {
+ fmt.Fprintf(fgo2, "\t_Cgo_no_callback(false)\n")
+ }
+
+ // skip _Cgo_use when noescape exist,
+ // so that the compiler won't force to escape them to heap.
+ if !p.noEscapes[n.C] {
+ fmt.Fprintf(fgo2, "\tif _Cgo_always_false {\n")
+ if d.Type.Params != nil {
+ for i := range d.Type.Params.List {
+ fmt.Fprintf(fgo2, "\t\t_Cgo_use(p%d)\n", i)
+ }
}
+ fmt.Fprintf(fgo2, "\t}\n")
}
- fmt.Fprintf(fgo2, "\t}\n")
fmt.Fprintf(fgo2, "\treturn\n")
fmt.Fprintf(fgo2, "}\n")
}
func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
+//go:noescape
func _cgoCheckPointer(interface{}, interface{})
//go:linkname _cgoCheckResult runtime.cgoCheckResult
+//go:noescape
func _cgoCheckResult(interface{})
`
continue
}
+ // #cgo (nocallback|noescape) <function name>
+ if fields := strings.Fields(line); len(fields) == 3 && (fields[1] == "nocallback" || fields[1] == "noescape") {
+ continue
+ }
+
// Split at colon.
line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
if !ok {
continue
}
+ // #cgo (nocallback|noescape) <function name>
+ if fields := strings.Fields(line); len(fields) == 3 && (fields[1] == "nocallback" || fields[1] == "noescape") {
+ continue
+ }
+
// Split at colon.
line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
if !ok {
var cgoAlwaysFalse bool
var cgo_yield = &_cgo_yield
+
+func cgoNoCallback(v bool) {
+ g := getg()
+ if g.nocgocallback && v {
+ panic("runtime: unexpected setting cgoNoCallback")
+ }
+ g.nocgocallback = v
+}
osPreemptExtExit(gp.m)
+ if gp.nocgocallback {
+ panic("runtime: function marked with #cgo nocallback called back into Go")
+ }
+
cgocallbackg1(fn, frame, ctxt) // will call unlockOSThread
// At this point unlockOSThread has been called.
}
}
+func TestCgoNoCallback(t *testing.T) {
+ got := runTestProg(t, "testprogcgo", "CgoNoCallback")
+ want := "function marked with #cgo nocallback called back into Go"
+ if !strings.Contains(got, want) {
+ t.Fatalf("did not see %q in output:\n%s", want, got)
+ }
+}
+
+func TestCgoNoEscape(t *testing.T) {
+ got := runTestProg(t, "testprogcgo", "CgoNoEscape")
+ want := "OK\n"
+ if got != want {
+ t.Fatalf("want %s, got %s\n", want, got)
+ }
+}
+
func TestCgoTracebackGoroutineProfile(t *testing.T) {
output := runTestProg(t, "testprogcgo", "GoroutineProfile")
want := "OK\n"
parkingOnChan atomic.Bool
raceignore int8 // ignore race detection events
+ nocgocallback bool // whether disable callback from C
tracking bool // whether we're tracking this G for sched latency statistics
trackingSeq uint8 // used to decide whether to track this G
trackingStamp int64 // timestamp of when the G last started being tracked
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "_cgo_export.h"
+
+void runCShouldNotCallback() {
+ CallbackToGo();
+}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// #cgo nocallback annotations for a C function means it should not callback to Go.
+// But it do callback to go in this test, Go should crash here.
+
+/*
+#cgo nocallback runCShouldNotCallback
+
+extern void runCShouldNotCallback();
+*/
+import "C"
+
+import (
+ "fmt"
+)
+
+func init() {
+ register("CgoNoCallback", CgoNoCallback)
+}
+
+//export CallbackToGo
+func CallbackToGo() {
+}
+
+func CgoNoCallback() {
+ C.runCShouldNotCallback()
+ fmt.Println("OK")
+}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// #cgo noescape annotations for a C function means its arguments won't escape to heap.
+
+// We assume that there won't be 100 new allocated heap objects in other places,
+// i.e. runtime.ReadMemStats or other runtime background works.
+// So, the tests are:
+// 1. at least 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+// 2. less than 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+
+/*
+#cgo noescape runCWithNoEscape
+
+void runCWithNoEscape(void *p) {
+}
+void runCWithoutNoEscape(void *p) {
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime"
+ "runtime/debug"
+ "unsafe"
+)
+
+const num = 100
+
+func init() {
+ register("CgoNoEscape", CgoNoEscape)
+}
+
+//go:noinline
+func withNoEscape() {
+ var str string
+ C.runCWithNoEscape(unsafe.Pointer(&str))
+}
+
+//go:noinline
+func withoutNoEscape() {
+ var str string
+ C.runCWithoutNoEscape(unsafe.Pointer(&str))
+}
+
+func CgoNoEscape() {
+ // make GC stop to see the heap objects allocated
+ debug.SetGCPercent(-1)
+
+ var stats runtime.MemStats
+ runtime.ReadMemStats(&stats)
+ preHeapObjects := stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects := stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects >= num {
+ fmt.Printf("too many heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ runtime.ReadMemStats(&stats)
+ preHeapObjects = stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withoutNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects = stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects < num {
+ fmt.Printf("too few heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ fmt.Println("OK")
+}