Skip to content

Commit 298ddc6

Browse files
committed
cgo: implement support for static functions
1 parent 9e8739b commit 298ddc6

File tree

10 files changed

+86
-18
lines changed

10 files changed

+86
-18
lines changed

cgo/cgo.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,23 @@ type cgoPackage struct {
3232
errors []error
3333
currentDir string // current working directory
3434
packageDir string // full path to the package to process
35+
importPath string
3536
fset *token.FileSet
3637
tokenFiles map[string]*token.File
3738
definedGlobally map[string]ast.Node
3839
anonDecls map[interface{}]string
3940
cflags []string // CFlags from #cgo lines
4041
ldflags []string // LDFlags from #cgo lines
4142
visitedFiles map[string][]byte
43+
cgoHeaders []string
4244
}
4345

4446
// cgoFile holds information only for a single Go file (with one or more
4547
// `import "C"` statements).
4648
type cgoFile struct {
4749
*cgoPackage
50+
file *ast.File
51+
index int
4852
defined map[string]ast.Node
4953
names map[string]clangCursor
5054
}
@@ -158,9 +162,10 @@ func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
158162
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
159163
// hashes of the accessed C header files. If there is one or more error, it
160164
// returns these in the []error slice but still modifies the AST.
161-
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
165+
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
162166
p := &cgoPackage{
163167
currentDir: dir,
168+
importPath: importPath,
164169
fset: fset,
165170
tokenFiles: map[string]*token.File{},
166171
definedGlobally: map[string]ast.Node{},
@@ -210,13 +215,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
210215
}
211216
}
212217
// Patch some types, for example *C.char in C.CString.
213-
cf := p.newCGoFile()
218+
cf := p.newCGoFile(nil, -1)
214219
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
215220
return cf.walker(cursor, nil)
216221
}, nil)
217222

218223
// Find `import "C"` C fragments in the file.
219-
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
224+
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
220225
for i, f := range files {
221226
var cgoHeader string
222227
for i := 0; i < len(f.Decls); i++ {
@@ -275,7 +280,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
275280
cgoHeader += fragment
276281
}
277282

278-
cgoHeaders[i] = cgoHeader
283+
p.cgoHeaders[i] = cgoHeader
279284
}
280285

281286
// Define CFlags that will be used while parsing the package.
@@ -289,7 +294,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
289294
}
290295

291296
// Retrieve types such as C.int, C.longlong, etc from C.
292-
p.newCGoFile().readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
297+
p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
293298
gen := &ast.GenDecl{
294299
TokPos: token.NoPos,
295300
Tok: token.TYPE,
@@ -303,8 +308,8 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
303308

304309
// Process CGo imports for each file.
305310
for i, f := range files {
306-
cf := p.newCGoFile()
307-
cf.readNames(cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
311+
cf := p.newCGoFile(f, i)
312+
cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
308313
for _, name := range builtinAliases {
309314
// Names such as C.int should not be obtained from C.
310315
// This works around an issue in picolibc that has `#define int`
@@ -320,12 +325,14 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
320325
// Print the newly generated in-memory AST, for debugging.
321326
//ast.Print(fset, p.generated)
322327

323-
return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
328+
return p.generated, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
324329
}
325330

326-
func (p *cgoPackage) newCGoFile() *cgoFile {
331+
func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile {
327332
return &cgoFile{
328333
cgoPackage: p,
334+
file: file,
335+
index: index,
329336
defined: make(map[string]ast.Node),
330337
names: make(map[string]clangCursor),
331338
}
@@ -1117,8 +1124,11 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
11171124
return alias
11181125
}
11191126
node := f.getASTDeclNode(name, found, iscall)
1120-
if _, ok := node.(*ast.FuncDecl); ok && !iscall {
1121-
return "C." + name + "$funcaddr"
1127+
if node, ok := node.(*ast.FuncDecl); ok {
1128+
if !iscall {
1129+
return node.Name.Name + "$funcaddr"
1130+
}
1131+
return node.Name.Name
11221132
}
11231133
return "C." + name
11241134
}
@@ -1142,7 +1152,7 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11421152
// Original cgo reports an error like
11431153
// cgo: inconsistent definitions for C.myint
11441154
// which is far less helpful.
1145-
f.addError(getPos(node), "defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
1155+
f.addError(getPos(node), name+" defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
11461156
}
11471157
f.defined[name] = node
11481158
return node
@@ -1152,17 +1162,33 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11521162
f.defined[name] = nil
11531163
node, elaboratedType := f.createASTNode(name, found)
11541164
f.defined[name] = node
1155-
f.definedGlobally[name] = node
11561165
switch node := node.(type) {
11571166
case *ast.FuncDecl:
1167+
if strings.HasPrefix(node.Doc.List[0].Text, "//export _Cgo_static_") {
1168+
// Static function. Only accessible in the current Go file.
1169+
globalName := strings.TrimPrefix(node.Doc.List[0].Text, "//export ")
1170+
aliasDeclaration := fmt.Sprintf(`
1171+
#ifdef __APPLE__
1172+
__asm__(".globl __%s");
1173+
__asm__(".set __%s, _%s");
1174+
extern __typeof(%s) %s;
1175+
#else
1176+
extern __typeof(%s) %s __attribute__((alias(%#v)));
1177+
#endif
1178+
`, globalName, globalName, name, name, globalName, name, globalName, name)
1179+
f.cgoHeaders[f.index] += "\n\n" + aliasDeclaration
1180+
} else {
1181+
// Regular (non-static) function.
1182+
f.definedGlobally[name] = node
1183+
}
11581184
f.generated.Decls = append(f.generated.Decls, node)
11591185
// Also add a declaration like the following:
11601186
// var C.foo$funcaddr unsafe.Pointer
11611187
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
11621188
Tok: token.VAR,
11631189
Specs: []ast.Spec{
11641190
&ast.ValueSpec{
1165-
Names: []*ast.Ident{{Name: "C." + name + "$funcaddr"}},
1191+
Names: []*ast.Ident{{Name: node.Name.Name + "$funcaddr"}},
11661192
Type: &ast.SelectorExpr{
11671193
X: &ast.Ident{Name: "unsafe"},
11681194
Sel: &ast.Ident{Name: "Pointer"},
@@ -1171,8 +1197,10 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11711197
},
11721198
})
11731199
case *ast.GenDecl:
1200+
f.definedGlobally[name] = node
11741201
f.generated.Decls = append(f.generated.Decls, node)
11751202
case *ast.TypeSpec:
1203+
f.definedGlobally[name] = node
11761204
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
11771205
Tok: token.TYPE,
11781206
Specs: []ast.Spec{node},

cgo/cgo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestCGo(t *testing.T) {
4848
}
4949

5050
// Process the AST with CGo.
51-
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags, "")
51+
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "")
5252

5353
// Check the AST for type errors.
5454
var typecheckErrors []error

cgo/libclang.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package cgo
44
// modification. It does not touch the AST itself.
55

66
import (
7+
"crypto/sha256"
78
"crypto/sha512"
9+
"encoding/hex"
810
"fmt"
911
"go/ast"
1012
"go/scanner"
@@ -50,6 +52,7 @@ CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
5052
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
5153
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
5254
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
55+
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c);
5356
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
5457
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
5558
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
@@ -200,19 +203,32 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaborat
200203
Kind: ast.Fun,
201204
Name: "C." + name,
202205
}
206+
exportName := name
207+
localName := name
208+
if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
209+
// A static function is assigned a globally unique symbol name based
210+
// on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
211+
// has a different Go name in the form of C.foo!symbols.go instead
212+
// of just C.foo.
213+
path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name())
214+
staticIDBuf := sha256.Sum256([]byte(path))
215+
staticID := hex.EncodeToString(staticIDBuf[:10])
216+
exportName = "_Cgo_static_" + staticID + "_" + name
217+
localName = name + "!" + filepath.Base(path)
218+
}
203219
args := make([]*ast.Field, numArgs)
204220
decl := &ast.FuncDecl{
205221
Doc: &ast.CommentGroup{
206222
List: []*ast.Comment{
207223
{
208224
Slash: pos - 1,
209-
Text: "//export " + name,
225+
Text: "//export " + exportName,
210226
},
211227
},
212228
},
213229
Name: &ast.Ident{
214230
NamePos: pos,
215-
Name: "C." + name,
231+
Name: "C." + localName,
216232
Obj: obj,
217233
},
218234
Type: &ast.FuncType{

cgo/libclang_stubs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
4545
return clang_Cursor_getArgument(c, i);
4646
}
4747

48+
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) {
49+
return clang_Cursor_getStorageClass(c);
50+
}
51+
4852
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
4953
return clang_getCursorLocation(c);
5054
}

