-
Notifications
You must be signed in to change notification settings - Fork 47
build: emit LLVM objects directly from modules #1761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<O0>" | ||
| cfg.target.CodeGenLevel = gllvm.CodeGenLevelNone | ||
| case arg == "-O1" || arg == "-O": | ||
| cfg.passPipeline = "default<O1>" | ||
| cfg.target.CodeGenLevel = gllvm.CodeGenLevelLess | ||
| case arg == "-O2": | ||
| cfg.passPipeline = "default<O2>" | ||
| cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault | ||
| case arg == "-O3": | ||
| cfg.passPipeline = "default<O3>" | ||
| cfg.target.CodeGenLevel = gllvm.CodeGenLevelAggressive | ||
| case arg == "-Os": | ||
| cfg.passPipeline = "default<Os>" | ||
| cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault | ||
| case arg == "-Oz": | ||
| cfg.passPipeline = "default<Oz>" | ||
| 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() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| mod.SetTarget(cfg.target.Spec().Triple) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Consider using |
||
| 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() | ||
|
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The explicit calls to |
||
| objFiles = append(objFiles, objPath) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
-marchspecifies a target architecture (e.g.armv7-a,haswell) while-mcpuspecifies a CPU model — these are distinct concepts in LLVM. Mapping both tocfg.target.CPUconflates them. LLVM'sCreateTargetMachineCPU parameter expects a CPU name, not an architecture string. Passing an architecture string may silently produce suboptimal code on some targets.