github.com/aykevl/tinygo@v0.5.0/ir/ir.go (about) 1 package ir 2 3 import ( 4 "go/ast" 5 "go/token" 6 "go/types" 7 "sort" 8 "strings" 9 10 "github.com/tinygo-org/tinygo/loader" 11 "golang.org/x/tools/go/ssa" 12 "tinygo.org/x/go-llvm" 13 ) 14 15 // This file provides a wrapper around go/ssa values and adds extra 16 // functionality to them. 17 18 // View on all functions, types, and globals in a program, with analysis 19 // results. 20 type Program struct { 21 Program *ssa.Program 22 LoaderProgram *loader.Program 23 mainPkg *ssa.Package 24 Functions []*Function 25 functionMap map[*ssa.Function]*Function 26 Globals []*Global 27 globalMap map[*ssa.Global]*Global 28 comments map[string]*ast.CommentGroup 29 NamedTypes []*NamedType 30 } 31 32 // Function or method. 33 type Function struct { 34 *ssa.Function 35 LLVMFn llvm.Value 36 linkName string // go:linkname, go:export, go:interrupt 37 exported bool // go:export 38 nobounds bool // go:nobounds 39 flag bool // used by dead code elimination 40 interrupt bool // go:interrupt 41 } 42 43 // Global variable, possibly constant. 44 type Global struct { 45 *ssa.Global 46 program *Program 47 LLVMGlobal llvm.Value 48 linkName string // go:extern 49 extern bool // go:extern 50 } 51 52 // Type with a name and possibly methods. 53 type NamedType struct { 54 *ssa.Type 55 LLVMType llvm.Type 56 } 57 58 // Type that is at some point put in an interface. 59 type TypeWithMethods struct { 60 t types.Type 61 Num int 62 Methods map[string]*types.Selection 63 } 64 65 // Interface type that is at some point used in a type assert (to check whether 66 // it implements another interface). 67 type Interface struct { 68 Num int 69 Type *types.Interface 70 } 71 72 // Create and intialize a new *Program from a *ssa.Program. 73 func NewProgram(lprogram *loader.Program, mainPath string) *Program { 74 comments := map[string]*ast.CommentGroup{} 75 for _, pkgInfo := range lprogram.Sorted() { 76 for _, file := range pkgInfo.Files { 77 for _, decl := range file.Decls { 78 switch decl := decl.(type) { 79 case *ast.GenDecl: 80 switch decl.Tok { 81 case token.TYPE, token.VAR: 82 if len(decl.Specs) != 1 { 83 continue 84 } 85 for _, spec := range decl.Specs { 86 switch spec := spec.(type) { 87 case *ast.TypeSpec: // decl.Tok == token.TYPE 88 id := pkgInfo.Pkg.Path() + "." + spec.Name.Name 89 comments[id] = decl.Doc 90 case *ast.ValueSpec: // decl.Tok == token.VAR 91 for _, name := range spec.Names { 92 id := pkgInfo.Pkg.Path() + "." + name.Name 93 comments[id] = decl.Doc 94 } 95 } 96 } 97 } 98 } 99 } 100 } 101 } 102 103 program := lprogram.LoadSSA() 104 program.Build() 105 106 // Find the main package, which is a bit difficult when running a .go file 107 // directly. 108 mainPkg := program.ImportedPackage(mainPath) 109 if mainPkg == nil { 110 for _, pkgInfo := range program.AllPackages() { 111 if pkgInfo.Pkg.Name() == "main" { 112 if mainPkg != nil { 113 panic("more than one main package found") 114 } 115 mainPkg = pkgInfo 116 } 117 } 118 } 119 if mainPkg == nil { 120 panic("could not find main package") 121 } 122 123 // Make a list of packages in import order. 124 packageList := []*ssa.Package{} 125 packageSet := map[string]struct{}{} 126 worklist := []string{"runtime", mainPath} 127 for len(worklist) != 0 { 128 pkgPath := worklist[0] 129 var pkg *ssa.Package 130 if pkgPath == mainPath { 131 pkg = mainPkg // necessary for compiling individual .go files 132 } else { 133 pkg = program.ImportedPackage(pkgPath) 134 } 135 if pkg == nil { 136 // Non-SSA package (e.g. cgo). 137 packageSet[pkgPath] = struct{}{} 138 worklist = worklist[1:] 139 continue 140 } 141 if _, ok := packageSet[pkgPath]; ok { 142 // Package already in the final package list. 143 worklist = worklist[1:] 144 continue 145 } 146 147 unsatisfiedImports := make([]string, 0) 148 imports := pkg.Pkg.Imports() 149 for _, pkg := range imports { 150 if _, ok := packageSet[pkg.Path()]; ok { 151 continue 152 } 153 unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) 154 } 155 if len(unsatisfiedImports) == 0 { 156 // All dependencies of this package are satisfied, so add this 157 // package to the list. 158 packageList = append(packageList, pkg) 159 packageSet[pkgPath] = struct{}{} 160 worklist = worklist[1:] 161 } else { 162 // Prepend all dependencies to the worklist and reconsider this 163 // package (by not removing it from the worklist). At that point, it 164 // must be possible to add it to packageList. 165 worklist = append(unsatisfiedImports, worklist...) 166 } 167 } 168 169 p := &Program{ 170 Program: program, 171 LoaderProgram: lprogram, 172 mainPkg: mainPkg, 173 functionMap: make(map[*ssa.Function]*Function), 174 globalMap: make(map[*ssa.Global]*Global), 175 comments: comments, 176 } 177 178 for _, pkg := range packageList { 179 p.AddPackage(pkg) 180 } 181 182 return p 183 } 184 185 // Add a package to this Program. All packages need to be added first before any 186 // analysis is done for correct results. 187 func (p *Program) AddPackage(pkg *ssa.Package) { 188 memberNames := make([]string, 0) 189 for name := range pkg.Members { 190 memberNames = append(memberNames, name) 191 } 192 sort.Strings(memberNames) 193 194 for _, name := range memberNames { 195 member := pkg.Members[name] 196 switch member := member.(type) { 197 case *ssa.Function: 198 p.addFunction(member) 199 case *ssa.Type: 200 t := &NamedType{Type: member} 201 p.NamedTypes = append(p.NamedTypes, t) 202 methods := getAllMethods(pkg.Prog, member.Type()) 203 if !types.IsInterface(member.Type()) { 204 // named type 205 for _, method := range methods { 206 p.addFunction(pkg.Prog.MethodValue(method)) 207 } 208 } 209 case *ssa.Global: 210 g := &Global{program: p, Global: member} 211 doc := p.comments[g.RelString(nil)] 212 if doc != nil { 213 g.parsePragmas(doc) 214 } 215 p.Globals = append(p.Globals, g) 216 p.globalMap[member] = g 217 case *ssa.NamedConst: 218 // Ignore: these are already resolved. 219 default: 220 panic("unknown member type: " + member.String()) 221 } 222 } 223 } 224 225 func (p *Program) addFunction(ssaFn *ssa.Function) { 226 f := &Function{Function: ssaFn} 227 f.parsePragmas() 228 p.Functions = append(p.Functions, f) 229 p.functionMap[ssaFn] = f 230 231 for _, anon := range ssaFn.AnonFuncs { 232 p.addFunction(anon) 233 } 234 } 235 236 // Return true if this package imports "unsafe", false otherwise. 237 func hasUnsafeImport(pkg *types.Package) bool { 238 for _, imp := range pkg.Imports() { 239 if imp == types.Unsafe { 240 return true 241 } 242 } 243 return false 244 } 245 246 func (p *Program) GetFunction(ssaFn *ssa.Function) *Function { 247 return p.functionMap[ssaFn] 248 } 249 250 func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global { 251 return p.globalMap[ssaGlobal] 252 } 253 254 func (p *Program) MainPkg() *ssa.Package { 255 return p.mainPkg 256 } 257 258 // Parse compiler directives in the preceding comments. 259 func (f *Function) parsePragmas() { 260 if f.Syntax() == nil { 261 return 262 } 263 if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { 264 for _, comment := range decl.Doc.List { 265 text := comment.Text 266 if strings.HasPrefix(text, "//export ") { 267 // Rewrite '//export' to '//go:export' for compatibility with 268 // gc. 269 text = "//go:" + text[2:] 270 } 271 if !strings.HasPrefix(text, "//go:") { 272 continue 273 } 274 parts := strings.Fields(text) 275 switch parts[0] { 276 case "//go:export": 277 if len(parts) != 2 { 278 continue 279 } 280 f.linkName = parts[1] 281 f.exported = true 282 case "//go:interrupt": 283 if len(parts) != 2 { 284 continue 285 } 286 name := parts[1] 287 if strings.HasSuffix(name, "_vect") { 288 // AVR vector naming 289 name = "__vector_" + name[:len(name)-5] 290 } 291 f.linkName = name 292 f.exported = true 293 f.interrupt = true 294 case "//go:linkname": 295 if len(parts) != 3 || parts[1] != f.Name() { 296 continue 297 } 298 // Only enable go:linkname when the package imports "unsafe". 299 // This is a slightly looser requirement than what gc uses: gc 300 // requires the file to import "unsafe", not the package as a 301 // whole. 302 if hasUnsafeImport(f.Pkg.Pkg) { 303 f.linkName = parts[2] 304 } 305 case "//go:nobounds": 306 // Skip bounds checking in this function. Useful for some 307 // runtime functions. 308 // This is somewhat dangerous and thus only imported in packages 309 // that import unsafe. 310 if hasUnsafeImport(f.Pkg.Pkg) { 311 f.nobounds = true 312 } 313 } 314 } 315 } 316 } 317 318 func (f *Function) IsNoBounds() bool { 319 return f.nobounds 320 } 321 322 // Return true iff this function is externally visible. 323 func (f *Function) IsExported() bool { 324 return f.exported || f.CName() != "" 325 } 326 327 // Return true for functions annotated with //go:interrupt. The function name is 328 // already customized in LinkName() to hook up in the interrupt vector. 329 // 330 // On some platforms (like AVR), interrupts need a special compiler flag. 331 func (f *Function) IsInterrupt() bool { 332 return f.interrupt 333 } 334 335 // Return the link name for this function. 336 func (f *Function) LinkName() string { 337 if f.linkName != "" { 338 return f.linkName 339 } 340 if f.Signature.Recv() != nil { 341 // Method on a defined type (which may be a pointer). 342 return f.RelString(nil) 343 } else { 344 // Bare function. 345 if name := f.CName(); name != "" { 346 // Name CGo functions directly. 347 return name 348 } else { 349 return f.RelString(nil) 350 } 351 } 352 } 353 354 // Return the name of the C function if this is a CGo wrapper. Otherwise, return 355 // a zero-length string. 356 func (f *Function) CName() string { 357 name := f.Name() 358 if strings.HasPrefix(name, "_Cfunc_") { 359 // emitted by `go tool cgo` 360 return name[len("_Cfunc_"):] 361 } 362 if strings.HasPrefix(name, "C.") { 363 // created by ../loader/cgo.go 364 return name[2:] 365 } 366 return "" 367 } 368 369 // Parse //go: pragma comments from the source. 370 func (g *Global) parsePragmas(doc *ast.CommentGroup) { 371 for _, comment := range doc.List { 372 if !strings.HasPrefix(comment.Text, "//go:") { 373 continue 374 } 375 parts := strings.Fields(comment.Text) 376 switch parts[0] { 377 case "//go:extern": 378 g.extern = true 379 if len(parts) == 2 { 380 g.linkName = parts[1] 381 } 382 } 383 } 384 } 385 386 // Return the link name for this global. 387 func (g *Global) LinkName() string { 388 if g.linkName != "" { 389 return g.linkName 390 } 391 if name := g.CName(); name != "" { 392 return name 393 } 394 return g.RelString(nil) 395 } 396 397 func (g *Global) IsExtern() bool { 398 return g.extern || g.CName() != "" 399 } 400 401 // Return the name of the C global if this is a CGo wrapper. Otherwise, return a 402 // zero-length string. 403 func (g *Global) CName() string { 404 name := g.Name() 405 if strings.HasPrefix(name, "C.") { 406 // created by ../loader/cgo.go 407 return name[2:] 408 } 409 return "" 410 } 411 412 // Return true if this named type is annotated with the //go:volatile pragma, 413 // for volatile loads and stores. 414 func (p *Program) IsVolatile(t types.Type) bool { 415 if t, ok := t.(*types.Named); !ok { 416 return false 417 } else { 418 if t.Obj().Pkg() == nil { 419 return false 420 } 421 id := t.Obj().Pkg().Path() + "." + t.Obj().Name() 422 doc := p.comments[id] 423 if doc == nil { 424 return false 425 } 426 for _, line := range doc.List { 427 if strings.TrimSpace(line.Text) == "//go:volatile" { 428 return true 429 } 430 } 431 return false 432 } 433 } 434 435 // Get all methods of a type. 436 func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { 437 ms := prog.MethodSets.MethodSet(typ) 438 methods := make([]*types.Selection, ms.Len()) 439 for i := 0; i < ms.Len(); i++ { 440 methods[i] = ms.At(i) 441 } 442 return methods 443 }