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).
Этот коммит содержится в:
родитель
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,17 +178,14 @@ 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)...)
|
||||
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", path, err}
|
||||
return &commandError{"failed to build", file, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = link(config.Target.Linker, ldflags...)
|
||||
|
|
|
@ -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{
|
||||
// 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 llvm.TargetMachine{}, err
|
||||
}
|
||||
features := strings.Join(config.Features(), ",")
|
||||
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(),
|
||||
}
|
||||
|
||||
target, err := llvm.GetTargetFromTriple(config.Triple())
|
||||
if err != nil {
|
||||
return nil, 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()
|
||||
|
||||
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 {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче