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