diff --git a/internal/build/build.go b/internal/build/build.go index ae14ce01c8..984c6ef396 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -273,9 +273,14 @@ func Do(args []string, conf *Config) ([]Package, error) { llssa.Initialize(llssa.InitAll) target := &llssa.Target{ - GOOS: conf.Goos, - GOARCH: conf.Goarch, - Target: conf.Target, + GOOS: conf.Goos, + GOARCH: conf.Goarch, + Target: conf.Target, + Triple: export.LLVMTarget, + CPU: export.CPU, + Features: export.Features, + CodeModel: export.CodeModel, + RelocationModel: export.RelocationModel, } prog := llssa.NewProgram(target) @@ -999,7 +1004,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa methodByName: methodByName, abiSymbols: linkedModuleGlobals(linkedOrder), }) - entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, []byte(entryPkg.LPkg.String())) + entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, entryPkg.LPkg.Module()) if err != nil { return err } @@ -1088,11 +1093,10 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose for _, objFile := range objFiles { if strings.HasSuffix(objFile, ".ll") { oFile := strings.TrimSuffix(objFile, ".ll") + ".o" - args := []string{"-o", oFile, "-c", objFile, "-Wno-override-module"} if printCmds { - fmt.Fprintln(os.Stderr, "clang", args) + fmt.Fprintf(os.Stderr, "# compiling %s from IR %s\n", oFile, objFile) } - if err := ctx.compiler().Compile(args...); err != nil { + if err := ctx.emitIRFileObject("link", objFile, oFile); err != nil { return fmt.Errorf("failed to compile %s: %v", objFile, err) } compiledObjFiles = append(compiledObjFiles, oFile) @@ -1292,7 +1296,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { aPkg.LinkArgs = append(aPkg.LinkArgs, goCgoLinkArgs(ctx.buildConf.Goos, aPkg.AltPkg.Syntax)...) } if pkg.ExportFile != "" { - exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String())) + exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, ret.Module()) if err != nil { return fmt.Errorf("export object of %v failed: %v", pkgPath, err) } @@ -1304,49 +1308,26 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { return nil } -func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) (string, error) { +func exportObject(ctx *context, pkgPath string, exportFile string, mod gllvm.Module) (string, error) { base := filepath.Base(exportFile) - f, err := os.CreateTemp("", base+"-*.ll") - if err != nil { - return "", err - } - if _, err := f.Write(data); err != nil { - f.Close() - return "", err - } - err = f.Close() - if err != nil { - return exportFile, err - } - if ctx.buildConf.CheckLLFiles { - if msg, err := llcCheck(ctx.env, f.Name()); err != nil { - fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", pkgPath, f.Name(), msg) - } - } - // If GenLL is enabled, keep a copy of the .ll file for debugging - if ctx.buildConf.GenLL { - llFile := exportFile + ".ll" - if err := os.Chmod(f.Name(), 0644); err != nil { - return "", err - } - // Copy instead of rename so we can still compile to .o - if err := copyFileAtomic(f.Name(), llFile); err != nil { + if ctx.buildConf.GenLL || ctx.buildConf.CheckLLFiles { + if err := ctx.writeModuleIR(base, exportFile, mod); err != nil { return "", err } } - // Always compile .ll to .o for linking objFile, err := os.CreateTemp("", base+"-*.o") if err != nil { return "", err } objFile.Close() - args := []string{"-o", objFile.Name(), "-c", f.Name(), "-Wno-override-module"} if ctx.shouldPrintCommands(false) { - fmt.Fprintf(os.Stderr, "# compiling %s for pkg: %s\n", f.Name(), pkgPath) - fmt.Fprintln(os.Stderr, "clang", args) + fmt.Fprintf(os.Stderr, "# emitting %s for pkg: %s via LLVM\n", objFile.Name(), pkgPath) } - cmd := ctx.compiler() - return objFile.Name(), cmd.Compile(args...) + if err := ctx.emitModuleObject(pkgPath, mod, objFile.Name()); err != nil { + os.Remove(objFile.Name()) + return "", err + } + return objFile.Name(), nil } func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) { diff --git a/internal/build/llvm_emit.go b/internal/build/llvm_emit.go new file mode 100644 index 0000000000..3e98362131 --- /dev/null +++ b/internal/build/llvm_emit.go @@ -0,0 +1,217 @@ +package build + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + llssa "github.com/goplus/llgo/ssa" + gllvm "github.com/goplus/llvm" +) + +type llvmCompileConfig struct { + target llssa.Target + passPipeline string +} + +func (c *context) llvmCompileConfig() llvmCompileConfig { + cfg := llvmCompileConfig{} + if base := c.prog.Target(); base != nil { + cfg.target = *base + } + if cfg.target.Triple == "" && c.crossCompile.LLVMTarget != "" { + cfg.target.Triple = c.crossCompile.LLVMTarget + } + if cfg.target.CPU == "" && c.crossCompile.CPU != "" { + cfg.target.CPU = c.crossCompile.CPU + } + if cfg.target.Features == "" && c.crossCompile.Features != "" { + cfg.target.Features = c.crossCompile.Features + } + if cfg.target.CodeModel == "" && c.crossCompile.CodeModel != "" { + cfg.target.CodeModel = c.crossCompile.CodeModel + } + if cfg.target.RelocationModel == "" && c.crossCompile.RelocationModel != "" { + cfg.target.RelocationModel = c.crossCompile.RelocationModel + } + + flags := c.compiler().CompilerFlags() + parseLLVMCompileFlags(&cfg, flags) + if cfg.target.RelocationModel == "" { + cfg.target.RelocationModel = defaultLLVMRelocationModel(cfg.target) + } + return cfg +} + +func defaultLLVMRelocationModel(target llssa.Target) string { + switch target.GOOS { + case "linux", "android", "darwin", "ios", "freebsd", "netbsd", "openbsd": + if target.GOARCH != "wasm" { + return "pic" + } + } + return "" +} + +func parseLLVMCompileFlags(cfg *llvmCompileConfig, flags []string) { + for i := 0; i < len(flags); i++ { + arg := flags[i] + switch { + case arg == "-O0": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelNone + case arg == "-O1" || arg == "-O": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelLess + case arg == "-O2": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault + case arg == "-O3": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelAggressive + case arg == "-Os": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault + case arg == "-Oz": + cfg.passPipeline = "default" + cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault + case arg == "-target" || arg == "--target": + if i+1 < len(flags) { + i++ + cfg.target.Triple = flags[i] + } + case strings.HasPrefix(arg, "-target="): + cfg.target.Triple = strings.TrimPrefix(arg, "-target=") + case strings.HasPrefix(arg, "--target="): + cfg.target.Triple = strings.TrimPrefix(arg, "--target=") + case arg == "-mcpu" || arg == "-march" || arg == "-mmcu": + if i+1 < len(flags) { + i++ + cfg.target.CPU = flags[i] + } + case strings.HasPrefix(arg, "-mcpu="): + cfg.target.CPU = strings.TrimPrefix(arg, "-mcpu=") + case strings.HasPrefix(arg, "-march="): + cfg.target.CPU = strings.TrimPrefix(arg, "-march=") + case strings.HasPrefix(arg, "-mmcu="): + cfg.target.CPU = strings.TrimPrefix(arg, "-mmcu=") + case arg == "-mattr": + if i+1 < len(flags) { + i++ + cfg.target.Features = flags[i] + } + case strings.HasPrefix(arg, "-mattr="): + cfg.target.Features = strings.TrimPrefix(arg, "-mattr=") + case arg == "-mcmodel": + if i+1 < len(flags) { + i++ + cfg.target.CodeModel = flags[i] + } + case strings.HasPrefix(arg, "-mcmodel="): + cfg.target.CodeModel = strings.TrimPrefix(arg, "-mcmodel=") + case arg == "-fPIC": + cfg.target.RelocationModel = "pic" + case arg == "-fno-pic": + cfg.target.RelocationModel = "static" + case arg == "-mllvm": + if i+1 < len(flags) { + i++ + parseLLVMBackendFlag(cfg, flags[i]) + } + case strings.HasPrefix(arg, "-mllvm="): + parseLLVMBackendFlag(cfg, strings.TrimPrefix(arg, "-mllvm=")) + } + } +} + +func parseLLVMBackendFlag(cfg *llvmCompileConfig, arg string) { + switch { + case strings.HasPrefix(arg, "-mcpu="): + cfg.target.CPU = strings.TrimPrefix(arg, "-mcpu=") + case strings.HasPrefix(arg, "-mattr="): + cfg.target.Features = strings.TrimPrefix(arg, "-mattr=") + } +} + +func (c *context) emitModuleObject(pkgPath string, mod gllvm.Module, objPath string) error { + cfg := c.llvmCompileConfig() + + mod.SetTarget(cfg.target.Spec().Triple) + td, tm := cfg.target.CreateTargetMachine() + defer td.Dispose() + defer tm.Dispose() + mod.SetDataLayout(td.String()) + if cfg.passPipeline != "" { + pbo := gllvm.NewPassBuilderOptions() + defer pbo.Dispose() + if err := mod.RunPasses(cfg.passPipeline, tm, pbo); err != nil { + return fmt.Errorf("%s: run LLVM passes %q: %w", pkgPath, cfg.passPipeline, err) + } + } + + buf, err := tm.EmitToMemoryBuffer(mod, gllvm.ObjectFile) + if err != nil { + return fmt.Errorf("%s: emit object: %w", pkgPath, err) + } + defer buf.Dispose() + + if err := os.WriteFile(objPath, buf.Bytes(), 0644); err != nil { + return fmt.Errorf("%s: write object %s: %w", pkgPath, objPath, err) + } + return nil +} + +func (c *context) emitIRFileObject(pkgPath string, llPath string, objPath string) error { + buf, err := gllvm.NewMemoryBufferFromFile(llPath) + if err != nil { + return fmt.Errorf("%s: open IR file %s: %w", pkgPath, llPath, err) + } + + ctx := gllvm.NewContext() + defer ctx.Dispose() + + mod, err := ctx.ParseIR(buf) + if err != nil { + return fmt.Errorf("%s: parse IR file %s: %w", pkgPath, llPath, err) + } + defer mod.Dispose() + + return c.emitModuleObject(pkgPath, mod, objPath) +} + +func (c *context) writeModuleIR(base string, exportFile string, mod gllvm.Module) error { + ll := mod.String() + + tmp, err := os.CreateTemp("", base+"-*.ll") + if err != nil { + return err + } + tmpPath := tmp.Name() + if _, err := tmp.WriteString(ll); err != nil { + tmp.Close() + os.Remove(tmpPath) + return err + } + if err := tmp.Close(); err != nil { + os.Remove(tmpPath) + return err + } + defer os.Remove(tmpPath) + + if c.buildConf.CheckLLFiles { + if msg, err := llcCheck(c.env, tmpPath); err != nil { + fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", filepath.Base(exportFile), tmpPath, msg) + } + } + if c.buildConf.GenLL { + llFile := exportFile + ".ll" + if err := os.Chmod(tmpPath, 0644); err != nil { + return err + } + if err := copyFileAtomic(tmpPath, llFile); err != nil { + return err + } + } + return nil +} diff --git a/internal/build/plan9asm.go b/internal/build/plan9asm.go index a3fa34633e..e38b690e73 100644 --- a/internal/build/plan9asm.go +++ b/internal/build/plan9asm.go @@ -77,35 +77,13 @@ func compilePkgSFiles(ctx *context, aPkg *aPackage, pkg *packages.Package, verbo if pkg.PkgPath != "runtime" { ctx.cTransformer.TransformModule(pkg.PkgPath, mod) } - ll := mod.String() - mod.Dispose() - baseName := aPkg.ExportFile + filepath.Base(sfile) // used for stable debug output paths tmpPrefix := "plan9asm-" + filepath.Base(sfile) + "-" - llFile, err := os.CreateTemp("", tmpPrefix+"*.ll") - if err != nil { - return nil, fmt.Errorf("%s: create temp .ll for %s: %w", pkg.PkgPath, sfile, err) - } - llPath := llFile.Name() - if _, err := llFile.WriteString(ll); err != nil { - llFile.Close() - os.Remove(llPath) - return nil, fmt.Errorf("%s: write temp .ll for %s: %w", pkg.PkgPath, sfile, err) - } - if err := llFile.Close(); err != nil { - os.Remove(llPath) - return nil, fmt.Errorf("%s: close temp .ll for %s: %w", pkg.PkgPath, sfile, err) - } - defer os.Remove(llPath) - // Keep a copy of translated .ll when GenLL is enabled (mirrors clFile/exportObject). - if ctx.buildConf.GenLL { - dst := baseName + ".ll" - if err := os.Chmod(llPath, 0644); err != nil { - return nil, fmt.Errorf("%s: chmod temp .ll for %s: %w", pkg.PkgPath, sfile, err) - } - if err := copyFileAtomic(llPath, dst); err != nil { + if ctx.buildConf.GenLL || ctx.buildConf.CheckLLFiles { + if err := ctx.writeModuleIR(tmpPrefix, baseName, mod); err != nil { + mod.Dispose() return nil, fmt.Errorf("%s: keep .ll for %s: %w", pkg.PkgPath, sfile, err) } } @@ -117,15 +95,15 @@ func compilePkgSFiles(ctx *context, aPkg *aPackage, pkg *packages.Package, verbo objPath := objFile.Name() objFile.Close() - args := []string{"-o", objPath, "-c", llPath, "-Wno-override-module"} if ctx.shouldPrintCommands(verbose) { - fmt.Fprintf(os.Stderr, "# compiling %s for pkg: %s\n", objPath, pkg.PkgPath) - fmt.Fprintln(os.Stderr, "clang", args) + fmt.Fprintf(os.Stderr, "# emitting %s for pkg: %s via LLVM\n", objPath, pkg.PkgPath) } - if err := ctx.compiler().Compile(args...); err != nil { + if err := ctx.emitModuleObject(pkg.PkgPath, mod, objPath); err != nil { + mod.Dispose() os.Remove(objPath) - return nil, fmt.Errorf("%s: clang compile asm ll for %s: %w", pkg.PkgPath, sfile, err) + return nil, fmt.Errorf("%s: emit asm object for %s: %w", pkg.PkgPath, sfile, err) } + mod.Dispose() objFiles = append(objFiles, objPath) } diff --git a/internal/cabi/cabi.go b/internal/cabi/cabi.go index 35382f8851..cdadcea879 100644 --- a/internal/cabi/cabi.go +++ b/internal/cabi/cabi.go @@ -617,7 +617,7 @@ func (p *Transformer) transformCallInstr(m llvm.Module, ctx llvm.Context, call l updateCallAttr(instr) } call.ReplaceAllUsesWith(instr) - call.RemoveFromParentAsInstruction() + call.EraseFromParentAsInstruction() return true } diff --git a/internal/clang/clang.go b/internal/clang/clang.go index 48ae7cb295..4a4efbb360 100644 --- a/internal/clang/clang.go +++ b/internal/clang/clang.go @@ -103,6 +103,11 @@ func (c *Cmd) Compile(args ...string) error { return c.exec(allArgs...) } +// CompilerFlags returns the merged compiler flags that would be used by Compile. +func (c *Cmd) CompilerFlags() []string { + return c.mergeCompilerFlags() +} + // Link executes a linking command with merged flags. func (c *Cmd) Link(args ...string) error { flags := c.mergeLinkerFlags() diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 0248b603d1..52b8a0cadb 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -34,11 +34,15 @@ type Export struct { ClangRoot string // Root directory of custom clang installation ClangBinPath string // Path to clang binary directory - LLVMTarget string // LLVM Target - TargetABI string // RISC-V Target ABI (e.g., "lp64", "lp64d") - BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2") - FormatDetail string // For uf2, it's uf2FamilyID - Emulator string // Emulator command template (e.g., "qemu-system-arm -M {} -kernel {}") + LLVMTarget string // LLVM Target + CPU string // Target CPU + Features string // Target feature string + TargetABI string // RISC-V Target ABI (e.g., "lp64", "lp64d") + CodeModel string // LLVM code model + RelocationModel string // LLVM relocation model + BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2") + FormatDetail string // For uf2, it's uf2FamilyID + Emulator string // Emulator command template (e.g., "qemu-system-arm -M {} -kernel {}") // Flashing/Debugging configuration Device flash.Device // Device configuration for flashing/debugging @@ -458,7 +462,11 @@ func UseTarget(targetName string) (export Export, err error) { export.GOARCH = config.GOARCH export.ExtraFiles = config.ExtraFiles export.LLVMTarget = config.LLVMTarget + export.CPU = config.CPU + export.Features = config.Features export.TargetABI = config.TargetABI + export.CodeModel = config.CodeModel + export.RelocationModel = config.RelocationModel export.BinaryFormat = config.BinaryFormat export.FormatDetail = config.FormatDetail() export.Emulator = config.Emulator diff --git a/ssa/target.go b/ssa/target.go index d7477e0eea..2aa1fb5771 100644 --- a/ssa/target.go +++ b/ssa/target.go @@ -25,10 +25,16 @@ import ( // ----------------------------------------------------------------------------- type Target struct { - GOOS string - GOARCH string - GOARM string // "5", "6", "7" (default) - Target string // target name from -target flag (e.g., "esp32", "arm7tdmi", "wasi") + GOOS string + GOARCH string + GOARM string // "5", "6", "7" (default) + Target string // target name from -target flag (e.g., "esp32", "arm7tdmi", "wasi") + Triple string + CPU string + Features string + CodeGenLevel llvm.CodeGenOptLevel + RelocationModel string + CodeModel string } func (p *Target) targetInfo() (llvm.TargetData, llvm.TargetMachine) { @@ -40,10 +46,18 @@ func (p *Target) targetInfo() (llvm.TargetData, llvm.TargetMachine) { if err != nil { panic(err) } - machine := t.CreateTargetMachine(spec.Triple, spec.CPU, spec.Features, llvm.CodeGenLevelDefault, llvm.RelocDefault, llvm.CodeModelDefault) + optLevel := p.CodeGenLevel + if optLevel == 0 { + optLevel = llvm.CodeGenLevelDefault + } + machine := t.CreateTargetMachine(spec.Triple, spec.CPU, spec.Features, optLevel, p.relocMode(), p.codeModel()) return machine.CreateTargetData(), machine } +func (p *Target) CreateTargetMachine() (llvm.TargetData, llvm.TargetMachine) { + return p.targetInfo() +} + type TargetSpec struct { Triple string CPU string @@ -51,6 +65,12 @@ type TargetSpec struct { } func (p *Target) Spec() (spec TargetSpec) { + if p.Triple != "" { + spec.Triple = p.Triple + spec.CPU = p.CPU + spec.Features = p.Features + return + } // Configure based on GOOS/GOARCH environment variables (falling back to // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. var llvmarch string @@ -137,7 +157,39 @@ func (p *Target) Spec() (spec TargetSpec) { spec.CPU = "generic" spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } + if p.CPU != "" { + spec.CPU = p.CPU + } + if p.Features != "" { + spec.Features = p.Features + } return } +func (p *Target) relocMode() llvm.RelocMode { + switch p.RelocationModel { + case "pic": + return llvm.RelocPIC + case "static": + return llvm.RelocStatic + default: + return llvm.RelocDefault + } +} + +func (p *Target) codeModel() llvm.CodeModel { + switch p.CodeModel { + case "small": + return llvm.CodeModelSmall + case "kernel": + return llvm.CodeModelKernel + case "medium": + return llvm.CodeModelMedium + case "large": + return llvm.CodeModelLarge + default: + return llvm.CodeModelDefault + } +} + // -----------------------------------------------------------------------------