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

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

@ -56,12 +56,6 @@ type compilerContext struct {
astComments map[string]*ast.CommentGroup 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. // builder contains all information relevant to build a single function.
type builder struct { type builder struct {
*compilerContext *compilerContext
@ -88,28 +82,40 @@ type Phi struct {
llvm llvm.Value llvm llvm.Value
} }
func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) { // NewTargetMachine returns a new llvm.TargetMachine based on the passed-in
c := &Compiler{ // configuration. It is used by the compiler and is needed for machine code
compilerContext: compilerContext{ // emission.
Config: config, func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
},
}
target, err := llvm.GetTargetFromTriple(config.Triple()) target, err := llvm.GetTargetFromTriple(config.Triple())
if err != nil { if err != nil {
return nil, err return llvm.TargetMachine{}, err
} }
features := strings.Join(config.Features(), ",") features := strings.Join(config.Features(), ",")
c.machine = target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault) machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
c.targetData = c.machine.CreateTargetData() 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.ctx = llvm.NewContext()
c.mod = c.ctx.NewModule(pkgName) c.mod = c.ctx.NewModule(pkgName)
c.mod.SetTarget(config.Triple()) c.mod.SetTarget(config.Triple())
c.mod.SetDataLayout(c.targetData.String()) c.mod.SetDataLayout(c.targetData.String())
c.builder = c.ctx.NewBuilder()
if c.Debug() { if c.Debug() {
c.dibuilder = llvm.NewDIBuilder(c.mod) c.dibuilder = llvm.NewDIBuilder(c.mod)
} }
@ -131,21 +137,6 @@ func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction() 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 // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
// the TinyGo root. // the TinyGo root.
overlayGopath := goenv.Get("GOPATH") overlayGopath := goenv.Get("GOPATH")
@ -157,7 +148,7 @@ func (c *Compiler) Compile(mainPath string) []error {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return []error{err} return c.mod, nil, []error{err}
} }
lprogram := &loader.Program{ lprogram := &loader.Program{
Build: &build.Context{ Build: &build.Context{
@ -217,17 +208,17 @@ func (c *Compiler) Compile(mainPath string) []error {
ClangHeaders: c.ClangHeaders, ClangHeaders: c.ClangHeaders,
} }
if strings.HasSuffix(mainPath, ".go") { if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(mainPath) _, err = lprogram.ImportFile(pkgName)
if err != nil { if err != nil {
return []error{err} return c.mod, nil, []error{err}
} }
} else { } else {
_, err = lprogram.Import(mainPath, wd, token.Position{ _, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments", Filename: "build command-line-arguments",
}) })
if err != nil { 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", Filename: "build default import",
}) })
if err != nil { if err != nil {
return []error{err} return c.mod, nil, []error{err}
} }
err = lprogram.Parse(c.TestConfig.CompileTestBinary) err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil { 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. // Run a simple dead code elimination pass.
c.ir.SimpleDCE() c.ir.SimpleDCE()
@ -252,7 +243,7 @@ func (c *Compiler) Compile(mainPath string) []error {
if c.Debug() { if c.Debug() {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
File: mainPath, File: pkgName,
Dir: "", Dir: "",
Producer: "TinyGo", Producer: "TinyGo",
Optimized: true, Optimized: true,
@ -281,9 +272,12 @@ func (c *Compiler) Compile(mainPath string) []error {
} }
// Add definitions to declarations. // Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
for _, f := range c.ir.Functions { for _, f := range c.ir.Functions {
if f.Synthetic == "package initializer" { if f.Synthetic == "package initializer" {
c.initFuncs = append(c.initFuncs, f.LLVMFn) initFuncs = append(initFuncs, f.LLVMFn)
} }
if f.CName() != "" { if f.CName() != "" {
continue continue
@ -294,8 +288,8 @@ func (c *Compiler) Compile(mainPath string) []error {
// Create the function definition. // Create the function definition.
b := builder{ b := builder{
compilerContext: &c.compilerContext, compilerContext: c,
Builder: c.builder, Builder: irbuilder,
fn: f, fn: f,
locals: make(map[ssa.Value]llvm.Value), locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata), dilocals: make(map[*types.Var]llvm.Metadata),
@ -313,14 +307,14 @@ func (c *Compiler) Compile(mainPath string) []error {
if c.Debug() { if c.Debug() {
difunc := c.attachDebugInfo(initFn) difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos()) 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") block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
c.builder.SetInsertPointAtEnd(block) irbuilder.SetInsertPointAtEnd(block)
for _, fn := range c.initFuncs { for _, fn := range initFuncs {
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") 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 // Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away. // would be optimized away.
@ -392,7 +386,15 @@ func (c *Compiler) Compile(mainPath string) []error {
c.dibuilder.Finalize() 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 // 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") 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 // 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. // 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{} c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() { for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files { for _, file := range pkgInfo.Files {