diff options
-rw-r--r-- | Makefile | 2 | ||||
-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.go | 36 | ||||
-rw-r--r-- | testdata/cgo/extra.go | 6 |
8 files changed, 317 insertions, 241 deletions
@@ -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" |