1// Package builder is the compiler driver of TinyGo. It takes in a package name 2// and an output path, and outputs an executable. It manages the entire 3// compilation pipeline in between. 4package builder 5 6import ( 7 "debug/elf" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "sort" 14 "strconv" 15 "strings" 16 17 "github.com/tinygo-org/tinygo/compileopts" 18 "github.com/tinygo-org/tinygo/compiler" 19 "github.com/tinygo-org/tinygo/goenv" 20 "github.com/tinygo-org/tinygo/interp" 21 "github.com/tinygo-org/tinygo/stacksize" 22 "github.com/tinygo-org/tinygo/transform" 23 "tinygo.org/x/go-llvm" 24) 25 26// Build performs a single package to executable Go build. It takes in a package 27// name, an output path, and set of compile options and from that it manages the 28// whole compilation process. 29// 30// The error value may be of type *MultiError. Callers will likely want to check 31// for this case and print such errors individually. 32func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error { 33 // Compile Go code to IR. 34 machine, err := compiler.NewTargetMachine(config) 35 if err != nil { 36 return err 37 } 38 mod, extraFiles, extraLDFlags, errs := compiler.Compile(pkgName, machine, config) 39 if errs != nil { 40 return newMultiError(errs) 41 } 42 43 if config.Options.PrintIR { 44 fmt.Println("; Generated LLVM IR:") 45 fmt.Println(mod.String()) 46 } 47 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 48 return errors.New("verification error after IR construction") 49 } 50 51 err = interp.Run(mod, config.DumpSSA()) 52 if err != nil { 53 return err 54 } 55 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 56 return errors.New("verification error after interpreting runtime.initAll") 57 } 58 59 if config.GOOS() != "darwin" { 60 transform.ApplyFunctionSections(mod) // -ffunction-sections 61 } 62 63 // Browsers cannot handle external functions that have type i64 because it 64 // cannot be represented exactly in JavaScript (JS only has doubles). To 65 // keep functions interoperable, pass int64 types as pointers to 66 // stack-allocated values. 67 // Use -wasm-abi=generic to disable this behaviour. 68 if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") { 69 err := transform.ExternalInt64AsPtr(mod) 70 if err != nil { 71 return err 72 } 73 } 74 75 // Optimization levels here are roughly the same as Clang, but probably not 76 // exactly. 77 errs = nil 78 switch config.Options.Opt { 79 /* 80 Currently, turning optimizations off causes compile failures. 81 We rely on the optimizer removing some dead symbols. 82 Avoid providing an option that does not work right now. 83 In the future once everything has been fixed we can re-enable this. 84 85 case "none", "0": 86 errs = transform.Optimize(mod, config, 0, 0, 0) // -O0 87 */ 88 case "1": 89 errs = transform.Optimize(mod, config, 1, 0, 0) // -O1 90 case "2": 91 errs = transform.Optimize(mod, config, 2, 0, 225) // -O2 92 case "s": 93 errs = transform.Optimize(mod, config, 2, 1, 225) // -Os 94 case "z": 95 errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default 96 default: 97 errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)} 98 } 99 if len(errs) > 0 { 100 return newMultiError(errs) 101 } 102 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 103 return errors.New("verification failure after LLVM optimization passes") 104 } 105 106 // On the AVR, pointers can point either to flash or to RAM, but we don't 107 // know. As a temporary fix, load all global variables in RAM. 108 // In the future, there should be a compiler pass that determines which 109 // pointers are flash and which are in RAM so that pointers can have a 110 // correct address space parameter (address space 1 is for flash). 111 if strings.HasPrefix(config.Triple(), "avr") { 112 transform.NonConstGlobals(mod) 113 if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { 114 return errors.New("verification error after making all globals non-constant on AVR") 115 } 116 } 117 118 // Generate output. 119 outext := filepath.Ext(outpath) 120 switch outext { 121 case ".o": 122 llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) 123 if err != nil { 124 return err 125 } 126 return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666) 127 case ".bc": 128 data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes() 129 return ioutil.WriteFile(outpath, data, 0666) 130 case ".ll": 131 data := []byte(mod.String()) 132 return ioutil.WriteFile(outpath, data, 0666) 133 default: 134 // Act as a compiler driver. 135 136 // Create a temporary directory for intermediary files. 137 dir, err := ioutil.TempDir("", "tinygo") 138 if err != nil { 139 return err 140 } 141 defer os.RemoveAll(dir) 142 143 // Write the object file. 144 objfile := filepath.Join(dir, "main.o") 145 llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) 146 if err != nil { 147 return err 148 } 149 err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666) 150 if err != nil { 151 return err 152 } 153 154 // Prepare link command. 155 executable := filepath.Join(dir, "main") 156 tmppath := executable // final file 157 ldflags := append(config.LDFlags(), "-o", executable, objfile) 158 159 // Load builtins library from the cache, possibly compiling it on the 160 // fly. 161 if config.Target.RTLib == "compiler-rt" { 162 librt, err := CompilerRT.Load(config.Triple()) 163 if err != nil { 164 return err 165 } 166 ldflags = append(ldflags, librt) 167 } 168 169 // Add libc. 170 if config.Target.Libc == "picolibc" { 171 libc, err := Picolibc.Load(config.Triple()) 172 if err != nil { 173 return err 174 } 175 ldflags = append(ldflags, libc) 176 } 177 178 // Compile extra files. 179 root := goenv.Get("TINYGOROOT") 180 for i, path := range config.ExtraFiles() { 181 abspath := filepath.Join(root, path) 182 outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o") 183 err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...) 184 if err != nil { 185 return &commandError{"failed to build", path, err} 186 } 187 ldflags = append(ldflags, outpath) 188 } 189 190 // Compile C files in packages. 191 for i, file := range extraFiles { 192 outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o") 193 err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) 194 if err != nil { 195 return &commandError{"failed to build", file, err} 196 } 197 ldflags = append(ldflags, outpath) 198 } 199 200 if len(extraLDFlags) > 0 { 201 ldflags = append(ldflags, extraLDFlags...) 202 } 203 204 // Link the object files together. 205 err = link(config.Target.Linker, ldflags...) 206 if err != nil { 207 return &commandError{"failed to link", executable, err} 208 } 209 210 if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { 211 sizes, err := loadProgramSize(executable) 212 if err != nil { 213 return err 214 } 215 if config.Options.PrintSizes == "short" { 216 fmt.Printf(" code data bss | flash ram\n") 217 fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) 218 } else { 219 fmt.Printf(" code rodata data bss | flash ram | package\n") 220 for _, name := range sizes.sortedPackageNames() { 221 pkgSize := sizes.Packages[name] 222 fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) 223 } 224 fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM()) 225 fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) 226 } 227 } 228 229 // Print goroutine stack sizes, as far as possible. 230 if config.Options.PrintStacks { 231 printStacks(mod, executable) 232 } 233 234 // Get an Intel .hex file or .bin file from the .elf file. 235 if outext == ".hex" || outext == ".bin" || outext == ".gba" { 236 tmppath = filepath.Join(dir, "main"+outext) 237 err := objcopy(executable, tmppath) 238 if err != nil { 239 return err 240 } 241 } else if outext == ".uf2" { 242 // Get UF2 from the .elf file. 243 tmppath = filepath.Join(dir, "main"+outext) 244 err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID) 245 if err != nil { 246 return err 247 } 248 } 249 return action(tmppath) 250 } 251} 252 253// printStacks prints the maximum stack depth for functions that are started as 254// goroutines. Stack sizes cannot always be determined statically, in particular 255// recursive functions and functions that call interface methods or function 256// pointers may have an unknown stack depth (depending on what the optimizer 257// manages to optimize away). 258// 259// It might print something like the following: 260// 261// function stack usage (in bytes) 262// Reset_Handler 316 263// .Lexamples/blinky2.led1 92 264// .Lruntime.run$1 300 265func printStacks(mod llvm.Module, executable string) { 266 // Determine which functions call a function pointer. 267 var callsIndirectFunction []string 268 for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { 269 for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { 270 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 271 if inst.IsACallInst().IsNil() { 272 continue 273 } 274 if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() { 275 callsIndirectFunction = append(callsIndirectFunction, fn.Name()) 276 } 277 } 278 } 279 } 280 281 // Load the ELF binary. 282 f, err := elf.Open(executable) 283 if err != nil { 284 fmt.Fprintln(os.Stderr, "could not load executable for stack size analysis:", err) 285 return 286 } 287 defer f.Close() 288 289 // Determine the frame size of each function (if available) and the callgraph. 290 functions, err := stacksize.CallGraph(f, callsIndirectFunction) 291 if err != nil { 292 fmt.Fprintln(os.Stderr, "could not parse executable for stack size analysis:", err) 293 return 294 } 295 296 // Get a list of "go wrappers", small wrapper functions that decode 297 // parameters when starting a new goroutine. 298 var gowrappers []string 299 for name := range functions { 300 if strings.HasSuffix(name, "$gowrapper") { 301 gowrappers = append(gowrappers, name) 302 } 303 } 304 sort.Strings(gowrappers) 305 306 switch f.Machine { 307 case elf.EM_ARM: 308 // Add the reset handler, which runs startup code and is the 309 // interrupt/scheduler stack with -scheduler=tasks. 310 // Note that because interrupts happen on this stack, the stack needed 311 // by just the Reset_Handler is not enough. Stacks needed by interrupt 312 // handlers should also be taken into account. 313 gowrappers = append([]string{"Reset_Handler"}, gowrappers...) 314 } 315 316 // Print the sizes of all stacks. 317 fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)") 318 for _, name := range gowrappers { 319 for _, fn := range functions[name] { 320 stackSize, stackSizeType, missingStackSize := fn.StackSize() 321 strippedName := name 322 if strings.HasSuffix(name, "$gowrapper") { 323 strippedName = name[:len(name)-len("$gowrapper")] 324 } 325 switch stackSizeType { 326 case stacksize.Bounded: 327 fmt.Printf("%-32s %d\n", strippedName, stackSize) 328 case stacksize.Unknown: 329 fmt.Printf("%-32s unknown, %s does not have stack frame information\n", strippedName, missingStackSize) 330 case stacksize.Recursive: 331 fmt.Printf("%-32s recursive, %s may call itself\n", strippedName, missingStackSize) 332 case stacksize.IndirectCall: 333 fmt.Printf("%-32s unknown, %s calls a function pointer\n", strippedName, missingStackSize) 334 } 335 } 336 } 337} 338