tinygo/loader/cgo.go
2018-12-10 15:38:02 +01:00

340 строки
7,9 КиБ
Go

package loader
// 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).
import (
"go/ast"
"go/token"
"sort"
"strconv"
"strings"
)
// fileInfo holds all Cgo-related information of a given *ast.File.
type fileInfo struct {
*ast.File
filename string
functions []*functionInfo
typedefs []*typedefInfo
importCPos token.Pos
}
// functionInfo stores some information about a Cgo function found by libclang
// and declared in the AST.
type functionInfo struct {
name string
args []paramInfo
result string
}
// paramInfo is a parameter of a Cgo function (see functionInfo).
type paramInfo struct {
name string
typeName string
}
// typedefInfo contains information about a single typedef in C.
type typedefInfo struct {
newName string // newly defined type name
oldName string // old type name, may be something like "unsigned long long"
size int // size in bytes
}
// 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",
}
// 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 = `
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;
`
// 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) error {
info := &fileInfo{
File: f,
filename: filename,
}
// 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()
// Stored for later use by generated functions, to use a somewhat sane
// source location.
info.importCPos = spec.Path.ValuePos
err = info.parseFragment(cgoComment + cgoTypes)
if err != nil {
return err
}
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i--
}
// Print the AST, for debugging.
//ast.Print(p.fset, f)
// Declare functions found by libclang.
info.addFuncDecls()
// Forward C types to Go types (like C.uint32_t -> uint32).
info.addTypeAliases()
// Add type declarations for C types, declared using typeef in C.
info.addTypedefs()
// Patch the AST to use the declared types and functions.
ast.Inspect(f, info.walker)
return nil
}
// 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.
for _, fn := range info.functions {
obj := &ast.Object{
Kind: ast.Fun,
Name: "C." + fn.name,
}
args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: "C." + fn.name,
Obj: obj,
},
Type: &ast.FuncType{
Func: info.importCPos,
Params: &ast.FieldList{
Opening: info.importCPos,
List: args,
Closing: info.importCPos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.Ident{
NamePos: info.importCPos,
Name: "C." + fn.result,
},
},
},
},
},
}
obj.Decl = decl
for i, arg := range fn.args {
args[i] = &ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
Name: arg.name,
Obj: &ast.Object{
Kind: ast.Var,
Name: "C." + arg.name,
Decl: decl,
},
},
},
Type: &ast.Ident{
NamePos: info.importCPos,
Name: "C." + arg.typeName,
},
}
}
info.Decls = append(info.Decls, decl)
}
}
// 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
// C.int16_t = int16
// // ...
// )
func (info *fileInfo) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
Lparen: info.importCPos,
Rparen: info.importCPos,
}
for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName]
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: typeName,
Obj: obj,
},
Assign: info.importCPos,
Type: &ast.Ident{
NamePos: info.importCPos,
Name: goTypeName,
},
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
info.Decls = append(info.Decls, gen)
}
func (info *fileInfo) addTypedefs() {
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
}
for _, typedef := range info.typedefs {
newType := "C." + typedef.newName
oldType := "C." + typedef.oldName
switch oldType {
// TODO: plain char (may be signed or unsigned)
case "C.signed char", "C.short", "C.int", "C.long", "C.long long":
switch typedef.size {
case 1:
oldType = "int8"
case 2:
oldType = "int16"
case 4:
oldType = "int32"
case 8:
oldType = "int64"
}
case "C.unsigned char", "C.unsigned short", "C.unsigned int", "C.unsigned long", "C.unsigned long long":
switch typedef.size {
case 1:
oldType = "uint8"
case 2:
oldType = "uint16"
case 4:
oldType = "uint32"
case 8:
oldType = "uint64"
}
}
if strings.HasPrefix(newType, "C._Cgo_") {
newType = "C." + newType[len("C._Cgo_"):]
}
if _, ok := cgoAliases[newType]; ok {
// This is a type that also exists in Go (defined in stdint.h).
continue
}
obj := &ast.Object{
Kind: ast.Typ,
Name: newType,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: newType,
Obj: obj,
},
Type: &ast.Ident{
NamePos: info.importCPos,
Name: oldType,
},
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
info.Decls = append(info.Decls, gen)
}
// walker replaces all "C".<something> call expressions to literal
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
// be used in the middle of a name) so is used as a new namespace for C call
// expressions.
func (info *fileInfo) walker(node ast.Node) bool {
switch node := 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 x.Name == "C" {
node.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: "C." + fun.Sel.Name,
}
}
case *ast.ValueSpec:
typ, ok := node.Type.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := typ.X.(*ast.Ident)
if !ok {
return true
}
if x.Name == "C" {
node.Type = &ast.Ident{
NamePos: x.NamePos,
Name: "C." + typ.Sel.Name,
}
}
}
return true
}