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- -> 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") }