Skip to content

Commit cd73bde

Browse files
committed
cgo: implement support for static functions
1 parent f337ac8 commit cd73bde

File tree

8 files changed

+73
-15
lines changed

8 files changed

+73
-15
lines changed

cgo/cgo.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ type cgoPackage struct {
3939
cflags []string // CFlags from #cgo lines
4040
ldflags []string // LDFlags from #cgo lines
4141
visitedFiles map[string][]byte
42+
cgoHeaders []string
4243
}
4344

4445
// cgoFile holds information only for a single Go file (with one or more
4546
// `import "C"` statements).
4647
type cgoFile struct {
4748
*cgoPackage
49+
file *ast.File
50+
index int
4851
defined map[string]ast.Node
4952
names map[string]clangCursor
5053
}
@@ -210,13 +213,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
210213
}
211214
}
212215
// Patch some types, for example *C.char in C.CString.
213-
cf := p.newCGoFile()
216+
cf := p.newCGoFile(nil, -1)
214217
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
215218
return cf.walker(cursor, nil)
216219
}, nil)
217220

218221
// Find `import "C"` C fragments in the file.
219-
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
222+
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
220223
for i, f := range files {
221224
var cgoHeader string
222225
for i := 0; i < len(f.Decls); i++ {
@@ -275,7 +278,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
275278
cgoHeader += fragment
276279
}
277280

278-
cgoHeaders[i] = cgoHeader
281+
p.cgoHeaders[i] = cgoHeader
279282
}
280283

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

291294
// Retrieve types such as C.int, C.longlong, etc from C.
292-
p.newCGoFile().readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
295+
p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
293296
gen := &ast.GenDecl{
294297
TokPos: token.NoPos,
295298
Tok: token.TYPE,
@@ -303,8 +306,8 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
303306

304307
// Process CGo imports for each file.
305308
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) {
309+
cf := p.newCGoFile(f, i)
310+
cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
308311
for _, name := range builtinAliases {
309312
// Names such as C.int should not be obtained from C.
310313
// This works around an issue in picolibc that has `#define int`
@@ -320,12 +323,14 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
320323
// Print the newly generated in-memory AST, for debugging.
321324
//ast.Print(fset, p.generated)
322325

323-
return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
326+
return p.generated, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
324327
}
325328

326-
func (p *cgoPackage) newCGoFile() *cgoFile {
329+
func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile {
327330
return &cgoFile{
328331
cgoPackage: p,
332+
file: file,
333+
index: index,
329334
defined: make(map[string]ast.Node),
330335
names: make(map[string]clangCursor),
331336
}
@@ -1117,8 +1122,11 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
11171122
return alias
11181123
}
11191124
node := f.getASTDeclNode(name, found, iscall)
1120-
if _, ok := node.(*ast.FuncDecl); ok && !iscall {
1121-
return "C." + name + "$funcaddr"
1125+
if node, ok := node.(*ast.FuncDecl); ok {
1126+
if !iscall {
1127+
return node.Name.Name + "$funcaddr"
1128+
}
1129+
return node.Name.Name
11221130
}
11231131
return "C." + name
11241132
}
@@ -1142,7 +1150,7 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11421150
// Original cgo reports an error like
11431151
// cgo: inconsistent definitions for C.myint
11441152
// which is far less helpful.
1145-
f.addError(getPos(node), "defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
1153+
f.addError(getPos(node), name+" defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
11461154
}
11471155
f.defined[name] = node
11481156
return node
@@ -1152,17 +1160,25 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11521160
f.defined[name] = nil
11531161
node, elaboratedType := f.createASTNode(name, found)
11541162
f.defined[name] = node
1155-
f.definedGlobally[name] = node
11561163
switch node := node.(type) {
11571164
case *ast.FuncDecl:
1165+
if strings.HasPrefix(node.Doc.List[0].Text, "//export _Cgo_static_") {
1166+
// Static function. Only accessible in the current Go file.
1167+
globalName := strings.TrimPrefix(node.Doc.List[0].Text, "//export ")
1168+
aliasDeclaration := fmt.Sprintf("void %s(void) __attribute__((alias(%#v)));", globalName, name)
1169+
f.cgoHeaders[f.index] += "\n\n" + aliasDeclaration
1170+
} else {
1171+
// Regular (non-static) function.
1172+
f.definedGlobally[name] = node
1173+
}
11581174
f.generated.Decls = append(f.generated.Decls, node)
11591175
// Also add a declaration like the following:
11601176
// var C.foo$funcaddr unsafe.Pointer
11611177
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
11621178
Tok: token.VAR,
11631179
Specs: []ast.Spec{
11641180
&ast.ValueSpec{
1165-
Names: []*ast.Ident{{Name: "C." + name + "$funcaddr"}},
1181+
Names: []*ast.Ident{{Name: node.Name.Name + "$funcaddr"}},
11661182
Type: &ast.SelectorExpr{
11671183
X: &ast.Ident{Name: "unsafe"},
11681184
Sel: &ast.Ident{Name: "Pointer"},
@@ -1171,8 +1187,10 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
11711187
},
11721188
})
11731189
case *ast.GenDecl:
1190+
f.definedGlobally[name] = node
11741191
f.generated.Decls = append(f.generated.Decls, node)
11751192
case *ast.TypeSpec:
1193+
f.definedGlobally[name] = node
11761194
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
11771195
Tok: token.TYPE,
11781196
Specs: []ast.Spec{node},

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"
@@ -47,6 +49,7 @@ CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
4749
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
4850
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
4951
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
52+
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c);
5053
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
5154
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
5255
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
@@ -187,19 +190,32 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaborat
187190
Kind: ast.Fun,
188191
Name: "C." + name,
189192
}
193+
exportName := name
194+
localName := name
195+
if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
196+
// A static function is assigned a globally unique symbol name based
197+
// on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
198+
// has a different Go name in the form of C.foo!symbols.go instead
199+
// of just C.foo.
200+
path := f.fset.File(f.file.Pos()).Name()
201+
staticIDBuf := sha256.Sum256([]byte(path))
202+
staticID := hex.EncodeToString(staticIDBuf[:10])
203+
exportName = "_Cgo_static_" + staticID + "_" + name
204+
localName = name + "!" + filepath.Base(path)
205+
}
190206
args := make([]*ast.Field, numArgs)
191207
decl := &ast.FuncDecl{
192208
Doc: &ast.CommentGroup{
193209
List: []*ast.Comment{
194210
{
195211
Slash: pos - 1,
196-
Text: "//export " + name,
212+
Text: "//export " + exportName,
197213
},
198214
},
199215
},
200216
Name: &ast.Ident{
201217
NamePos: pos,
202-
Name: "C." + name,
218+
Name: "C." + localName,
203219
Obj: obj,
204220
},
205221
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_2d09198adbf58f4f4655_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

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)