compiler: refactor public interface

This commit merges NewCompiler and Compile into one simplifying the
external interface. More importantly, it does away with the entire
Compiler object so the public API becomes a lot smaller.

The refactor is not complete: eventually, the compiler should just
compile a single package without trying to load it first (that should be
done by the builder package).
Этот коммит содержится в:
Ayke van Laethem 2020-03-18 21:35:12 +01:00 коммит произвёл Ron Evans
родитель 8ef921e028
коммит c4fd19be99
3 изменённых файлов: 96 добавлений и 130 удалений

Просмотреть файл

@ -17,6 +17,7 @@ import (
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Build performs a single package to executable Go build. It takes in a package
@ -26,34 +27,34 @@ import (
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
c, err := compiler.NewCompiler(pkgName, config)
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
// Compile Go code to IR.
errs := c.Compile(pkgName)
if len(errs) != 0 {
mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
fmt.Println(mod.String())
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(c.Module(), config.DumpSSA())
err = interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
transform.ApplyFunctionSections(c.Module()) // -ffunction-sections
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Browsers cannot handle external functions that have type i64 because it
@ -62,7 +63,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
err := transform.ExternalInt64AsPtr(c.Module())
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
}
@ -73,22 +74,22 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
errs = nil
switch config.Options.Opt {
case "none", "0":
errs = transform.Optimize(c.Module(), config, 0, 0, 0) // -O0
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = transform.Optimize(c.Module(), config, 1, 0, 0) // -O1
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = transform.Optimize(c.Module(), config, 2, 0, 225) // -O2
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = transform.Optimize(c.Module(), config, 2, 1, 225) // -Os
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = transform.Optimize(c.Module(), config, 2, 2, 5) // -Oz, default
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
}
@ -98,8 +99,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// pointers are flash and which are in RAM so that pointers can have a
// correct address space parameter (address space 1 is for flash).
if strings.HasPrefix(config.Triple(), "avr") {
transform.NonConstGlobals(c.Module())
if err := c.Verify(); err != nil {
transform.NonConstGlobals(mod)
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after making all globals non-constant on AVR")
}
}
@ -108,11 +109,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
outext := filepath.Ext(outpath)
switch outext {
case ".o":
return c.EmitObject(outpath)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
return c.EmitBitcode(outpath)
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
return c.EmitText(outpath)
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
@ -125,7 +132,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// Write the object file.
objfile := filepath.Join(dir, "main.o")
err = c.EmitObject(objfile)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
@ -167,16 +178,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
}
// Compile C files in packages.
for i, pkg := range c.Packages() {
for _, file := range pkg.CFiles {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
for i, file := range extraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
ldflags = append(ldflags, outpath)
}
// Link the object files together.

Просмотреть файл

@ -56,12 +56,6 @@ type compilerContext struct {
astComments map[string]*ast.CommentGroup
}
type Compiler struct {
compilerContext
builder llvm.Builder
initFuncs []llvm.Value
}
// builder contains all information relevant to build a single function.
type builder struct {
*compilerContext
@ -88,28 +82,40 @@ type Phi struct {
llvm llvm.Value
}
func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) {
c := &Compiler{
compilerContext: compilerContext{
Config: config,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
},
}
// NewTargetMachine returns a new llvm.TargetMachine based on the passed-in
// configuration. It is used by the compiler and is needed for machine code
// emission.
func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
target, err := llvm.GetTargetFromTriple(config.Triple())
if err != nil {
return nil, err
return llvm.TargetMachine{}, err
}
features := strings.Join(config.Features(), ",")
c.machine = target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
c.targetData = c.machine.CreateTargetData()
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
return machine, nil
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage). If successful it returns the LLVM module and a list of
// extra C files to be compiled. If not, one or more errors will be returned.
//
// The fact that it returns a list of filenames to compile is a layering
// violation. Eventually, this Compile function should only compile a single
// package and not the whole program, and loading of the program (including CGo
// processing) should be moved outside the compiler package.
func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) {
c := &compilerContext{
Config: config,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
}
c.ctx = llvm.NewContext()
c.mod = c.ctx.NewModule(pkgName)
c.mod.SetTarget(config.Triple())
c.mod.SetDataLayout(c.targetData.String())
c.builder = c.ctx.NewBuilder()
if c.Debug() {
c.dibuilder = llvm.NewDIBuilder(c.mod)
}
@ -131,21 +137,6 @@ func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction()
return c, nil
}
func (c *Compiler) Packages() []*loader.Package {
return c.ir.LoaderProgram.Sorted()
}
// Return the LLVM module. Only valid after a successful compile.
func (c *Compiler) Module() llvm.Module {
return c.mod
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage).
func (c *Compiler) Compile(mainPath string) []error {
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
// the TinyGo root.
overlayGopath := goenv.Get("GOPATH")
@ -157,7 +148,7 @@ func (c *Compiler) Compile(mainPath string) []error {
wd, err := os.Getwd()
if err != nil {
return []error{err}
return c.mod, nil, []error{err}
}
lprogram := &loader.Program{
Build: &build.Context{
@ -217,17 +208,17 @@ func (c *Compiler) Compile(mainPath string) []error {
ClangHeaders: c.ClangHeaders,
}
if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath)
if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(pkgName)
if err != nil {
return []error{err}
return c.mod, nil, []error{err}
}
} else {
_, err = lprogram.Import(mainPath, wd, token.Position{
_, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments",
})
if err != nil {
return []error{err}
return c.mod, nil, []error{err}
}
}
@ -235,15 +226,15 @@ func (c *Compiler) Compile(mainPath string) []error {
Filename: "build default import",
})
if err != nil {
return []error{err}
return c.mod, nil, []error{err}
}
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil {
return []error{err}
return c.mod, nil, []error{err}
}
c.ir = ir.NewProgram(lprogram, mainPath)
c.ir = ir.NewProgram(lprogram, pkgName)
// Run a simple dead code elimination pass.
c.ir.SimpleDCE()
@ -252,7 +243,7 @@ func (c *Compiler) Compile(mainPath string) []error {
if c.Debug() {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
File: mainPath,
File: pkgName,
Dir: "",
Producer: "TinyGo",
Optimized: true,
@ -281,9 +272,12 @@ func (c *Compiler) Compile(mainPath string) []error {
}
// Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
for _, f := range c.ir.Functions {
if f.Synthetic == "package initializer" {
c.initFuncs = append(c.initFuncs, f.LLVMFn)
initFuncs = append(initFuncs, f.LLVMFn)
}
if f.CName() != "" {
continue
@ -294,8 +288,8 @@ func (c *Compiler) Compile(mainPath string) []error {
// Create the function definition.
b := builder{
compilerContext: &c.compilerContext,
Builder: c.builder,
compilerContext: c,
Builder: irbuilder,
fn: f,
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
@ -313,14 +307,14 @@ func (c *Compiler) Compile(mainPath string) []error {
if c.Debug() {
difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos())
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
c.builder.SetInsertPointAtEnd(block)
for _, fn := range c.initFuncs {
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
}
c.builder.CreateRetVoid()
irbuilder.CreateRetVoid()
// Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away.
@ -392,7 +386,15 @@ func (c *Compiler) Compile(mainPath string) []error {
c.dibuilder.Finalize()
}
return c.diagnostics
// Gather the list of (C) file paths that should be included in the build.
var extraFiles []string
for _, pkg := range c.ir.LoaderProgram.Sorted() {
for _, file := range pkg.CFiles {
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
}
}
return c.mod, extraFiles, c.diagnostics
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
@ -2577,47 +2579,3 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
return llvm.Value{}, b.makeError(unop.Pos(), "todo: unknown unop")
}
}
// IR returns the whole IR as a human-readable string.
func (c *Compiler) IR() string {
return c.mod.String()
}
func (c *Compiler) Verify() error {
return llvm.VerifyModule(c.mod, llvm.PrintMessageAction)
}
// Emit object file (.o).
func (c *Compiler) EmitObject(path string) error {
llvmBuf, err := c.machine.EmitToMemoryBuffer(c.mod, llvm.ObjectFile)
if err != nil {
return err
}
return c.writeFile(llvmBuf.Bytes(), path)
}
// Emit LLVM bitcode file (.bc).
func (c *Compiler) EmitBitcode(path string) error {
data := llvm.WriteBitcodeToMemoryBuffer(c.mod).Bytes()
return c.writeFile(data, path)
}
// Emit LLVM IR source file (.ll).
func (c *Compiler) EmitText(path string) error {
data := []byte(c.mod.String())
return c.writeFile(data, path)
}
// Write the data to the file specified by path.
func (c *Compiler) writeFile(data []byte, path string) error {
// Write output to file
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
_, err = f.Write(data)
if err != nil {
return err
}
return f.Close()
}

Просмотреть файл

@ -26,7 +26,7 @@ type globalInfo struct {
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {