mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1440 lines
37 KiB
1440 lines
37 KiB
// 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 for the C
|
|
// source file parsing.
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/scanner"
|
|
"go/token"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/shlex"
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
)
|
|
|
|
// cgoPackage holds all CGo-related information of a package.
|
|
type cgoPackage struct {
|
|
generated *ast.File
|
|
generatedPos token.Pos
|
|
errors []error
|
|
currentDir string // current working directory
|
|
packageDir string // full path to the package to process
|
|
fset *token.FileSet
|
|
tokenFiles map[string]*token.File
|
|
missingSymbols map[string]struct{}
|
|
constants map[string]constantInfo
|
|
functions map[string]*functionInfo
|
|
globals map[string]globalInfo
|
|
typedefs map[string]*typedefInfo
|
|
elaboratedTypes map[string]*elaboratedTypeInfo
|
|
enums map[string]enumInfo
|
|
anonStructNum int
|
|
cflags []string // CFlags from #cgo lines
|
|
ldflags []string // LDFlags from #cgo lines
|
|
visitedFiles map[string][]byte
|
|
}
|
|
|
|
// constantInfo stores some information about a CGo constant found by libclang
|
|
// and declared in the Go AST.
|
|
type constantInfo struct {
|
|
expr ast.Expr
|
|
pos token.Pos
|
|
}
|
|
|
|
// functionInfo stores some information about a CGo function found by libclang
|
|
// and declared in the AST.
|
|
type functionInfo struct {
|
|
args []paramInfo
|
|
results *ast.FieldList
|
|
pos token.Pos
|
|
variadic bool
|
|
}
|
|
|
|
// paramInfo is a parameter of a CGo function (see functionInfo).
|
|
type paramInfo struct {
|
|
name string
|
|
typeExpr ast.Expr
|
|
}
|
|
|
|
// 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.StructType
|
|
pos token.Pos
|
|
bitfields []bitfieldInfo
|
|
unionSize int64 // union size in bytes, nonzero when union getters/setters should be created
|
|
unionAlign int64 // union alignment in bytes
|
|
}
|
|
|
|
// bitfieldInfo contains information about a single bitfield in a struct. It
|
|
// keeps information about the start, end, and the special (renamed) base field
|
|
// of this bitfield.
|
|
type bitfieldInfo struct {
|
|
field *ast.Field
|
|
name string
|
|
pos token.Pos
|
|
startBit int64
|
|
endBit int64 // may be 0 meaning "until the end of the field"
|
|
}
|
|
|
|
// enumInfo contains information about an enum in the C.
|
|
type enumInfo 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
|
|
// in both languages. See addTypeAliases.
|
|
var cgoAliases = map[string]string{
|
|
"C.int8_t": "int8",
|
|
"C.int16_t": "int16",
|
|
"C.int32_t": "int32",
|
|
"C.int64_t": "int64",
|
|
"C.uint8_t": "uint8",
|
|
"C.uint16_t": "uint16",
|
|
"C.uint32_t": "uint32",
|
|
"C.uint64_t": "uint64",
|
|
"C.uintptr_t": "uintptr",
|
|
}
|
|
|
|
// 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": {},
|
|
"schar": {},
|
|
"uchar": {},
|
|
"short": {},
|
|
"ushort": {},
|
|
"int": {},
|
|
"uint": {},
|
|
"long": {},
|
|
"ulong": {},
|
|
"longlong": {},
|
|
"ulonglong": {},
|
|
}
|
|
|
|
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
|
// somehow from C. This is done by adding some typedefs to get the size of each
|
|
// type.
|
|
const cgoTypes = `
|
|
# 1 "<cgo>"
|
|
typedef char _Cgo_char;
|
|
typedef signed char _Cgo_schar;
|
|
typedef unsigned char _Cgo_uchar;
|
|
typedef short _Cgo_short;
|
|
typedef unsigned short _Cgo_ushort;
|
|
typedef int _Cgo_int;
|
|
typedef unsigned int _Cgo_uint;
|
|
typedef long _Cgo_long;
|
|
typedef unsigned long _Cgo_ulong;
|
|
typedef long long _Cgo_longlong;
|
|
typedef unsigned long long _Cgo_ulonglong;
|
|
`
|
|
|
|
// First part of the generated Go file. Written here as Go because that's much
|
|
// easier than constructing the entire AST in memory.
|
|
// The string/bytes functions below implement C.CString etc. To make sure the
|
|
// runtime doesn't need to know the C int type, lengths are converted to uintptr
|
|
// first.
|
|
// These functions will be modified to get a "C." prefix, so the source below
|
|
// doesn't reflect the final AST.
|
|
const generatedGoFilePrefix = `
|
|
import "unsafe"
|
|
|
|
var _ unsafe.Pointer
|
|
|
|
//go:linkname C.CString runtime.cgo_CString
|
|
func CString(string) *C.char
|
|
|
|
//go:linkname C.GoString runtime.cgo_GoString
|
|
func GoString(*C.char) string
|
|
|
|
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
|
func __GoStringN(*C.char, uintptr) string
|
|
|
|
func GoStringN(cstr *C.char, length C.int) string {
|
|
return C.__GoStringN(cstr, uintptr(length))
|
|
}
|
|
|
|
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
|
func __GoBytes(unsafe.Pointer, uintptr) []byte
|
|
|
|
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
|
return C.__GoBytes(ptr, uintptr(length))
|
|
}
|
|
`
|
|
|
|
// 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, the CGo header snippets that should be compiled (for inline
|
|
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
|
|
// hashes of the accessed C header files. If there is one or more error, it
|
|
// returns these in the []error slice but still modifies the AST.
|
|
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
|
|
p := &cgoPackage{
|
|
currentDir: dir,
|
|
fset: fset,
|
|
tokenFiles: map[string]*token.File{},
|
|
missingSymbols: map[string]struct{}{},
|
|
constants: map[string]constantInfo{},
|
|
functions: map[string]*functionInfo{},
|
|
globals: map[string]globalInfo{},
|
|
typedefs: map[string]*typedefInfo{},
|
|
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
|
enums: map[string]enumInfo{},
|
|
visitedFiles: map[string][]byte{},
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Find the absolute path for this package.
|
|
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
|
if err != nil {
|
|
return nil, nil, nil, nil, nil, []error{
|
|
scanner.Error{
|
|
Pos: fset.Position(files[0].Pos()),
|
|
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
|
},
|
|
}
|
|
}
|
|
p.packageDir = filepath.Dir(packagePath)
|
|
|
|
// Construct a new in-memory AST for CGo declarations of this package.
|
|
// The first part is written as Go code that is then parsed, but more code
|
|
// is added later to the AST to declare functions, globals, etc.
|
|
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
|
|
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
|
|
if err != nil {
|
|
// This is always a bug in the cgo package.
|
|
panic("unexpected error: " + err.Error())
|
|
}
|
|
// If the Comments field is not set to nil, the fmt package will get
|
|
// confused about where comments should go.
|
|
p.generated.Comments = nil
|
|
// Adjust some of the functions in there.
|
|
for _, decl := range p.generated.Decls {
|
|
switch decl := decl.(type) {
|
|
case *ast.FuncDecl:
|
|
switch decl.Name.Name {
|
|
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
|
|
// Adjust the name to have a "C." prefix so it is correctly
|
|
// resolved.
|
|
decl.Name.Name = "C." + decl.Name.Name
|
|
}
|
|
}
|
|
}
|
|
// Patch some types, for example *C.char in C.CString.
|
|
astutil.Apply(p.generated, p.walker, nil)
|
|
|
|
// Find all C.* symbols.
|
|
for _, f := range files {
|
|
astutil.Apply(f, p.findMissingCGoNames, nil)
|
|
}
|
|
for name := range builtinAliases {
|
|
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
|
}
|
|
|
|
// Find `import "C"` C fragments in the file.
|
|
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
|
|
for i, f := range files {
|
|
var cgoHeader string
|
|
for i := 0; i < len(f.Decls); i++ {
|
|
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 {
|
|
// This should not happen. An import path that is not properly
|
|
// quoted should not exist in a correct AST.
|
|
panic("could not parse import path: " + err.Error())
|
|
}
|
|
if path != "C" {
|
|
continue
|
|
}
|
|
|
|
// Remove this import declaration.
|
|
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
|
i--
|
|
|
|
if genDecl.Doc == nil {
|
|
continue
|
|
}
|
|
|
|
// Iterate through all parts of the CGo header. Note that every //
|
|
// line is a new comment.
|
|
position := fset.Position(genDecl.Doc.Pos())
|
|
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
|
|
for _, comment := range genDecl.Doc.List {
|
|
// Find all #cgo lines, extract and use their contents, and
|
|
// replace the lines with spaces (to preserve locations).
|
|
c := p.parseCGoPreprocessorLines(comment.Text, comment.Slash)
|
|
|
|
// Change the comment (which is still in Go syntax, with // and
|
|
// /* */ ) to a regular string by replacing the start/end
|
|
// markers of comments with spaces.
|
|
// It is similar to the Text() method but differs in that it
|
|
// doesn't strip anything and tries to keep all offsets correct
|
|
// by adding spaces and newlines where necessary.
|
|
if c[1] == '/' { /* comment */
|
|
c = " " + c[2:]
|
|
} else { // comment
|
|
c = " " + c[2:len(c)-2]
|
|
}
|
|
fragment += c + "\n"
|
|
}
|
|
cgoHeader += fragment
|
|
}
|
|
|
|
cgoHeaders[i] = cgoHeader
|
|
}
|
|
|
|
// Define CFlags that will be used while parsing the package.
|
|
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
|
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
|
// have better alternatives anyway.
|
|
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
|
|
cflagsForCGo = append(cflagsForCGo, p.cflags...)
|
|
if clangHeaders != "" {
|
|
cflagsForCGo = append(cflagsForCGo, "-isystem", clangHeaders)
|
|
}
|
|
|
|
// Process CGo imports for each file.
|
|
for i, f := range files {
|
|
p.parseFragment(cgoHeaders[i]+cgoTypes, cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()))
|
|
}
|
|
|
|
// Declare functions found by libclang.
|
|
p.addFuncDecls()
|
|
|
|
// Declare stub function pointer values found by libclang.
|
|
p.addFuncPtrDecls()
|
|
|
|
// Declare globals found by libclang.
|
|
p.addConstDecls()
|
|
|
|
// Declare globals found by libclang.
|
|
p.addVarDecls()
|
|
|
|
// Forward C types to Go types (like C.uint32_t -> uint32).
|
|
p.addTypeAliases()
|
|
|
|
// Add type declarations for C types, declared using typedef in C.
|
|
p.addTypedefs()
|
|
|
|
// Add elaborated types for C structs and unions.
|
|
p.addElaboratedTypes()
|
|
|
|
// Add enum types and enum constants for C enums.
|
|
p.addEnumTypes()
|
|
|
|
// Patch the AST to use the declared types and functions.
|
|
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 p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
|
|
}
|
|
|
|
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
|
// relative flags into absolute flags, if they are relative. This is necessary
|
|
// because the C compiler is usually not invoked from the package path.
|
|
func (p *cgoPackage) makePathsAbsolute(args []string) {
|
|
nextIsPath := false
|
|
for i, arg := range args {
|
|
if nextIsPath {
|
|
if !filepath.IsAbs(arg) {
|
|
args[i] = filepath.Join(p.packageDir, arg)
|
|
}
|
|
}
|
|
if arg == "-I" || arg == "-L" {
|
|
nextIsPath = true
|
|
continue
|
|
}
|
|
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
|
|
path := arg[2:]
|
|
if !filepath.IsAbs(path) {
|
|
args[i] = arg[:2] + filepath.Join(p.packageDir, path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseCGoPreprocessorLines reads #cgo pseudo-preprocessor lines in the source
|
|
// text (import "C" fragment), stores their information such as CFLAGS, and
|
|
// returns the same text but with those #cgo lines replaced by spaces (to keep
|
|
// position offsets the same).
|
|
func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) string {
|
|
for {
|
|
// Extract the #cgo line, and replace it with spaces.
|
|
// Replacing with spaces makes sure that error locations are
|
|
// still correct, while not interfering with parsing in any way.
|
|
lineStart := strings.Index(text, "#cgo ")
|
|
if lineStart < 0 {
|
|
break
|
|
}
|
|
lineLen := strings.IndexByte(text[lineStart:], '\n')
|
|
if lineLen < 0 {
|
|
lineLen = len(text) - lineStart
|
|
}
|
|
lineEnd := lineStart + lineLen
|
|
line := text[lineStart:lineEnd]
|
|
spaces := make([]byte, len(line))
|
|
for i := range spaces {
|
|
spaces[i] = ' '
|
|
}
|
|
text = text[:lineStart] + string(spaces) + text[lineEnd:]
|
|
|
|
// Get the text before the colon in the #cgo directive.
|
|
colon := strings.IndexByte(line, ':')
|
|
if colon < 0 {
|
|
p.addErrorAfter(pos, text[:lineStart], "missing colon in #cgo line")
|
|
continue
|
|
}
|
|
|
|
// Extract the fields before the colon. These fields are a list
|
|
// of build tags and the C environment variable.
|
|
fields := strings.Fields(line[4:colon])
|
|
if len(fields) == 0 {
|
|
p.addErrorAfter(pos, text[:lineStart+colon-1], "invalid #cgo line")
|
|
continue
|
|
}
|
|
|
|
if len(fields) > 1 {
|
|
p.addErrorAfter(pos, text[:lineStart+5], "not implemented: build constraints in #cgo line")
|
|
continue
|
|
}
|
|
|
|
name := fields[len(fields)-1]
|
|
value := line[colon+1:]
|
|
switch name {
|
|
case "CFLAGS":
|
|
flags, err := shlex.Split(value)
|
|
if err != nil {
|
|
// TODO: find the exact location where the error happened.
|
|
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
|
continue
|
|
}
|
|
if err := checkCompilerFlags(name, flags); err != nil {
|
|
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
|
|
continue
|
|
}
|
|
p.makePathsAbsolute(flags)
|
|
p.cflags = append(p.cflags, flags...)
|
|
case "LDFLAGS":
|
|
flags, err := shlex.Split(value)
|
|
if err != nil {
|
|
// TODO: find the exact location where the error happened.
|
|
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
|
continue
|
|
}
|
|
if err := checkLinkerFlags(name, flags); err != nil {
|
|
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
|
|
continue
|
|
}
|
|
p.makePathsAbsolute(flags)
|
|
p.ldflags = append(p.ldflags, flags...)
|
|
default:
|
|
startPos := strings.LastIndex(line[4:colon], name) + 4
|
|
p.addErrorAfter(pos, text[:lineStart+startPos], "invalid #cgo line: "+name)
|
|
continue
|
|
}
|
|
}
|
|
return text
|
|
}
|
|
|
|
// addFuncDecls adds the C function declarations found by libclang in the
|
|
// comment above the `import "C"` statement.
|
|
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 := p.functions[name]
|
|
obj := &ast.Object{
|
|
Kind: ast.Fun,
|
|
Name: "C." + name,
|
|
}
|
|
args := make([]*ast.Field, len(fn.args))
|
|
decl := &ast.FuncDecl{
|
|
Doc: &ast.CommentGroup{
|
|
List: []*ast.Comment{
|
|
{
|
|
Slash: fn.pos - 1,
|
|
Text: "//export " + name,
|
|
},
|
|
},
|
|
},
|
|
Name: &ast.Ident{
|
|
NamePos: fn.pos,
|
|
Name: "C." + name,
|
|
Obj: obj,
|
|
},
|
|
Type: &ast.FuncType{
|
|
Func: fn.pos,
|
|
Params: &ast.FieldList{
|
|
Opening: fn.pos,
|
|
List: args,
|
|
Closing: fn.pos,
|
|
},
|
|
Results: fn.results,
|
|
},
|
|
}
|
|
if fn.variadic {
|
|
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
|
|
Slash: fn.pos - 1,
|
|
Text: "//go:variadic",
|
|
})
|
|
}
|
|
obj.Decl = decl
|
|
for i, arg := range fn.args {
|
|
args[i] = &ast.Field{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: fn.pos,
|
|
Name: arg.name,
|
|
Obj: &ast.Object{
|
|
Kind: ast.Var,
|
|
Name: arg.name,
|
|
Decl: decl,
|
|
},
|
|
},
|
|
},
|
|
Type: arg.typeExpr,
|
|
}
|
|
}
|
|
p.generated.Decls = append(p.generated.Decls, decl)
|
|
}
|
|
}
|
|
|
|
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
|
// values will later be replaced with the real values in the compiler.
|
|
// It adds code like the following to the AST:
|
|
//
|
|
// var (
|
|
// C.add unsafe.Pointer
|
|
// C.mul unsafe.Pointer
|
|
// // ...
|
|
// )
|
|
func (p *cgoPackage) addFuncPtrDecls() {
|
|
if len(p.functions) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.functions))
|
|
for name := range p.functions {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.VAR,
|
|
Lparen: token.NoPos,
|
|
Rparen: token.NoPos,
|
|
}
|
|
fn := p.functions[name]
|
|
obj := &ast.Object{
|
|
Kind: ast.Typ,
|
|
Name: "C." + name + "$funcaddr",
|
|
}
|
|
valueSpec := &ast.ValueSpec{
|
|
Names: []*ast.Ident{{
|
|
NamePos: fn.pos,
|
|
Name: "C." + name + "$funcaddr",
|
|
Obj: obj,
|
|
}},
|
|
Type: &ast.SelectorExpr{
|
|
X: &ast.Ident{
|
|
NamePos: fn.pos,
|
|
Name: "unsafe",
|
|
},
|
|
Sel: &ast.Ident{
|
|
NamePos: fn.pos,
|
|
Name: "Pointer",
|
|
},
|
|
},
|
|
}
|
|
obj.Decl = valueSpec
|
|
gen.Specs = append(gen.Specs, valueSpec)
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
// addConstDecls declares external C constants in the Go source.
|
|
// It adds code like the following to the AST:
|
|
//
|
|
// const C.CONST_INT = 5
|
|
// const C.CONST_FLOAT = 5.8
|
|
// // ...
|
|
func (p *cgoPackage) addConstDecls() {
|
|
if len(p.constants) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.constants))
|
|
for name := range p.constants {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.CONST,
|
|
Lparen: token.NoPos,
|
|
Rparen: token.NoPos,
|
|
}
|
|
constVal := p.constants[name]
|
|
obj := &ast.Object{
|
|
Kind: ast.Con,
|
|
Name: "C." + name,
|
|
}
|
|
valueSpec := &ast.ValueSpec{
|
|
Names: []*ast.Ident{{
|
|
NamePos: constVal.pos,
|
|
Name: "C." + name,
|
|
Obj: obj,
|
|
}},
|
|
Values: []ast.Expr{constVal.expr},
|
|
}
|
|
obj.Decl = valueSpec
|
|
gen.Specs = append(gen.Specs, valueSpec)
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
// addVarDecls declares external C globals in the Go source.
|
|
// It adds code like the following to the AST:
|
|
//
|
|
// var C.globalInt int
|
|
// var C.globalBool bool
|
|
// // ...
|
|
func (p *cgoPackage) addVarDecls() {
|
|
if len(p.globals) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.globals))
|
|
for name := range p.globals {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
global := p.globals[name]
|
|
gen := &ast.GenDecl{
|
|
TokPos: global.pos,
|
|
Tok: token.VAR,
|
|
Lparen: token.NoPos,
|
|
Rparen: token.NoPos,
|
|
Doc: &ast.CommentGroup{
|
|
List: []*ast.Comment{
|
|
{
|
|
Slash: global.pos - 1,
|
|
Text: "//go:extern " + name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
obj := &ast.Object{
|
|
Kind: ast.Var,
|
|
Name: "C." + name,
|
|
}
|
|
valueSpec := &ast.ValueSpec{
|
|
Names: []*ast.Ident{{
|
|
NamePos: global.pos,
|
|
Name: "C." + name,
|
|
Obj: obj,
|
|
}},
|
|
Type: global.typeExpr,
|
|
}
|
|
obj.Decl = valueSpec
|
|
gen.Specs = append(gen.Specs, valueSpec)
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
|
// It adds code like the following to the AST:
|
|
//
|
|
// type C.int8_t = int8
|
|
// type C.int16_t = int16
|
|
// // ...
|
|
func (p *cgoPackage) addTypeAliases() {
|
|
aliasKeys := make([]string, 0, len(cgoAliases))
|
|
for key := range cgoAliases {
|
|
aliasKeys = append(aliasKeys, key)
|
|
}
|
|
sort.Strings(aliasKeys)
|
|
for _, typeName := range aliasKeys {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.TYPE,
|
|
Lparen: token.NoPos,
|
|
Rparen: token.NoPos,
|
|
}
|
|
goTypeName := cgoAliases[typeName]
|
|
obj := &ast.Object{
|
|
Kind: ast.Typ,
|
|
Name: typeName,
|
|
}
|
|
typeSpec := &ast.TypeSpec{
|
|
Name: &ast.Ident{
|
|
NamePos: token.NoPos,
|
|
Name: typeName,
|
|
Obj: obj,
|
|
},
|
|
Assign: p.generatedPos,
|
|
Type: &ast.Ident{
|
|
NamePos: token.NoPos,
|
|
Name: goTypeName,
|
|
},
|
|
}
|
|
obj.Decl = typeSpec
|
|
gen.Specs = append(gen.Specs, typeSpec)
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
func (p *cgoPackage) addTypedefs() {
|
|
if len(p.typedefs) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.typedefs))
|
|
for name := range p.typedefs {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.TYPE,
|
|
}
|
|
typedef := p.typedefs[name]
|
|
typeName := "C." + name
|
|
isAlias := true
|
|
if strings.HasPrefix(name, "_Cgo_") {
|
|
typeName = "C." + name[len("_Cgo_"):]
|
|
isAlias = false // C.short etc. should not be aliased to the equivalent Go type (not portable)
|
|
}
|
|
if _, ok := cgoAliases[typeName]; ok {
|
|
// This is a type that also exists in Go (defined in stdint.h).
|
|
continue
|
|
}
|
|
obj := &ast.Object{
|
|
Kind: ast.Typ,
|
|
Name: typeName,
|
|
}
|
|
typeSpec := &ast.TypeSpec{
|
|
Name: &ast.Ident{
|
|
NamePos: typedef.pos,
|
|
Name: typeName,
|
|
Obj: obj,
|
|
},
|
|
Type: typedef.typeExpr,
|
|
}
|
|
if isAlias {
|
|
typeSpec.Assign = typedef.pos
|
|
}
|
|
obj.Decl = typeSpec
|
|
gen.Specs = append(gen.Specs, typeSpec)
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
|
// foo" or "union foo" types, often used in a typedef.
|
|
//
|
|
// See also:
|
|
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
|
func (p *cgoPackage) addElaboratedTypes() {
|
|
if len(p.elaboratedTypes) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.elaboratedTypes))
|
|
for name := range p.elaboratedTypes {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.TYPE,
|
|
}
|
|
typ := p.elaboratedTypes[name]
|
|
typeName := "C." + name
|
|
obj := &ast.Object{
|
|
Kind: ast.Typ,
|
|
Name: typeName,
|
|
}
|
|
typeExpr := typ.typeExpr
|
|
if typ.unionSize != 0 {
|
|
// Create getters/setters.
|
|
for _, field := range typ.typeExpr.Fields.List {
|
|
if len(field.Names) != 1 {
|
|
p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
|
|
continue
|
|
}
|
|
p.createUnionAccessor(field, typeName)
|
|
}
|
|
// Convert to a single-field struct type.
|
|
typeExpr = p.makeUnionField(typ)
|
|
if typeExpr == nil {
|
|
// There was an error, that was already added to the list of
|
|
// errors.
|
|
continue
|
|
}
|
|
}
|
|
typeSpec := &ast.TypeSpec{
|
|
Name: &ast.Ident{
|
|
NamePos: typ.pos,
|
|
Name: typeName,
|
|
Obj: obj,
|
|
},
|
|
Type: typeExpr,
|
|
}
|
|
obj.Decl = typeSpec
|
|
gen.Specs = append(gen.Specs, typeSpec)
|
|
// If this struct has bitfields, create getters for them.
|
|
for _, bitfield := range typ.bitfields {
|
|
p.createBitfieldGetter(bitfield, typeName)
|
|
p.createBitfieldSetter(bitfield, typeName)
|
|
}
|
|
p.generated.Decls = append(p.generated.Decls, gen)
|
|
}
|
|
}
|
|
|
|
// makeUnionField creates a new struct from an existing *elaboratedTypeInfo,
|
|
// that has just a single field that must be accessed through special accessors.
|
|
// It returns nil when there is an error. In case of an error, that error has
|
|
// already been added to the list of errors using p.addError.
|
|
func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
|
|
unionFieldTypeName, ok := map[int64]string{
|
|
1: "uint8",
|
|
2: "uint16",
|
|
4: "uint32",
|
|
8: "uint64",
|
|
}[typ.unionAlign]
|
|
if !ok {
|
|
p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign))
|
|
return nil
|
|
}
|
|
var unionFieldType ast.Expr = &ast.Ident{
|
|
NamePos: token.NoPos,
|
|
Name: unionFieldTypeName,
|
|
}
|
|
if typ.unionSize != typ.unionAlign {
|
|
// A plain struct{uintX} isn't enough, we have to make a
|
|
// struct{[N]uintX} to make the union big enough.
|
|
if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize {
|
|
p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign))
|
|
return nil
|
|
}
|
|
unionFieldType = &ast.ArrayType{
|
|
Len: &ast.BasicLit{
|
|
Kind: token.INT,
|
|
Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10),
|
|
},
|
|
Elt: unionFieldType,
|
|
}
|
|
}
|
|
return &ast.StructType{
|
|
Struct: typ.typeExpr.Struct,
|
|
Fields: &ast.FieldList{
|
|
Opening: typ.typeExpr.Fields.Opening,
|
|
List: []*ast.Field{{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: typ.typeExpr.Fields.Opening,
|
|
Name: "$union",
|
|
},
|
|
},
|
|
Type: unionFieldType,
|
|
}},
|
|
Closing: typ.typeExpr.Fields.Closing,
|
|
},
|
|
}
|
|
}
|
|
|
|
// createUnionAccessor creates a function that returns a typed pointer to a
|
|
// union field for each field in a union. For example:
|
|
//
|
|
// func (union *C.union_1) unionfield_d() *float64 {
|
|
// return (*float64)(unsafe.Pointer(&union.$union))
|
|
// }
|
|
//
|
|
// Where C.union_1 is defined as:
|
|
//
|
|
// type C.union_1 struct{
|
|
// $union uint64
|
|
// }
|
|
//
|
|
// The returned pointer can be used to get or set the field, or get the pointer
|
|
// to a subfield.
|
|
func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
|
if len(field.Names) != 1 {
|
|
panic("number of names in union field must be exactly 1")
|
|
}
|
|
fieldName := field.Names[0]
|
|
pos := fieldName.NamePos
|
|
|
|
// The method receiver.
|
|
receiver := &ast.SelectorExpr{
|
|
X: &ast.Ident{
|
|
NamePos: pos,
|
|
Name: "union",
|
|
Obj: nil,
|
|
},
|
|
Sel: &ast.Ident{
|
|
NamePos: pos,
|
|
Name: "$union",
|
|
},
|
|
}
|
|
|
|
// Get the address of the $union field.
|
|
receiverPtr := &ast.UnaryExpr{
|
|
Op: token.AND,
|
|
X: receiver,
|
|
}
|
|
|
|
// Cast to unsafe.Pointer.
|
|
sourcePointer := &ast.CallExpr{
|
|
Fun: &ast.SelectorExpr{
|
|
X: &ast.Ident{Name: "unsafe"},
|
|
Sel: &ast.Ident{Name: "Pointer"},
|
|
},
|
|
Args: []ast.Expr{receiverPtr},
|
|
}
|
|
|
|
// Cast to the target pointer type.
|
|
targetPointer := &ast.CallExpr{
|
|
Lparen: pos,
|
|
Fun: &ast.ParenExpr{
|
|
Lparen: pos,
|
|
X: &ast.StarExpr{
|
|
X: field.Type,
|
|
},
|
|
Rparen: pos,
|
|
},
|
|
Args: []ast.Expr{sourcePointer},
|
|
Rparen: pos,
|
|
}
|
|
|
|
// Create the accessor function.
|
|
accessor := &ast.FuncDecl{
|
|
Recv: &ast.FieldList{
|
|
Opening: pos,
|
|
List: []*ast.Field{
|
|
{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: pos,
|
|
Name: "union",
|
|
},
|
|
},
|
|
Type: &ast.StarExpr{
|
|
Star: pos,
|
|
X: &ast.Ident{
|
|
NamePos: pos,
|
|
Name: typeName,
|
|
Obj: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Closing: pos,
|
|
},
|
|
Name: &ast.Ident{
|
|
NamePos: pos,
|
|
Name: "unionfield_" + fieldName.Name,
|
|
},
|
|
Type: &ast.FuncType{
|
|
Func: pos,
|
|
Params: &ast.FieldList{
|
|
Opening: pos,
|
|
Closing: pos,
|
|
},
|
|
Results: &ast.FieldList{
|
|
List: []*ast.Field{
|
|
{
|
|
Type: &ast.StarExpr{
|
|
Star: pos,
|
|
X: field.Type,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Body: &ast.BlockStmt{
|
|
Lbrace: pos,
|
|
List: []ast.Stmt{
|
|
&ast.ReturnStmt{
|
|
Return: pos,
|
|
Results: []ast.Expr{
|
|
targetPointer,
|
|
},
|
|
},
|
|
},
|
|
Rbrace: pos,
|
|
},
|
|
}
|
|
p.generated.Decls = append(p.generated.Decls, accessor)
|
|
}
|
|
|
|
// createBitfieldGetter creates a bitfield getter function like the following:
|
|
//
|
|
// func (s *C.struct_foo) bitfield_b() byte {
|
|
// return (s.__bitfield_1 >> 5) & 0x1
|
|
// }
|
|
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
|
|
// The value to return from the getter.
|
|
// Not complete: this is just an expression to get the complete field.
|
|
var result ast.Expr = &ast.SelectorExpr{
|
|
X: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "s",
|
|
Obj: nil,
|
|
},
|
|
Sel: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: bitfield.field.Names[0].Name,
|
|
},
|
|
}
|
|
if bitfield.startBit != 0 {
|
|
// Shift to the right by .startBit so that fields that come before are
|
|
// shifted off.
|
|
result = &ast.BinaryExpr{
|
|
X: result,
|
|
OpPos: bitfield.pos,
|
|
Op: token.SHR,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: strconv.FormatInt(bitfield.startBit, 10),
|
|
},
|
|
}
|
|
}
|
|
if bitfield.endBit != 0 {
|
|
// Mask off the high bits so that fields that come after this field are
|
|
// masked off.
|
|
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
|
|
result = &ast.BinaryExpr{
|
|
X: result,
|
|
OpPos: bitfield.pos,
|
|
Op: token.AND,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: "0x" + strconv.FormatUint(and, 16),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Create the getter function.
|
|
getter := &ast.FuncDecl{
|
|
Recv: &ast.FieldList{
|
|
Opening: bitfield.pos,
|
|
List: []*ast.Field{
|
|
{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: bitfield.pos,
|
|
Name: "s",
|
|
Obj: &ast.Object{
|
|
Kind: ast.Var,
|
|
Name: "s",
|
|
Decl: nil,
|
|
},
|
|
},
|
|
},
|
|
Type: &ast.StarExpr{
|
|
Star: bitfield.pos,
|
|
X: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: typeName,
|
|
Obj: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Closing: bitfield.pos,
|
|
},
|
|
Name: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "bitfield_" + bitfield.name,
|
|
},
|
|
Type: &ast.FuncType{
|
|
Func: bitfield.pos,
|
|
Params: &ast.FieldList{
|
|
Opening: bitfield.pos,
|
|
Closing: bitfield.pos,
|
|
},
|
|
Results: &ast.FieldList{
|
|
List: []*ast.Field{
|
|
{
|
|
Type: bitfield.field.Type,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Body: &ast.BlockStmt{
|
|
Lbrace: bitfield.pos,
|
|
List: []ast.Stmt{
|
|
&ast.ReturnStmt{
|
|
Return: bitfield.pos,
|
|
Results: []ast.Expr{
|
|
result,
|
|
},
|
|
},
|
|
},
|
|
Rbrace: bitfield.pos,
|
|
},
|
|
}
|
|
p.generated.Decls = append(p.generated.Decls, getter)
|
|
}
|
|
|
|
// createBitfieldSetter creates a bitfield setter function like the following:
|
|
//
|
|
// func (s *C.struct_foo) set_bitfield_b(value byte) {
|
|
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
|
|
// }
|
|
//
|
|
// Or the following:
|
|
//
|
|
// func (s *C.struct_foo) set_bitfield_c(value byte) {
|
|
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
|
|
// }
|
|
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
|
|
// The full field with all bitfields.
|
|
var field ast.Expr = &ast.SelectorExpr{
|
|
X: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "s",
|
|
Obj: nil,
|
|
},
|
|
Sel: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: bitfield.field.Names[0].Name,
|
|
},
|
|
}
|
|
// The value to insert into the field.
|
|
var valueToInsert ast.Expr = &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "value",
|
|
}
|
|
|
|
if bitfield.endBit != 0 {
|
|
// Make sure the value is in range with a mask.
|
|
valueToInsert = &ast.BinaryExpr{
|
|
X: valueToInsert,
|
|
OpPos: bitfield.pos,
|
|
Op: token.AND,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
|
|
},
|
|
}
|
|
// Create a mask for the AND NOT operation.
|
|
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
|
|
// Zero the bits in the field that will soon be inserted.
|
|
field = &ast.BinaryExpr{
|
|
X: field,
|
|
OpPos: bitfield.pos,
|
|
Op: token.AND_NOT,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: "0x" + strconv.FormatUint(mask, 16),
|
|
},
|
|
}
|
|
} else { // bitfield.endBit == 0
|
|
// We don't know exactly how many high bits should be zeroed. So we do
|
|
// something different: keep the low bits with a mask and OR the new
|
|
// value with it.
|
|
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
|
|
// Extract the lower bits.
|
|
field = &ast.BinaryExpr{
|
|
X: field,
|
|
OpPos: bitfield.pos,
|
|
Op: token.AND,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: "0x" + strconv.FormatUint(mask, 16),
|
|
},
|
|
}
|
|
}
|
|
|
|
// Bitwise OR with the new value (after the new value has been shifted).
|
|
field = &ast.BinaryExpr{
|
|
X: field,
|
|
OpPos: bitfield.pos,
|
|
Op: token.OR,
|
|
Y: &ast.BinaryExpr{
|
|
X: valueToInsert,
|
|
OpPos: bitfield.pos,
|
|
Op: token.SHL,
|
|
Y: &ast.BasicLit{
|
|
ValuePos: bitfield.pos,
|
|
Kind: token.INT,
|
|
Value: strconv.FormatInt(bitfield.startBit, 10),
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create the setter function.
|
|
setter := &ast.FuncDecl{
|
|
Recv: &ast.FieldList{
|
|
Opening: bitfield.pos,
|
|
List: []*ast.Field{
|
|
{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: bitfield.pos,
|
|
Name: "s",
|
|
Obj: &ast.Object{
|
|
Kind: ast.Var,
|
|
Name: "s",
|
|
Decl: nil,
|
|
},
|
|
},
|
|
},
|
|
Type: &ast.StarExpr{
|
|
Star: bitfield.pos,
|
|
X: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: typeName,
|
|
Obj: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Closing: bitfield.pos,
|
|
},
|
|
Name: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "set_bitfield_" + bitfield.name,
|
|
},
|
|
Type: &ast.FuncType{
|
|
Func: bitfield.pos,
|
|
Params: &ast.FieldList{
|
|
Opening: bitfield.pos,
|
|
List: []*ast.Field{
|
|
{
|
|
Names: []*ast.Ident{
|
|
{
|
|
NamePos: bitfield.pos,
|
|
Name: "value",
|
|
Obj: nil,
|
|
},
|
|
},
|
|
Type: bitfield.field.Type,
|
|
},
|
|
},
|
|
Closing: bitfield.pos,
|
|
},
|
|
},
|
|
Body: &ast.BlockStmt{
|
|
Lbrace: bitfield.pos,
|
|
List: []ast.Stmt{
|
|
&ast.AssignStmt{
|
|
Lhs: []ast.Expr{
|
|
&ast.SelectorExpr{
|
|
X: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: "s",
|
|
Obj: nil,
|
|
},
|
|
Sel: &ast.Ident{
|
|
NamePos: bitfield.pos,
|
|
Name: bitfield.field.Names[0].Name,
|
|
},
|
|
},
|
|
},
|
|
TokPos: bitfield.pos,
|
|
Tok: token.ASSIGN,
|
|
Rhs: []ast.Expr{
|
|
field,
|
|
},
|
|
},
|
|
},
|
|
Rbrace: bitfield.pos,
|
|
},
|
|
}
|
|
p.generated.Decls = append(p.generated.Decls, setter)
|
|
}
|
|
|
|
// addEnumTypes adds C enums to the AST. For example, the following C code:
|
|
//
|
|
// enum option {
|
|
// optionA,
|
|
// optionB = 5,
|
|
// };
|
|
//
|
|
// is translated to the following Go code equivalent:
|
|
//
|
|
// type C.enum_option int32
|
|
//
|
|
// The constants are treated just like macros so are inserted into the AST by
|
|
// addConstDecls.
|
|
// See also: https://en.cppreference.com/w/c/language/enum
|
|
func (p *cgoPackage) addEnumTypes() {
|
|
if len(p.enums) == 0 {
|
|
return
|
|
}
|
|
names := make([]string, 0, len(p.enums))
|
|
for name := range p.enums {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
gen := &ast.GenDecl{
|
|
TokPos: token.NoPos,
|
|
Tok: token.TYPE,
|
|
}
|
|
typ := p.enums[name]
|
|
typeName := "C.enum_" + name
|
|
obj := &ast.Object{
|
|
Kind: ast.Typ,
|
|
Name: typeName,
|
|
}
|
|
typeSpec := &ast.TypeSpec{
|
|
Name: &ast.Ident{
|
|
NamePos: typ.pos,
|
|
Name: typeName,
|
|
Obj: obj,
|
|
},
|
|
Type: typ.typeExpr,
|
|
}
|
|
obj.Decl = typeSpec
|
|
gen.Specs = append(gen.Specs, typeSpec)
|
|
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 (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
|
switch node := cursor.Node().(type) {
|
|
case *ast.SelectorExpr:
|
|
x, ok := node.X.(*ast.Ident)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if x.Name == "C" {
|
|
name := node.Sel.Name
|
|
if _, ok := builtinAliases[name]; ok {
|
|
name = "_Cgo_" + name
|
|
}
|
|
p.missingSymbols[name] = struct{}{}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
|
// 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 (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
|
|
switch node := cursor.Node().(type) {
|
|
case *ast.CallExpr:
|
|
fun, ok := node.Fun.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
x, ok := fun.X.(*ast.Ident)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
|
node.Fun = &ast.Ident{
|
|
NamePos: x.NamePos,
|
|
Name: "C." + fun.Sel.Name,
|
|
}
|
|
}
|
|
case *ast.SelectorExpr:
|
|
x, ok := node.X.(*ast.Ident)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if x.Name == "C" {
|
|
name := "C." + node.Sel.Name
|
|
if _, ok := p.functions[node.Sel.Name]; ok {
|
|
name += "$funcaddr"
|
|
}
|
|
cursor.Replace(&ast.Ident{
|
|
NamePos: x.NamePos,
|
|
Name: name,
|
|
})
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// renameFieldKeywords renames all reserved words in Go to some other field name
|
|
// with a "_" prefix. For example, it renames `type` to `_type`.
|
|
//
|
|
// See: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
|
|
func renameFieldKeywords(fieldList *ast.FieldList) {
|
|
renameFieldName(fieldList, "type")
|
|
}
|
|
|
|
// renameFieldName renames a given field name to a name with a "_" prepended. It
|
|
// makes sure to do the same thing for any field sharing the same name.
|
|
func renameFieldName(fieldList *ast.FieldList, name string) {
|
|
var ident *ast.Ident
|
|
for _, f := range fieldList.List {
|
|
for _, n := range f.Names {
|
|
if n.Name == name {
|
|
ident = n
|
|
}
|
|
}
|
|
}
|
|
if ident == nil {
|
|
return
|
|
}
|
|
renameFieldName(fieldList, "_"+name)
|
|
ident.Name = "_" + ident.Name
|
|
}
|
|
|