cgo/testdata/symbols.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package main
55
int foo(int a, int b);
66
void variadic0();
77
void variadic2(int x, int y, ...);
8+
static void staticfunc(int x);
89
910
// Global variable signatures.
1011
extern int someValue;
@@ -16,6 +17,7 @@ func accessFunctions() {
1617
C.foo(3, 4)
1718
C.variadic0()
1819
C.variadic2(3, 5)
20+
C.staticfunc(3)
1921
}
2022

2123
func accessGlobals() {

cgo/testdata/symbols.out.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,10 @@ func C.variadic2(x C.int, y C.int)
5555

5656
var C.variadic2$funcaddr unsafe.Pointer
5757

58+
//export _Cgo_static_173c95a79b6df1980521_staticfunc
59+
func C.staticfunc!symbols.go(x C.int)
60+
61+
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
62+
5863
//go:extern someValue
5964
var C.someValue C.int

loader/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
440440
var initialCFlags []string
441441
initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
442442
initialCFlags = append(initialCFlags, "-I"+p.Dir)
443-
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, initialCFlags, p.program.clangHeaders)
443+
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.clangHeaders)
444444
p.CFlags = append(initialCFlags, cflags...)
445445
p.CGoHeaders = headerCode
446446
for path, hash := range accessedFiles {

testdata/cgo/extra.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,12 @@ package main
33
// Make sure CGo supports multiple files.
44

55
// int fortytwo(void);
6+
// static float headerfunc_static(float a) { return a - 1; }
67
import "C"
8+
9+
func headerfunc_2() {
10+
// Call headerfunc_static that is different from the headerfunc_static in
11+
// the main.go file.
12+
// The upstream CGo implementation does not handle this case correctly.
13+
println("static headerfunc 2:", C.headerfunc_static(5))
14+
}

testdata/cgo/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ int mul(int, int);
1212
import "C"
1313

1414
// int headerfunc(int a) { return a + 1; }
15+
// static int headerfunc_static(int a) { return a - 1; }
1516
import "C"
1617

1718
import "unsafe"
@@ -47,6 +48,8 @@ func main() {
4748

4849
// functions in the header C snippet
4950
println("headerfunc:", C.headerfunc(5))
51+
println("static headerfunc:", C.headerfunc_static(5))
52+
headerfunc_2()
5053

5154
// equivalent types
5255
var goInt8 int8 = 5

testdata/cgo/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ callback 2: 600
1616
variadic0: 1
1717
variadic2: 15
1818
headerfunc: 6
19+
static headerfunc: 4
20+
static headerfunc 2: +4.000000e+000
1921
bool: true true
2022
float: +3.100000e+000
2123
double: +3.200000e+000

0 commit comments

Comments
 (0)