//===- compiler.go - IR generator entry point -----------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file implements the main IR generator entry point, (*Compiler).Compile. // //===----------------------------------------------------------------------===// package irgen import ( "bytes" "go/ast" "go/token" "log" "sort" "strconv" "strings" llgobuild "llvm.org/llgo/build" "llvm.org/llgo/debug" "llvm.org/llvm/bindings/go/llvm" "llvm.org/llgo/third_party/go.tools/go/gccgoimporter" "llvm.org/llgo/third_party/go.tools/go/importer" "llvm.org/llgo/third_party/go.tools/go/loader" "llvm.org/llgo/third_party/go.tools/go/ssa" "llvm.org/llgo/third_party/go.tools/go/types" ) type Module struct { llvm.Module Path string ExportData []byte Package *types.Package disposed bool } func (m *Module) Dispose() { if m.disposed { return } m.Module.Dispose() m.disposed = true } /////////////////////////////////////////////////////////////////////////////// type CompilerOptions struct { // TargetTriple is the LLVM triple for the target. TargetTriple string // GenerateDebug decides whether debug data is // generated in the output module. GenerateDebug bool // DebugPrefixMaps is a list of mappings from source prefixes to // replacement prefixes, to be applied in debug info. DebugPrefixMaps []debug.PrefixMap // Logger is a logger used for tracing compilation. Logger *log.Logger // DumpSSA is a debugging option that dumps each SSA function // to stderr before generating code for it. DumpSSA bool // GccgoPath is the path to the gccgo binary whose libgo we read import // data from. If blank, the caller is expected to supply an import // path in ImportPaths. GccgoPath string // Whether to use the gccgo ABI. GccgoABI bool // ImportPaths is the list of additional import paths ImportPaths []string // SanitizerAttribute is an attribute to apply to functions to enable // dynamic instrumentation using a sanitizer. SanitizerAttribute llvm.Attribute // Importer is the importer. If nil, the compiler will set this field // automatically using MakeImporter(). Importer types.Importer // InitMap is the init map used by Importer. If Importer is nil, the // compiler will set this field automatically using MakeImporter(). // If Importer is non-nil, InitMap must be non-nil also. InitMap map[*types.Package]gccgoimporter.InitData } type Compiler struct { opts CompilerOptions dataLayout string pnacl bool } func NewCompiler(opts CompilerOptions) (*Compiler, error) { compiler := &Compiler{opts: opts} if strings.ToLower(compiler.opts.TargetTriple) == "pnacl" { compiler.opts.TargetTriple = PNaClTriple compiler.pnacl = true } dataLayout, err := llvmDataLayout(compiler.opts.TargetTriple) if err != nil { return nil, err } compiler.dataLayout = dataLayout return compiler, nil } func (c *Compiler) Compile(fset *token.FileSet, astFiles []*ast.File, importpath string) (m *Module, err error) { target := llvm.NewTargetData(c.dataLayout) compiler := &compiler{ CompilerOptions: c.opts, dataLayout: c.dataLayout, target: target, pnacl: c.pnacl, llvmtypes: NewLLVMTypeMap(llvm.GlobalContext(), target), } return compiler.compile(fset, astFiles, importpath) } type compiler struct { CompilerOptions module *Module dataLayout string target llvm.TargetData fileset *token.FileSet runtime *runtimeInterface llvmtypes *llvmTypeMap types *TypeMap // pnacl is set to true if the target triple was originally // specified as "pnacl". This is necessary, as the TargetTriple // field will have been updated to the true triple used to // compile PNaCl modules. pnacl bool debug *debug.DIBuilder } func (c *compiler) logf(format string, v ...interface{}) { if c.Logger != nil { c.Logger.Printf(format, v...) } } func (c *compiler) addCommonFunctionAttrs(fn llvm.Value) { fn.AddTargetDependentFunctionAttr("disable-tail-calls", "true") fn.AddTargetDependentFunctionAttr("split-stack", "") if attr := c.SanitizerAttribute; attr != 0 { fn.AddFunctionAttr(attr) } } // MakeImporter sets CompilerOptions.Importer to an appropriate importer // for the search paths given in CompilerOptions.ImportPaths, and sets // CompilerOptions.InitMap to an init map belonging to that importer. // If CompilerOptions.GccgoPath is non-empty, the importer will also use // the search paths for that gccgo installation. func (opts *CompilerOptions) MakeImporter() error { opts.InitMap = make(map[*types.Package]gccgoimporter.InitData) if opts.GccgoPath == "" { paths := append(append([]string{}, opts.ImportPaths...), ".") opts.Importer = gccgoimporter.GetImporter(paths, opts.InitMap) } else { var inst gccgoimporter.GccgoInstallation err := inst.InitFromDriver(opts.GccgoPath) if err != nil { return err } opts.Importer = inst.GetImporter(opts.ImportPaths, opts.InitMap) } return nil } func (compiler *compiler) compile(fset *token.FileSet, astFiles []*ast.File, importpath string) (m *Module, err error) { buildctx, err := llgobuild.ContextFromTriple(compiler.TargetTriple) if err != nil { return nil, err } if compiler.Importer == nil { err = compiler.MakeImporter() if err != nil { return nil, err } } impcfg := &loader.Config{ Fset: fset, TypeChecker: types.Config{ Import: compiler.Importer, Sizes: compiler.llvmtypes, }, Build: &buildctx.Context, } // If no import path is specified, then set the import // path to be the same as the package's name. if importpath == "" { importpath = astFiles[0].Name.String() } impcfg.CreateFromFiles(importpath, astFiles...) iprog, err := impcfg.Load() if err != nil { return nil, err } program := ssa.Create(iprog, ssa.BareInits) mainPkginfo := iprog.InitialPackages()[0] mainPkg := program.CreatePackage(mainPkginfo) // Create a Module, which contains the LLVM module. modulename := importpath compiler.module = &Module{Module: llvm.NewModule(modulename), Path: modulename, Package: mainPkg.Object} compiler.module.SetTarget(compiler.TargetTriple) compiler.module.SetDataLayout(compiler.dataLayout) // Create a new translation unit. unit := newUnit(compiler, mainPkg) // Create the runtime interface. compiler.runtime, err = newRuntimeInterface(compiler.module.Module, compiler.llvmtypes) if err != nil { return nil, err } mainPkg.Build() // Create a struct responsible for mapping static types to LLVM types, // and to runtime/dynamic type values. compiler.types = NewTypeMap( mainPkg, compiler.llvmtypes, compiler.module.Module, compiler.runtime, MethodResolver(unit), ) if compiler.GenerateDebug { compiler.debug = debug.NewDIBuilder( types.Sizes(compiler.llvmtypes), compiler.module.Module, impcfg.Fset, compiler.DebugPrefixMaps, ) defer compiler.debug.Destroy() defer compiler.debug.Finalize() } unit.translatePackage(mainPkg) compiler.processAnnotations(unit, mainPkginfo) if importpath == "main" { compiler.createInitMainFunction(mainPkg) } else { compiler.module.ExportData = compiler.buildExportData(mainPkg) } return compiler.module, nil } type byPriorityThenFunc []gccgoimporter.PackageInit func (a byPriorityThenFunc) Len() int { return len(a) } func (a byPriorityThenFunc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byPriorityThenFunc) Less(i, j int) bool { switch { case a[i].Priority < a[j].Priority: return true case a[i].Priority > a[j].Priority: return false case a[i].InitFunc < a[j].InitFunc: return true default: return false } } func (c *compiler) buildPackageInitData(mainPkg *ssa.Package) gccgoimporter.InitData { var inits []gccgoimporter.PackageInit for _, imp := range mainPkg.Object.Imports() { inits = append(inits, c.InitMap[imp].Inits...) } sort.Sort(byPriorityThenFunc(inits)) // Deduplicate init entries. We want to preserve the entry with the highest priority. // Normally a package's priorities will be consistent among its dependencies, but it is // possible for them to be different. For example, if a standard library test augments a // package which is a dependency of 'regexp' (which is imported by every test main package) // with additional dependencies, those dependencies may cause the package under test to // receive a higher priority than indicated by its init clause in 'regexp'. uniqinits := make([]gccgoimporter.PackageInit, len(inits)) uniqinitpos := len(inits) uniqinitnames := make(map[string]struct{}) for i, _ := range inits { init := inits[len(inits)-1-i] if _, ok := uniqinitnames[init.InitFunc]; !ok { uniqinitnames[init.InitFunc] = struct{}{} uniqinitpos-- uniqinits[uniqinitpos] = init } } uniqinits = uniqinits[uniqinitpos:] ourprio := 1 if len(uniqinits) != 0 { ourprio = uniqinits[len(uniqinits)-1].Priority + 1 } if imp := mainPkg.Func("init"); imp != nil { impname := c.types.mc.mangleFunctionName(imp) uniqinits = append(uniqinits, gccgoimporter.PackageInit{mainPkg.Object.Name(), impname, ourprio}) } return gccgoimporter.InitData{ourprio, uniqinits} } func (c *compiler) createInitMainFunction(mainPkg *ssa.Package) { ftyp := llvm.FunctionType(llvm.VoidType(), nil, false) initMain := llvm.AddFunction(c.module.Module, "__go_init_main", ftyp) c.addCommonFunctionAttrs(initMain) entry := llvm.AddBasicBlock(initMain, "entry") builder := llvm.GlobalContext().NewBuilder() defer builder.Dispose() builder.SetInsertPointAtEnd(entry) if !c.GccgoABI { initfn := c.module.Module.NamedFunction("main..import") if !initfn.IsNil() { builder.CreateCall(initfn, nil, "") } builder.CreateRetVoid() return } initdata := c.buildPackageInitData(mainPkg) for _, init := range initdata.Inits { initfn := c.module.Module.NamedFunction(init.InitFunc) if initfn.IsNil() { initfn = llvm.AddFunction(c.module.Module, init.InitFunc, ftyp) } builder.CreateCall(initfn, nil, "") } builder.CreateRetVoid() } func (c *compiler) buildExportData(mainPkg *ssa.Package) []byte { exportData := importer.ExportData(mainPkg.Object) b := bytes.NewBuffer(exportData) b.WriteString("v1;\n") if !c.GccgoABI { return b.Bytes() } initdata := c.buildPackageInitData(mainPkg) b.WriteString("priority ") b.WriteString(strconv.Itoa(initdata.Priority)) b.WriteString(";\n") if len(initdata.Inits) != 0 { b.WriteString("init") for _, init := range initdata.Inits { b.WriteRune(' ') b.WriteString(init.Name) b.WriteRune(' ') b.WriteString(init.InitFunc) b.WriteRune(' ') b.WriteString(strconv.Itoa(init.Priority)) } b.WriteString(";\n") } return b.Bytes() } // vim: set ft=go :