github.com/aykevl/tinygo@v0.5.0/loader/cgo.go (about)

     1  package loader
     2  
     3  // This file extracts the `import "C"` statement from the source and modifies
     4  // the AST for Cgo. It does not use libclang directly (see libclang.go).
     5  
     6  import (
     7  	"go/ast"
     8  	"go/token"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"golang.org/x/tools/go/ast/astutil"
    14  )
    15  
    16  // fileInfo holds all Cgo-related information of a given *ast.File.
    17  type fileInfo struct {
    18  	*ast.File
    19  	*Package
    20  	filename        string
    21  	functions       map[string]*functionInfo
    22  	globals         map[string]*globalInfo
    23  	typedefs        map[string]*typedefInfo
    24  	elaboratedTypes map[string]ast.Expr
    25  	importCPos      token.Pos
    26  }
    27  
    28  // functionInfo stores some information about a Cgo function found by libclang
    29  // and declared in the AST.
    30  type functionInfo struct {
    31  	args    []paramInfo
    32  	results *ast.FieldList
    33  }
    34  
    35  // paramInfo is a parameter of a Cgo function (see functionInfo).
    36  type paramInfo struct {
    37  	name     string
    38  	typeExpr ast.Expr
    39  }
    40  
    41  // typedefInfo contains information about a single typedef in C.
    42  type typedefInfo struct {
    43  	typeExpr ast.Expr
    44  }
    45  
    46  // globalInfo contains information about a declared global variable in C.
    47  type globalInfo struct {
    48  	typeExpr ast.Expr
    49  }
    50  
    51  // cgoAliases list type aliases between Go and C, for types that are equivalent
    52  // in both languages. See addTypeAliases.
    53  var cgoAliases = map[string]string{
    54  	"C.int8_t":    "int8",
    55  	"C.int16_t":   "int16",
    56  	"C.int32_t":   "int32",
    57  	"C.int64_t":   "int64",
    58  	"C.uint8_t":   "uint8",
    59  	"C.uint16_t":  "uint16",
    60  	"C.uint32_t":  "uint32",
    61  	"C.uint64_t":  "uint64",
    62  	"C.uintptr_t": "uintptr",
    63  }
    64  
    65  // cgoTypes lists some C types with ambiguous sizes that must be retrieved
    66  // somehow from C. This is done by adding some typedefs to get the size of each
    67  // type.
    68  const cgoTypes = `
    69  typedef signed char         _Cgo_schar;
    70  typedef unsigned char       _Cgo_uchar;
    71  typedef short               _Cgo_short;
    72  typedef unsigned short      _Cgo_ushort;
    73  typedef int                 _Cgo_int;
    74  typedef unsigned int        _Cgo_uint;
    75  typedef long                _Cgo_long;
    76  typedef unsigned long       _Cgo_ulong;
    77  typedef long long           _Cgo_longlong;
    78  typedef unsigned long long  _Cgo_ulonglong;
    79  `
    80  
    81  // processCgo extracts the `import "C"` statement from the AST, parses the
    82  // comment with libclang, and modifies the AST to use this information.
    83  func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
    84  	info := &fileInfo{
    85  		File:            f,
    86  		Package:         p,
    87  		filename:        filename,
    88  		functions:       map[string]*functionInfo{},
    89  		globals:         map[string]*globalInfo{},
    90  		typedefs:        map[string]*typedefInfo{},
    91  		elaboratedTypes: map[string]ast.Expr{},
    92  	}
    93  
    94  	// Find `import "C"` statements in the file.
    95  	for i := 0; i < len(f.Decls); i++ {
    96  		decl := f.Decls[i]
    97  		genDecl, ok := decl.(*ast.GenDecl)
    98  		if !ok {
    99  			continue
   100  		}
   101  		if len(genDecl.Specs) != 1 {
   102  			continue
   103  		}
   104  		spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
   105  		if !ok {
   106  			continue
   107  		}
   108  		path, err := strconv.Unquote(spec.Path.Value)
   109  		if err != nil {
   110  			panic("could not parse import path: " + err.Error())
   111  		}
   112  		if path != "C" {
   113  			continue
   114  		}
   115  		cgoComment := genDecl.Doc.Text()
   116  
   117  		// Stored for later use by generated functions, to use a somewhat sane
   118  		// source location.
   119  		info.importCPos = spec.Path.ValuePos
   120  
   121  		pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
   122  		errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
   123  		if errs != nil {
   124  			return errs
   125  		}
   126  
   127  		// Remove this import declaration.
   128  		f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
   129  		i--
   130  	}
   131  
   132  	// Print the AST, for debugging.
   133  	//ast.Print(p.fset, f)
   134  
   135  	// Declare functions found by libclang.
   136  	info.addFuncDecls()
   137  
   138  	// Declare stub function pointer values found by libclang.
   139  	info.addFuncPtrDecls()
   140  
   141  	// Declare globals found by libclang.
   142  	info.addVarDecls()
   143  
   144  	// Forward C types to Go types (like C.uint32_t -> uint32).
   145  	info.addTypeAliases()
   146  
   147  	// Add type declarations for C types, declared using typedef in C.
   148  	info.addTypedefs()
   149  
   150  	// Add elaborated types for C structs and unions.
   151  	info.addElaboratedTypes()
   152  
   153  	// Patch the AST to use the declared types and functions.
   154  	f = astutil.Apply(f, info.walker, nil).(*ast.File)
   155  
   156  	return nil
   157  }
   158  
   159  // addFuncDecls adds the C function declarations found by libclang in the
   160  // comment above the `import "C"` statement.
   161  func (info *fileInfo) addFuncDecls() {
   162  	// TODO: replace all uses of importCPos with the real locations from
   163  	// libclang.
   164  	names := make([]string, 0, len(info.functions))
   165  	for name := range info.functions {
   166  		names = append(names, name)
   167  	}
   168  	sort.Strings(names)
   169  	for _, name := range names {
   170  		fn := info.functions[name]
   171  		obj := &ast.Object{
   172  			Kind: ast.Fun,
   173  			Name: "C." + name,
   174  		}
   175  		args := make([]*ast.Field, len(fn.args))
   176  		decl := &ast.FuncDecl{
   177  			Name: &ast.Ident{
   178  				NamePos: info.importCPos,
   179  				Name:    "C." + name,
   180  				Obj:     obj,
   181  			},
   182  			Type: &ast.FuncType{
   183  				Func: info.importCPos,
   184  				Params: &ast.FieldList{
   185  					Opening: info.importCPos,
   186  					List:    args,
   187  					Closing: info.importCPos,
   188  				},
   189  				Results: fn.results,
   190  			},
   191  		}
   192  		obj.Decl = decl
   193  		for i, arg := range fn.args {
   194  			args[i] = &ast.Field{
   195  				Names: []*ast.Ident{
   196  					&ast.Ident{
   197  						NamePos: info.importCPos,
   198  						Name:    arg.name,
   199  						Obj: &ast.Object{
   200  							Kind: ast.Var,
   201  							Name: arg.name,
   202  							Decl: decl,
   203  						},
   204  					},
   205  				},
   206  				Type: arg.typeExpr,
   207  			}
   208  		}
   209  		info.Decls = append(info.Decls, decl)
   210  	}
   211  }
   212  
   213  // addFuncPtrDecls creates stub declarations of function pointer values. These
   214  // values will later be replaced with the real values in the compiler.
   215  // It adds code like the following to the AST:
   216  //
   217  //     var (
   218  //         C.add unsafe.Pointer
   219  //         C.mul unsafe.Pointer
   220  //         // ...
   221  //     )
   222  func (info *fileInfo) addFuncPtrDecls() {
   223  	gen := &ast.GenDecl{
   224  		TokPos: info.importCPos,
   225  		Tok:    token.VAR,
   226  		Lparen: info.importCPos,
   227  		Rparen: info.importCPos,
   228  	}
   229  	names := make([]string, 0, len(info.functions))
   230  	for name := range info.functions {
   231  		names = append(names, name)
   232  	}
   233  	sort.Strings(names)
   234  	for _, name := range names {
   235  		obj := &ast.Object{
   236  			Kind: ast.Typ,
   237  			Name: "C." + name + "$funcaddr",
   238  		}
   239  		valueSpec := &ast.ValueSpec{
   240  			Names: []*ast.Ident{&ast.Ident{
   241  				NamePos: info.importCPos,
   242  				Name:    "C." + name + "$funcaddr",
   243  				Obj:     obj,
   244  			}},
   245  			Type: &ast.SelectorExpr{
   246  				X: &ast.Ident{
   247  					NamePos: info.importCPos,
   248  					Name:    "unsafe",
   249  				},
   250  				Sel: &ast.Ident{
   251  					NamePos: info.importCPos,
   252  					Name:    "Pointer",
   253  				},
   254  			},
   255  		}
   256  		obj.Decl = valueSpec
   257  		gen.Specs = append(gen.Specs, valueSpec)
   258  	}
   259  	info.Decls = append(info.Decls, gen)
   260  }
   261  
   262  // addVarDecls declares external C globals in the Go source.
   263  // It adds code like the following to the AST:
   264  //
   265  //     var (
   266  //         C.globalInt  int
   267  //         C.globalBool bool
   268  //         // ...
   269  //     )
   270  func (info *fileInfo) addVarDecls() {
   271  	gen := &ast.GenDecl{
   272  		TokPos: info.importCPos,
   273  		Tok:    token.VAR,
   274  		Lparen: info.importCPos,
   275  		Rparen: info.importCPos,
   276  	}
   277  	names := make([]string, 0, len(info.globals))
   278  	for name := range info.globals {
   279  		names = append(names, name)
   280  	}
   281  	sort.Strings(names)
   282  	for _, name := range names {
   283  		global := info.globals[name]
   284  		obj := &ast.Object{
   285  			Kind: ast.Typ,
   286  			Name: "C." + name,
   287  		}
   288  		valueSpec := &ast.ValueSpec{
   289  			Names: []*ast.Ident{&ast.Ident{
   290  				NamePos: info.importCPos,
   291  				Name:    "C." + name,
   292  				Obj:     obj,
   293  			}},
   294  			Type: global.typeExpr,
   295  		}
   296  		obj.Decl = valueSpec
   297  		gen.Specs = append(gen.Specs, valueSpec)
   298  	}
   299  	info.Decls = append(info.Decls, gen)
   300  }
   301  
   302  // addTypeAliases aliases some built-in Go types with their equivalent C types.
   303  // It adds code like the following to the AST:
   304  //
   305  //     type (
   306  //         C.int8_t  = int8
   307  //         C.int16_t = int16
   308  //         // ...
   309  //     )
   310  func (info *fileInfo) addTypeAliases() {
   311  	aliasKeys := make([]string, 0, len(cgoAliases))
   312  	for key := range cgoAliases {
   313  		aliasKeys = append(aliasKeys, key)
   314  	}
   315  	sort.Strings(aliasKeys)
   316  	gen := &ast.GenDecl{
   317  		TokPos: info.importCPos,
   318  		Tok:    token.TYPE,
   319  		Lparen: info.importCPos,
   320  		Rparen: info.importCPos,
   321  	}
   322  	for _, typeName := range aliasKeys {
   323  		goTypeName := cgoAliases[typeName]
   324  		obj := &ast.Object{
   325  			Kind: ast.Typ,
   326  			Name: typeName,
   327  		}
   328  		typeSpec := &ast.TypeSpec{
   329  			Name: &ast.Ident{
   330  				NamePos: info.importCPos,
   331  				Name:    typeName,
   332  				Obj:     obj,
   333  			},
   334  			Assign: info.importCPos,
   335  			Type: &ast.Ident{
   336  				NamePos: info.importCPos,
   337  				Name:    goTypeName,
   338  			},
   339  		}
   340  		obj.Decl = typeSpec
   341  		gen.Specs = append(gen.Specs, typeSpec)
   342  	}
   343  	info.Decls = append(info.Decls, gen)
   344  }
   345  
   346  func (info *fileInfo) addTypedefs() {
   347  	gen := &ast.GenDecl{
   348  		TokPos: info.importCPos,
   349  		Tok:    token.TYPE,
   350  	}
   351  	names := make([]string, 0, len(info.typedefs))
   352  	for name := range info.typedefs {
   353  		names = append(names, name)
   354  	}
   355  	sort.Strings(names)
   356  	for _, name := range names {
   357  		typedef := info.typedefs[name]
   358  		typeName := "C." + name
   359  		if strings.HasPrefix(name, "_Cgo_") {
   360  			typeName = "C." + name[len("_Cgo_"):]
   361  		}
   362  		if _, ok := cgoAliases[typeName]; ok {
   363  			// This is a type that also exists in Go (defined in stdint.h).
   364  			continue
   365  		}
   366  		obj := &ast.Object{
   367  			Kind: ast.Typ,
   368  			Name: typeName,
   369  		}
   370  		typeSpec := &ast.TypeSpec{
   371  			Name: &ast.Ident{
   372  				NamePos: info.importCPos,
   373  				Name:    typeName,
   374  				Obj:     obj,
   375  			},
   376  			Type: typedef.typeExpr,
   377  		}
   378  		obj.Decl = typeSpec
   379  		gen.Specs = append(gen.Specs, typeSpec)
   380  	}
   381  	info.Decls = append(info.Decls, gen)
   382  }
   383  
   384  // addElaboratedTypes adds C elaborated types as aliases. These are the "struct
   385  // foo" or "union foo" types, often used in a typedef.
   386  //
   387  // See also:
   388  // https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
   389  func (info *fileInfo) addElaboratedTypes() {
   390  	gen := &ast.GenDecl{
   391  		TokPos: info.importCPos,
   392  		Tok:    token.TYPE,
   393  	}
   394  	names := make([]string, 0, len(info.elaboratedTypes))
   395  	for name := range info.elaboratedTypes {
   396  		names = append(names, name)
   397  	}
   398  	sort.Strings(names)
   399  	for _, name := range names {
   400  		typ := info.elaboratedTypes[name]
   401  		typeName := "C.struct_" + name
   402  		obj := &ast.Object{
   403  			Kind: ast.Typ,
   404  			Name: typeName,
   405  		}
   406  		typeSpec := &ast.TypeSpec{
   407  			Name: &ast.Ident{
   408  				NamePos: info.importCPos,
   409  				Name:    typeName,
   410  				Obj:     obj,
   411  			},
   412  			Type: typ,
   413  		}
   414  		obj.Decl = typeSpec
   415  		gen.Specs = append(gen.Specs, typeSpec)
   416  	}
   417  	info.Decls = append(info.Decls, gen)
   418  }
   419  
   420  // walker replaces all "C".<something> expressions to literal "C.<something>"
   421  // expressions. Such expressions are impossible to write in Go (a dot cannot be
   422  // used in the middle of a name) so in practice all C identifiers live in a
   423  // separate namespace (no _Cgo_ hacks like in gc).
   424  func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
   425  	switch node := cursor.Node().(type) {
   426  	case *ast.CallExpr:
   427  		fun, ok := node.Fun.(*ast.SelectorExpr)
   428  		if !ok {
   429  			return true
   430  		}
   431  		x, ok := fun.X.(*ast.Ident)
   432  		if !ok {
   433  			return true
   434  		}
   435  		if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
   436  			node.Fun = &ast.Ident{
   437  				NamePos: x.NamePos,
   438  				Name:    "C." + fun.Sel.Name,
   439  			}
   440  		}
   441  	case *ast.SelectorExpr:
   442  		x, ok := node.X.(*ast.Ident)
   443  		if !ok {
   444  			return true
   445  		}
   446  		if x.Name == "C" {
   447  			name := "C." + node.Sel.Name
   448  			if _, ok := info.functions[node.Sel.Name]; ok {
   449  				name += "$funcaddr"
   450  			}
   451  			cursor.Replace(&ast.Ident{
   452  				NamePos: x.NamePos,
   453  				Name:    name,
   454  			})
   455  		}
   456  	}
   457  	return true
   458  }