aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-05-10 19:48:33 +0200
committerRon Evans <[email protected]>2019-05-12 10:49:15 +0200
commit11567c62d45418685161cd484b55c225f1d1f65d (patch)
tree60762f41abf7c25502dae481d6fc7ba5a6d7d920
parent4619207f99839c388e8310bb5921749bcdcb7d3e (diff)
downloadtinygo-11567c62d45418685161cd484b55c225f1d1f65d.tar.gz
tinygo-11567c62d45418685161cd484b55c225f1d1f65d.zip
cgo: refactor; support multiple cgo files in a single package
This is a big commit that does a few things: * It moves CGo processing into a separate package. It never really belonged in the loader package, and certainly not now that the loader package may be refactored into a driver package. * It adds support for multiple CGo files (files that import package "C") in a single package. Previously, this led to multiple definition errors in the Go typecheck phase because certain C symbols were defined multiple times in all the files. Now it generates a new fake AST that defines these, to avoid multiple definition errors. * It improves debug info in a few edge cases that are probably not relevant outside of bugs in cgo itself.
-rw-r--r--Makefile2
-rw-r--r--cgo/cgo.go (renamed from loader/cgo.go)369
-rw-r--r--cgo/libclang.go (renamed from loader/libclang.go)131
-rw-r--r--cgo/libclang_config.go (renamed from loader/libclang_config.go)2
-rw-r--r--cgo/libclang_stubs.c (renamed from loader/libclang_stubs.c)0
-rw-r--r--cgo/sync.go (renamed from loader/sync.go)12
-rw-r--r--loader/loader.go36
-rw-r--r--testdata/cgo/extra.go6
8 files changed, 317 insertions, 241 deletions
diff --git a/Makefile b/Makefile
index 0f11c7085..48936468e 100644
--- a/Makefile
+++ b/Makefile
@@ -32,7 +32,7 @@ CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUIL
clean:
@rm -rf build
-FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
+FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
diff --git a/loader/cgo.go b/cgo/cgo.go
index 07d7718b8..e7f44dee0 100644
--- a/loader/cgo.go
+++ b/cgo/cgo.go
@@ -1,7 +1,15 @@
-package loader
+// Package cgo implements CGo by modifying a loaded AST. It does this by parsing
+// the `import "C"` statements found in the source code with libclang and
+// generating stub function and global declarations.
+//
+// There are a few advantages to modifying the AST directly instead of doing CGo
+// as a preprocessing step, with the main advantage being that debug information
+// is kept intact as much as possible.
+package cgo
// This file extracts the `import "C"` statement from the source and modifies
-// the AST for Cgo. It does not use libclang directly (see libclang.go).
+// the AST for CCo. It does not use libclang directly: see libclang.go for the C
+// source file parsing.
import (
"go/ast"
@@ -13,25 +21,35 @@ import (
"golang.org/x/tools/go/ast/astutil"
)
-// fileInfo holds all Cgo-related information of a given *ast.File.
-type fileInfo struct {
- *ast.File
- *Package
- filename string
- constants map[string]*ast.BasicLit
+// cgoPackage holds all CCo-related information of a package.
+type cgoPackage struct {
+ generated *ast.File
+ generatedPos token.Pos
+ errors []error
+ dir string
+ fset *token.FileSet
+ tokenFiles map[string]*token.File
+ missingSymbols map[string]struct{}
+ constants map[string]constantInfo
functions map[string]*functionInfo
- globals map[string]*globalInfo
+ globals map[string]globalInfo
typedefs map[string]*typedefInfo
- elaboratedTypes map[string]ast.Expr
- importCPos token.Pos
- missingSymbols map[string]struct{}
+ elaboratedTypes map[string]*elaboratedTypeInfo
+}
+
+// constantInfo stores some information about a CGo constant found by libclang
+// and declared in the Go AST.
+type constantInfo struct {
+ expr *ast.BasicLit
+ pos token.Pos
}
-// functionInfo stores some information about a Cgo function found by libclang
+// functionInfo stores some information about a CCo function found by libclang
// and declared in the AST.
type functionInfo struct {
args []paramInfo
results *ast.FieldList
+ pos token.Pos
}
// paramInfo is a parameter of a Cgo function (see functionInfo).
@@ -43,11 +61,20 @@ type paramInfo struct {
// typedefInfo contains information about a single typedef in C.
type typedefInfo struct {
typeExpr ast.Expr
+ pos token.Pos
+}
+
+// elaboratedTypeInfo contains some information about an elaborated type
+// (struct, union) found in the C AST.
+type elaboratedTypeInfo struct {
+ typeExpr ast.Expr
+ pos token.Pos
}
// globalInfo contains information about a declared global variable in C.
type globalInfo struct {
typeExpr ast.Expr
+ pos token.Pos
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
@@ -64,9 +91,9 @@ var cgoAliases = map[string]string{
"C.uintptr_t": "uintptr",
}
-// cgoBuiltinAliases are handled specially because they only exist on the Go
-// side of CGo, not on the CGo (they're prefixed with "_Cgo_" there).
-var cgoBuiltinAliases = map[string]struct{}{
+// builtinAliases are handled specially because they only exist on the Go side
+// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
+var builtinAliases = map[string]struct{}{
"char": struct{}{},
"schar": struct{}{},
"uchar": struct{}{},
@@ -97,111 +124,146 @@ typedef long long _Cgo_longlong;
typedef unsigned long long _Cgo_ulonglong;
`
-// processCgo extracts the `import "C"` statement from the AST, parses the
-// comment with libclang, and modifies the AST to use this information.
-func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
- info := &fileInfo{
- File: f,
- Package: p,
- filename: filename,
- constants: map[string]*ast.BasicLit{},
+// Process extracts `import "C"` statements from the AST, parses the comment
+// with libclang, and modifies the AST to use this information. It returns a
+// newly created *ast.File that should be added to the list of to-be-parsed
+// 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) (*ast.File, []error) {
+ p := &cgoPackage{
+ dir: dir,
+ fset: fset,
+ tokenFiles: map[string]*token.File{},
+ missingSymbols: map[string]struct{}{},
+ constants: map[string]constantInfo{},
functions: map[string]*functionInfo{},
- globals: map[string]*globalInfo{},
+ globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{},
- elaboratedTypes: map[string]ast.Expr{},
- missingSymbols: map[string]struct{}{},
+ elaboratedTypes: map[string]*elaboratedTypeInfo{},
+ }
+
+ // Add a new location for the following file.
+ generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
+ generatedTokenPos.SetLines([]int{0})
+ p.generatedPos = generatedTokenPos.Pos(0)
+
+ // Construct a new in-memory AST for CGo declarations of this package.
+ unsafeImport := &ast.ImportSpec{
+ Path: &ast.BasicLit{
+ ValuePos: p.generatedPos,
+ Kind: token.STRING,
+ Value: "\"unsafe\"",
+ },
+ EndPos: p.generatedPos,
+ }
+ p.generated = &ast.File{
+ Package: p.generatedPos,
+ Name: &ast.Ident{
+ NamePos: p.generatedPos,
+ Name: files[0].Name.Name,
+ },
+ Decls: []ast.Decl{
+ &ast.GenDecl{
+ TokPos: p.generatedPos,
+ Tok: token.IMPORT,
+ Specs: []ast.Spec{
+ unsafeImport,
+ },
+ },
+ },
+ Imports: []*ast.ImportSpec{unsafeImport},
}
// Find all C.* symbols.
- f = astutil.Apply(f, info.findMissingCGoNames, nil).(*ast.File)
- for name := range cgoBuiltinAliases {
- info.missingSymbols["_Cgo_"+name] = struct{}{}
+ for _, f := range files {
+ astutil.Apply(f, p.findMissingCGoNames, nil)
+ }
+ for name := range builtinAliases {
+ p.missingSymbols["_Cgo_"+name] = struct{}{}
}
// Find `import "C"` statements in the file.
- for i := 0; i < len(f.Decls); i++ {
- decl := f.Decls[i]
- genDecl, ok := decl.(*ast.GenDecl)
- if !ok {
- continue
- }
- if len(genDecl.Specs) != 1 {
- continue
- }
- spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
- if !ok {
- continue
- }
- path, err := strconv.Unquote(spec.Path.Value)
- if err != nil {
- panic("could not parse import path: " + err.Error())
- }
- if path != "C" {
- continue
- }
- cgoComment := genDecl.Doc.Text()
+ for _, f := range files {
+ for i := 0; i < len(f.Decls); i++ {
+ decl := f.Decls[i]
+ genDecl, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ if len(genDecl.Specs) != 1 {
+ continue
+ }
+ spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
+ if !ok {
+ continue
+ }
+ path, err := strconv.Unquote(spec.Path.Value)
+ if err != nil {
+ panic("could not parse import path: " + err.Error())
+ }
+ if path != "C" {
+ continue
+ }
+ cgoComment := genDecl.Doc.Text()
- // Stored for later use by generated functions, to use a somewhat sane
- // source location.
- info.importCPos = spec.Path.ValuePos
+ pos := genDecl.Pos()
+ if genDecl.Doc != nil {
+ pos = genDecl.Doc.Pos()
+ }
+ position := fset.PositionFor(pos, true)
+ p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
- pos := genDecl.Pos()
- if genDecl.Doc != nil {
- pos = genDecl.Doc.Pos()
- }
- position := info.fset.PositionFor(pos, true)
- errs := info.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
- if errs != nil {
- return errs
+ // Remove this import declaration.
+ f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
+ i--
}
- // Remove this import declaration.
- f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
- i--
+ // Print the AST, for debugging.
+ //ast.Print(fset, f)
}
- // Print the AST, for debugging.
- //ast.Print(p.fset, f)
-
// Declare functions found by libclang.
- info.addFuncDecls()
+ p.addFuncDecls()
// Declare stub function pointer values found by libclang.
- info.addFuncPtrDecls()
+ p.addFuncPtrDecls()
// Declare globals found by libclang.
- info.addConstDecls()
+ p.addConstDecls()
// Declare globals found by libclang.
- info.addVarDecls()
+ p.addVarDecls()
// Forward C types to Go types (like C.uint32_t -> uint32).
- info.addTypeAliases()
+ p.addTypeAliases()
// Add type declarations for C types, declared using typedef in C.
- info.addTypedefs()
+ p.addTypedefs()
// Add elaborated types for C structs and unions.
- info.addElaboratedTypes()
+ p.addElaboratedTypes()
// Patch the AST to use the declared types and functions.
- f = astutil.Apply(f, info.walker, nil).(*ast.File)
+ for _, f := range files {
+ astutil.Apply(f, p.walker, nil)
+ }
+
+ // Print the newly generated in-memory AST, for debugging.
+ //ast.Print(fset, p.generated)
- return nil
+ return p.generated, p.errors
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
-func (info *fileInfo) addFuncDecls() {
- // TODO: replace all uses of importCPos with the real locations from
- // libclang.
- names := make([]string, 0, len(info.functions))
- for name := range info.functions {
+func (p *cgoPackage) addFuncDecls() {
+ names := make([]string, 0, len(p.functions))
+ for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
- fn := info.functions[name]
+ fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Fun,
Name: "C." + name,
@@ -209,16 +271,16 @@ func (info *fileInfo) addFuncDecls() {
args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{
Name: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: fn.pos,
Name: "C." + name,
Obj: obj,
},
Type: &ast.FuncType{
- Func: info.importCPos,
+ Func: fn.pos,
Params: &ast.FieldList{
- Opening: info.importCPos,
+ Opening: fn.pos,
List: args,
- Closing: info.importCPos,
+ Closing: fn.pos,
},
Results: fn.results,
},
@@ -228,7 +290,7 @@ func (info *fileInfo) addFuncDecls() {
args[i] = &ast.Field{
Names: []*ast.Ident{
&ast.Ident{
- NamePos: info.importCPos,
+ NamePos: fn.pos,
Name: arg.name,
Obj: &ast.Object{
Kind: ast.Var,
@@ -240,7 +302,7 @@ func (info *fileInfo) addFuncDecls() {
Type: arg.typeExpr,
}
}
- info.Decls = append(info.Decls, decl)
+ p.generated.Decls = append(p.generated.Decls, decl)
}
}
@@ -253,39 +315,40 @@ func (info *fileInfo) addFuncDecls() {
// C.mul unsafe.Pointer
// // ...
// )
-func (info *fileInfo) addFuncPtrDecls() {
- if len(info.functions) == 0 {
+func (p *cgoPackage) addFuncPtrDecls() {
+ if len(p.functions) == 0 {
return
}
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.VAR,
- Lparen: info.importCPos,
- Rparen: info.importCPos,
+ Lparen: token.NoPos,
+ Rparen: token.NoPos,
}
- names := make([]string, 0, len(info.functions))
- for name := range info.functions {
+ names := make([]string, 0, len(p.functions))
+ for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
+ fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name + "$funcaddr",
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
- NamePos: info.importCPos,
+ NamePos: fn.pos,
Name: "C." + name + "$funcaddr",
Obj: obj,
}},
Type: &ast.SelectorExpr{
X: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: fn.pos,
Name: "unsafe",
},
Sel: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: fn.pos,
Name: "Pointer",
},
},
@@ -293,7 +356,7 @@ func (info *fileInfo) addFuncPtrDecls() {
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
@@ -304,39 +367,39 @@ func (info *fileInfo) addFuncPtrDecls() {
// C.CONST_FLOAT = 5.8
// // ...
// )
-func (info *fileInfo) addConstDecls() {
- if len(info.constants) == 0 {
+func (p *cgoPackage) addConstDecls() {
+ if len(p.constants) == 0 {
return
}
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.CONST,
- Lparen: info.importCPos,
- Rparen: info.importCPos,
+ Lparen: token.NoPos,
+ Rparen: token.NoPos,
}
- names := make([]string, 0, len(info.constants))
- for name := range info.constants {
+ names := make([]string, 0, len(p.constants))
+ for name := range p.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
- constVal := info.constants[name]
+ constVal := p.constants[name]
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
- NamePos: info.importCPos,
+ NamePos: constVal.pos,
Name: "C." + name,
Obj: obj,
}},
- Values: []ast.Expr{constVal},
+ Values: []ast.Expr{constVal.expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
// addVarDecls declares external C globals in the Go source.
@@ -347,30 +410,30 @@ func (info *fileInfo) addConstDecls() {
// C.globalBool bool
// // ...
// )
-func (info *fileInfo) addVarDecls() {
- if len(info.globals) == 0 {
+func (p *cgoPackage) addVarDecls() {
+ if len(p.globals) == 0 {
return
}
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.VAR,
- Lparen: info.importCPos,
- Rparen: info.importCPos,
+ Lparen: token.NoPos,
+ Rparen: token.NoPos,
}
- names := make([]string, 0, len(info.globals))
- for name := range info.globals {
+ names := make([]string, 0, len(p.globals))
+ for name := range p.globals {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
- global := info.globals[name]
+ global := p.globals[name]
obj := &ast.Object{
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
- NamePos: info.importCPos,
+ NamePos: global.pos,
Name: "C." + name,
Obj: obj,
}},
@@ -379,7 +442,7 @@ func (info *fileInfo) addVarDecls() {
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
// addTypeAliases aliases some built-in Go types with their equivalent C types.
@@ -390,17 +453,17 @@ func (info *fileInfo) addVarDecls() {
// C.int16_t = int16
// // ...
// )
-func (info *fileInfo) addTypeAliases() {
+func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.TYPE,
- Lparen: info.importCPos,
- Rparen: info.importCPos,
+ Lparen: token.NoPos,
+ Rparen: token.NoPos,
}
for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName]
@@ -410,37 +473,37 @@ func (info *fileInfo) addTypeAliases() {
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: token.NoPos,
Name: typeName,
Obj: obj,
},
- Assign: info.importCPos,
+ Assign: p.generatedPos,
Type: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: token.NoPos,
Name: goTypeName,
},
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
-func (info *fileInfo) addTypedefs() {
- if len(info.typedefs) == 0 {
+func (p *cgoPackage) addTypedefs() {
+ if len(p.typedefs) == 0 {
return
}
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.TYPE,
}
- names := make([]string, 0, len(info.typedefs))
- for name := range info.typedefs {
+ names := make([]string, 0, len(p.typedefs))
+ for name := range p.typedefs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
- typedef := info.typedefs[name]
+ typedef := p.typedefs[name]
typeName := "C." + name
isAlias := true
if strings.HasPrefix(name, "_Cgo_") {
@@ -457,19 +520,19 @@ func (info *fileInfo) addTypedefs() {
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: typedef.pos,
Name: typeName,
Obj: obj,
},
Type: typedef.typeExpr,
}
if isAlias {
- typeSpec.Assign = info.importCPos
+ typeSpec.Assign = typedef.pos
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
@@ -477,21 +540,21 @@ func (info *fileInfo) addTypedefs() {
//
// See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
-func (info *fileInfo) addElaboratedTypes() {
- if len(info.elaboratedTypes) == 0 {
+func (p *cgoPackage) addElaboratedTypes() {
+ if len(p.elaboratedTypes) == 0 {
return
}
gen := &ast.GenDecl{
- TokPos: info.importCPos,
+ TokPos: token.NoPos,
Tok: token.TYPE,
}
- names := make([]string, 0, len(info.elaboratedTypes))
- for name := range info.elaboratedTypes {
+ names := make([]string, 0, len(p.elaboratedTypes))
+ for name := range p.elaboratedTypes {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
- typ := info.elaboratedTypes[name]
+ typ := p.elaboratedTypes[name]
typeName := "C." + name
obj := &ast.Object{
Kind: ast.Typ,
@@ -499,22 +562,22 @@ func (info *fileInfo) addElaboratedTypes() {
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
- NamePos: info.importCPos,
+ NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
- Type: typ,
+ Type: typ.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
- info.Decls = append(info.Decls, gen)
+ p.generated.Decls = append(p.generated.Decls, gen)
}
// findMissingCGoNames traverses the AST and finds all C.something names. Only
// these symbols are extracted from the parsed C AST and converted to the Go
// equivalent.
-func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
+func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) {
case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident)
@@ -523,10 +586,10 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
}
if x.Name == "C" {
name := node.Sel.Name
- if _, ok := cgoBuiltinAliases[name]; ok {
+ if _, ok := builtinAliases[name]; ok {
name = "_Cgo_" + name
}
- info.missingSymbols[name] = struct{}{}
+ p.missingSymbols[name] = struct{}{}
}
}
return true
@@ -536,7 +599,7 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
// expressions. Such expressions are impossible to write in Go (a dot cannot be
// used in the middle of a name) so in practice all C identifiers live in a
// separate namespace (no _Cgo_ hacks like in gc).
-func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
+func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) {
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
@@ -547,7 +610,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
if !ok {
return true
}
- if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
+ if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
node.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: "C." + fun.Sel.Name,
@@ -560,7 +623,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
}
if x.Name == "C" {
name := "C." + node.Sel.Name
- if _, ok := info.functions[node.Sel.Name]; ok {
+ if _, ok := p.functions[node.Sel.Name]; ok {
name += "$funcaddr"
}
cursor.Replace(&ast.Ident{
diff --git a/loader/libclang.go b/cgo/libclang.go
index 3d643cf0d..24dac8c3c 100644
--- a/loader/libclang.go
+++ b/cgo/libclang.go
@@ -1,4 +1,4 @@
-package loader
+package cgo
// This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself.
@@ -55,8 +55,8 @@ int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData cl
*/
import "C"
-// refMap stores references to types, used for clang_visitChildren.
-var refMap RefMap
+// storedRefs stores references to types, used for clang_visitChildren.
+var storedRefs refMap
var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored",
@@ -66,7 +66,7 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal",
}
-func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error {
+func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
@@ -110,7 +110,6 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
defer C.clang_disposeTranslationUnit(unit)
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
- errs := []error{}
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
@@ -122,12 +121,12 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
filename := getString(libclangFilename)
if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors.
- relpath, err := filepath.Rel(info.Program.Dir, filename)
+ relpath, err := filepath.Rel(p.dir, filename)
if err == nil {
filename = relpath
}
}
- errs = append(errs, &scanner.Error{
+ p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation
@@ -147,26 +146,24 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
}
}
- return errs
+ return
}
- ref := refMap.Put(info)
- defer refMap.Remove(ref)
+ ref := storedRefs.Put(p)
+ defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
-
- return nil
}
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
- info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo)
+ p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
kind := C.tinygo_clang_getCursorKind(c)
- pos := info.getCursorPosition(c)
+ pos := p.getCursorPosition(c)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
- if _, required := info.missingSymbols[name]; !required {
+ if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
@@ -174,8 +171,10 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
- fn := &functionInfo{}
- info.functions[name] = fn
+ fn := &functionInfo{
+ pos: pos,
+ }
+ p.functions[name] = fn
for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
@@ -185,7 +184,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
}
fn.args = append(fn.args, paramInfo{
name: argName,
- typeExpr: info.makeASTType(argType, pos),
+ typeExpr: p.makeASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
@@ -193,7 +192,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
- Type: info.makeASTType(resultType, pos),
+ Type: p.makeASTType(resultType, pos),
},
},
}
@@ -201,29 +200,30 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
- if _, required := info.missingSymbols["struct_"+name]; !required {
+ if _, required := p.missingSymbols["struct_"+name]; !required {
return C.CXChildVisit_Continue
}
- info.makeASTType(typ, pos)
+ p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
- if _, required := info.missingSymbols[name]; !required {
+ if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
- info.makeASTType(typedefType, pos)
+ p.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
- if _, required := info.missingSymbols[name]; !required {
+ if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
- info.globals[name] = &globalInfo{
- typeExpr: info.makeASTType(cursorType, pos),
+ p.globals[name] = globalInfo{
+ typeExpr: p.makeASTType(cursorType, pos),
+ pos: pos,
}
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
- if _, required := info.missingSymbols[name]; !required {
+ if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
sourceRange := C.tinygo_clang_getCursorExtent(c)
@@ -266,12 +266,12 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
- info.constants[name] = &ast.BasicLit{pos, token.STRING, value}
+ p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
- info.constants[name] = &ast.BasicLit{pos, token.CHAR, value}
+ p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
@@ -289,15 +289,15 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
switch nonnum {
case 0:
// no non-number found, must be an integer
- info.constants[name] = &ast.BasicLit{pos, token.INT, value}
+ p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
- info.constants[name] = &ast.BasicLit{pos, token.INT, value}
+ p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
- info.constants[name] = &ast.BasicLit{pos, token.FLOAT, value}
+ p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
default:
// unknown type, ignore
}
@@ -315,7 +315,7 @@ func getString(clangString C.CXString) (s string) {
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
// file for this cursor has not been seen before, it is read from libclang
// (which already has the file in memory) and added to the token.FileSet.
-func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
+func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
location := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
@@ -327,7 +327,7 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return token.NoPos
}
filename := getString(C.clang_getFileName(file))
- if _, ok := info.tokenFiles[filename]; !ok {
+ if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
@@ -340,16 +340,16 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
lines = append(lines, i+1)
}
}
- f := info.fset.AddFile(filename, -1, int(size))
+ f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines)
- info.tokenFiles[filename] = f
+ p.tokenFiles[filename] = f
}
- return info.tokenFiles[filename].Pos(int(offset))
+ return p.tokenFiles[filename].Pos(int(offset))
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST.
-func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
+func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U:
@@ -410,7 +410,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
return &ast.StarExpr{
Star: pos,
- X: info.makeASTType(pointeeType, pos),
+ X: p.makeASTType(pointeeType, pos),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
@@ -420,7 +420,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
- Elt: info.makeASTType(C.clang_getElementType(typ), pos),
+ Elt: p.makeASTType(C.clang_getElementType(typ), pos),
}
case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function
@@ -441,11 +441,11 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ))
- if _, ok := info.typedefs[name]; !ok {
- info.typedefs[name] = nil // don't recurse
+ if _, ok := p.typedefs[name]; !ok {
+ p.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
- expr := info.makeASTType(underlyingType, pos)
+ expr := p.makeASTType(underlyingType, pos)
if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType)
@@ -487,8 +487,9 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
}
}
- info.typedefs[name] = &typedefInfo{
+ p.typedefs[name] = &typedefInfo{
typeExpr: expr,
+ pos: pos,
}
}
return &ast.Ident{
@@ -499,7 +500,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind {
case C.CXType_Record:
- return info.makeASTType(underlying, pos)
+ return p.makeASTType(underlying, pos)
default:
panic("unknown elaborated type")
}
@@ -515,23 +516,26 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
default:
panic("unknown record declaration")
}
- if _, ok := info.elaboratedTypes[cgoName]; !ok {
- info.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
+ if _, ok := p.elaboratedTypes[cgoName]; !ok {
+ p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
- ref := refMap.Put(struct {
+ ref := storedRefs.Put(struct {
fieldList *ast.FieldList
- info *fileInfo
- }{fieldList, info})
- defer refMap.Remove(ref)
+ pkg *cgoPackage
+ }{fieldList, p})
+ defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
- info.elaboratedTypes[cgoName] = &ast.StructType{
- Struct: pos,
- Fields: fieldList,
+ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
+ typeExpr: &ast.StructType{
+ Struct: pos,
+ Fields: fieldList,
+ },
+ pos: pos,
}
case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 {
@@ -561,9 +565,12 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
- info.elaboratedTypes[cgoName] = &ast.StructType{
- Struct: pos,
- Fields: fieldList,
+ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
+ typeExpr: &ast.StructType{
+ Struct: pos,
+ Fields: fieldList,
+ },
+ pos: pos,
}
default:
panic("unreachable")
@@ -587,23 +594,23 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
- passed := refMap.Get(unsafe.Pointer(client_data)).(struct {
+ passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
- info *fileInfo
+ pkg *cgoPackage
})
fieldList := passed.fieldList
- info := passed.info
+ p := passed.pkg
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
- Type: info.makeASTType(typ, info.getCursorPosition(c)),
+ Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
field.Names = []*ast.Ident{
&ast.Ident{
- NamePos: info.getCursorPosition(c),
+ NamePos: p.getCursorPosition(c),
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
diff --git a/loader/libclang_config.go b/cgo/libclang_config.go
index ece35a5a8..96b4f4bcf 100644
--- a/loader/libclang_config.go
+++ b/cgo/libclang_config.go
@@ -1,6 +1,6 @@
// +build !byollvm
-package loader
+package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
diff --git a/loader/libclang_stubs.c b/cgo/libclang_stubs.c
index c282540a4..c282540a4 100644
--- a/loader/libclang_stubs.c
+++ b/cgo/libclang_stubs.c
diff --git a/loader/sync.go b/cgo/sync.go
index c0b3ebc92..64eab30be 100644
--- a/loader/sync.go
+++ b/cgo/sync.go
@@ -1,4 +1,4 @@
-package loader
+package cgo
import (
"sync"
@@ -8,17 +8,17 @@ import (
// #include <stdlib.h>
import "C"
-// RefMap is a convenient way to store opaque references that can be passed to
+// refMap is a convenient way to store opaque references that can be passed to
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
-type RefMap struct {
+type refMap struct {
refs map[unsafe.Pointer]interface{}
lock sync.Mutex
}
// Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks.
-func (m *RefMap) Put(v interface{}) unsafe.Pointer {
+func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
@@ -31,14 +31,14 @@ func (m *RefMap) Put(v interface{}) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
-func (m *RefMap) Get(ref unsafe.Pointer) interface{} {
+func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
}
// Remove deletes a single reference from the map.
-func (m *RefMap) Remove(ref unsafe.Pointer) {
+func (m *refMap) Remove(ref unsafe.Pointer) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.refs, ref)
diff --git a/loader/loader.go b/loader/loader.go
index cebf72287..5c824795c 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -10,6 +10,8 @@ import (
"os"
"path/filepath"
"sort"
+
+ "github.com/tinygo-org/tinygo/cgo"
)
// Program holds all packages and some metadata about the program as a whole.
@@ -30,11 +32,10 @@ type Program struct {
type Package struct {
*Program
*build.Package
- Imports map[string]*Package
- Importing bool
- Files []*ast.File
- tokenFiles map[string]*token.File
- Pkg *types.Package
+ Imports map[string]*Package
+ Importing bool
+ Files []*ast.File
+ Pkg *types.Package
types.Info
}
@@ -107,7 +108,6 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
- tokenFiles: map[string]*token.File{},
}
}
@@ -295,8 +295,17 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
}
files = append(files, f)
}
- clangIncludes := ""
+ for _, file := range p.CgoFiles {
+ path := filepath.Join(p.Package.Dir, file)
+ f, err := p.parseFile(path, parser.ParseComments)
+ if err != nil {
+ fileErrs = append(fileErrs, err)
+ continue
+ }
+ files = append(files, f)
+ }
if len(p.CgoFiles) != 0 {
+ clangIncludes := ""
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
// Running from the source directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
@@ -304,20 +313,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
}
- }
- for _, file := range p.CgoFiles {
- path := filepath.Join(p.Package.Dir, file)
- f, err := p.parseFile(path, parser.ParseComments)
- if err != nil {
- fileErrs = append(fileErrs, err)
- continue
- }
- errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
+ generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
if errs != nil {
fileErrs = append(fileErrs, errs...)
- continue
}
- files = append(files, f)
+ files = append(files, generated)
}
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
diff --git a/testdata/cgo/extra.go b/testdata/cgo/extra.go
new file mode 100644
index 000000000..8c2c2691b
--- /dev/null
+++ b/testdata/cgo/extra.go
@@ -0,0 +1,6 @@
+package main
+
+// Make sure CGo supports multiple files.
+
+// int fortytwo(void);
+import "C"