diff options
author | Ayke van Laethem <[email protected]> | 2022-04-13 21:34:43 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2022-09-16 14:05:17 +0200 |
commit | 5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8 (patch) | |
tree | 0fcff842574327f6615ed0909ed57efaf726dba5 /cgo | |
parent | 91104b2f276348e251a25e9e58e7faafe781358f (diff) | |
download | tinygo-5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8.tar.gz tinygo-5551ec7a1ed9d59764cfdf5a73b23dc40365a6f8.zip |
cgo: implement support for static functions
Diffstat (limited to 'cgo')
-rw-r--r-- | cgo/cgo.go | 73 | ||||
-rw-r--r-- | cgo/cgo_test.go | 2 | ||||
-rw-r--r-- | cgo/libclang.go | 37 | ||||
-rw-r--r-- | cgo/libclang_stubs.c | 12 | ||||
-rw-r--r-- | cgo/testdata/symbols.go | 2 | ||||
-rw-r--r-- | cgo/testdata/symbols.out.go | 5 |
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 |