166 lines
4.6 KiB
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")
|
|
}
|