fspl/compiler/compile-unit.go

166 lines
4.6 KiB
Go

package compiler
import "os"
import "fmt"
import "errors"
import "os/exec"
import "path/filepath"
import "git.tebibyte.media/fspl/fspl/llvm"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/analyzer"
import "git.tebibyte.media/fspl/fspl/generator/native"
func (this *Compiler) CompileUnit (address entity.Address) error {
this.Debugln("using search path", this.Resolver.Path)
path, err := this.ResolveCwd(address)
if err != nil { return err }
this.Debugln("compiling unit", path)
var semanticTree analyzer.Tree
_, err = this.AnalyzeUnit(&semanticTree, path, false)
if err != nil { return err }
if this.Target == nil {
target := native.NativeTarget()
this.Target = &target
}
irModule, err := this.Target.Generate(semanticTree)
if err != nil {
return this.bug(err)
}
// if the format isn't specified, try to get it from the filename
// extension of the input. if that isn't specified, default to .o
if this.Filetype == FiletypeUnknown {
this.Debugln("filetype was not specified")
if this.Output == "" {
this.Filetype = FiletypeObject
} else {
var ok bool
this.Filetype, ok = FiletypeFromExt (
*this.Target,
filepath.Ext(this.Output))
if !ok {
this.Filetype = FiletypeObject
}
}
}
// if the output file is unspecified, generate a nickname from the
// input address. if that doesn't work, default to "output"
if this.Output == "" {
nickname, ok := entity.Address(path).Nickname()
if !ok { nickname = "output" }
this.Output = this.Filetype.Extend(*this.Target, nickname)
}
// do something based on the output extension
// TODO: add .so
switch this.Filetype {
case FiletypeAssembly, FiletypeObject:
return this.CompileIRModule(irModule, this.Filetype)
case FiletypeIR:
file, err := os.Create(this.Output)
if err != nil { return err }
defer file.Close()
_, err = irModule.WriteTo(file)
return err
default:
return errors.New(fmt.Sprintf (
"unknown output type %s", this.Filetype))
}
}
func (this *Compiler) CompileIRModule (module *llvm.Module, filetype Filetype) error {
this.Debugln("compiling ir module to filetype", filetype)
commandName, args, err := this.FindBackend(filetype)
if err != nil { return err }
command := exec.Command(commandName, args...)
this.Debugln("compiling ir using:", command)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
pipe, err := command.StdinPipe()
if err != nil { return err }
err = command.Start()
if err != nil { return err }
_, err = module.WriteTo(pipe)
if err != nil { return err }
pipe.Close()
return command.Wait()
}
// FindBackend returns the name of an LLVM backend command, and a list of
// arguments to pass to it. It tries commands in this order:
// - llc
// - llc-<latest> -> llc-14
// - clang
// If none were found, it returns an error.
func (this *Compiler) FindBackend (filetype Filetype) (string, []string, error) {
optimization := "0"
if this.Optimization != "" { optimization = this.Optimization }
llcArgs := []string {
"-",
fmt.Sprintf("-filetype=%s", filetype),
"-o", this.Output,
fmt.Sprintf("-O=%s", optimization),
}
// attempt to use llc command
commandName := "llc"
_, err := exec.LookPath(commandName)
this.Debugln("trying", commandName)
if err == nil {
return commandName, llcArgs, nil
}
// attempt to find a versioned llc command, counting down from the
// latest known version
llcVersion := 17 // TODO change this number to the latest llc version in
// the future
for err != nil && llcVersion >= 14 {
commandName := fmt.Sprintf("llc-%d", llcVersion)
this.Debugln("trying", commandName)
_, err = exec.LookPath(commandName)
if err == nil {
if llcVersion == 14 {
// -opaque-pointers is needed in version 14
llcArgs = append(llcArgs, "-opaque-pointers")
this.Warnln (
"using llvm llc version 14, which",
"does not have proper support for",
"opaque pointers. expect bugs")
}
return commandName, llcArgs, nil
}
llcVersion --
}
// attempt to use clang
commandName = "clang"
_, err = exec.LookPath(commandName)
this.Debugln("trying", commandName)
if err == nil {
if filetype != FiletypeObject {
return "", nil, errors.New(fmt.Sprint (
"need 'llc' to compile to ", filetype))
}
this.Warnln("falling back to clang to compile llvm ir. expect bugs")
return commandName, []string {
"-c",
"-x", "ir",
"-o", this.Output,
fmt.Sprintf("-O%s", this.Optimization),
"-",
}, nil
}
return "", nil, errors.New (
"no suitable backends found: please make sure either 'llc' " +
"or 'clang' are accessable from your PATH")
}