Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 57 additions & 16 deletions cgo/cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,23 @@ 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
anonDecls map[interface{}]string
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
}
Expand Down Expand Up @@ -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{},
Expand Down Expand Up @@ -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++ {
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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`
Expand All @@ -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),
}
Expand Down Expand Up @@ -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
}
Expand All @@ -1142,27 +1152,55 @@ 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
}

// 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__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😿 oh Apple

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The worst part is that the linker actually does support aliases, it's just the compiler that doesn't.
...and I don't feel like creating a Clang patch and pushing for its inclusion.

%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
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
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"},
Expand All @@ -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},
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cgo/cgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 33 additions & 4 deletions cgo/libclang.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -43,13 +45,16 @@ 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);
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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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{
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions cgo/libclang_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions cgo/testdata/symbols.go
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,6 +17,7 @@ func accessFunctions() {
C.foo(3, 4)
C.variadic0()
C.variadic2(3, 5)
C.staticfunc(3)
}

func accessGlobals() {
Expand Down
5 changes: 5 additions & 0 deletions cgo/testdata/symbols.out.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions testdata/cgo/extra.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 0 deletions testdata/cgo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
Loading