blob: db0eee98d35a8963d415a565e4d4aa257063de27 [file] [log] [blame]
Peter Collingbournead9841e2014-11-27 00:06:42 +00001//===- gllgo.go - gccgo-like driver for llgo ------------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This is llgo's driver. It has a gccgo-like interface in order to easily
11// interoperate with the "go" command and the libgo build system.
12//
13//===----------------------------------------------------------------------===//
14
15package main
16
17import (
18 "errors"
19 "fmt"
20 "go/scanner"
21 "io/ioutil"
22 "log"
23 "os"
24 "os/exec"
25 "path/filepath"
26 "strings"
27
28 "llvm.org/llgo/debug"
29 "llvm.org/llgo/irgen"
30 "llvm.org/llvm/bindings/go/llvm"
31)
32
33func report(err error) {
34 if list, ok := err.(scanner.ErrorList); ok {
35 for _, e := range list {
36 fmt.Fprintf(os.Stderr, "%s\n", e)
37 }
38 } else if err != nil {
39 fmt.Fprintf(os.Stderr, "gllgo: error: %s\n", err)
40 }
41}
42
43func llvmVersion() string {
44 return strings.Replace(llvm.Version, "svn", "", 1)
45}
46
47func displayVersion() {
48 fmt.Printf("llgo version %s (%s)\n\n", llvmVersion(), irgen.GoVersion())
49 os.Exit(0)
50}
51
52func initCompiler(opts *driverOptions) (*irgen.Compiler, error) {
53 importPaths := make([]string, len(opts.importPaths)+len(opts.libPaths))
54 copy(importPaths, opts.importPaths)
55 copy(importPaths[len(opts.importPaths):], opts.libPaths)
56 if opts.prefix != "" {
57 importPaths = append(importPaths, filepath.Join(opts.prefix, "lib", "go", "llgo-"+llvmVersion()))
58 }
59 copts := irgen.CompilerOptions{
60 TargetTriple: opts.triple,
61 GenerateDebug: opts.generateDebug,
62 DebugPrefixMaps: opts.debugPrefixMaps,
63 DumpSSA: opts.dumpSSA,
64 GccgoPath: opts.gccgoPath,
65 ImportPaths: importPaths,
66 SanitizerAttribute: opts.sanitizer.getAttribute(),
67 }
68 if opts.dumpTrace {
69 copts.Logger = log.New(os.Stderr, "", 0)
70 }
71 return irgen.NewCompiler(copts)
72}
73
74type actionKind int
75
76const (
77 actionAssemble = actionKind(iota)
78 actionCompile
79 actionLink
80 actionPrint
81)
82
83type action struct {
84 kind actionKind
85 inputs []string
86}
87
88type sanitizerOptions struct {
89 blacklist string
90 crtPrefix string
91
92 address, thread, memory, dataflow bool
93}
94
95func (san *sanitizerOptions) resourcePath() string {
96 return filepath.Join(san.crtPrefix, "lib", "clang", llvmVersion())
97}
98
99func (san *sanitizerOptions) isPIEDefault() bool {
100 return san.thread || san.memory || san.dataflow
101}
102
103func (san *sanitizerOptions) addPasses(mpm, fpm llvm.PassManager) {
104 switch {
105 case san.address:
106 mpm.AddAddressSanitizerModulePass()
107 fpm.AddAddressSanitizerFunctionPass()
108 case san.thread:
109 mpm.AddThreadSanitizerPass()
110 case san.memory:
111 mpm.AddMemorySanitizerPass()
112 case san.dataflow:
113 blacklist := san.blacklist
114 if blacklist == "" {
115 blacklist = filepath.Join(san.resourcePath(), "dfsan_abilist.txt")
116 }
117 mpm.AddDataFlowSanitizerPass(blacklist)
118 }
119}
120
121func (san *sanitizerOptions) libPath(triple, sanitizerName string) string {
122 s := strings.Split(triple, "-")
123 return filepath.Join(san.resourcePath(), "lib", s[2], "libclang_rt."+sanitizerName+"-"+s[0]+".a")
124}
125
126func (san *sanitizerOptions) addLibsForSanitizer(flags []string, triple, sanitizerName string) []string {
127 return append(flags, san.libPath(triple, sanitizerName),
128 "-Wl,--no-as-needed", "-lpthread", "-lrt", "-lm", "-ldl")
129}
130
131func (san *sanitizerOptions) addLibs(triple string, flags []string) []string {
132 switch {
133 case san.address:
134 flags = san.addLibsForSanitizer(flags, triple, "asan")
135 case san.thread:
136 flags = san.addLibsForSanitizer(flags, triple, "tsan")
137 case san.memory:
138 flags = san.addLibsForSanitizer(flags, triple, "msan")
139 case san.dataflow:
140 flags = san.addLibsForSanitizer(flags, triple, "dfsan")
141 }
142
143 return flags
144}
145
146func (san *sanitizerOptions) getAttribute() llvm.Attribute {
147 switch {
148 case san.address:
149 return llvm.SanitizeAddressAttribute
150 case san.thread:
151 return llvm.SanitizeThreadAttribute
152 case san.memory:
153 return llvm.SanitizeMemoryAttribute
154 default:
155 return 0
156 }
157}
158
159type driverOptions struct {
160 actions []action
161 output string
162
163 bprefix string
164 debugPrefixMaps []debug.PrefixMap
165 dumpSSA bool
166 dumpTrace bool
167 emitIR bool
168 gccgoPath string
169 generateDebug bool
170 importPaths []string
171 libPaths []string
172 llvmArgs []string
173 lto bool
174 optLevel int
175 pic bool
176 pieLink bool
177 pkgpath string
178 plugins []string
179 prefix string
180 sanitizer sanitizerOptions
181 sizeLevel int
182 staticLibgcc bool
183 staticLibgo bool
184 staticLink bool
185 triple string
186}
187
188func getInstPrefix() (string, error) {
189 path, err := exec.LookPath(os.Args[0])
190 if err != nil {
191 return "", err
192 }
193
194 path, err = filepath.EvalSymlinks(path)
195 if err != nil {
196 return "", err
197 }
198
199 prefix := filepath.Join(path, "..", "..")
200 return prefix, nil
201}
202
203func parseArguments(args []string) (opts driverOptions, err error) {
204 var goInputs, otherInputs []string
205 hasOtherNonFlagInputs := false
206 noPrefix := false
207 actionKind := actionLink
208 opts.triple = llvm.DefaultTargetTriple()
209
210 for len(args) > 0 {
211 consumedArgs := 1
212
213 switch {
214 case !strings.HasPrefix(args[0], "-"):
215 if strings.HasSuffix(args[0], ".go") {
216 goInputs = append(goInputs, args[0])
217 } else {
218 hasOtherNonFlagInputs = true
219 otherInputs = append(otherInputs, args[0])
220 }
221
222 case strings.HasPrefix(args[0], "-Wl,"), strings.HasPrefix(args[0], "-l"), strings.HasPrefix(args[0], "--sysroot="):
223 // TODO(pcc): Handle these correctly.
224 otherInputs = append(otherInputs, args[0])
225
226 case args[0] == "-B":
227 if len(args) == 1 {
228 return opts, errors.New("missing argument after '-B'")
229 }
230 opts.bprefix = args[1]
231 consumedArgs = 2
232
233 case args[0] == "-D":
234 if len(args) == 1 {
235 return opts, errors.New("missing argument after '-D'")
236 }
237 otherInputs = append(otherInputs, args[0], args[1])
238 consumedArgs = 2
239
240 case strings.HasPrefix(args[0], "-D"):
241 otherInputs = append(otherInputs, args[0])
242
243 case args[0] == "-I":
244 if len(args) == 1 {
245 return opts, errors.New("missing argument after '-I'")
246 }
247 opts.importPaths = append(opts.importPaths, args[1])
248 consumedArgs = 2
249
250 case strings.HasPrefix(args[0], "-I"):
251 opts.importPaths = append(opts.importPaths, args[0][2:])
252
253 case args[0] == "-isystem":
254 if len(args) == 1 {
255 return opts, errors.New("missing argument after '-isystem'")
256 }
257 otherInputs = append(otherInputs, args[0], args[1])
258 consumedArgs = 2
259
260 case args[0] == "-L":
261 if len(args) == 1 {
262 return opts, errors.New("missing argument after '-L'")
263 }
264 opts.libPaths = append(opts.libPaths, args[1])
265 consumedArgs = 2
266
267 case strings.HasPrefix(args[0], "-L"):
268 opts.libPaths = append(opts.libPaths, args[0][2:])
269
270 case args[0] == "-O0":
271 opts.optLevel = 0
272
273 case args[0] == "-O1", args[0] == "-O":
274 opts.optLevel = 1
275
276 case args[0] == "-O2":
277 opts.optLevel = 2
278
279 case args[0] == "-Os":
280 opts.optLevel = 2
281 opts.sizeLevel = 1
282
283 case args[0] == "-O3":
284 opts.optLevel = 3
285
286 case args[0] == "-S":
287 actionKind = actionAssemble
288
289 case args[0] == "-c":
290 actionKind = actionCompile
291
292 case strings.HasPrefix(args[0], "-fcompilerrt-prefix="):
293 opts.sanitizer.crtPrefix = args[0][20:]
294
295 case strings.HasPrefix(args[0], "-fdebug-prefix-map="):
296 split := strings.SplitN(args[0][19:], "=", 2)
297 if len(split) < 2 {
298 return opts, fmt.Errorf("argument '%s' must be of form '-fdebug-prefix-map=SOURCE=REPLACEMENT'", args[0])
299 }
300 opts.debugPrefixMaps = append(opts.debugPrefixMaps, debug.PrefixMap{split[0], split[1]})
301
302 case args[0] == "-fdump-ssa":
303 opts.dumpSSA = true
304
305 case args[0] == "-fdump-trace":
306 opts.dumpTrace = true
307
308 case strings.HasPrefix(args[0], "-fgccgo-path="):
309 opts.gccgoPath = args[0][13:]
310
311 case strings.HasPrefix(args[0], "-fgo-pkgpath="):
312 opts.pkgpath = args[0][13:]
313
314 case strings.HasPrefix(args[0], "-fgo-relative-import-path="):
315 // TODO(pcc): Handle this.
316
317 case args[0] == "-fload-plugin":
318 if len(args) == 1 {
319 return opts, errors.New("missing argument after '-fload-plugin'")
320 }
321 opts.plugins = append(opts.plugins, args[1])
322 consumedArgs = 2
323
324 case args[0] == "-fno-toplevel-reorder":
325 // This is a GCC-specific code generation option. Ignore.
326
327 case args[0] == "-emit-llvm":
328 opts.emitIR = true
329
330 case args[0] == "-flto":
331 opts.lto = true
332
333 case args[0] == "-fPIC":
334 opts.pic = true
335
336 case strings.HasPrefix(args[0], "-fsanitize-blacklist="):
337 opts.sanitizer.blacklist = args[0][21:]
338
339 // TODO(pcc): Enforce mutual exclusion between sanitizers.
340
341 case args[0] == "-fsanitize=address":
342 opts.sanitizer.address = true
343
344 case args[0] == "-fsanitize=thread":
345 opts.sanitizer.thread = true
346
347 case args[0] == "-fsanitize=memory":
348 opts.sanitizer.memory = true
349
350 case args[0] == "-fsanitize=dataflow":
351 opts.sanitizer.dataflow = true
352
353 case args[0] == "-g":
354 opts.generateDebug = true
355
356 case args[0] == "-mllvm":
357 if len(args) == 1 {
358 return opts, errors.New("missing argument after '-mllvm'")
359 }
360 opts.llvmArgs = append(opts.llvmArgs, args[1])
361 consumedArgs = 2
362
363 case strings.HasPrefix(args[0], "-m"), args[0] == "-funsafe-math-optimizations", args[0] == "-ffp-contract=off":
364 // TODO(pcc): Handle code generation options.
365
366 case args[0] == "-no-prefix":
367 noPrefix = true
368
369 case args[0] == "-o":
370 if len(args) == 1 {
371 return opts, errors.New("missing argument after '-o'")
372 }
373 opts.output = args[1]
374 consumedArgs = 2
375
376 case args[0] == "-pie":
377 opts.pieLink = true
378
379 case args[0] == "-dumpversion",
380 args[0] == "-print-libgcc-file-name",
381 args[0] == "-print-multi-os-directory",
382 args[0] == "--version":
383 actionKind = actionPrint
384 opts.output = args[0]
385
386 case args[0] == "-static":
387 opts.staticLink = true
388
389 case args[0] == "-static-libgcc":
390 opts.staticLibgcc = true
391
392 case args[0] == "-static-libgo":
393 opts.staticLibgo = true
394
395 default:
396 return opts, fmt.Errorf("unrecognized command line option '%s'", args[0])
397 }
398
399 args = args[consumedArgs:]
400 }
401
402 if actionKind != actionPrint && len(goInputs) == 0 && !hasOtherNonFlagInputs {
403 return opts, errors.New("no input files")
404 }
405
406 if !noPrefix {
407 opts.prefix, err = getInstPrefix()
408 if err != nil {
409 return opts, err
410 }
411 }
412
413 if opts.sanitizer.crtPrefix == "" {
414 opts.sanitizer.crtPrefix = opts.prefix
415 }
416
417 if opts.sanitizer.isPIEDefault() {
418 // This should really only be turning on -fPIE, but this isn't
419 // easy to do from Go, and -fPIC is a superset of it anyway.
420 opts.pic = true
421 opts.pieLink = true
422 }
423
424 switch actionKind {
425 case actionLink:
426 if len(goInputs) != 0 {
427 opts.actions = []action{action{actionCompile, goInputs}}
428 }
429 opts.actions = append(opts.actions, action{actionLink, otherInputs})
430
431 case actionCompile, actionAssemble:
432 if len(goInputs) != 0 {
433 opts.actions = []action{action{actionKind, goInputs}}
434 }
435
436 case actionPrint:
437 opts.actions = []action{action{actionKind, nil}}
438 }
439
440 if opts.output == "" && len(opts.actions) != 0 {
441 switch actionKind {
442 case actionCompile, actionAssemble:
443 base := filepath.Base(goInputs[0])
444 base = base[0 : len(base)-3]
445 if actionKind == actionCompile {
446 opts.output = base + ".o"
447 } else {
448 opts.output = base + ".s"
449 }
450
451 case actionLink:
452 opts.output = "a.out"
453 }
454 }
455
456 return opts, nil
457}
458
459func runPasses(opts *driverOptions, tm llvm.TargetMachine, m llvm.Module) {
460 fpm := llvm.NewFunctionPassManagerForModule(m)
461 defer fpm.Dispose()
462
463 mpm := llvm.NewPassManager()
464 defer mpm.Dispose()
465
466 pmb := llvm.NewPassManagerBuilder()
467 defer pmb.Dispose()
468
469 pmb.SetOptLevel(opts.optLevel)
470 pmb.SetSizeLevel(opts.sizeLevel)
471
472 target := tm.TargetData()
473 mpm.Add(target)
474 fpm.Add(target)
475 tm.AddAnalysisPasses(mpm)
476 tm.AddAnalysisPasses(fpm)
477
478 mpm.AddVerifierPass()
479 fpm.AddVerifierPass()
480
481 pmb.Populate(mpm)
482 pmb.PopulateFunc(fpm)
483
484 if opts.optLevel == 0 {
485 // Remove references (via the descriptor) to dead functions,
486 // for compatibility with other compilers.
487 mpm.AddGlobalDCEPass()
488 }
489
490 opts.sanitizer.addPasses(mpm, fpm)
491
492 fpm.InitializeFunc()
493 for fn := m.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
494 fpm.RunFunc(fn)
495 }
496 fpm.FinalizeFunc()
497
498 mpm.Run(m)
499}
500
501func getMetadataSectionInlineAsm(name string) string {
502 // ELF: creates a non-allocated excluded section.
503 return ".section \"" + name + "\", \"e\"\n"
504}
505
506func getDataInlineAsm(data []byte) string {
507 edata := make([]byte, 0, len(data)*4+10)
508
509 edata = append(edata, ".ascii \""...)
510 for i := range data {
511 switch data[i] {
512 case '\000':
513 edata = append(edata, "\\000"...)
514 continue
515 case '\n':
516 edata = append(edata, "\\n"...)
517 continue
518 case '"', '\\':
519 edata = append(edata, '\\')
520 }
521 edata = append(edata, data[i])
522 }
523 edata = append(edata, "\"\n"...)
524 return string(edata)
525}
526
527// Get the lib-relative path to the standard libraries for the given driver
528// options. This is normally '.' but can vary for cross compilation, LTO,
529// sanitizers etc.
530func getVariantDir(opts *driverOptions) string {
531 switch {
532 case opts.lto:
533 return "llvm-lto.0"
534 case opts.sanitizer.address:
535 return "llvm-asan.0"
536 case opts.sanitizer.thread:
537 return "llvm-tsan.0"
538 case opts.sanitizer.memory:
539 return "llvm-msan.0"
540 case opts.sanitizer.dataflow:
541 return "llvm-dfsan.0"
542 default:
543 return "."
544 }
545}
546
547func performAction(opts *driverOptions, kind actionKind, inputs []string, output string) error {
548 switch kind {
549 case actionPrint:
550 switch opts.output {
551 case "-dumpversion":
552 fmt.Println("llgo-"+llvmVersion())
553 return nil
554 case "-print-libgcc-file-name":
555 cmd := exec.Command(opts.bprefix+"gcc", "-print-libgcc-file-name")
556 out, err := cmd.CombinedOutput()
557 os.Stdout.Write(out)
558 return err
559 case "-print-multi-os-directory":
560 fmt.Println(getVariantDir(opts))
561 return nil
562 case "--version":
563 displayVersion()
564 return nil
565 default:
566 panic("unexpected print command")
567 }
568
569 case actionCompile, actionAssemble:
570 compiler, err := initCompiler(opts)
571 if err != nil {
572 return err
573 }
574
575 module, err := compiler.Compile(inputs, opts.pkgpath)
576 if err != nil {
577 return err
578 }
579
580 defer module.Dispose()
581
582 target, err := llvm.GetTargetFromTriple(opts.triple)
583 if err != nil {
584 return err
585 }
586
587 optLevel := [...]llvm.CodeGenOptLevel{
588 llvm.CodeGenLevelNone,
589 llvm.CodeGenLevelLess,
590 llvm.CodeGenLevelDefault,
591 llvm.CodeGenLevelAggressive,
592 }[opts.optLevel]
593
594 relocMode := llvm.RelocStatic
595 if opts.pic {
596 relocMode = llvm.RelocPIC
597 }
598
599 tm := target.CreateTargetMachine(opts.triple, "", "", optLevel,
600 relocMode, llvm.CodeModelDefault)
601 defer tm.Dispose()
602
603 runPasses(opts, tm, module.Module)
604
605 var file *os.File
606 if output == "-" {
607 file = os.Stdout
608 } else {
609 file, err = os.Create(output)
610 if err != nil {
611 return err
612 }
613 defer file.Close()
614 }
615
616 switch {
617 case !opts.lto && !opts.emitIR:
618 if module.ExportData != nil {
619 asm := getMetadataSectionInlineAsm(".go_export")
620 asm += getDataInlineAsm(module.ExportData)
621 module.Module.SetInlineAsm(asm)
622 }
623
624 fileType := llvm.AssemblyFile
625 if kind == actionCompile {
626 fileType = llvm.ObjectFile
627 }
628 mb, err := tm.EmitToMemoryBuffer(module.Module, fileType)
629 if err != nil {
630 return err
631 }
632 defer mb.Dispose()
633
634 bytes := mb.Bytes()
635 _, err = file.Write(bytes)
636 return err
637
638 case opts.lto:
639 bcmb := llvm.WriteBitcodeToMemoryBuffer(module.Module)
640 defer bcmb.Dispose()
641
642 // This is a bit of a hack. We just want an object file
643 // containing some metadata sections. This might be simpler
644 // if we had bindings for the MC library, but for now we create
645 // a fresh module containing only inline asm that creates the
646 // sections.
647 outmodule := llvm.NewModule("")
648 defer outmodule.Dispose()
649 asm := getMetadataSectionInlineAsm(".llvmbc")
650 asm += getDataInlineAsm(bcmb.Bytes())
651 if module.ExportData != nil {
652 asm += getMetadataSectionInlineAsm(".go_export")
653 asm += getDataInlineAsm(module.ExportData)
654 }
655 outmodule.SetInlineAsm(asm)
656
657 fileType := llvm.AssemblyFile
658 if kind == actionCompile {
659 fileType = llvm.ObjectFile
660 }
661 mb, err := tm.EmitToMemoryBuffer(outmodule, fileType)
662 if err != nil {
663 return err
664 }
665 defer mb.Dispose()
666
667 bytes := mb.Bytes()
668 _, err = file.Write(bytes)
669 return err
670
671 case kind == actionCompile:
672 err := llvm.WriteBitcodeToFile(module.Module, file)
673 return err
674
675 case kind == actionAssemble:
676 _, err := file.WriteString(module.Module.String())
677 return err
678
679 default:
680 panic("unexpected action kind")
681 }
682
683 case actionLink:
684 // TODO(pcc): Teach this to do LTO.
685 args := []string{"-o", output}
686 if opts.pic {
687 args = append(args, "-fPIC")
688 }
689 if opts.pieLink {
690 args = append(args, "-pie")
691 }
692 if opts.staticLink {
693 args = append(args, "-static")
694 }
695 if opts.staticLibgcc {
696 args = append(args, "-static-libgcc")
697 }
698 for _, p := range opts.libPaths {
699 args = append(args, "-L", p)
700 }
701 for _, p := range opts.importPaths {
702 args = append(args, "-I", p)
703 }
704 args = append(args, inputs...)
705 var linkerPath string
706 if opts.gccgoPath == "" {
707 // TODO(pcc): See if we can avoid calling gcc here.
708 // We currently rely on it to find crt*.o and compile
709 // any C source files passed as arguments.
710 linkerPath = opts.bprefix + "gcc"
711
712 if opts.prefix != "" {
713 libdir := filepath.Join(opts.prefix, "lib", getVariantDir(opts))
714 args = append(args, "-L", libdir)
715 if !opts.staticLibgo {
716 args = append(args, "-Wl,-rpath,"+libdir)
717 }
718 }
719
720 args = append(args, "-lgobegin-llgo")
721 if opts.staticLibgo {
722 args = append(args, "-Wl,-Bstatic", "-lgo-llgo", "-Wl,-Bdynamic", "-lpthread", "-lm")
723 } else {
724 args = append(args, "-lgo-llgo")
725 }
726 } else {
727 linkerPath = opts.gccgoPath
728 if opts.staticLibgo {
729 args = append(args, "-static-libgo")
730 }
731 }
732
733 args = opts.sanitizer.addLibs(opts.triple, args)
734
735 cmd := exec.Command(linkerPath, args...)
736 out, err := cmd.CombinedOutput()
737 if err != nil {
738 os.Stderr.Write(out)
739 }
740 return err
741
742 default:
743 panic("unexpected action kind")
744 }
745}
746
747func performActions(opts *driverOptions) error {
748 var extraInput string
749
750 for _, plugin := range opts.plugins {
751 err := llvm.LoadLibraryPermanently(plugin)
752 if err != nil {
753 return err
754 }
755 }
756
757 llvm.ParseCommandLineOptions(append([]string{"llgo"}, opts.llvmArgs...), "llgo (LLVM option parsing)\n")
758
759 for i, action := range opts.actions {
760 var output string
761 if i == len(opts.actions)-1 {
762 output = opts.output
763 } else {
764 tmpfile, err := ioutil.TempFile("", "llgo")
765 if err != nil {
766 return err
767 }
768 output = tmpfile.Name() + ".o"
769 tmpfile.Close()
770 err = os.Remove(tmpfile.Name())
771 if err != nil {
772 return err
773 }
774 defer os.Remove(output)
775 }
776
777 inputs := action.inputs
778 if extraInput != "" {
779 inputs = append([]string{extraInput}, inputs...)
780 }
781
782 err := performAction(opts, action.kind, inputs, output)
783 if err != nil {
784 return err
785 }
786
787 extraInput = output
788 }
789
790 return nil
791}
792
793func main() {
794 llvm.InitializeAllTargets()
795 llvm.InitializeAllTargetMCs()
796 llvm.InitializeAllTargetInfos()
797 llvm.InitializeAllAsmParsers()
798 llvm.InitializeAllAsmPrinters()
799
800 opts, err := parseArguments(os.Args[1:])
801 if err != nil {
802 report(err)
803 os.Exit(1)
804 }
805
806 err = performActions(&opts)
807 if err != nil {
808 report(err)
809 os.Exit(1)
810 }
811}