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 }