cgo: implement support for static functions
Этот коммит содержится в:
родитель
91104b2f27
коммит
5551ec7a1e
10 изменённых файлов: 131 добавлений и 22 удалений
73
cgo/cgo.go
73
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
2
cgo/testdata/symbols.go
предоставленный
2
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() {
|
||||
|
|
5
cgo/testdata/symbols.out.go
предоставленный
5
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
|
||||
|
|
|
@ -440,7 +440,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
|||
var initialCFlags []string
|
||||
initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
|
||||
initialCFlags = append(initialCFlags, "-I"+p.Dir)
|
||||
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, initialCFlags, p.program.clangHeaders)
|
||||
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.clangHeaders)
|
||||
p.CFlags = append(initialCFlags, cflags...)
|
||||
p.CGoHeaders = headerCode
|
||||
for path, hash := range accessedFiles {
|
||||
|
|
14
testdata/cgo/extra.go
предоставленный
14
testdata/cgo/extra.go
предоставленный
|
@ -3,4 +3,18 @@ package main
|
|||
// Make sure CGo supports multiple files.
|
||||
|
||||
// int fortytwo(void);
|
||||
// static float headerfunc_static(float a) { return a - 1; }
|
||||
// static void headerfunc_void(int a, int *ptr) { *ptr = a; }
|
||||
import "C"
|
||||
|
||||
func headerfunc_2() {
|
||||
// Call headerfunc_static that is different from the headerfunc_static in
|
||||
// the main.go file.
|
||||
// The upstream CGo implementation does not handle this case correctly.
|
||||
println("static headerfunc 2:", C.headerfunc_static(5))
|
||||
|
||||
// Test function without return value.
|
||||
var n C.int
|
||||
C.headerfunc_void(3, &n)
|
||||
println("static headerfunc void:", n)
|
||||
}
|
||||
|
|
3
testdata/cgo/main.go
предоставленный
3
testdata/cgo/main.go
предоставленный
|
@ -12,6 +12,7 @@ int mul(int, int);
|
|||
import "C"
|
||||
|
||||
// int headerfunc(int a) { return a + 1; }
|
||||
// static int headerfunc_static(int a) { return a - 1; }
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
@ -47,6 +48,8 @@ func main() {
|
|||
|
||||
// functions in the header C snippet
|
||||
println("headerfunc:", C.headerfunc(5))
|
||||
println("static headerfunc:", C.headerfunc_static(5))
|
||||
headerfunc_2()
|
||||
|
||||
// equivalent types
|
||||
var goInt8 int8 = 5
|
||||
|
|
3
testdata/cgo/out.txt
предоставленный
3
testdata/cgo/out.txt
предоставленный
|
@ -16,6 +16,9 @@ callback 2: 600
|
|||
variadic0: 1
|
||||
variadic2: 15
|
||||
headerfunc: 6
|
||||
static headerfunc: 4
|
||||
static headerfunc 2: +4.000000e+000
|
||||
static headerfunc void: 3
|
||||
bool: true true
|
||||
float: +3.100000e+000
|
||||
double: +3.200000e+000
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче