aboutsummaryrefslogtreecommitdiffhomepage
path: root/cgo
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2022-04-13 21:34:43 +0200
committerRon Evans <[email protected]>2022-09-16 14:05:17 +0200
commit5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8 (patch)
tree0fcff842574327f6615ed0909ed57efaf726dba5 /cgo
parent91104b2f276348e251a25e9e58e7faafe781358f (diff)
downloadtinygo-5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8.tar.gz
tinygo-5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8.zip
cgo: implement support for static functions
Diffstat (limited to 'cgo')
-rw-r--r--cgo/cgo.go73
-rw-r--r--cgo/cgo_test.go2
-rw-r--r--cgo/libclang.go37
-rw-r--r--cgo/libclang_stubs.c12
-rw-r--r--cgo/testdata/symbols.go2
-rw-r--r--cgo/testdata/symbols.out.go5
6 files changed, 110 insertions, 21 deletions
diff --git a/cgo/cgo.go b/cgo/cgo.go
index e1be8ab49..dfdd223ed 100644
--- a/cgo/cgo.go
+++ b/cgo/cgo.go
@@ -32,6 +32,7 @@ type cgoPackage struct {
errors []error
currentDir string // current working directory
packageDir string // full path to the package to process
+ importPath string
fset *token.FileSet
tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node
@@ -39,12 +40,15 @@ type cgoPackage struct {
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
+ cgoHeaders []string
}
// cgoFile holds information only for a single Go file (with one or more
// `import "C"` statements).
type cgoFile struct {
*cgoPackage
+ file *ast.File
+ index int
defined map[string]ast.Node
names map[string]clangCursor
}
@@ -158,9 +162,10 @@ func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
-func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
+func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
currentDir: dir,
+ importPath: importPath,
fset: fset,
tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{},
@@ -210,13 +215,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
}
// Patch some types, for example *C.char in C.CString.
- cf := p.newCGoFile()
+ cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
return cf.walker(cursor, nil)
}, nil)
// Find `import "C"` C fragments in the file.
- cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
+ p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files {
var cgoHeader string
for i := 0; i < len(f.Decls); i++ {
@@ -275,7 +280,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
cgoHeader += fragment
}
- cgoHeaders[i] = cgoHeader
+ p.cgoHeaders[i] = cgoHeader
}
// 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
}
// Retrieve types such as C.int, C.longlong, etc from C.
- p.newCGoFile().readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
+ p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
@@ -303,8 +308,8 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Process CGo imports for each file.
for i, f := range files {
- cf := p.newCGoFile()
- cf.readNames(cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
+ cf := p.newCGoFile(f, i)
+ cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
for _, name := range builtinAliases {
// Names such as C.int should not be obtained from C.
// 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
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
- return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
+ return p.generated, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
}
-func (p *cgoPackage) newCGoFile() *cgoFile {
+func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile {
return &cgoFile{
cgoPackage: p,
+ file: file,
+ index: index,
defined: make(map[string]ast.Node),
names: make(map[string]clangCursor),
}
@@ -1117,8 +1124,11 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
return alias
}
node := f.getASTDeclNode(name, found, iscall)
- if _, ok := node.(*ast.FuncDecl); ok && !iscall {
- return "C." + name + "$funcaddr"
+ if node, ok := node.(*ast.FuncDecl); ok {
+ if !iscall {
+ return node.Name.Name + "$funcaddr"
+ }
+ return node.Name.Name
}
return "C." + name
}
@@ -1142,7 +1152,7 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
// Original cgo reports an error like
// cgo: inconsistent definitions for C.myint
// which is far less helpful.
- f.addError(getPos(node), "defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
+ f.addError(getPos(node), name+" defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
}
f.defined[name] = node
return node
@@ -1150,11 +1160,39 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
// The declaration has no AST node. Create it now.
f.defined[name] = nil
- node, elaboratedType := f.createASTNode(name, found)
+ node, extra := f.createASTNode(name, found)
f.defined[name] = node
- f.definedGlobally[name] = node
switch node := node.(type) {
case *ast.FuncDecl:
+ if strings.HasPrefix(node.Doc.List[0].Text, "//export _Cgo_static_") {
+ // Static function. Only accessible in the current Go file.
+ globalName := strings.TrimPrefix(node.Doc.List[0].Text, "//export ")
+ // Make an alias. Normally this is done using the alias function
+ // attribute, but MacOS for some reason doesn't support this (even
+ // though the linker has support for aliases in the form of N_INDR).
+ // Therefore, create an actual function for MacOS.
+ var params []string
+ for _, param := range node.Type.Params.List {
+ params = append(params, param.Names[0].Name)
+ }
+ callInst := fmt.Sprintf("%s(%s);", name, strings.Join(params, ", "))
+ if node.Type.Results != nil {
+ callInst = "return " + callInst
+ }
+ aliasDeclaration := fmt.Sprintf(`
+#ifdef __APPLE__
+%s {
+ %s
+}
+#else
+extern __typeof(%s) %s __attribute__((alias(%#v)));
+#endif
+`, extra.(string), callInst, name, globalName, name)
+ f.cgoHeaders[f.index] += "\n\n" + aliasDeclaration
+ } else {
+ // Regular (non-static) function.
+ f.definedGlobally[name] = node
+ }
f.generated.Decls = append(f.generated.Decls, node)
// Also add a declaration like the following:
// var C.foo$funcaddr unsafe.Pointer
@@ -1162,7 +1200,7 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
- Names: []*ast.Ident{{Name: "C." + name + "$funcaddr"}},
+ Names: []*ast.Ident{{Name: node.Name.Name + "$funcaddr"}},
Type: &ast.SelectorExpr{
X: &ast.Ident{Name: "unsafe"},
Sel: &ast.Ident{Name: "Pointer"},
@@ -1171,8 +1209,10 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
},
})
case *ast.GenDecl:
+ f.definedGlobally[name] = node
f.generated.Decls = append(f.generated.Decls, node)
case *ast.TypeSpec:
+ f.definedGlobally[name] = node
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{node},
@@ -1186,7 +1226,8 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
// If this is a struct or union it may need bitfields or union accessor
// methods.
- if elaboratedType != nil {
+ switch elaboratedType := extra.(type) {
+ case *elaboratedTypeInfo:
// Add struct bitfields.
for _, bitfield := range elaboratedType.bitfields {
f.createBitfieldGetter(bitfield, "C."+name)
diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go
index ffc8bb01f..f919972b2 100644
--- a/cgo/cgo_test.go
+++ b/cgo/cgo_test.go
@@ -48,7 +48,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
- cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags, "")
+ cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "")
// Check the AST for type errors.
var typecheckErrors []error
diff --git a/cgo/libclang.go b/cgo/libclang.go
index 9afef323f..3c0d196df 100644
--- a/cgo/libclang.go
+++ b/cgo/libclang.go
@@ -4,7 +4,9 @@ package cgo
// modification. It does not touch the AST itself.
import (
+ "crypto/sha256"
"crypto/sha512"
+ "encoding/hex"
"fmt"
"go/ast"
"go/scanner"
@@ -43,6 +45,8 @@ typedef struct {
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
+CXString tinygo_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy);
+CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(GoCXCursor c);
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
CXType tinygo_clang_getCursorType(GoCXCursor c);
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
@@ -50,6 +54,7 @@ CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
+enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
@@ -189,7 +194,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// Convert the AST node under the given Clang cursor to a Go AST node and return
// it.
-func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaboratedTypeInfo) {
+func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
kind := C.tinygo_clang_getCursorKind(c)
pos := f.getCursorPosition(c)
switch kind {
@@ -200,19 +205,43 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaborat
Kind: ast.Fun,
Name: "C." + name,
}
+ exportName := name
+ localName := name
+ var stringSignature string
+ if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
+ // A static function is assigned a globally unique symbol name based
+ // on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
+ // has a different Go name in the form of C.foo!symbols.go instead
+ // of just C.foo.
+ path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name())
+ staticIDBuf := sha256.Sum256([]byte(path))
+ staticID := hex.EncodeToString(staticIDBuf[:10])
+ exportName = "_Cgo_static_" + staticID + "_" + name
+ localName = name + "!" + filepath.Base(path)
+
+ // Create a signature. This is necessary for MacOS to forward the
+ // call, because MacOS doesn't support aliases like ELF and PE do.
+ // (There is N_INDR but __attribute__((alias("..."))) doesn't work).
+ policy := C.tinygo_clang_getCursorPrintingPolicy(c)
+ defer C.clang_PrintingPolicy_dispose(policy)
+ C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1)
+ stringSignature = getString(C.tinygo_clang_getCursorPrettyPrinted(c, policy))
+ stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1)
+ stringSignature = strings.TrimPrefix(stringSignature, "static ")
+ }
args := make([]*ast.Field, numArgs)
decl := &ast.FuncDecl{
Doc: &ast.CommentGroup{
List: []*ast.Comment{
{
Slash: pos - 1,
- Text: "//export " + name,
+ Text: "//export " + exportName,
},
},
},
Name: &ast.Ident{
NamePos: pos,
- Name: "C." + name,
+ Name: "C." + localName,
Obj: obj,
},
Type: &ast.FuncType{
@@ -263,7 +292,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaborat
}
}
obj.Decl = decl
- return decl, nil
+ return decl, stringSignature
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos)
typeName := "C." + name
diff --git a/cgo/libclang_stubs.c b/cgo/libclang_stubs.c
index 2bc4451c6..b0668ed71 100644
--- a/cgo/libclang_stubs.c
+++ b/cgo/libclang_stubs.c
@@ -17,6 +17,14 @@ CXString tinygo_clang_getCursorSpelling(CXCursor c) {
return clang_getCursorSpelling(c);
}
+CXString tinygo_clang_getCursorPrettyPrinted(CXCursor c, CXPrintingPolicy policy) {
+ return clang_getCursorPrettyPrinted(c, policy);
+}
+
+CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(CXCursor c) {
+ return clang_getCursorPrintingPolicy(c);
+}
+
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
return clang_getCursorKind(c);
}
@@ -45,6 +53,10 @@ CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i);
}
+enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) {
+ return clang_Cursor_getStorageClass(c);
+}
+
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
diff --git a/cgo/testdata/symbols.go b/cgo/testdata/symbols.go
index c724cd921..fb585c2f8 100644
--- a/cgo/testdata/symbols.go
+++ b/cgo/testdata/symbols.go
@@ -5,6 +5,7 @@ package main
int foo(int a, int b);
void variadic0();
void variadic2(int x, int y, ...);
+static void staticfunc(int x);
// Global variable signatures.
extern int someValue;
@@ -16,6 +17,7 @@ func accessFunctions() {
C.foo(3, 4)
C.variadic0()
C.variadic2(3, 5)
+ C.staticfunc(3)
}
func accessGlobals() {
diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go
index 6791999ad..f2826ae2e 100644
--- a/cgo/testdata/symbols.out.go
+++ b/cgo/testdata/symbols.out.go
@@ -55,5 +55,10 @@ func C.variadic2(x C.int, y C.int)
var C.variadic2$funcaddr unsafe.Pointer
+//export _Cgo_static_173c95a79b6df1980521_staticfunc
+func C.staticfunc!symbols.go(x C.int)
+
+var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
+
//go:extern someValue
var C.someValue C.int