forked from OSchip/llvm-project
395 lines
11 KiB
Go
395 lines
11 KiB
Go
//===- compiler.go - IR generator entry point -----------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements the main IR generator entry point, (*Compiler).Compile.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
package irgen
|
|
|
|
import (
|
|
"bytes"
|
|
"go/ast"
|
|
"go/token"
|
|
"log"
|
|
"sort"
|
|
"strconv"
|
|
|
|
llgobuild "llvm.org/llgo/build"
|
|
"llvm.org/llgo/debug"
|
|
"llvm.org/llvm/bindings/go/llvm"
|
|
|
|
"llvm.org/llgo/third_party/gotools/go/gccgoimporter"
|
|
"llvm.org/llgo/third_party/gotools/go/importer"
|
|
"llvm.org/llgo/third_party/gotools/go/loader"
|
|
"llvm.org/llgo/third_party/gotools/go/ssa"
|
|
"llvm.org/llgo/third_party/gotools/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
|
|
|
|
// PackageCreated is a hook passed to the go/loader package via
|
|
// loader.Config, see the documentation for that package for more
|
|
// information.
|
|
PackageCreated func(*types.Package)
|
|
|
|
// DisableUnusedImportCheck disables the unused import check performed
|
|
// by go/types if set to true.
|
|
DisableUnusedImportCheck bool
|
|
|
|
// Packages is used by go/types as the imported package map if non-nil.
|
|
Packages map[string]*types.Package
|
|
}
|
|
|
|
type Compiler struct {
|
|
opts CompilerOptions
|
|
dataLayout string
|
|
}
|
|
|
|
func NewCompiler(opts CompilerOptions) (*Compiler, error) {
|
|
compiler := &Compiler{opts: opts}
|
|
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,
|
|
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
|
|
|
|
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 c.SanitizerAttribute.GetEnumKind() != 0 {
|
|
fn.AddFunctionAttr(c.SanitizerAttribute)
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
Packages: compiler.Packages,
|
|
Import: compiler.Importer,
|
|
Sizes: compiler.llvmtypes,
|
|
DisableUnusedImportCheck: compiler.DisableUnusedImportCheck,
|
|
},
|
|
ImportFromBinary: true,
|
|
Build: &buildctx.Context,
|
|
PackageCreated: compiler.PackageCreated,
|
|
}
|
|
// 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) {
|
|
int8ptr := llvm.PointerType(c.types.ctx.Int8Type(), 0)
|
|
ftyp := llvm.FunctionType(llvm.VoidType(), []llvm.Type{int8ptr}, 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)
|
|
|
|
args := []llvm.Value{llvm.Undef(int8ptr)}
|
|
|
|
if !c.GccgoABI {
|
|
initfn := c.module.Module.NamedFunction("main..import")
|
|
if !initfn.IsNil() {
|
|
builder.CreateCall(initfn, args, "")
|
|
}
|
|
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, args, "")
|
|
}
|
|
|
|
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 :
|