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.
Этот коммит содержится в:
Ayke van Laethem 2019-05-10 19:48:33 +02:00 коммит произвёл Ron Evans
родитель 4619207f99
коммит 11567c62d4
8 изменённых файлов: 320 добавлений и 244 удалений

Просмотреть файл

@ -32,7 +32,7 @@ CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUIL
clean: clean:
@rm -rf build @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: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:

Просмотреть файл

@ -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 // 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 ( import (
"go/ast" "go/ast"
@ -13,25 +21,35 @@ import (
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
) )
// fileInfo holds all Cgo-related information of a given *ast.File. // cgoPackage holds all CCo-related information of a package.
type fileInfo struct { type cgoPackage struct {
*ast.File generated *ast.File
*Package generatedPos token.Pos
filename string errors []error
constants map[string]*ast.BasicLit dir string
functions map[string]*functionInfo fset *token.FileSet
globals map[string]*globalInfo tokenFiles map[string]*token.File
typedefs map[string]*typedefInfo
elaboratedTypes map[string]ast.Expr
importCPos token.Pos
missingSymbols map[string]struct{} missingSymbols map[string]struct{}
constants map[string]constantInfo
functions map[string]*functionInfo
globals map[string]globalInfo
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
} }
// functionInfo stores some information about a Cgo function found by libclang // 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 CCo function found by libclang
// and declared in the AST. // and declared in the AST.
type functionInfo struct { type functionInfo struct {
args []paramInfo args []paramInfo
results *ast.FieldList results *ast.FieldList
pos token.Pos
} }
// paramInfo is a parameter of a Cgo function (see functionInfo). // 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. // typedefInfo contains information about a single typedef in C.
type typedefInfo struct { type typedefInfo struct {
typeExpr ast.Expr 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. // globalInfo contains information about a declared global variable in C.
type globalInfo struct { type globalInfo struct {
typeExpr ast.Expr typeExpr ast.Expr
pos token.Pos
} }
// cgoAliases list type aliases between Go and C, for types that are equivalent // 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", "C.uintptr_t": "uintptr",
} }
// cgoBuiltinAliases are handled specially because they only exist on the Go // builtinAliases are handled specially because they only exist on the Go side
// side of CGo, not on the CGo (they're prefixed with "_Cgo_" there). // of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
var cgoBuiltinAliases = map[string]struct{}{ var builtinAliases = map[string]struct{}{
"char": struct{}{}, "char": struct{}{},
"schar": struct{}{}, "schar": struct{}{},
"uchar": struct{}{}, "uchar": struct{}{},
@ -97,111 +124,146 @@ typedef long long _Cgo_longlong;
typedef unsigned long long _Cgo_ulonglong; typedef unsigned long long _Cgo_ulonglong;
` `
// processCgo extracts the `import "C"` statement from the AST, parses the // Process extracts `import "C"` statements from the AST, parses the comment
// comment with libclang, and modifies the AST to use this information. // with libclang, and modifies the AST to use this information. It returns a
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error { // newly created *ast.File that should be added to the list of to-be-parsed
info := &fileInfo{ // files. If there is one or more error, it returns these in the []error slice
File: f, // but still modifies the AST.
Package: p, func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
filename: filename, p := &cgoPackage{
constants: map[string]*ast.BasicLit{}, dir: dir,
functions: map[string]*functionInfo{}, fset: fset,
globals: map[string]*globalInfo{}, tokenFiles: map[string]*token.File{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]ast.Expr{},
missingSymbols: map[string]struct{}{}, missingSymbols: map[string]struct{}{},
constants: map[string]constantInfo{},
functions: map[string]*functionInfo{},
globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{},
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. // Find all C.* symbols.
f = astutil.Apply(f, info.findMissingCGoNames, nil).(*ast.File) for _, f := range files {
for name := range cgoBuiltinAliases { astutil.Apply(f, p.findMissingCGoNames, nil)
info.missingSymbols["_Cgo_"+name] = struct{}{} }
for name := range builtinAliases {
p.missingSymbols["_Cgo_"+name] = struct{}{}
} }
// Find `import "C"` statements in the file. // Find `import "C"` statements in the file.
for i := 0; i < len(f.Decls); i++ { for _, f := range files {
decl := f.Decls[i] for i := 0; i < len(f.Decls); i++ {
genDecl, ok := decl.(*ast.GenDecl) decl := f.Decls[i]
if !ok { genDecl, ok := decl.(*ast.GenDecl)
continue if !ok {
} continue
if len(genDecl.Specs) != 1 { }
continue if len(genDecl.Specs) != 1 {
} continue
spec, ok := genDecl.Specs[0].(*ast.ImportSpec) }
if !ok { spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
continue if !ok {
} continue
path, err := strconv.Unquote(spec.Path.Value) }
if err != nil { path, err := strconv.Unquote(spec.Path.Value)
panic("could not parse import path: " + err.Error()) if err != nil {
} panic("could not parse import path: " + err.Error())
if path != "C" { }
continue if path != "C" {
} continue
cgoComment := genDecl.Doc.Text() }
cgoComment := genDecl.Doc.Text()
// Stored for later use by generated functions, to use a somewhat sane pos := genDecl.Pos()
// source location. if genDecl.Doc != nil {
info.importCPos = spec.Path.ValuePos pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
pos := genDecl.Pos() // Remove this import declaration.
if genDecl.Doc != nil { f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
pos = genDecl.Doc.Pos() i--
}
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. // Print the AST, for debugging.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) //ast.Print(fset, f)
i--
} }
// Print the AST, for debugging.
//ast.Print(p.fset, f)
// Declare functions found by libclang. // Declare functions found by libclang.
info.addFuncDecls() p.addFuncDecls()
// Declare stub function pointer values found by libclang. // Declare stub function pointer values found by libclang.
info.addFuncPtrDecls() p.addFuncPtrDecls()
// Declare globals found by libclang. // Declare globals found by libclang.
info.addConstDecls() p.addConstDecls()
// Declare globals found by libclang. // Declare globals found by libclang.
info.addVarDecls() p.addVarDecls()
// Forward C types to Go types (like C.uint32_t -> uint32). // 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. // Add type declarations for C types, declared using typedef in C.
info.addTypedefs() p.addTypedefs()
// Add elaborated types for C structs and unions. // Add elaborated types for C structs and unions.
info.addElaboratedTypes() p.addElaboratedTypes()
// Patch the AST to use the declared types and functions. // 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)
}
return nil // Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.errors
} }
// addFuncDecls adds the C function declarations found by libclang in the // addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement. // comment above the `import "C"` statement.
func (info *fileInfo) addFuncDecls() { func (p *cgoPackage) addFuncDecls() {
// TODO: replace all uses of importCPos with the real locations from names := make([]string, 0, len(p.functions))
// libclang. for name := range p.functions {
names := make([]string, 0, len(info.functions))
for name := range info.functions {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
fn := info.functions[name] fn := p.functions[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Fun, Kind: ast.Fun,
Name: "C." + name, Name: "C." + name,
@ -209,16 +271,16 @@ func (info *fileInfo) addFuncDecls() {
args := make([]*ast.Field, len(fn.args)) args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{ decl := &ast.FuncDecl{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
}, },
Type: &ast.FuncType{ Type: &ast.FuncType{
Func: info.importCPos, Func: fn.pos,
Params: &ast.FieldList{ Params: &ast.FieldList{
Opening: info.importCPos, Opening: fn.pos,
List: args, List: args,
Closing: info.importCPos, Closing: fn.pos,
}, },
Results: fn.results, Results: fn.results,
}, },
@ -228,7 +290,7 @@ func (info *fileInfo) addFuncDecls() {
args[i] = &ast.Field{ args[i] = &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
&ast.Ident{ &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: arg.name, Name: arg.name,
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,
@ -240,7 +302,7 @@ func (info *fileInfo) addFuncDecls() {
Type: arg.typeExpr, 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 // C.mul unsafe.Pointer
// // ... // // ...
// ) // )
func (info *fileInfo) addFuncPtrDecls() { func (p *cgoPackage) addFuncPtrDecls() {
if len(info.functions) == 0 { if len(p.functions) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.VAR, Tok: token.VAR,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
names := make([]string, 0, len(info.functions)) names := make([]string, 0, len(p.functions))
for name := range info.functions { for name := range p.functions {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
fn := p.functions[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Typ, Kind: ast.Typ,
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{ Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
Obj: obj, Obj: obj,
}}, }},
Type: &ast.SelectorExpr{ Type: &ast.SelectorExpr{
X: &ast.Ident{ X: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "unsafe", Name: "unsafe",
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: info.importCPos, NamePos: fn.pos,
Name: "Pointer", Name: "Pointer",
}, },
}, },
@ -293,7 +356,7 @@ func (info *fileInfo) addFuncPtrDecls() {
obj.Decl = valueSpec obj.Decl = valueSpec
gen.Specs = append(gen.Specs, 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. // addConstDecls declares external C constants in the Go source.
@ -304,39 +367,39 @@ func (info *fileInfo) addFuncPtrDecls() {
// C.CONST_FLOAT = 5.8 // C.CONST_FLOAT = 5.8
// // ... // // ...
// ) // )
func (info *fileInfo) addConstDecls() { func (p *cgoPackage) addConstDecls() {
if len(info.constants) == 0 { if len(p.constants) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.CONST, Tok: token.CONST,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
names := make([]string, 0, len(info.constants)) names := make([]string, 0, len(p.constants))
for name := range info.constants { for name := range p.constants {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
constVal := info.constants[name] constVal := p.constants[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Con, Kind: ast.Con,
Name: "C." + name, Name: "C." + name,
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{ Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos, NamePos: constVal.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
}}, }},
Values: []ast.Expr{constVal}, Values: []ast.Expr{constVal.expr},
} }
obj.Decl = valueSpec obj.Decl = valueSpec
gen.Specs = append(gen.Specs, 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. // addVarDecls declares external C globals in the Go source.
@ -347,30 +410,30 @@ func (info *fileInfo) addConstDecls() {
// C.globalBool bool // C.globalBool bool
// // ... // // ...
// ) // )
func (info *fileInfo) addVarDecls() { func (p *cgoPackage) addVarDecls() {
if len(info.globals) == 0 { if len(p.globals) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.VAR, Tok: token.VAR,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
names := make([]string, 0, len(info.globals)) names := make([]string, 0, len(p.globals))
for name := range info.globals { for name := range p.globals {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
global := info.globals[name] global := p.globals[name]
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Var, Kind: ast.Var,
Name: "C." + name, Name: "C." + name,
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{ Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos, NamePos: global.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
}}, }},
@ -379,7 +442,7 @@ func (info *fileInfo) addVarDecls() {
obj.Decl = valueSpec obj.Decl = valueSpec
gen.Specs = append(gen.Specs, 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. // addTypeAliases aliases some built-in Go types with their equivalent C types.
@ -390,17 +453,17 @@ func (info *fileInfo) addVarDecls() {
// C.int16_t = int16 // C.int16_t = int16
// // ... // // ...
// ) // )
func (info *fileInfo) addTypeAliases() { func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases)) aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases { for key := range cgoAliases {
aliasKeys = append(aliasKeys, key) aliasKeys = append(aliasKeys, key)
} }
sort.Strings(aliasKeys) sort.Strings(aliasKeys)
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
Lparen: info.importCPos, Lparen: token.NoPos,
Rparen: info.importCPos, Rparen: token.NoPos,
} }
for _, typeName := range aliasKeys { for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName] goTypeName := cgoAliases[typeName]
@ -410,37 +473,37 @@ func (info *fileInfo) addTypeAliases() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: token.NoPos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Assign: info.importCPos, Assign: p.generatedPos,
Type: &ast.Ident{ Type: &ast.Ident{
NamePos: info.importCPos, NamePos: token.NoPos,
Name: goTypeName, Name: goTypeName,
}, },
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, 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() { func (p *cgoPackage) addTypedefs() {
if len(info.typedefs) == 0 { if len(p.typedefs) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
} }
names := make([]string, 0, len(info.typedefs)) names := make([]string, 0, len(p.typedefs))
for name := range info.typedefs { for name := range p.typedefs {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
typedef := info.typedefs[name] typedef := p.typedefs[name]
typeName := "C." + name typeName := "C." + name
isAlias := true isAlias := true
if strings.HasPrefix(name, "_Cgo_") { if strings.HasPrefix(name, "_Cgo_") {
@ -457,19 +520,19 @@ func (info *fileInfo) addTypedefs() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: typedef.pos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Type: typedef.typeExpr, Type: typedef.typeExpr,
} }
if isAlias { if isAlias {
typeSpec.Assign = info.importCPos typeSpec.Assign = typedef.pos
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, 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 // addElaboratedTypes adds C elaborated types as aliases. These are the "struct
@ -477,21 +540,21 @@ func (info *fileInfo) addTypedefs() {
// //
// See also: // See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier // https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
func (info *fileInfo) addElaboratedTypes() { func (p *cgoPackage) addElaboratedTypes() {
if len(info.elaboratedTypes) == 0 { if len(p.elaboratedTypes) == 0 {
return return
} }
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: info.importCPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
} }
names := make([]string, 0, len(info.elaboratedTypes)) names := make([]string, 0, len(p.elaboratedTypes))
for name := range info.elaboratedTypes { for name := range p.elaboratedTypes {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
typ := info.elaboratedTypes[name] typ := p.elaboratedTypes[name]
typeName := "C." + name typeName := "C." + name
obj := &ast.Object{ obj := &ast.Object{
Kind: ast.Typ, Kind: ast.Typ,
@ -499,22 +562,22 @@ func (info *fileInfo) addElaboratedTypes() {
} }
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: info.importCPos, NamePos: typ.pos,
Name: typeName, Name: typeName,
Obj: obj, Obj: obj,
}, },
Type: typ, Type: typ.typeExpr,
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, 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 // 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 // these symbols are extracted from the parsed C AST and converted to the Go
// equivalent. // equivalent.
func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool { func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) { switch node := cursor.Node().(type) {
case *ast.SelectorExpr: case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident) x, ok := node.X.(*ast.Ident)
@ -523,10 +586,10 @@ func (info *fileInfo) findMissingCGoNames(cursor *astutil.Cursor) bool {
} }
if x.Name == "C" { if x.Name == "C" {
name := node.Sel.Name name := node.Sel.Name
if _, ok := cgoBuiltinAliases[name]; ok { if _, ok := builtinAliases[name]; ok {
name = "_Cgo_" + name name = "_Cgo_" + name
} }
info.missingSymbols[name] = struct{}{} p.missingSymbols[name] = struct{}{}
} }
} }
return true 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 // 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 // used in the middle of a name) so in practice all C identifiers live in a
// separate namespace (no _Cgo_ hacks like in gc). // 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) { switch node := cursor.Node().(type) {
case *ast.CallExpr: case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr) fun, ok := node.Fun.(*ast.SelectorExpr)
@ -547,7 +610,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
if !ok { if !ok {
return true 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{ node.Fun = &ast.Ident{
NamePos: x.NamePos, NamePos: x.NamePos,
Name: "C." + fun.Sel.Name, Name: "C." + fun.Sel.Name,
@ -560,7 +623,7 @@ func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
} }
if x.Name == "C" { if x.Name == "C" {
name := "C." + node.Sel.Name name := "C." + node.Sel.Name
if _, ok := info.functions[node.Sel.Name]; ok { if _, ok := p.functions[node.Sel.Name]; ok {
name += "$funcaddr" name += "$funcaddr"
} }
cursor.Replace(&ast.Ident{ cursor.Replace(&ast.Ident{

Просмотреть файл

@ -1,4 +1,4 @@
package loader package cgo
// This file parses a fragment of C with libclang and stores the result for AST // This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself. // 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" import "C"
// refMap stores references to types, used for clang_visitChildren. // storedRefs stores references to types, used for clang_visitChildren.
var refMap RefMap var storedRefs refMap
var diagnosticSeverity = [...]string{ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored", C.CXDiagnostic_Ignored: "ignored",
@ -66,7 +66,7 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal", 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) index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index) defer C.clang_disposeIndex(index)
@ -110,7 +110,6 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
defer C.clang_disposeTranslationUnit(unit) defer C.clang_disposeTranslationUnit(unit)
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
errs := []error{}
addDiagnostic := func(diagnostic C.CXDiagnostic) { addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)] severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
@ -122,12 +121,12 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
filename := getString(libclangFilename) filename := getString(libclangFilename)
if filepath.IsAbs(filename) { if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors. // 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 { if err == nil {
filename = relpath filename = relpath
} }
} }
errs = append(errs, &scanner.Error{ p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{ Pos: token.Position{
Filename: filename, Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation 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))) addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
} }
} }
return errs return
} }
ref := refMap.Put(info) ref := storedRefs.Put(p)
defer refMap.Remove(ref) defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit) cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
return nil
} }
//export tinygo_clang_globals_visitor //export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 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) kind := C.tinygo_clang_getCursorKind(c)
pos := info.getCursorPosition(c) pos := p.getCursorPosition(c)
switch kind { switch kind {
case C.CXCursor_FunctionDecl: case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
cursorType := C.tinygo_clang_getCursorType(c) 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 return C.CXChildVisit_Continue // not supported
} }
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{} fn := &functionInfo{
info.functions[name] = fn pos: pos,
}
p.functions[name] = fn
for i := 0; i < numArgs; i++ { for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg)) 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{ fn.args = append(fn.args, paramInfo{
name: argName, name: argName,
typeExpr: info.makeASTType(argType, pos), typeExpr: p.makeASTType(argType, pos),
}) })
} }
resultType := C.tinygo_clang_getCursorResultType(c) 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{ fn.results = &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
&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: case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c) typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(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 return C.CXChildVisit_Continue
} }
info.makeASTType(typ, pos) p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl: case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c) typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType)) name := getString(C.clang_getTypedefName(typedefType))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
info.makeASTType(typedefType, pos) p.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl: case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
info.globals[name] = &globalInfo{ p.globals[name] = globalInfo{
typeExpr: info.makeASTType(cursorType, pos), typeExpr: p.makeASTType(cursorType, pos),
pos: pos,
} }
case C.CXCursor_MacroDefinition: case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required { if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
sourceRange := C.tinygo_clang_getCursorExtent(c) 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 // https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' { if value[0] == '"' {
// string constant // 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 return C.CXChildVisit_Continue
} }
if value[0] == '\'' { if value[0] == '\'' {
// char constant // 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 return C.CXChildVisit_Continue
} }
// assume it's a number (int or float) // 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 { switch nonnum {
case 0: case 0:
// no non-number found, must be an integer // 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': case 'x', 'X':
// hex integer constant // hex integer constant
// TODO: may also be a floating point number per C++17. // 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': case '.', 'e':
// float constant // float constant
value = strings.TrimRight(value, "fFlL") 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: default:
// unknown type, ignore // 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 // 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 // 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. // (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) location := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile var file C.CXFile
var line C.unsigned var line C.unsigned
@ -327,7 +327,7 @@ func (info *fileInfo) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return token.NoPos return token.NoPos
} }
filename := getString(C.clang_getFileName(file)) 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 // File has not been seen before in this package, add line information
// now by reading the file from libclang. // now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor) 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) lines = append(lines, i+1)
} }
} }
f := info.fset.AddFile(filename, -1, int(size)) f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines) 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, // 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. // 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 var typeName string
switch typ.kind { switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U: 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{ return &ast.StarExpr{
Star: pos, Star: pos,
X: info.makeASTType(pointeeType, pos), X: p.makeASTType(pointeeType, pos),
} }
case C.CXType_ConstantArray: case C.CXType_ConstantArray:
return &ast.ArrayType{ return &ast.ArrayType{
@ -420,7 +420,7 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
Kind: token.INT, Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10), 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: case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function // 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: case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ)) name := getString(C.clang_getTypedefName(typ))
if _, ok := info.typedefs[name]; !ok { if _, ok := p.typedefs[name]; !ok {
info.typedefs[name] = nil // don't recurse p.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ) c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := info.makeASTType(underlyingType, pos) expr := p.makeASTType(underlyingType, pos)
if strings.HasPrefix(name, "_Cgo_") { if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident) expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType) 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, typeExpr: expr,
pos: pos,
} }
} }
return &ast.Ident{ 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) underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind { switch underlying.kind {
case C.CXType_Record: case C.CXType_Record:
return info.makeASTType(underlying, pos) return p.makeASTType(underlying, pos)
default: default:
panic("unknown elaborated type") panic("unknown elaborated type")
} }
@ -515,23 +516,26 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
default: default:
panic("unknown record declaration") panic("unknown record declaration")
} }
if _, ok := info.elaboratedTypes[cgoName]; !ok { if _, ok := p.elaboratedTypes[cgoName]; !ok {
info.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion) p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{ fieldList := &ast.FieldList{
Opening: pos, Opening: pos,
Closing: pos, Closing: pos,
} }
ref := refMap.Put(struct { ref := storedRefs.Put(struct {
fieldList *ast.FieldList fieldList *ast.FieldList
info *fileInfo pkg *cgoPackage
}{fieldList, info}) }{fieldList, p})
defer refMap.Remove(ref) defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) { switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl: case C.CXCursor_StructDecl:
info.elaboratedTypes[cgoName] = &ast.StructType{ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
Struct: pos, typeExpr: &ast.StructType{
Fields: fieldList, Struct: pos,
Fields: fieldList,
},
pos: pos,
} }
case C.CXCursor_UnionDecl: case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 { 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...) fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
} }
info.elaboratedTypes[cgoName] = &ast.StructType{ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
Struct: pos, typeExpr: &ast.StructType{
Fields: fieldList, Struct: pos,
Fields: fieldList,
},
pos: pos,
} }
default: default:
panic("unreachable") panic("unreachable")
@ -587,23 +594,23 @@ func (info *fileInfo) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
//export tinygo_clang_struct_visitor //export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { 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 fieldList *ast.FieldList
info *fileInfo pkg *cgoPackage
}) })
fieldList := passed.fieldList fieldList := passed.fieldList
info := passed.info p := passed.pkg
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl { if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor") panic("expected field inside cursor")
} }
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
typ := C.tinygo_clang_getCursorType(c) typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{ field := &ast.Field{
Type: info.makeASTType(typ, info.getCursorPosition(c)), Type: p.makeASTType(typ, p.getCursorPosition(c)),
} }
field.Names = []*ast.Ident{ field.Names = []*ast.Ident{
&ast.Ident{ &ast.Ident{
NamePos: info.getCursorPosition(c), NamePos: p.getCursorPosition(c),
Name: name, Name: name,
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,

Просмотреть файл

@ -1,6 +1,6 @@
// +build !byollvm // +build !byollvm
package loader package cgo
/* /*
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include #cgo linux CFLAGS: -I/usr/lib/llvm-8/include

Просмотреть файл

Просмотреть файл

@ -1,4 +1,4 @@
package loader package cgo
import ( import (
"sync" "sync"
@ -8,17 +8,17 @@ import (
// #include <stdlib.h> // #include <stdlib.h>
import "C" 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 // C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer. // pointer but only a C pointer.
type RefMap struct { type refMap struct {
refs map[unsafe.Pointer]interface{} refs map[unsafe.Pointer]interface{}
lock sync.Mutex lock sync.Mutex
} }
// Put stores a value in the map. It can later be retrieved using Get. It must // Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks. // 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() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if m.refs == nil { 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 // Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put. // 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() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
return m.refs[ref] return m.refs[ref]
} }
// Remove deletes a single reference from the map. // 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() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
delete(m.refs, ref) delete(m.refs, ref)

Просмотреть файл

@ -10,6 +10,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/tinygo-org/tinygo/cgo"
) )
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
@ -30,11 +32,10 @@ type Program struct {
type Package struct { type Package struct {
*Program *Program
*build.Package *build.Package
Imports map[string]*Package Imports map[string]*Package
Importing bool Importing bool
Files []*ast.File Files []*ast.File
tokenFiles map[string]*token.File Pkg *types.Package
Pkg *types.Package
types.Info types.Info
} }
@ -107,7 +108,6 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
Scopes: make(map[ast.Node]*types.Scope), Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection), Selections: make(map[*ast.SelectorExpr]*types.Selection),
}, },
tokenFiles: map[string]*token.File{},
} }
} }
@ -295,16 +295,6 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
} }
files = append(files, f) files = append(files, f)
} }
clangIncludes := ""
if len(p.CgoFiles) != 0 {
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")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
}
}
for _, file := range p.CgoFiles { for _, file := range p.CgoFiles {
path := filepath.Join(p.Package.Dir, file) path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments) f, err := p.parseFile(path, parser.ParseComments)
@ -312,12 +302,22 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
continue continue
} }
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes)) 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")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
if errs != nil { if errs != nil {
fileErrs = append(fileErrs, errs...) fileErrs = append(fileErrs, errs...)
continue
} }
files = append(files, f) files = append(files, generated)
} }
if len(fileErrs) != 0 { if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs} return nil, Errors{p, fileErrs}

6
testdata/cgo/extra.go предоставленный Обычный файл
Просмотреть файл

@ -0,0 +1,6 @@
package main
// Make sure CGo supports multiple files.
// int fortytwo(void);
import "C"